Support [ONOS-4593] and implement [ONOS-4594]

Changes:
- Adds extension to sp2mp intents;
- Adds extension to linkcollection intents;
- Adds extension to sp2mp compiler;
- Adds extension to linkcollection compiler;
- Adds re-ordering of the actions;
- Adds unit tests for both sp2mp intents and linkcollection intents;

Change-Id: Ib925e9066682e077a0bb4bbfd20a4382623b7541
diff --git a/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java b/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java
index 6a58b80..85bd5df 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/LinkCollectionIntent.java
@@ -16,19 +16,20 @@
 
 package org.onosproject.net.intent;
 
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import com.google.common.annotations.Beta;
-import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Link;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * Abstraction of a connectivity intent that is implemented by a set of path
@@ -41,12 +42,15 @@
 
     private final Set<ConnectPoint> ingressPoints;
     private final Set<ConnectPoint> egressPoints;
+    private final boolean egressTreatmentFlag;
     /**
      * To manage multiple selectors use case.
      */
     private final Map<ConnectPoint, TrafficSelector> ingressSelectors;
-
-    private final boolean egressTreatmentFlag;
+    /**
+     * To manage multiple treatments use case.
+     */
+    private final Map<ConnectPoint, TrafficTreatment> egressTreatments;
 
     /**
      * Creates a new actionable intent capable of funneling the selected
@@ -64,6 +68,7 @@
      * @param priority    priority to use for the flows generated by this intent
      * @param egressTreatment true if treatment should be applied by the egress device
      * @param ingressSelectors map to store the association ingress to selector
+     * @param egressTreatments map to store the association egress to treatment
      * @throws NullPointerException {@code path} is null
      */
     private LinkCollectionIntent(ApplicationId appId,
@@ -76,13 +81,15 @@
                                  List<Constraint> constraints,
                                  int priority,
                                  boolean egressTreatment,
-                                 Map<ConnectPoint, TrafficSelector> ingressSelectors) {
+                                 Map<ConnectPoint, TrafficSelector> ingressSelectors,
+                                 Map<ConnectPoint, TrafficTreatment> egressTreatments) {
         super(appId, key, resources(links), selector, treatment, constraints, priority);
         this.links = links;
         this.ingressPoints = ingressPoints;
         this.egressPoints = egressPoints;
         this.egressTreatmentFlag = egressTreatment;
         this.ingressSelectors = ingressSelectors;
+        this.egressTreatments = egressTreatments;
     }
 
     /**
@@ -95,6 +102,7 @@
         this.egressPoints = null;
         this.egressTreatmentFlag = false;
         this.ingressSelectors = null;
+        this.egressTreatments = null;
     }
 
     /**
@@ -117,6 +125,7 @@
         Set<ConnectPoint> ingressPoints;
         Set<ConnectPoint> egressPoints;
         Map<ConnectPoint, TrafficSelector> ingressSelectors = ImmutableMap.of();
+        Map<ConnectPoint, TrafficTreatment> egressTreatments = ImmutableMap.of();
         boolean egressTreatmentFlag;
 
         private Builder() {
@@ -189,6 +198,17 @@
         }
 
         /**
+         * Sets the map egress treatments to connection points of the intent.
+         *
+         * @param egressTreatments maps connection point to traffic treatment
+         * @return this builder
+         */
+        public Builder egressTreatments(Map<ConnectPoint, TrafficTreatment> egressTreatments) {
+            this.egressTreatments = ImmutableMap.copyOf(egressTreatments);
+            return this;
+        }
+
+        /**
          * Sets the links of the link collection intent
          * that will be built.
          *
@@ -231,12 +251,12 @@
                     constraints,
                     priority,
                     egressTreatmentFlag,
-                    ingressSelectors
+                    ingressSelectors,
+                    egressTreatments
             );
         }
     }
 
-
     /**
      * Returns the set of links that represent the network connections needed
      * by this intent.
@@ -274,6 +294,14 @@
     }
 
     /**
+     * Returns the multiple treatments jointly with their connection points.
+     * @return multiple treatments
+     */
+    public Map<ConnectPoint, TrafficTreatment> egressTreatments() {
+        return egressTreatments;
+    }
+
+    /**
      * Returns whether treatment should be applied on egress.
      *
      * @return the egress treatment flag
@@ -296,7 +324,8 @@
                 .add("ingress", ingressPoints())
                 .add("egress", egressPoints())
                 .add("selectors", ingressSelectors())
-                .add("treatmentOnEgress", applyTreatmentOnEgress())
+                .add("treatments", egressTreatments())
+                .add("treatementOnEgress", applyTreatmentOnEgress())
                 .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java b/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java
index 51dfcb8..4b17c66 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/SinglePointToMultiPointIntent.java
@@ -17,14 +17,18 @@
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
+import com.google.common.collect.Sets;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 
-import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 import java.util.List;
 
@@ -39,6 +43,10 @@
 
     private final ConnectPoint ingressPoint;
     private final Set<ConnectPoint> egressPoints;
+    /**
+     * To manage multiple treatments use case.
+     */
+    private final Map<ConnectPoint, TrafficTreatment> egressTreatments;
 
     /**
      * Creates a new single-to-multi point connectivity intent.
@@ -51,27 +59,32 @@
      * @param egressPoints set of ports on which traffic will egress
      * @param constraints constraints to apply to the intent
      * @param priority priority to use for flows generated by this intent
+     * @param egressTreatments map to store the association egress to treatment
      * @throws NullPointerException if {@code ingressPoint} or
      *             {@code egressPoints} is null
      * @throws IllegalArgumentException if the size of {@code egressPoints} is
      *             not more than 1
      */
     private SinglePointToMultiPointIntent(ApplicationId appId,
-            Key key,
-            TrafficSelector selector, TrafficTreatment treatment,
-            ConnectPoint ingressPoint, Set<ConnectPoint> egressPoints,
-            List<Constraint> constraints,
-            int priority) {
-        super(appId, key, Collections.emptyList(), selector, treatment, constraints,
-                priority);
+                                          Key key,
+                                          TrafficSelector selector,
+                                          TrafficTreatment treatment,
+                                          ConnectPoint ingressPoint,
+                                          Set<ConnectPoint> egressPoints,
+                                          List<Constraint> constraints,
+                                          int priority,
+                                          Map<ConnectPoint, TrafficTreatment> egressTreatments) {
+        super(appId, key, ImmutableList.of(), selector, treatment, constraints,
+              priority);
         checkNotNull(egressPoints);
         checkNotNull(ingressPoint);
         checkArgument(!egressPoints.isEmpty(), "Egress point set cannot be empty");
         checkArgument(!egressPoints.contains(ingressPoint),
                 "Set of egresses should not contain ingress (ingress: %s)", ingressPoint);
 
-        this.ingressPoint = checkNotNull(ingressPoint);
-        this.egressPoints = egressPoints;
+        this.ingressPoint = ingressPoint;
+        this.egressPoints = Sets.newHashSet(egressPoints);
+        this.egressTreatments = egressTreatments;
     }
 
     /**
@@ -92,6 +105,7 @@
     public static final class Builder extends ConnectivityIntent.Builder {
         ConnectPoint ingressPoint;
         Set<ConnectPoint> egressPoints;
+        Map<ConnectPoint, TrafficTreatment> egressTreatments = ImmutableMap.of();
 
         private Builder() {
             // Hide constructor
@@ -152,6 +166,18 @@
         }
 
         /**
+         * Sets the treatments of the single point to multi point intent
+         * that will be built.
+         *
+         * @param egressTreatments the multiple treatments
+         * @return this builder
+         */
+        public Builder treatments(Map<ConnectPoint, TrafficTreatment> egressTreatments) {
+            this.egressTreatments = ImmutableMap.copyOf(egressTreatments);
+            return this;
+        }
+
+        /**
          * Builds a single point to multi point intent from the
          * accumulated parameters.
          *
@@ -159,6 +185,12 @@
          */
         public SinglePointToMultiPointIntent build() {
 
+            if (treatment != null && !treatment.allInstructions().isEmpty() &&
+                    !treatment.equals(DefaultTrafficTreatment.emptyTreatment()) &&
+                    egressTreatments != null && !egressTreatments.isEmpty()) {
+                throw new IllegalArgumentException("Treatment and Multiple Treatments are both set");
+            }
+
             return new SinglePointToMultiPointIntent(
                     appId,
                     key,
@@ -167,7 +199,8 @@
                     ingressPoint,
                     egressPoints,
                     constraints,
-                    priority
+                    priority,
+                    egressTreatments
             );
         }
     }
@@ -179,6 +212,7 @@
         super();
         this.ingressPoint = null;
         this.egressPoints = null;
+        this.egressTreatments = null;
     }
 
     /**
@@ -200,6 +234,14 @@
         return egressPoints;
     }
 
+    /**
+     * Returns the multiple treatments jointly with their connection points.
+     * @return multiple treatments
+     */
+    public Map<ConnectPoint, TrafficTreatment> egressTreatments() {
+        return egressTreatments;
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
@@ -212,6 +254,7 @@
                 .add("treatment", treatment())
                 .add("ingress", ingressPoint)
                 .add("egress", egressPoints)
+                .add("treatments", egressTreatments)
                 .add("constraints", constraints())
                 .toString();
     }
diff --git a/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java b/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java
index 6e9d68c..9cbe99f 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/ConnectivityIntentTest.java
@@ -43,6 +43,7 @@
     public static final TrafficSelector MATCH = DefaultTrafficSelector.emptySelector();
     public static final TrafficTreatment NOP = DefaultTrafficTreatment.emptyTreatment();
     public static final Map<ConnectPoint, TrafficSelector> MATCHES = Collections.emptyMap();
+    public static final Map<ConnectPoint, TrafficTreatment> TREATMENTS = Collections.emptyMap();
 
     public static final ConnectPoint P1 = new ConnectPoint(DeviceId.deviceId("111"), PortNumber.portNumber(0x1));
     public static final ConnectPoint P2 = new ConnectPoint(DeviceId.deviceId("222"), PortNumber.portNumber(0x2));
@@ -64,4 +65,17 @@
         VLANMATCHES.put(P2, VLANMATCH2);
     }
 
+    public static final TrafficTreatment VLANACTION1 = DefaultTrafficTreatment.builder()
+            .setVlanId(VlanId.vlanId("2"))
+            .build();
+    public static final TrafficTreatment VLANACTION2 = DefaultTrafficTreatment.builder()
+            .setVlanId(VlanId.vlanId("3"))
+            .build();
+
+    public static final Map<ConnectPoint, TrafficTreatment> VLANACTIONS = Maps.newHashMap();
+    static {
+        VLANACTIONS.put(P1, VLANACTION1);
+        VLANACTIONS.put(P2, VLANACTION2);
+    }
+
 }
diff --git a/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java b/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java
index b4c5952..f3db2fb 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/SinglePointToMultiPointIntentTest.java
@@ -15,7 +15,9 @@
  */
 package org.onosproject.net.intent;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import static org.junit.Assert.assertEquals;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
@@ -42,6 +44,41 @@
         assertEquals("incorrect egress", PS2, intent.egressPoints());
     }
 
+    @Rule
+    public ExpectedException wrongMultiple = ExpectedException.none();
+
+    @Test
+    public void multipleTreatments() {
+
+        SinglePointToMultiPointIntent intent = createFirstMultiple();
+        assertEquals("incorrect id", APPID, intent.appId());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect ingress", P1, intent.ingressPoint());
+        assertEquals("incorrect egress", PS2, intent.egressPoints());
+        assertEquals("incorrect treatment", NOP, intent.treatment());
+        assertEquals("incorrect treatments", TREATMENTS, intent.egressTreatments());
+
+        intent = createSecondMultiple();
+        assertEquals("incorrect id", APPID, intent.appId());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect ingress", P1, intent.ingressPoint());
+        assertEquals("incorrect egress", PS2, intent.egressPoints());
+        assertEquals("incorrect treatment", VLANACTION1, intent.treatment());
+        assertEquals("incorrect selectors", TREATMENTS, intent.egressTreatments());
+
+        intent = createThirdMultiple();
+        assertEquals("incorrect id", APPID, intent.appId());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect ingress", P1, intent.ingressPoint());
+        assertEquals("incorrect egress", PS2, intent.egressPoints());
+        assertEquals("incorrect treatment", NOP, intent.treatment());
+        assertEquals("incorrect selectors", VLANACTIONS, intent.egressTreatments());
+
+        wrongMultiple.expect(IllegalArgumentException.class);
+        wrongMultiple.expectMessage("Treatment and Multiple Treatments are both set");
+        intent = createWrongMultiple();
+    }
+
     @Override
     protected SinglePointToMultiPointIntent createOne() {
         return SinglePointToMultiPointIntent.builder()
@@ -63,4 +100,50 @@
                 .egressPoints(PS1)
                 .build();
     }
+
+
+    protected SinglePointToMultiPointIntent createFirstMultiple() {
+        return SinglePointToMultiPointIntent.builder()
+                .appId(APPID)
+                .selector(MATCH)
+                .treatment(NOP)
+                .ingressPoint(P1)
+                .egressPoints(PS2)
+                .treatments(TREATMENTS)
+                .build();
+    }
+
+    protected SinglePointToMultiPointIntent createSecondMultiple() {
+        return SinglePointToMultiPointIntent.builder()
+                .appId(APPID)
+                .selector(MATCH)
+                .treatment(VLANACTION1)
+                .ingressPoint(P1)
+                .egressPoints(PS2)
+                .treatments(TREATMENTS)
+                .build();
+    }
+
+    protected SinglePointToMultiPointIntent createThirdMultiple() {
+        return SinglePointToMultiPointIntent.builder()
+                .appId(APPID)
+                .selector(MATCH)
+                .treatment(NOP)
+                .ingressPoint(P1)
+                .egressPoints(PS2)
+                .treatments(VLANACTIONS)
+                .build();
+    }
+
+    protected SinglePointToMultiPointIntent createWrongMultiple() {
+        return SinglePointToMultiPointIntent.builder()
+                .appId(APPID)
+                .selector(MATCH)
+                .treatment(VLANACTION1)
+                .ingressPoint(P1)
+                .egressPoints(PS2)
+                .treatments(VLANACTIONS)
+                .build();
+    }
+
 }