[ONOS-7839] suggestedPath for optical connectivity intent with optical-rest support

Change-Id: I2b5093ac26149e450a14467c71656447233b5ce2
diff --git a/apps/optical-rest/src/main/java/org/onosproject/net/optical/rest/OpticalIntentsWebResource.java b/apps/optical-rest/src/main/java/org/onosproject/net/optical/rest/OpticalIntentsWebResource.java
index 2d16e02..2cb1143 100644
--- a/apps/optical-rest/src/main/java/org/onosproject/net/optical/rest/OpticalIntentsWebResource.java
+++ b/apps/optical-rest/src/main/java/org/onosproject/net/optical/rest/OpticalIntentsWebResource.java
@@ -20,23 +20,40 @@
 import static org.onlab.util.Tools.nullIsNotFound;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.graph.ScalarWeight;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
-import org.onosproject.net.OchSignal;
+import org.onosproject.net.Device;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Link;
+import org.onosproject.net.DefaultPath;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.OpticalConnectivityIntent;
+import org.onosproject.net.link.LinkService;
 import org.onosproject.net.optical.json.OchSignalCodec;
+import org.onosproject.net.provider.ProviderId;
 import org.onosproject.rest.AbstractWebResource;
 import org.slf4j.Logger;
 
-import javax.ws.rs.Consumes;
 import javax.ws.rs.POST;
+import javax.ws.rs.GET;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Consumes;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
@@ -44,11 +61,15 @@
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
 
+import static org.onosproject.net.optical.util.OpticalIntentUtility.createExplicitOpticalIntent;
 import static org.slf4j.LoggerFactory.getLogger;
 
 import static org.onlab.util.Tools.readTreeFromStream;
-import static org.onosproject.net.optical.util.OpticalIntentUtility.createOpticalIntent;
 
 
 /**
@@ -60,20 +81,15 @@
     private static final Logger log = getLogger(OpticalIntentsWebResource.class);
 
     private static final String JSON_INVALID = "Invalid json input";
-
     private static final String APP_ID = "appId";
-
     private static final String INGRESS_POINT = "ingressPoint";
     private static final String EGRESS_POINT = "egressPoint";
-
     private static final String BIDIRECTIONAL = "bidirectional";
-
     private static final String SIGNAL = "signal";
-
-    protected static final String MISSING_MEMBER_MESSAGE =
-            " member is required";
-    private static final String E_APP_ID_NOT_FOUND =
-            "Application ID is not found";
+    private static final String SUGGESTEDPATH = "suggestedPath";
+    private static final String MISSING_MEMBER_MESSAGE = " member is required";
+    private static final String E_APP_ID_NOT_FOUND = "Application ID is not found";
+    private static final ProviderId PROVIDER_ID = new ProviderId("netconf", "optical-rest");
 
     @Context
     private UriInfo uriInfo;
@@ -108,6 +124,130 @@
         }
     }
 
+    /**
+     * Get the optical intents on the network.
+     *
+     * @return 200 OK
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getIntents() {
+
+        DeviceService deviceService = get(DeviceService.class);
+
+        IntentService intentService = get(IntentService.class);
+        Iterator intentItr = intentService.getIntents().iterator();
+
+        ArrayNode arrayFlows = mapper().createArrayNode();
+
+        while (intentItr.hasNext()) {
+
+            Intent intent = (Intent) intentItr.next();
+            if (intent instanceof OpticalConnectivityIntent) {
+
+                OpticalConnectivityIntent opticalConnectivityIntent = (OpticalConnectivityIntent) intent;
+
+                Device srcDevice = deviceService.getDevice(opticalConnectivityIntent.getSrc().deviceId());
+                Device dstDevice = deviceService.getDevice(opticalConnectivityIntent.getDst().deviceId());
+
+                String srcDeviceName = srcDevice.annotations().value(AnnotationKeys.NAME);
+                String dstDeviceName = dstDevice.annotations().value(AnnotationKeys.NAME);
+
+                ObjectNode objectNode = mapper().createObjectNode();
+
+                objectNode.put("intent id", opticalConnectivityIntent.id().toString());
+                objectNode.put("app id", opticalConnectivityIntent.appId().name());
+                objectNode.put("state", intentService.getIntentState(opticalConnectivityIntent.key()).toString());
+                objectNode.put("src", opticalConnectivityIntent.getSrc().toString());
+                objectNode.put("dst", opticalConnectivityIntent.getDst().toString());
+                objectNode.put("srcName", srcDeviceName);
+                objectNode.put("dstName", dstDeviceName);
+
+                //Only for INSTALLED intents
+                if (intentService.getIntentState(intent.key()) == IntentState.INSTALLED) {
+
+                    //Retrieve associated FlowRuleIntent
+                    FlowRuleIntent installableIntent =
+                            (FlowRuleIntent) intentService.getInstallableIntents(opticalConnectivityIntent.key())
+                            .stream()
+                            .filter(FlowRuleIntent.class::isInstance)
+                            .findFirst()
+                            .orElse(null);
+
+                    //Retrieve used ochSignal from the Selector of one of the installed FlowRule
+                    //TODO store utilized ochSignal in the intent resources
+                    if (installableIntent != null) {
+                        OchSignal signal = installableIntent.flowRules().stream()
+                                .map(r -> ((OchSignalCriterion)
+                                        r.selector().getCriterion(Criterion.Type.OCH_SIGID)).lambda())
+                                .findFirst()
+                                .orElse(null);
+
+                        objectNode.put("ochSignal", signal.toString());
+                        objectNode.put("centralFreq", signal.centralFrequency().asTHz() + " THz");
+                    }
+
+                    //Retrieve path and print it to REST
+                    if (installableIntent != null) {
+                        String path = installableIntent.resources().stream()
+                                .filter(Link.class::isInstance)
+                                .map(Link.class::cast)
+                                .map(r -> deviceService.getDevice(r.src().deviceId()))
+                                .map(r -> r.annotations().value(AnnotationKeys.NAME))
+                                .collect(Collectors.joining(" -> "));
+
+                        List<Link> pathLinks = installableIntent.resources().stream()
+                                .filter(Link.class::isInstance)
+                                .map(Link.class::cast)
+                                .collect(Collectors.toList());
+
+                        DefaultPath defaultPath = new DefaultPath(PROVIDER_ID, pathLinks, new ScalarWeight(1));
+
+                        objectNode.put("path", defaultPath.toString());
+                        objectNode.put("pathName", path + " -> " + dstDeviceName);
+                    }
+                }
+
+                arrayFlows.add(objectNode);
+            }
+        }
+
+        ObjectNode root = this.mapper().createObjectNode().putPOJO("Intents", arrayFlows);
+        return ok(root).build();
+    }
+
+    /**
+     * Delete the specified optical intent.
+     *
+     * @param appId application identifier
+     * @param keyString   intent key
+     * @return 204 NO CONTENT
+     */
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("{appId}/{key}")
+    public Response deleteIntent(@PathParam("appId") String appId,
+                                 @PathParam("key") String keyString) {
+
+        final ApplicationId app = get(CoreService.class).getAppId(appId);
+        nullIsNotFound(app, "Application Id not found");
+
+        IntentService intentService = get(IntentService.class);
+        Intent intent = intentService.getIntent(Key.of(keyString, app));
+        if (intent == null) {
+            intent = intentService.getIntent(Key.of(Long.decode(keyString), app));
+        }
+        nullIsNotFound(intent, "Intent Id is not found");
+
+        if (intent instanceof OpticalConnectivityIntent) {
+            intentService.withdraw(intent);
+        } else {
+            throw new IllegalArgumentException("Specified intent is not of type OpticalConnectivityIntent");
+        }
+
+        return Response.noContent().build();
+    }
+
     private Intent decode(ObjectNode json) {
         JsonNode ingressJson = json.get(INGRESS_POINT);
         if (!ingressJson.isObject()) {
@@ -139,9 +279,75 @@
         String appIdString = nullIsIllegal(json.get(APP_ID), APP_ID + MISSING_MEMBER_MESSAGE).asText();
         CoreService service = getService(CoreService.class);
         ApplicationId appId = nullIsNotFound(service.getAppId(appIdString), E_APP_ID_NOT_FOUND);
+
         Key key = null;
         DeviceService deviceService = get(DeviceService.class);
 
-        return createOpticalIntent(ingress, egress, deviceService, key, appId, bidirectional, signal);
+        JsonNode suggestedPathJson = json.get(SUGGESTEDPATH);
+        DefaultPath suggestedPath = null;
+        LinkService linkService = get(LinkService.class);
+
+        if (suggestedPathJson != null) {
+            if (!suggestedPathJson.isObject()) {
+                throw new IllegalArgumentException(JSON_INVALID);
+            } else {
+                ArrayNode linksJson = nullIsIllegal((ArrayNode) suggestedPathJson.get("links"),
+                        "Suggested path specified without links");
+
+                List<Link> listLinks = new ArrayList<>();
+
+                for (JsonNode node : linksJson) {
+
+                    String srcString = node.get("src").asText();
+                    String dstString = node.get("dst").asText();
+
+                    ConnectPoint srcConnectPoint = ConnectPoint.fromString(srcString);
+                    ConnectPoint dstConnectPoint = ConnectPoint.fromString(dstString);
+
+                    Link link = linkService.getLink(srcConnectPoint, dstConnectPoint);
+                    if (link == null) {
+                        throw new IllegalArgumentException("Not existing link in the suggested path");
+                    }
+
+                    listLinks.add(link);
+                }
+
+                if ((!listLinks.get(0).src().deviceId().equals(ingress.deviceId())) ||
+                        (!listLinks.get(0).src().port().equals(ingress.port())) ||
+                        (!listLinks.get(listLinks.size() - 1).dst().deviceId().equals(egress.deviceId())) ||
+                        (!listLinks.get(listLinks.size() - 1).dst().port().equals(egress.port()))) {
+                    throw new IllegalArgumentException(
+                            "Suggested path not compatible with ingress or egress connect points");
+                }
+
+                if (!isPathContiguous(listLinks)) {
+                    throw new IllegalArgumentException(
+                            "Links specified in the suggested path are not contiguous");
+                }
+
+                suggestedPath = new DefaultPath(PROVIDER_ID, listLinks, new ScalarWeight(1));
+
+                log.debug("OpticalIntent along suggestedPath {}", suggestedPath);
+            }
+        }
+
+        return createExplicitOpticalIntent(
+                ingress, egress, deviceService, key, appId, bidirectional, signal, suggestedPath);
+    }
+
+    private boolean isPathContiguous(List<Link> path) {
+        DeviceId previousDst;
+        DeviceId currentSrc;
+
+        for (int i = 1; i < path.size(); i++) {
+            previousDst = path.get(i - 1).dst().deviceId();
+            currentSrc = path.get(i).src().deviceId();
+
+            if (!previousDst.equals(currentSrc)) {
+                log.debug("OpticalIntent links are not contiguous previous {} current {}", previousDst, currentSrc);
+                return false;
+            }
+        }
+        return true;
     }
 }