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 index e948a7a7dc..02a7836d1e 100644 --- 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 @@ -55,6 +55,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Constructor of ImmutableListWorkflow. + * * @param builder builder of ImmutableListWorkflow */ private ImmutableListWorkflow(Builder builder) { @@ -148,7 +149,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { WorkflowStore store; try { - store = DefaultServiceDirectory.getService(WorkflowStore.class); + store = DefaultServiceDirectory.getService(WorkflowStore.class); } catch (ServiceNotFoundException e) { throw new WorkflowException(e); } @@ -179,8 +180,14 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { return ImmutableSet.copyOf(attributes); } + @Override + public List 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 */ @@ -195,6 +202,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Checks whether class is allowed class or not. + * * @param clazz class to check * @return Check result */ @@ -245,6 +253,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Gets a instance of builder. + * * @return instance of builder */ public static Builder builder() { @@ -263,6 +272,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Sets id of immutable list workflow. + * * @param uri id of immutable list workflow * @return builder */ @@ -274,6 +284,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Sets init worklet class name of immutable list workflow. + * * @param workletClassName class name of worklet * @return builder */ @@ -284,6 +295,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Chains worklet class name of immutable list workflow. + * * @param workletClassName class name of worklet * @return builder */ @@ -294,6 +306,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Adds workflow attribute. + * * @param attribute workflow attribute to be added * @return builder */ @@ -304,6 +317,7 @@ public final class ImmutableListWorkflow extends AbstractWorkflow { /** * Builds ImmutableListWorkflow. + * * @return instance of ImmutableListWorkflow */ public ImmutableListWorkflow build() { 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 index 96e623fa9d..848a70626a 100644 --- 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 @@ -16,6 +16,7 @@ package org.onosproject.workflow.api; import java.net.URI; +import java.util.List; import java.util.Set; /** @@ -84,4 +85,10 @@ public interface Workflow { * @return attributes */ Set attributes(); + + /** + * Returns worklet type list. + * @return worklet type + */ + List getWorkletTypeList(); } 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 0000000000..9227092121 --- /dev/null +++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataModelException.java @@ -0,0 +1,62 @@ +/* + * 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 java.util.Map; + +/** + * Workflow DataModel exception class. + */ +public class WorkflowDataModelException extends WorkflowException { + + private String workflowName; + private Map> errorListMap; + + + /** + * Constructor for Workflow DataModel Exception. + * + * @param msg exception message + */ + public WorkflowDataModelException(String msg) { + + super(msg); + } + + /** + * Constructor for Workflow DataModel Exception. + * + * @param msg exception message + * @param workflowName workflow name + * @param errorListMap throwable to deliver + */ + public WorkflowDataModelException(String msg, String workflowName, Map> errorListMap) { + super(msg); + this.workflowName = workflowName; + this.errorListMap = errorListMap; + } + + @Override + public String toString() { + return "WorkflowDataModelException{" + + "workflowName='" + workflowName + '\'' + + ", errorListMap=" + errorListMap.toString() + + '}'; + } + +} 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 index 941ba70d43..381b2d5370 100644 --- 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 @@ -37,15 +37,16 @@ import java.util.Objects; 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 + ")", + description = "Test name (" + INVOKE_SAMPLE + " | " + EXCEPTION_SAMPLE + ")", required = true) @Completion(WorkFlowTestCompleter.class) private String testName = null; @Argument(index = 1, name = "arg1", - description = "number of test for " + INVOKE_SAMPLE, + description = "number of test for (" + INVOKE_SAMPLE + " | " + EXCEPTION_SAMPLE + ")", required = false) private String arg1 = null; @@ -76,6 +77,26 @@ public class WorkFlowTestCommand extends AbstractShellCommand { 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); } @@ -83,6 +104,7 @@ public class WorkFlowTestCommand extends AbstractShellCommand { /** * Workflow invoke test_name. + * * @param num the arg1 of workflow to test_name */ private void invokeSampleTest(int num) { @@ -94,9 +116,23 @@ public class WorkFlowTestCommand extends AbstractShellCommand { } } + /** + * 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 workflowId workflow id * @param workplaceName workplace name */ private void invoke(String workflowId, String workplaceName) { 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 index 5866932d3c..8eb7c0c029 100644 --- 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 @@ -15,22 +15,27 @@ */ package org.onosproject.workflow.impl; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.JsonNodeType; import org.onosproject.net.config.NetworkConfigRegistry; import org.onosproject.net.config.NetworkConfigService; -import org.onosproject.workflow.api.DefaultWorkplace; -import org.onosproject.workflow.api.JsonDataModelTree; -import org.onosproject.workflow.api.Workflow; -import org.onosproject.workflow.api.WorkflowContext; -import org.onosproject.workflow.api.WorkflowDescription; -import org.onosproject.workflow.api.WorkflowException; import org.onosproject.workflow.api.WorkflowService; import org.onosproject.workflow.api.WorkflowExecutionService; -import org.onosproject.workflow.api.WorkflowStore; -import org.onosproject.workflow.api.Workplace; -import org.onosproject.workflow.api.WorkplaceDescription; 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.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -38,8 +43,15 @@ import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.slf4j.Logger; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; import java.net.URI; +import java.util.Map; +import java.util.HashMap; import java.util.Objects; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.slf4j.LoggerFactory.getLogger; @@ -118,21 +130,121 @@ public class WorkflowManager implements WorkflowService { @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(WorkplaceWorkflow.WF_CREATE_WORKFLOW)); + Workflow workflow = workflowStore.get(URI.create(worklowDescJson.get("id").asText())); if (Objects.isNull(workflow)) { + throw new WorkflowException("Invalid Workflow"); + } + + if (!checkWorkflowSchema(workflow, worklowDescJson)) { + throw new WorkflowException("Invalid Workflow " + worklowDescJson.get("id").asText()); + } + + 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 = workflow.buildSystemContext(workplace, new JsonDataModelTree(worklowDescJson)); + 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 jsonNode jsonNode + * @throws WorkflowException workflow exception + */ + + private boolean checkWorkflowSchema(Workflow workflow, JsonNode jsonNode) throws WorkflowException { + + Map> workletDataTypeMap = new HashMap<>(); + for (String workletType : workflow.getWorkletTypeList()) { + Map jsonDataModelMap = new HashMap<>(); + if (Objects.equals(workletType, Worklet.Common.INIT.tag()) + || (Objects.equals(workletType, Worklet.Common.COMPLETED.tag()))) { + continue; + } + Worklet worklet = workflow.getWorkletInstance(workletType); + Class cls = worklet.getClass(); + for (Field field : cls.getDeclaredFields()) { + if (field.isSynthetic()) { + continue; + } + Annotation[] annotations = field.getAnnotations(); + for (Annotation annotation : annotations) { + 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"); + } + String path = matcher.group(1); + if (checkJsonNodeDataType(jsonNode, field, path)) { + jsonDataModelMap.put(path, field.getType().getName()); + } + } + } + } + if (!jsonDataModelMap.isEmpty()) { + workletDataTypeMap.put(worklet.tag(), jsonDataModelMap); + } + + } + if (!workletDataTypeMap.isEmpty()) { + throw new WorkflowDataModelException("invalid workflow ", workflow.id().toString(), workletDataTypeMap); + } + return true; + } + + + private boolean checkJsonNodeDataType(JsonNode jsonNode, Field field, String path) throws WorkflowException { + if (!Objects.nonNull(jsonNode.get("data")) && !Objects.nonNull(jsonNode.get("data").get(path))) { + throw new WorkflowException("Invalid Json"); + } + JsonNodeType jsonNodeType = jsonNode.get("data").get(path).getNodeType(); + if (jsonNodeType != null) { + switch (jsonNodeType) { + case NUMBER: + if (!(field.getType().isAssignableFrom(Integer.class))) { + return true; + } + break; + case STRING: + if (!(field.getType().isAssignableFrom(String.class))) { + return true; + } + break; + case OBJECT: + if (!(field.getType().isAssignableFrom(Objects.class))) { + return true; + } + break; + case BOOLEAN: + if (!(field.getType().isAssignableFrom(Boolean.class))) { + return true; + } + break; + case ARRAY: + if (!(field.getType().isAssignableFrom(Arrays.class))) { + return true; + } + break; + default: + return true; + } + } else { + return false; + + } + return false; + } + @Override public void terminateWorkflow(WorkflowDescription wfDesc) throws WorkflowException { log.info("terminateWorkflow: {}", wfDesc); 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 index 252c17d29a..2a9b9f6025 100644 --- 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 @@ -116,6 +116,15 @@ public class SampleWorkflow { .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); + } /** @@ -308,4 +317,29 @@ public class SampleWorkflow { } } + /** + * 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"); + } + } }