[ONOS-7732] Automating switch workflow
Change-Id: Ie047c34df2278bc2220cade744f51ca6950f48f6
diff --git a/apps/workflow/BUCK b/apps/workflow/BUCK
new file mode 100644
index 0000000..725153a
--- /dev/null
+++ b/apps/workflow/BUCK
@@ -0,0 +1,13 @@
+BUNDLES = [
+ '//apps/workflow/api:onos-apps-workflow-api',
+ '//apps/workflow/model:onos-apps-workflow-model',
+ '//apps/workflow/app:onos-apps-workflow-app',
+]
+
+onos_app(
+ category = 'Utility',
+ description = "Workflow application",
+ included_bundles = BUNDLES,
+ title = 'Workflow',
+ url = 'http://onosproject.org',
+)
diff --git a/apps/workflow/BUILD b/apps/workflow/BUILD
new file mode 100644
index 0000000..e8b6a91
--- /dev/null
+++ b/apps/workflow/BUILD
@@ -0,0 +1,13 @@
+BUNDLES = [
+ "//apps/workflow/api:onos-apps-workflow-api",
+ "//apps/workflow/model:onos-apps-workflow-model",
+ "//apps/workflow/app:onos-apps-workflow-app",
+]
+
+onos_app(
+ category = "Utility",
+ description = "Workflow application",
+ included_bundles = BUNDLES,
+ title = "Workflow",
+ url = "http://onosproject.org",
+)
diff --git a/apps/workflow/api/BUCK b/apps/workflow/api/BUCK
new file mode 100644
index 0000000..c858089
--- /dev/null
+++ b/apps/workflow/api/BUCK
@@ -0,0 +1,11 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:jackson-core',
+ '//lib:jackson-annotations',
+ '//lib:jackson-databind',
+ '//core/store/serializers:onos-core-serializers',
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/api/BUILD b/apps/workflow/api/BUILD
new file mode 100644
index 0000000..721e4b9
--- /dev/null
+++ b/apps/workflow/api/BUILD
@@ -0,0 +1,7 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + [
+ "//core/store/serializers:onos-core-serializers",
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorkflow.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorkflow.java
new file mode 100644
index 0000000..5fb8943
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorkflow.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import java.net.URI;
+
+/**
+ * Abstract class for workflow.
+ */
+public abstract class AbstractWorkflow implements Workflow {
+
+ /**
+ * ID of workflow.
+ */
+ private URI id;
+
+ /**
+ * Constructor for AbstractWorkflow.
+ * @param id ID of workflow
+ */
+ protected AbstractWorkflow(URI id) {
+ this.id = id;
+ }
+
+ @Override
+ public URI id() {
+ return id;
+ }
+
+ @Override
+ public WorkflowContext buildContext(Workplace workplace, DataModelTree data) throws WorkflowException {
+ return DefaultWorkflowContext.builder()
+ .workflowId(id)
+ .workplaceName(workplace.name())
+ .data(data)
+ .build();
+ }
+
+ @Override
+ public WorkflowContext buildSystemContext(Workplace workplace, DataModelTree data) throws WorkflowException {
+ return SystemWorkflowContext.systemBuilder()
+ .workflowId(id)
+ .workplaceName(workplace.name())
+ .data(data)
+ .build();
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorklet.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorklet.java
new file mode 100644
index 0000000..b9fb133
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorklet.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+import org.onosproject.event.Event;
+
+/**
+ * Abstract class for worklet.
+ */
+public abstract class AbstractWorklet implements Worklet {
+
+ @Override
+ public String tag() {
+ return this.getClass().getName();
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isCompleted should not be called");
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isNext should not be called");
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("Timeout happened");
+ }
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/BranchWorklet.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/BranchWorklet.java
new file mode 100644
index 0000000..4f91bba
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/BranchWorklet.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+/**
+ * An interface representing branch worklet. Branch worklet is used for branching workflow execution.
+ */
+public interface BranchWorklet extends Worklet {
+
+ @Override
+ default void process(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("This workletType.process should not be called");
+ }
+
+ @Override
+ default boolean isNext(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("This workletType.isNext should not be called");
+ }
+
+ /**
+ * Returns next worklet class for branching.
+ * @param context workflow context
+ * @return next worklet class
+ * @throws WorkflowException workflow exception
+ */
+ Class<? extends Worklet> next(WorkflowContext context) throws WorkflowException;
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/CheckCondition.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/CheckCondition.java
new file mode 100644
index 0000000..7419583
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/CheckCondition.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+/**
+ * Static convenience class that help a whether it was invoked correctly(whether its preconditions have been met).
+ * Methods of this class generally accept a boolean expression which is expected to be true.
+ * When false (or null) is passed instead, the Preconditions method throws an workflow exception.
+ */
+public final class CheckCondition {
+
+ /**
+ * Private class of check condition.
+ */
+ private CheckCondition() {
+ }
+
+ /**
+ * Checks the condition, and if it is false, it raises workflow exception with exception message.
+ * @param condition condition to check. A boolean expression is located on here.
+ * @param exceptionMessage exception message for workflow exception
+ * @throws WorkflowException workflow exception
+ */
+ public static void check(boolean condition, String exceptionMessage) throws WorkflowException {
+ if (!condition) {
+ throw new WorkflowException(exceptionMessage);
+ }
+ }
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ContextEventMapStore.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ContextEventMapStore.java
new file mode 100644
index 0000000..4b54cf3
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ContextEventMapStore.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.store.service.DocumentPath;
+import org.onosproject.store.service.Versioned;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * WorkflowContext Event Map Store.
+ */
+public interface ContextEventMapStore {
+
+ /**
+ * Registers workflow context event mapping.
+ * @param eventType the class name of event
+ * @param eventHintSet Set of event hint string value of the event
+ * @param contextName workflow context name
+ * @param programCounterString the program counter of workflow
+ * @throws WorkflowException workflow exception
+ */
+ void registerEventMap(String eventType, Set<String> eventHintSet,
+ String contextName, String programCounterString) throws WorkflowException;
+
+ /**
+ * Unregisters workflow context event mapping.
+ * @param eventType the class name of event
+ * @param contextName workflow context name
+ * @throws WorkflowException workflow exception
+ */
+ void unregisterEventMap(String eventType,
+ String contextName) throws WorkflowException;
+
+ /**
+ * Returns workflow context event mapping.
+ * @param eventType the class name of event
+ * @param eventHint vent hint string value of the event
+ * @return Map of workflow context and value (program counter)
+ * @throws WorkflowException workflow exception
+ */
+ Map<String, String> getEventMapByHint(String eventType,
+ String eventHint) throws WorkflowException;
+
+ /**
+ * Returns true or false depending on existence of eventMap of given context.
+ * @param contextName name of workflow context
+ * @return Boolean true or false depending on existence of eventMap of given context
+ */
+ boolean isEventMapPresent(String contextName);
+
+ /**
+ * Returns child nodes on document tree path.
+ * @param path document tree path including eventType and Hint
+ * @return children under document tree path
+ * @throws WorkflowException workflow exception
+ */
+ Map<String, Versioned<String>> getChildren(String path) throws WorkflowException;
+
+ /**
+ * Returns document path.
+ * @param path document path string
+ * @return document tree
+ * @throws WorkflowException workflow exception
+ */
+ DocumentPath getDocumentPath(String path) throws WorkflowException;
+
+ /**
+ * Transforms document tree to json tree.
+ * @return json tree
+ * @throws WorkflowException workflow exception
+ */
+ ObjectNode asJsonTree() throws WorkflowException;
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelPointer.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelPointer.java
new file mode 100644
index 0000000..271ce4f
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelPointer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+/**
+ * Common data model tree path.
+ */
+public interface DataModelPointer {
+
+ /**
+ * Workplace array pointer.
+ */
+ String WORKPLACES_PTR = "/workplaces";
+
+ /**
+ * Workplace name pointer.
+ */
+ String WORKPLACE_NAME_PTR = "/name";
+
+ /**
+ * Workplace data pointer.
+ */
+ String WORKPLACE_DATA_PTR = "/data";
+
+ /**
+ * Workplace workflow pointer.
+ */
+ String WORKPLACE_WORKFLOWS_PTR = "/workflows";
+
+ /**
+ * Workflow op pointer.
+ */
+ String WORKFLOW_OP_PTR = "/op";
+
+ /**
+ * Workflow id pointer.
+ */
+ String WORKFLOW_ID_PTR = "/id";
+
+ /**
+ * Workflow data pointer.
+ */
+ String WORKFLOW_DATA_PTR = "/data";
+
+ /**
+ * Gets path string.
+ * @return path string
+ */
+ String getPath();
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
new file mode 100644
index 0000000..f7ada8d
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+/**
+ * Interface for data model tree.
+ */
+public interface DataModelTree {
+
+ /**
+ * Data model tree node type (map or array).
+ */
+ enum Nodetype {
+
+ /**
+ * Map type data model tree node.
+ */
+ MAP,
+
+ /**
+ * Array type data model tree node.
+ */
+ ARRAY
+ }
+
+ /**
+ * Returns subtree on the path.
+ * @param path data model tree path
+ * @return subtree on the path
+ */
+ DataModelTree subtree(String path);
+
+ /**
+ * Attaches subtree on the path.
+ * @param path data model tree path where subtree will be attached
+ * @param tree subtree to be attached
+ * @throws WorkflowException workflow exception
+ */
+ void attach(String path, DataModelTree tree) throws WorkflowException;
+
+ /**
+ * Allocates leaf node on the path.
+ * @param path data model tree path where new leaf node will be allocated
+ * @param leaftype leaf node type
+ * @return data model tree
+ * @throws WorkflowException workflow exception
+ */
+ DataModelTree alloc(String path, Nodetype leaftype) throws WorkflowException;
+
+ /**
+ * Remove node on the path.
+ * @param path data model tree path
+ * @throws WorkflowException workflow exception
+ */
+ void remove(String path) throws WorkflowException;
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultRpcDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultRpcDescription.java
new file mode 100644
index 0000000..6f9d3c3
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultRpcDescription.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Class of workflow RPC description.
+ */
+public final class DefaultRpcDescription implements RpcDescription {
+
+ /**
+ * Workflow RPC operation.
+ */
+ private final String op;
+
+ /**
+ * Parameters.
+ */
+ private final JsonNode params;
+
+ /**
+ * Invocation ID.
+ */
+ private final String id;
+
+ /**
+ * Constructor of workplace description.
+ * @param builder workplace builder
+ */
+ private DefaultRpcDescription(Builder builder) {
+ this.op = builder.op;
+ this.params = builder.params;
+ this.id = builder.id;
+ }
+
+ @Override
+ public String op() {
+ return this.op;
+ }
+
+ @Override
+ public JsonNode params() {
+ return this.params;
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ /**
+ * Creating workflow RPC description from json tree.
+ * @param root root node for workflow RPC description
+ * @return workflow RPC description
+ * @throws WorkflowException workflow exception
+ */
+ public static DefaultRpcDescription valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(RPC_OP_PTR);
+ if (!(node instanceof TextNode)) {
+ throw new WorkflowException("invalid RPC operation for " + root);
+ }
+ String rpcOp = node.asText();
+
+ node = root.at(RPC_PARAMS_PTR);
+ if (node instanceof MissingNode) {
+ throw new WorkflowException("invalid RPC parameters for " + root);
+ }
+ JsonNode rpcParams = node;
+
+ node = root.at(RPC_ID_PTR);
+ if (!(node instanceof TextNode)) {
+ throw new WorkflowException("invalid RPC invocation ID for " + root);
+ }
+ String rpcId = node.asText();
+
+
+ return builder()
+ .setOp(rpcOp)
+ .setParams(rpcParams)
+ .setId(rpcId)
+ .build();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("op", op())
+ .add("params", params())
+ .add("id", id())
+ .toString();
+ }
+
+ /**
+ * Gets builder instance.
+ * @return builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for workplace RPC description.
+ */
+ public static class Builder {
+
+ /**
+ * Workflow RPC operation.
+ */
+ private String op;
+
+ /**
+ * Parameters.
+ */
+ private JsonNode params;
+
+ /**
+ * Invocation ID.
+ */
+ private String id;
+
+ /**
+ * Sets workflow RPC operation.
+ * @param op workflow RPC operation
+ * @return builder
+ */
+ public Builder setOp(String op) {
+ this.op = op;
+ return this;
+ }
+
+ /**
+ * Sets workflow RPC parameters.
+ * @param params workflow RPC parameters
+ * @return builder
+ */
+ public Builder setParams(JsonNode params) {
+ this.params = params;
+ return this;
+ }
+
+ /**
+ * Sets workflow RPC invocation ID.
+ * @param id workflow invocation ID
+ * @return builder
+ */
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * Builds workplace RPC description from builder.
+ * @return instance of workflow RPC description
+ * @throws WorkflowException workflow exception
+ */
+ public DefaultRpcDescription build() throws WorkflowException {
+ check(op != null, "op is invalid");
+ check(params != null, "params is invalid");
+ check(id != null, "id is invalid");
+ return new DefaultRpcDescription(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowContext.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowContext.java
new file mode 100644
index 0000000..8f5d3c7
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowContext.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceNotFoundException;
+import org.onosproject.event.Event;
+
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Default implementation of WorkflowContext.
+ */
+public class DefaultWorkflowContext extends WorkflowContext {
+
+ /**
+ * ID of workflow.
+ */
+ private URI workflowId;
+
+ /**
+ * Workplace name of the workflow.
+ */
+ private String workplaceName;
+
+ /**
+ * State of workflow.
+ */
+ private WorkflowState state;
+
+ /**
+ * Current program counter of the workflow.
+ */
+ private ProgramCounter current;
+
+ /**
+ * Cause of workflow exception.
+ */
+ private String cause;
+
+ /**
+ * Completion event type.
+ */
+ private transient Class<? extends Event> completionEventType;
+
+ /**
+ * Completion event hint Set.
+ */
+ private transient Set<String> completionEventHintSet;
+
+ /**
+ * Completion event generator method reference.
+ */
+ private transient WorkExecutor completionEventGenerator;
+
+ /**
+ * Completion event timeout milliseconds.
+ */
+ private transient long completionEventTimeoutMs;
+
+ /**
+ * Service reference for workflow service.
+ */
+ private transient WorkflowExecutionService workflowExecutionService;
+
+ /**
+ * Service reference for workflow store.
+ */
+ private transient WorkflowStore workflowStore;
+
+ /**
+ * Service reference for workplace store.
+ */
+ private transient WorkplaceStore workplaceStore;
+
+ /**
+ * Constructor of DefaultWorkflowContext.
+ * @param builder default workflow context builder
+ */
+ protected DefaultWorkflowContext(Builder builder) {
+ super(builder.data);
+ this.workflowId = builder.workflowId;
+ this.workplaceName = builder.workplaceName;
+ this.state = WorkflowState.IDLE;
+ this.current = ProgramCounter.INIT_PC;
+ }
+
+ /**
+ * DefaultWorkflowContext name builder.
+ * @param workflowid workflow id
+ * @param workplacename workplace name
+ * @return DefaultWorkflowContext name
+ */
+ public static String nameBuilder(URI workflowid, String workplacename) {
+ return workflowid.toString() + "@" + workplacename;
+ }
+
+ @Override
+ public String name() {
+ return nameBuilder(workflowId, workplaceName);
+ }
+
+ @Override
+ public String distributor() {
+ return workplaceName();
+ }
+
+ @Override
+ public URI workflowId() {
+ return this.workflowId;
+ }
+
+ @Override
+ public String workplaceName() {
+ return workplaceName;
+ }
+
+ @Override
+ public WorkflowState state() {
+ return state;
+ }
+
+ @Override
+ public void setState(WorkflowState state) {
+ this.state = state;
+ }
+
+ @Override
+ public ProgramCounter current() {
+ return this.current;
+ }
+
+ @Override
+ public void setCurrent(ProgramCounter pc) {
+ this.current = pc;
+ }
+
+ @Override
+ public String cause() {
+ return this.cause;
+ }
+
+ @Override
+ public void setCause(String cause) {
+ this.cause = cause;
+ }
+
+ @Override
+ public void completed() {
+ setTriggerNext(true);
+ }
+
+ @Override
+ public void waitCompletion(Class<? extends Event> eventType, String eventHint,
+ WorkExecutor eventGenerator, long timeoutMs) {
+ this.completionEventType = eventType;
+ this.completionEventHintSet = new HashSet<>();
+ this.completionEventHintSet.add(eventHint);
+ this.completionEventGenerator = eventGenerator;
+ this.completionEventTimeoutMs = timeoutMs;
+ }
+
+ @Override
+ public void waitAnyCompletion(Class<? extends Event> eventType, Set<String> eventHint,
+ WorkExecutor eventGenerator, long timeoutMs) {
+ this.completionEventType = eventType;
+ this.completionEventHintSet = new HashSet<>();
+ this.completionEventHintSet.addAll(eventHint);
+ this.completionEventGenerator = eventGenerator;
+ this.completionEventTimeoutMs = timeoutMs;
+ }
+
+ @Override
+ public void waitFor(long timeoutMs) {
+ this.completionEventTimeoutMs = timeoutMs;
+ }
+
+ @Override
+ public Class<? extends Event> completionEventType() {
+ return completionEventType;
+ }
+
+ @Override
+ public Set<String> completionEventHints() {
+ return completionEventHintSet;
+ }
+
+ @Override
+ public WorkExecutor completionEventGenerator() {
+ return completionEventGenerator;
+ }
+
+ @Override
+ public long completionEventTimeout() {
+ return completionEventTimeoutMs;
+ }
+
+ @Override
+ public void setWorkflowExecutionService(WorkflowExecutionService workflowExecutionService) {
+ this.workflowExecutionService = workflowExecutionService;
+ }
+
+ @Override
+ public WorkflowExecutionService workflowService() {
+ return workflowExecutionService;
+ }
+
+ @Override
+ public void setWorkflowStore(WorkflowStore workflowStore) {
+ this.workflowStore = workflowStore;
+ }
+
+ @Override
+ public WorkflowStore workflowStore() {
+ return workflowStore;
+ }
+
+ @Override
+ public void setWorkplaceStore(WorkplaceStore workplaceStore) {
+ this.workplaceStore = workplaceStore;
+ }
+
+ @Override
+ public WorkplaceStore workplaceStore() {
+ return workplaceStore;
+ }
+
+ public <T> T getService(Class<T> serviceClass) throws WorkflowException {
+ T service;
+ try {
+ service = DefaultServiceDirectory.getService(serviceClass);
+ } catch (ServiceNotFoundException e) {
+ throw new WorkflowException(e);
+ }
+ return service;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name())
+ .add("triggernext", triggerNext())
+ .add("data", data())
+ .add("current", current)
+ .add("state", state())
+ .add("cause", cause())
+ .toString();
+ }
+
+ /**
+ * Gets builder instance.
+ * @return builder instance
+ */
+ public static final Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for default workflow context.
+ */
+ public static class Builder {
+
+ /**
+ * ID of workflow.
+ */
+ private URI workflowId;
+
+ /**
+ * Workplace name of the workflow.
+ */
+ private String workplaceName;
+
+ /**
+ * Data model tree.
+ */
+ private DataModelTree data;
+
+ /**
+ * Sets workflow id.
+ * @param workflowId workflow id
+ * @return builder
+ */
+ public Builder workflowId(URI workflowId) {
+ this.workflowId = workflowId;
+ return this;
+ }
+
+ /**
+ * Sets workplace name.
+ * @param workplaceName workplace name
+ * @return builder
+ */
+ public Builder workplaceName(String workplaceName) {
+ this.workplaceName = workplaceName;
+ return this;
+ }
+
+ /**
+ * Sets data model tree.
+ * @param data data model tree
+ * @return builder
+ */
+ public Builder data(DataModelTree data) {
+ this.data = data;
+ return this;
+ }
+
+ /**
+ * Builds default workflow context.
+ * @return instance of default workflow context
+ * @throws WorkflowException workflow exception
+ */
+ public DefaultWorkflowContext build() throws WorkflowException {
+ check(data != null, "Invalid data model tree");
+ check(workflowId != null, "Invalid workflowId");
+ check(workplaceName != null, "Invalid workplaceName");
+ return new DefaultWorkflowContext(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowDescription.java
new file mode 100644
index 0000000..13a8f46
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowDescription.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+
+import java.net.URI;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+
+/**
+ * Class for default workflow description.
+ */
+public final class DefaultWorkflowDescription implements WorkflowDescription {
+
+ /**
+ * Workplace Name.
+ */
+ private String workplaceName;
+
+ /**
+ * Workflow ID.
+ */
+ private URI id;
+
+ /**
+ * Workflow data model.
+ */
+ private JsonNode data;
+
+ /**
+ * Constructor of workflow description.
+ * @param builder workflow description builder
+ */
+ private DefaultWorkflowDescription(Builder builder) {
+ this.workplaceName = builder.workplaceName;
+ this.id = builder.id;
+ this.data = builder.data;
+ }
+
+ @Override
+ public String workplaceName() {
+ return this.workplaceName;
+ }
+
+ @Override
+ public URI id() {
+ return this.id;
+ }
+
+ @Override
+ public String workflowContextName() {
+ return DefaultWorkflowContext.nameBuilder(id(), workplaceName());
+ }
+
+ @Override
+ public JsonNode data() {
+ return this.data;
+ }
+
+ /**
+ * Creating workflow description from json tree.
+ * @param root root node for workflow description
+ * @return workflow description
+ * @throws WorkflowException workflow exception
+ */
+ public static DefaultWorkflowDescription valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(WF_WORKPLACE));
+ if (!(node instanceof TextNode)) {
+ throw new WorkflowException("invalid workflow workplace for " + root);
+ }
+ String wfWorkplaceName = node.asText();
+
+ node = root.at(ptr(WF_ID));
+ if (!(node instanceof TextNode)) {
+ throw new WorkflowException("invalid workflow id for " + root);
+ }
+ URI wfId = URI.create(node.asText());
+
+ node = root.at(ptr(WF_DATA));
+ if (node instanceof MissingNode) {
+ throw new WorkflowException("invalid workflow data for " + root);
+ }
+ JsonNode wfData = node;
+
+ return builder()
+ .workplaceName(wfWorkplaceName)
+ .id(wfId)
+ .data(wfData)
+ .build();
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ @Override
+ public JsonNode toJson() {
+ ObjectNode root = JsonNodeFactory.instance.objectNode();
+ root.put(WF_WORKPLACE, workplaceName());
+ root.put(WF_ID, id().toString());
+ root.put(WF_DATA, data());
+ return root;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("workplace", workplaceName())
+ .add("id", id())
+ .add("data", data())
+ .toString();
+ }
+
+ /**
+ * Gets builder instance.
+ * @return builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for workflow description.
+ */
+ public static class Builder {
+
+ /**
+ * Workplace name.
+ */
+ private String workplaceName;
+
+ /**
+ * Workflow ID.
+ */
+ private URI id;
+
+ /**
+ * Workflow data model.
+ */
+ private JsonNode data;
+
+ /**
+ * Sets workplace name.
+ * @param workplaceName workplace name
+ * @return builder
+ */
+ public Builder workplaceName(String workplaceName) {
+ this.workplaceName = workplaceName;
+ return this;
+ }
+
+ /**
+ * Sets workflow id.
+ * @param id workflow ID
+ * @return builder
+ */
+ public Builder id(URI id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * Sets workflow id.
+ * @param id workflow ID string
+ * @return builder
+ */
+ public Builder id(String id) {
+ this.id = URI.create(id);
+ return this;
+ }
+
+ /**
+ * Sets workflow data model.
+ * @param data workflow data model
+ * @return builder
+ */
+ public Builder data(JsonNode data) {
+ this.data = data;
+ return this;
+ }
+
+ /**
+ * Builds workflow description from builder.
+ * @return instance of workflow description
+ * @throws WorkflowException workflow exception
+ */
+ public DefaultWorkflowDescription build() throws WorkflowException {
+ check(workplaceName != null, "workplaceName is invalid");
+ check(id != null, "id is invalid");
+ check(data != null, "data is invalid");
+ return new DefaultWorkflowDescription(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplace.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplace.java
new file mode 100644
index 0000000..1c60eaf
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplace.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceNotFoundException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+/**
+ * Default implementation of workplace.
+ */
+public class DefaultWorkplace extends Workplace {
+
+ /**
+ * Name of workplace.
+ */
+ private String name;
+
+ /**
+ * Constructor of DefaultWorkplace.
+ * @param name name of workplace
+ * @param data data model tree
+ */
+ public DefaultWorkplace(String name, DataModelTree data) {
+ super(data);
+ this.name = name;
+ }
+
+ @Override
+ public String name() {
+ return this.name;
+ }
+
+ @Override
+ public String distributor() {
+ return name();
+ }
+
+ @Override
+ public Collection<WorkflowContext> getContexts() throws WorkflowException {
+ WorkplaceStore workplaceStore;
+ try {
+ workplaceStore = DefaultServiceDirectory.getService(WorkplaceStore.class);
+ } catch (ServiceNotFoundException e) {
+ throw new WorkflowException(e);
+ }
+
+ return workplaceStore.getWorkplaceContexts(name());
+ }
+
+ /**
+ * Returns collection of context names.
+ * @return collection of context names
+ */
+ private Collection<String> getContextNames() {
+ Collection<WorkflowContext> ctx;
+ try {
+ ctx = getContexts();
+ } catch (WorkflowException e) {
+ ctx = Collections.emptyList();
+ }
+
+ return ctx.stream().map(x -> x.name()).collect(Collectors.toList());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name())
+ .add("triggernext", triggerNext())
+ .add("context", data())
+ .add("contexts", getContextNames())
+ .toString();
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplaceDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplaceDescription.java
new file mode 100644
index 0000000..e8125f7
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplaceDescription.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Class for default workplace description.
+ */
+public final class DefaultWorkplaceDescription implements WorkplaceDescription {
+
+ /**
+ * Name of workplace.
+ */
+ private final String name;
+
+ /**
+ * Data model of workplace(Optional).
+ */
+ private final Optional<JsonNode> optData;
+
+ /**
+ * Constructor of workplace description.
+ * @param builder workplace builder
+ */
+ private DefaultWorkplaceDescription(Builder builder) {
+ this.name = builder.name;
+ this.optData = builder.optData;
+ }
+
+ @Override
+ public String name() {
+ return this.name;
+ }
+
+ @Override
+ public Optional<JsonNode> data() {
+ return this.optData;
+ }
+
+ /**
+ * Creating workplace description from json tree.
+ * @param root root node for workplace description
+ * @return workplace description
+ * @throws WorkflowException workflow exception
+ */
+ public static DefaultWorkplaceDescription valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(WP_NAME));
+ if (!(node instanceof TextNode)) {
+ throw new WorkflowException("invalid workplace name for " + root);
+ }
+
+ Builder builder = builder()
+ .name(node.asText());
+
+ node = root.at(ptr(WP_DATA));
+ if (node != null && !(node instanceof MissingNode)) {
+ if (!(node instanceof ObjectNode) && !(node instanceof ArrayNode)) {
+ throw new WorkflowException("invalid workplace data for " + root);
+ }
+ builder.data(node);
+ }
+
+ return builder.build();
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ @Override
+ public JsonNode toJson() {
+ ObjectNode root = JsonNodeFactory.instance.objectNode();
+ root.put(WP_NAME, name());
+ if (data().isPresent()) {
+ root.put(WP_DATA, data().get());
+ }
+ return root;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name())
+ .add("optData", data())
+ .toString();
+ }
+
+ /**
+ * Gets builder instance.
+ * @return builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for workplace description.
+ */
+ public static class Builder {
+
+ /**
+ * Workplace name.
+ */
+ private String name;
+
+ /**
+ * Workplace optData model.
+ */
+ private Optional<JsonNode> optData = Optional.empty();
+
+ /**
+ * List of workflow.
+ */
+ private List<DefaultWorkflowDescription> workflowDescs = new ArrayList<DefaultWorkflowDescription>();
+
+ /**
+ * Sets workplace name.
+ * @param name workplace name
+ * @return builder
+ */
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Sets optData model.
+ * @param data workplace optData model
+ * @return builder
+ */
+ public Builder data(JsonNode data) {
+ this.optData = Optional.of(data);
+ return this;
+ }
+
+ /**
+ * Builds workplace description from builder.
+ * @return instance of workflow description
+ * @throws WorkflowException workflow exception
+ */
+ public DefaultWorkplaceDescription build() throws WorkflowException {
+ check(name != null, "name is invalid");
+ return new DefaultWorkplaceDescription(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventHintSupplier.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventHintSupplier.java
new file mode 100644
index 0000000..076bfc0
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventHintSupplier.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import org.onosproject.event.Event;
+
+/**
+ * Functional interface for delivering event hint supplier.
+ */
+@FunctionalInterface
+public interface EventHintSupplier {
+ String apply(Event event) throws Throwable;
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTask.java
new file mode 100644
index 0000000..c2af3e6
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTask.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+import org.onosproject.event.Event;
+
+import java.util.Objects;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Class for event task.
+ */
+public final class EventTask extends HandlerTask {
+
+ /**
+ * Event triggering event task.
+ */
+ private final Event event;
+
+ /**
+ * Event hint value for finding target event.
+ */
+ private final String eventHint;
+
+ /**
+ * Constructor of event task.
+ * @param builder builder of event task
+ */
+ private EventTask(Builder builder) {
+ super(builder);
+ this.event = builder.event;
+ this.eventHint = builder.eventHint;
+ }
+
+ /**
+ * Gets event of event task.
+ * @return event triggering event task
+ */
+ public Event event() {
+ return event;
+ }
+
+ /**
+ * Gets event type (class name of event) of event task.
+ * @return event type
+ */
+ public String eventType() {
+ return event.getClass().getName();
+ }
+
+ /**
+ * Gets event hint of event task.
+ * @return event hint string
+ */
+ public String eventHint() {
+ return eventHint;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof EventTask)) {
+ return false;
+ }
+ return Objects.equals(this.event(), ((EventTask) obj).event())
+ && Objects.equals(this.eventType(), ((EventTask) obj).eventType())
+ && Objects.equals(this.eventHint(), ((EventTask) obj).eventHint());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("context", context())
+ .add("programCounter", programCounter())
+ .add("event", event())
+ .add("eventHint", eventHint())
+ .toString();
+ }
+
+ /**
+ * Gets a instance of builder.
+ * @return instance of builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of EventTask.
+ */
+ public static class Builder extends HandlerTask.Builder {
+
+ /**
+ * Event triggering event task.
+ */
+ private Event event;
+
+ /**
+ * Event hint value for finding target event.
+ */
+ private String eventHint;
+
+ /**
+ * Sets event.
+ * @param event event triggering event task
+ * @return Builder of EventTask
+ */
+ public Builder event(Event event) {
+ this.event = event;
+ return this;
+ }
+
+ /**
+ * Sets event hint.
+ * @param eventHint event hint value for finding target event
+ * @return Builder of EventTask
+ */
+ public Builder eventHint(String eventHint) {
+ this.eventHint = eventHint;
+ return this;
+ }
+
+ @Override
+ public Builder context(WorkflowContext context) {
+ super.context(context);
+ return this;
+ }
+
+ @Override
+ public Builder programCounter(ProgramCounter programCounter) {
+ super.programCounter(programCounter);
+ return this;
+ }
+
+ /**
+ * Builds EventTask.
+ * @return instance of EventTask
+ * @throws WorkflowException workflow exception
+ */
+ public EventTask build() throws WorkflowException {
+ check(event != null, "event is invalid");
+ check(eventHint != null, "eventHint is invalid");
+ return new EventTask(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTimeoutTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTimeoutTask.java
new file mode 100644
index 0000000..2ee2672
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTimeoutTask.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Class for event timeout task.
+ */
+public final class EventTimeoutTask extends HandlerTask {
+
+ /**
+ * Event type (Class name of event).
+ */
+ private final String eventType;
+
+ /**
+ * Set of Event hint value for finding target event.
+ */
+ private final Set<String> eventHintSet = new HashSet<>();
+
+ /**
+ * Constructor of EventTimeoutTask.
+ * @param builder builder of EventTimeoutTask
+ */
+ private EventTimeoutTask(Builder builder) {
+ super(builder);
+ this.eventType = builder.eventType;
+ this.eventHintSet.addAll(builder.eventHintSet);
+ }
+
+ /**
+ * Gets event type (Class name of event).
+ * @return event type
+ */
+ public String eventType() {
+ return eventType;
+ }
+
+ /**
+ * Gets set of event hint value for finding target event.
+ * @return event hint set
+ */
+ public Set<String> eventHintSet() {
+ return eventHintSet;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof EventTask)) {
+ return false;
+ }
+ return Objects.equals(this.eventType(), ((EventTask) obj).eventType())
+ && Objects.equals(this.eventHintSet(), ((EventTask) obj).eventHint());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("context", context())
+ .add("programCounter", programCounter())
+ .add("eventType", eventType())
+ .add("eventHint", eventHintSet())
+ .toString();
+ }
+
+ /**
+ * Gets a instance of builder.
+ * @return instance of builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of EventTimeoutTask.
+ */
+ public static class Builder extends HandlerTask.Builder {
+ /**
+ * Event type (Class name of event).
+ */
+ private String eventType;
+
+ /**
+ * Set of Event hint value for finding target event.
+ */
+ private Set<String> eventHintSet;
+
+ /**
+ * Sets Event type (Class name of event).
+ * @param eventType event type
+ * @return builder of EventTimeoutTask
+ */
+ public Builder eventType(String eventType) {
+ this.eventType = eventType;
+ return this;
+ }
+
+ /**
+ * Sets event hint string for finding target event.
+ * @param eventHintSet Set of event hint string
+ * @return builder of EventTimeoutTask
+ */
+ public Builder eventHintSet(Set<String> eventHintSet) {
+ this.eventHintSet = eventHintSet;
+ return this;
+ }
+
+ @Override
+ public Builder context(WorkflowContext context) {
+ super.context(context);
+ return this;
+ }
+
+ @Override
+ public Builder programCounter(ProgramCounter programCounter) {
+ super.programCounter(programCounter);
+ return this;
+ }
+
+ /**
+ * Builds EventTimeoutTask.
+ * @return instance of EventTimeoutTask
+ * @throws WorkflowException workflow exception
+ */
+ public EventTimeoutTask build() throws WorkflowException {
+ check(eventType != null, "eventType is invalid");
+ check(eventHintSet != null, "eventHintSet is invalid");
+ return new EventTimeoutTask(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTask.java
new file mode 100644
index 0000000..94203bc
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTask.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Abstract class for handler task.
+ */
+public abstract class HandlerTask {
+
+ /**
+ * Workflow context of handler task.
+ */
+ private final WorkflowContext context;
+
+ /**
+ * Program counter of handler task.
+ */
+ private final ProgramCounter programCounter;
+
+ /**
+ * Constructor for handler task.
+ * @param builder handler task builder
+ */
+ protected HandlerTask(Builder builder) {
+ this.context = builder.context;
+ this.programCounter = builder.programCounter;
+ }
+
+ /**
+ * Returns workflow context of this handler task.
+ * @return workflow context
+ */
+ public WorkflowContext context() {
+ return context;
+ }
+
+ /**
+ * Returns program counter of this handler task.
+ * @return program counter
+ */
+ public ProgramCounter programCounter() {
+ return programCounter;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("context", context())
+ .add("programCounter", programCounter())
+ .toString();
+ }
+
+ /**
+ * Builder of HandlerTask.
+ */
+ public static class Builder {
+ protected WorkflowContext context;
+ protected ProgramCounter programCounter;
+
+ /**
+ * Sets workflow context of handler task.
+ * @param context workflow context
+ * @return builder of handler task
+ */
+ public Builder context(WorkflowContext context) {
+ this.context = context;
+ return this;
+ }
+
+ /**
+ * Sets program counter of handler task.
+ * @param programCounter program counter of handler type
+ * @return builder of handler task
+ */
+ public Builder programCounter(ProgramCounter programCounter) {
+ this.programCounter = programCounter;
+ return this;
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTaskBatchDelegate.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTaskBatchDelegate.java
new file mode 100644
index 0000000..c5859f2
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTaskBatchDelegate.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import java.util.Collection;
+
+/**
+ * Facade for receiving notifications from the handler task batch service.
+ */
+public interface HandlerTaskBatchDelegate {
+
+ /**
+ * Submits the specified batch of handler task operations for processing.
+ *
+ * @param operations batch of operations
+ */
+ void execute(Collection<Collection<HandlerTask>> operations);
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
new file mode 100644
index 0000000..ec2be65
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceNotFoundException;
+
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Class for immutable list workflow.
+ */
+public final class ImmutableListWorkflow extends AbstractWorkflow {
+
+ /**
+ * Init worklet type(class name of init worklet type).
+ */
+ private String initWorkletType;
+
+ /**
+ * List of worklet.
+ */
+ private List<String> workletTypeList;
+
+ /**
+ * Set of workflow attributes.
+ */
+ private Set<WorkflowAttribute> attributes;
+
+ private static JsonDataModelInjector dataModelInjector = new JsonDataModelInjector();
+
+ /**
+ * Constructor of ImmutableListWorkflow.
+ *
+ * @param builder builder of ImmutableListWorkflow
+ */
+ private ImmutableListWorkflow(Builder builder) {
+ super(builder.id);
+ this.initWorkletType = builder.initWorkletType;
+ workletTypeList = ImmutableList.copyOf(builder.workletTypeList);
+ attributes = ImmutableSet.copyOf(builder.attributes);
+ }
+
+ @Override
+ public Worklet init(WorkflowContext context) throws WorkflowException {
+ if (Objects.isNull(initWorkletType)) {
+ return null;
+ }
+
+ return getWorkletInstance(initWorkletType);
+ }
+
+
+ @Override
+ public ProgramCounter next(WorkflowContext context) throws WorkflowException {
+
+ int cnt = 0;
+
+ ProgramCounter pc = context.current();
+ check(pc != null, "Invalid program counter");
+
+ for (int i = pc.workletIndex(); i < workletTypeList.size(); i++) {
+
+ if (cnt++ > Worklet.MAX_WORKS) {
+ throw new WorkflowException("Maximum worklet execution exceeded");
+ }
+
+ String workletType = workletTypeList.get(i);
+
+ if (Worklet.Common.COMPLETED.tag().equals(workletType)) {
+ return ProgramCounter.valueOf(workletType, i);
+ }
+
+ if (Worklet.Common.INIT.tag().equals(workletType)) {
+ continue;
+ }
+
+ Worklet worklet = getWorkletInstance(workletType);
+ Class workClass = worklet.getClass();
+
+ if (BranchWorklet.class.isAssignableFrom(workClass)) {
+ Class nextClass = ((BranchWorklet) worklet).next(context);
+ if (nextClass == null) {
+ throw new WorkflowException("Invalid next Worklet for " + workClass);
+ }
+
+ // TODO : it does not support duplicated use of WorkType. It needs to consider label concept
+ int nextIdx = getClassIndex(nextClass);
+ if (nextIdx == -1) {
+ throw new WorkflowException("Failed to find next " + nextClass + " for " + workClass);
+ }
+
+ i = nextIdx;
+ continue;
+
+ } else {
+ // isNext is read only. It does not perform 'inhale'.
+ dataModelInjector.inject(worklet, context);
+ if (worklet.isNext(context)) {
+ return ProgramCounter.valueOf(workletType, i);
+ }
+ }
+ }
+ throw new WorkflowException("workflow reached to end but not COMPLETED");
+ }
+
+ @Override
+ public ProgramCounter increased(ProgramCounter pc) throws WorkflowException {
+
+ int increaedIndex = pc.workletIndex() + 1;
+ if (increaedIndex >= workletTypeList.size()) {
+ throw new WorkflowException("Out of bound in program counter(" + pc + ")");
+ }
+
+ String workletType = workletTypeList.get(increaedIndex);
+ return ProgramCounter.valueOf(workletType, increaedIndex);
+ }
+
+ @Override
+ public Worklet getWorkletInstance(String workletType) throws WorkflowException {
+
+ if (Worklet.Common.INIT.tag().equals(workletType)) {
+ return Worklet.Common.INIT;
+ }
+
+ if (Worklet.Common.COMPLETED.tag().equals(workletType)) {
+ return Worklet.Common.COMPLETED;
+ }
+
+ WorkflowStore store;
+ try {
+ store = DefaultServiceDirectory.getService(WorkflowStore.class);
+ } catch (ServiceNotFoundException e) {
+ throw new WorkflowException(e);
+ }
+
+ Class workClass;
+ try {
+ workClass = store.getClass(workletType);
+ } catch (ClassNotFoundException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (!isAllowed(workClass)) {
+ throw new WorkflowException("Not allowed class(" + workClass.getSimpleName() + ")");
+ }
+
+ Worklet worklet;
+ try {
+ worklet = (Worklet) workClass.newInstance();
+ } catch (Exception e) {
+ throw new WorkflowException(e);
+ }
+
+ return worklet;
+ }
+
+ @Override
+ public Set<WorkflowAttribute> attributes() {
+ return ImmutableSet.copyOf(attributes);
+ }
+
+ @Override
+ public List<String> getWorkletTypeList() {
+ return ImmutableList.copyOf(workletTypeList);
+ }
+
+ /**
+ * Gets index of class in worklet type list.
+ *
+ * @param aClass class to get index
+ * @return index of class in worklet type list
+ */
+ private int getClassIndex(Class aClass) {
+ for (int i = 0; i < workletTypeList.size(); i++) {
+ if (Objects.equals(aClass.getName(), workletTypeList.get(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Checks whether class is allowed class or not.
+ *
+ * @param clazz class to check
+ * @return Check result
+ */
+ private boolean isAllowed(Class clazz) {
+ // non static inner class is not allowed
+ if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
+ return false;
+ }
+ // enum is not allowed
+ if (clazz.isEnum()) {
+ return false;
+ }
+ // class should be subclass of Work
+ if (!Worklet.class.isAssignableFrom(clazz)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof EventTask)) {
+ return false;
+ }
+ return Objects.equals(this.id(), ((ImmutableListWorkflow) obj).id())
+ && Objects.equals(this.initWorkletType, ((ImmutableListWorkflow) obj).initWorkletType)
+ && Objects.equals(this.workletTypeList, ((ImmutableListWorkflow) obj).workletTypeList)
+ && Objects.equals(this.attributes, ((ImmutableListWorkflow) obj).attributes);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id())
+ .add("initWorklet", initWorkletType)
+ .add("workList", workletTypeList)
+ .add("attributes", attributes)
+ .toString();
+ }
+
+ /**
+ * Gets a instance of builder.
+ *
+ * @return instance of builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of ImmutableListWorkflow.
+ */
+ public static class Builder {
+
+ private URI id;
+ private String initWorkletType;
+ private final List<String> workletTypeList = Lists.newArrayList();
+ private final Set<WorkflowAttribute> attributes = Sets.newHashSet();
+
+ /**
+ * Sets id of immutable list workflow.
+ *
+ * @param uri id of immutable list workflow
+ * @return builder
+ */
+ public Builder id(URI uri) {
+ this.id = uri;
+ workletTypeList.add(Worklet.Common.INIT.tag());
+ return this;
+ }
+
+ /**
+ * Sets init worklet class name of immutable list workflow.
+ *
+ * @param workletClassName class name of worklet
+ * @return builder
+ */
+ public Builder init(String workletClassName) {
+ this.initWorkletType = workletClassName;
+ return this;
+ }
+
+ /**
+ * Chains worklet class name of immutable list workflow.
+ *
+ * @param workletClassName class name of worklet
+ * @return builder
+ */
+ public Builder chain(String workletClassName) {
+ workletTypeList.add(workletClassName);
+ return this;
+ }
+
+ /**
+ * Adds workflow attribute.
+ *
+ * @param attribute workflow attribute to be added
+ * @return builder
+ */
+ public Builder attribute(WorkflowAttribute attribute) {
+ attributes.add(attribute);
+ return this;
+ }
+
+ /**
+ * Builds ImmutableListWorkflow.
+ *
+ * @return instance of ImmutableListWorkflow
+ */
+ public ImmutableListWorkflow build() {
+ workletTypeList.add(Worklet.Common.COMPLETED.tag());
+ return new ImmutableListWorkflow(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModel.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModel.java
new file mode 100644
index 0000000..fb26642
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModel.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+
+/**
+ * Annotation for injecting json data model on work-let execution context.
+ */
+public @interface JsonDataModel {
+
+ /**
+ * Path of data model.
+ *
+ * @return path of data model
+ */
+ String path() default "/";
+
+ /**
+ * Representing whether this data model is optional or not.
+ *
+ * @return optional or not
+ */
+ boolean optional() default false;
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelInjector.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelInjector.java
new file mode 100644
index 0000000..6f87411
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelInjector.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class for injecting json data model on the work-let execution context.
+ */
+public class JsonDataModelInjector {
+
+ private static final Logger log = LoggerFactory.getLogger(JsonDataModelInjector.class);
+
+ /**
+ * Injects data model to work-let.
+ *
+ * @param worklet work-let to be injected
+ * @param context workflow context
+ * @throws WorkflowException workflow exception
+ */
+ public void inject(Worklet worklet, WorkflowContext context) throws WorkflowException {
+
+ handle(worklet, context, this::injectModel);
+ }
+
+ /**
+ * Inhales data model from work-let.
+ *
+ * @param worklet work-let to be inhaled
+ * @param context workflow context
+ * @throws WorkflowException workflow exception
+ */
+ public void inhale(Worklet worklet, WorkflowContext context) throws WorkflowException {
+
+ handle(worklet, context, this::inhaleModel);
+ }
+
+ private void handle(Worklet worklet, WorkflowContext context, DataModelFieldBehavior func)
+ throws WorkflowException {
+ Class cl = worklet.getClass();
+ List<Field> fields = getInheritedFields(cl);
+ if (Objects.isNull(fields)) {
+ log.error("Invalid fields on {}", cl);
+ return;
+ }
+
+ for (Field field : fields) {
+ Annotation[] annotations = field.getAnnotations();
+ if (Objects.isNull(annotations)) {
+ continue;
+ }
+ for (Annotation annotation : annotations) {
+ if (!(annotation instanceof JsonDataModel)) {
+ continue;
+ }
+ JsonDataModel model = (JsonDataModel) annotation;
+ func.apply(worklet, context, field, model);
+ }
+ }
+ }
+
+ private static List<Field> getInheritedFields(Class<?> type) {
+ List<Field> fields = new ArrayList<Field>();
+
+ Class<?> cl = type;
+ while (cl != null && cl != Object.class) {
+ for (Field field : cl.getDeclaredFields()) {
+ if (!field.isSynthetic()) {
+ fields.add(field);
+ }
+ }
+ cl = cl.getSuperclass();
+ }
+ return fields;
+ }
+
+ /**
+ * Functional interface for json data model annotated field behavior.
+ */
+ @FunctionalInterface
+ public interface DataModelFieldBehavior {
+ void apply(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException;
+ }
+
+ private static Map<Class, DataModelFieldBehavior> injectTypeMap = new HashMap<>();
+
+ static {
+ injectTypeMap.put(String.class, JsonDataModelInjector::injectText);
+ injectTypeMap.put(Integer.class, JsonDataModelInjector::injectInteger);
+ injectTypeMap.put(Boolean.class, JsonDataModelInjector::injectBoolean);
+ injectTypeMap.put(JsonNode.class, JsonDataModelInjector::injectJsonNode);
+ injectTypeMap.put(ArrayNode.class, JsonDataModelInjector::injectArrayNode);
+ injectTypeMap.put(ObjectNode.class, JsonDataModelInjector::injectObjectNode);
+ }
+
+ /**
+ * Injects data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private void injectModel(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ DataModelFieldBehavior behavior = injectTypeMap.get(field.getType());
+ if (Objects.isNull(behavior)) {
+ throw new WorkflowException("Not supported type(" + field.getType() + ")");
+ }
+ behavior.apply(worklet, context, field, model);
+ }
+
+ /**
+ * Injects text data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model text data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectText(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ String text = ((JsonDataModelTree) context.data()).textAt(model.path());
+ if (Objects.isNull(text)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid text data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), String.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not String");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, text);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects integer data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model integer data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectInteger(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+ Integer number = ((JsonDataModelTree) context.data()).intAt(model.path());
+ if (Objects.isNull(number)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid number data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), Integer.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Integer");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, number);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects boolean data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model boolean data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectBoolean(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ Boolean bool = ((JsonDataModelTree) context.data()).booleanAt(model.path());
+ if (Objects.isNull(bool)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid boolean data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), Boolean.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Boolean");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, bool);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects json node data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectJsonNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ JsonNode jsonNode = ((JsonDataModelTree) context.data()).nodeAt(model.path());
+ if (Objects.isNull(jsonNode)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid json node data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), JsonNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not JsonNode");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, jsonNode);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects json array node data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json array node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectArrayNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ ArrayNode arrayNode = ((JsonDataModelTree) context.data()).arrayAt(model.path());
+ if (Objects.isNull(arrayNode)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid array node data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), ArrayNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ArrayNode");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, arrayNode);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects json object node data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json object node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectObjectNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ ObjectNode objNode = ((JsonDataModelTree) context.data()).objectAt(model.path());
+ if (Objects.isNull(objNode)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid object node data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), ObjectNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ObjectNode");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, objNode);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ private static Map<Class, DataModelFieldBehavior> inhaleTypeMap = new HashMap<>();
+
+ static {
+ inhaleTypeMap.put(String.class, JsonDataModelInjector::inhaleText);
+ inhaleTypeMap.put(Integer.class, JsonDataModelInjector::inhaleInteger);
+ inhaleTypeMap.put(Boolean.class, JsonDataModelInjector::inhaleBoolean);
+ inhaleTypeMap.put(JsonNode.class, JsonDataModelInjector::inhaleJsonNode);
+ inhaleTypeMap.put(ArrayNode.class, JsonDataModelInjector::inhaleArrayNode);
+ inhaleTypeMap.put(ObjectNode.class, JsonDataModelInjector::inhaleObjectNode);
+ }
+
+ /**
+ * Inhales data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private void inhaleModel(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ DataModelFieldBehavior behavior = inhaleTypeMap.get(field.getType());
+ if (Objects.isNull(behavior)) {
+ throw new WorkflowException("Not supported type(" + field.getType() + ")");
+ }
+ behavior.apply(worklet, context, field, model);
+ }
+
+ /**
+ * Inhales text data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model text data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleText(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ if (!(Objects.equals(field.getType(), String.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not String");
+ }
+
+ String text;
+ try {
+ field.setAccessible(true);
+ text = (String) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(text)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.setAt(model.path(), text);
+ } else if (!(jsonNode instanceof TextNode)) {
+ throw new WorkflowException("Invalid text data model on (" + model.path() + ")");
+ } else {
+ tree.remove(model.path());
+ tree.setAt(model.path(), text);
+ }
+ }
+
+ /**
+ * Inhales integer data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model integer data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleInteger(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+ if (!(Objects.equals(field.getType(), Integer.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Integer");
+ }
+
+ Integer number;
+ try {
+ field.setAccessible(true);
+ number = (Integer) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(number)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.setAt(model.path(), number);
+ } else if (!(jsonNode instanceof IntNode)) {
+ throw new WorkflowException("Invalid integer data model on (" + model.path() + ")");
+ } else {
+ tree.remove(model.path());
+ tree.setAt(model.path(), number);
+ }
+ }
+
+ /**
+ * Inhales boolean data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model boolean data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleBoolean(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ if (!(Objects.equals(field.getType(), Boolean.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Boolean");
+ }
+
+ Boolean bool;
+ try {
+ field.setAccessible(true);
+ bool = (Boolean) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(bool)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.setAt(model.path(), bool);
+ } else if (!(jsonNode instanceof BooleanNode)) {
+ throw new WorkflowException("Invalid boolean data model on (" + model.path() + ")");
+ } else {
+ tree.remove(model.path());
+ tree.setAt(model.path(), bool);
+ }
+ }
+
+ /**
+ * Inhales json node data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleJsonNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ if (!(Objects.equals(field.getType(), JsonNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not JsonNode");
+ }
+
+ JsonNode tgtJsonNode;
+ try {
+ field.setAccessible(true);
+ tgtJsonNode = (JsonNode) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(tgtJsonNode)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.attach(model.path(), new JsonDataModelTree(tgtJsonNode));
+ } else if (!(jsonNode instanceof JsonNode)) {
+ throw new WorkflowException("Invalid json node data model on (" + model.path() + ")");
+ } else {
+ // do nothing
+ }
+ }
+
+ /**
+ * Inhales json array node data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json array node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleArrayNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+ if (!(Objects.equals(field.getType(), ArrayNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ArrayNode");
+ }
+
+ ArrayNode tgtArrayNode;
+ try {
+ field.setAccessible(true);
+ tgtArrayNode = (ArrayNode) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(tgtArrayNode)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.attach(model.path(), new JsonDataModelTree(tgtArrayNode));
+ } else if (!(jsonNode instanceof ArrayNode)) {
+ throw new WorkflowException("Invalid array node data model on (" + model.path() + ")");
+ } else {
+ // do nothing
+ }
+ }
+
+ /**
+ * Inhales json object node data model on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json object node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleObjectNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+ if (!(Objects.equals(field.getType(), ObjectNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ObjectNode");
+ }
+
+ ObjectNode tgtObjNode;
+ try {
+ field.setAccessible(true);
+ tgtObjNode = (ObjectNode) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(tgtObjNode)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.attach(model.path(), new JsonDataModelTree(tgtObjNode));
+ } else if (!(jsonNode instanceof ObjectNode)) {
+ throw new WorkflowException("Invalid object node data model on (" + model.path() + ")");
+ } else {
+ // do nothing
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
new file mode 100644
index 0000000..7ee1be7
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+import com.fasterxml.jackson.core.JsonPointer;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+
+/**
+ * Class for json data model tree.
+ */
+public final class JsonDataModelTree implements DataModelTree {
+
+ private static final Logger log = LoggerFactory.getLogger(JsonDataModelTree.class);
+
+ /**
+ * Root node of json data model tree.
+ */
+ private JsonNode root;
+
+ /**
+ * Constructor of JsonDataModelTree.
+ */
+ public JsonDataModelTree() {
+ this.root = JsonNodeFactory.instance.objectNode();
+ }
+
+ /**
+ * Constructor of JsonDataModelTree.
+ * @param root root node of json data model tree
+ */
+ public JsonDataModelTree(JsonNode root) {
+ this.root = root;
+ }
+
+ @Override
+ public DataModelTree subtree(String path) {
+ JsonNode node = root.at(path);
+ if (Objects.isNull(node) || node.isMissingNode()) {
+ return null;
+ }
+ return new JsonDataModelTree(node);
+ }
+
+ @Override
+ public void attach(String path, DataModelTree tree) throws WorkflowException {
+
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+
+ JsonPointer ptr = JsonPointer.compile(path);
+
+ if (!(tree instanceof JsonDataModelTree)) {
+ throw new WorkflowException("Invalid subTree(" + tree + ")");
+ }
+ JsonNode attachingNode = ((JsonDataModelTree) tree).root();
+
+ attach(ptr, attachingNode);
+ }
+
+ private void attach(JsonPointer ptr, JsonNode attachingNode) throws WorkflowException {
+
+ JsonNode node = root.at(ptr);
+ if (!(node instanceof MissingNode)) {
+ throw new WorkflowException("Path(" + ptr + ") has already subtree(" + node + ")");
+ }
+
+ if (ptr.last().getMatchingIndex() != -1) {
+
+ alloc(ptr.head(), Nodetype.ARRAY);
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isArray()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
+ }
+ int index = ptr.last().getMatchingIndex();
+ ((ArrayNode) parentNode).insert(index, attachingNode);
+
+ } else if (ptr.last().getMatchingProperty() != null) {
+
+ alloc(ptr.head(), Nodetype.MAP);
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isObject()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
+ }
+ String key = ptr.last().getMatchingProperty();
+ ((ObjectNode) parentNode).put(key, attachingNode);
+
+ } else {
+ throw new WorkflowException("Invalid path(" + ptr + ")");
+ }
+ }
+
+ @Override
+ public void remove(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ remove(ptr);
+ }
+
+ private void remove(JsonPointer ptr) throws WorkflowException {
+
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ log.warn("{} does not have valid node", ptr);
+ return;
+ }
+
+ if (ptr.last().getMatchingIndex() != -1) {
+
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isArray()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
+ }
+ int index = ptr.last().getMatchingIndex();
+ ((ArrayNode) parentNode).remove(index);
+
+ } else if (ptr.last().getMatchingProperty() != null) {
+
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isObject()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
+ }
+ String key = ptr.last().getMatchingProperty();
+ ((ObjectNode) parentNode).remove(key);
+
+ } else {
+ throw new WorkflowException("Invalid path(" + ptr + ")");
+ }
+ }
+
+ @Override
+ public JsonDataModelTree alloc(String path, Nodetype leaftype) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+
+ JsonPointer ptr = JsonPointer.compile(path);
+ return alloc(ptr, leaftype);
+ }
+
+ /**
+ * Allocates json data model tree on json pointer path with specific leaf type.
+ * @param ptr json pointer to allocate
+ * @param leaftype type of leaf node
+ * @return json data model tree
+ * @throws WorkflowException workflow exception
+ */
+ private JsonDataModelTree alloc(JsonPointer ptr, Nodetype leaftype) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+
+ switch (leaftype) {
+ case MAP:
+ alloc(root, ptr, JsonNodeType.OBJECT);
+ break;
+ case ARRAY:
+ alloc(root, ptr, JsonNodeType.ARRAY);
+ break;
+ default:
+ throw new WorkflowException("Not supported leaftype(" + leaftype + ")");
+ }
+ return this;
+ }
+
+ /**
+ * Gets root json node.
+ * @return root json node
+ * @throws WorkflowException workflow exception
+ */
+ public JsonNode root() throws WorkflowException {
+ return nodeAt("");
+ }
+
+ /**
+ * Gets root json node as ObjectNode (MAP type).
+ * @return root json node as ObjectNode
+ * @throws WorkflowException workflow exception
+ */
+ public ObjectNode rootObject() throws WorkflowException {
+ return objectAt("");
+ }
+
+ /**
+ * Gets root json node as ArrayNode (Array type).
+ * @return root json node as ArrayNode
+ * @throws WorkflowException workflow exception
+ */
+ public ArrayNode rootArray() throws WorkflowException {
+ return arrayAt("");
+ }
+
+ /**
+ * Gets json node on specific path.
+ * @param path path of json node
+ * @return json node on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public JsonNode nodeAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return nodeAt(ptr);
+ }
+
+ /**
+ * Gets json node on specific json pointer.
+ * @param ptr json pointer
+ * @return json node on specific json pointer.
+ * @throws WorkflowException workflow exception
+ */
+ public JsonNode nodeAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ return node;
+ }
+
+ /**
+ * Gets json node on specific path as ObjectNode.
+ * @param path path of json node
+ * @return ObjectNode type json node on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public ObjectNode objectAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return objectAt(ptr);
+ }
+
+ /**
+ * Gets json node on specific json pointer as ObjectNode.
+ * @param ptr json pointer
+ * @return ObjectNode type json node on specific json pointer.
+ * @throws WorkflowException workflow exception
+ */
+ public ObjectNode objectAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof ObjectNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return (ObjectNode) node;
+ }
+
+ /**
+ * Gets json node on specific path as ArrayNode.
+ * @param path path of json node
+ * @return ArrayNode type json node on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public ArrayNode arrayAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return arrayAt(ptr);
+ }
+
+ /**
+ * Gets json node on specific json pointer as ArrayNode.
+ * @param ptr json pointer
+ * @return ArrayNode type json node on specific json pointer.
+ * @throws WorkflowException workflow exception
+ */
+ public ArrayNode arrayAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof ArrayNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return (ArrayNode) node;
+ }
+
+ /**
+ * Gets text node on specific path.
+ * @param path path of json node
+ * @return text on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public String textAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return textAt(ptr);
+ }
+
+ /**
+ * Gets text on specific json pointer.
+ * @param ptr json pointer
+ * @return text on specific json pointer
+ * @throws WorkflowException workflow exception
+ */
+ public String textAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof TextNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return ((TextNode) node).asText();
+ }
+
+ /**
+ * Gets integer node on specific path.
+ * @param path path of json node
+ * @return integer on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public Integer intAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return intAt(ptr);
+ }
+
+ /**
+ * Gets integer on specific json pointer.
+ * @param ptr json pointer
+ * @return integer on specific json pointer
+ * @throws WorkflowException workflow exception
+ */
+ public Integer intAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof NumericNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return ((NumericNode) node).asInt();
+ }
+
+ /**
+ * Gets boolean on specific path.
+ * @param path path of json node
+ * @return boolean on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public Boolean booleanAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return booleanAt(ptr);
+ }
+
+ /**
+ * Gets boolean on specific json pointer.
+ * @param ptr json pointer
+ * @return boolean on specific json pointer
+ * @throws WorkflowException workflow exception
+ */
+ public Boolean booleanAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof BooleanNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return ((BooleanNode) node).asBoolean();
+ }
+
+ /**
+ * Sets text on specific json path.
+ * @param path json path
+ * @param text text to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(String path, String text) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ setAt(ptr, text);
+ }
+
+ /**
+ * Sets text on the specific json pointer.
+ * @param ptr json pointer
+ * @param text text to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(JsonPointer ptr, String text) throws WorkflowException {
+ TextNode textNode = TextNode.valueOf(text);
+ attach(ptr, textNode);
+ }
+
+ /**
+ * Sets boolean on specific json path.
+ * @param path json path
+ * @param isTrue boolean to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(String path, Boolean isTrue) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ setAt(ptr, isTrue);
+ }
+
+ /**
+ * Sets boolean on the specific json pointer.
+ * @param ptr json pointer
+ * @param isTrue boolean to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(JsonPointer ptr, Boolean isTrue) throws WorkflowException {
+ BooleanNode booleanNode = BooleanNode.valueOf(isTrue);
+ attach(ptr, booleanNode);
+ }
+
+ /**
+ * Sets integer on specific json path.
+ * @param path json path
+ * @param number number to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(String path, Integer number) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ setAt(ptr, number);
+ }
+
+ /**
+ * Sets integer on the specific json pointer.
+ * @param ptr json pointer
+ * @param number number to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(JsonPointer ptr, Integer number) throws WorkflowException {
+ IntNode intNode = IntNode.valueOf(number);
+ attach(ptr, intNode);
+ }
+
+ /**
+ * Allocates json data model tree on json pointer path with specific leaf type.
+ * @param node current json node in the json tree path
+ * @param ptr json pointer
+ * @param leaftype leaf type to be allocated
+ * @return allocated json node
+ * @throws WorkflowException workflow exception
+ */
+ private JsonNode alloc(JsonNode node, JsonPointer ptr, JsonNodeType leaftype) throws WorkflowException {
+
+ if (ptr.matches()) {
+ if (node == null || node instanceof MissingNode) {
+ node = createEmpty(leaftype);
+ } else {
+ //TODO: checking existing node type is matched with leaftype
+ if (!Objects.equals(node.getNodeType(), leaftype)) {
+ throw new WorkflowException("Requesting leaftype(" + leaftype + ") is not matched with "
+ + "existing nodetype(" + node.getNodeType() + ") for " + ptr);
+ }
+ }
+ return node;
+ }
+
+ if (ptr.getMatchingIndex() != -1) {
+ if (node == null || node instanceof MissingNode) {
+ node = createEmpty(JsonNodeType.ARRAY);
+ }
+ JsonNode child = alloc(node.get(ptr.getMatchingIndex()), ptr.tail(), leaftype);
+ if (!node.has(ptr.getMatchingIndex())) {
+ ((ArrayNode) node).insert(ptr.getMatchingIndex(), child);
+ }
+ } else if (ptr.getMatchingProperty() != null) {
+ if (node == null || node instanceof MissingNode) {
+ node = createEmpty(JsonNodeType.OBJECT);
+ }
+ JsonNode child = alloc(node.get(ptr.getMatchingProperty()), ptr.tail(), leaftype);
+ if (!node.has(ptr.getMatchingProperty())) {
+ ((ObjectNode) node).put(ptr.getMatchingProperty(), child);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * Creating empty json node.
+ * @param type json node type to create
+ * @return created json node
+ * @throws WorkflowException workflow exception
+ */
+ private JsonNode createEmpty(JsonNodeType type) throws WorkflowException {
+ if (type == JsonNodeType.OBJECT) {
+ return JsonNodeFactory.instance.objectNode();
+ } else if (type == JsonNodeType.ARRAY) {
+ return JsonNodeFactory.instance.arrayNode();
+ } else if (type == JsonNodeType.STRING) {
+ return JsonNodeFactory.instance.textNode("");
+ } else {
+ throw new WorkflowException("Not supported JsonNodetype(" + type + ")");
+ }
+ }
+
+ /**
+ * Gets the pretty json formatted string of this json data model tree.
+ * @return pretty json formatted string of this json data model tree
+ */
+ public String formattedRootString() {
+ String str = "";
+ try {
+ str = (new ObjectMapper()).writerWithDefaultPrettyPrinter().writeValueAsString(root);
+ } catch (JsonProcessingException e) {
+ log.error("Exception: ", e);
+ }
+ return str;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("json", root)
+ .toString();
+ }
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ProgramCounter.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ProgramCounter.java
new file mode 100644
index 0000000..6af50e0
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ProgramCounter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An interface representing workflow program counter.
+ */
+public final class ProgramCounter {
+
+ public static final ProgramCounter INIT_PC = ProgramCounter.valueOf(Worklet.Common.INIT.name(), 0);
+
+ /**
+ * index of the worklet.
+ */
+ private int workletIndex;
+
+ /**
+ * Type of worklet.
+ */
+ private String workletType;
+
+ /**
+ * Index of worklet.
+ * @return index of worklet
+ */
+ public int workletIndex() {
+ return this.workletIndex;
+ }
+
+ /**
+ * Type of worklet.
+ * @return type of worklet
+ */
+ public String workletType() {
+ return this.workletType;
+ }
+
+ /**
+ * Constructor of workflow Program Counter.
+ * @param workletType type of worklet
+ * @param workletIndex index of worklet
+ */
+ private ProgramCounter(String workletType, int workletIndex) {
+ this.workletType = workletType;
+ this.workletIndex = workletIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof ProgramCounter)) {
+ return false;
+ }
+ return Objects.equals(this.workletType(), ((ProgramCounter) obj).workletType())
+ && Objects.equals(this.workletIndex(), ((ProgramCounter) obj).workletIndex());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%d)%s", workletIndex, workletType);
+ }
+
+ /**
+ * Builder of workflow Program Counter.
+ * @param workletType type of worklet
+ * @param workletIndex index of worklet
+ * @return program counter
+ */
+ public static ProgramCounter valueOf(String workletType, int workletIndex) {
+ return new ProgramCounter(workletType, workletIndex);
+ }
+
+ /**
+ * Builder of workflow Program Counter.
+ * @param strProgramCounter string format for program counter
+ * @return program counter
+ */
+ public static ProgramCounter valueOf(String strProgramCounter) {
+
+ Matcher m = Pattern.compile("\\((\\d+)\\)(.+)").matcher(strProgramCounter);
+
+ if (!m.matches()) {
+ throw new IllegalArgumentException("Malformed program counter string");
+ }
+
+ return new ProgramCounter(m.group(2), Integer.parseInt(m.group(1)));
+ }
+
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/RpcDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/RpcDescription.java
new file mode 100644
index 0000000..5b067b4
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/RpcDescription.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Interface for workflow RPC description.
+ */
+public interface RpcDescription {
+
+ /**
+ * Workflow RPC operation pointer.
+ */
+ String RPC_OP_PTR = "/op";
+
+ /**
+ * Workflow RPC parameters pointer.
+ */
+ String RPC_PARAMS_PTR = "/params";
+
+ /**
+ * Workflow RPC invocation ID pointer.
+ */
+ String RPC_ID_PTR = "/id";
+
+ /**
+ * Returns workflow RPC operation.
+ * @return workflow RPC operation
+ */
+ String op();
+
+ /**
+ * Returns workflow RPC parameters.
+ * @return workflow RPC parameters
+ */
+ JsonNode params();
+
+ /**
+ * Returns workflow RPC invocation ID.
+ * @return workflow RPC invocation ID
+ */
+ String id();
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/SystemWorkflowContext.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/SystemWorkflowContext.java
new file mode 100644
index 0000000..4cc15bd
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/SystemWorkflowContext.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * WorkflowContext for system workflow.
+ */
+public class SystemWorkflowContext extends DefaultWorkflowContext {
+
+ /**
+ * Timestamp when this system workflow context was created.
+ */
+ private final long timestamp;
+
+ /**
+ * Distributor string for designating which onos node executes this workflow context with work partition.
+ */
+ private String distributor;
+
+ /**
+ * The constructor of SystemWorkflowContext.
+ * @param builder builder of SystemWorkflowContext
+ */
+ public SystemWorkflowContext(Builder builder) {
+ super(builder);
+ timestamp = System.currentTimeMillis();
+ //initial distributor(It can be changed)
+ distributor = name();
+ }
+
+ @Override
+ public String distributor() {
+ return distributor;
+ }
+
+ /**
+ * Sets distributor string of this workflow context.
+ * @param distributor distributor string
+ */
+ public void setDistributor(String distributor) {
+ this.distributor = distributor;
+ }
+
+ @Override
+ public String name() {
+ return workflowId().toString()
+ + ":" + timestamp
+ + "@" + workplaceName();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name())
+ .add("triggernext", triggerNext())
+ .add("data", data())
+ .add("current", current())
+ .add("state", state())
+ .add("cause", cause())
+ .toString();
+ }
+
+ /**
+ * Gets systemBuilder instance.
+ * @return systemBuilder instance
+ */
+ public static final Builder systemBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for system workflow context.
+ */
+ public static class Builder extends DefaultWorkflowContext.Builder {
+
+ /**
+ * Builds system workflow context.
+ * @return instance of default workflow context.
+ */
+ public SystemWorkflowContext build() {
+ return new SystemWorkflowContext(this);
+ }
+ }
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimeoutTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimeoutTask.java
new file mode 100644
index 0000000..180d9eb
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimeoutTask.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.base.MoreObjects;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+
+public final class TimeoutTask extends HandlerTask {
+
+ private TimeoutTask(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("context", context())
+ .add("programCounter", programCounter())
+ .toString();
+ }
+
+ /**
+ * Gets a instance of builder.
+ * @return instance of builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of TimeoutTask.
+ */
+ public static class Builder extends HandlerTask.Builder {
+ @Override
+ public Builder context(WorkflowContext context) {
+ super.context(context);
+ return this;
+ }
+
+ @Override
+ public Builder programCounter(ProgramCounter programCounter) {
+ super.programCounter(programCounter);
+ return this;
+ }
+
+ /**
+ * Builds TimeoutTask.
+ * @return instance of TimeoutTask
+ * @throws WorkflowException workflow exception
+ */
+ public TimeoutTask build() throws WorkflowException {
+ check(context != null, "context is invalid");
+ check(programCounter != null, "programCounter is invalid");
+ return new TimeoutTask(this);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimerChain.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimerChain.java
new file mode 100644
index 0000000..afa2ac6
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimerChain.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import java.util.Date;
+import java.util.Objects;
+import java.util.PriorityQueue;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Class for time chain timer.
+ */
+public class TimerChain {
+
+ private PriorityQueue<TimerChainTask> taskQueue = new PriorityQueue<>();
+
+ private Timer impendingTimer;
+ private TimerChainTask impendingTask;
+
+ /**
+ * Constructor of timer chain.
+ */
+ public TimerChain() {
+
+ }
+
+ /**
+ * Schedules timer event.
+ * @param afterMs millisecond which time event happens.
+ * @param runnable runnable to be executed after 'afterMs'
+ */
+ public void schedule(long afterMs, Runnable runnable) {
+ schedule(new Date((new Date()).getTime() + afterMs), runnable);
+ }
+
+ /**
+ * Schedules timer event.
+ * @param date date which timer event happens.
+ * @param runnable runnable to be executed on 'date'
+ */
+ public void schedule(Date date, Runnable runnable) {
+ schedule(new TimerChainTask(this, date, runnable));
+ }
+
+ /**
+ * Schedule timer chain task.
+ * @param task task to be scheduled.
+ */
+ private void schedule(TimerChainTask task) {
+ synchronized (this) {
+ if (taskQueue.isEmpty()) {
+ scheduleImpending(task);
+ return;
+ }
+
+ if (task.date().getTime() < head().date().getTime()) {
+ impendingTimer.cancel();
+ impendingTask.cancel();
+ TimerChainTask prevImpendingTask = pop().copy();
+ taskQueue.offer(prevImpendingTask);
+
+ scheduleImpending(task);
+ } else {
+ taskQueue.offer(task);
+ }
+ }
+ }
+
+ /**
+ * Schedule impending timer task.
+ * @param task impending timer chain task
+ * @return timer chain task
+ */
+ private TimerChainTask scheduleImpending(TimerChainTask task) {
+ taskQueue.offer(task);
+ Timer timer = new Timer();
+ this.setImpendingTask(task);
+ this.setImpendingTimer(timer);
+ timer.schedule(task, task.date());
+ return task;
+ }
+
+ /**
+ * Gets impending timer.
+ * @return impending timer
+ */
+ public Timer implendingTimer() {
+ return impendingTimer;
+ }
+
+ /**
+ * Sets impending timer.
+ * @param impendingTimer impending timer
+ */
+ public void setImpendingTimer(Timer impendingTimer) {
+ this.impendingTimer = impendingTimer;
+ }
+
+ /**
+ * Gets impending timer task.
+ * @return impending timer task
+ */
+ public TimerTask impendingTask() {
+ return impendingTask;
+ }
+
+ /**
+ * Sets impending timer task.
+ * @param impendingTask impending timer task
+ */
+ public void setImpendingTask(TimerChainTask impendingTask) {
+ this.impendingTask = impendingTask;
+ }
+
+ /**
+ * Gets head of timer chain task queue.
+ * @return timer chain task
+ */
+ public TimerChainTask head() {
+ if (!taskQueue.isEmpty()) {
+ return taskQueue.peek();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Pops head of timer chain task queue.
+ * @return timer chain task
+ */
+ public TimerChainTask pop() {
+ if (!taskQueue.isEmpty()) {
+ return taskQueue.poll();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Class for timer chain task.
+ */
+ public static class TimerChainTask extends TimerTask implements Comparable<TimerChainTask> {
+
+ private final TimerChain timerchain;
+ private final Date date;
+ private final Runnable runnable;
+
+ /**
+ * Constructor of timer chain task.
+ * @param timerchain timer chain
+ * @param date date to be scheduled
+ * @param runnable runnable to be executed by timer
+ */
+ public TimerChainTask(TimerChain timerchain, Date date, Runnable runnable) {
+ this.timerchain = timerchain;
+ this.date = date;
+ this.runnable = runnable;
+ }
+
+ /**
+ * Gets date.
+ * @return date of timer chain task
+ */
+ public Date date() {
+ return this.date;
+ }
+
+ /**
+ * Gets runnable.
+ * @return runnable of timer chain task
+ */
+ public Runnable runnable() {
+ return this.runnable;
+ }
+
+ @Override
+ public void run() {
+
+ TimerChainTask nextTask;
+ synchronized (timerchain) {
+ if (timerchain.impendingTask() != this) {
+ runnable().run();
+ return;
+ }
+
+ timerchain.implendingTimer().cancel();
+ timerchain.pop();
+
+ nextTask = timerchain.head();
+
+ if (nextTask != null) {
+ Timer nextTimer = new Timer();
+ this.timerchain.setImpendingTask(nextTask);
+ this.timerchain.setImpendingTimer(nextTimer);
+ nextTimer.schedule(nextTask, nextTask.date());
+ }
+ }
+
+ runnable().run();
+
+ }
+
+ @Override
+ public int compareTo(TimerChainTask target) {
+ return date().compareTo(target.date());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TimerChainTask)) {
+ return false;
+ }
+ TimerChainTask that = (TimerChainTask) o;
+
+ return this.date().equals(that.date());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(date);
+ }
+
+ /**
+ * Copies timer chain task.
+ * @return timer chain task
+ */
+ public TimerChainTask copy() {
+ return new TimerChainTask(timerchain, date, runnable);
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkExecutor.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkExecutor.java
new file mode 100644
index 0000000..e834e81
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkExecutor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+/**
+ * Functional interface for delivering method reference of worklet completion event generator.
+ */
+@FunctionalInterface
+public interface WorkExecutor {
+
+ /**
+ * Applies this method reference.
+ * @throws WorkflowException workflow exception
+ */
+ void apply() throws WorkflowException;
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workflow.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workflow.java
new file mode 100644
index 0000000..848a706
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workflow.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An interface representing workflow.
+ */
+public interface Workflow {
+
+ /**
+ * Id of workflow.
+ * @return id
+ */
+ URI id();
+
+ /**
+ * Returns init worklet.
+ * @param context workflow context
+ * @return init worklet
+ * @throws WorkflowException workflow exception
+ */
+ Worklet init(WorkflowContext context) throws WorkflowException;
+
+ /**
+ * Returns next program counter.
+ * @param context workflow context
+ * @return next program counter
+ * @throws WorkflowException workflow exception
+ */
+ ProgramCounter next(WorkflowContext context) throws WorkflowException;
+
+ /**
+ * Gets increased program coounter.
+ * @param pc program counter
+ * @return increased program counter
+ * @throws WorkflowException workflow exception
+ */
+ ProgramCounter increased(ProgramCounter pc) throws WorkflowException;
+
+ /**
+ * Returns instance of worklet.
+ * @param workletType class name of worklet
+ * @return instance of worklet
+ * @throws WorkflowException workflow exception
+ */
+ Worklet getWorkletInstance(String workletType) throws WorkflowException;
+
+ /**
+ * Builds workflow context.
+ * @param workplace workplace of system workflow
+ * @param data data model of system workflow context
+ * @return workflow context
+ * @throws WorkflowException workflow exception
+ */
+ WorkflowContext buildContext(Workplace workplace, DataModelTree data) throws WorkflowException;
+
+ /**
+ * Builds system workflow context.
+ * @param workplace workplace of system workflow
+ * @param data data model of system workflow context
+ * @return system workflow context
+ * @throws WorkflowException workflow exception
+ */
+ WorkflowContext buildSystemContext(Workplace workplace, DataModelTree data) throws WorkflowException;
+
+ /**
+ * Returns workflow attributes.
+ * @return attributes
+ */
+ Set<WorkflowAttribute> attributes();
+
+ /**
+ * Returns worklet type list.
+ * @return worklet type
+ */
+ List<String> getWorkletTypeList();
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowAttribute.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowAttribute.java
new file mode 100644
index 0000000..18ed701
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowAttribute.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+/**
+ * Attributes of workflow.
+ */
+public enum WorkflowAttribute {
+
+ /**
+ * Removes workflow context after completion of workflow.
+ */
+ REMOVE_AFTER_COMPLETE,
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowBatchDelegate.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowBatchDelegate.java
new file mode 100644
index 0000000..d54a92a
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowBatchDelegate.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Collection;
+
+/**
+ * Facade for receiving notifications from the workflow batch service.
+ */
+@Beta
+public interface WorkflowBatchDelegate {
+
+ /**
+ * Submits the specified batch of workflow operations for processing.
+ *
+ * @param operations batch of operations
+ */
+ void execute(Collection<WorkflowData> operations);
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowContext.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowContext.java
new file mode 100644
index 0000000..836524d
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowContext.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import org.onosproject.event.Event;
+
+import java.net.URI;
+import java.util.Set;
+
+/**
+ * An abstract class representing WorkflowContext.
+ */
+public abstract class WorkflowContext extends WorkflowData {
+
+ /**
+ * Constructor of workflow context.
+ * @param data data model tree
+ */
+ public WorkflowContext(DataModelTree data) {
+ super(data);
+ }
+
+ /**
+ * Returns workflow id of this workflow context.
+ * @return workflow id
+ */
+ public abstract URI workflowId();
+
+ /**
+ * Returns workplace name.
+ * @return workplace name
+ */
+ public abstract String workplaceName();
+
+ /**
+ * Returns the current state of workflow context.
+ * @return current state of workflow context
+ */
+ public abstract WorkflowState state();
+
+ /**
+ * Sets the current state of workflow context.
+ * @param state current state of workflow context
+ */
+ public abstract void setState(WorkflowState state);
+
+ /**
+ * Sets the current program counter of workflow context.
+ * @param pc current program counter
+ */
+ public abstract void setCurrent(ProgramCounter pc);
+
+ /**
+ * Returns the current program counter of workflow.
+ * @return the current program counter of workflow
+ */
+ public abstract ProgramCounter current();
+
+ /**
+ * Returns the cause string of exception state.
+ * @return cause string
+ */
+ public abstract String cause();
+
+ /**
+ * Sets the cause string of exception state.
+ * @param cause cause string
+ */
+ public abstract void setCause(String cause);
+
+ /**
+ * Indicates the worklet process become completed.
+ * By calling this, workflow triggers the next worklet selection
+ */
+ public abstract void completed();
+
+ /**
+ * Waits an event which have 'eventHint' after executing executor.
+ * If the event happens, Worklet.isCompleted will be called.
+ * If the event does not happen for timeoutMs, Worklet.timeout will be called.
+ * @param eventType the class of event to wait
+ * @param eventHint the event of the event to wait
+ * @param eventGenerator a method reference to be executed after executing executor
+ * @param timeoutMs timeout millisecond
+ */
+ public abstract void waitCompletion(Class<? extends Event> eventType, String eventHint,
+ WorkExecutor eventGenerator, long timeoutMs);
+
+
+ /**
+ * Waits an event which has any one of eventHint from Set 'eventHintSet' after executing executor.
+ * If the event happens, Worklet.isCompleted will be called.
+ * If the event does not happen for timeoutMs, Worklet.timeout will be called.
+ * @param eventType the class of event to wait
+ * @param eventHintSet the Set of eventHints of the event to wait
+ * @param eventGenerator a method reference to be executed after executing executor
+ * @param timeoutMs timeout millisecond
+ */
+ public abstract void waitAnyCompletion(Class<? extends Event> eventType, Set<String> eventHintSet,
+ WorkExecutor eventGenerator, long timeoutMs);
+
+
+ /**
+ * Waits timeout milliseconds. After timeoutMs Worklet.timeout will be called.
+ * @param timeoutMs timeout millisecond
+ */
+ public abstract void waitFor(long timeoutMs);
+
+ /**
+ * Returns the class of a completion event to wait.
+ * @return the class of a completion event
+ */
+ public abstract Class<? extends Event> completionEventType();
+
+ /**
+ * Returns the set of event hint string to wait.
+ * @return the event hint string set
+ */
+ public abstract Set<String> completionEventHints();
+
+ /**
+ * Returns method reference for generating completion event.
+ * @return a method reference
+ */
+ public abstract WorkExecutor completionEventGenerator();
+
+ /**
+ * Returns completion event timeout.
+ * @return completion event timeout
+ */
+ public abstract long completionEventTimeout();
+
+ /**
+ * Sets workflow service.
+ * @param workflowExecutionService workflow service
+ */
+ public abstract void setWorkflowExecutionService(WorkflowExecutionService workflowExecutionService);
+
+ /**
+ * Gets workflow service.
+ * @return workflow service
+ */
+ public abstract WorkflowExecutionService workflowService();
+
+ /**
+ * Sets workflow store.
+ * @param workflowStore workflow store.
+ */
+ public abstract void setWorkflowStore(WorkflowStore workflowStore);
+
+ /**
+ * Gets worklow store.
+ * @return workflow store
+ */
+ public abstract WorkflowStore workflowStore();
+
+ /**
+ * Sets workplace store.
+ * @param workplaceStore work place store.
+ */
+ public abstract void setWorkplaceStore(WorkplaceStore workplaceStore);
+
+ /**
+ * Gets workplace store.
+ * @return workplace store
+ */
+ public abstract WorkplaceStore workplaceStore();
+
+ /**
+ * Get service.
+ * @param serviceClass service class
+ * @param <T> service class type
+ * @return service reference
+ * @throws WorkflowException workflow exception
+ */
+ public abstract <T> T getService(Class<T> serviceClass) throws WorkflowException;
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowData.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowData.java
new file mode 100644
index 0000000..6a8590c
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowData.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+/**
+ * An abstract class representing workflow data.
+ */
+public abstract class WorkflowData {
+
+ private DataModelTree data;
+ private boolean triggerNext = true; // default is true
+
+ /**
+ * Constructor of workflow data.
+ * @param data data model tree
+ */
+ public WorkflowData(DataModelTree data) {
+ this.data = data;
+ }
+
+ /**
+ * Returns name.
+ * @return name
+ */
+ public abstract String name();
+
+ /**
+ * Returns work-partition distributor.
+ * @return work-partition distributor
+ */
+ public abstract String distributor();
+
+ /**
+ * Returns context model tree.
+ * @return context model tree
+ */
+ public DataModelTree data() {
+ return data;
+ }
+
+ /**
+ * Returns whether to trigger next worklet selection.
+ * @return whether to trigger next worklet selection
+ */
+ public boolean triggerNext() {
+ return triggerNext;
+ }
+
+ /**
+ * Sets whether to handle update event.
+ * @param triggerNext whether to handle update event
+ */
+ public void setTriggerNext(boolean triggerNext) {
+ this.triggerNext = triggerNext;
+ }
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataEvent.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataEvent.java
new file mode 100644
index 0000000..4833251
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataEvent.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Workflow data event class.
+ */
+public class WorkflowDataEvent extends AbstractEvent<WorkflowDataEvent.Type, WorkflowData> {
+
+ /**
+ * Workflow data event types.
+ */
+ public enum Type {
+
+ /**
+ * Insertion of workflow data.
+ */
+ INSERT,
+
+ /**
+ * Updation of workflow data.
+ */
+ UPDATE,
+
+ /**
+ * Removal of workflow data.
+ */
+ REMOVE
+ }
+
+ /**
+ * Constructor for workflow data event.
+ * @param type workflow data event type
+ * @param data workflow data
+ */
+ public WorkflowDataEvent(Type type, WorkflowData data) {
+ super(type, data);
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataListener.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataListener.java
new file mode 100644
index 0000000..b777e74
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Interface for workflow data listener.
+ */
+public interface WorkflowDataListener extends EventListener<WorkflowDataEvent> {
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataModelException.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataModelException.java
new file mode 100644
index 0000000..7d17529
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataModelException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.workflow.api;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * Workflow DataModel exception class.
+ */
+public class WorkflowDataModelException extends WorkflowException {
+
+ private URI workflowId;
+ private JsonNode parameterJson;
+ private List<String> errorMsgs;
+
+ /**
+ * Default Constructor for Workflow DataModel Exception.
+ *
+ * @param msg exception message
+ */
+ public WorkflowDataModelException(String msg) {
+
+ super(msg);
+ }
+
+ /**
+ * Constructor for Workflow DataModel Exception.
+ *
+ * @param workflowId id of workflow
+ * @param parameterJson paramter json data model
+ * @param errorMsgs error message for json data model
+ */
+ public WorkflowDataModelException(URI workflowId, JsonNode parameterJson, List<String> errorMsgs) {
+ super("Invalid workflow data model: " +
+ " workflow: " + workflowId.toString() +
+ ", parameter json: " + parameterJson.toString() +
+ ", errors: " + errorMsgs);
+ this.workflowId = workflowId;
+ this.parameterJson = parameterJson;
+ this.errorMsgs = errorMsgs;
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDescription.java
new file mode 100644
index 0000000..ca483f0
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDescription.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.net.URI;
+
+
+/**
+ * interface for workflow description.
+ */
+public interface WorkflowDescription {
+
+ /**
+ * Workflow workplace field name.
+ */
+ String WF_WORKPLACE = "workplace";
+
+ /**
+ * Workflow ID field name.
+ */
+ String WF_ID = "id";
+
+ /**
+ * Workflow data field name.
+ */
+ String WF_DATA = "data";
+
+ /**
+ * Gets workplace name.
+ * @return workplace name
+ */
+ String workplaceName();
+
+ /**
+ * Gets workflow ID.
+ * @return workflow ID
+ */
+ URI id();
+
+ /**
+ * Gets workflow context name.
+ * @return workflow context name
+ */
+ String workflowContextName();
+
+ /**
+ * Gets workflow data model.
+ * @return workflow data model
+ */
+ JsonNode data();
+
+ /**
+ * Gets json of workflow description.
+ * @return json of workflow description
+ */
+ JsonNode toJson();
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowException.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowException.java
new file mode 100644
index 0000000..09506bd
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+/**
+ * Workflow exception class.
+ */
+public class WorkflowException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor for workflow exception.
+ * @param msg exception message
+ */
+ public WorkflowException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructor for workflow exception.
+ * @param e throwable to deliver
+ */
+ public WorkflowException(Throwable e) {
+ super(e);
+ }
+
+ /**
+ * Constructor for workflow exception.
+ * @param msg exception message
+ * @param e throwable to deliver
+ */
+ public WorkflowException(String msg, Throwable e) {
+ super(msg, e);
+ }
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowExecutionService.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowExecutionService.java
new file mode 100644
index 0000000..c5e00a7
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowExecutionService.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import org.onosproject.event.Event;
+import org.onosproject.event.ListenerService;
+
+import java.util.Set;
+
+/**
+ * Interface for workflow execution service.
+ */
+public interface WorkflowExecutionService extends ListenerService<WorkflowDataEvent, WorkflowDataListener> {
+
+ /**
+ * Executes init worklet.
+ * @param context workflow context
+ */
+ void execInitWorklet(WorkflowContext context);
+
+ /**
+ * Evals workflow context.
+ * @param contextName the name of workflow context
+ */
+ void eval(String contextName);
+
+ /**
+ * Triggers workflow event map.
+ * @param event triggering event
+ * @param generator event hint generation method reference
+ */
+ void eventMapTrigger(Event event, EventHintSupplier generator);
+
+ /**
+ * Registers workflow event map.
+ * @param eventType event type (class name of event)
+ * @param eventHintSet Set of event hint value
+ * @param contextName workflow context name to be called by this event map
+ * @param programCounterString worklet type to be called by this event map
+ * @throws WorkflowException workflow exception
+ */
+ void registerEventMap(Class<? extends Event> eventType, Set<String> eventHintSet,
+ String contextName, String programCounterString) throws WorkflowException;
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowService.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowService.java
new file mode 100644
index 0000000..b11e835
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Interface for workflow service.
+ */
+public interface WorkflowService {
+
+ /**
+ * Creates workplace.
+ * @param wpDesc workplace description
+ * @throws WorkflowException workflow exception
+ */
+ void createWorkplace(WorkplaceDescription wpDesc) throws WorkflowException;
+
+ /**
+ * Removes workplace.
+ * @param wpDesc workplace description
+ * @throws WorkflowException workflow exception
+ */
+ void removeWorkplace(WorkplaceDescription wpDesc) throws WorkflowException;
+
+ /**
+ * Clears all workplaces.
+ * @throws WorkflowException workflow exception
+ */
+ void clearWorkplace() throws WorkflowException;
+
+ /**
+ * Invokes workflow.
+ * @param wfDesc workflow description
+ * @throws WorkflowException workflow exception
+ */
+ void invokeWorkflow(WorkflowDescription wfDesc) throws WorkflowException;
+
+ /**
+ * Invokes workflow.
+ * @param worklowDescJson workflow description json
+ * @throws WorkflowException workflow exception
+ */
+ void invokeWorkflow(JsonNode worklowDescJson) throws WorkflowException;
+
+ /**
+ * Terminates workflow.
+ * @param wfDesc workflow description
+ * @throws WorkflowException workflow exception
+ */
+ void terminateWorkflow(WorkflowDescription wfDesc) throws WorkflowException;
+}
\ No newline at end of file
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowState.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowState.java
new file mode 100644
index 0000000..0f92624
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowState.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+/**
+ * State of workflow.
+ */
+public enum WorkflowState {
+
+ /**
+ * Workflow is idle state.
+ */
+ IDLE,
+
+ /**
+ * Workflow is running state.
+ */
+ RUNNING,
+
+ /**
+ * Workflow is exception state.
+ */
+ EXCEPTION
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowStore.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowStore.java
new file mode 100644
index 0000000..ac0de4d
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowStore.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import java.net.URI;
+import java.util.Collection;
+
+/**
+ * Store for managing workflow.
+ */
+public interface WorkflowStore {
+
+ /**
+ * Registers workflow.
+ * @param workflow registering workflow
+ */
+ void register(Workflow workflow);
+
+ /**
+ * Unregisters workflow.
+ * @param id id of workflow
+ */
+ void unregister(URI id);
+
+ /**
+ * Gets workflow.
+ * @param id id of workflow
+ * @return workflow
+ */
+ Workflow get(URI id);
+
+ /**
+ * Gets all workflow.
+ * @return collection of workflow
+ */
+ Collection<Workflow> getAll();
+
+ /**
+ * Registers local class loader.
+ * @param loader class loader
+ */
+ void registerLocal(ClassLoader loader);
+
+ /**
+ * Unregisters local class loader.
+ * @param loader class loader
+ */
+ void unregisterLocal(ClassLoader loader);
+
+ /**
+ * Gets class from registered class loaders.
+ * @param name name of class
+ * @return class
+ * @throws ClassNotFoundException class not found exception
+ */
+ Class getClass(String name) throws ClassNotFoundException;
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Worklet.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Worklet.java
new file mode 100644
index 0000000..c4b4fb2
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Worklet.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+
+import org.onosproject.event.Event;
+
+/**
+ * An interface representing worklet. A workflow is composed of worklets.
+ */
+public interface Worklet {
+
+ int MAX_WORKS = 10000;
+
+ /**
+ * Returns tag name of worklet. class name is usually used.
+ * @return tag name
+ */
+ String tag();
+
+ /**
+ * Processes tasks of the worklet under the workflow context.
+ * @param context workflow context
+ * @throws WorkflowException workflow exception
+ */
+ void process(WorkflowContext context) throws WorkflowException;
+
+ /**
+ * Checks whether is this worklet next worklet to be done under the workflow context.
+ * @param context workflow context
+ * @return true means this worklet is the next worklet to be processed
+ * @throws WorkflowException workflow exception
+ */
+ boolean isNext(WorkflowContext context) throws WorkflowException;
+
+ /**
+ * Checks whether is this worklet completed or not. 'isCompleted' checking is triggered by an event task.
+ * @param context workflow context
+ * @param event an event triggering this 'isCompleted' checking
+ * @return completed or not
+ * @throws WorkflowException workflow exception
+ */
+ boolean isCompleted(WorkflowContext context, Event event) throws WorkflowException;
+
+ /**
+ * Completion event timeout handler.
+ * @param context workflow context
+ * @throws WorkflowException workflow exception
+ */
+ void timeout(WorkflowContext context) throws WorkflowException;
+
+ /**
+ * Common worklet enum.
+ */
+ enum Common implements Worklet {
+
+ /**
+ * Init worklet.
+ */
+ INIT {
+ @Override
+ public String tag() {
+ return INIT.name();
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").process should not be called");
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isNext should not be called");
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isCompleted should not be called");
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").timeout should not be called");
+ }
+ },
+
+ /**
+ * Completed worklet.
+ */
+ COMPLETED {
+ @Override
+ public String tag() {
+ return COMPLETED.name();
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").process should not be called");
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isNext should not be called");
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isCompleted should not be called");
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").timeout should not be called");
+ }
+ },
+
+ /**
+ * Interrupted worklet.
+ */
+ INTERRUPTED {
+ @Override
+ public String tag() {
+ return INTERRUPTED.name();
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").process should not be called");
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isNext should not be called");
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").isCompleted should not be called");
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ throw new WorkflowException("(" + tag() + ").timeout should not be called");
+ }
+ }
+ }
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workplace.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workplace.java
new file mode 100644
index 0000000..a7439d6
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workplace.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import java.util.Collection;
+
+/**
+ * Abstract class for workplace. A common workflow data container for multiple work-flows.
+ */
+public abstract class Workplace extends WorkflowData {
+
+ public static final String SYSTEM_WORKPLACE = "system-workplace";
+
+ /**
+ * Constructor of workplace.
+ * @param data data model tree
+ */
+ public Workplace(DataModelTree data) {
+ super(data);
+ }
+
+ /**
+ * Gets workflow contexts of workplace.
+ * @return collection of workflow context
+ * @throws WorkflowException workflow exception
+ */
+ abstract Collection<WorkflowContext> getContexts() throws WorkflowException;
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceDescription.java
new file mode 100644
index 0000000..16515eb
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceDescription.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Optional;
+
+/**
+ * Interface for workplace description.
+ */
+public interface WorkplaceDescription {
+
+ /**
+ * Workplace name field name.
+ */
+ String WP_NAME = "name";
+
+ /**
+ * Workplace data field name.
+ */
+ String WP_DATA = "data";
+
+ /**
+ * Gets workplace name.
+ * @return workplace name
+ */
+ String name();
+
+ /**
+ * Gets optional workplace data model.
+ * @return workplace optData model
+ */
+ Optional<JsonNode> data();
+
+ /**
+ * Gets json of workflow description.
+ * @return json of workflow description
+ */
+ JsonNode toJson();
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceStore.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceStore.java
new file mode 100644
index 0000000..b73cd4f
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceStore.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import org.onosproject.store.Store;
+import org.onosproject.store.service.StorageException;
+
+import java.util.Collection;
+
+/**
+ * Interface for workplace store.
+ */
+public interface WorkplaceStore extends Store<WorkflowDataEvent, WorkplaceStoreDelegate> {
+
+ /**
+ * Registers workplace on workplace store.
+ * @param name workplace name to register
+ * @param workplace workplace
+ * @throws StorageException storage exception
+ */
+ void registerWorkplace(String name, Workplace workplace) throws StorageException;
+
+ /**
+ * Removes workplace from workplace store.
+ * @param name workplace name to remove
+ * @throws StorageException storage exception
+ */
+ void removeWorkplace(String name) throws StorageException;
+
+ /**
+ * Gets workplace on workplace store.
+ * @param name workplace name to get
+ * @return workplace
+ * @throws StorageException storage exception
+ */
+ Workplace getWorkplace(String name) throws StorageException;
+
+ /**
+ * Commits workplace on workplace store.
+ * @param name workplace name to commit
+ * @param workplace workplace to commit
+ * @param handleEvent whether or not to handle workplace(workflow data) event
+ * @throws StorageException storage exception
+ */
+ void commitWorkplace(String name, Workplace workplace, boolean handleEvent) throws StorageException;
+
+ /**
+ * Gets all workplaces from workplace store.
+ * @return collection of workplace
+ * @throws StorageException storage exception
+ */
+ Collection<Workplace> getWorkplaces() throws StorageException;
+
+ /**
+ * Registers workflow context on workplace store.
+ * @param name workflow context name to register
+ * @param context workflow context to register
+ * @throws StorageException storage exception
+ */
+ void registerContext(String name, WorkflowContext context) throws StorageException;
+
+ /**
+ * Removes workflow context from workplace store.
+ * @param name workflow context name
+ * @throws StorageException storage exception
+ */
+ void removeContext(String name) throws StorageException;
+
+ /**
+ * Gets workflow context with name.
+ * @param name workflow context name to get
+ * @return workflow context
+ * @throws StorageException storage exception
+ */
+ WorkflowContext getContext(String name) throws StorageException;
+
+ /**
+ * Commits workflow context on workplace store.
+ * @param name workflow context name to commit
+ * @param context workflow context to commit
+ * @param handleEvent whether or not to handle workflow context(workflow data) event
+ * @throws StorageException storage exception
+ */
+ void commitContext(String name, WorkflowContext context, boolean handleEvent) throws StorageException;
+
+ /**
+ * Gets all workflow context from workplace store.
+ * @return collection of workflow context
+ * @throws StorageException storage exception
+ */
+ Collection<WorkflowContext> getContexts() throws StorageException;
+
+ /**
+ * Gets workflow contexts belonging to a workplace.
+ * @param workplaceName workplace name
+ * @return collection of workflow contexts belonging to a workplace
+ */
+ Collection<WorkflowContext> getWorkplaceContexts(String workplaceName);
+
+ /**
+ * Removes all workflow contexts beloinging to a workplace.
+ * @param workplaceName workplace name
+ */
+ void removeWorkplaceContexts(String workplaceName);
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceStoreDelegate.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceStoreDelegate.java
new file mode 100644
index 0000000..9e7ba14
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkplaceStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.api;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Workplace store delegate.
+ */
+public interface WorkplaceStoreDelegate extends StoreDelegate<WorkflowDataEvent> {
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/package-info.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/package-info.java
new file mode 100644
index 0000000..7e68f8f
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Package for workflow APIs.
+ */
+package org.onosproject.workflow.api;
\ No newline at end of file
diff --git a/apps/workflow/app/BUCK b/apps/workflow/app/BUCK
new file mode 100644
index 0000000..bc7e9af
--- /dev/null
+++ b/apps/workflow/app/BUCK
@@ -0,0 +1,17 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:KRYO',
+ '//lib:JACKSON',
+ '//lib:jsch',
+ '//lib:org.apache.karaf.shell.console',
+ '//lib:jackson-core',
+ '//lib:jackson-annotations',
+ '//lib:jackson-databind',
+ '//cli:onos-cli',
+ '//core/store/serializers:onos-core-serializers',
+ '//apps/workflow/api:onos-apps-workflow-api',
+]
+
+osgi_jar(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/app/BUILD b/apps/workflow/app/BUILD
new file mode 100644
index 0000000..d5af5a2
--- /dev/null
+++ b/apps/workflow/app/BUILD
@@ -0,0 +1,10 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + CLI + [
+ "//core/store/serializers:onos-core-serializers",
+ "//core/store/primitives:onos-core-primitives",
+ "//apps/workflow/api:onos-apps-workflow-api",
+]
+
+osgi_jar(
+ karaf_command_packages = ["org.onosproject.workflow.cli"],
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCommand.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCommand.java
new file mode 100644
index 0000000..b0f4168
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCommand.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.workflow.api.DefaultWorkflowDescription;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowExecutionService;
+import org.onosproject.workflow.api.WorkflowService;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.WorkplaceStore;
+
+import java.util.Objects;
+
+@Command(scope = "onos", name = "workflow", description = "workflow cli")
+public class WorkFlowCommand extends AbstractShellCommand {
+
+ static final String INVOKE = "invoke";
+ static final String EVAL = "eval";
+
+
+
+ @Argument(index = 0, name = "cmd",
+ description = "command(" + INVOKE + "|" + EVAL + "eval)",
+ required = true)
+ private String cmd = null;
+
+ @Argument(index = 1, name = "name",
+ description = "workflow context name(workflow@workplace)",
+ required = true)
+ private String name = null;
+
+ @Override
+ protected void execute() {
+ if (Objects.isNull(cmd)) {
+ error("invalid cmd parameter");
+ return;
+ }
+
+ if (Objects.isNull(name)) {
+ error("invalid workflow context name");
+ return;
+ }
+
+ String[] tokens = name.split("@");
+ if (tokens != null && tokens.length != 2) {
+ error("invalid workflow context name(workflow@workplace)");
+ return;
+ }
+
+ String workflowId = tokens[0];
+ String workplace = tokens[1];
+
+ switch (cmd) {
+ case INVOKE:
+ invoke(workflowId, workplace);
+ break;
+ case EVAL:
+ eval(name);
+ break;
+ default:
+ print("Unsupported cmd: " + cmd);
+ }
+ }
+
+ /**
+ * Invokes workflow.
+ * @param workflowId workflow id
+ * @param workplaceName workplace name
+ */
+ private void invoke(String workflowId, String workplaceName) {
+
+ WorkflowService service = get(WorkflowService.class);
+ try {
+ DefaultWorkflowDescription wfDesc = DefaultWorkflowDescription.builder()
+ .workplaceName(workplaceName)
+ .id(workflowId)
+ .data(JsonNodeFactory.instance.objectNode())
+ .build();
+
+ service.invokeWorkflow(wfDesc);
+ } catch (WorkflowException e) {
+ error("Exception: ", e);
+ }
+ }
+
+ /**
+ * Evaluates workflow context.
+ * @param workflowContextName workflow context name
+ */
+ private void eval(String workflowContextName) {
+ WorkplaceStore storService = get(WorkplaceStore.class);
+ WorkflowExecutionService execService = get(WorkflowExecutionService.class);
+
+ WorkflowContext context = storService.getContext(workflowContextName);
+ if (context == null) {
+ error("failed to find workflow context {}", workflowContextName);
+ return;
+ }
+ execService.eval(workflowContextName);
+ }
+
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCompleter.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCompleter.java
new file mode 100644
index 0000000..9460c03
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCompleter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.cli.AbstractChoicesCompleter;
+
+import java.util.List;
+
+import static org.onosproject.workflow.cli.WorkFlowCommand.EVAL;
+import static org.onosproject.workflow.cli.WorkFlowCommand.INVOKE;
+
+
+/**
+ * Workflow command completer.
+ */
+public class WorkFlowCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ return ImmutableList.of(INVOKE, EVAL);
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCtxtNameCompleter.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCtxtNameCompleter.java
new file mode 100644
index 0000000..a8bd53a
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowCtxtNameCompleter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.workflow.api.WorkplaceStore;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+/**
+ * Workflow context name completer.
+ */
+public class WorkFlowCtxtNameCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ WorkplaceStore workplaceStore = getService(WorkplaceStore.class);
+ return workplaceStore.getContexts().stream()
+ .map(workplace -> workplace.name())
+ .collect(Collectors.toList());
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowEventMapCommand.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowEventMapCommand.java
new file mode 100644
index 0000000..8eb0749
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowEventMapCommand.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.workflow.api.ContextEventMapStore;
+import org.onosproject.workflow.api.WorkflowException;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+@Command(scope = "onos", name = "workflow-eventmap", description = "workflow event map cli")
+public class WorkFlowEventMapCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "cmd", description = "command(print)", required = true)
+ private String cmd = null;
+
+ @Override
+ protected void execute() {
+ if (Objects.isNull(cmd)) {
+ error("invalid cmd parameter");
+ return;
+ }
+
+ ContextEventMapStore store = get(ContextEventMapStore.class);
+ try {
+ switch (cmd) {
+ case "print":
+ JsonNode tree = store.asJsonTree();
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ print(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tree));
+ } catch (JsonProcessingException e) {
+ error("Exception: " + e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+ }
+ break;
+
+ default:
+ print("Unsupported cmd: " + cmd);
+ }
+ } catch (WorkflowException e) {
+ error(e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+ }
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowIdCompleter.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowIdCompleter.java
new file mode 100644
index 0000000..0f56184
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowIdCompleter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.workflow.api.WorkflowStore;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+/**
+ * Workflow ID completer.
+ */
+public class WorkFlowIdCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ WorkflowStore workflowStore = getService(WorkflowStore.class);
+ return workflowStore.getAll().stream()
+ .map(workflow -> workflow.id().toString())
+ .collect(Collectors.toList());
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowStoreCommand.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowStoreCommand.java
new file mode 100644
index 0000000..dcf38a6
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowStoreCommand.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.WorkflowStore;
+
+import java.util.Objects;
+import java.net.URI;
+
+@Command(scope = "onos", name = "workflowstore", description = "workflow store cli")
+public class WorkFlowStoreCommand extends AbstractShellCommand {
+
+ static final String RM = "rm";
+
+ @Argument(index = 0, name = "cmd",
+ description = "command(" + RM + ")", required = false)
+ private String cmd = null;
+
+ @Argument(index = 1, name = "id",
+ description = "workflow id(URI)", required = false)
+ private String id = null;
+
+ @Override
+ protected void execute() {
+
+ if (Objects.isNull(cmd)) {
+ printAllWorkflow();
+ return;
+ }
+
+ if (Objects.isNull(id)) {
+ print("invalid id");
+ return;
+ }
+
+ switch (cmd) {
+ case RM:
+ rmWorkflow(id);
+ break;
+ default:
+ print("Unsupported cmd: " + cmd);
+ }
+ }
+
+ private void rmWorkflow(String id) {
+ WorkflowStore workflowStore = get(WorkflowStore.class);
+ workflowStore.unregister(URI.create(id));
+ }
+
+ private void printAllWorkflow() {
+ WorkflowStore workflowStore = get(WorkflowStore.class);
+ for (Workflow workflow : workflowStore.getAll()) {
+ print(getWorkflowString(workflow));
+ }
+ }
+
+ private String getWorkflowString(Workflow workflow) {
+ return workflow.toString();
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowStoreCompleter.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowStoreCompleter.java
new file mode 100644
index 0000000..c6b425f
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowStoreCompleter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.cli.AbstractChoicesCompleter;
+
+import java.util.List;
+
+import static org.onosproject.workflow.cli.WorkFlowStoreCommand.RM;
+
+/**
+ * Workflow Store command completer.
+ */
+public class WorkFlowStoreCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ return ImmutableList.of(RM);
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCommand.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCommand.java
new file mode 100644
index 0000000..c882e07
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCommand.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.workflow.api.DefaultWorkflowDescription;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.WorkflowService;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Workflow test command.
+ */
+@Command(scope = "onos", name = "workflow-test", description = "workflow test cli")
+public class WorkFlowTestCommand extends AbstractShellCommand {
+
+ static final String INVOKE_SAMPLE = "invoke-sample";
+ static final String EXCEPTION_SAMPLE = "exception-sample";
+
+ @Argument(index = 0, name = "test-name",
+ description = "Test name (" + INVOKE_SAMPLE + " | " + EXCEPTION_SAMPLE + ")",
+ required = true)
+ private String testName = null;
+
+ @Argument(index = 1, name = "arg1",
+ description = "number of test for (" + INVOKE_SAMPLE + " | " + EXCEPTION_SAMPLE + ")",
+ required = false)
+ private String arg1 = null;
+
+ @Override
+ protected void execute() {
+ if (Objects.isNull(testName)) {
+ error("invalid test-name parameter");
+ return;
+ }
+
+ switch (testName) {
+ case INVOKE_SAMPLE:
+ if (Objects.isNull(arg1)) {
+ error("arg1 is required for test " + INVOKE_SAMPLE);
+ return;
+ }
+
+ int num;
+ try {
+ num = Integer.parseInt(arg1);
+ } catch (NumberFormatException e) {
+ error("arg1 should be an integer value");
+ return;
+ } catch (Exception e) {
+ error(e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+ return;
+ }
+
+ invokeSampleTest(num);
+ break;
+
+ case EXCEPTION_SAMPLE:
+ if (Objects.isNull(arg1)) {
+ error("arg1 is required for test " + EXCEPTION_SAMPLE);
+ return;
+ }
+ int count;
+ try {
+ count = Integer.parseInt(arg1);
+ } catch (NumberFormatException e) {
+ error("arg1 should be an integer value");
+ return;
+ } catch (Exception e) {
+ error(e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+ return;
+ }
+
+ invokeExceptionTest(count);
+ break;
+
+ default:
+ print("Unsupported test-name: " + testName);
+ }
+ }
+
+ /**
+ * Workflow invoke test_name.
+ *
+ * @param num the arg1 of workflow to test_name
+ */
+ private void invokeSampleTest(int num) {
+ for (int i = 0; i <= num; i++) {
+ String wpName = "test_name-" + i;
+ invoke("sample.workflow-0", wpName);
+ invoke("sample.workflow-1", wpName);
+ invoke("sample.workflow-2", wpName);
+ }
+ }
+
+ /**
+ * Workflow datatype exception test.
+ *
+ * @param num the number of workflow to test
+ */
+ private void invokeExceptionTest(int num) {
+ for (int i = 0; i <= num; i++) {
+ String wpName = "test-" + i;
+ invoke("sample.workflow-3", wpName);
+ }
+ }
+
+
+ /**
+ * Invokes workflow.
+ *
+ * @param workflowId workflow id
+ * @param workplaceName workplace name
+ */
+ private void invoke(String workflowId, String workplaceName) {
+
+ WorkflowService service = get(WorkflowService.class);
+
+ ObjectNode dataModel = JsonNodeFactory.instance.objectNode();
+ dataModel.put("count", 0);
+
+ try {
+ DefaultWorkflowDescription wfDesc = DefaultWorkflowDescription.builder()
+ .workplaceName(workplaceName)
+ .id(workflowId)
+ .data(dataModel)
+ .build();
+ service.invokeWorkflow(wfDesc);
+ } catch (WorkflowException e) {
+ error(e.getMessage() + "trace: " + Arrays.asList(e.getStackTrace()));
+ }
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCompleter.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCompleter.java
new file mode 100644
index 0000000..d1fb7f4
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCompleter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.cli.AbstractChoicesCompleter;
+
+import java.util.List;
+
+import static org.onosproject.workflow.cli.WorkFlowTestCommand.EXCEPTION_SAMPLE;
+import static org.onosproject.workflow.cli.WorkFlowTestCommand.INVOKE_SAMPLE;
+
+/**
+ * Workflow test command completer.
+ */
+public class WorkFlowTestCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ return ImmutableList.of(INVOKE_SAMPLE, EXCEPTION_SAMPLE);
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceNameCompleter.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceNameCompleter.java
new file mode 100644
index 0000000..5dd2f23
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceNameCompleter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.workflow.api.WorkplaceStore;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+/**
+ * Workplace name completer.
+ */
+public class WorkplaceNameCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ WorkplaceStore workplaceStore = getService(WorkplaceStore.class);
+ return workplaceStore.getWorkplaces().stream()
+ .map(workplace -> workplace.name())
+ .collect(Collectors.toList());
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceStoreCommand.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceStoreCommand.java
new file mode 100644
index 0000000..f8c2310
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceStoreCommand.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.WorkflowService;
+import org.onosproject.workflow.api.Workplace;
+import org.onosproject.workflow.api.DefaultWorkplaceDescription;
+import org.onosproject.workflow.api.WorkplaceStore;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+@Command(scope = "onos", name = "workplace",
+ description = "workplace cli")
+public class WorkplaceStoreCommand extends AbstractShellCommand {
+
+ static final String ADD = "add";
+ static final String RM = "rm";
+ static final String CLEAR = "clear";
+ static final String PRINT = "print";
+
+ @Argument(index = 0, name = "cmd",
+ description = "command(" + ADD + "/" + RM + "/" + CLEAR + "/" + PRINT + ")",
+ required = false)
+ private String cmd = null;
+
+ @Argument(index = 1, name = "name",
+ description = "workplace name",
+ required = false)
+ private String name = null;
+
+ @Option(name = "-f", aliases = "--filter", description = "including filter",
+ required = false, multiValued = false)
+ private String inFilter = null;
+
+ @Option(name = "-e", aliases = "--excludefilter", description = "excluding filter",
+ required = false, multiValued = false)
+ private String exFilter = null;
+
+ @Override
+ protected void execute() {
+
+ if (Objects.isNull(cmd)) {
+ printAllWorkplace();
+ return;
+ }
+
+ switch (cmd) {
+ case ADD:
+ if (Objects.isNull(name)) {
+ error("invalid name");
+ return;
+ }
+ addEmptyWorkplace(name);
+ return;
+
+ case RM:
+ if (Objects.isNull(name)) {
+ print("invalid name");
+ return;
+ }
+ rmWorkplace(name);
+ break;
+
+ case CLEAR:
+ clearWorkplace();
+ break;
+
+ case PRINT:
+ if (Objects.isNull(name)) {
+ print("invalid name");
+ return;
+ }
+ printWorkplace(name);
+ break;
+
+ default:
+ print("Unsupported cmd: " + cmd);
+ }
+ }
+
+ /**
+ * Adds empty workplace.
+ * @param name workplace name
+ */
+ private void addEmptyWorkplace(String name) {
+ WorkflowService service = get(WorkflowService.class);
+ try {
+ DefaultWorkplaceDescription wpDesc = DefaultWorkplaceDescription.builder()
+ .name(name)
+ .build();
+ service.createWorkplace(wpDesc);
+ } catch (WorkflowException e) {
+ error(e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+ }
+ }
+
+ /**
+ * Clears all workplaces.
+ */
+ private void clearWorkplace() {
+ WorkflowService service = get(WorkflowService.class);
+ try {
+ service.clearWorkplace();
+ } catch (WorkflowException e) {
+ error(e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+ }
+ }
+
+ /**
+ * Removes workplace.
+ * @param name workplace name to remove
+ */
+ private void rmWorkplace(String name) {
+ WorkflowService service = get(WorkflowService.class);
+ try {
+ DefaultWorkplaceDescription wpDesc = DefaultWorkplaceDescription.builder()
+ .name(name)
+ .build();
+ service.removeWorkplace(wpDesc);
+ } catch (WorkflowException e) {
+ error(e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+ }
+ }
+
+ /**
+ * Prints workplace.
+ * @param name workplace name
+ */
+ private void printWorkplace(String name) {
+ WorkplaceStore workplaceStore = get(WorkplaceStore.class);
+ Workplace workplace = workplaceStore.getWorkplace(name);
+ if (Objects.isNull(workplace)) {
+ print("Not existing workplace " + name);
+ return;
+ }
+ print(getWorkplaceString(workplace));
+ }
+
+ /**
+ * Prints all workplaces.
+ */
+ private void printAllWorkplace() {
+ WorkplaceStore workplaceStore = get(WorkplaceStore.class);
+ for (Workplace workplace : workplaceStore.getWorkplaces()) {
+ print(getWorkplaceString(workplace));
+ printWorkplaceContexts(workplaceStore, workplace.name());
+ }
+ }
+
+ /**
+ * Prints contexts of workplace.
+ * @param workplaceStore workplace store service
+ * @param workplaceName workplace name
+ */
+ private void printWorkplaceContexts(WorkplaceStore workplaceStore, String workplaceName) {
+ for (WorkflowContext context : workplaceStore.getWorkplaceContexts(workplaceName)) {
+ String str = context.toString();
+ if (Objects.nonNull(inFilter)) {
+ if (str.indexOf(inFilter) != -1) {
+ if (Objects.nonNull(exFilter)) {
+ if (str.indexOf(exFilter) == -1) {
+ print(" - " + context);
+ }
+ } else {
+ print(" - " + context);
+ }
+ }
+ } else {
+ if (Objects.nonNull(exFilter)) {
+ if (str.indexOf(exFilter) == -1) {
+ print(" - " + context);
+ }
+ } else {
+ print(" - " + context);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets workplace string.
+ * @param workplace workplace
+ * @return workplace string
+ */
+ private String getWorkplaceString(Workplace workplace) {
+ return workplace.toString();
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceStoreCompleter.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceStoreCompleter.java
new file mode 100644
index 0000000..6654340
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkplaceStoreCompleter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.cli;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.cli.AbstractChoicesCompleter;
+
+import java.util.List;
+
+import static org.onosproject.workflow.cli.WorkplaceStoreCommand.ADD;
+import static org.onosproject.workflow.cli.WorkplaceStoreCommand.CLEAR;
+import static org.onosproject.workflow.cli.WorkplaceStoreCommand.PRINT;
+import static org.onosproject.workflow.cli.WorkplaceStoreCommand.RM;
+
+/**
+ * Workplace store command completer.
+ */
+public class WorkplaceStoreCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ return ImmutableList.of(ADD, RM, CLEAR, PRINT);
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/package-info.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/package-info.java
new file mode 100644
index 0000000..0383d04
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Workflow cli package.
+ */
+package org.onosproject.workflow.cli;
\ No newline at end of file
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/DistributedContextEventMapTreeStore.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/DistributedContextEventMapTreeStore.java
new file mode 100644
index 0000000..9554cd8
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/DistributedContextEventMapTreeStore.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.AsyncDocumentTree;
+import org.onosproject.store.service.DocumentPath;
+import org.onosproject.store.service.IllegalDocumentModificationException;
+import org.onosproject.store.service.NoSuchDocumentPathException;
+import org.onosproject.store.service.Ordering;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.onosproject.workflow.api.ContextEventMapStore;
+import org.onosproject.workflow.api.WorkflowException;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+public class DistributedContextEventMapTreeStore implements ContextEventMapStore {
+
+ protected static final Logger log = getLogger(DistributedContextEventMapTreeStore.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private StorageService storageService;
+
+ private ApplicationId appId;
+
+ private AsyncDocumentTree<String> eventMapTree;
+
+ private EventuallyConsistentMap<String, Set<String>> hintSetPerCxtMap;
+
+
+ @Activate
+ public void activate() {
+
+ appId = coreService.registerApplication("org.onosproject.contexteventmapstore");
+ log.info("appId=" + appId);
+
+ KryoNamespace eventMapNamespace = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .build();
+
+ eventMapTree = storageService.<String>documentTreeBuilder()
+ .withSerializer(Serializer.using(eventMapNamespace))
+ .withName("context-event-map-store")
+ .withOrdering(Ordering.INSERTION)
+ .buildDocumentTree();
+
+ hintSetPerCxtMap = storageService.<String, Set<String>>eventuallyConsistentMapBuilder()
+ .withName("workflow-event-hint-per-cxt")
+ .withSerializer(eventMapNamespace)
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .build();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventMapTree.destroy();
+ hintSetPerCxtMap.destroy();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void registerEventMap(String eventType, Set<String> eventHintSet,
+ String contextName, String programCounterString) throws WorkflowException {
+ for (String eventHint : eventHintSet) {
+ //Insert in eventCxtPerHintMap
+ DocumentPath dpathForCxt = DocumentPath.from(Lists.newArrayList(
+ "root", eventType, eventHint, contextName));
+ String currentWorkletType = completeVersioned(eventMapTree.get(dpathForCxt));
+ if (currentWorkletType == null) {
+ complete(eventMapTree.createRecursive(dpathForCxt, programCounterString));
+ } else {
+ complete(eventMapTree.replace(dpathForCxt, programCounterString, currentWorkletType));
+ }
+ log.trace("RegisterEventMap for eventType:{}, eventSet:{}, contextName:{}, pc:{}",
+ eventType, eventHintSet, contextName, programCounterString);
+
+ }
+ hintSetPerCxtMap.put(contextName, eventHintSet);
+ log.trace("RegisterEventMap in hintSetPerCxt for " +
+ "eventType:{}, eventSet:{}, contextName:{}, pc:{}",
+ eventType, eventHintSet, contextName, programCounterString);
+ }
+
+ @Override
+ public void unregisterEventMap(String eventType, String contextName)
+ throws WorkflowException {
+
+ Set<String> hints = hintSetPerCxtMap.get(contextName);
+ for (String eventHint : hints) {
+ //Remove from eventCxtPerHintMap
+ complete(eventMapTree.removeNode(DocumentPath.from(Lists.newArrayList(
+ "root", eventType, eventHint, contextName))));
+ log.trace("UnregisterEventMap from eventCxtPerHintMap for eventType:{}, eventSet:{}, contextName:{}",
+ eventType, eventHint, contextName);
+ }
+ hintSetPerCxtMap.remove(contextName);
+ }
+
+
+ @Override
+ public Map<String, String> getEventMapByHint(String eventType, String eventHint) throws WorkflowException {
+ DocumentPath path = DocumentPath.from(
+ Lists.newArrayList("root", eventType, eventHint));
+ Map<String, Versioned<String>> contexts = complete(eventMapTree.getChildren(path));
+ Map<String, String> eventMap = Maps.newHashMap();
+ if (Objects.isNull(contexts)) {
+ return eventMap;
+ }
+
+ for (Map.Entry<String, Versioned<String>> entry : contexts.entrySet()) {
+ eventMap.put(entry.getKey(), entry.getValue().value());
+ }
+ log.trace("getEventMapByHint returns eventMap {} ", eventMap);
+ return eventMap;
+ }
+
+ @Override
+ public boolean isEventMapPresent(String contextName) {
+ Map<String, String> eventMap = Maps.newHashMap();
+ Set<String> eventHintSet = hintSetPerCxtMap.get(contextName);
+ if (Objects.nonNull(eventHintSet)) {
+ log.trace("EventMap present for Context:{}", contextName);
+ return true;
+ } else {
+ log.trace("EventMap Doesnt exist for Context:{}", contextName);
+ return false;
+ }
+ }
+
+
+ @Override
+ public Map<String, Versioned<String>> getChildren(String path) throws WorkflowException {
+ DocumentPath dpath = DocumentPath.from(path);
+ Map<String, Versioned<String>> entries = complete(eventMapTree.getChildren(dpath));
+ return entries;
+ }
+
+ @Override
+ public DocumentPath getDocumentPath(String path) throws WorkflowException {
+ DocumentPath dpath = DocumentPath.from(path);
+ return dpath;
+ }
+
+ @Override
+ public ObjectNode asJsonTree() throws WorkflowException {
+
+ DocumentPath rootPath = DocumentPath.from(Lists.newArrayList("root"));
+ Map<String, Versioned<String>> eventmap = complete(eventMapTree.getChildren(rootPath));
+
+ ObjectNode rootNode = JsonNodeFactory.instance.objectNode();
+
+ for (Map.Entry<String, Versioned<String>> eventTypeEntry : eventmap.entrySet()) {
+
+ String eventType = eventTypeEntry.getKey();
+
+ ObjectNode eventTypeNode = JsonNodeFactory.instance.objectNode();
+ rootNode.put(eventType, eventTypeNode);
+
+ DocumentPath eventTypePath = DocumentPath.from(Lists.newArrayList("root", eventType));
+ Map<String, Versioned<String>> hintmap = complete(eventMapTree.getChildren(eventTypePath));
+
+ for (Map.Entry<String, Versioned<String>> hintEntry : hintmap.entrySet()) {
+
+ String hint = hintEntry.getKey();
+
+ ObjectNode hintNode = JsonNodeFactory.instance.objectNode();
+ eventTypeNode.put(hint, hintNode);
+
+ DocumentPath hintPath = DocumentPath.from(Lists.newArrayList("root", eventType, hint));
+ Map<String, Versioned<String>> contextmap = complete(eventMapTree.getChildren(hintPath));
+
+ for (Map.Entry<String, Versioned<String>> ctxtEntry : contextmap.entrySet()) {
+ hintNode.put(ctxtEntry.getKey(), ctxtEntry.getValue().value());
+ }
+ }
+ }
+
+ return rootNode;
+ }
+
+ private <T> T complete(CompletableFuture<T> future) throws WorkflowException {
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new WorkflowException(e.getCause().getMessage());
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof IllegalDocumentModificationException) {
+ throw new WorkflowException("Node or parent does not exist or is root or is not a Leaf Node",
+ e.getCause());
+ } else if (e.getCause() instanceof NoSuchDocumentPathException) {
+ return null;
+ } else {
+ throw new WorkflowException("Datastore operation failed", e.getCause());
+ }
+ }
+ }
+
+ private <T> T completeVersioned(CompletableFuture<Versioned<T>> future) throws WorkflowException {
+ return Optional.ofNullable(complete(future))
+ .map(Versioned::value)
+ .orElse(null);
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/DistributedWorkplaceStore.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/DistributedWorkplaceStore.java
new file mode 100644
index 0000000..1361758
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/DistributedWorkplaceStore.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BaseJsonNode;
+import com.fasterxml.jackson.databind.node.BigIntegerNode;
+import com.fasterxml.jackson.databind.node.BinaryNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.ContainerNode;
+import com.fasterxml.jackson.databind.node.DecimalNode;
+import com.fasterxml.jackson.databind.node.DoubleNode;
+import com.fasterxml.jackson.databind.node.FloatNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+import com.fasterxml.jackson.databind.node.JsonNodeCreator;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.fasterxml.jackson.databind.node.LongNode;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.POJONode;
+import com.fasterxml.jackson.databind.node.ShortNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.fasterxml.jackson.databind.node.ValueNode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.workflow.api.DataModelTree;
+import org.onosproject.workflow.api.DefaultWorkplace;
+import org.onosproject.workflow.api.DefaultWorkflowContext;
+import org.onosproject.workflow.api.JsonDataModelTree;
+import org.onosproject.workflow.api.ProgramCounter;
+import org.onosproject.workflow.api.SystemWorkflowContext;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowData;
+import org.onosproject.workflow.api.WorkflowState;
+import org.onosproject.workflow.api.Workplace;
+import org.onosproject.workflow.api.WorkflowDataEvent;
+import org.onosproject.workflow.api.WorkplaceStore;
+import org.onosproject.workflow.api.WorkplaceStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageException;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+public class DistributedWorkplaceStore
+ extends AbstractStore<WorkflowDataEvent, WorkplaceStoreDelegate> implements WorkplaceStore {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private ApplicationId appId;
+ private final Logger log = getLogger(getClass());
+
+ private final WorkplaceMapListener workplaceMapEventListener = new WorkplaceMapListener();
+ private ConsistentMap<String, WorkflowData> workplaceMap;
+ private Map<String, Workplace> localWorkplaceMap = Maps.newConcurrentMap();
+
+ private final WorkflowContextMapListener contextMapEventListener = new WorkflowContextMapListener();
+ private ConsistentMap<String, WorkflowData> contextMap;
+ private Map<String, WorkflowContext> localContextMap = Maps.newConcurrentMap();
+
+ private Map<String, Map<String, WorkflowContext>> localWorkplaceMemberMap = Maps.newConcurrentMap();
+
+ @Activate
+ public void activate() {
+
+ appId = coreService.registerApplication("org.onosproject.workplacestore");
+ log.info("appId=" + appId);
+
+ KryoNamespace workplaceNamespace = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(WorkflowData.class)
+ .register(Workplace.class)
+ .register(DefaultWorkplace.class)
+ .register(WorkflowContext.class)
+ .register(DefaultWorkflowContext.class)
+ .register(SystemWorkflowContext.class)
+ .register(WorkflowState.class)
+ .register(ProgramCounter.class)
+ .register(DataModelTree.class)
+ .register(JsonDataModelTree.class)
+ .register(List.class)
+ .register(ArrayList.class)
+ .register(JsonNode.class)
+ .register(ObjectNode.class)
+ .register(TextNode.class)
+ .register(LinkedHashMap.class)
+ .register(ArrayNode.class)
+ .register(BaseJsonNode.class)
+ .register(BigIntegerNode.class)
+ .register(BinaryNode.class)
+ .register(BooleanNode.class)
+ .register(ContainerNode.class)
+ .register(DecimalNode.class)
+ .register(DoubleNode.class)
+ .register(FloatNode.class)
+ .register(IntNode.class)
+ .register(JsonNodeType.class)
+ .register(LongNode.class)
+ .register(MissingNode.class)
+ .register(NullNode.class)
+ .register(NumericNode.class)
+ .register(POJONode.class)
+ .register(ShortNode.class)
+ .register(ValueNode.class)
+ .register(JsonNodeCreator.class)
+ .register(JsonNodeFactory.class)
+ .build();
+
+ localWorkplaceMap.clear();
+ workplaceMap = storageService.<String, WorkflowData>consistentMapBuilder()
+ .withSerializer(Serializer.using(workplaceNamespace))
+ .withName("workplace-map")
+ .withApplicationId(appId)
+ .build();
+ workplaceMap.addListener(workplaceMapEventListener);
+
+ localContextMap.clear();
+ contextMap = storageService.<String, WorkflowData>consistentMapBuilder()
+ .withSerializer(Serializer.using(workplaceNamespace))
+ .withName("workflow-context-map")
+ .withApplicationId(appId)
+ .build();
+ contextMap.addListener(contextMapEventListener);
+
+ workplaceMapEventListener.syncLocal();
+ contextMapEventListener.syncLocal();
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ workplaceMap.destroy();
+ localWorkplaceMap.clear();
+ contextMap.destroy();
+ localContextMap.clear();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void registerWorkplace(String name, Workplace workplace) throws StorageException {
+ workplaceMap.put(name, workplace);
+ }
+
+ @Override
+ public void removeWorkplace(String name) throws StorageException {
+ removeWorkplaceContexts(name);
+ workplaceMap.remove(name);
+ }
+
+ @Override
+ public Workplace getWorkplace(String name) throws StorageException {
+ return localWorkplaceMap.get(name);
+ }
+
+ @Override
+ public Collection<Workplace> getWorkplaces() throws StorageException {
+ return ImmutableList.copyOf(localWorkplaceMap.values());
+ }
+
+ @Override
+ public void commitWorkplace(String name, Workplace workplace, boolean handleEvent) throws StorageException {
+ workplace.setTriggerNext(handleEvent);
+ if (workplaceMap.containsKey(name)) {
+ workplaceMap.replace(name, workplace);
+ } else {
+ registerWorkplace(name, workplace);
+ }
+ }
+
+ @Override
+ public void registerContext(String name, WorkflowContext context) throws StorageException {
+ contextMap.put(name, context);
+ }
+
+ @Override
+ public void removeContext(String name) throws StorageException {
+ contextMap.remove(name);
+ }
+
+ @Override
+ public WorkflowContext getContext(String name) throws StorageException {
+ return localContextMap.get(name);
+ }
+
+ @Override
+ public void commitContext(String name, WorkflowContext context, boolean handleEvent) throws StorageException {
+ context.setTriggerNext(handleEvent);
+ if (contextMap.containsKey(name)) {
+ contextMap.replace(name, context);
+ } else {
+ registerContext(name, context);
+ }
+ }
+
+ @Override
+ public Collection<WorkflowContext> getContexts() throws StorageException {
+ return ImmutableList.copyOf(localContextMap.values());
+ }
+
+ @Override
+ public Collection<WorkflowContext> getWorkplaceContexts(String workplaceName) {
+ Map<String, WorkflowContext> ctxMap = localWorkplaceMemberMap.get(workplaceName);
+ if (ctxMap == null) {
+ return Collections.emptyList();
+ }
+
+ return ImmutableList.copyOf(ctxMap.values());
+ }
+
+ @Override
+ public void removeWorkplaceContexts(String workplaceName) {
+ for (WorkflowContext ctx : getWorkplaceContexts(workplaceName)) {
+ removeContext(ctx.name());
+ }
+ }
+
+ private class WorkplaceMapListener implements MapEventListener<String, WorkflowData> {
+
+ @Override
+ public void event(MapEvent<String, WorkflowData> event) {
+
+ Workplace newWorkplace = (Workplace) Versioned.valueOrNull(event.newValue());
+ Workplace oldWorkplace = (Workplace) Versioned.valueOrNull(event.oldValue());
+
+ log.info("WorkplaceMap event: {}", event);
+ switch (event.type()) {
+ case INSERT:
+ insert(newWorkplace);
+ notifyDelegate(new WorkflowDataEvent(WorkflowDataEvent.Type.INSERT, newWorkplace));
+ break;
+ case UPDATE:
+ update(newWorkplace);
+ notifyDelegate(new WorkflowDataEvent(WorkflowDataEvent.Type.UPDATE, newWorkplace));
+ break;
+ case REMOVE:
+ remove(oldWorkplace);
+ notifyDelegate(new WorkflowDataEvent(WorkflowDataEvent.Type.REMOVE, oldWorkplace));
+ break;
+ default:
+ }
+ }
+
+ private void insert(Workplace workplace) {
+ localWorkplaceMap.put(workplace.name(), workplace);
+ }
+
+ private void update(Workplace workplace) {
+ localWorkplaceMap.replace(workplace.name(), workplace);
+ }
+
+ private void remove(Workplace workplace) {
+ localWorkplaceMap.remove(workplace.name());
+ }
+
+ public void syncLocal() {
+ workplaceMap.values().stream().forEach(
+ x -> insert((Workplace) (x.value()))
+ );
+ }
+ }
+
+ private class WorkflowContextMapListener implements MapEventListener<String, WorkflowData> {
+
+ @Override
+ public void event(MapEvent<String, WorkflowData> event) {
+
+ WorkflowContext newContext = (WorkflowContext) Versioned.valueOrNull(event.newValue());
+ WorkflowContext oldContext = (WorkflowContext) Versioned.valueOrNull(event.oldValue());
+
+ log.debug("WorkflowContext event: {}", event);
+ switch (event.type()) {
+ case INSERT:
+ insert(newContext);
+ notifyDelegate(new WorkflowDataEvent(WorkflowDataEvent.Type.INSERT, newContext));
+ break;
+ case UPDATE:
+ update(newContext);
+ notifyDelegate(new WorkflowDataEvent(WorkflowDataEvent.Type.UPDATE, newContext));
+ break;
+ case REMOVE:
+ remove(oldContext);
+ notifyDelegate(new WorkflowDataEvent(WorkflowDataEvent.Type.REMOVE, oldContext));
+ break;
+ default:
+ }
+ }
+
+ /**
+ * Inserts workflow context on local hash map.
+ * @param context workflow context
+ */
+ private void insert(WorkflowContext context) {
+ String workplaceName = context.workplaceName();
+ Map<String, WorkflowContext> ctxMap = localWorkplaceMemberMap.get(workplaceName);
+ if (ctxMap == null) {
+ ctxMap = new HashMap<>();
+ localWorkplaceMemberMap.put(workplaceName, ctxMap);
+ }
+ ctxMap.put(context.name(), context);
+
+ localContextMap.put(context.name(), context);
+ }
+
+ /**
+ * Updates workflow context on local hash map.
+ * @param context workflow context
+ */
+ private void update(WorkflowContext context) {
+ String workplaceName = context.workplaceName();
+ Map<String, WorkflowContext> ctxMap = localWorkplaceMemberMap.get(workplaceName);
+ if (ctxMap == null) {
+ ctxMap = new HashMap<>();
+ localWorkplaceMemberMap.put(workplaceName, ctxMap);
+ }
+ ctxMap.put(context.name(), context);
+
+ localContextMap.put(context.name(), context);
+ }
+
+ /**
+ * Removes workflow context from local hash map.
+ * @param context workflow context
+ */
+ private void remove(WorkflowContext context) {
+ localContextMap.remove(context.name());
+
+ String workplaceName = context.workplaceName();
+ Map<String, WorkflowContext> ctxMap = localWorkplaceMemberMap.get(workplaceName);
+ if (ctxMap == null) {
+ log.error("remove-context: Failed to find workplace({}) in localWorkplaceMemberMap", workplaceName);
+ return;
+ }
+ ctxMap.remove(context.name());
+ if (ctxMap.size() == 0) {
+ localWorkplaceMemberMap.remove(workplaceName, ctxMap);
+ }
+ }
+
+ /**
+ * Synchronizes local hash map.
+ */
+ public void syncLocal() {
+ contextMap.values().stream().forEach(
+ x -> insert((WorkflowContext) (x.value()))
+ );
+ }
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/ECWorkFlowStore.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/ECWorkFlowStore.java
new file mode 100644
index 0000000..8cd56ec
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/ECWorkFlowStore.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.util.KryoNamespace;
+import org.slf4j.Logger;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.workflow.api.AbstractWorkflow;
+import org.onosproject.workflow.api.ImmutableListWorkflow;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.WorkflowAttribute;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+public class ECWorkFlowStore
+ extends AbstractStore<GroupEvent, GroupStoreDelegate> implements WorkflowStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private ApplicationId appId;
+ private EventuallyConsistentMap<URI, Workflow> workflowStore;
+ private Set<ClassLoader> classloaders = Sets.newConcurrentHashSet();
+
+ @Activate
+ public void activate() {
+
+ appId = coreService.registerApplication("org.onosproject.nfconfig");
+ log.info("appId=" + appId);
+
+ KryoNamespace.Builder workflowSerializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(URI.class)
+ .register(Workflow.class)
+ .register(AbstractWorkflow.class)
+ .register(ImmutableListWorkflow.class)
+ .register(List.class)
+ .register(ImmutableList.class)
+ .register(Class.class)
+ .register(WorkflowAttribute.class)
+ .register(Set.class)
+ .register(ImmutableSet.class)
+ .register(HashSet.class);
+
+ workflowStore = storageService.<URI, Workflow>eventuallyConsistentMapBuilder()
+ .withName("workflow-workplaceStore")
+ .withSerializer(workflowSerializer)
+ .withAntiEntropyPeriod(5, TimeUnit.SECONDS)
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .withTombstonesDisabled()
+ .build();
+
+ classloaders.add(this.getClass().getClassLoader());
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ workflowStore.destroy();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void register(Workflow workflow) {
+ workflowStore.put(workflow.id(), workflow);
+ }
+
+ @Override
+ public void unregister(URI id) {
+ workflowStore.remove(id);
+ }
+
+ @Override
+ public Workflow get(URI id) {
+ return workflowStore.get(id);
+ }
+
+ @Override
+ public Collection<Workflow> getAll() {
+ return workflowStore.values();
+ }
+
+ @Override
+ public void registerLocal(ClassLoader loader) {
+ classloaders.add(loader);
+ }
+
+ @Override
+ public void unregisterLocal(ClassLoader loader) {
+ classloaders.remove(loader);
+ }
+
+ @Override
+ public Class getClass(String name) throws ClassNotFoundException {
+ for (ClassLoader loader : classloaders) {
+ Class cl = null;
+ try {
+ cl = loader.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ // do nothing
+ }
+ if (cl != null) {
+ return cl;
+ }
+ }
+ throw new ClassNotFoundException(name);
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/HandlerTaskAccumulator.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/HandlerTaskAccumulator.java
new file mode 100644
index 0000000..4651cd4
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/HandlerTaskAccumulator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import org.onlab.util.AbstractAccumulator;
+import org.onosproject.workflow.api.HandlerTask;
+import org.onosproject.workflow.api.HandlerTaskBatchDelegate;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Timer;
+
+/**
+ * An accumulator for building batches of event task operations. Only one batch should
+ * be in process per instance at a time.
+ */
+public class HandlerTaskAccumulator extends AbstractAccumulator<HandlerTask> {
+
+ private static final int DEFAULT_MAX_EVENTS = 5000;
+ private static final int DEFAULT_MAX_IDLE_MS = 10;
+ private static final int DEFAULT_MAX_BATCH_MS = 50;
+
+ private static final Timer TIMER = new Timer("onos-workflow-handlertask-batching");
+
+ private final HandlerTaskBatchDelegate delegate;
+
+ private volatile boolean ready;
+
+ /**
+ * Creates an event-task operation accumulator.
+ *
+ * @param delegate the event-task batch delegate
+ */
+ protected HandlerTaskAccumulator(HandlerTaskBatchDelegate delegate) {
+ super(TIMER, DEFAULT_MAX_EVENTS, DEFAULT_MAX_BATCH_MS, DEFAULT_MAX_IDLE_MS);
+ this.delegate = delegate;
+ // Assume that the delegate is ready for workletType at the start
+ ready = true; //TODO validate the assumption that delegate is ready
+ }
+
+ @Override
+ public void processItems(List<HandlerTask> items) {
+ ready = false;
+ delegate.execute(epoch(items));
+ }
+
+ /**
+ * Gets epoch.
+ * @param ops handler tasks
+ * @return collection of collection of handler task.
+ */
+ private Collection<Collection<HandlerTask>> epoch(List<HandlerTask> ops) {
+
+ ListMultimap<String, HandlerTask> tasks = ArrayListMultimap.create();
+
+ // align event-tasks with context
+ for (HandlerTask op : ops) {
+ tasks.put(op.context().name(), op);
+ }
+
+ return tasks.asMap().values();
+ }
+
+ @Override
+ public boolean isReady() {
+ return ready;
+ }
+
+ /**
+ * Making accumulator to be ready.
+ */
+ public void ready() {
+ ready = true;
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
new file mode 100644
index 0000000..8f1d109
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
@@ -0,0 +1,843 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.store.service.StorageException;
+import org.onosproject.workflow.api.DefaultWorkplace;
+import org.onosproject.workflow.api.EventHintSupplier;
+import org.onosproject.workflow.api.EventTask;
+import org.onosproject.workflow.api.JsonDataModelInjector;
+import org.onosproject.workflow.api.JsonDataModelTree;
+import org.onosproject.workflow.api.ProgramCounter;
+import org.onosproject.workflow.api.SystemWorkflowContext;
+import org.onosproject.workflow.api.EventTimeoutTask;
+import org.onosproject.workflow.api.TimeoutTask;
+import org.onosproject.workflow.api.TimerChain;
+import org.onosproject.workflow.api.Worklet;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowData;
+import org.onosproject.workflow.api.ContextEventMapStore;
+import org.onosproject.workflow.api.WorkflowState;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.workflow.api.WorkflowBatchDelegate;
+import org.onosproject.workflow.api.WorkflowDataEvent;
+import org.onosproject.workflow.api.WorkflowDataListener;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.HandlerTask;
+import org.onosproject.workflow.api.HandlerTaskBatchDelegate;
+import org.onosproject.workflow.api.Workplace;
+import org.onosproject.workflow.api.WorkplaceStore;
+import org.onosproject.workflow.api.WorkplaceStoreDelegate;
+import org.onosproject.workflow.api.WorkflowExecutionService;
+import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.event.Event;
+import org.onosproject.net.intent.WorkPartitionService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newFixedThreadPool;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.workflow.api.WorkflowAttribute.REMOVE_AFTER_COMPLETE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+public class WorkFlowEngine extends AbstractListenerManager<WorkflowDataEvent, WorkflowDataListener>
+ implements WorkflowExecutionService {
+
+ protected static final Logger log = getLogger(WorkFlowEngine.class);
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkPartitionService partitionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkplaceStore workplaceStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkflowStore workflowStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ContextEventMapStore eventMapStore;
+
+ private final WorkplaceStoreDelegate workplaceStoreDelegate = this::post;
+
+ private final WorkflowBatchDelegate workflowBatchDelegate = new InternalWorkflowBatchDelegate();
+ private final WorkflowAccumulator workflowAccumulator = new WorkflowAccumulator(workflowBatchDelegate);
+
+ private final HandlerTaskBatchDelegate eventtaskBatchDelegate = new InternalHandlerTaskBatchDelegate();
+ private final HandlerTaskAccumulator eventtaskAccumulator = new HandlerTaskAccumulator(eventtaskBatchDelegate);
+
+ private ExecutorService workflowBatchExecutor;
+ private ExecutorService workflowExecutor;
+
+ private ExecutorService handlerTaskBatchExecutor;
+ private ExecutorService handlerTaskExecutor;
+
+ private static final int DEFAULT_WORKFLOW_THREADS = 12;
+ private static final int DEFAULT_EVENTTASK_THREADS = 12;
+ private static final int MAX_REGISTER_EVENTMAP_WAITS = 10;
+
+ private ScheduledExecutorService eventMapTriggerExecutor;
+
+ private TimerChain timerChain = new TimerChain();
+
+ private JsonDataModelInjector dataModelInjector = new JsonDataModelInjector();
+
+ public static final String APPID = "org.onosproject.workflow";
+ private ApplicationId appId;
+ private NodeId localNodeId;
+
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication(APPID);
+ workplaceStore.setDelegate(workplaceStoreDelegate);
+ localNodeId = clusterService.getLocalNode().id();
+ leadershipService.runForLeadership(appId.name());
+
+ workflowBatchExecutor = newSingleThreadExecutor(
+ groupedThreads("onos/workflow", "workflow-batch", log));
+ workflowExecutor = newFixedThreadPool(DEFAULT_WORKFLOW_THREADS,
+ groupedThreads("onos/workflow-exec", "worker-%d", log));
+ handlerTaskBatchExecutor = newSingleThreadExecutor(
+ groupedThreads("onos/workflow", "handlertask-batch", log));
+ handlerTaskExecutor = newFixedThreadPool(DEFAULT_EVENTTASK_THREADS,
+ groupedThreads("onos/handlertask-exec", "worker-%d", log));
+ eventMapTriggerExecutor = newSingleThreadScheduledExecutor(
+ groupedThreads("onos/workflow-engine", "eventmap-trigger-executor"));
+
+ (new WorkplaceWorkflow(this, workflowStore)).registerWorkflows();
+ JsonDataModelTree data = new JsonDataModelTree(JsonNodeFactory.instance.objectNode());
+ workplaceStore.registerWorkplace(Workplace.SYSTEM_WORKPLACE,
+ new DefaultWorkplace(Workplace.SYSTEM_WORKPLACE, data));
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ leadershipService.withdraw(appId.name());
+ workplaceStore.unsetDelegate(workplaceStoreDelegate);
+ workflowBatchExecutor.shutdown();
+ workflowExecutor.shutdown();
+ handlerTaskBatchExecutor.shutdown();
+ handlerTaskExecutor.shutdown();
+ eventMapTriggerExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void execInitWorklet(WorkflowContext context) {
+
+ Workflow workflow = workflowStore.get(context.workflowId());
+ if (workflow == null) {
+ log.error("Invalid workflow {}", context.workflowId());
+ return;
+ }
+
+ initWorkletExecution(context);
+ try {
+ Worklet initWorklet = workflow.init(context);
+ if (initWorklet != null) {
+
+ log.info("{} worklet.process:{}", context.name(), initWorklet.tag());
+ log.trace("{} context: {}", context.name(), context);
+
+ dataModelInjector.inject(initWorklet, context);
+ initWorklet.process(context);
+ dataModelInjector.inhale(initWorklet, context);
+
+ log.info("{} worklet.process(done): {}", context.name(), initWorklet.tag());
+ log.trace("{} context: {}", context.name(), context);
+ }
+
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ context.setCause(e.getMessage());
+ context.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(context.name(), context, false);
+ return;
+ }
+ // trigger the execution of next worklet.
+ workplaceStore.registerContext(context.name(), context);
+ }
+
+ @Override
+ public void eval(String contextName) {
+
+ final WorkflowContext latestContext = workplaceStore.getContext(contextName);
+ if (latestContext == null) {
+ log.error("Invalid workflow context {}", contextName);
+ return;
+ }
+
+ initWorkletExecution(latestContext);
+
+ workplaceStore.commitContext(latestContext.name(), latestContext, true);
+ }
+
+ @Override
+ public void eventMapTrigger(Event event, EventHintSupplier supplier) {
+
+ if (event.subject() instanceof SystemWorkflowContext) {
+ return;
+ }
+
+ Map<String, String> eventMap;
+
+ String eventHint;
+ try {
+ eventHint = supplier.apply(event);
+ } catch (Throwable e) {
+ log.error("Exception: ", e);
+ return;
+ }
+ if (eventHint == null) {
+ // do nothing
+ log.error("Invalid eventHint, event: {}", event);
+ return;
+ }
+
+ try {
+ eventMap = eventMapStore.getEventMapByHint(event.getClass().getName(), eventHint);
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ return;
+ }
+
+ if (Objects.isNull(eventMap) || eventMap.isEmpty()) {
+ // do nothing;
+ log.debug("Invalid eventMap, event: {}", event);
+ return;
+ }
+
+ for (Map.Entry<String, String> entry : eventMap.entrySet()) {
+ String contextName = entry.getKey();
+ String strProgramCounter = entry.getValue();
+ ProgramCounter pc;
+ try {
+ pc = ProgramCounter.valueOf(strProgramCounter);
+ } catch (IllegalArgumentException e) {
+ log.error("Exception: ", e);
+ return;
+ }
+
+ WorkflowContext context = workplaceStore.getContext(contextName);
+ if (Objects.isNull(context)) {
+ log.info("Invalid context: {}, event: {}", contextName, event);
+ continue;
+ }
+ EventTask eventtask = null;
+ try {
+ eventtask = EventTask.builder()
+ .event(event)
+ .eventHint(eventHint)
+ .context(context)
+ .programCounter(pc)
+ .build();
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ }
+
+ log.debug("eventtaskAccumulator.add: task: {}", eventtask);
+ if (!Objects.isNull(eventtask)) {
+ eventtaskAccumulator.add(eventtask);
+ }
+ }
+ }
+
+ @Override
+ public void registerEventMap(Class<? extends Event> eventType, Set<String> eventHintSet,
+ String contextName, String programCounterString) throws WorkflowException {
+ eventMapStore.registerEventMap(eventType.getName(), eventHintSet, contextName, programCounterString);
+ for (String eventHint : eventHintSet) {
+ for (int i = 0; i < MAX_REGISTER_EVENTMAP_WAITS; i++) {
+ Map<String, String> eventMap = eventMapStore.getEventMapByHint(eventType.getName(), eventHint);
+ if (eventMap != null && eventMap.containsKey(contextName)) {
+ break;
+ }
+ try {
+ log.info("sleep {}", i);
+ Thread.sleep(10L * (i + 1));
+ } catch (InterruptedException e) {
+ log.error("Exception: ", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ }
+
+ @Override
+ protected void post(WorkflowDataEvent event) {
+
+ if (event.subject() == null || !isRelevant(event.subject())) {
+ log.debug("ignore event {}", event);
+ return;
+ }
+
+ // trigger next worklet selection
+ WorkflowData dataModelContainer = event.subject();
+ switch (event.type()) {
+ case INSERT:
+ case UPDATE:
+ if (dataModelContainer.triggerNext()) {
+ log.debug("workflowAccumulator.add: {}", dataModelContainer);
+ workflowAccumulator.add(dataModelContainer);
+ } else {
+ log.debug("pass-workflowAccumulator.add: {}", dataModelContainer);
+ }
+ break;
+ case REMOVE:
+ break;
+ default:
+ }
+
+ // trigger EventTask for WorkflowDataEvent
+ eventMapTriggerExecutor.submit(
+ () -> eventMapTrigger(
+ event,
+ // event hint supplier
+ (ev) -> {
+ if (ev == null || ev.subject() == null) {
+ return null;
+ }
+
+ if (ev.subject() instanceof WorkflowData) {
+ return ((WorkflowData) ev.subject()).name();
+ } else {
+ return null;
+ }
+ }
+ )
+ );
+ }
+
+ /**
+ * Checks whether this workflow data job is relevant to this ONOS node.
+ * @param job workflow data
+ * @return checking result
+ */
+ private boolean isRelevant(WorkflowData job) {
+ // distributes event processing with work-partition
+ return partitionService.isMine(job.distributor(), this::stringHash);
+ }
+
+ /**
+ * Gets hash of the string.
+ * @param str string to get a hash
+ * @return hash value
+ */
+ public Long stringHash(String str) {
+ return UUID.nameUUIDFromBytes(str.getBytes()).getMostSignificantBits();
+ }
+
+ /**
+ * Class for handler task batch delegation.
+ */
+ private class InternalHandlerTaskBatchDelegate implements HandlerTaskBatchDelegate {
+ @Override
+ public void execute(Collection<Collection<HandlerTask>> operations) {
+ log.debug("Execute {} operation(s).", operations.size());
+
+ CompletableFuture.runAsync(() -> {
+ List<CompletableFuture<Collection<HandlerTask>>> futures = operations.stream()
+ .map(
+ x -> CompletableFuture.completedFuture(x)
+ .thenApplyAsync(WorkFlowEngine.this::processHandlerTask, handlerTaskExecutor)
+ .exceptionally(e -> null)
+ )
+ .collect(Collectors.toList());
+
+ // waiting the completion of futures
+ futures.parallelStream().forEach(x -> x.join());
+
+ }, handlerTaskBatchExecutor).exceptionally(e -> {
+ log.error("Error submitting batches:", e);
+ return null;
+ }).thenRun(eventtaskAccumulator::ready);
+ }
+ }
+
+ /**
+ * Initializes worklet execution.
+ * @param context workflow context
+ */
+ private void initWorkletExecution(WorkflowContext context) {
+ context.setState(WorkflowState.RUNNING);
+ context.setCause("");
+ context.setWorkflowExecutionService(this);
+ context.setWorkflowStore(workflowStore);
+ context.setWorkplaceStore(workplaceStore);
+ context.waitCompletion(null, null, null, 0L);
+ context.setTriggerNext(false);
+ }
+
+ /**
+ * Processes handler tasks.
+ * @param tasks handler tasks
+ * @return handler tasks processed
+ */
+ private Collection<HandlerTask> processHandlerTask(Collection<HandlerTask> tasks) {
+
+ for (HandlerTask task : tasks) {
+ if (task instanceof EventTimeoutTask) {
+ execEventTimeoutTask((EventTimeoutTask) task);
+ } else if (task instanceof TimeoutTask) {
+ execTimeoutTask((TimeoutTask) task);
+ } else if (task instanceof EventTask) {
+ execEventTask((EventTask) task);
+ } else {
+ log.error("Unsupported handler task {}", task);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Executes event task.
+ * @param task event task
+ * @return event task
+ */
+ private EventTask execEventTask(EventTask task) {
+
+ if (!eventMapStore.isEventMapPresent(task.context().name())) {
+ log.trace("EventMap doesnt exist for taskcontext:{}", task.context().name());
+ return task;
+ }
+
+ log.debug("execEventTask- task: {}, hash: {}", task, stringHash(task.context().distributor()));
+
+ WorkflowContext context = (WorkflowContext) (task.context());
+ Workflow workflow = workflowStore.get(context.workflowId());
+ if (workflow == null) {
+ log.error("Invalid workflow {}", context.workflowId());
+ return task;
+ }
+
+ WorkflowContext latestContext = workplaceStore.getContext(context.name());
+ if (latestContext == null) {
+ log.error("Invalid workflow context {}", context.name());
+ return task;
+ }
+
+ try {
+ if (!Objects.equals(latestContext.current(), task.programCounter())) {
+ log.error("Current worklet({}) is not mismatched with task work({}). Ignored.",
+ latestContext.current(), task.programCounter());
+ return task;
+ }
+
+ Worklet worklet = workflow.getWorkletInstance(task.programCounter().workletType());
+ if (Worklet.Common.COMPLETED.equals(worklet) || Worklet.Common.INIT.equals(worklet)) {
+ log.error("Current worklet is {}, Ignored", worklet);
+ return task;
+ }
+
+ initWorkletExecution(latestContext);
+
+ log.info("{} worklet.isCompleted:{}", latestContext.name(), worklet.tag());
+ log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
+
+ dataModelInjector.inject(worklet, latestContext);
+ boolean completed = worklet.isCompleted(latestContext, task.event());
+ dataModelInjector.inhale(worklet, latestContext);
+
+ if (completed) {
+
+ log.info("{} worklet.isCompleted(true):{}", latestContext.name(), worklet.tag());
+ log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
+
+ eventMapStore.unregisterEventMap(
+ task.eventType(), latestContext.name());
+
+ //completed case
+ //increase program counter
+ ProgramCounter pc = latestContext.current();
+ latestContext.setCurrent(workflow.increased(pc));
+
+ workplaceStore.commitContext(latestContext.name(), latestContext, true);
+ return null;
+ } else {
+
+ log.info("{} worklet.isCompleted(false):{}", latestContext.name(), worklet.tag());
+ log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
+
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ }
+
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ } catch (StorageException e) {
+ log.error("Exception: ", e);
+ // StorageException does not commit context.
+ } catch (Exception e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ }
+
+ return task;
+ }
+
+ /**
+ * Executes event timeout task.
+ * @param task event timeout task
+ * @return handler task
+ */
+ private HandlerTask execEventTimeoutTask(EventTimeoutTask task) {
+
+ if (!eventMapStore.isEventMapPresent(task.context().name())) {
+ log.trace("EventMap doesnt exist for taskcontext:{}", task.context().name());
+ return task;
+ }
+
+ log.debug("execEventTimeoutTask- task: {}, hash: {}", task, stringHash(task.context().distributor()));
+
+ WorkflowContext context = (WorkflowContext) (task.context());
+ Workflow workflow = workflowStore.get(context.workflowId());
+ if (workflow == null) {
+ log.error("execEventTimeoutTask: Invalid workflow {}", context.workflowId());
+ return task;
+ }
+
+ WorkflowContext latestContext = workplaceStore.getContext(context.name());
+ if (latestContext == null) {
+ log.error("execEventTimeoutTask: Invalid workflow context {}", context.name());
+ return task;
+ }
+
+ try {
+ if (!Objects.equals(latestContext.current(), task.programCounter())) {
+ log.error("execEventTimeoutTask: Current worklet({}) is not mismatched with task work({}). Ignored.",
+ latestContext.current(), task.programCounter());
+ return task;
+ }
+
+ Worklet worklet = workflow.getWorkletInstance(task.programCounter().workletType());
+ if (worklet == Worklet.Common.COMPLETED || worklet == Worklet.Common.INIT) {
+ log.error("execEventTimeoutTask: Current worklet is {}, Ignored", worklet);
+ return task;
+ }
+
+ initWorkletExecution(latestContext);
+
+ eventMapStore.unregisterEventMap(task.eventType(), latestContext.name());
+
+ log.info("{} worklet.timeout(for event):{}", latestContext.name(), worklet.tag());
+ log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
+
+ dataModelInjector.inject(worklet, latestContext);
+ worklet.timeout(latestContext);
+ dataModelInjector.inhale(worklet, latestContext);
+
+ log.info("{} worklet.timeout(for event)(done):{}", latestContext.name(), worklet.tag());
+ log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
+
+
+ workplaceStore.commitContext(latestContext.name(), latestContext, latestContext.triggerNext());
+
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ } catch (StorageException e) {
+ log.error("Exception: ", e);
+ // StorageException does not commit context.
+ } catch (Exception e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ }
+
+ return task;
+ }
+
+ /**
+ * Executes timeout task.
+ * @param task time out task
+ * @return handler task
+ */
+ private HandlerTask execTimeoutTask(TimeoutTask task) {
+
+ log.debug("execTimeoutTask- task: {}, hash: {}", task, stringHash(task.context().distributor()));
+
+ WorkflowContext context = (WorkflowContext) (task.context());
+ Workflow workflow = workflowStore.get(context.workflowId());
+ if (workflow == null) {
+ log.error("execTimeoutTask: Invalid workflow {}", context.workflowId());
+ return task;
+ }
+
+ WorkflowContext latestContext = workplaceStore.getContext(context.name());
+ if (latestContext == null) {
+ log.error("execTimeoutTask: Invalid workflow context {}", context.name());
+ return task;
+ }
+
+ try {
+ if (!Objects.equals(latestContext.current(), task.programCounter())) {
+ log.error("execTimeoutTask: Current worklet({}) is not mismatched with task work({}). Ignored.",
+ latestContext.current(), task.programCounter());
+ return task;
+ }
+
+ Worklet worklet = workflow.getWorkletInstance(task.programCounter().workletType());
+ if (worklet == Worklet.Common.COMPLETED || worklet == Worklet.Common.INIT) {
+ log.error("execTimeoutTask: Current worklet is {}, Ignored", worklet);
+ return task;
+ }
+
+ initWorkletExecution(latestContext);
+
+ log.info("{} worklet.timeout:{}", latestContext.name(), worklet.tag());
+ log.trace("{} context: {}", latestContext.name(), latestContext);
+
+ dataModelInjector.inject(worklet, latestContext);
+ worklet.timeout(latestContext);
+ dataModelInjector.inhale(worklet, latestContext);
+
+ log.info("{} worklet.timeout(done):{}", latestContext.name(), worklet.tag());
+ log.trace("{} context: {}", latestContext.name(), latestContext);
+
+ workplaceStore.commitContext(latestContext.name(), latestContext, latestContext.triggerNext());
+
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ } catch (StorageException e) {
+ log.error("Exception: ", e);
+ // StorageException does not commit context.
+ } catch (Exception e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ }
+
+ return task;
+ }
+
+ /**
+ * Class for delegation of workflow batch execution.
+ */
+ private class InternalWorkflowBatchDelegate implements WorkflowBatchDelegate {
+ @Override
+ public void execute(Collection<WorkflowData> operations) {
+ log.debug("Execute {} operation(s).", operations.size());
+
+ CompletableFuture.runAsync(() -> {
+ List<CompletableFuture<WorkflowData>> futures = operations.stream()
+ .map(
+ x -> CompletableFuture.completedFuture(x)
+ .thenApplyAsync(WorkFlowEngine.this::execWorkflow, workflowExecutor)
+ .exceptionally(e -> null)
+ )
+ .collect(Collectors.toList());
+
+ // waiting the completion of futures
+ futures.parallelStream().forEach(x -> x.join());
+
+ }, workflowBatchExecutor).exceptionally(e -> {
+ log.error("Error submitting batches:", e);
+ return null;
+ }).thenRun(workflowAccumulator::ready);
+ }
+ }
+
+ /**
+ * Executes workflow.
+ * @param dataModelContainer workflow data model container(workflow or workplace)
+ * @return
+ */
+ private WorkflowData execWorkflow(WorkflowData dataModelContainer) {
+ if (dataModelContainer instanceof WorkflowContext) {
+ return execWorkflowContext((WorkflowContext) dataModelContainer);
+ } else if (dataModelContainer instanceof Workplace) {
+ return execWorkplace((Workplace) dataModelContainer);
+ } else {
+ log.error("Invalid context {}", dataModelContainer);
+ return null;
+ }
+ }
+
+ /**
+ * Executes workflow context.
+ * @param context workflow context
+ * @return workflow context
+ */
+ private WorkflowContext execWorkflowContext(WorkflowContext context) {
+
+ Workflow workflow = workflowStore.get(context.workflowId());
+ if (workflow == null) {
+ log.error("Invalid workflow {}", context.workflowId());
+ return null;
+ }
+
+ final WorkflowContext latestContext = workplaceStore.getContext(context.name());
+ if (latestContext == null) {
+ log.error("Invalid workflow context {}", context.name());
+ return null;
+ }
+
+ initWorkletExecution(latestContext);
+
+ try {
+ final ProgramCounter pc = workflow.next(latestContext);
+ final Worklet worklet = workflow.getWorkletInstance(pc.workletType());
+
+ if (worklet == Worklet.Common.INIT) {
+ log.error("workflow.next gave INIT. It cannot be executed (context: {})", context.name());
+ return latestContext;
+ }
+
+ latestContext.setCurrent(pc);
+ if (worklet == Worklet.Common.COMPLETED) {
+
+ if (workflow.attributes().contains(REMOVE_AFTER_COMPLETE)) {
+ workplaceStore.removeContext(latestContext.name());
+ return null;
+ } else {
+ latestContext.setState(WorkflowState.IDLE);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ return latestContext;
+ }
+ }
+
+ log.info("{} worklet.process:{}", latestContext.name(), worklet.tag());
+ log.trace("{} context: {}", latestContext.name(), latestContext);
+
+ dataModelInjector.inject(worklet, latestContext);
+ worklet.process(latestContext);
+ dataModelInjector.inhale(worklet, latestContext);
+
+ log.info("{} worklet.process(done): {}", latestContext.name(), worklet.tag());
+ log.trace("{} context: {}", latestContext.name(), latestContext);
+
+
+ if (latestContext.completionEventType() != null) {
+ if (latestContext.completionEventGenerator() == null) {
+ String msg = String.format("Invalid exepecting event(%s), generator(%s)",
+ latestContext.completionEventType(),
+ latestContext.completionEventGenerator());
+ throw new WorkflowException(msg);
+ }
+
+ registerEventMap(latestContext.completionEventType(), latestContext.completionEventHints(),
+ latestContext.name(), pc.toString());
+
+ latestContext.completionEventGenerator().apply();
+
+ if (latestContext.completionEventTimeout() != 0L) {
+ final EventTimeoutTask eventTimeoutTask = EventTimeoutTask.builder()
+ .context(latestContext)
+ .programCounter(pc)
+ .eventType(latestContext.completionEventType().getName())
+ .eventHintSet(latestContext.completionEventHints())
+ .build();
+ timerChain.schedule(latestContext.completionEventTimeout(),
+ () -> {
+ eventtaskAccumulator.add(eventTimeoutTask);
+ });
+ }
+ } else {
+ if (latestContext.completionEventTimeout() != 0L) {
+ final TimeoutTask timeoutTask = TimeoutTask.builder()
+ .context(latestContext)
+ .programCounter(pc)
+ .build();
+
+ timerChain.schedule(latestContext.completionEventTimeout(),
+ () -> {
+ eventtaskAccumulator.add(timeoutTask);
+ });
+ } else {
+ //completed case
+ // increase program counter
+ latestContext.setCurrent(workflow.increased(pc));
+ }
+ }
+
+ workplaceStore.commitContext(latestContext.name(), latestContext, latestContext.triggerNext());
+
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ } catch (StorageException e) {
+ log.error("Exception: ", e);
+ // StorageException does not commit context.
+ } catch (Exception e) {
+ log.error("Exception: ", e);
+ latestContext.setCause(e.getMessage());
+ latestContext.setState(WorkflowState.EXCEPTION);
+ workplaceStore.commitContext(latestContext.name(), latestContext, false);
+ }
+
+ return latestContext;
+ }
+
+ /**
+ * Execute workplace.
+ * @param workplace workplace
+ * @return workplace
+ */
+ private Workplace execWorkplace(Workplace workplace) {
+
+ return null;
+ }
+
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowAccumulator.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowAccumulator.java
new file mode 100644
index 0000000..d13b66f
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowAccumulator.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.google.common.collect.Maps;
+import org.onlab.util.AbstractAccumulator;
+import org.onosproject.workflow.api.WorkflowData;
+import org.onosproject.workflow.api.WorkflowBatchDelegate;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+
+/**
+ * An accumulator for building batches of workflow operations. Only one batch should
+ * be in process per instance at a time.
+ */
+public class WorkflowAccumulator extends AbstractAccumulator<WorkflowData> {
+
+ private static final int DEFAULT_MAX_EVENTS = 1000;
+ private static final int DEFAULT_MAX_IDLE_MS = 10;
+ private static final int DEFAULT_MAX_BATCH_MS = 50;
+
+ private static final Timer TIMER = new Timer("onos-workflow-op-batching");
+
+ private final WorkflowBatchDelegate delegate;
+
+ private volatile boolean ready;
+
+ /**
+ * Creates an intent operation accumulator.
+ *
+ * @param delegate the intent batch delegate
+ */
+ protected WorkflowAccumulator(WorkflowBatchDelegate delegate) {
+ super(TIMER, DEFAULT_MAX_EVENTS, DEFAULT_MAX_BATCH_MS, DEFAULT_MAX_IDLE_MS);
+ this.delegate = delegate;
+ // Assume that the delegate is ready for worklettype at the start
+ ready = true; //TODO validate the assumption that delegate is ready
+ }
+
+ @Override
+ public void processItems(List<WorkflowData> items) {
+ ready = false;
+ delegate.execute(reduce(items));
+ }
+
+ private Collection<WorkflowData> reduce(List<WorkflowData> ops) {
+ Map<String, WorkflowData> map = Maps.newHashMap();
+ for (WorkflowData op : ops) {
+ map.put(op.name(), op);
+ }
+ //TODO check the version... or maybe workplaceStore will handle this.
+ return map.values();
+ }
+
+ @Override
+ public boolean isReady() {
+ return ready;
+ }
+
+ /**
+ * Making accumulator to be ready.
+ */
+ public void ready() {
+ ready = true;
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowManager.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowManager.java
new file mode 100644
index 0000000..66bd709
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowManager.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.workflow.api.WorkflowService;
+import org.onosproject.workflow.api.WorkflowExecutionService;
+import org.onosproject.workflow.api.WorkplaceStore;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.workflow.api.WorkplaceDescription;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.DefaultWorkplace;
+import org.onosproject.workflow.api.JsonDataModelTree;
+import org.onosproject.workflow.api.WorkflowDescription;
+import org.onosproject.workflow.api.Workplace;
+import org.onosproject.workflow.api.WorkflowDataModelException;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.Worklet;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.JsonDataModel;
+import org.slf4j.Logger;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+public class WorkflowManager implements WorkflowService {
+
+ protected static final Logger log = getLogger(WorkflowManager.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private WorkflowExecutionService workflowExecutionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkplaceStore workplaceStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkflowStore workflowStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private NetworkConfigService networkConfigService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private NetworkConfigRegistry networkConfigRegistry;
+
+ private WorkflowNetConfigListener netcfgListener;
+
+ @Activate
+ public void activate() {
+ netcfgListener = new WorkflowNetConfigListener(this);
+ networkConfigRegistry.registerConfigFactory(netcfgListener.getConfigFactory());
+ networkConfigService.addListener(netcfgListener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ networkConfigService.removeListener(netcfgListener);
+ networkConfigRegistry.unregisterConfigFactory(netcfgListener.getConfigFactory());
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createWorkplace(WorkplaceDescription wpDesc) throws WorkflowException {
+ log.info("createWorkplace: {}", wpDesc);
+
+ JsonNode root;
+ if (wpDesc.data().isPresent()) {
+ root = wpDesc.data().get();
+ } else {
+ root = JsonNodeFactory.instance.objectNode();
+ }
+ DefaultWorkplace workplace =
+ new DefaultWorkplace(wpDesc.name(), new JsonDataModelTree(root));
+ workplaceStore.registerWorkplace(wpDesc.name(), workplace);
+ }
+
+ @Override
+ public void removeWorkplace(WorkplaceDescription wpDesc) throws WorkflowException {
+ log.info("removeWorkplace: {}", wpDesc);
+ //TODO: Removing workflows belong to this workplace
+ workplaceStore.removeWorkplace(wpDesc.name());
+ }
+
+ @Override
+ public void clearWorkplace() throws WorkflowException {
+ log.info("clearWorkplace");
+ workplaceStore.getWorkplaces().stream()
+ .filter(wp -> !Objects.equals(wp.name(), Workplace.SYSTEM_WORKPLACE))
+ .forEach(wp -> workplaceStore.removeWorkplace(wp.name()));
+ }
+
+ @Override
+ public void invokeWorkflow(WorkflowDescription wfDesc) throws WorkflowException {
+ invokeWorkflow(wfDesc.toJson());
+ }
+
+ @Override
+ public void invokeWorkflow(JsonNode worklowDescJson) throws WorkflowException {
+ log.info("invokeWorkflow: {}", worklowDescJson);
+ Workplace workplace = workplaceStore.getWorkplace(Workplace.SYSTEM_WORKPLACE);
+ if (Objects.isNull(workplace)) {
+ throw new WorkflowException("Invalid system workplace");
+ }
+
+ Workflow workflow = workflowStore.get(URI.create(worklowDescJson.get("id").asText()));
+ if (Objects.isNull(workflow)) {
+ throw new WorkflowException("Invalid Workflow");
+ }
+
+ checkWorkflowSchema(workflow, worklowDescJson);
+
+ Workflow wfCreationWf = workflowStore.get(URI.create(WorkplaceWorkflow.WF_CREATE_WORKFLOW));
+ if (Objects.isNull(wfCreationWf)) {
+ throw new WorkflowException("Invalid workflow " + WorkplaceWorkflow.WF_CREATE_WORKFLOW);
+ }
+
+ WorkflowContext context = wfCreationWf.buildSystemContext(workplace, new JsonDataModelTree(worklowDescJson));
+ workflowExecutionService.execInitWorklet(context);
+ }
+
+ /**
+ * Checks if the type of worklet is same as that of wfdesc Json.
+ *
+ * @param workflow workflow
+ * @param worklowDescJson jsonNode
+ * @throws WorkflowException workflow exception
+ */
+ private void checkWorkflowSchema(Workflow workflow, JsonNode worklowDescJson) throws WorkflowException {
+
+ List<String> errors = new ArrayList<>();
+
+ JsonNode dataNode = worklowDescJson.get("data");
+ if (Objects.isNull(dataNode) || dataNode instanceof MissingNode) {
+ errors.add("workflow description json does not have 'data'");
+ throw new WorkflowDataModelException(workflow.id(), worklowDescJson, errors);
+ }
+
+ for (String workletType : workflow.getWorkletTypeList()) {
+
+ Worklet worklet = workflow.getWorkletInstance(workletType);
+ if (Worklet.Common.COMPLETED.equals(worklet) || Worklet.Common.INIT.equals(worklet)) {
+ continue;
+ }
+
+ Class cls = worklet.getClass();
+ for (Field field : cls.getDeclaredFields()) {
+
+ if (field.isSynthetic()) {
+ continue;
+ }
+
+ for (Annotation annotation : field.getAnnotations()) {
+
+ if (annotation instanceof JsonDataModel) {
+
+ JsonDataModel jsonDataModel = (JsonDataModel) annotation;
+ Matcher matcher = Pattern.compile("(\\w+)").matcher(jsonDataModel.path());
+ if (!matcher.find()) {
+ throw new WorkflowException(
+ "Invalid Json Data Model Path(" + jsonDataModel.path() + ") in " + worklet.tag());
+ }
+ String path = matcher.group(1);
+
+ Optional<String> optError =
+ getJsonNodeDataError(dataNode, worklet, field, path, jsonDataModel.optional());
+
+ if (optError.isPresent()) {
+ errors.add(optError.get());
+ }
+ }
+ }
+ }
+ }
+
+ if (!errors.isEmpty()) {
+ throw new WorkflowDataModelException(workflow.id(), worklowDescJson, errors);
+ }
+ }
+
+ private Optional<String> getJsonNodeDataError(
+ JsonNode dataNode, Worklet worklet, Field field, String path, boolean isOptional) throws WorkflowException {
+
+ // Checking the existence of path in dataNode
+ JsonNode pathNode = dataNode.get(path);
+ if (Objects.isNull(pathNode) || pathNode instanceof MissingNode) {
+
+ if (isOptional) {
+ return Optional.empty();
+
+ } else {
+ return Optional.of("data doesn't have '" + path + "' in worklet<" + worklet.tag() + ">");
+ }
+ }
+
+ // Checking the type of path
+ JsonNodeType type = pathNode.getNodeType();
+
+ if (Objects.isNull(type)) {
+ throw new WorkflowException("Invalid type for " + pathNode);
+ }
+
+ switch (type) {
+ case NUMBER:
+ if (!(field.getType().isAssignableFrom(Integer.class))) {
+ return Optional.of("'" + path + "<NUMBER>' cannot be assigned to " +
+ field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
+ }
+ break;
+ case STRING:
+ if (!(field.getType().isAssignableFrom(String.class))) {
+ return Optional.of("'" + path + "<STRING>' cannot be assigned to " +
+ field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
+ }
+ break;
+ case BOOLEAN:
+ if (!(field.getType().isAssignableFrom(Boolean.class))) {
+ return Optional.of("'" + path + "<BOOLEAN>' cannot be assigned to " +
+ field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
+ }
+ break;
+ case OBJECT:
+ if (!(field.getType().isAssignableFrom(JsonNode.class))) {
+ return Optional.of("'" + path + "<OBJECT>' cannot be assigned to " +
+ field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
+ }
+ break;
+ case ARRAY:
+ if (!(field.getType().isAssignableFrom(ArrayNode.class))) {
+ return Optional.of("'" + path + "<ARRAY>' cannot be assigned to " +
+ field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
+ }
+ break;
+ default:
+ return Optional.of("'" + path + "<" + type + ">' is not supported");
+ }
+
+ return Optional.empty();
+ }
+
+ @Override
+ public void terminateWorkflow(WorkflowDescription wfDesc) throws WorkflowException {
+ log.info("terminateWorkflow: {}", wfDesc);
+ if (Objects.nonNull(workplaceStore.getContext(wfDesc.workflowContextName()))) {
+ workplaceStore.removeContext(wfDesc.workflowContextName());
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowNetConfig.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowNetConfig.java
new file mode 100644
index 0000000..2bca555
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowNetConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+import org.onosproject.workflow.api.DefaultRpcDescription;
+import org.onosproject.workflow.api.RpcDescription;
+import org.onosproject.workflow.api.WorkflowException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+
+public class WorkflowNetConfig extends Config<ApplicationId> {
+
+ private static final Logger log = LoggerFactory.getLogger(WorkflowNetConfig.class);
+
+ /**
+ * Workflow RPC pointer.
+ */
+ private static final String RPC_PTR = "/rpc";
+
+ public Collection<RpcDescription> getRpcDescriptions() throws WorkflowException {
+
+ JsonNode node = object.at(RPC_PTR);
+ if (!(node instanceof ArrayNode)) {
+ throw new WorkflowException("invalid rpc for " + object);
+ }
+ ArrayNode rpcArrayNode = (ArrayNode) node;
+
+ List<RpcDescription> rpcDescriptions = new ArrayList<>();
+ for (JsonNode rpcNode : rpcArrayNode) {
+ rpcDescriptions.add(DefaultRpcDescription.valueOf(rpcNode));
+ }
+
+ return rpcDescriptions;
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowNetConfigListener.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowNetConfigListener.java
new file mode 100644
index 0000000..bb7f875
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowNetConfigListener.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.workflow.api.DefaultWorkflowDescription;
+import org.onosproject.workflow.api.RpcDescription;
+import org.onosproject.workflow.api.WorkflowService;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.DefaultWorkplaceDescription;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+
+public class WorkflowNetConfigListener implements NetworkConfigListener {
+
+ private static final Logger log = LoggerFactory.getLogger(WorkflowNetConfigListener.class);
+
+ public static final String CONFIG_KEY = "workflow";
+ public static final String EXECUTOR_GROUPNAME = "onos/workflow-netcfg";
+ public static final String EXECUTOR_PATTERN = "netcfg-event-handler";
+
+ private final ConfigFactory<ApplicationId, WorkflowNetConfig> configFactory =
+ new ConfigFactory<ApplicationId, WorkflowNetConfig>(
+ SubjectFactories.APP_SUBJECT_FACTORY, WorkflowNetConfig.class, CONFIG_KEY) {
+ @Override
+ public WorkflowNetConfig createConfig() {
+ return new WorkflowNetConfig();
+ }
+ };
+
+ private final WorkflowService workflowService;
+
+ private final ScheduledExecutorService executor =
+ newSingleThreadScheduledExecutor(groupedThreads(EXECUTOR_GROUPNAME, EXECUTOR_PATTERN));
+
+ public WorkflowNetConfigListener(WorkflowService workflowService) {
+ this.workflowService = workflowService;
+ }
+
+ public ConfigFactory<ApplicationId, WorkflowNetConfig> getConfigFactory() {
+ return configFactory;
+ }
+
+ @Override
+ public boolean isRelevant(NetworkConfigEvent event) {
+ return event.config().isPresent() && event.config().get() instanceof WorkflowNetConfig;
+ }
+
+ @Override
+ public void event(NetworkConfigEvent event) {
+ log.info("Configuration event: {}", event);
+ switch (event.type()) {
+ case CONFIG_ADDED:
+ case CONFIG_UPDATED:
+ if (!event.config().isPresent()) {
+ log.error("No configuration found");
+ return;
+ }
+ WorkflowNetConfig config = (WorkflowNetConfig) event.config().get();
+
+ //Single thread executor(locking is not required)
+ executor.execute(new Handler(workflowService, config));
+ break;
+ default:
+ break;
+ }
+ }
+
+ public static class Handler implements Runnable {
+
+ private WorkflowService workflowService;
+ private WorkflowNetConfig config;
+
+ public Handler(WorkflowService workflowService, WorkflowNetConfig config) {
+ this.workflowService = workflowService;
+ this.config = config;
+ }
+
+ @Override
+ public void run() {
+
+ try {
+ Collection<RpcDescription> rpcs = config.getRpcDescriptions();
+ log.info("" + rpcs);
+ for (RpcDescription rpc : rpcs) {
+ if (!rpcMap.containsKey(rpc.op())) {
+ log.error("Invalid RPC: {}", rpc);
+ continue;
+ }
+
+ rpcMap.get(rpc.op()).apply(this.workflowService, rpc);
+ }
+ } catch (WorkflowException e) {
+ log.error("Exception: ", e);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ public interface RpcCall {
+ void apply(WorkflowService workflowService, RpcDescription rpcDesc) throws WorkflowException;
+ }
+
+ private static Map<String, RpcCall> rpcMap = new HashMap<>();
+ static {
+ rpcMap.put("workplace.create",
+ (service, desc) -> service.createWorkplace(DefaultWorkplaceDescription.valueOf(desc.params())));
+ rpcMap.put("workplace.remove",
+ (service, desc) -> service.removeWorkplace(DefaultWorkplaceDescription.valueOf(desc.params())));
+ rpcMap.put("workflow.invoke",
+ (service, desc) -> service.invokeWorkflow(desc.params()));
+ rpcMap.put("workflow.terminate",
+ (service, desc) -> service.terminateWorkflow(DefaultWorkflowDescription.valueOf(desc.params())));
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkplaceWorkflow.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkplaceWorkflow.java
new file mode 100644
index 0000000..219436c
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkplaceWorkflow.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.event.Event;
+import org.onosproject.workflow.api.AbstractWorklet;
+import org.onosproject.workflow.api.DefaultWorkflowContext;
+import org.onosproject.workflow.api.DefaultWorkplace;
+import org.onosproject.workflow.api.ImmutableListWorkflow;
+import org.onosproject.workflow.api.JsonDataModelTree;
+import org.onosproject.workflow.api.SystemWorkflowContext;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.WorkflowAttribute;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowData;
+import org.onosproject.workflow.api.WorkflowDataEvent;
+import org.onosproject.workflow.api.DefaultWorkflowDescription;
+import org.onosproject.workflow.api.WorkflowDescription;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.WorkflowExecutionService;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.workflow.api.Workplace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.Objects;
+
+import static org.onosproject.workflow.api.WorkflowDataEvent.Type.INSERT;
+
+public class WorkplaceWorkflow {
+
+ private static final Logger log = LoggerFactory.getLogger(WorkplaceWorkflow.class);
+
+ private WorkflowExecutionService workflowExecutionService;
+ private WorkflowStore workflowStore;
+
+ public WorkplaceWorkflow(WorkflowExecutionService workflowExecutionService,
+ WorkflowStore workflowStore) {
+ this.workflowExecutionService = workflowExecutionService;
+ this.workflowStore = workflowStore;
+ }
+
+ public static final String WF_CREATE_WORKFLOW = "workplace.create-workflow";
+
+ public void registerWorkflows() {
+
+ Workflow workflow = ImmutableListWorkflow.builder()
+ .id(URI.create(WF_CREATE_WORKFLOW))
+ .attribute(WorkflowAttribute.REMOVE_AFTER_COMPLETE)
+ .init(ChangeDistributor.class.getName())
+ .chain(CreateWorkplace.class.getName())
+ .chain(CreateWorkflowContext.class.getName())
+ .build();
+ workflowStore.register(workflow);
+ }
+
+ public abstract static class AbsWorkflowWorklet extends AbstractWorklet {
+
+ protected WorkflowDescription getWorkflowDesc(WorkflowContext context) throws WorkflowException {
+
+ JsonNode root = ((JsonDataModelTree) context.data()).root();
+ return DefaultWorkflowDescription.valueOf(root);
+ }
+ }
+
+ public static class ChangeDistributor extends AbsWorkflowWorklet {
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ String workplaceName = getWorkflowDesc(context).workplaceName();
+ // Sets workflow job distribution hash value to make this workflow to be executed on the
+ // same cluster node to execute workplace tasks.
+ ((SystemWorkflowContext) context).setDistributor(workplaceName);
+
+ context.completed();
+ }
+ }
+
+ public static class CreateWorkplace extends AbsWorkflowWorklet {
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ WorkflowDescription wfDesc = getWorkflowDesc(context);
+
+ Workplace workplace = context.workplaceStore().getWorkplace(wfDesc.workplaceName());
+ return Objects.isNull(workplace);
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ WorkflowDescription wfDesc = getWorkflowDesc(context);
+
+ // creates workplace with empty data model
+ DefaultWorkplace workplace = new DefaultWorkplace(wfDesc.workplaceName(), new JsonDataModelTree());
+ log.info("registerWorkplace {}", workplace);
+ context.waitCompletion(WorkflowDataEvent.class, wfDesc.workplaceName(),
+ () -> context.workplaceStore().registerWorkplace(wfDesc.workplaceName(), workplace),
+ 60000L
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+
+ if (!(event instanceof WorkflowDataEvent)) {
+ return false;
+ }
+
+ WorkflowDataEvent wfEvent = (WorkflowDataEvent) event;
+ WorkflowData wfData = wfEvent.subject();
+
+ WorkflowDescription wfDesc = getWorkflowDesc(context);
+
+ if (wfData instanceof Workplace
+ && Objects.equals(wfData.name(), wfDesc.workplaceName())
+ && wfEvent.type() == INSERT) {
+ log.info("isCompleted(true): event:{}, context:{}, workplace:{}",
+ event, context, wfDesc.workplaceName());
+ return true;
+ } else {
+ log.info("isCompleted(false) event:{}, context:{}, workplace:{}",
+ event, context, wfDesc.workplaceName());
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ public static class CreateWorkflowContext extends AbsWorkflowWorklet {
+
+ private static final String SUBMITTED = "submitted";
+
+ private boolean isSubmitted(WorkflowContext context) throws WorkflowException {
+ JsonNode node = ((JsonDataModelTree) context.data()).nodeAt("/" + SUBMITTED);
+ if (!(node instanceof BooleanNode)) {
+ return false;
+ }
+ return node.asBoolean();
+ }
+
+ private void submitTrue(WorkflowContext context) throws WorkflowException {
+ JsonNode root = ((JsonDataModelTree) context.data()).root();
+ if (!(root instanceof ObjectNode)) {
+ throw new WorkflowException("Invalid root node for " + context);
+ }
+ ((ObjectNode) root).put(SUBMITTED, true);
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ WorkflowDescription wfDesc = getWorkflowDesc(context);
+
+ String contextName = DefaultWorkflowContext.nameBuilder(wfDesc.id(), wfDesc.workplaceName());
+ if (Objects.isNull(context.workplaceStore().getContext(contextName))) {
+ return (!isSubmitted(context));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ WorkflowDescription wfDesc = getWorkflowDesc(context);
+
+ Workplace workplace = context.workplaceStore().getWorkplace(wfDesc.workplaceName());
+ if (Objects.isNull(workplace)) {
+
+ log.error("Failed to find workplace with " + wfDesc.workplaceName());
+ throw new WorkflowException("Failed to find workplace with " + wfDesc.workplaceName());
+ }
+
+ Workflow workflow = context.workflowStore().get(wfDesc.id());
+ if (Objects.isNull(workflow)) {
+ throw new WorkflowException("Failed to find workflow with " + wfDesc.id());
+ }
+
+ //String contextName = DefaultWorkflowContext.nameBuilder(wfDesc.id(), wfDesc.workplaceName());
+ String contextName = wfDesc.workflowContextName();
+ if (Objects.nonNull(context.workplaceStore().getContext(contextName))) {
+ throw new WorkflowException(contextName + " exists already");
+ }
+
+ JsonDataModelTree subTree = new JsonDataModelTree(wfDesc.data());
+ WorkflowContext buildingContext = workflow.buildContext(workplace, subTree);
+ log.info("registerContext {}", buildingContext.name());
+ context.workplaceStore().registerContext(buildingContext.name(), buildingContext);
+ submitTrue(context);
+
+ context.completed();
+ }
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/SampleWorkflow.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/SampleWorkflow.java
new file mode 100644
index 0000000..9da6d60
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/SampleWorkflow.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.impl.example;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.workflow.api.AbstractWorklet;
+import org.onosproject.workflow.api.DataModelTree;
+import org.onosproject.workflow.api.ImmutableListWorkflow;
+import org.onosproject.workflow.api.JsonDataModel;
+import org.onosproject.workflow.api.JsonDataModelTree;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.WorkflowExecutionService;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.workflow.api.WorkplaceStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+
+/**
+ * Class for sample workflow.
+ */
+@Component(immediate = true)
+public class SampleWorkflow {
+
+ private static final Logger log = LoggerFactory.getLogger(SampleWorkflow.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkflowStore workflowStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkplaceStore workplaceStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkflowExecutionService workflowExecutionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+
+ @Activate
+ public void activate() {
+ log.info("Activated");
+
+ registerWorkflows();
+
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Deactivated");
+ }
+
+ /**
+ * Registers example workflows.
+ */
+ private void registerWorkflows() {
+ // registering class-loader
+ workflowStore.registerLocal(this.getClass().getClassLoader());
+
+ // registering new workflow definition
+ URI uri = URI.create("sample.workflow-0");
+ Workflow workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(SampleWorklet1.class.getName())
+ .chain(SampleWorklet2.class.getName())
+ .chain(SampleWorklet3.class.getName())
+ .chain(SampleWorklet4.class.getName())
+ .chain(SampleWorklet5.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ // registering new workflow definition
+ uri = URI.create("sample.workflow-1");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(SampleWorklet3.class.getName())
+ .chain(SampleWorklet2.class.getName())
+ .chain(SampleWorklet1.class.getName())
+ .chain(SampleWorklet4.class.getName())
+ .chain(SampleWorklet5.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ // registering new workflow definition
+ uri = URI.create("sample.workflow-2");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(SampleWorklet1.class.getName())
+ .chain(SampleWorklet3.class.getName())
+ .chain(SampleWorklet2.class.getName())
+ .chain(SampleWorklet4.class.getName())
+ .chain(SampleWorklet5.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ // registering new workflow definition
+ uri = URI.create("sample.workflow-3");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(SampleWorklet6.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ }
+
+ /**
+ * Abstract class for sample worklet.
+ */
+ public abstract static class AbsSampleWorklet extends AbstractWorklet {
+
+ protected static final String MODEL_SAMPLE_JOB = "/sample/job";
+ protected static final String MODEL_COUNT = "/count";
+
+ /**
+ * Constructor for sample worklet.
+ */
+ protected AbsSampleWorklet() {
+
+ }
+
+ /**
+ * Allocates or gets data model.
+ * @param context workflow context
+ * @return json object node
+ * @throws WorkflowException workflow exception
+ */
+ protected ObjectNode allocOrGetModel(WorkflowContext context) throws WorkflowException {
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode params = tree.root();
+
+ if (params.at(MODEL_SAMPLE_JOB).getNodeType() == JsonNodeType.MISSING) {
+ tree.alloc(MODEL_SAMPLE_JOB, DataModelTree.Nodetype.MAP);
+ }
+ return (ObjectNode) params.at(MODEL_SAMPLE_JOB);
+ }
+
+ /**
+ * Gets data model.
+ * @param context workflow context
+ * @return json object node
+ * @throws WorkflowException workflow exception
+ */
+ protected ObjectNode getDataModel(WorkflowContext context) throws WorkflowException {
+ DataModelTree tree = context.data();
+ return ((JsonDataModelTree) tree.subtree(MODEL_SAMPLE_JOB)).rootObject();
+ }
+
+ /**
+ * Sleeps for 'ms' milli seconds.
+ * @param ms milli seconds to sleep
+ */
+ protected void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ log.error("Exception: ", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Class for sample worklet-1.
+ */
+ public static class SampleWorklet1 extends AbsSampleWorklet {
+
+ @JsonDataModel(path = MODEL_COUNT)
+ Integer intCount;
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = getDataModel(context);
+ node.put("work1", "done");
+ log.info("workflow-process {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(30);
+
+ context.completed(); //Complete the job of worklet in the process
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = allocOrGetModel(context);
+ log.info("workflow-isNext {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(30);
+ return !node.has("work1");
+
+ }
+ }
+
+ /**
+ * Class for sample worklet-2 (using timeout).
+ */
+ public static class SampleWorklet2 extends AbsSampleWorklet {
+
+ @JsonDataModel(path = MODEL_COUNT)
+ Integer intCount;
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = getDataModel(context);
+ node.put("work2", "done");
+ log.info("workflow-process {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(50);
+ intCount++;
+
+ context.waitFor(50L); //Timeout will happen after 50 milli seconds.
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ context.completed(); //Complete the job of worklet by timeout
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = allocOrGetModel(context);
+ log.info("workflow-isNext {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(50);
+ return !node.has("work2");
+ }
+ }
+
+ public static class SampleWorklet3 extends AbsSampleWorklet {
+
+ @JsonDataModel(path = MODEL_COUNT)
+ Integer intCount;
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = getDataModel(context);
+ node.put("work3", "done");
+ log.info("workflow-process {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ intCount++;
+ context.completed();
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = allocOrGetModel(context);
+ log.info("workflow-isNext {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ return !node.has("work3");
+ }
+ }
+
+ public static class SampleWorklet4 extends AbsSampleWorklet {
+
+ @JsonDataModel(path = MODEL_COUNT)
+ Integer intCount;
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = getDataModel(context);
+ node.put("work4", "done");
+ log.info("workflow-process {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ intCount++;
+ context.completed();
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = allocOrGetModel(context);
+ log.info("workflow-isNext {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ return !node.has("work4");
+ }
+ }
+
+ public static class SampleWorklet5 extends AbsSampleWorklet {
+
+ @JsonDataModel(path = MODEL_COUNT)
+ Integer intCount;
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = getDataModel(context);
+ node.put("work5", "done");
+ log.info("workflow-process {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ intCount++;
+ context.completed();
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = allocOrGetModel(context);
+ log.info("workflow-isNext {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ return !node.has("work5");
+ }
+ }
+
+ /**
+ * Class for sample worklet-6 to test workflow datamodel exception.
+ */
+ public static class SampleWorklet6 extends AbsSampleWorklet {
+
+ @JsonDataModel(path = MODEL_COUNT)
+ String str;
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = getDataModel(context);
+ node.put("work6", "done");
+ log.info("workflow-process {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ context.completed();
+ }
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ ObjectNode node = allocOrGetModel(context);
+ log.info("workflow-isNext {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+ sleep(10);
+ return !node.has("work6");
+ }
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/package-info.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/package-info.java
new file mode 100644
index 0000000..2fbc9549a
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Workflow example package.
+ */
+package org.onosproject.workflow.impl.example;
\ No newline at end of file
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/package-info.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/package-info.java
new file mode 100644
index 0000000..f82c452
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Workflow implementation package.
+ */
+package org.onosproject.workflow.impl;
\ No newline at end of file
diff --git a/apps/workflow/model/BUCK b/apps/workflow/model/BUCK
new file mode 100644
index 0000000..a1c6f0b
--- /dev/null
+++ b/apps/workflow/model/BUCK
@@ -0,0 +1,12 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:jackson-core',
+ '//lib:jackson-annotations',
+ '//lib:jackson-databind',
+ '//core/store/serializers:onos-core-serializers',
+ '//apps/workflow/api:onos-apps-workflow-api',
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/model/BUILD b/apps/workflow/model/BUILD
new file mode 100644
index 0000000..016e14a
--- /dev/null
+++ b/apps/workflow/model/BUILD
@@ -0,0 +1,8 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + [
+ "//core/store/serializers:onos-core-serializers",
+ "//apps/workflow/api:onos-apps-workflow-api",
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/NetconfAccessInfo.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/NetconfAccessInfo.java
new file mode 100644
index 0000000..8e428e3
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/NetconfAccessInfo.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.model.accessinfo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.workflow.api.WorkflowException;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Class for NETCONF access information.
+ */
+public final class NetconfAccessInfo {
+
+ private static final String REMOTE_IP = "remoteIp";
+ private static final String PORT = "port";
+ private static final String USER = "user";
+ private static final String PASSWORD = "password";
+
+ private final IpAddress remoteIp;
+ private final TpPort port;
+ private final String user;
+ private final String password;
+
+ /**
+ * Constructor for a given NETCONF access information.
+ *
+ * @param remoteIp remote ip address
+ * @param port port number
+ * @param user user name
+ * @param password password
+ */
+ public NetconfAccessInfo(IpAddress remoteIp,
+ TpPort port,
+ String user,
+ String password) {
+ this.remoteIp = checkNotNull(remoteIp);
+ this.port = checkNotNull(port);
+ this.user = checkNotNull(user);
+ this.password = checkNotNull(password);
+ }
+
+ /**
+ * Builds NetconfAccessInfo from json.
+ * @param root json root node for NetconfAccessinfo
+ * @return NETCONF access information
+ * @throws WorkflowException workflow exception
+ */
+ public static NetconfAccessInfo valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(REMOTE_IP));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid remoteIp for " + root);
+ }
+ IpAddress remoteIp = IpAddress.valueOf(node.asText());
+
+ node = root.at(ptr(PORT));
+ if (node == null || !(node instanceof NumericNode)) {
+ throw new WorkflowException("invalid port for " + root);
+ }
+ TpPort tpPort = TpPort.tpPort(node.asInt());
+
+ node = root.at(ptr(USER));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid user for " + root);
+ }
+ String strUser = node.asText();
+
+ node = root.at(ptr(PASSWORD));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid password for " + root);
+ }
+ String strPassword = node.asText();
+
+ return new NetconfAccessInfo(remoteIp, tpPort, strUser, strPassword);
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ /**
+ * Returns the remote IP address.
+ *
+ * @return ip address
+ */
+ public IpAddress remoteIp() {
+ return this.remoteIp;
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return port
+ */
+ public TpPort port() {
+ return this.port;
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return user name
+ */
+ public String user() {
+ return this.user;
+ }
+
+ /**
+ * Returns the password.
+ * @return password
+ */
+ public String password() {
+ return this.password;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof NetconfAccessInfo) {
+ NetconfAccessInfo that = (NetconfAccessInfo) obj;
+ return Objects.equals(remoteIp, that.remoteIp) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(user, that.user);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(remoteIp, port, user);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("remoteIp", remoteIp)
+ .add("port", port)
+ .add("user", user)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/RestAccessInfo.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/RestAccessInfo.java
new file mode 100644
index 0000000..c13270d
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/RestAccessInfo.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.model.accessinfo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.workflow.api.WorkflowException;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Class for REST access information.
+ */
+public final class RestAccessInfo {
+
+ private static final String REMOTE_IP = "remoteIp";
+ private static final String PORT = "port";
+ private static final String USER = "user";
+ private static final String PASSWORD = "password";
+
+ private final IpAddress remoteIp;
+ private final TpPort port;
+ private final String user;
+ private String password;
+
+ /**
+ * Creates a new REST access information.
+ *
+ * @param remoteIp remote ip address
+ * @param port port number
+ * @param user user name
+ * @param password password
+ */
+ private RestAccessInfo(IpAddress remoteIp,
+ TpPort port,
+ String user,
+ String password) {
+ this.remoteIp = checkNotNull(remoteIp);
+ this.port = checkNotNull(port);
+ this.user = checkNotNull(user);
+ this.password = checkNotNull(password);
+ }
+
+ /**
+ * Builds RestAccessInfo from json.
+ * @param root json root node for RestAccessinfo
+ * @return REST access information
+ * @throws WorkflowException workflow exception
+ */
+ public static RestAccessInfo valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(REMOTE_IP));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid remoteIp for " + root);
+ }
+ IpAddress remoteIp = IpAddress.valueOf(node.asText());
+
+ node = root.at(ptr(PORT));
+ if (node == null || !(node instanceof NumericNode)) {
+ throw new WorkflowException("invalid port for " + root);
+ }
+ TpPort tpPort = TpPort.tpPort(node.asInt());
+
+ node = root.at(ptr(USER));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid user for " + root);
+ }
+ String strUser = node.asText();
+
+ node = root.at(ptr(PASSWORD));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid password for " + root);
+ }
+ String strPassword = node.asText();
+
+ return new RestAccessInfo(remoteIp, tpPort, strUser, strPassword);
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ /**
+ * Returns the remote IP address.
+ *
+ * @return ip address
+ */
+ public IpAddress remoteIp() {
+ return this.remoteIp;
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return REST port
+ */
+ public TpPort port() {
+ return this.port;
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return user name
+ */
+ public String user() {
+ return this.user;
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return password
+ */
+ public String password() {
+ return this.password;
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof RestAccessInfo) {
+ RestAccessInfo that = (RestAccessInfo) obj;
+ return Objects.equals(remoteIp, that.remoteIp) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(user, that.user);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(remoteIp, port, user);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("remoteIp", remoteIp)
+ .add("port", port)
+ .add("user", user)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/SshAccessInfo.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/SshAccessInfo.java
new file mode 100644
index 0000000..aec224a
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/SshAccessInfo.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.workflow.model.accessinfo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.workflow.api.WorkflowException;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Class for SSH access information.
+ */
+public final class SshAccessInfo {
+
+ private static final String REMOTE_IP = "remoteIp";
+ private static final String PORT = "port";
+ private static final String USER = "user";
+ private static final String PASSWORD = "password";
+ private static final String KEYFILE = "keyfile";
+
+ private final IpAddress remoteIp;
+ private final TpPort port;
+ private final String user;
+ private String password;
+ private final String privateKey;
+
+ /**
+ * Constructor for SSH access information.
+ *
+ * @param remoteIp ssh remote ip address
+ * @param port ssh port number
+ * @param user user name
+ * @param password password
+ * @param privateKey path of ssh private key
+ */
+ private SshAccessInfo(IpAddress remoteIp, TpPort port, String user, String password, String privateKey) {
+ this.remoteIp = checkNotNull(remoteIp);
+ this.port = checkNotNull(port);
+ this.user = checkNotNull(user);
+ this.password = password;
+ this.privateKey = checkNotNull(privateKey);
+ }
+
+ /**
+ * Builds SshAccessInfo from json.
+ * @param root json root node for SshAccessinfo
+ * @return SSH access information
+ * @throws WorkflowException workflow exception
+ */
+ public static SshAccessInfo valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(REMOTE_IP));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid remoteIp for " + root);
+ }
+ IpAddress sshIp = IpAddress.valueOf(node.asText());
+
+ node = root.at(ptr(PORT));
+ if (node == null || !(node instanceof NumericNode)) {
+ throw new WorkflowException("invalid port for " + root);
+ }
+ TpPort sshPort = TpPort.tpPort(node.asInt());
+
+ node = root.at(ptr(USER));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid user for " + root);
+ }
+ String sshUser = node.asText();
+
+ node = root.at(ptr(PASSWORD));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid password for " + root);
+ }
+ String sshPassword = node.asText();
+
+ node = root.at(ptr(KEYFILE));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid keyfile for " + root);
+ }
+ String sshKeyfile = node.asText();
+
+ return new SshAccessInfo(sshIp, sshPort, sshUser, sshPassword, sshKeyfile);
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ /**
+ * Returns the remote IP address.
+ *
+ * @return ip address
+ */
+ public IpAddress remoteIp() {
+ return this.remoteIp;
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return ssh port
+ */
+ public TpPort port() {
+ return this.port;
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return user name
+ */
+ public String user() {
+ return this.user;
+ }
+
+ /**
+ * Returns the password.
+ * @return password
+ */
+ public String password() {
+ return this.password;
+ }
+
+ /**
+ * Returns the private key path.
+ *
+ * @return privateKey
+ */
+ public String privateKey() {
+ return privateKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof SshAccessInfo) {
+ SshAccessInfo that = (SshAccessInfo) obj;
+ return Objects.equals(remoteIp, that.remoteIp) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(user, that.user) &&
+ Objects.equals(privateKey, that.privateKey);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(remoteIp, port, user, privateKey);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("remoteIp", remoteIp)
+ .add("port", port)
+ .add("user", user)
+ .add("privateKey", privateKey)
+ .toString();
+ }
+}
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/package-info.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/package-info.java
new file mode 100644
index 0000000..bc88e3b
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Package for workflow common data model - access info.
+ */
+package org.onosproject.workflow.model.accessinfo;
diff --git a/apps/workflow/ofoverlay/BUCK b/apps/workflow/ofoverlay/BUCK
new file mode 100644
index 0000000..2f3fd5a
--- /dev/null
+++ b/apps/workflow/ofoverlay/BUCK
@@ -0,0 +1,18 @@
+BUNDLES = [
+ '//apps/workflow/ofoverlay/app:onos-apps-workflow-ofoverlay-app',
+]
+
+onos_app(
+ app_name = 'org.onosproject.workflow.ofoverlay',
+ category = 'Utility',
+ description = 'Openflow overaly application',
+ included_bundles = BUNDLES,
+ required_apps = [
+ 'org.onosproject.openflow',
+ 'org.onosproject.ovsdb',
+ 'org.onosproject.drivers.ovsdb',
+ 'org.onosproject.workflow',
+ ],
+ title = 'Openflow overlay',
+ url = 'http://onosproject.org',
+)
diff --git a/apps/workflow/ofoverlay/BUILD b/apps/workflow/ofoverlay/BUILD
new file mode 100644
index 0000000..cc75d82
--- /dev/null
+++ b/apps/workflow/ofoverlay/BUILD
@@ -0,0 +1,21 @@
+BUNDLES = [
+ "@jsch//jar",
+ "//protocols/ovsdb/api:onos-protocols-ovsdb-api",
+ "//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc",
+ "//apps/workflow/ofoverlay/app:onos-apps-workflow-ofoverlay-app",
+]
+
+onos_app(
+ app_name = "org.onosproject.workflow.ofoverlay",
+ category = "Utility",
+ description = "Openflow overaly application",
+ included_bundles = BUNDLES,
+ required_apps = [
+ "org.onosproject.openflow",
+ "org.onosproject.ovsdb",
+ "org.onosproject.drivers.ovsdb",
+ "org.onosproject.workflow",
+ ],
+ title = "Openflow overlay",
+ url = "http://onosproject.org",
+)
diff --git a/apps/workflow/ofoverlay/app/BUCK b/apps/workflow/ofoverlay/app/BUCK
new file mode 100644
index 0000000..ba31441d
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/BUCK
@@ -0,0 +1,20 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:KRYO',
+ '//lib:jsch',
+ '//lib:org.apache.karaf.shell.console',
+ '//lib:jackson-core',
+ '//lib:jackson-annotations',
+ '//lib:jackson-databind',
+ '//cli:onos-cli',
+ '//core/store/serializers:onos-core-serializers',
+ '//core/store/primitives:onos-core-primitives',
+ '//apps/workflow/api:onos-apps-workflow-api',
+ '//apps/workflow/model:onos-apps-workflow-model',
+ '//protocols/ovsdb/api:onos-protocols-ovsdb-api',
+ '//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc',
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/ofoverlay/app/BUILD b/apps/workflow/ofoverlay/app/BUILD
new file mode 100644
index 0000000..d8a5fa0
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/BUILD
@@ -0,0 +1,13 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + CLI + [
+ "//core/store/serializers:onos-core-serializers",
+ "//core/store/primitives:onos-core-primitives",
+ "//apps/workflow/api:onos-apps-workflow-api",
+ "//apps/workflow/model:onos-apps-workflow-model",
+ "@jsch//jar",
+ "//protocols/ovsdb/api:onos-protocols-ovsdb-api",
+ "//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc",
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/OfOverlayWorkflow.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/OfOverlayWorkflow.java
new file mode 100644
index 0000000..8ba91be
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/OfOverlayWorkflow.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.workflow.api.ImmutableListWorkflow;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.WorkflowExecutionService;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.workflow.api.WorkplaceStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.concurrent.ScheduledExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Class for Open-flow overlay configuration workflow.
+ */
+@Component(immediate = true)
+public class OfOverlayWorkflow {
+
+ private static final Logger log = LoggerFactory.getLogger(OfOverlayWorkflow.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkflowStore workflowStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkplaceStore workplaceStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected WorkflowExecutionService workflowExecutionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ private ScheduledExecutorService eventMapTriggerExecutor;
+
+ @Activate
+ public void activate() {
+ log.info("Activated");
+
+ eventMapTriggerExecutor = newSingleThreadScheduledExecutor(
+ groupedThreads("onos/of-overlay", "eventmap-trigger-executor"));
+
+ registerWorkflows();
+
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Deactivated");
+ }
+
+ /**
+ * Registers workflows.
+ */
+ private void registerWorkflows() {
+ // registering class-loader
+ workflowStore.registerLocal(this.getClass().getClassLoader());
+
+ // registering new workflow definition
+ URI uri = URI.create("of-overlay.workflow-nova");
+ Workflow workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ //.attribute(WorkflowAttribute.REMOVE_AFTER_COMPLETE)
+ .chain(Ovs.CreateOvsdbDevice.class.getName())
+ .chain(Ovs.UpdateOvsVersion.class.getName())
+ .chain(Ovs.UpdateOverlayBridgeId.class.getName())
+ .chain(Ovs.CreateOverlayBridge.class.getName())
+ .chain(Ovs.UpdateUnderlayBridgeId.class.getName())
+ .chain(Ovs.CreateUnderlayBridge.class.getName())
+ .chain(Ovs.CreateOverlayBridgeVxlanPort.class.getName())
+ .chain(Ovs.AddPhysicalPortsOnUnderlayBridge.class.getName())
+ .chain(Ovs.ConfigureUnderlayBridgeLocalIp.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ // registering new workflow definition based on multi-event handling
+ uri = URI.create("of-overlay.workflow-nova-multiEvent-test");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ //.attribute(WorkflowAttribute.REMOVE_AFTER_COMPLETE)
+ .chain(Ovs.CreateOvsdbDevice.class.getName())
+ .chain(Ovs.UpdateOvsVersion.class.getName())
+ .chain(Ovs.UpdateOverlayBridgeId.class.getName())
+ .chain(Ovs.CreateOverlayBridgeMultiEvent.class.getName())
+ .chain(Ovs.UpdateUnderlayBridgeId.class.getName())
+ .chain(Ovs.CreateUnderlayBridge.class.getName())
+ .chain(Ovs.CreateOverlayBridgeVxlanPort.class.getName())
+ .chain(Ovs.AddPhysicalPortsOnUnderlayBridge.class.getName())
+ .chain(Ovs.ConfigureUnderlayBridgeLocalIp.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ uri = URI.create("of-overlay.clean-workflow-nova");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ //.attribute(WorkflowAttribute.REMOVE_AFTER_COMPLETE)
+ .chain(Ovs.DeleteOverlayBridgeConfig.class.getName())
+ .chain(Ovs.RemoveOverlayBridgeOfDevice.class.getName())
+ .chain(Ovs.DeleteUnderlayBridgeConfig.class.getName())
+ .chain(Ovs.RemoveUnderlayBridgeOfDevice.class.getName())
+ .chain(Ovs.RemoveOvsdbDevice.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ uri = URI.create("of-overlay.clean-workflow-nova-waitAll-Bridge-Del");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ //.attribute(WorkflowAttribute.REMOVE_AFTER_COMPLETE)
+ .chain(Ovs.DeleteOverlayBridgeConfig.class.getName())
+ .chain(Ovs.DeleteUnderlayBridgeConfig.class.getName())
+ .chain(Ovs.RemoveBridgeOfDevice.class.getName())
+ .chain(Ovs.RemoveOvsdbDevice.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ uri = URI.create("of-overlay.workflow-ovs-leaf");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(Ovs.CreateOvsdbDevice.class.getName())
+ .chain(Ovs.UpdateOvsVersion.class.getName())
+ .chain(Ovs.UpdateUnderlayBridgeId.class.getName())
+ .chain(Ovs.CreateUnderlayBridge.class.getName())
+ .chain(Ovs.AddPhysicalPortsOnUnderlayBridge.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ uri = URI.create("of-overlay.workflow-ovs-spine");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(Ovs.CreateOvsdbDevice.class.getName())
+ .chain(Ovs.UpdateOvsVersion.class.getName())
+ .chain(Ovs.UpdateUnderlayBridgeId.class.getName())
+ .chain(Ovs.CreateUnderlayBridge.class.getName())
+ .chain(Ovs.AddPhysicalPortsOnUnderlayBridge.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ deviceService.addListener(
+ event -> {
+ // trigger EventTask for DeviceEvent
+ eventMapTriggerExecutor.submit(
+ () -> workflowExecutionService.eventMapTrigger(
+ event,
+ // event hint supplier
+ (ev) -> {
+ if (ev == null || ev.subject() == null) {
+ return null;
+ }
+ String hint = event.subject().id().toString();
+ log.debug("hint: {}", hint);
+ return hint;
+ }
+ )
+ );
+ }
+ );
+
+ }
+
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/Ovs.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/Ovs.java
new file mode 100644
index 0000000..ad56d52
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/Ovs.java
@@ -0,0 +1,1418 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.event.Event;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.behaviour.BridgeConfig;
+import org.onosproject.net.behaviour.BridgeDescription;
+import org.onosproject.net.behaviour.BridgeName;
+import org.onosproject.net.behaviour.ControlProtocolVersion;
+import org.onosproject.net.behaviour.ControllerInfo;
+import org.onosproject.net.behaviour.DefaultBridgeDescription;
+import org.onosproject.net.behaviour.DefaultTunnelDescription;
+import org.onosproject.net.behaviour.InterfaceConfig;
+import org.onosproject.net.behaviour.TunnelDescription;
+import org.onosproject.net.behaviour.TunnelEndPoints;
+import org.onosproject.net.behaviour.TunnelKeys;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.ofoverlay.impl.util.NetworkAddress;
+import org.onosproject.ofoverlay.impl.util.OvsDatapathType;
+import org.onosproject.ofoverlay.impl.util.OvsVersion;
+import org.onosproject.ofoverlay.impl.util.SshUtil;
+import org.onosproject.ovsdb.controller.OvsdbClientService;
+import org.onosproject.ovsdb.controller.OvsdbController;
+import org.onosproject.ovsdb.controller.OvsdbNodeId;
+import org.onosproject.workflow.api.AbstractWorklet;
+import org.onosproject.workflow.api.JsonDataModel;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.model.accessinfo.SshAccessInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Class for defining OVS workflows.
+ */
+public final class Ovs {
+
+ private static final Logger log = LoggerFactory.getLogger(Ovs.class);
+
+ private static final String MODEL_MGMT_IP = "/mgmtIp";
+ private static final String MODEL_OVSDB_PORT = "/ovsdbPort";
+ private static final String MODEL_OVS_VERSION = "/ovsVersion";
+ private static final String MODEL_OVS_DATAPATH_TYPE = "/ovsDatapathType";
+ private static final String MODEL_SSH_ACCESSINFO = "/sshAccessInfo";
+ private static final String MODEL_OF_DEVID_OVERLAY_BRIDGE = "/ofDevIdBrInt";
+ private static final String MODEL_OF_DEVID_UNDERLAY_BRIDGE = "/ofDevIdBrPhy";
+ private static final String MODEL_OF_DEVID_FOR_OVERLAY_UNDERLAY_BRIDGE = "/ofDevIdBrIntBrPhy";
+ private static final String MODEL_PHY_PORTS = "/physicalPorts";
+ private static final String MODEL_VTEP_IP = "/vtepIp";
+
+ private static final String BRIDGE_OVERLAY = "br-int";
+ private static final String BRIDGE_UNDERLAY = "br-phy";
+
+ private static final int DEVID_IDX_BRIDGE_OVERLAY = 0;
+ private static final int DEVID_IDX_BRIDGE_UNDERLAY_NOVA = 1;
+
+ private static final ControlProtocolVersion BRIDGE_DEFAULT_OF_VERSION = ControlProtocolVersion.OF_1_3;
+ private static final int OPENFLOW_PORT = 6653;
+ private static final String OPENFLOW_CHANNEL_PROTO = "tcp";
+ private static final String OVSDB_DEVICE_PREFIX = "ovsdb:";
+
+ private static final long TIMEOUT_DEVICE_CREATION_MS = 60000L;
+ private static final long TIMEOUT_PORT_ADDITION_MS = 120000L;
+
+
+ /**
+ * Utility class for OVS workflow.
+ */
+ public static final class OvsUtil {
+
+ private OvsUtil() {
+
+ }
+
+ private static final String OPENFLOW_DEVID_FORMAT = "of:%08x%08x";
+
+ /**
+ * Builds Open-flow device id with ip address, and index.
+ * @param addr ip address
+ * @param index index
+ * @return created device id
+ */
+ public static DeviceId buildOfDeviceId(IpAddress addr, int index) {
+ if (addr.isIp4()) {
+ Ip4Address v4Addr = addr.getIp4Address();
+ return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, v4Addr.toInt(), index));
+ } else if (addr.isIp6()) {
+ Ip6Address v6Addr = addr.getIp6Address();
+ return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, v6Addr.hashCode(), index));
+ } else {
+ return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, addr.hashCode(), index));
+ }
+ }
+
+ /**
+ * Builds OVS data path type.
+ * @param strOvsDatapathType string ovs data path type
+ * @return ovs data path type
+ * @throws WorkflowException workflow exception
+ */
+ public static final OvsDatapathType buildOvsDatapathType(String strOvsDatapathType) throws WorkflowException {
+ try {
+ return OvsDatapathType.valueOf(strOvsDatapathType.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Gets OVSDB behavior.
+ * @param context workflow context
+ * @param mgmtIp management ip
+ * @param behaviourClass behavior class
+ * @param <T> behavior class
+ * @return OVSDB behavior
+ * @throws WorkflowException workflow exception
+ */
+ public static final <T extends Behaviour> T getOvsdbBehaviour(WorkflowContext context, String mgmtIp,
+ Class<T> behaviourClass) throws WorkflowException {
+
+ DriverService driverService = context.getService(DriverService.class);
+
+ DeviceId devId = ovsdbDeviceId(mgmtIp);
+ DriverHandler handler = driverService.createHandler(devId);
+ if (Objects.isNull(handler)) {
+ throw new WorkflowException("Failed to get DriverHandler for " + devId);
+ }
+ T behaviour;
+ try {
+ behaviour = handler.behaviour(behaviourClass);
+ if (Objects.isNull(behaviour)) {
+ throw new WorkflowException("Failed to get " + behaviourClass + " for " + devId + "-" + handler);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new WorkflowException("Failed to get " + behaviourClass + " for " + devId + "-" + handler);
+ }
+ return behaviour;
+ }
+
+ /**
+ * Gets bridge description.
+ * @param bridgeConfig bridge config
+ * @param bridgeName bridge name
+ * @return bridge description optional
+ */
+ public static final Optional<BridgeDescription> getBridgeDescription(BridgeConfig bridgeConfig,
+ String bridgeName) {
+ try {
+ Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
+ for (BridgeDescription br: bridges) {
+ if (Objects.equals(bridgeName, br.name())) {
+ return Optional.of(br);
+ }
+ }
+ } catch (Exception e) {
+ log.error("Exception : ", e);
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Builds OVSDB device id.
+ * @param mgmtIp management ip address string
+ * @return OVSDB device id
+ */
+ public static final DeviceId ovsdbDeviceId(String mgmtIp) {
+ return DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(mgmtIp));
+ }
+
+ /**
+ * Returns {@code true} if this bridge is available;
+ * returns {@code false} otherwise.
+ * @param context workflow context
+ * @param devId device id
+ * @return {@code true} if this bridge is available; {@code false} otherwise.
+ * @throws WorkflowException workflow exception
+ */
+ public static final boolean isAvailableBridge(WorkflowContext context, DeviceId devId)
+ throws WorkflowException {
+
+ if (Objects.isNull(devId)) {
+ throw new WorkflowException("Invalid device id in data model");
+ }
+
+ DeviceService deviceService = context.getService(DeviceService.class);
+ Device dev = deviceService.getDevice(devId);
+ if (Objects.isNull(dev)) {
+ return false;
+ }
+
+ return deviceService.isAvailable(devId);
+ }
+
+ /**
+ * Gets openflow controller information list.
+ * @param context workflow context
+ * @return openflow controller information list
+ * @throws WorkflowException workflow exception
+ */
+ public static final List<ControllerInfo> getOpenflowControllerInfoList(WorkflowContext context)
+ throws WorkflowException {
+ ClusterService clusterService = context.getService(ClusterService.class);
+ java.util.List<org.onosproject.net.behaviour.ControllerInfo> controllers = new ArrayList<>();
+ Sets.newHashSet(clusterService.getNodes()).forEach(
+ controller -> {
+ org.onosproject.net.behaviour.ControllerInfo ctrlInfo =
+ new org.onosproject.net.behaviour.ControllerInfo(controller.ip(),
+ OPENFLOW_PORT,
+ OPENFLOW_CHANNEL_PROTO);
+ controllers.add(ctrlInfo);
+ }
+ );
+ return controllers;
+ }
+
+ /**
+ * Creates bridge.
+ * @param bridgeConfig bridge config
+ * @param name bridge name to create
+ * @param dpid openflow data path id of bridge to create
+ * @param ofControllers openflow controller information list
+ * @param datapathType OVS data path type
+ */
+ public static final void createBridge(BridgeConfig bridgeConfig, String name, String dpid,
+ List<ControllerInfo> ofControllers, OvsDatapathType datapathType) {
+ BridgeDescription.Builder bridgeDescBuilder = DefaultBridgeDescription.builder()
+ .name(name)
+ .failMode(BridgeDescription.FailMode.SECURE)
+ .datapathId(dpid)
+ .disableInBand()
+ .controlProtocols(Collections.singletonList(BRIDGE_DEFAULT_OF_VERSION))
+ .controllers(ofControllers);
+
+ if (datapathType != null && !(datapathType.equals(OvsDatapathType.EMPTY))) {
+ bridgeDescBuilder.datapathType(datapathType.toString());
+ log.info("create {} with dataPathType {}", name, datapathType);
+ }
+
+ BridgeDescription bridgeDesc = bridgeDescBuilder.build();
+ bridgeConfig.addBridge(bridgeDesc);
+ }
+
+ /**
+ * Index of data path id in openflow device id.
+ */
+ private static final int DPID_BEGIN_INDEX = 3;
+
+ /**
+ * Gets bridge data path id.
+ * @param devId device id
+ * @return bridge data path id
+ */
+ public static final String bridgeDatapathId(DeviceId devId) {
+ return devId.toString().substring(DPID_BEGIN_INDEX);
+ }
+
+ /**
+ * Gets OVSDB client.
+ * @param context workflow context
+ * @param strMgmtIp management ip address
+ * @param intOvsdbPort OVSDB port
+ * @return ovsdb client
+ * @throws WorkflowException workflow exception
+ */
+ public static final OvsdbClientService getOvsdbClient(
+ WorkflowContext context, String strMgmtIp, int intOvsdbPort) throws WorkflowException {
+ IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
+ TpPort ovsdbPort = TpPort.tpPort(intOvsdbPort);
+ OvsdbController ovsdbController = context.getService(OvsdbController.class);
+ return ovsdbController.getOvsdbClient(new OvsdbNodeId(mgmtIp, ovsdbPort.toInt()));
+ }
+
+ /**
+ * Checks whether 2 controller informations include same controller information.
+ * @param a controller information list
+ * @param b controller information list
+ * @return {@code true} if 2 controller informations include same controller information
+ */
+ public static boolean isEqual(List<ControllerInfo> a, List<ControllerInfo> b) {
+ if (a == b) {
+ return true;
+ } else if (a == null) {
+ // equivalent to (a == null && b != null)
+ return false;
+ } else if (b == null) {
+ // equivalent to (a != null && b == null)
+ return false;
+ } else if (a.size() != b.size()) {
+ return false;
+ }
+
+ return a.containsAll(b);
+ }
+
+ /**
+ * Gets the name of the port.
+ * @param port port
+ * @return the name of the port
+ */
+ public static final String portName(Port port) {
+ return port.annotations().value(PORT_NAME);
+ }
+ }
+
+ /**
+ * Work-let class for creating OVSDB device.
+ */
+ public static class CreateOvsdbDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ return ovsdbClient == null || !ovsdbClient.isConnected();
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
+ TpPort ovsdbPort = TpPort.tpPort(intOvsdbPort);
+ OvsdbController ovsdbController = context.getService(OvsdbController.class);
+ context.waitCompletion(DeviceEvent.class, OVSDB_DEVICE_PREFIX.concat(strMgmtIp),
+ () -> ovsdbController.connect(mgmtIp, ovsdbPort),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ Device device = deviceEvent.subject();
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_UPDATED:
+ return context.getService(DeviceService.class).isAvailable(device.id());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+
+ /**
+ * Work-let class for removing OVSDB device.
+ */
+ public static class RemoveOvsdbDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(strMgmtIp));
+
+ Device dev = context.getService(DeviceService.class).getDevice(devId);
+ return dev != null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
+ check(mgmtIp != null, "mgmt ip is invalid");
+ DeviceId devId = DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(strMgmtIp));
+ DeviceAdminService adminService = context.getService(DeviceAdminService.class);
+
+ context.waitCompletion(DeviceEvent.class, devId.toString(),
+ () -> adminService.removeDevice(devId),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case DEVICE_REMOVED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for updating OVS version.
+ */
+ public static class UpdateOvsVersion extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_OVS_VERSION, optional = true)
+ String strOvsVersion;
+
+ @JsonDataModel(path = MODEL_SSH_ACCESSINFO)
+ JsonNode strSshAccessInfo;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ return strOvsVersion == null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
+ check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
+
+ OvsVersion ovsVersion = SshUtil.exec(sshAccessInfo,
+ session -> SshUtil.fetchOvsVersion(session));
+
+ check(Objects.nonNull(ovsVersion), "Failed to fetch ovs version " + context.data());
+ strOvsVersion = ovsVersion.toString();
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for updating overlay bridge device id.
+ */
+ public static class UpdateOverlayBridgeId extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
+ String strOfDevIdOverlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ return strOfDevIdOverlay == null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
+ if (optBd.isPresent()) {
+ Optional<DeviceId> optDevId = optBd.get().deviceId();
+ if (optDevId.isPresent()) {
+ log.info("Updates {} of device id with existing device id {}", BRIDGE_OVERLAY, optDevId.get());
+ strOfDevIdOverlay = optDevId.get().toString();
+ } else {
+ DeviceId newDevId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+ log.info("Failed to find devId. Updates {} of device id with new device id {}",
+ BRIDGE_OVERLAY, newDevId);
+ strOfDevIdOverlay = newDevId.toString();
+ }
+ } else {
+ DeviceId newDevId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+ log.info("Failed to find description. Updates {} of device id with new device id {}",
+ BRIDGE_OVERLAY, newDevId);
+ strOfDevIdOverlay = newDevId.toString();
+ }
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for creating overlay openflow bridge.
+ */
+ public static class CreateOverlayBridge extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
+ String strOvsDatapath;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
+ String strOfDevIdOverlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(strOfDevIdOverlay));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
+ DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdOverlay);
+
+ if (ofControllers == null || ofControllers.size() == 0) {
+ throw new WorkflowException("Invalid of controllers");
+ }
+
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
+ if (!optBd.isPresent()) {
+
+ // If bridge does not exist, just creates a new bridge.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> OvsUtil.createBridge(bridgeConfig,
+ BRIDGE_OVERLAY,
+ OvsUtil.bridgeDatapathId(ofDeviceId),
+ ofControllers,
+ OvsUtil.buildOvsDatapathType(strOvsDatapath)),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+
+ } else {
+ BridgeDescription bd = optBd.get();
+ if (OvsUtil.isEqual(ofControllers, bd.controllers())) {
+ log.error("{} has valid controller setting({})", BRIDGE_OVERLAY, bd.controllers());
+ context.completed();
+ return;
+ }
+
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ if (ovsdbClient == null || !ovsdbClient.isConnected()) {
+ throw new WorkflowException("Invalid ovsdb client for " + strMgmtIp);
+ }
+
+ // If controller settings are not matched, set controller with valid controller information.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> ovsdbClient.setControllersWithDeviceId(bd.deviceId().get(), ofControllers),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+ }
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ Device device = deviceEvent.subject();
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_UPDATED:
+ return context.getService(DeviceService.class).isAvailable(device.id());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for creating overlay openflow bridge.
+ */
+ public static class CreateOverlayBridgeMultiEvent extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
+ String strOvsDatapath;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
+ String strOfDevIdOverlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(strOfDevIdOverlay));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
+ DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdOverlay);
+
+ if (ofControllers == null || ofControllers.size() == 0) {
+ throw new WorkflowException("Invalid of controllers");
+ }
+
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
+ if (!optBd.isPresent()) {
+
+ Set<String> eventHints = Sets.newHashSet(ofDeviceId.toString());
+
+ context.waitAnyCompletion(DeviceEvent.class, eventHints,
+ () -> OvsUtil.createBridge(bridgeConfig,
+ BRIDGE_OVERLAY,
+ OvsUtil.bridgeDatapathId(ofDeviceId),
+ ofControllers,
+ OvsUtil.buildOvsDatapathType(strOvsDatapath)),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+
+ } else {
+ BridgeDescription bd = optBd.get();
+ if (OvsUtil.isEqual(ofControllers, bd.controllers())) {
+ log.error("{} has valid controller setting({})", BRIDGE_OVERLAY, bd.controllers());
+ context.completed();
+ return;
+ }
+
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ if (ovsdbClient == null || !ovsdbClient.isConnected()) {
+ throw new WorkflowException("Invalid ovsdb client for " + strMgmtIp);
+ }
+
+ // If controller settings are not matched, set controller with valid controller information.
+ Set<String> eventHints = Sets.newHashSet(ofDeviceId.toString());
+
+ context.waitAnyCompletion(DeviceEvent.class, eventHints,
+ () -> ovsdbClient.setControllersWithDeviceId(bd.deviceId().get(), ofControllers),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+ }
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ Device device = deviceEvent.subject();
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_UPDATED:
+ return context.getService(DeviceService.class).isAvailable(device.id());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for updating underlay bridge device id.
+ */
+ public static class UpdateUnderlayBridgeId extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE, optional = true)
+ String strOfDevIdUnderlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ return strOfDevIdUnderlay == null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_UNDERLAY);
+ if (optBd.isPresent()) {
+ Optional<DeviceId> optDevId = optBd.get().deviceId();
+ if (optDevId.isPresent()) {
+ log.info("Updates {} of device id with existing device id {}", BRIDGE_UNDERLAY, optDevId.get());
+ strOfDevIdUnderlay = optDevId.get().toString();
+ } else {
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp),
+ DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+ log.info("Failed to find devId. Updates {} of device id with new device id {}",
+ BRIDGE_UNDERLAY, devId);
+ strOfDevIdUnderlay = devId.toString();
+ }
+ } else {
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+ log.info("Failed to find description. Updates {} of device id with new device id {}",
+ BRIDGE_UNDERLAY, devId);
+ strOfDevIdUnderlay = devId.toString();
+ }
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for creating underlay openflow bridge.
+ */
+ public static class CreateUnderlayBridge extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
+ String strOvsDatapath;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE, optional = true)
+ String strOfDevIdUnderlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(strOfDevIdUnderlay));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
+ DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdUnderlay);
+
+ if (ofControllers == null || ofControllers.size() == 0) {
+ throw new WorkflowException("Invalid of controllers");
+ }
+
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_UNDERLAY);
+ if (!optBd.isPresent()) {
+
+ // If bridge does not exist, just creates a new bridge.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> OvsUtil.createBridge(bridgeConfig,
+ BRIDGE_UNDERLAY,
+ OvsUtil.bridgeDatapathId(ofDeviceId),
+ ofControllers,
+ OvsUtil.buildOvsDatapathType(strOvsDatapath)),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+
+ } else {
+ BridgeDescription bd = optBd.get();
+ if (OvsUtil.isEqual(ofControllers, bd.controllers())) {
+ log.error("{} has valid controller setting({})", BRIDGE_UNDERLAY, bd.controllers());
+ context.completed();
+ return;
+ }
+
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ if (ovsdbClient == null || !ovsdbClient.isConnected()) {
+ throw new WorkflowException("Invalid ovsdb client for " + strMgmtIp);
+ }
+
+ // If controller settings are not matched, set controller with valid controller information.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> ovsdbClient.setControllersWithDeviceId(bd.deviceId().get(), ofControllers),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+ }
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ Device device = deviceEvent.subject();
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_UPDATED:
+ return context.getService(DeviceService.class).isAvailable(device.id());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+
+ }
+
+ /**
+ * Work-let class for creating vxlan port on the overlay bridge.
+ */
+ public static class CreateOverlayBridgeVxlanPort extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
+ String strOfDevIdOverlay;
+
+ private static final String OVS_VXLAN_PORTNAME = "vxlan";
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ DeviceId deviceId = DeviceId.deviceId(strOfDevIdOverlay);
+ if (Objects.isNull(deviceId)) {
+ throw new WorkflowException("Invalid br-int bridge, before creating VXLAN port");
+ }
+
+ DeviceService deviceService = context.getService(DeviceService.class);
+ return !deviceService.getPorts(deviceId)
+ .stream()
+ .filter(port -> OvsUtil.portName(port).contains(OVS_VXLAN_PORTNAME) && port.isEnabled())
+ .findAny().isPresent();
+ }
+
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ TunnelDescription description = DefaultTunnelDescription.builder()
+ .deviceId(BRIDGE_OVERLAY)
+ .ifaceName(OVS_VXLAN_PORTNAME)
+ .type(TunnelDescription.Type.VXLAN)
+ .remote(TunnelEndPoints.flowTunnelEndpoint())
+ .key(TunnelKeys.flowTunnelKey())
+ .build();
+
+ DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdOverlay);
+ InterfaceConfig interfaceConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, InterfaceConfig.class);
+
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> interfaceConfig.addTunnelMode(BRIDGE_OVERLAY, description),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case PORT_ADDED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for adding physical ports on the underlay openflow bridge.
+ */
+ public static class AddPhysicalPortsOnUnderlayBridge extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE, optional = true)
+ String strOfDevIdUnderlay;
+
+ @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
+ String strOvsDatapath;
+
+ @JsonDataModel(path = MODEL_PHY_PORTS)
+ ArrayNode arrNodePhysicalPorts;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ DeviceId brphyDevId = DeviceId.deviceId(strOfDevIdUnderlay);
+ return !hasAllPhysicalPorts(context, brphyDevId);
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ DeviceId brphyDevId = DeviceId.deviceId(strOfDevIdUnderlay);
+
+ context.waitCompletion(DeviceEvent.class, brphyDevId.toString(),
+ () -> addPhysicalPorts(context, brphyDevId, BRIDGE_UNDERLAY, strOvsDatapath),
+ TIMEOUT_PORT_ADDITION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case PORT_ADDED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+
+ private final List<String> getPhysicalPorts(WorkflowContext context) throws WorkflowException {
+ List<String> ports = Lists.newArrayList();
+ for (JsonNode jsonNode : arrNodePhysicalPorts) {
+ check(jsonNode instanceof TextNode, "Invalid physical ports " + arrNodePhysicalPorts);
+ ports.add(jsonNode.asText());
+ }
+ return ports;
+ }
+
+ private final boolean hasAllPhysicalPorts(WorkflowContext context, DeviceId devId) throws WorkflowException {
+
+ List<Port> devPorts = context.getService(DeviceService.class).getPorts(devId);
+ check(devPorts != null, "Invalid device ports for " + devId);
+ List<String> physicalPorts = getPhysicalPorts(context);
+ check(physicalPorts != null, "Invalid physical ports" + context);
+
+ log.info("physicalPorts: {} for {}", physicalPorts, devId);
+ for (String port: physicalPorts) {
+ if (devPorts.stream().noneMatch(p -> OvsUtil.portName(p).contains(port))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private final boolean hasPort(WorkflowContext context, DeviceId devId, String portName)
+ throws WorkflowException {
+
+ List<Port> devPorts = context.getService(DeviceService.class).getPorts(devId);
+ check(devPorts != null, "Invalid device ports for " + devId);
+ return devPorts.stream().anyMatch(p -> OvsUtil.portName(p).contains(portName));
+ }
+
+ private final void addPhysicalPorts(WorkflowContext context, DeviceId devId, String bridgeName,
+ String strOvsDatapathType)
+ throws WorkflowException {
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ check(ovsdbClient != null, "Invalid ovsdb client");
+
+ List<String> physicalPorts = getPhysicalPorts(context);
+ check(physicalPorts != null, "Invalid physical ports");
+
+ OvsDatapathType datapathType = OvsUtil.buildOvsDatapathType(strOvsDatapathType);
+ check(datapathType != null, "Invalid data path type");
+
+ List<String> sortedPhyPorts = physicalPorts.stream().sorted().collect(Collectors.toList());
+
+ for (String port: sortedPhyPorts) {
+ if (hasPort(context, devId, port)) {
+ continue;
+ }
+ log.info("adding port {} on {}", port, devId);
+ switch (datapathType) {
+ case NETDEV:
+ throw new WorkflowException("NETDEV datapathType are not supported");
+ //break;
+ case SYSTEM:
+ default:
+ ovsdbClient.createPort(BridgeName.bridgeName(bridgeName).name(), port);
+ }
+ }
+ }
+ }
+
+ /**
+ * Work-let class for configure local ip of underlay openflow bridge.
+ */
+ public static class ConfigureUnderlayBridgeLocalIp extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_SSH_ACCESSINFO)
+ JsonNode strSshAccessInfo;
+
+ @JsonDataModel(path = MODEL_VTEP_IP)
+ String strVtepIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
+ check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
+
+ NetworkAddress vtepIp = NetworkAddress.valueOf(strVtepIp);
+ check(Objects.nonNull(vtepIp), "Invalid vtep ip " + context.data());
+
+ return !SshUtil.exec(sshAccessInfo,
+ session ->
+ SshUtil.hasIpAddrOnInterface(session, BRIDGE_UNDERLAY, vtepIp)
+ && SshUtil.isIpLinkUpOnInterface(session, BRIDGE_UNDERLAY)
+ );
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
+ check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
+
+ NetworkAddress vtepIp = NetworkAddress.valueOf(strVtepIp);
+ check(Objects.nonNull(vtepIp), "Invalid vtep ip " + context.data());
+
+ SshUtil.exec(sshAccessInfo,
+ session -> {
+ SshUtil.addIpAddrOnInterface(session, BRIDGE_UNDERLAY, vtepIp);
+ SshUtil.setIpLinkUpOnInterface(session, BRIDGE_UNDERLAY);
+ return "";
+ });
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for deleting overlay bridge config.
+ */
+ public static class DeleteOverlayBridgeConfig extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
+ return bridges.stream()
+ .anyMatch(bd -> Objects.equals(bd.name(), BRIDGE_OVERLAY));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ bridgeConfig.deleteBridge(BridgeName.bridgeName(BRIDGE_OVERLAY));
+
+ for (int i = 0; i < 10; i++) {
+ if (!isNext(context)) {
+ context.completed();
+ return;
+ }
+ sleep(50);
+ }
+ throw new WorkflowException("Timeout happened for removing config");
+ }
+
+ protected void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Work-let class for removing overlay bridge openflow device.
+ */
+ public static class RemoveOverlayBridgeOfDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+
+ Device dev = context.getService(DeviceService.class).getDevice(devId);
+ return dev != null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+
+ DeviceAdminService adminService = context.getService(DeviceAdminService.class);
+
+ context.waitCompletion(DeviceEvent.class, devId.toString(),
+ () -> adminService.removeDevice(devId),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case DEVICE_REMOVED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for deleting underlay bridge config.
+ */
+ public static class DeleteUnderlayBridgeConfig extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
+ return bridges.stream()
+ .anyMatch(bd -> Objects.equals(bd.name(), BRIDGE_UNDERLAY));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ bridgeConfig.deleteBridge(BridgeName.bridgeName(BRIDGE_UNDERLAY));
+
+ for (int i = 0; i < 10; i++) {
+ if (!isNext(context)) {
+ context.completed();
+ return;
+ }
+ sleep(50);
+ }
+ throw new WorkflowException("Timeout happened for removing config");
+ }
+
+ protected void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Work-let class for removing underlay bridge openflow device.
+ */
+ public static class RemoveUnderlayBridgeOfDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+
+ Device dev = context.getService(DeviceService.class).getDevice(devId);
+ return dev != null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+
+ DeviceAdminService adminService = context.getService(DeviceAdminService.class);
+
+ context.waitCompletion(DeviceEvent.class, devId.toString(),
+ () -> adminService.removeDevice(devId),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case DEVICE_REMOVED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for removing underlay bridge and overlay openflow device.
+ */
+ public static class RemoveBridgeOfDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_FOR_OVERLAY_UNDERLAY_BRIDGE, optional = true)
+ ObjectNode ofDevId;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ boolean isOfDevicePresent = true;
+
+ if (ofDevId == null) {
+ ofDevId = JsonNodeFactory.instance.objectNode();
+ ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY), OvsUtil.buildOfDeviceId(
+ IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY).toString());
+ ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA), OvsUtil.buildOfDeviceId(
+ IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA).toString());
+ }
+
+ if (context.getService(DeviceService.class).
+ getDevice(DeviceId.deviceId(
+ ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY)).asText())) == null) {
+ isOfDevicePresent = false;
+ }
+ if (context.getService(DeviceService.class).
+ getDevice(DeviceId.deviceId(
+ ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA)).asText())) == null) {
+ isOfDevicePresent = false;
+ }
+
+ return isOfDevicePresent;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ DeviceAdminService adminService = context.getService(DeviceAdminService.class);
+ String ofDevIdOverlay;
+ String ofDevIdUnderlay;
+
+ if (ofDevId == null) {
+ ofDevId = JsonNodeFactory.instance.objectNode();
+ ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY), OvsUtil.buildOfDeviceId(
+ IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY).toString());
+ ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA), OvsUtil.buildOfDeviceId(
+ IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA).toString());
+ }
+
+ ofDevIdOverlay = ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY)).asText();
+ ofDevIdUnderlay = ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA)).asText();
+
+ Set<String> eventHints = Sets.newHashSet(ofDevIdOverlay, ofDevIdUnderlay);
+
+ context.waitAnyCompletion(DeviceEvent.class, eventHints,
+ () -> {
+ adminService.removeDevice(DeviceId.deviceId(ofDevIdOverlay));
+ adminService.removeDevice(DeviceId.deviceId(ofDevIdUnderlay));
+ },
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case DEVICE_REMOVED:
+ log.trace("GOT DEVICE REMOVED EVENT FOR DEVICE {}", event.subject());
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/package-info.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/package-info.java
new file mode 100644
index 0000000..42214bc
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Open-flow overlay implementation package.
+ */
+package org.onosproject.ofoverlay.impl;
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/NetworkAddress.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/NetworkAddress.java
new file mode 100644
index 0000000..c8a8829
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/NetworkAddress.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl.util;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Representation of a network address, which consists of IP address and prefix.
+ */
+public final class NetworkAddress {
+ private final IpAddress ip;
+ private final IpPrefix prefix;
+
+ /**
+ * Constructor for a given IP address and prefix.
+ *
+ * @param ip ip address
+ * @param prefix ip prefix
+ */
+ private NetworkAddress(IpAddress ip, IpPrefix prefix) {
+ this.ip = ip;
+ this.prefix = prefix;
+ }
+
+ /**
+ * Converts a CIDR notation string into a network address.
+ *
+ * @param cidr cidr
+ * @return network address
+ * @throws IllegalArgumentException if the cidr is not valid
+ */
+ public static NetworkAddress valueOf(String cidr) {
+ checkArgument(cidr.contains("/"));
+
+ IpAddress ipAddress = IpAddress.valueOf(cidr.split("/")[0]);
+ IpPrefix ipPrefix = IpPrefix.valueOf(cidr);
+
+ return new NetworkAddress(ipAddress, ipPrefix);
+ }
+
+ /**
+ * Returns the IP address value of the network address.
+ *
+ * @return ip address
+ */
+ public IpAddress ip() {
+ return this.ip;
+ }
+
+ /**
+ * Returns the IP prefix value of the network address.
+ *
+ * @return ip prefix
+ */
+ public IpPrefix prefix() {
+ return this.prefix;
+ }
+
+ /**
+ * Converts a network address to a CIDR notation.
+ *
+ * @return cidr notation string
+ */
+ public String cidr() {
+ return ip.toString() + "/" + prefix.prefixLength();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof NetworkAddress) {
+ NetworkAddress that = (NetworkAddress) obj;
+ return Objects.equals(ip, that.ip) && Objects.equals(prefix, that.prefix);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ip, prefix);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("IpAddress", ip)
+ .add("IpPrefix", prefix)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsDatapathType.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsDatapathType.java
new file mode 100644
index 0000000..0a64e93
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsDatapathType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl.util;
+
+/**
+ * Enumeration for OVS data path type.
+ */
+public enum OvsDatapathType {
+ EMPTY(""),
+ NETDEV("netdev"),
+ SYSTEM("system");
+
+ private final String value;
+
+ /**
+ * Constructor for OvsDatapathType enumeration.
+ *
+ * @param value string OvsDatapathType
+ */
+ OvsDatapathType(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsVersion.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsVersion.java
new file mode 100644
index 0000000..015ac73
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsVersion.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class for OVS Version.
+ */
+public final class OvsVersion {
+ protected static final Logger log = LoggerFactory.getLogger(OvsVersion.class);
+
+ private int[] versionElements = new int[] {0, 0, 0, Integer.MAX_VALUE};
+ private int depth = 0;
+
+ /**
+ * Constructor for OvsVersion.
+ * @param ovsVerStr OVS version string
+ */
+ private OvsVersion(String ovsVerStr) {
+
+ // Supporting
+ // 1.2.3 (depth = 3, public release)
+ // 1.2.3.4 (depth = 4, beta release)
+ Matcher m = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?").matcher(ovsVerStr);
+
+ if (!m.matches()) {
+ throw new IllegalArgumentException("Malformed OVS version");
+ }
+
+ versionElements[0] = Integer.parseInt(m.group(1));
+ versionElements[1] = Integer.parseInt(m.group(2));
+ versionElements[2] = Integer.parseInt(m.group(3));
+
+ if (m.group(4) == null) {
+ depth = 3;
+ return;
+ }
+
+ versionElements[3] = Integer.parseInt(m.group(5));
+ depth = 4;
+ }
+
+ /**
+ * Builder for OvsVersion.
+ * @param ovsVersionStr OVS version string
+ * @return ovs version
+ */
+ public static OvsVersion build(String ovsVersionStr) {
+ try {
+ return new OvsVersion(ovsVersionStr);
+ } catch (IllegalArgumentException e) {
+ log.error("Exception Occurred {}", e);
+ return null;
+ }
+ }
+
+ private int get(int level) {
+ return versionElements[level];
+ }
+
+ private int compare(OvsVersion tgt) {
+ //Comparison example
+ // 2.7.0 < 2.7.2
+ // 2.7.0 > 2.6.9.12
+ // 2.7.0 > 2.7.0.0 (because 2.7.0 is public release)
+ for (int i = 0; i < versionElements.length; i++) {
+ if (versionElements[i] == tgt.get(i)) {
+ continue;
+ } else if (versionElements[i] < tgt.get(i)) {
+ return (i + 1) * -1;
+ } else {
+ return (i + 1);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns whether this OVS version is equal to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is equal to the target OVS version
+ */
+ public boolean isEqOf(OvsVersion tgt) {
+ return (compare(tgt) == 0);
+ }
+
+ /**
+ * Returns whether this OVS version is prior to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is prior to the target OVS version
+ */
+ public boolean isPriorOf(OvsVersion tgt) {
+ return (compare(tgt) < 0);
+ }
+
+ /**
+ * Returns whether this OVS version is prior or equal to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is prior or equal to the target OVS version
+ */
+ public boolean isPriorOrEqOf(OvsVersion tgt) {
+ return (compare(tgt) <= 0);
+ }
+
+ /**
+ * Returns whether this OVS version is later to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is later to the target OVS version
+ */
+ public boolean isLaterOf(OvsVersion tgt) {
+ return (compare(tgt) > 0);
+ }
+
+ /**
+ * Returns whether this OVS version is later or equal to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is later or equal to the target OVS version
+ */
+ public boolean isLaterOrEqOf(OvsVersion tgt) {
+ return (compare(tgt) >= 0);
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuilder strbuild = new StringBuilder();
+ strbuild.append(versionElements[0]);
+
+ for (int i = 1; i < depth; i++) {
+ strbuild.append(".");
+ strbuild.append(versionElements[i]);
+ }
+ return strbuild.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshBehavior.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshBehavior.java
new file mode 100644
index 0000000..6189837
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshBehavior.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl.util;
+
+import com.jcraft.jsch.Session;
+import org.onosproject.workflow.api.WorkflowException;
+
+/**
+ * Functional interface for ssh behavior.
+ * @param <R> return value of ssh behavior
+ */
+@FunctionalInterface
+public interface SshBehavior<R> {
+
+ R apply(Session session) throws WorkflowException;
+
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshUtil.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshUtil.java
new file mode 100644
index 0000000..0c6caff
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshUtil.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl.util;
+
+import com.google.common.io.CharStreams;
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import org.onlab.packet.IpAddress;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.model.accessinfo.SshAccessInfo;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Class for SSH utilities.
+ */
+public final class SshUtil {
+
+ protected static final Logger log = getLogger(SshUtil.class);
+
+ private static final String STRICT_HOST_CHECKING = "StrictHostKeyChecking";
+ private static final String DEFAULT_STRICT_HOST_CHECKING = "no";
+ private static final int DEFAULT_SESSION_TIMEOUT = 30000; // milliseconds
+
+ private static final String SPACESEPERATOR = " ";
+
+ /**
+ * Default constructor.
+ */
+ private SshUtil() {
+ }
+
+ /**
+ * Creates a new session with a given ssh access information.
+ *
+ * @param sshInfo information to ssh to the remote server
+ * @return ssh session, or null
+ */
+ public static Session connect(SshAccessInfo sshInfo) {
+ Session session;
+
+ try {
+ JSch jsch = new JSch();
+ jsch.addIdentity(sshInfo.privateKey());
+
+ session = jsch.getSession(sshInfo.user(),
+ sshInfo.remoteIp().toString(),
+ sshInfo.port().toInt());
+ session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
+ session.connect(DEFAULT_SESSION_TIMEOUT);
+
+ } catch (JSchException e) {
+ log.warn("Failed to connect to {}", sshInfo.toString(), e);
+ session = authUserPwd(sshInfo);
+ }
+ return session;
+ }
+
+ /**
+ * Creates a new session with ssh access info.
+ *
+ * @param sshInfo information to ssh to the remote server
+ * @return ssh session, or null
+ */
+ public static Session authUserPwd(SshAccessInfo sshInfo) {
+ log.info("Retrying Session with {}", sshInfo);
+ try {
+ JSch jsch = new JSch();
+
+ Session session = jsch.getSession(sshInfo.user(),
+ sshInfo.remoteIp().toString(),
+ sshInfo.port().toInt());
+ session.setPassword(sshInfo.password());
+ session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
+ session.connect(DEFAULT_SESSION_TIMEOUT);
+
+ return session;
+ } catch (JSchException e) {
+ log.warn("Failed to connect to {} due to {}", sshInfo.toString(), e);
+ return null;
+ }
+ }
+
+ /**
+ * Closes a connection.
+ *
+ * @param session session ssh session
+ */
+ public static void disconnect(Session session) {
+ if (session.isConnected()) {
+ session.disconnect();
+ }
+ }
+
+ /**
+ * Fetches last term after executing command.
+ * @param session ssh session
+ * @param command command to execute
+ * @return last term, or null
+ */
+ public static String fetchLastTerm(Session session, String command) {
+ if (session == null || !session.isConnected()) {
+ log.error("Invalid session({})", session);
+ return null;
+ }
+
+ log.info("fetchLastTerm: ssh command {} to {}", command, session.getHost());
+
+ try {
+ Channel channel = session.openChannel("exec");
+ if (channel == null) {
+ log.error("Invalid channel of session({}) for command({})", session, command);
+ return null;
+ }
+
+ ((ChannelExec) channel).setCommand(command);
+ channel.setInputStream(null);
+ InputStream output = channel.getInputStream();
+ channel.connect();
+ String[] lineList = null;
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(output, StandardCharsets.UTF_8))) {
+ lineList = reader.lines().findFirst().get().split(SPACESEPERATOR);
+ } catch (IOException e) {
+ log.error("Exception in fetchLastTerm", e);
+ } finally {
+ channel.disconnect();
+ output.close();
+ }
+
+ if (lineList.length > 0) {
+ return lineList[lineList.length - 1];
+ } else {
+ return null;
+ }
+
+ } catch (JSchException | IOException e) {
+ log.error("Exception in fetchLastTerm", e);
+ return null;
+ }
+ }
+
+ /**
+ * Executes a given command. It opens exec channel for the command and closes
+ * the channel when it's done.
+ *
+ * @param session ssh connection to a remote server
+ * @param command command to execute
+ * @return command output string if the command succeeds, or null
+ */
+ public static String executeCommand(Session session, String command) {
+ if (session == null || !session.isConnected()) {
+ log.error("Invalid session({})", session);
+ return null;
+ }
+
+ log.info("executeCommand: ssh command {} to {}", command, session.getHost());
+
+ try {
+ Channel channel = session.openChannel("exec");
+
+ if (channel == null) {
+ log.debug("Invalid channel of session({}) for command({})", session, command);
+ return null;
+ }
+
+ ((ChannelExec) channel).setCommand(command);
+ channel.setInputStream(null);
+ InputStream output = channel.getInputStream();
+
+ channel.connect();
+ String result = CharStreams.toString(new InputStreamReader(output, StandardCharsets.UTF_8));
+ log.trace("SSH result(on {}): {}", session.getHost(), result);
+ channel.disconnect();
+
+ return result;
+ } catch (JSchException | IOException e) {
+ log.debug("Failed to execute command {} due to {}", command, e);
+ return null;
+ }
+ }
+
+ /**
+ * Fetches OVS version information.
+ * @param session Jsch session
+ * @return OVS version
+ * @throws WorkflowException workflow exception
+ */
+ public static OvsVersion fetchOvsVersion(Session session) throws WorkflowException {
+
+ OvsVersion devOvsVersion;
+
+ String ovsVersionStr = fetchLastTerm(session, "ovs-vswitchd --version");
+ if (ovsVersionStr == null) {
+ log.error("Failed to get ovs Version String for ssh session:{}", session);
+ throw new WorkflowException("Failed to get ovs Version String");
+ }
+
+ devOvsVersion = OvsVersion.build(ovsVersionStr);
+ if (devOvsVersion == null) {
+ log.error("Failed to build OVS version for {}", ovsVersionStr);
+ throw new WorkflowException("Failed to build OVS version");
+ }
+
+ return devOvsVersion;
+ }
+
+ private static final String IP_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
+
+ private static boolean isIPv6(String address) {
+ boolean isCorrect = true;
+ try {
+ IpAddress.valueOf(address);
+ } catch (IllegalArgumentException e) {
+ log.debug("Exception Occurred {}", e.toString());
+ isCorrect = false;
+ }
+ return isCorrect;
+ }
+
+ private static boolean isCidr(String s) {
+ String[] splits = s.split("/");
+ return splits.length == 2 &&
+ (splits[0].matches(IP_PATTERN) || isIPv6(splits[0]));
+ }
+
+ /**
+ * Adds IP address on the interface.
+ * @param session SSH session
+ * @param ifname interface name
+ * @param address network address
+ * @throws WorkflowException workflow exception
+ */
+ public static void addIpAddrOnInterface(Session session, String ifname, NetworkAddress address)
+ throws WorkflowException {
+
+ executeCommand(session, String.format("ip addr add %s dev %s", address.cidr(), ifname));
+
+ Set<NetworkAddress> result = getIpAddrOfInterface(session, ifname);
+ if (!result.contains(address)) {
+ throw new WorkflowException("Failed to set ip(" + address + ") on " + ifname + ", result: " + result);
+ }
+ }
+
+ /**
+ * Gets IP addresses of interface.
+ * @param session SSH session
+ * @param ifname interface name
+ * @return IP addresses of interface
+ */
+ public static Set<NetworkAddress> getIpAddrOfInterface(Session session, String ifname) {
+
+ String output = executeCommand(session, String.format("ip addr show %s", ifname));
+
+ if (output == null) {
+ return Collections.emptySet();
+ }
+
+ Set<NetworkAddress> result = Pattern.compile(" ")
+ .splitAsStream(output)
+ .filter(SshUtil::isCidr)
+ .map(NetworkAddress::valueOf)
+ .collect(Collectors.toSet());
+ return result;
+ }
+
+ /**
+ * Returns whether the interface has IP address.
+ * @param session SSH session
+ * @param ifname interface name
+ * @param addr network address
+ * @return whether the interface has IP address
+ */
+ public static boolean hasIpAddrOnInterface(Session session, String ifname, NetworkAddress addr) {
+
+ Set<NetworkAddress> phyBrIps = getIpAddrOfInterface(session, ifname);
+
+ return phyBrIps.stream()
+ .anyMatch(ip -> addr.ip().equals(ip.ip()));
+ }
+
+ /**
+ * Sets IP link UP on the interface.
+ * @param session SSH session
+ * @param ifname interface name
+ * @throws WorkflowException workflow exception
+ */
+ public static void setIpLinkUpOnInterface(Session session, String ifname)
+ throws WorkflowException {
+
+ executeCommand(session, String.format("ip link set %s up", ifname));
+
+ if (!isIpLinkUpOnInterface(session, ifname)) {
+ throw new WorkflowException("Failed to set UP on " + ifname);
+ }
+ }
+
+ /**
+ * Returns whether the link of the interface is up.
+ * @param session SSH session
+ * @param ifname interface name
+ * @return whether the link of the interface is up
+ */
+ public static boolean isIpLinkUpOnInterface(Session session, String ifname) {
+ String output = executeCommand(session, String.format("ip link show %s", ifname));
+
+ return output != null && output.contains("UP");
+ }
+
+ /**
+ * Executes SSH behavior.
+ * @param sshAccessInfo SSH Access information
+ * @param behavior SSH behavior
+ * @param <R> Return type of SSH behavior
+ * @return return of SSH behavior
+ * @throws WorkflowException workflow exception
+ */
+ public static <R> R exec(SshAccessInfo sshAccessInfo, SshBehavior<R> behavior)
+ throws WorkflowException {
+
+ check(sshAccessInfo != null, "Invalid sshAccessInfo");
+ Session session = connect(sshAccessInfo);
+ if (session == null || !session.isConnected()) {
+ log.error("Failed to get session for ssh:{}", sshAccessInfo);
+ throw new WorkflowException("Failed to get session for ssh:" + sshAccessInfo);
+ }
+
+ try {
+ return behavior.apply(session);
+ } finally {
+ disconnect(session);
+ }
+ }
+
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshkeyExchange.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshkeyExchange.java
new file mode 100644
index 0000000..5615112
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshkeyExchange.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ofoverlay.impl.util;
+
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SftpATTRS;
+import com.jcraft.jsch.SftpException;
+import org.onlab.packet.IpAddress;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Class for SSH key exchange.
+ */
+public class SshkeyExchange {
+
+ protected static final Logger log = getLogger(SshkeyExchange.class);
+ private static final String HOME_ENV = System.getProperty("user.home");
+ private static final String USER_ENV = System.getProperty("user.name");
+ private static final String PUBLIC_KEY = "/.ssh/id_rsa.pub";
+ private static final String PRIVATE_KEY = "/.ssh/id_rsa";
+ private static final String SFTP_CHANNEL = "sftp";
+ private static final String SSH_HOME = "/.ssh/";
+ private static final String SSH_AUTH_KEY = "/.ssh/authorized_keys";
+ private static final int SFTPPORT = 22;
+ private static final int DIR_PER = 448;
+ private static final int FILE_PER = 384;
+ private static final int KEY_SIZE = 2048;
+ private static final int DEFAULT_SESSION_TIMEOUT = 30000; // milliseconds
+
+ private Session getJschSession(String user, String host, String password) {
+ java.util.Properties config = new java.util.Properties();
+ config.put("StrictHostKeyChecking", "no");
+ Session session;
+ try {
+ session = new JSch().getSession(user, host, SFTPPORT);
+ session.setPassword(password);
+ session.setConfig(config);
+ } catch (JSchException e) {
+ log.error("Exception in getJschSession", e);
+ return null;
+ }
+
+ return session;
+ }
+
+ private boolean generateKeyPair() {
+ KeyPair kpair;
+ StringBuilder command = new StringBuilder()
+ .append("chmod 600 ")
+ .append(HOME_ENV)
+ .append(PRIVATE_KEY);
+ try {
+ kpair = KeyPair.genKeyPair(new JSch(), KeyPair.RSA, KEY_SIZE);
+ kpair.writePrivateKey(HOME_ENV + PRIVATE_KEY);
+ kpair.writePublicKey(HOME_ENV + PUBLIC_KEY, USER_ENV);
+ Runtime.getRuntime().exec(command.toString());
+ kpair.dispose();
+ } catch (JSchException | IOException e) {
+ log.error("Exception in generateKeyPair", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Exchanges SSH key.
+ * @param host SSH server host
+ * @param user user
+ * @param password password
+ * @return SSH key exchange success or not
+ */
+ public boolean sshAutoKeyExchange(IpAddress host, String user, String password) {
+
+ Session session = getJschSession(user, host.toString(), password);
+ boolean returnFlag;
+ File publickeyPath = new File(HOME_ENV + PUBLIC_KEY);
+ if (session == null) {
+ log.error("Error While establishing SFTP connection with {}", host.toString());
+ return false;
+ }
+ Channel channel;
+ String remoteHome;
+ ChannelSftp sftp;
+ FileInputStream fis = null;
+ SftpATTRS attrs = null;
+ try {
+ session.connect(DEFAULT_SESSION_TIMEOUT);
+ channel = session.openChannel(SFTP_CHANNEL);
+ if (channel == null) {
+ log.error("SFTP channel open failed for {}", host.toString());
+ return false;
+ }
+ channel.connect();
+ sftp = (ChannelSftp) channel;
+ remoteHome = sftp.getHome();
+ // checking key pair existance
+
+ if (!publickeyPath.exists()) {
+ File dirs = new File(HOME_ENV + SSH_HOME);
+ if (!dirs.exists() && !dirs.mkdirs()) {
+ log.error("{} not exists and unable to create ", dirs.getPath());
+ return false;
+ } else if (!generateKeyPair()) {
+ log.error("SSH Key pair generation failed");
+ return false;
+ }
+ }
+
+ // checking for authenticate_keys file existance
+ fis = new FileInputStream(publickeyPath);
+ try {
+ sftp.lstat(remoteHome + SSH_HOME);
+ } catch (SftpException e) {
+ sftp.mkdir(remoteHome + SSH_HOME);
+ sftp.chmod(700, remoteHome + SSH_HOME);
+ }
+ try {
+ attrs = sftp.lstat(remoteHome + SSH_AUTH_KEY);
+ } catch (SftpException e) {
+ log.info("authorized_keys file does not exist at remote device ,"
+ + "a new file will be created");
+ }
+
+ if (attrs != null) {
+ sftp.get(remoteHome + SSH_AUTH_KEY, HOME_ENV + "/tempauthorized_keys");
+
+ String pubKey;
+ try (Stream<String> st = Files.lines(Paths.get(HOME_ENV + PUBLIC_KEY))) {
+ pubKey = st.collect(Collectors.joining());
+ }
+
+ String authKey;
+ try (Stream<String> st = Files.lines(Paths.get(HOME_ENV + "/tempauthorized_keys"))) {
+ authKey = st.collect(Collectors.joining());
+ }
+
+ if (authKey.contains(pubKey)) {
+ log.info("Skipping key append to server as Key is already added");
+ } else {
+ sftp.put(fis, remoteHome + SSH_AUTH_KEY, ChannelSftp.APPEND);
+ log.info("Public key appended to server");
+ }
+ } else {
+
+ sftp.put(fis, remoteHome + SSH_AUTH_KEY, ChannelSftp.APPEND);
+ // Give proper permission to file and directory.
+ sftp.chmod(DIR_PER, remoteHome + SSH_HOME);
+ sftp.chmod(FILE_PER, remoteHome + SSH_AUTH_KEY);
+ log.info("Public key appended to server");
+ }
+
+ sftp.exit();
+ session.disconnect();
+ returnFlag = true;
+ } catch (JSchException | SftpException | IOException e) {
+ log.error("Exception occured because of {} ", e);
+ returnFlag = false;
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ log.info("Error closing public key file");
+ }
+ }
+ }
+ return returnFlag;
+
+ }
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/package-info.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/package-info.java
new file mode 100644
index 0000000..c76ba58
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Utility classes of ofoverlay.
+ */
+package org.onosproject.ofoverlay.impl.util;
\ No newline at end of file
diff --git a/apps/workflow/ofoverlay/test-cfg/empty.json b/apps/workflow/ofoverlay/test-cfg/empty.json
new file mode 100755
index 0000000..2691a31
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/empty.json
@@ -0,0 +1,12 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ ]
+
+ }
+
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/test-cfg/network-cfg-cr-wf.json b/apps/workflow/ofoverlay/test-cfg/network-cfg-cr-wf.json
new file mode 100755
index 0000000..19e2669
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/network-cfg-cr-wf.json
@@ -0,0 +1,317 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-000",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.8",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.8",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova0_1" ],
+ "vtepIp" : "120.0.0.200/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-001",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.9",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.9",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova1_1" ],
+ "vtepIp" : "120.0.0.201/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-002",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.10",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.10",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova2_1" ],
+ "vtepIp" : "120.0.0.202/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-003",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.11",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.11",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova3_1" ],
+ "vtepIp" : "120.0.0.203/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-000",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.4",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.4",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf0_0", "leaf0_1", "leaf0_nova00" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-001",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.5",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.5",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf1_0", "leaf1_1", "leaf1_nova10" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-002",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.6",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.6",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf2_0", "leaf2_1", "leaf2_nova20" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-003",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.7",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.7",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf3_0", "leaf3_1", "leaf3_nova30" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Spine-000",
+ "id" : "of-overlay.workflow-ovs-spine",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.2",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.2",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "spine0_0", "spine0_1", "spine0_2", "spine0_3" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Spine-001",
+ "id" : "of-overlay.workflow-ovs-spine",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.3",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.3",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "spine1_0", "spine1_1", "spine1_2", "spine1_3" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ }
+
+ ]
+ }
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/test-cfg/network-cfg-ovs-cr-multiEvent-wf.json b/apps/workflow/ofoverlay/test-cfg/network-cfg-ovs-cr-multiEvent-wf.json
new file mode 100755
index 0000000..6f9d2a5
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/network-cfg-ovs-cr-multiEvent-wf.json
@@ -0,0 +1,41 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-000",
+ "id" : "of-overlay.workflow-nova-multiEvent-test",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.8",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.8",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "enp4s2" ],
+ "vtepIp" : "120.0.0.200/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/test-cfg/network-cfg-ovs-waitAll-rm-bridge-wf.json b/apps/workflow/ofoverlay/test-cfg/network-cfg-ovs-waitAll-rm-bridge-wf.json
new file mode 100755
index 0000000..c9e387e
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/network-cfg-ovs-waitAll-rm-bridge-wf.json
@@ -0,0 +1,34 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-000",
+ "id" : "of-overlay.clean-workflow-nova-waitAll-Bridge-Del",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.8",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.8",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ }
+
+ ]
+
+ }
+
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/test-cfg/network-cfg-rm-wf.json b/apps/workflow/ofoverlay/test-cfg/network-cfg-rm-wf.json
new file mode 100755
index 0000000..be554a9
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/network-cfg-rm-wf.json
@@ -0,0 +1,98 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-000",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.8",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.8",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-001",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.9",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.9",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-002",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.10",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.10",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-003",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.11",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.11",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ }
+
+
+ ]
+
+ }
+
+ }
+ }
+}