blob: cff8d934264be5dce9a2b43ae584b49b8912e190 [file] [log] [blame]
Laszlo Papp5bdd0e42017-10-27 10:18:21 +01001/*
2 * Copyright 2017-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 */
16
17package org.onosproject.net.optical.rest;
18
19import static org.onlab.util.Tools.nullIsIllegal;
20import static org.onlab.util.Tools.nullIsNotFound;
21
22import com.fasterxml.jackson.databind.JsonNode;
alessio3039bdb2018-11-29 14:12:32 +010023import com.fasterxml.jackson.databind.node.ArrayNode;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010024import com.fasterxml.jackson.databind.node.ObjectNode;
alessio3039bdb2018-11-29 14:12:32 +010025import org.onlab.graph.ScalarWeight;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010026import org.onosproject.core.ApplicationId;
27import org.onosproject.core.CoreService;
alessio3039bdb2018-11-29 14:12:32 +010028import org.onosproject.net.Device;
29import org.onosproject.net.AnnotationKeys;
30import org.onosproject.net.Link;
31import org.onosproject.net.DefaultPath;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010032import org.onosproject.net.ConnectPoint;
alessio3039bdb2018-11-29 14:12:32 +010033import org.onosproject.net.OchSignal;
34import org.onosproject.net.DeviceId;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010035import org.onosproject.net.device.DeviceService;
alessio3039bdb2018-11-29 14:12:32 +010036import org.onosproject.net.flow.criteria.Criterion;
37import org.onosproject.net.flow.criteria.OchSignalCriterion;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010038import org.onosproject.net.intent.Intent;
39import org.onosproject.net.intent.IntentService;
alessio3039bdb2018-11-29 14:12:32 +010040import org.onosproject.net.intent.FlowRuleIntent;
41import org.onosproject.net.intent.IntentState;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010042import org.onosproject.net.intent.Key;
alessio3039bdb2018-11-29 14:12:32 +010043import org.onosproject.net.intent.OpticalConnectivityIntent;
alessio0a0f3342019-10-28 16:58:01 +010044import org.onosproject.net.intent.OpticalCircuitIntent;
alessio3039bdb2018-11-29 14:12:32 +010045import org.onosproject.net.link.LinkService;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010046import org.onosproject.net.optical.json.OchSignalCodec;
alessio3039bdb2018-11-29 14:12:32 +010047import org.onosproject.net.provider.ProviderId;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010048import org.onosproject.rest.AbstractWebResource;
49import org.slf4j.Logger;
50
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010051import javax.ws.rs.POST;
alessio3039bdb2018-11-29 14:12:32 +010052import javax.ws.rs.GET;
53import javax.ws.rs.DELETE;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010054import javax.ws.rs.Path;
55import javax.ws.rs.Produces;
alessio3039bdb2018-11-29 14:12:32 +010056import javax.ws.rs.PathParam;
57import javax.ws.rs.Consumes;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010058import javax.ws.rs.core.Context;
59import javax.ws.rs.core.MediaType;
60import javax.ws.rs.core.Response;
61import javax.ws.rs.core.UriBuilder;
62import javax.ws.rs.core.UriInfo;
63import java.io.IOException;
64import java.io.InputStream;
alessio3039bdb2018-11-29 14:12:32 +010065import java.util.ArrayList;
66import java.util.Iterator;
67import java.util.List;
68import java.util.stream.Collectors;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010069
alessio3039bdb2018-11-29 14:12:32 +010070import static org.onosproject.net.optical.util.OpticalIntentUtility.createExplicitOpticalIntent;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010071import static org.slf4j.LoggerFactory.getLogger;
72
Ray Milkey86ee5e82018-04-02 15:33:07 -070073import static org.onlab.util.Tools.readTreeFromStream;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010074
Ray Milkey86ee5e82018-04-02 15:33:07 -070075
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010076/**
77 * Query, submit and withdraw optical network intents.
78 */
79@Path("intents")
80public class OpticalIntentsWebResource extends AbstractWebResource {
81
82 private static final Logger log = getLogger(OpticalIntentsWebResource.class);
83
84 private static final String JSON_INVALID = "Invalid json input";
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010085 private static final String APP_ID = "appId";
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010086 private static final String INGRESS_POINT = "ingressPoint";
87 private static final String EGRESS_POINT = "egressPoint";
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010088 private static final String BIDIRECTIONAL = "bidirectional";
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010089 private static final String SIGNAL = "signal";
alessio3039bdb2018-11-29 14:12:32 +010090 private static final String SUGGESTEDPATH = "suggestedPath";
91 private static final String MISSING_MEMBER_MESSAGE = " member is required";
92 private static final String E_APP_ID_NOT_FOUND = "Application ID is not found";
93 private static final ProviderId PROVIDER_ID = new ProviderId("netconf", "optical-rest");
alessio0a0f3342019-10-28 16:58:01 +010094 private static final int NUM_CRITERIA_OPTICAL_CONNECTIVIY_RULE = 3;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +010095
96 @Context
97 private UriInfo uriInfo;
98
99 /**
100 * Submits a new optical intent.
101 * Creates and submits optical intents from the JSON request.
102 *
103 * @param stream input JSON
104 * @return status of the request - CREATED if the JSON is correct,
105 * BAD_REQUEST if the JSON is invalid
106 * @onos.rsModel CreateIntent
107 */
108 @POST
109 @Consumes(MediaType.APPLICATION_JSON)
110 @Produces(MediaType.APPLICATION_JSON)
111 public Response createIntent(InputStream stream) {
112 try {
113 IntentService service = get(IntentService.class);
Ray Milkey86ee5e82018-04-02 15:33:07 -0700114 ObjectNode root = readTreeFromStream(mapper(), stream);
Laszlo Papp5bdd0e42017-10-27 10:18:21 +0100115 Intent intent = decode(root);
116 service.submit(intent);
117 UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
118 .path("intents")
119 .path(intent.appId().name())
120 .path(Long.toString(intent.id().fingerprint()));
121 return Response
122 .created(locationBuilder.build())
123 .build();
124 } catch (IOException ioe) {
125 throw new IllegalArgumentException(ioe);
126 }
127 }
128
alessio3039bdb2018-11-29 14:12:32 +0100129 /**
130 * Get the optical intents on the network.
131 *
132 * @return 200 OK
133 */
134 @GET
135 @Produces(MediaType.APPLICATION_JSON)
136 public Response getIntents() {
137
138 DeviceService deviceService = get(DeviceService.class);
139
140 IntentService intentService = get(IntentService.class);
141 Iterator intentItr = intentService.getIntents().iterator();
142
143 ArrayNode arrayFlows = mapper().createArrayNode();
144
145 while (intentItr.hasNext()) {
146
147 Intent intent = (Intent) intentItr.next();
148 if (intent instanceof OpticalConnectivityIntent) {
149
150 OpticalConnectivityIntent opticalConnectivityIntent = (OpticalConnectivityIntent) intent;
151
152 Device srcDevice = deviceService.getDevice(opticalConnectivityIntent.getSrc().deviceId());
153 Device dstDevice = deviceService.getDevice(opticalConnectivityIntent.getDst().deviceId());
154
155 String srcDeviceName = srcDevice.annotations().value(AnnotationKeys.NAME);
156 String dstDeviceName = dstDevice.annotations().value(AnnotationKeys.NAME);
157
158 ObjectNode objectNode = mapper().createObjectNode();
159
160 objectNode.put("intent id", opticalConnectivityIntent.id().toString());
161 objectNode.put("app id", opticalConnectivityIntent.appId().name());
alessio0a0f3342019-10-28 16:58:01 +0100162 objectNode.put("state",
163 intentService.getIntentState(opticalConnectivityIntent.key()).toString());
alessio3039bdb2018-11-29 14:12:32 +0100164 objectNode.put("src", opticalConnectivityIntent.getSrc().toString());
165 objectNode.put("dst", opticalConnectivityIntent.getDst().toString());
166 objectNode.put("srcName", srcDeviceName);
167 objectNode.put("dstName", dstDeviceName);
168
169 //Only for INSTALLED intents
170 if (intentService.getIntentState(intent.key()) == IntentState.INSTALLED) {
171
172 //Retrieve associated FlowRuleIntent
173 FlowRuleIntent installableIntent =
174 (FlowRuleIntent) intentService.getInstallableIntents(opticalConnectivityIntent.key())
175 .stream()
176 .filter(FlowRuleIntent.class::isInstance)
177 .findFirst()
178 .orElse(null);
179
alessio0a0f3342019-10-28 16:58:01 +0100180 //FlowRules computed by the OpticalConnectivityIntentCompiler includes 3 criteria, one of those
181 //is the OchSignal, thus retrieve used ochSignal from the selector of one of the installed rules
alessio3039bdb2018-11-29 14:12:32 +0100182 //TODO store utilized ochSignal in the intent resources
183 if (installableIntent != null) {
184 OchSignal signal = installableIntent.flowRules().stream()
alessio0a0f3342019-10-28 16:58:01 +0100185 .filter(r -> r.selector().criteria().size() == NUM_CRITERIA_OPTICAL_CONNECTIVIY_RULE)
alessio3039bdb2018-11-29 14:12:32 +0100186 .map(r -> ((OchSignalCriterion)
187 r.selector().getCriterion(Criterion.Type.OCH_SIGID)).lambda())
188 .findFirst()
189 .orElse(null);
190
191 objectNode.put("ochSignal", signal.toString());
192 objectNode.put("centralFreq", signal.centralFrequency().asTHz() + " THz");
193 }
194
195 //Retrieve path and print it to REST
196 if (installableIntent != null) {
197 String path = installableIntent.resources().stream()
198 .filter(Link.class::isInstance)
199 .map(Link.class::cast)
200 .map(r -> deviceService.getDevice(r.src().deviceId()))
201 .map(r -> r.annotations().value(AnnotationKeys.NAME))
202 .collect(Collectors.joining(" -> "));
203
204 List<Link> pathLinks = installableIntent.resources().stream()
205 .filter(Link.class::isInstance)
206 .map(Link.class::cast)
207 .collect(Collectors.toList());
208
209 DefaultPath defaultPath = new DefaultPath(PROVIDER_ID, pathLinks, new ScalarWeight(1));
210
211 objectNode.put("path", defaultPath.toString());
212 objectNode.put("pathName", path + " -> " + dstDeviceName);
213 }
214 }
215
216 arrayFlows.add(objectNode);
217 }
218 }
219
220 ObjectNode root = this.mapper().createObjectNode().putPOJO("Intents", arrayFlows);
221 return ok(root).build();
222 }
223
224 /**
225 * Delete the specified optical intent.
226 *
227 * @param appId application identifier
228 * @param keyString intent key
229 * @return 204 NO CONTENT
230 */
231 @DELETE
232 @Consumes(MediaType.APPLICATION_JSON)
233 @Path("{appId}/{key}")
234 public Response deleteIntent(@PathParam("appId") String appId,
235 @PathParam("key") String keyString) {
236
237 final ApplicationId app = get(CoreService.class).getAppId(appId);
238 nullIsNotFound(app, "Application Id not found");
239
240 IntentService intentService = get(IntentService.class);
241 Intent intent = intentService.getIntent(Key.of(keyString, app));
242 if (intent == null) {
243 intent = intentService.getIntent(Key.of(Long.decode(keyString), app));
244 }
245 nullIsNotFound(intent, "Intent Id is not found");
246
alessio0a0f3342019-10-28 16:58:01 +0100247 if ((intent instanceof OpticalConnectivityIntent) || (intent instanceof OpticalCircuitIntent)) {
alessio3039bdb2018-11-29 14:12:32 +0100248 intentService.withdraw(intent);
249 } else {
250 throw new IllegalArgumentException("Specified intent is not of type OpticalConnectivityIntent");
251 }
252
253 return Response.noContent().build();
254 }
255
Laszlo Papp5bdd0e42017-10-27 10:18:21 +0100256 private Intent decode(ObjectNode json) {
257 JsonNode ingressJson = json.get(INGRESS_POINT);
258 if (!ingressJson.isObject()) {
259 throw new IllegalArgumentException(JSON_INVALID);
260 }
261
262 ConnectPoint ingress = codec(ConnectPoint.class).decode((ObjectNode) ingressJson, this);
263
264 JsonNode egressJson = json.get(EGRESS_POINT);
265 if (!egressJson.isObject()) {
266 throw new IllegalArgumentException(JSON_INVALID);
267 }
268
269 ConnectPoint egress = codec(ConnectPoint.class).decode((ObjectNode) egressJson, this);
270
271 JsonNode bidirectionalJson = json.get(BIDIRECTIONAL);
272 boolean bidirectional = bidirectionalJson != null ? bidirectionalJson.asBoolean() : false;
273
274 JsonNode signalJson = json.get(SIGNAL);
275 OchSignal signal = null;
276 if (signalJson != null) {
277 if (!signalJson.isObject()) {
278 throw new IllegalArgumentException(JSON_INVALID);
279 } else {
280 signal = OchSignalCodec.decode((ObjectNode) signalJson);
281 }
282 }
283
284 String appIdString = nullIsIllegal(json.get(APP_ID), APP_ID + MISSING_MEMBER_MESSAGE).asText();
285 CoreService service = getService(CoreService.class);
286 ApplicationId appId = nullIsNotFound(service.getAppId(appIdString), E_APP_ID_NOT_FOUND);
alessio3039bdb2018-11-29 14:12:32 +0100287
Laszlo Papp5bdd0e42017-10-27 10:18:21 +0100288 Key key = null;
289 DeviceService deviceService = get(DeviceService.class);
290
alessio3039bdb2018-11-29 14:12:32 +0100291 JsonNode suggestedPathJson = json.get(SUGGESTEDPATH);
292 DefaultPath suggestedPath = null;
293 LinkService linkService = get(LinkService.class);
294
295 if (suggestedPathJson != null) {
296 if (!suggestedPathJson.isObject()) {
297 throw new IllegalArgumentException(JSON_INVALID);
298 } else {
299 ArrayNode linksJson = nullIsIllegal((ArrayNode) suggestedPathJson.get("links"),
300 "Suggested path specified without links");
301
302 List<Link> listLinks = new ArrayList<>();
303
304 for (JsonNode node : linksJson) {
305
306 String srcString = node.get("src").asText();
307 String dstString = node.get("dst").asText();
308
309 ConnectPoint srcConnectPoint = ConnectPoint.fromString(srcString);
310 ConnectPoint dstConnectPoint = ConnectPoint.fromString(dstString);
311
312 Link link = linkService.getLink(srcConnectPoint, dstConnectPoint);
313 if (link == null) {
314 throw new IllegalArgumentException("Not existing link in the suggested path");
315 }
316
317 listLinks.add(link);
318 }
319
320 if ((!listLinks.get(0).src().deviceId().equals(ingress.deviceId())) ||
321 (!listLinks.get(0).src().port().equals(ingress.port())) ||
322 (!listLinks.get(listLinks.size() - 1).dst().deviceId().equals(egress.deviceId())) ||
323 (!listLinks.get(listLinks.size() - 1).dst().port().equals(egress.port()))) {
324 throw new IllegalArgumentException(
325 "Suggested path not compatible with ingress or egress connect points");
326 }
327
328 if (!isPathContiguous(listLinks)) {
329 throw new IllegalArgumentException(
330 "Links specified in the suggested path are not contiguous");
331 }
332
333 suggestedPath = new DefaultPath(PROVIDER_ID, listLinks, new ScalarWeight(1));
334
335 log.debug("OpticalIntent along suggestedPath {}", suggestedPath);
336 }
337 }
338
339 return createExplicitOpticalIntent(
340 ingress, egress, deviceService, key, appId, bidirectional, signal, suggestedPath);
341 }
342
343 private boolean isPathContiguous(List<Link> path) {
344 DeviceId previousDst;
345 DeviceId currentSrc;
346
347 for (int i = 1; i < path.size(); i++) {
348 previousDst = path.get(i - 1).dst().deviceId();
349 currentSrc = path.get(i).src().deviceId();
350
351 if (!previousDst.equals(currentSrc)) {
352 log.debug("OpticalIntent links are not contiguous previous {} current {}", previousDst, currentSrc);
353 return false;
354 }
355 }
356 return true;
Laszlo Papp5bdd0e42017-10-27 10:18:21 +0100357 }
358}