SinglePoint to MultiPoint Intent initial implementation

Change-Id: I1010997ce4ea993ae34afb8dab4b6c0ae112448d
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddSinglePointToMultiPointIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddSinglePointToMultiPointIntentCommand.java
new file mode 100644
index 0000000..d9ad30b
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddSinglePointToMultiPointIntentCommand.java
@@ -0,0 +1,97 @@
+package org.onlab.onos.cli.net;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.Constraint;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.SinglePointToMultiPointIntent;
+
+
+@Command(scope = "onos", name = "add-single-to-multi-intent",
+        description = "Installs connectivity intent between multiple egress devices and a single ingress device")
+public class AddSinglePointToMultiPointIntentCommand extends ConnectivityIntentCommand {
+    @Argument(index = 0, name = "egressDevices ingressDevice",
+            description = "egress Device/Port...egress Device/Port ingressDevice/port",
+            required = true, multiValued = true)
+    String[] deviceStrings = null;
+
+    @Override
+    protected void execute() {
+        IntentService service = get(IntentService.class);
+
+        if (deviceStrings.length < 2) {
+            return;
+        }
+
+        String ingressDeviceString = deviceStrings[deviceStrings.length - 1];
+        DeviceId ingressDeviceId = deviceId(getDeviceId(ingressDeviceString));
+        PortNumber ingressPortNumber = portNumber(getPortNumber(ingressDeviceString));
+        ConnectPoint ingressPoint = new ConnectPoint(ingressDeviceId,
+                                                     ingressPortNumber);
+
+        Set<ConnectPoint> egressPoints = new HashSet<>();
+        for (int index = 0; index < deviceStrings.length - 1; index++) {
+            String egressDeviceString = deviceStrings[index];
+            DeviceId egressDeviceId = deviceId(getDeviceId(egressDeviceString));
+            PortNumber egressPortNumber = portNumber(getPortNumber(egressDeviceString));
+            ConnectPoint egress = new ConnectPoint(egressDeviceId,
+                                                   egressPortNumber);
+            egressPoints.add(egress);
+        }
+
+        TrafficSelector selector = buildTrafficSelector();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+        List<Constraint> constraints = buildConstraints();
+
+        SinglePointToMultiPointIntent intent = new SinglePointToMultiPointIntent(
+                                                                                 appId(),
+                                                                                 selector,
+                                                                                 treatment,
+                                                                                 ingressPoint,
+                                                                                 egressPoints,
+                                                                                 constraints);
+        service.submit(intent);
+    }
+
+    /**
+     * Extracts the port number portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return port number as a string, empty string if the port is not found
+     */
+    private String getPortNumber(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(slash + 1, deviceString.length());
+    }
+
+    /**
+     * Extracts the device ID portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return device ID string
+     */
+    private String getDeviceId(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(0, slash);
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
index 0a6fe41..0f24647 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
@@ -340,7 +340,7 @@
         } else if (intent instanceof LinkCollectionIntent) {
             LinkCollectionIntent li = (LinkCollectionIntent) intent;
             print("    links=%s", li.links());
-            print("    egress=%s", li.egressPoint());
+            print("    egress=%s", li.egressPoints());
         }
 
         List<Intent> installable = service.getInstallableIntents(intent.id());
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 132acfd..30bee25 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -161,6 +161,15 @@
             </optional-completers>
         </command>
         <command>
+            <action class="org.onlab.onos.cli.net.AddSinglePointToMultiPointIntentCommand"/>
+            <completers>
+                <ref component-id="connectPointCompleter"/>
+            </completers>
+            <optional-completers>
+                <entry key="-t" value-ref="ethTypeCompleter"/>
+            </optional-completers>
+        </command>
+        <command>
             <action class="org.onlab.onos.cli.net.IntentPushTestCommand"/>
             <completers>
                 <ref component-id="connectPointCompleter"/>
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java
index ce1f6b1..3844611 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java
@@ -16,6 +16,8 @@
 package org.onlab.onos.net.intent;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
 import org.onlab.onos.core.ApplicationId;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.Link;
@@ -34,11 +36,11 @@
 
     private final Set<Link> links;
 
-    private final ConnectPoint egressPoint;
+    private final Set<ConnectPoint> egressPoints;
 
     /**
-     * Creates a new actionable intent capable of funneling the selected
-     * traffic along the specified convergent tree and out the given egress point.
+     * Creates a new actionable intent capable of funneling the selected traffic
+     * along the specified convergent tree and out the given egress point.
      *
      * @param appId       application identifier
      * @param selector    traffic match
@@ -77,7 +79,31 @@
         super(id(LinkCollectionIntent.class, selector, treatment, links, egressPoint, constraints),
                 appId, resources(links), selector, treatment, constraints);
         this.links = links;
-        this.egressPoint = egressPoint;
+        this.egressPoints = ImmutableSet.of(egressPoint);
+    }
+
+    /**
+     * Creates a new actionable intent capable of funneling the selected traffic
+     * along the specified convergent tree and out the given egress point.
+     *
+     * @param appId        application identifier
+     * @param selector     traffic match
+     * @param treatment    action
+     * @param links        traversed links
+     * @param egressPoints Set of egress point
+     * @throws NullPointerException {@code path} is null
+     */
+    public LinkCollectionIntent(ApplicationId appId,
+                                TrafficSelector selector,
+                                TrafficTreatment treatment,
+                                Set<Link> links,
+                                Set<ConnectPoint> egressPoints,
+                                List<Constraint> constraints) {
+        super(id(LinkCollectionIntent.class, selector, treatment, links,
+                 egressPoints), appId, resources(links), selector, treatment);
+
+        this.links = links;
+        this.egressPoints = ImmutableSet.copyOf(egressPoints);
     }
 
     /**
@@ -86,7 +112,7 @@
     protected LinkCollectionIntent() {
         super();
         this.links = null;
-        this.egressPoint = null;
+        this.egressPoints = null;
     }
 
     /**
@@ -104,8 +130,8 @@
      *
      * @return the egress point
      */
-    public ConnectPoint egressPoint() {
-        return egressPoint;
+    public Set<ConnectPoint> egressPoints() {
+        return egressPoints;
     }
 
     @Override
@@ -121,7 +147,7 @@
                 .add("selector", selector())
                 .add("treatment", treatment())
                 .add("links", links())
-                .add("egress", egressPoint())
+                .add("egress", egressPoints())
                 .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
index efe96e2..7851e53 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
@@ -17,12 +17,15 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Sets;
+
 import org.onlab.onos.core.ApplicationId;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
+import java.util.Collections;
 import java.util.Set;
+import java.util.List;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -38,30 +41,50 @@
     /**
      * Creates a new single-to-multi point connectivity intent.
      *
-     * @param appId        application identifier
-     * @param selector     traffic selector
-     * @param treatment    treatment
+     * @param appId application identifier
+     * @param selector traffic selector
+     * @param treatment treatment
      * @param ingressPoint port on which traffic will ingress
      * @param egressPoints set of ports on which traffic will egress
-     * @throws NullPointerException     if {@code ingressPoint} or
-     *                                  {@code egressPoints} is null
+     * @throws NullPointerException if {@code ingressPoint} or
+     *             {@code egressPoints} is null
      * @throws IllegalArgumentException if the size of {@code egressPoints} is
-     *                                  not more than 1
+     *             not more than 1
      */
     public SinglePointToMultiPointIntent(ApplicationId appId,
-                                         TrafficSelector selector,
-                                         TrafficTreatment treatment,
-                                         ConnectPoint ingressPoint,
-                                         Set<ConnectPoint> egressPoints) {
+            TrafficSelector selector, TrafficTreatment treatment,
+            ConnectPoint ingressPoint, Set<ConnectPoint> egressPoints) {
+        this(appId, selector, treatment, ingressPoint, egressPoints, Collections.emptyList());
+    }
+
+    /**
+     * Creates a new single-to-multi point connectivity intent.
+     *
+     * @param appId application identifier
+     * @param selector traffic selector
+     * @param treatment treatment
+     * @param ingressPoint port on which traffic will ingress
+     * @param egressPoints set of ports on which traffic will egress
+     * @param constraints constraints to apply to the intent
+     * @throws NullPointerException if {@code ingressPoint} or
+     *             {@code egressPoints} is null
+     * @throws IllegalArgumentException if the size of {@code egressPoints} is
+     *             not more than 1
+     */
+    public SinglePointToMultiPointIntent(ApplicationId appId,
+            TrafficSelector selector, TrafficTreatment treatment,
+            ConnectPoint ingressPoint, Set<ConnectPoint> egressPoints,
+            List<Constraint> constraints) {
         super(id(SinglePointToMultiPointIntent.class, selector, treatment,
-                 ingressPoint, egressPoints), appId, null, selector, treatment);
+                 ingressPoint, egressPoints), appId, null, selector, treatment,
+              constraints);
         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 = ingressPoint;
+        this.ingressPoint = checkNotNull(ingressPoint);
         this.egressPoints = Sets.newHashSet(egressPoints);
     }
 
@@ -75,7 +98,8 @@
     }
 
     /**
-     * Returns the port on which the ingress traffic should be connected to the egress.
+     * Returns the port on which the ingress traffic should be connected to the
+     * egress.
      *
      * @return ingress port
      */
@@ -101,6 +125,7 @@
                 .add("treatment", treatment())
                 .add("ingress", ingressPoint)
                 .add("egress", egressPoints)
+                .add("constraints", constraints())
                 .toString();
     }
 
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/LinkCollectionIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/LinkCollectionIntentTest.java
index ddee9f9..f69e479 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/LinkCollectionIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/LinkCollectionIntentTest.java
@@ -28,6 +28,7 @@
 import org.onlab.onos.net.intent.constraint.LambdaConstraint;
 import org.onlab.onos.net.resource.Lambda;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.testing.EqualsTester;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -111,7 +112,7 @@
         assertThat(collectionIntent.isInstallable(), is(true));
         assertThat(collectionIntent.treatment(), is(treatment));
         assertThat(collectionIntent.selector(), is(selector));
-        assertThat(collectionIntent.egressPoint(), is(egress));
+        assertThat(collectionIntent.egressPoints(), is(ImmutableSet.of(egress)));
         assertThat(collectionIntent.resources(), hasSize(1));
         final List<Constraint> createdConstraints = collectionIntent.constraints();
         assertThat(createdConstraints, hasSize(0));
@@ -140,7 +141,7 @@
         assertThat(collectionIntent.isInstallable(), is(true));
         assertThat(collectionIntent.treatment(), is(treatment));
         assertThat(collectionIntent.selector(), is(selector));
-        assertThat(collectionIntent.egressPoint(), is(egress));
+        assertThat(collectionIntent.egressPoints(), is(ImmutableSet.of(egress)));
 
         final List<Constraint> createdConstraints = collectionIntent.constraints();
         assertThat(createdConstraints, hasSize(1));
@@ -161,7 +162,7 @@
         assertThat(collectionIntent.isInstallable(), is(true));
         assertThat(collectionIntent.treatment(), nullValue());
         assertThat(collectionIntent.selector(), nullValue());
-        assertThat(collectionIntent.egressPoint(), nullValue());
+        assertThat(collectionIntent.egressPoints(), nullValue());
 
         final List<Constraint> createdConstraints = collectionIntent.constraints();
         assertThat(createdConstraints, hasSize(0));
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
index fb8e2d4..66e5fb1 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
@@ -15,9 +15,12 @@
  */
 package org.onlab.onos.net.intent.impl;
 
-import static org.slf4j.LoggerFactory.getLogger;
-
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -26,6 +29,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.onos.core.ApplicationId;
 import org.onlab.onos.core.CoreService;
+import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.PortNumber;
@@ -42,18 +46,16 @@
 import org.onlab.onos.net.intent.IntentInstaller;
 import org.onlab.onos.net.intent.LinkCollectionIntent;
 import org.onlab.onos.net.intent.PathIntent;
-import org.slf4j.Logger;
 
 import com.google.common.collect.Lists;
 
 /**
- * Installer for {@link org.onlab.onos.net.intent.LinkCollectionIntent}
- * path segment intents.
+ * Installer for {@link org.onlab.onos.net.intent.LinkCollectionIntent} path
+ * segment intents.
  */
 @Component(immediate = true)
-public class LinkCollectionIntentInstaller implements IntentInstaller<LinkCollectionIntent> {
-
-    private final Logger log = getLogger(getClass());
+public class LinkCollectionIntentInstaller
+        implements IntentInstaller<LinkCollectionIntent> {
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentExtensionService intentManager;
@@ -76,37 +78,58 @@
 
     @Override
     public List<FlowRuleBatchOperation> install(LinkCollectionIntent intent) {
+        Map<DeviceId, Set<PortNumber>> outputMap = new HashMap<DeviceId, Set<PortNumber>>();
         List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+
         for (Link link : intent.links()) {
-            rules.add(createBatchEntry(FlowRuleOperation.ADD,
-                   intent,
-                   link.src().deviceId(),
-                   link.src().port()));
+            if (outputMap.get(link.src().deviceId()) == null) {
+                outputMap.put(link.src().deviceId(), new HashSet<PortNumber>());
+            }
+            outputMap.get(link.src().deviceId()).add(link.src().port());
+
         }
 
-        rules.add(createBatchEntry(FlowRuleOperation.ADD,
-                intent,
-                intent.egressPoint().deviceId(),
-                intent.egressPoint().port()));
+        for (ConnectPoint egressPoint : intent.egressPoints()) {
+            if (outputMap.get(egressPoint.deviceId()) == null) {
+                outputMap
+                        .put(egressPoint.deviceId(), new HashSet<PortNumber>());
+            }
+            outputMap.get(egressPoint.deviceId()).add(egressPoint.port());
+
+        }
+
+        for (Entry<DeviceId, Set<PortNumber>> entry : outputMap.entrySet()) {
+            rules.add(createBatchEntry(FlowRuleOperation.ADD, intent,
+                                       entry.getKey(), entry.getValue()));
+        }
 
         return Lists.newArrayList(new FlowRuleBatchOperation(rules));
     }
 
     @Override
     public List<FlowRuleBatchOperation> uninstall(LinkCollectionIntent intent) {
+        Map<DeviceId, Set<PortNumber>> outputMap = new HashMap<DeviceId, Set<PortNumber>>();
         List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
 
         for (Link link : intent.links()) {
-            rules.add(createBatchEntry(FlowRuleOperation.REMOVE,
-                    intent,
-                    link.src().deviceId(),
-                    link.src().port()));
+            if (outputMap.get(link.src().deviceId()) == null) {
+                outputMap.put(link.src().deviceId(), new HashSet<PortNumber>());
+            }
+            outputMap.get(link.src().deviceId()).add(link.src().port());
         }
 
-        rules.add(createBatchEntry(FlowRuleOperation.REMOVE,
-               intent,
-               intent.egressPoint().deviceId(),
-               intent.egressPoint().port()));
+        for (ConnectPoint egressPoint : intent.egressPoints()) {
+            if (outputMap.get(egressPoint.deviceId()) == null) {
+                outputMap
+                        .put(egressPoint.deviceId(), new HashSet<PortNumber>());
+            }
+            outputMap.get(egressPoint.deviceId()).add(egressPoint.port());
+        }
+
+        for (Entry<DeviceId, Set<PortNumber>> entry : outputMap.entrySet()) {
+            rules.add(createBatchEntry(FlowRuleOperation.REMOVE, intent,
+                                       entry.getKey(), entry.getValue()));
+        }
 
         return Lists.newArrayList(new FlowRuleBatchOperation(rules));
     }
@@ -128,17 +151,20 @@
      * @return the new flow rule batch entry
      */
     private FlowRuleBatchEntry createBatchEntry(FlowRuleOperation operation,
-                                    LinkCollectionIntent intent,
-                                    DeviceId deviceId,
-                                    PortNumber outPort) {
+                                                LinkCollectionIntent intent,
+                                                DeviceId deviceId,
+                                                Set<PortNumber> outPorts) {
 
-        TrafficTreatment.Builder treatmentBuilder =
-                DefaultTrafficTreatment.builder(intent.treatment());
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment
+                .builder(intent.treatment());
 
-        TrafficTreatment treatment = treatmentBuilder.setOutput(outPort).build();
+        for (PortNumber outPort : outPorts) {
+            treatmentBuilder.setOutput(outPort);
+        }
+        TrafficTreatment treatment = treatmentBuilder.build();
 
-        TrafficSelector selector = DefaultTrafficSelector.builder(intent.selector())
-                                   .build();
+        TrafficSelector selector = DefaultTrafficSelector
+                .builder(intent.selector()).build();
 
         FlowRule rule = new DefaultFlowRule(deviceId,
                 selector, treatment, 123,
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/SinglePointToMultiPointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/SinglePointToMultiPointIntentCompiler.java
new file mode 100644
index 0000000..46689ec
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/SinglePointToMultiPointIntentCompiler.java
@@ -0,0 +1,58 @@
+package org.onlab.onos.net.intent.impl;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.LinkCollectionIntent;
+import org.onlab.onos.net.intent.SinglePointToMultiPointIntent;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+
+@Component(immediate = true)
+public class SinglePointToMultiPointIntentCompiler
+        extends ConnectivityIntentCompiler<SinglePointToMultiPointIntent> {
+
+    // TODO: use off-the-shell core provider ID
+    private static final ProviderId PID =
+            new ProviderId("core", "org.onlab.onos.core", true);
+
+    @Activate
+    public void activate() {
+        intentManager.registerCompiler(SinglePointToMultiPointIntent.class,
+                                       this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterCompiler(SinglePointToMultiPointIntent.class);
+    }
+
+
+    @Override
+    public List<Intent> compile(SinglePointToMultiPointIntent intent,
+                                List<Intent> installable,
+                                Set<LinkResourceAllocations> resources) {
+        Set<Link> links = new HashSet<>();
+        //FIXME: need to handle the case where ingress/egress points are on same switch
+        for (ConnectPoint egressPoint : intent.egressPoints()) {
+            Path path = getPath(intent, intent.ingressPoint().deviceId(), egressPoint.deviceId());
+            links.addAll(path.links());
+        }
+
+        Intent result = new LinkCollectionIntent(intent.appId(),
+                                                 intent.selector(),
+                                                 intent.treatment(), links,
+                                                 intent.egressPoints(), null);
+
+        return Arrays.asList(result);
+    }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
index 264dd4a..5e0c6dd 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
@@ -80,6 +80,7 @@
 import org.onlab.onos.net.intent.PathIntent;
 import org.onlab.onos.net.intent.PointToPointIntent;
 import org.onlab.onos.net.intent.constraint.AnnotationConstraint;
+import org.onlab.onos.net.intent.SinglePointToMultiPointIntent;
 import org.onlab.onos.net.intent.constraint.BandwidthConstraint;
 import org.onlab.onos.net.intent.constraint.BooleanConstraint;
 import org.onlab.onos.net.intent.constraint.LambdaConstraint;
@@ -251,6 +252,7 @@
                     HostToHostIntent.class,
                     PointToPointIntent.class,
                     MultiPointToSinglePointIntent.class,
+                    SinglePointToMultiPointIntent.class,
                     LinkCollectionIntent.class,
                     OpticalConnectivityIntent.class,
                     OpticalPathIntent.class,