blob: ca0570f6168b0af6d84a4eab7d64130092fd45da [file] [log] [blame]
jaegonkim6a7b5242018-09-12 23:09:42 +09001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.workflow.impl;
17
mohamedrahilr63a921c2019-02-27 19:48:25 +053018
jaegonkim6a7b5242018-09-12 23:09:42 +090019import com.fasterxml.jackson.databind.JsonNode;
jaegonkim13b25cb2019-03-22 06:23:23 +090020import com.fasterxml.jackson.databind.node.ArrayNode;
jaegonkim6a7b5242018-09-12 23:09:42 +090021import com.fasterxml.jackson.databind.node.JsonNodeFactory;
mohamedrahilr63a921c2019-02-27 19:48:25 +053022import com.fasterxml.jackson.databind.node.JsonNodeType;
jaegonkim13b25cb2019-03-22 06:23:23 +090023import com.fasterxml.jackson.databind.node.MissingNode;
jaegonkim6a7b5242018-09-12 23:09:42 +090024import org.onosproject.net.config.NetworkConfigRegistry;
25import org.onosproject.net.config.NetworkConfigService;
jaegonkim6a7b5242018-09-12 23:09:42 +090026import org.onosproject.workflow.api.WorkflowService;
27import org.onosproject.workflow.api.WorkflowExecutionService;
jaegonkim6a7b5242018-09-12 23:09:42 +090028import org.onosproject.workflow.api.WorkplaceStore;
mohamedrahilr63a921c2019-02-27 19:48:25 +053029import org.onosproject.workflow.api.WorkflowStore;
30import org.onosproject.workflow.api.WorkplaceDescription;
31import org.onosproject.workflow.api.WorkflowException;
32import org.onosproject.workflow.api.DefaultWorkplace;
33import org.onosproject.workflow.api.JsonDataModelTree;
34import org.onosproject.workflow.api.WorkflowDescription;
35import org.onosproject.workflow.api.Workplace;
36import org.onosproject.workflow.api.WorkflowDataModelException;
37import org.onosproject.workflow.api.Workflow;
38import org.onosproject.workflow.api.Worklet;
39import org.onosproject.workflow.api.WorkflowContext;
40import org.onosproject.workflow.api.JsonDataModel;
Ray Milkeydf521292018-10-04 15:13:33 -070041import org.osgi.service.component.annotations.Activate;
42import org.osgi.service.component.annotations.Component;
43import org.osgi.service.component.annotations.Deactivate;
44import org.osgi.service.component.annotations.Reference;
45import org.osgi.service.component.annotations.ReferenceCardinality;
jaegonkim6a7b5242018-09-12 23:09:42 +090046import org.slf4j.Logger;
47
mohamedrahilr63a921c2019-02-27 19:48:25 +053048import java.lang.annotation.Annotation;
49import java.lang.reflect.Field;
jaegonkim6a7b5242018-09-12 23:09:42 +090050import java.net.URI;
jaegonkim13b25cb2019-03-22 06:23:23 +090051import java.util.ArrayList;
52import java.util.List;
jaegonkim6a7b5242018-09-12 23:09:42 +090053import java.util.Objects;
jaegonkim13b25cb2019-03-22 06:23:23 +090054import java.util.Optional;
mohamedrahilr63a921c2019-02-27 19:48:25 +053055import java.util.regex.Matcher;
56import java.util.regex.Pattern;
jaegonkim6a7b5242018-09-12 23:09:42 +090057
58import static org.slf4j.LoggerFactory.getLogger;
59
Ray Milkeydf521292018-10-04 15:13:33 -070060@Component(immediate = true, service = WorkflowService.class)
jaegonkim6a7b5242018-09-12 23:09:42 +090061public class WorkflowManager implements WorkflowService {
62
63 protected static final Logger log = getLogger(WorkflowManager.class);
64
Ray Milkeydf521292018-10-04 15:13:33 -070065 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jaegonkim6a7b5242018-09-12 23:09:42 +090066 private WorkflowExecutionService workflowExecutionService;
67
Ray Milkeydf521292018-10-04 15:13:33 -070068 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jaegonkim6a7b5242018-09-12 23:09:42 +090069 protected WorkplaceStore workplaceStore;
70
Ray Milkeydf521292018-10-04 15:13:33 -070071 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jaegonkim6a7b5242018-09-12 23:09:42 +090072 protected WorkflowStore workflowStore;
73
Ray Milkeydf521292018-10-04 15:13:33 -070074 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jaegonkim6a7b5242018-09-12 23:09:42 +090075 private NetworkConfigService networkConfigService;
76
Ray Milkeydf521292018-10-04 15:13:33 -070077 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jaegonkim6a7b5242018-09-12 23:09:42 +090078 private NetworkConfigRegistry networkConfigRegistry;
79
80 private WorkflowNetConfigListener netcfgListener;
81
82 @Activate
83 public void activate() {
84 netcfgListener = new WorkflowNetConfigListener(this);
85 networkConfigRegistry.registerConfigFactory(netcfgListener.getConfigFactory());
86 networkConfigService.addListener(netcfgListener);
87 log.info("Started");
88 }
89
90 @Deactivate
91 public void deactivate() {
92 networkConfigService.removeListener(netcfgListener);
93 networkConfigRegistry.unregisterConfigFactory(netcfgListener.getConfigFactory());
94 log.info("Stopped");
95 }
96
97 @Override
98 public void createWorkplace(WorkplaceDescription wpDesc) throws WorkflowException {
99 log.info("createWorkplace: {}", wpDesc);
100
101 JsonNode root;
102 if (wpDesc.data().isPresent()) {
103 root = wpDesc.data().get();
104 } else {
105 root = JsonNodeFactory.instance.objectNode();
106 }
107 DefaultWorkplace workplace =
108 new DefaultWorkplace(wpDesc.name(), new JsonDataModelTree(root));
109 workplaceStore.registerWorkplace(wpDesc.name(), workplace);
110 }
111
112 @Override
113 public void removeWorkplace(WorkplaceDescription wpDesc) throws WorkflowException {
114 log.info("removeWorkplace: {}", wpDesc);
115 //TODO: Removing workflows belong to this workplace
116 workplaceStore.removeWorkplace(wpDesc.name());
117 }
118
119 @Override
120 public void clearWorkplace() throws WorkflowException {
121 log.info("clearWorkplace");
122 workplaceStore.getWorkplaces().stream()
123 .filter(wp -> !Objects.equals(wp.name(), Workplace.SYSTEM_WORKPLACE))
124 .forEach(wp -> workplaceStore.removeWorkplace(wp.name()));
125 }
126
127 @Override
128 public void invokeWorkflow(WorkflowDescription wfDesc) throws WorkflowException {
129 invokeWorkflow(wfDesc.toJson());
130 }
131
132 @Override
133 public void invokeWorkflow(JsonNode worklowDescJson) throws WorkflowException {
134 log.info("invokeWorkflow: {}", worklowDescJson);
jaegonkim6a7b5242018-09-12 23:09:42 +0900135 Workplace workplace = workplaceStore.getWorkplace(Workplace.SYSTEM_WORKPLACE);
136 if (Objects.isNull(workplace)) {
137 throw new WorkflowException("Invalid system workplace");
138 }
139
mohamedrahilr63a921c2019-02-27 19:48:25 +0530140 Workflow workflow = workflowStore.get(URI.create(worklowDescJson.get("id").asText()));
jaegonkim6a7b5242018-09-12 23:09:42 +0900141 if (Objects.isNull(workflow)) {
mohamedrahilr63a921c2019-02-27 19:48:25 +0530142 throw new WorkflowException("Invalid Workflow");
143 }
144
jaegonkim13b25cb2019-03-22 06:23:23 +0900145 checkWorkflowSchema(workflow, worklowDescJson);
mohamedrahilr63a921c2019-02-27 19:48:25 +0530146
147 Workflow wfCreationWf = workflowStore.get(URI.create(WorkplaceWorkflow.WF_CREATE_WORKFLOW));
148 if (Objects.isNull(wfCreationWf)) {
jaegonkim6a7b5242018-09-12 23:09:42 +0900149 throw new WorkflowException("Invalid workflow " + WorkplaceWorkflow.WF_CREATE_WORKFLOW);
150 }
151
mohamedrahilr63a921c2019-02-27 19:48:25 +0530152 WorkflowContext context = wfCreationWf.buildSystemContext(workplace, new JsonDataModelTree(worklowDescJson));
jaegonkim6a7b5242018-09-12 23:09:42 +0900153 workflowExecutionService.execInitWorklet(context);
154 }
155
mohamedrahilr63a921c2019-02-27 19:48:25 +0530156 /**
157 * Checks if the type of worklet is same as that of wfdesc Json.
158 *
159 * @param workflow workflow
jaegonkim13b25cb2019-03-22 06:23:23 +0900160 * @param worklowDescJson jsonNode
mohamedrahilr63a921c2019-02-27 19:48:25 +0530161 * @throws WorkflowException workflow exception
162 */
jaegonkim13b25cb2019-03-22 06:23:23 +0900163 private void checkWorkflowSchema(Workflow workflow, JsonNode worklowDescJson) throws WorkflowException {
mohamedrahilr63a921c2019-02-27 19:48:25 +0530164
jaegonkim13b25cb2019-03-22 06:23:23 +0900165 List<String> errors = new ArrayList<>();
mohamedrahilr63a921c2019-02-27 19:48:25 +0530166
jaegonkim13b25cb2019-03-22 06:23:23 +0900167 JsonNode dataNode = worklowDescJson.get("data");
168 if (Objects.isNull(dataNode) || dataNode instanceof MissingNode) {
169 errors.add("workflow description json does not have 'data'");
170 throw new WorkflowDataModelException(workflow.id(), worklowDescJson, errors);
171 }
172
mohamedrahilr63a921c2019-02-27 19:48:25 +0530173 for (String workletType : workflow.getWorkletTypeList()) {
jaegonkim13b25cb2019-03-22 06:23:23 +0900174
175 Worklet worklet = workflow.getWorkletInstance(workletType);
176 if (Worklet.Common.COMPLETED.equals(worklet) || Worklet.Common.INIT.equals(worklet)) {
mohamedrahilr63a921c2019-02-27 19:48:25 +0530177 continue;
178 }
jaegonkim13b25cb2019-03-22 06:23:23 +0900179
mohamedrahilr63a921c2019-02-27 19:48:25 +0530180 Class cls = worklet.getClass();
181 for (Field field : cls.getDeclaredFields()) {
jaegonkim13b25cb2019-03-22 06:23:23 +0900182
mohamedrahilr63a921c2019-02-27 19:48:25 +0530183 if (field.isSynthetic()) {
184 continue;
185 }
jaegonkim13b25cb2019-03-22 06:23:23 +0900186
187 for (Annotation annotation : field.getAnnotations()) {
188
mohamedrahilr63a921c2019-02-27 19:48:25 +0530189 if (annotation instanceof JsonDataModel) {
jaegonkim13b25cb2019-03-22 06:23:23 +0900190
mohamedrahilr63a921c2019-02-27 19:48:25 +0530191 JsonDataModel jsonDataModel = (JsonDataModel) annotation;
192 Matcher matcher = Pattern.compile("(\\w+)").matcher(jsonDataModel.path());
193 if (!matcher.find()) {
jaegonkim13b25cb2019-03-22 06:23:23 +0900194 throw new WorkflowException(
195 "Invalid Json Data Model Path(" + jsonDataModel.path() + ") in " + worklet.tag());
mohamedrahilr63a921c2019-02-27 19:48:25 +0530196 }
197 String path = matcher.group(1);
jaegonkim13b25cb2019-03-22 06:23:23 +0900198
199 Optional<String> optError =
200 getJsonNodeDataError(dataNode, worklet, field, path, jsonDataModel.optional());
201
202 if (optError.isPresent()) {
203 errors.add(optError.get());
mohamedrahilr63a921c2019-02-27 19:48:25 +0530204 }
205 }
206 }
207 }
jaegonkim13b25cb2019-03-22 06:23:23 +0900208 }
mohamedrahilr63a921c2019-02-27 19:48:25 +0530209
jaegonkim13b25cb2019-03-22 06:23:23 +0900210 if (!errors.isEmpty()) {
211 throw new WorkflowDataModelException(workflow.id(), worklowDescJson, errors);
mohamedrahilr63a921c2019-02-27 19:48:25 +0530212 }
mohamedrahilr63a921c2019-02-27 19:48:25 +0530213 }
214
jaegonkim13b25cb2019-03-22 06:23:23 +0900215 private Optional<String> getJsonNodeDataError(
216 JsonNode dataNode, Worklet worklet, Field field, String path, boolean isOptional) throws WorkflowException {
mohamedrahilr63a921c2019-02-27 19:48:25 +0530217
jaegonkim13b25cb2019-03-22 06:23:23 +0900218 // Checking the existence of path in dataNode
219 JsonNode pathNode = dataNode.get(path);
220 if (Objects.isNull(pathNode) || pathNode instanceof MissingNode) {
221
222 if (isOptional) {
223 return Optional.empty();
224
225 } else {
226 return Optional.of("data doesn't have '" + path + "' in worklet<" + worklet.tag() + ">");
mohamedrahilr63a921c2019-02-27 19:48:25 +0530227 }
mohamedrahilr63a921c2019-02-27 19:48:25 +0530228 }
jaegonkim13b25cb2019-03-22 06:23:23 +0900229
230 // Checking the type of path
231 JsonNodeType type = pathNode.getNodeType();
232
233 if (Objects.isNull(type)) {
234 throw new WorkflowException("Invalid type for " + pathNode);
235 }
236
237 switch (type) {
238 case NUMBER:
239 if (!(field.getType().isAssignableFrom(Integer.class))) {
240 return Optional.of("'" + path + "<NUMBER>' cannot be assigned to " +
241 field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
242 }
243 break;
244 case STRING:
245 if (!(field.getType().isAssignableFrom(String.class))) {
246 return Optional.of("'" + path + "<STRING>' cannot be assigned to " +
247 field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
248 }
249 break;
250 case BOOLEAN:
251 if (!(field.getType().isAssignableFrom(Boolean.class))) {
252 return Optional.of("'" + path + "<BOOLEAN>' cannot be assigned to " +
253 field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
254 }
255 break;
256 case OBJECT:
257 if (!(field.getType().isAssignableFrom(JsonNode.class))) {
258 return Optional.of("'" + path + "<OBJECT>' cannot be assigned to " +
259 field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
260 }
261 break;
262 case ARRAY:
263 if (!(field.getType().isAssignableFrom(ArrayNode.class))) {
264 return Optional.of("'" + path + "<ARRAY>' cannot be assigned to " +
265 field.getName() + "<" + field.getType() + "> in worklet<" + worklet.tag() + ">");
266 }
267 break;
268 default:
269 return Optional.of("'" + path + "<" + type + ">' is not supported");
270 }
271
272 return Optional.empty();
mohamedrahilr63a921c2019-02-27 19:48:25 +0530273 }
274
jaegonkim6a7b5242018-09-12 23:09:42 +0900275 @Override
276 public void terminateWorkflow(WorkflowDescription wfDesc) throws WorkflowException {
277 log.info("terminateWorkflow: {}", wfDesc);
278 if (Objects.nonNull(workplaceStore.getContext(wfDesc.workflowContextName()))) {
279 workplaceStore.removeContext(wfDesc.workflowContextName());
280 }
281 }
282}