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/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java
index b37095a..cfc6652 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java
@@ -27,6 +27,7 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.L0ModificationInstruction;
 import org.onosproject.net.flow.instructions.L1ModificationInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
@@ -211,10 +212,29 @@
             }
         } else {
             if (outPorts.stream().allMatch(egressPorts::contains)) {
-                TrafficTreatment.Builder egressTreatmentBuilder =
-                        DefaultTrafficTreatment.builder(intent.treatment());
-                outPorts.forEach(egressTreatmentBuilder::setOutput);
-
+                TrafficTreatment.Builder egressTreatmentBuilder = DefaultTrafficTreatment.builder();
+                if (intent.egressTreatments() != null && !intent.egressTreatments().isEmpty()) {
+                    for (PortNumber outPort : outPorts) {
+                        Optional<ConnectPoint> connectPoint = intent.egressPoints()
+                                .stream()
+                                .filter(egressPoint -> egressPoint.port().equals(outPort)
+                                        && egressPoint.deviceId().equals(deviceId))
+                                .findFirst();
+                        if (connectPoint.isPresent()) {
+                            TrafficTreatment egressTreatment = intent.egressTreatments().get(connectPoint.get());
+                            this.addTreatment(egressTreatmentBuilder, egressTreatment);
+                            egressTreatmentBuilder = this.updateBuilder(egressTreatmentBuilder, intent.selector());
+                            egressTreatmentBuilder.setOutput(outPort);
+                        } else {
+                            throw new IntentCompilationException("Looking for connect point associated to " +
+                                                                         "the treatment. outPort not in egressPoints");
+                        }
+                    }
+                } else {
+                    egressTreatmentBuilder = this
+                            .updateBuilder(DefaultTrafficTreatment.builder(intent.treatment()), intent.selector());
+                    outPorts.forEach(egressTreatmentBuilder::setOutput);
+                }
                 selectorBuilder = DefaultTrafficSelector.builder(intent.selector());
                 treatment = egressTreatmentBuilder.build();
             } else {
@@ -230,10 +250,34 @@
     }
 
     /**
+     * Update a builder using a treatment.
+     * @param builder the builder to update
+     * @param treatment the treatment to add
+     * @return the new builder
+     */
+    private TrafficTreatment.Builder addTreatment(TrafficTreatment.Builder builder, TrafficTreatment treatment) {
+        builder.deferred();
+        for (Instruction instruction : treatment.deferred()) {
+            builder.add(instruction);
+        }
+        builder.immediate();
+        for (Instruction instruction : treatment.immediate()) {
+            builder.add(instruction);
+        }
+        return builder;
+    }
+
+    /**
      * Update the original builder with the necessary operations
      * to have a correct forwarding given an ingress selector.
+     * TODO
+     * This means that if the ingress selectors match on different vlanids and
+     * the egress treatment rewrite the vlanid the forwarding works
+     * but if we need to push for example an mpls label at the egress
+     * we need to implement properly this method.
      *
      * @param treatmentBuilder the builder to modify
+     * @param intentSelector the intent selector to use as input
      * @return the new treatment created
      */
     private TrafficTreatment.Builder updateBuilder(TrafficTreatment.Builder treatmentBuilder,
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
index 83547e2..735ea7f 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
@@ -34,6 +34,8 @@
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentCompiler;
 import org.onosproject.net.intent.LinkCollectionIntent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -48,6 +50,9 @@
         extends LinkCollectionCompiler<FlowRule>
         implements IntentCompiler<LinkCollectionIntent> {
 
+    private static Logger log = LoggerFactory.getLogger(LinkCollectionIntentCompiler.class);
+
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentConfigurableRegistrator registrator;
 
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java
index 0da2194..76640f2 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java
@@ -77,6 +77,7 @@
                 .priority(intent.priority())
                 .applyTreatmentOnEgress(true)
                 .constraints(intent.constraints())
+                .egressTreatments(intent.egressTreatments())
                 .build();
 
         return Collections.singletonList(result);
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
index 8e89d14..43524da 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
@@ -64,13 +64,18 @@
 
     private final ApplicationId appId = new TestApplicationId("test");
 
-    private final ConnectPoint d1p1 = connectPoint("s1", 0);
+    private final ConnectPoint d1p1 = connectPoint("s1", 1);
     private final ConnectPoint d2p0 = connectPoint("s2", 0);
     private final ConnectPoint d2p1 = connectPoint("s2", 1);
+    private final ConnectPoint d2p2 = connectPoint("s2", 2);
+    private final ConnectPoint d2p3 = connectPoint("s2", 3);
     private final ConnectPoint d3p1 = connectPoint("s3", 1);
     private final ConnectPoint d3p2 = connectPoint("s3", 9);
     private final ConnectPoint d3p0 = connectPoint("s3", 10);
     private final ConnectPoint d1p0 = connectPoint("s1", 10);
+    private final ConnectPoint d4p1 = connectPoint("s4", 1);
+    private final ConnectPoint d4p0 = connectPoint("s4", 10);
+
 
     private final Set<Link> links = ImmutableSet.of(
             DefaultLink.builder().providerId(PID).src(d1p1).dst(d2p0).type(DIRECT).build(),
@@ -80,6 +85,11 @@
     private final Set<Link> linksMultiple = ImmutableSet.of(
             DefaultLink.builder().providerId(PID).src(d3p1).dst(d2p0).type(DIRECT).build());
 
+    private final Set<Link> linksMultiple2 = ImmutableSet.of(
+            DefaultLink.builder().providerId(PID).src(d2p0).dst(d1p1).type(DIRECT).build(),
+            DefaultLink.builder().providerId(PID).src(d2p1).dst(d3p1).type(DIRECT).build(),
+            DefaultLink.builder().providerId(PID).src(d2p2).dst(d4p1).type(DIRECT).build());
+
     private final TrafficSelector selector = DefaultTrafficSelector.builder().build();
     private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
 
@@ -101,14 +111,40 @@
             .setVlanId(egressVlan)
             .build();
 
+private final VlanId ingressVlan = VlanId.vlanId("10");
+    private final TrafficSelector vlanSelector = DefaultTrafficSelector
+            .builder()
+            .matchVlanId(ingressVlan)
+            .build();
+
+    private final VlanId egressVlan1 = VlanId.vlanId("20");
+    private final TrafficTreatment vlanTreatment1 = DefaultTrafficTreatment
+            .builder()
+            .setVlanId(egressVlan1)
+            .build();
+
+    private final VlanId egressVlan2 = VlanId.vlanId("666");
+    private final TrafficTreatment vlanTreatment2 = DefaultTrafficTreatment
+            .builder()
+            .setVlanId(egressVlan2)
+            .build();
+
+    private final VlanId egressVlan3 = VlanId.vlanId("69");
+    private final TrafficTreatment vlanTreatment3 = DefaultTrafficTreatment
+            .builder()
+            .setVlanId(egressVlan3)
+            .build();
+
+
     private CoreService coreService;
     private IntentExtensionService intentExtensionService;
     private IntentConfigurableRegistrator registrator;
     private IdGenerator idGenerator = new MockIdGenerator();
 
     private LinkCollectionIntent intent;
-    private LinkCollectionIntent intentMultiple;
-
+    private LinkCollectionIntent intentMultipleSelectors;
+    private LinkCollectionIntent intentMultipleTreatments;
+    private LinkCollectionIntent intentMultipleTreatments2;
 
     private LinkCollectionIntentCompiler sut;
 
@@ -130,7 +166,7 @@
                 .ingressPoints(ImmutableSet.of(d1p1))
                 .egressPoints(ImmutableSet.of(d3p1))
                 .build();
-        intentMultiple = LinkCollectionIntent.builder()
+        intentMultipleSelectors = LinkCollectionIntent.builder()
                 .appId(APP_ID)
                 .treatment(vlanTreatment)
                 .links(linksMultiple)
@@ -138,6 +174,24 @@
                 .egressPoints(ImmutableSet.of(d2p1))
                 .ingressSelectors(this.createIngressSelectors())
                 .build();
+        intentMultipleTreatments = LinkCollectionIntent.builder()
+                .appId(APP_ID)
+                .selector(vlanSelector)
+                .links(linksMultiple)
+                .ingressPoints(ImmutableSet.of(d3p0))
+                .egressPoints(ImmutableSet.of(d2p1, d2p2))
+                .egressTreatments(this.createEgressTreatments())
+                .applyTreatmentOnEgress(true)
+                .build();
+        intentMultipleTreatments2 = LinkCollectionIntent.builder()
+                .appId(APP_ID)
+                .selector(vlanSelector)
+                .links(linksMultiple2)
+                .ingressPoints(ImmutableSet.of(d2p3))
+                .egressPoints(ImmutableSet.of(d1p0, d3p0, d4p0))
+                .egressTreatments(this.createEgressTreatments2())
+                .applyTreatmentOnEgress(true)
+                .build();
 
         intentExtensionService = createMock(IntentExtensionService.class);
         intentExtensionService.registerCompiler(LinkCollectionIntent.class, sut);
@@ -208,22 +262,22 @@
         sut.deactivate();
     }
 
-    @Test
+@Test
     public void testCompileMultipleSelectors() {
         sut.activate();
 
-        List<Intent> compiled = sut.compile(intentMultiple, Collections.emptyList());
+        List<Intent> compiled = sut.compile(intentMultipleSelectors, Collections.emptyList());
         assertThat(compiled, hasSize(1));
 
 
         Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
-        assertThat(rules, hasSize((linksMultiple.size()) + intentMultiple.ingressPoints().size()));
+        assertThat(rules, hasSize((linksMultiple.size()) + intentMultipleSelectors.ingressPoints().size()));
 
         Set<FlowRule> d3Rules = rules
                 .parallelStream()
                 .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
                 .collect(Collectors.toSet());
-        assertThat(d3Rules, hasSize(intentMultiple.ingressPoints().size()));
+        assertThat(d3Rules, hasSize(intentMultipleSelectors.ingressPoints().size()));
 
         FlowRule rule1 = rules.stream()
                 .filter(rule -> rule.deviceId().equals(d3p0.deviceId())
@@ -233,18 +287,18 @@
                 .get();
         assertThat(rule1.selector(), is(
                 DefaultTrafficSelector
-                        .builder(intentMultiple.selector())
+                        .builder(intentMultipleSelectors.selector())
                         .matchInPort(d3p0.port())
                         .matchVlanId(ingressVlan1)
                         .build()
         ));
         assertThat(rule1.treatment(), is(
                 DefaultTrafficTreatment
-                        .builder(intentMultiple.treatment())
+                        .builder(intentMultipleSelectors.treatment())
                         .setOutput(d3p1.port())
                         .build()
         ));
-        assertThat(rule1.priority(), is(intentMultiple.priority()));
+        assertThat(rule1.priority(), is(intentMultipleSelectors.priority()));
 
         FlowRule rule2 = rules.stream()
                 .filter(rule -> rule.deviceId().equals(d3p0.deviceId())
@@ -254,24 +308,24 @@
                 .get();
         assertThat(rule2.selector(), is(
                 DefaultTrafficSelector
-                        .builder(intentMultiple.selector())
+                        .builder(intentMultipleSelectors.selector())
                         .matchInPort(d3p2.port())
                         .matchVlanId(ingressVlan2)
                         .build()
         ));
         assertThat(rule2.treatment(), is(
                 DefaultTrafficTreatment
-                        .builder(intentMultiple.treatment())
+                        .builder(intentMultipleSelectors.treatment())
                         .setOutput(d3p1.port())
                         .build()
         ));
-        assertThat(rule1.priority(), is(intentMultiple.priority()));
+        assertThat(rule2.priority(), is(intentMultipleSelectors.priority()));
 
         Set<FlowRule> d2Rules = rules
                 .parallelStream()
                 .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
                 .collect(Collectors.toSet());
-        assertThat(d2Rules, hasSize(intentMultiple.egressPoints().size()));
+        assertThat(d2Rules, hasSize(intentMultipleSelectors.egressPoints().size()));
 
         // We do not need in_port filter
         FlowRule rule3 = rules.stream()
@@ -280,7 +334,7 @@
                 .get();
         assertThat(rule3.selector(), is(
                 DefaultTrafficSelector
-                        .builder(intentMultiple.selector())
+                        .builder(intentMultipleSelectors.selector())
                         .matchInPort(d2p0.port())
                         .matchVlanId(egressVlan)
                         .build()
@@ -291,15 +345,216 @@
                         .setOutput(d2p1.port())
                         .build()
         ));
-        assertThat(rule3.priority(), is(intentMultiple.priority()));
+        assertThat(rule3.priority(), is(intentMultipleSelectors.priority()));
 
         sut.deactivate();
     }
 
+    @Test
+    public void testCompileMultipleTreatments() {
+        sut.activate();
+
+        List<Intent> compiled = sut.compile(intentMultipleTreatments, Collections.emptyList());
+        assertThat(compiled, hasSize(1));
+
+        Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+        assertThat(rules, hasSize((linksMultiple.size() + intentMultipleTreatments.egressPoints().size())));
+
+        Set<FlowRule> d3Rules = rules
+                .parallelStream()
+                .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+                .collect(Collectors.toSet());
+        assertThat(d3Rules, hasSize(intentMultipleTreatments.ingressPoints().size()));
+
+        FlowRule rule1 = rules.stream()
+                .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+                .findFirst()
+                .get();
+        assertThat(rule1.selector(), is(
+                DefaultTrafficSelector
+                        .builder(intentMultipleTreatments.selector())
+                        .matchInPort(d3p0.port())
+                        .matchVlanId(ingressVlan)
+                        .build()
+        ));
+        assertThat(rule1.treatment(), is(
+                DefaultTrafficTreatment
+                        .builder(intentMultipleTreatments.treatment())
+                        .setOutput(d3p1.port())
+                        .build()
+        ));
+        assertThat(rule1.priority(), is(intentMultipleTreatments.priority()));
+
+        Set<FlowRule> d2Rules = rules
+                .parallelStream()
+                .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+                .collect(Collectors.toSet());
+        assertThat(d2Rules, hasSize(1));
+
+        FlowRule rule2 = rules.stream()
+                .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+                .findFirst()
+                .get();
+        assertThat(rule2.selector(), is(
+                DefaultTrafficSelector
+                        .builder(intentMultipleTreatments.selector())
+                        .matchInPort(d2p0.port())
+                        .matchVlanId(ingressVlan)
+                        .build()
+        ));
+        assertThat(rule2.treatment(), is(
+                DefaultTrafficTreatment
+                        .builder(intentMultipleTreatments.treatment())
+                        .setVlanId(egressVlan1)
+                        .setOutput(d2p1.port())
+                        .setVlanId(egressVlan2)
+                        .setOutput(d2p2.port())
+                        .build()
+        ));
+        assertThat(rule2.priority(), is(intentMultipleTreatments.priority()));
+
+    }
+
+    @Test
+    public void testCompileMultipleTreatments2() {
+        sut.activate();
+
+        List<Intent> compiled = sut.compile(intentMultipleTreatments2, Collections.emptyList());
+        assertThat(compiled, hasSize(1));
+
+
+        Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+        assertThat(rules, hasSize((linksMultiple2.size() + intentMultipleTreatments2.egressPoints().size())));
+
+
+        Set<FlowRule> d2Rules = rules
+                .parallelStream()
+                .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+                .collect(Collectors.toSet());
+        assertThat(d2Rules, hasSize(1));
+
+
+        FlowRule rule1 = rules.stream()
+                .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+                .findFirst()
+                .get();
+        assertThat(rule1.selector(), is(
+                DefaultTrafficSelector
+                        .builder(intentMultipleTreatments.selector())
+                        .matchInPort(d2p3.port())
+                        .matchVlanId(ingressVlan)
+                        .build()
+        ));
+        assertThat(rule1.treatment(), is(
+                DefaultTrafficTreatment
+                        .builder(intentMultipleTreatments.treatment())
+                        .setOutput(d2p0.port())
+                        .setOutput(d2p1.port())
+                        .setOutput(d2p2.port())
+                        .build()
+        ));
+        assertThat(rule1.priority(), is(intentMultipleTreatments.priority()));
+
+        Set<FlowRule> d1Rules = rules
+                .parallelStream()
+                .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+                .collect(Collectors.toSet());
+        assertThat(d1Rules, hasSize(1));
+
+        FlowRule rule2 = rules.stream()
+                .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+                .findFirst()
+                .get();
+        assertThat(rule2.selector(), is(
+                DefaultTrafficSelector
+                        .builder(intentMultipleTreatments2.selector())
+                        .matchInPort(d1p1.port())
+                        .matchVlanId(ingressVlan)
+                        .build()
+        ));
+        assertThat(rule2.treatment(), is(
+                DefaultTrafficTreatment
+                        .builder(intentMultipleTreatments2.treatment())
+                        .setVlanId(egressVlan1)
+                        .setOutput(d1p0.port())
+                        .build()
+        ));
+        assertThat(rule2.priority(), is(intentMultipleTreatments.priority()));
+
+        Set<FlowRule> d3Rules = rules
+                .parallelStream()
+                .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+                .collect(Collectors.toSet());
+        assertThat(d3Rules, hasSize(1));
+
+        FlowRule rule3 = rules.stream()
+                .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+                .findFirst()
+                .get();
+        assertThat(rule3.selector(), is(
+                DefaultTrafficSelector
+                        .builder(intentMultipleTreatments2.selector())
+                        .matchInPort(d3p1.port())
+                        .matchVlanId(ingressVlan)
+                        .build()
+        ));
+        assertThat(rule3.treatment(), is(
+                DefaultTrafficTreatment
+                        .builder(intentMultipleTreatments2.treatment())
+                        .setVlanId(egressVlan2)
+                        .setOutput(d3p0.port())
+                        .build()
+        ));
+        assertThat(rule3.priority(), is(intentMultipleTreatments.priority()));
+
+        Set<FlowRule> d4Rules = rules
+                .parallelStream()
+                .filter(rule -> rule.deviceId().equals(d4p0.deviceId()))
+                .collect(Collectors.toSet());
+        assertThat(d4Rules, hasSize(1));
+
+        FlowRule rule4 = rules.stream()
+                .filter(rule -> rule.deviceId().equals(d4p0.deviceId()))
+                .findFirst()
+                .get();
+        assertThat(rule4.selector(), is(
+                DefaultTrafficSelector
+                        .builder(intentMultipleTreatments2.selector())
+                        .matchInPort(d4p1.port())
+                        .matchVlanId(ingressVlan)
+                        .build()
+        ));
+        assertThat(rule4.treatment(), is(
+                DefaultTrafficTreatment
+                        .builder(intentMultipleTreatments2.treatment())
+                        .setVlanId(egressVlan3)
+                        .setOutput(d4p0.port())
+                        .build()
+        ));
+        assertThat(rule4.priority(), is(intentMultipleTreatments.priority()));
+
+    }
+
+    public Map<ConnectPoint, TrafficTreatment> createEgressTreatments() {
+        Map<ConnectPoint, TrafficTreatment> mapToReturn = Maps.newHashMap();
+        mapToReturn.put(d2p1, vlanTreatment1);
+        mapToReturn.put(d2p2, vlanTreatment2);
+        return mapToReturn;
+    }
+
+    public Map<ConnectPoint, TrafficTreatment> createEgressTreatments2() {
+        Map<ConnectPoint, TrafficTreatment> mapToReturn = Maps.newHashMap();
+        mapToReturn.put(d1p0, vlanTreatment1);
+        mapToReturn.put(d3p0, vlanTreatment2);
+        mapToReturn.put(d4p0, vlanTreatment3);
+        return mapToReturn;
+    }
+
     public Map<ConnectPoint, TrafficSelector> createIngressSelectors() {
         Map<ConnectPoint, TrafficSelector> mapToReturn = Maps.newHashMap();
         mapToReturn.put(d3p0, selectorVlan1);
         mapToReturn.put(d3p2, selectorVlan2);
         return mapToReturn;
     }
+
 }