[ONOS-7732] Automating switch workflow: api, app, and sample workflows
Change-Id: Iee87d4fe6cf61c1f8904d1d77df5f913a712b64a
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..af62aaa
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkplaceWorkflow.java
@@ -0,0 +1,213 @@
+/*
+ * 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;
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+}