Moving Source from connect point to HostId in MulticastHandling

Change-Id: Ie8f678e150b7ee388680b8d8f27df0bce60ec01f
diff --git a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRoute.java b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRoute.java
index 9d5afeb..7fe1a90 100644
--- a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRoute.java
+++ b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRoute.java
@@ -57,9 +57,10 @@
 
     /**
      * Creates the McastRoute object. The source Ip can be null if this route is intent for ASM.
+     *
      * @param source source Ip. Null if ASM route. Will translate in Optional.empty.
-     * @param group the multicast group
-     * @param type the route type.
+     * @param group  the multicast group
+     * @param type   the route type.
      */
     public McastRoute(IpAddress source, IpAddress group, Type type) {
         checkNotNull(group, "Multicast route must specify a group address");
diff --git a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteData.java b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteData.java
index e95c417..ce81ea9 100644
--- a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteData.java
+++ b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteData.java
@@ -17,7 +17,6 @@
 
 import com.google.common.annotations.Beta;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.HostId;
 
@@ -38,7 +37,7 @@
 @Beta
 public final class McastRouteData {
 
-    private final ConcurrentHashMap<ConnectPoint, Boolean> sources = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<HostId, Set<ConnectPoint>> sources = new ConcurrentHashMap<>();
     private final ConcurrentHashMap<HostId, Set<ConnectPoint>> sinks = new ConcurrentHashMap<>();
 
     private McastRouteData() {
@@ -49,13 +48,41 @@
      *
      * @return set of sources
      */
-    public Set<ConnectPoint> sources() {
-        return ImmutableSet.copyOf(sources.keySet());
+    public Map<HostId, Set<ConnectPoint>> sources() {
+        return ImmutableMap.copyOf(sources);
     }
 
     /**
      * Sources contained in the associated route.
      *
+     * @return map of hostIds and associated sources
+     */
+    public Set<ConnectPoint> allSources() {
+        return sources.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
+    }
+
+    /**
+     * Sources contained in the associated route for the given host.
+     *
+     * @param hostId the host
+     * @return set of sources
+     */
+    public Set<ConnectPoint> sources(HostId hostId) {
+        return sources.get(hostId);
+    }
+
+    /**
+     * Sources contained in the associated route that are not bound to any host.
+     *
+     * @return set of sources
+     */
+    public Set<ConnectPoint> nonHostSources() {
+        return sources.get(HostId.NONE);
+    }
+
+    /**
+     * Sinks contained in the associated route.
+     *
      * @return map of hostIds and associated sinks
      */
     public Map<HostId, Set<ConnectPoint>> sinks() {
@@ -91,13 +118,34 @@
     }
 
     /**
-     * Add the sources for the associated route.
+     * Adds sources for a given host Id. If the Host Id is {@link HostId#NONE} the sources are intended to be
+     * used at all times independently of the attached host.
      *
-     * @param sources set of sources
+     * @param hostId the host
+     * @param sources  the sources
      */
-    public void addSources(Set<ConnectPoint> sources) {
+    public void addSources(HostId hostId, Set<ConnectPoint> sources) {
+        checkNotNull(hostId);
         checkArgument(!sources.contains(null));
-        sources.forEach(source -> this.sources.put(source, true));
+        //if existing we add to current set, otherwise we put them all
+        this.sources.compute(hostId, (host, existingSources) -> {
+            if (existingSources != null) {
+                existingSources.addAll(sources);
+                return existingSources;
+            } else {
+                return sources;
+            }
+        });
+    }
+
+    /**
+     * Adds sources for this route that are not associated directly with a given host.
+     *
+     * @param sources the sources
+     */
+    public void addNonHostSources(Set<ConnectPoint> sources) {
+        checkArgument(!sources.contains(null));
+        addSources(HostId.NONE, sources);
     }
 
     /**
@@ -108,13 +156,31 @@
     }
 
     /**
-     * Removes the given sources contained in the associated route.
+     * Removes the given source contained in the associated route.
      *
+     * @param source the source to remove
+     */
+    public void removeSource(HostId source) {
+        checkNotNull(source);
+        sources.remove(source);
+    }
+
+    /**
+     * Removes all the given sources for the given host for this route.
+     *
+     * @param hostId  the host
      * @param sources the sources to remove
      */
-    public void removeSources(Set<ConnectPoint> sources) {
+    public void removeSources(HostId hostId, Set<ConnectPoint> sources) {
+        checkNotNull(hostId);
         checkArgument(!sources.contains(null));
-        sources.forEach(this.sources::remove);
+        //if existing we remove from current set, otherwise just skip them
+        this.sources.compute(hostId, (host, existingSources) -> {
+            if (existingSources != null) {
+                existingSources.removeAll(sources);
+            }
+            return existingSources;
+        });
     }
 
     /**
@@ -128,11 +194,14 @@
         checkNotNull(hostId);
         checkArgument(!sinks.contains(null));
         //if existing we add to current set, otherwise we put them all
-        if (this.sinks.containsKey(hostId)) {
-            this.sinks.get(hostId).addAll(sinks);
-        } else {
-            this.sinks.put(hostId, sinks);
-        }
+        this.sinks.compute(hostId, (host, existingSinks) -> {
+            if (existingSinks != null) {
+                existingSinks.addAll(sinks);
+                return existingSinks;
+            } else {
+                return sinks;
+            }
+        });
     }
 
     /**
@@ -142,7 +211,7 @@
      */
     public void addNonHostSinks(Set<ConnectPoint> sinks) {
         checkArgument(!sinks.contains(null));
-        this.sinks.put(HostId.NONE, sinks);
+        this.addSinks(HostId.NONE, sinks);
     }
 
     /**
@@ -172,9 +241,12 @@
         checkNotNull(hostId);
         checkArgument(!sinks.contains(null));
         //if existing we remove from current set, otherwise just skip them
-        if (this.sinks.containsKey(hostId)) {
-            this.sinks.get(hostId).removeAll(sinks);
-        }
+        this.sinks.compute(hostId, (host, existingSinks) -> {
+            if (existingSinks != null) {
+                existingSinks.removeAll(sinks);
+            }
+            return existingSinks;
+        });
     }
 
     /**
diff --git a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteUpdate.java b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteUpdate.java
index 4333465..a117cc4 100644
--- a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteUpdate.java
+++ b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastRouteUpdate.java
@@ -37,12 +37,14 @@
     private static final String SINK_NOT_NULL = "Sink cannot be null";
 
     private final McastRoute route;
-    private final Set<ConnectPoint> sources;
+    private final Map<HostId, Set<ConnectPoint>> sources;
     private final Map<HostId, Set<ConnectPoint>> sinks;
 
-    private McastRouteUpdate(McastRoute route, Set<ConnectPoint> source, Map<HostId, Set<ConnectPoint>> sinks) {
+    private McastRouteUpdate(McastRoute route,
+                             Map<HostId, Set<ConnectPoint>> sources,
+                             Map<HostId, Set<ConnectPoint>> sinks) {
         this.route = checkNotNull(route, ROUTE_NOT_NULL);
-        this.sources = checkNotNull(source, SOURCE_NOT_NULL);
+        this.sources = checkNotNull(sources, SOURCE_NOT_NULL);
         this.sinks = checkNotNull(sinks, SINK_NOT_NULL);
     }
 
@@ -55,7 +57,7 @@
      * @return the McastRouteUpdate object.
      */
     public static McastRouteUpdate mcastRouteUpdate(McastRoute route,
-                                                    Set<ConnectPoint> sources,
+                                                    Map<HostId, Set<ConnectPoint>> sources,
                                                     Map<HostId, Set<ConnectPoint>> sinks) {
         return new McastRouteUpdate(route, sources, sinks);
     }
@@ -72,9 +74,9 @@
     /**
      * The sources.
      *
-     * @return an optional connect point
+     * @return a set of connect points
      */
-    public Set<ConnectPoint> sources() {
+    public Map<HostId, Set<ConnectPoint>> sources() {
         return sources;
     }
 
diff --git a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastStore.java b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastStore.java
index aa429b2..484f32ec 100644
--- a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastStore.java
+++ b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/McastStore.java
@@ -43,10 +43,22 @@
     void removeRoute(McastRoute route);
 
     /**
-     * Add to the store with source information for the given route.
+     * Updates the store with a host based source information for a given route. There may be
+     * multiple source connect points for the given host.
      *
-     * @param route   a Multicast route
-     * @param sources a set of sources
+     * @param route         a Multicast route
+     * @param hostId        the host source
+     * @param connectPoints the sources connect point
+     */
+    void storeSource(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints);
+
+    /**
+     * Updates the store with source information for a given route.
+     * The source stored with this method are not tied with any host.
+     * Traffic will be sent from all of them.
+     *
+     * @param route a Multicast route
+     * @param sources set of specific connect points
      */
     void storeSources(McastRoute route, Set<ConnectPoint> sources);
 
@@ -59,12 +71,20 @@
 
     /**
      * Removes from the store the source information for the given route.
-     * value.
      *
-     * @param route   a Multicast route
-     * @param sources a set of sources
+     * @param route  a Multicast route
+     * @param source a source
      */
-    void removeSources(McastRoute route, Set<ConnectPoint> sources);
+    void removeSource(McastRoute route, HostId source);
+
+    /**
+     * Removes a set of source connect points for a given host the route.
+     *
+     * @param route         the multicast route
+     * @param hostId        a source host
+     * @param connectPoints a given set of connect points to remove
+     */
+    void removeSources(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints);
 
     /**
      * Updates the store with a host based sink information for a given route. There may be
@@ -127,6 +147,15 @@
     Set<ConnectPoint> sourcesFor(McastRoute route);
 
     /**
+     * Obtains the sources for a given host for a given Multicast route.
+     *
+     * @param route  a Multicast route
+     * @param hostId the host
+     * @return a set of sources
+     */
+    Set<ConnectPoint> sourcesFor(McastRoute route, HostId hostId);
+
+    /**
      * Obtains the sinks for a Multicast route.
      *
      * @param route a Multicast route
diff --git a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/MulticastRouteService.java b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/MulticastRouteService.java
index bcee070..bd32363 100644
--- a/apps/mcast/api/src/main/java/org/onosproject/mcast/api/MulticastRouteService.java
+++ b/apps/mcast/api/src/main/java/org/onosproject/mcast/api/MulticastRouteService.java
@@ -61,11 +61,33 @@
     Set<McastRoute> getRoute(IpAddress groupIp, IpAddress sourceIp);
 
     /**
-     * Adds a set of sources connect points to the route from where the
+     * Adds a host as a source to the route from where the
      * data stream is originating.
      *
-     * @param route   the Multicast route
-     * @param sources a set of sources
+     * @param route  the Multicast route
+     * @param source a source host
+     */
+    void addSource(McastRoute route, HostId source);
+
+
+    /**
+     * Adds a set of source connect points for a given host source to the route to
+     * which a data stream should be sent to.
+     *
+     * @param route         a Multicast route
+     * @param hostId        a source host
+     * @param connectPoints the source for the specific host
+     */
+    void addSources(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints);
+
+    /**
+     * Adds a set of sources to the route from which a data stream should be
+     * sent to. If this method is used the connect points will all be
+     * used as different sources for that Mcast Tree. For dual-homed sources
+     * please use {@link #addSource(McastRoute route, HostId hostId) addSource}.
+     *
+     * @param route a Multicast route
+     * @param sources a set of source connect points
      */
     void addSources(McastRoute route, Set<ConnectPoint> sources);
 
@@ -77,12 +99,12 @@
     void removeSources(McastRoute route);
 
     /**
-     * Removes a set of sources connect points from the route.
+     * Removes a source host from the route.
      *
-     * @param route   the Multicast route
-     * @param sources a set of sources
+     * @param route  the Multicast route
+     * @param source a host source
      */
-    void removeSources(McastRoute route, Set<ConnectPoint> sources);
+    void removeSource(McastRoute route, HostId source);
 
     /**
      * Adds a sink to the route to which a data stream should be
@@ -112,7 +134,7 @@
      * @param route a Multicast route
      * @param sinks a set of sink connect point
      */
-    void addSink(McastRoute route, Set<ConnectPoint> sinks);
+    void addSinks(McastRoute route, Set<ConnectPoint> sinks);
 
     /**
      * Removes all the sinks from the route.
@@ -157,6 +179,15 @@
     Set<ConnectPoint> sources(McastRoute route);
 
     /**
+     * Find the set of connect points for a given source for this route.
+     *
+     * @param route  a Multicast route
+     * @param hostId the host
+     * @return a list of connect points
+     */
+    Set<ConnectPoint> sources(McastRoute route, HostId hostId);
+
+    /**
      * Find the list of sinks for this route.
      *
      * @param route a Multicast route
diff --git a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastHostJoinCommand.java b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastHostJoinCommand.java
index 629cb2a..7650649 100644
--- a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastHostJoinCommand.java
+++ b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastHostJoinCommand.java
@@ -21,13 +21,8 @@
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.mcast.api.McastRoute;
 import org.onosproject.mcast.api.MulticastRouteService;
-import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.HostId;
 
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
 /**
  * Installs a source, multicast group flow.
  */
@@ -52,8 +47,8 @@
     String gAddr = null;
 
     @Option(name = "-srcs", aliases = "--sources",
-            description = "Ingress port of:XXXXXXXXXX/XX",
-            valueToShowInHelp = "of:0000000000000001/1",
+            description = "Host sink format: MAC/VLAN",
+            valueToShowInHelp = "00:00:00:00:00:00/None",
             multiValued = true)
     String[] sources = null;
 
@@ -62,7 +57,7 @@
             description = "Host sink format: MAC/VLAN",
             valueToShowInHelp = "00:00:00:00:00:00/None",
             multiValued = true)
-    String[] hosts = null;
+    String[] sinks = null;
 
     @Override
     protected void execute() {
@@ -78,16 +73,14 @@
         mcastRouteManager.add(mRoute);
 
         if (sources != null) {
-            Set<ConnectPoint> sourcesSet = Arrays.stream(sources)
-                    .map(ConnectPoint::deviceConnectPoint)
-                    .collect(Collectors.toSet());
-            mcastRouteManager.addSources(mRoute, sourcesSet);
+            for (String hostId : sources) {
+                mcastRouteManager.addSource(mRoute, HostId.hostId(hostId));
+            }
         }
 
-        if (hosts != null) {
-            for (String hostId : hosts) {
+        if (sinks != null) {
+            for (String hostId : sinks) {
                 mcastRouteManager.addSink(mRoute, HostId.hostId(hostId));
-
             }
         }
         printMcastRoute(mRoute);
@@ -96,7 +89,7 @@
     private void printMcastRoute(McastRoute mcastRoute) {
         // If the source is present let's use it, otherwise we need to print *
         print(FORMAT_MAPPING, mcastRoute.type(), mcastRoute.group(),
-              mcastRoute.source().isPresent() ? mcastRoute.source().get() : "*");
+                mcastRoute.source().isPresent() ? mcastRoute.source().get() : "*");
     }
 
 }
diff --git a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java
index 24ee11b..dbf2ca6 100644
--- a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java
+++ b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastShowHostCommand.java
@@ -89,7 +89,7 @@
 
     private void printRoute(MulticastRouteService mcastService, McastRoute route) {
         Map<HostId, Set<ConnectPoint>> sinks = mcastService.routeData(route).sinks();
-        Set<ConnectPoint> sources = mcastService.sources(route);
+        Map<HostId, Set<ConnectPoint>> sources = mcastService.routeData(route).sources();
         String srcIp = "*";
         if (route.source().isPresent()) {
             srcIp = route.source().get().toString();
diff --git a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastHostDeleteCommand.java b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastSinkDeleteCommand.java
similarity index 89%
rename from apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastHostDeleteCommand.java
rename to apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastSinkDeleteCommand.java
index ca5959d..524fcb2 100644
--- a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastHostDeleteCommand.java
+++ b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastSinkDeleteCommand.java
@@ -26,9 +26,9 @@
 /**
  * Deletes a multicast route.
  */
-@Command(scope = "onos", name = "mcast-host-delete",
-        description = "Delete a multicast route flow")
-public class McastHostDeleteCommand extends AbstractShellCommand {
+@Command(scope = "onos", name = "mcast-sink-delete",
+        description = "Delete a sink from multicast route flow. If no sin is specified removes the whole route.")
+public class McastSinkDeleteCommand extends AbstractShellCommand {
 
     // Delete format for group line
     private static final String D_FORMAT_MAPPING = "Deleted the mcast route: " +
@@ -50,7 +50,7 @@
             required = true, multiValued = false)
     String gAddr = null;
 
-    @Option(name = "-h", aliases = "--host",
+    @Option(name = "-s", aliases = "--sinks",
             description = "Host sink format: MAC/VLAN",
             valueToShowInHelp = "00:00:00:00:00:00/None")
     String host = null;
@@ -70,7 +70,7 @@
             sAddrIp = IpAddress.valueOf(sAddr);
         }
         McastRoute mRoute = new McastRoute(sAddrIp, IpAddress.valueOf(gAddr),
-                                           McastRoute.Type.STATIC);
+                McastRoute.Type.STATIC);
         // If the user provides only sAddr and gAddr, we have to remove the route
         if (host == null || host.isEmpty()) {
             mcastRouteManager.remove(mRoute);
@@ -93,6 +93,6 @@
     private void printMcastRoute(String format, McastRoute mcastRoute) {
         // If the source is present let's use it, otherwise we need to print *
         print(format, mcastRoute.type(), mcastRoute.group(),
-              mcastRoute.source().isPresent() ? mcastRoute.source().get() : "*");
+                mcastRoute.source().isPresent() ? mcastRoute.source().get() : "*");
     }
 }
diff --git a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastSourceDeleteCommand.java b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastSourceDeleteCommand.java
index 0a6080c..d1f646f 100644
--- a/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastSourceDeleteCommand.java
+++ b/apps/mcast/cli/src/main/java/org/onosproject/mcast/cli/McastSourceDeleteCommand.java
@@ -21,11 +21,7 @@
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.mcast.api.McastRoute;
 import org.onosproject.mcast.api.MulticastRouteService;
-import org.onosproject.net.ConnectPoint;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
+import org.onosproject.net.HostId;
 
 /**
  * Deletes a multicast route.
@@ -55,8 +51,8 @@
     String gAddr = null;
 
     @Option(name = "-src", aliases = "--connectPoint",
-            description = "Source port of:XXXXXXXXXX/XX",
-            valueToShowInHelp = "of:0000000000000001/1",
+            description = "Host sink format: MAC/VLAN",
+            valueToShowInHelp = "00:00:00:00:00:00/None",
             multiValued = true)
     String[] sourceList = null;
 
@@ -76,7 +72,7 @@
             sAddrIp = IpAddress.valueOf(sAddr);
         }
         McastRoute mRoute = new McastRoute(sAddrIp, IpAddress.valueOf(gAddr),
-                                           McastRoute.Type.STATIC);
+                McastRoute.Type.STATIC);
         // No specific connect points, we have to remove everything
         if (sourceList == null) {
             mcastRouteManager.remove(mRoute);
@@ -88,16 +84,16 @@
             print("Route is not present, store it first");
             return;
         }
-        Set<ConnectPoint> sourcesSet = Arrays.stream(sourceList)
-                .map(ConnectPoint::deviceConnectPoint)
-                .collect(Collectors.toSet());
-        mcastRouteManager.removeSources(mRoute, sourcesSet);
+        for (String hostId : sourceList) {
+            mcastRouteManager.removeSource(mRoute, HostId.hostId(hostId));
+
+        }
         printMcastRoute(U_FORMAT_MAPPING, mRoute);
     }
 
     private void printMcastRoute(String format, McastRoute mcastRoute) {
         // If the source is present let's use it, otherwise we need to print *
         print(format, mcastRoute.type(), mcastRoute.group(),
-              mcastRoute.source().isPresent() ? mcastRoute.source().get() : "*");
+                mcastRoute.source().isPresent() ? mcastRoute.source().get() : "*");
     }
 }
diff --git a/apps/mcast/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/mcast/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 1525938..7bf0fcb 100644
--- a/apps/mcast/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/mcast/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -21,7 +21,7 @@
             <action class="org.onosproject.mcast.cli.McastHostJoinCommand"/>
             <optional-completers>
                 <entry key="-gAddr" value-ref="mcastGroupCompleter"/>
-                <entry key="-srcs" value-ref="connectpointCompleter"/>
+                <entry key="-srcs" value-ref="hostIdCompleter"/>
                 <entry key="-sinks" value-ref="hostIdCompleter"/>
             </optional-completers>
         </command>
@@ -32,18 +32,18 @@
             </optional-completers>
         </command>
         <command>
-            <action class="org.onosproject.mcast.cli.McastHostDeleteCommand"/>
+            <action class="org.onosproject.mcast.cli.McastSinkDeleteCommand"/>
             <optional-completers>
                 <entry key="-gAddr" value-ref="mcastGroupCompleter"/>
                 <entry key="-cps" value-ref="connectpointCompleter"/>
-                <entry key="-h" value-ref="hostIdCompleter"/>
+                <entry key="-s" value-ref="hostIdCompleter"/>
             </optional-completers>
         </command>
         <command>
             <action class="org.onosproject.mcast.cli.McastSourceDeleteCommand"/>
             <optional-completers>
                 <entry key="-gAddr" value-ref="mcastGroupCompleter"/>
-                <entry key="-src" value-ref="connectpointCompleter"/>
+                <entry key="-src" value-ref="hostIdCompleter"/>
             </optional-completers>
         </command>
         <command>
diff --git a/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/DistributedMcastRoutesStore.java b/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/DistributedMcastRoutesStore.java
index 84e2131..c9baac3 100644
--- a/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/DistributedMcastRoutesStore.java
+++ b/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/DistributedMcastRoutesStore.java
@@ -17,7 +17,6 @@
 
 
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -111,15 +110,25 @@
         mcastRoutes.remove(route);
     }
 
+
     @Override
-    public void storeSources(McastRoute route, Set<ConnectPoint> sources) {
+    public void storeSource(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints) {
         mcastRoutes.compute(route, (k, v) -> {
-            v.addSources(sources);
+            v.addSources(hostId, connectPoints);
             return v;
         });
     }
 
     @Override
+    public void storeSources(McastRoute route, Set<ConnectPoint> sources) {
+        mcastRoutes.compute(route, (k, v) -> {
+            v.addSources(HostId.NONE, sources);
+            return v;
+        });
+    }
+
+
+    @Override
     public void removeSources(McastRoute route) {
         mcastRoutes.compute(route, (k, v) -> {
             v.removeSources();
@@ -129,15 +138,23 @@
     }
 
     @Override
-    public void removeSources(McastRoute route, Set<ConnectPoint> sources) {
+    public void removeSource(McastRoute route, HostId source) {
         mcastRoutes.compute(route, (k, v) -> {
-            v.removeSources(sources);
+            v.removeSource(source);
             // Since there are no sources, we should remove the route
             return v.sources().isEmpty() ? null : v;
         });
     }
 
     @Override
+    public void removeSources(McastRoute route, HostId hostId, Set<ConnectPoint> sources) {
+        mcastRoutes.compute(route, (k, v) -> {
+            v.removeSources(hostId, sources);
+            return v;
+        });
+    }
+
+    @Override
     public void addSink(McastRoute route, HostId hostId, Set<ConnectPoint> sinks) {
         mcastRoutes.compute(route, (k, v) -> {
             v.addSinks(hostId, sinks);
@@ -189,7 +206,14 @@
     @Override
     public Set<ConnectPoint> sourcesFor(McastRoute route) {
         McastRouteData data = mcastRoutes.getOrDefault(route, null);
-        return data == null ? ImmutableSet.of() : ImmutableSet.copyOf(data.sources());
+        return data == null ? ImmutableSet.of() : ImmutableSet.copyOf(data.sources().values().stream()
+                .flatMap(Collection::stream).collect(Collectors.toSet()));
+    }
+
+    @Override
+    public Set<ConnectPoint> sourcesFor(McastRoute route, HostId hostId) {
+        McastRouteData data = mcastRoutes.getOrDefault(route, null);
+        return data == null ? ImmutableSet.of() : ImmutableSet.copyOf(data.sources(hostId));
     }
 
     @Override
@@ -227,27 +251,18 @@
             switch (event.type()) {
                 case INSERT:
                     checkNotNull(newData);
-                    McastEvent.Type type;
-                    if (!newData.sources().isEmpty() || !newData.sinks().isEmpty()) {
-                        type = McastEvent.Type.SOURCES_ADDED;
-                    } else if (!newData.sinks().isEmpty()) {
-                        type = McastEvent.Type.SINKS_ADDED;
-                    } else {
-                        type = McastEvent.Type.ROUTE_ADDED;
-                    }
-                    notifyDelegate(new McastEvent(type, null,
+                    notifyDelegate(new McastEvent(McastEvent.Type.ROUTE_ADDED, null,
                             mcastRouteUpdate(route, newData.sources(), newData.sinks())));
                     break;
                 case UPDATE:
                     checkNotNull(newData);
                     checkNotNull(oldData);
 
-                    if (!Sets.difference(newData.sources(), oldData.sources()).isEmpty()) {
+                    if (newData.allSources().size() > oldData.allSources().size()) {
                         notifyDelegate(new McastEvent(McastEvent.Type.SOURCES_ADDED,
                                 mcastRouteUpdate(route, oldData.sources(), oldData.sinks()),
                                 mcastRouteUpdate(route, newData.sources(), newData.sinks())));
-                    }
-                    if (!Sets.difference(oldData.sources(), newData.sources()).isEmpty()) {
+                    } else if (newData.allSources().size() < oldData.allSources().size()) {
                         notifyDelegate(new McastEvent(McastEvent.Type.SOURCES_REMOVED,
                                 mcastRouteUpdate(route, oldData.sources(), oldData.sinks()),
                                 mcastRouteUpdate(route, newData.sources(), newData.sinks())));
diff --git a/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/MulticastRouteManager.java b/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/MulticastRouteManager.java
index 055d8de..9f15b6c 100644
--- a/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/MulticastRouteManager.java
+++ b/apps/mcast/impl/src/main/java/org/onosproject/mcast/impl/MulticastRouteManager.java
@@ -113,16 +113,40 @@
         final Optional<IpAddress> source = Optional.ofNullable(sourceIp);
         return store.getRoutes().stream()
                 .filter(route -> route.group().equals(groupIp) &&
-                                Objects.equal(route.source(), source))
+                        Objects.equal(route.source(), source))
                 .collect(Collectors.toSet());
     }
 
     @Override
-    public void addSources(McastRoute route, Set<ConnectPoint> connectPoints) {
+    public void addSource(McastRoute route, HostId source) {
         checkNotNull(route, "Route cannot be null");
-        checkNotNull(connectPoints, "Source cannot be null");
+        checkNotNull(source, "Source cannot be null");
         if (checkRoute(route)) {
-            store.storeSources(route, connectPoints);
+            Set<ConnectPoint> sources = new HashSet<>();
+            Host host = hostService.getHost(source);
+            if (host != null) {
+                sources.addAll(host.locations());
+            }
+            store.storeSource(route, source, sources);
+        }
+    }
+
+    @Override
+    public void addSources(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints) {
+        checkNotNull(route, "Route cannot be null");
+        checkNotNull(hostId, "HostId cannot be null");
+        checkNotNull(connectPoints, "Sources cannot be null");
+        if (checkRoute(route)) {
+            store.storeSource(route, hostId, connectPoints);
+        }
+    }
+
+    @Override
+    public void addSources(McastRoute route, Set<ConnectPoint> sources) {
+        checkNotNull(route, "Route cannot be null");
+        checkNotNull(sources, "sources cannot be null");
+        if (checkRoute(route)) {
+            store.storeSources(route, sources);
         }
     }
 
@@ -135,11 +159,11 @@
     }
 
     @Override
-    public void removeSources(McastRoute route, Set<ConnectPoint> sources) {
+    public void removeSource(McastRoute route, HostId source) {
         checkNotNull(route, "Route cannot be null");
-        checkNotNull(sources, "Source cannot be null");
+        checkNotNull(source, "Source cannot be null");
         if (checkRoute(route)) {
-            store.removeSources(route, sources);
+            store.removeSource(route, source);
         }
     }
 
@@ -149,8 +173,7 @@
             Set<ConnectPoint> sinks = new HashSet<>();
             Host host = hostService.getHost(hostId);
             if (host != null) {
-                host.locations().forEach(hostLocation -> sinks.add(
-                        ConnectPoint.deviceConnectPoint(hostLocation.deviceId() + "/" + hostLocation.port())));
+                sinks.addAll(host.locations());
             }
             store.addSink(route, hostId, sinks);
         }
@@ -166,7 +189,7 @@
     }
 
     @Override
-    public void addSink(McastRoute route, Set<ConnectPoint> sinks) {
+    public void addSinks(McastRoute route, Set<ConnectPoint> sinks) {
         checkNotNull(route, "Route cannot be null");
         checkNotNull(sinks, "Sinks cannot be null");
         if (checkRoute(route)) {
@@ -212,6 +235,12 @@
     }
 
     @Override
+    public Set<ConnectPoint> sources(McastRoute route, HostId hostId) {
+        checkNotNull(route, "Route cannot be null");
+        return checkRoute(route) ? store.sourcesFor(route, hostId) : ImmutableSet.of();
+    }
+
+    @Override
     public Set<ConnectPoint> sinks(McastRoute route) {
         checkNotNull(route, "Route cannot be null");
         return checkRoute(route) ? store.sinksFor(route) : ImmutableSet.of();
@@ -252,33 +281,55 @@
         public void event(HostEvent event) {
             HostId hostId = event.subject().id();
             log.debug("Host event: {}", event);
+            Set<McastRoute> routesForSource = routesForSource(hostId);
+            Set<McastRoute> routesForSink = routesForSink(hostId);
             switch (event.type()) {
                 case HOST_ADDED:
                     //the host is added, if it already comes with some locations let's use them
-                    eventAddSinks(hostId, event.subject().locations());
+                    if (!routesForSource.isEmpty()) {
+                        eventAddSources(hostId, event.subject().locations(), routesForSource);
+                    }
+                    if (!routesForSink.isEmpty()) {
+                        eventAddSinks(hostId, event.subject().locations(), routesForSink);
+                    }
                     break;
                 case HOST_MOVED:
                     //both subjects must be null or the system is in an incoherent state
                     if ((event.prevSubject() != null && event.subject() != null)) {
                         //we compute the difference between old locations and new ones and remove the previous
-                        Set<HostLocation> removedSinks = Sets.difference(event.prevSubject().locations(),
+                        Set<HostLocation> removedConnectPoint = Sets.difference(event.prevSubject().locations(),
                                 event.subject().locations()).immutableCopy();
-                        if (!removedSinks.isEmpty()) {
-                            eventRemoveSinks(hostId, removedSinks);
+                        if (!removedConnectPoint.isEmpty()) {
+                            if (!routesForSource.isEmpty()) {
+                                eventRemoveSources(hostId, removedConnectPoint, routesForSource);
+                            }
+                            if (!routesForSink.isEmpty()) {
+                                eventRemoveSinks(hostId, removedConnectPoint, routesForSink);
+                            }
                         }
-                        Set<HostLocation> addedSinks = Sets.difference(event.subject().locations(),
+                        Set<HostLocation> addedConnectPoints = Sets.difference(event.subject().locations(),
                                 event.prevSubject().locations()).immutableCopy();
                         //if the host now has some new locations we add them to the sinks set
-                        if (!addedSinks.isEmpty()) {
-                            eventAddSinks(hostId, addedSinks);
+                        if (!addedConnectPoints.isEmpty()) {
+                            if (!routesForSource.isEmpty()) {
+                                eventAddSources(hostId, addedConnectPoints, routesForSource);
+                            }
+                            if (!routesForSink.isEmpty()) {
+                                eventAddSinks(hostId, addedConnectPoints, routesForSink);
+                            }
                         }
                     }
                     break;
                 case HOST_REMOVED:
-                    // Removing all the sinks for that specific host
+                    // Removing all the connect points for that specific host
                     // even if the locations are 0 we keep
                     // the host information in the route in case it shows up again
-                    eventRemoveSinks(event.subject().id(), event.subject().locations());
+                    if (!routesForSource.isEmpty()) {
+                        eventRemoveSources(hostId, event.subject().locations(), routesForSource);
+                    }
+                    if (!routesForSink.isEmpty()) {
+                        eventRemoveSinks(hostId, event.subject().locations(), routesForSink);
+                    }
                     break;
                 case HOST_UPDATED:
                 default:
@@ -287,25 +338,52 @@
         }
     }
 
-    //Adds sinks for a given host event
-    private void eventRemoveSinks(HostId hostId, Set<HostLocation> removedSinks) {
+    //Finds the route for which a host is source
+    private Set<McastRoute> routesForSource(HostId hostId) {
+        // Filter by host id
+        return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
+                .sources().containsKey(hostId)).collect(Collectors.toSet());
+    }
+
+    //Finds the route for which a host is sink
+    private Set<McastRoute> routesForSink(HostId hostId) {
+        return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
+                .sinks().containsKey(hostId)).collect(Collectors.toSet());
+    }
+
+    //Removes sources for a given host event
+    private void eventRemoveSources(HostId hostId, Set<HostLocation> removedSources, Set<McastRoute> routesForSource) {
+        Set<ConnectPoint> sources = new HashSet<>();
+        // Build sink using host location
+        sources.addAll(removedSources);
+        // Remove from each route the provided sinks
+        routesForSource.forEach(route -> store.removeSources(route, hostId, sources));
+    }
+
+    //Adds the sources for a given host event
+    private void eventAddSources(HostId hostId, Set<HostLocation> addedSources, Set<McastRoute> routesForSource) {
+        Set<ConnectPoint> sources = new HashSet<>();
+        // Build source using host location
+        sources.addAll(addedSources);
+        // Add to each route the provided sources
+        routesForSource.forEach(route -> store.storeSource(route, hostId, sources));
+    }
+
+    //Remove sinks for a given host event
+    private void eventRemoveSinks(HostId hostId, Set<HostLocation> removedSinks, Set<McastRoute> routesForSinks) {
         Set<ConnectPoint> sinks = new HashSet<>();
         // Build sink using host location
         sinks.addAll(removedSinks);
-        // Filter by host id and then remove from each route the provided sinks
-        store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
-                .sinks().get(hostId) != null)
-                .forEach(route -> store.removeSinks(route, hostId, sinks));
+        // Remove from each route the provided sinks
+        routesForSinks.forEach(route -> store.removeSinks(route, hostId, sinks));
     }
 
-    //Removes the sinks for a given host event
-    private void eventAddSinks(HostId hostId, Set<HostLocation> addedSinks) {
+    //Adds the sinks for a given host event
+    private void eventAddSinks(HostId hostId, Set<HostLocation> addedSinks, Set<McastRoute> routesForSinks) {
         Set<ConnectPoint> sinks = new HashSet<>();
         // Build sink using host location
         sinks.addAll(addedSinks);
-        // Filter by host id and then add to each route the provided sinks
-        store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
-                .sinks().get(hostId) != null)
-                .forEach(route -> store.addSink(route, hostId, sinks));
+        // Add to each route the provided sinks
+        routesForSinks.forEach(route -> store.addSink(route, hostId, sinks));
     }
 }
diff --git a/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastHostRouteCodec.java b/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastHostRouteCodec.java
index cb41e34..4629293 100644
--- a/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastHostRouteCodec.java
+++ b/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastHostRouteCodec.java
@@ -56,10 +56,15 @@
             root.put(SOURCE, "*");
         }
 
-        ArrayNode sources = context.mapper().createArrayNode();
-        context.getService(MulticastRouteService.class).sources(route).forEach(source -> {
-            sources.add(source.toString());
+        ObjectNode sources = context.mapper().createObjectNode();
+        context.getService(MulticastRouteService.class).routeData(route).sources().forEach((k, v) -> {
+            ArrayNode node = context.mapper().createArrayNode();
+            v.forEach(source -> {
+                node.add(source.toString());
+            });
+            sources.putPOJO(k.toString(), node);
         });
+
         root.putPOJO(SOURCES, sources);
 
         ObjectNode sinks = context.mapper().createObjectNode();
diff --git a/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastRouteWebResource.java b/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastRouteWebResource.java
index ffe6e65..e4ff608 100644
--- a/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastRouteWebResource.java
+++ b/apps/mcast/web/src/main/java/org/onosproject/mcast/web/McastRouteWebResource.java
@@ -19,7 +19,6 @@
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.annotations.Beta;
-import com.google.common.collect.ImmutableSet;
 import org.onlab.packet.IpAddress;
 import org.onosproject.mcast.api.McastRoute;
 import org.onosproject.mcast.api.MulticastRouteService;
@@ -115,12 +114,15 @@
                                @PathParam("srcIp") String srcIp) {
         Optional<McastRoute> route = getMcastRoute(group, srcIp);
         if (route.isPresent()) {
-            get(MulticastRouteService.class).sources(route.get());
-            ArrayNode node = this.mapper().createArrayNode();
-            get(MulticastRouteService.class).sources(route.get()).forEach(source -> {
-                node.add(source.toString());
+            ObjectNode sources = this.mapper().createObjectNode();
+            get(MulticastRouteService.class).routeData(route.get()).sources().forEach((k, v) -> {
+                ArrayNode node = this.mapper().createArrayNode();
+                v.forEach(source -> {
+                    node.add(source.toString());
+                });
+                sources.putPOJO(k.toString(), node);
             });
-            ObjectNode root = this.mapper().createObjectNode().putPOJO(SOURCES, node);
+            ObjectNode root = this.mapper().createObjectNode().putPOJO(SOURCES, sources);
             return ok(root).build();
         }
         return Response.noContent().build();
@@ -140,7 +142,6 @@
                              @PathParam("srcIp") String srcIp) {
         Optional<McastRoute> route = getMcastRoute(group, srcIp);
         if (route.isPresent()) {
-            get(MulticastRouteService.class).sources(route.get());
             ObjectNode sinks = this.mapper().createObjectNode();
             get(MulticastRouteService.class).routeData(route.get()).sinks().forEach((k, v) -> {
                 ArrayNode node = this.mapper().createArrayNode();
@@ -156,6 +157,33 @@
     }
 
     /**
+     * Get all source connect points for a given sink host in a multicast route.
+     *
+     * @param group  group IP address
+     * @param srcIp  source IP address
+     * @param hostId host Id
+     * @return 200 OK with array of all sources for multicast route
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("sources/{group}/{srcIp}/{hostId}")
+    public Response getHostSources(@PathParam("group") String group,
+                                   @PathParam("srcIp") String srcIp,
+                                   @PathParam("hostId") String hostId) {
+        Optional<McastRoute> route = getMcastRoute(group, srcIp);
+        if (route.isPresent()) {
+            ArrayNode node = this.mapper().createArrayNode();
+            get(MulticastRouteService.class).sources(route.get(), HostId.hostId(hostId))
+                    .forEach(source -> {
+                        node.add(source.toString());
+                    });
+            ObjectNode root = this.mapper().createObjectNode().putPOJO(SOURCES, node);
+            return ok(root).build();
+        }
+        return Response.noContent().build();
+    }
+
+    /**
      * Get all sink connect points for a given sink host in a multicast route.
      *
      * @param group  group IP address
@@ -171,7 +199,6 @@
                                  @PathParam("hostId") String hostId) {
         Optional<McastRoute> route = getMcastRoute(group, srcIp);
         if (route.isPresent()) {
-            get(MulticastRouteService.class).sources(route.get());
             ArrayNode node = this.mapper().createArrayNode();
             get(MulticastRouteService.class).sinks(route.get(), HostId.hostId(hostId))
                     .forEach(source -> {
@@ -204,9 +231,9 @@
                 McastRoute route = codec(McastRoute.class).decode((ObjectNode) routeJson, this);
                 service.add(route);
 
-                Set<ConnectPoint> sources = new HashSet<>();
+                Set<HostId> sources = new HashSet<>();
                 routeJson.path(SOURCES).elements().forEachRemaining(src -> {
-                    sources.add(ConnectPoint.deviceConnectPoint(src.asText()));
+                    sources.add(HostId.hostId(src.asText()));
                 });
                 Set<HostId> sinks = new HashSet<>();
                 routeJson.path(SINKS).elements().forEachRemaining(sink -> {
@@ -214,7 +241,9 @@
                 });
 
                 if (!sources.isEmpty()) {
-                    service.addSources(route, sources);
+                    sources.forEach(source -> {
+                        service.addSource(route, source);
+                    });
                 }
                 if (!sinks.isEmpty()) {
                     sinks.forEach(sink -> {
@@ -248,9 +277,9 @@
             McastRoute route = codec(McastRoute.class).decode(jsonTree, this);
             service.add(route);
 
-            Set<ConnectPoint> sources = new HashSet<>();
+            Set<HostId> sources = new HashSet<>();
             jsonTree.path(SOURCES).elements().forEachRemaining(src -> {
-                sources.add(ConnectPoint.deviceConnectPoint(src.asText()));
+                sources.add(HostId.hostId(src.asText()));
             });
             Set<HostId> sinks = new HashSet<>();
             jsonTree.path(SINKS).elements().forEachRemaining(sink -> {
@@ -258,7 +287,9 @@
             });
 
             if (!sources.isEmpty()) {
-                service.addSources(route, sources);
+                sources.forEach(source -> {
+                    service.addSource(route, source);
+                });
             }
             if (!sinks.isEmpty()) {
                 sinks.forEach(sink -> {
@@ -298,12 +329,14 @@
             ArrayNode jsonTree;
             try {
                 jsonTree = (ArrayNode) mapper().readTree(stream).get(SOURCES);
-                Set<ConnectPoint> sources = new HashSet<>();
+                Set<HostId> sources = new HashSet<>();
                 jsonTree.elements().forEachRemaining(src -> {
-                    sources.add(ConnectPoint.deviceConnectPoint(src.asText()));
+                    sources.add(HostId.hostId(src.asText()));
                 });
                 if (!sources.isEmpty()) {
-                    service.addSources(route.get(), sources);
+                    sources.forEach(src -> {
+                        service.addSource(route.get(), src);
+                    });
                 }
             } catch (IOException e) {
                 throw new IllegalArgumentException(e);
@@ -354,6 +387,45 @@
         return Response.noContent().build();
     }
 
+    /**
+     * Adds a new set of connect points for an existing host source in a given multicast route.
+     *
+     * @param group  group IP address
+     * @param srcIp  source IP address
+     * @param hostId the host Id
+     * @param stream source connect points JSON
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel McastHostSourcesAdd
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("source/{group}/{srcIp}/{hostId}")
+    public Response addHostSource(@PathParam("group") String group,
+                                  @PathParam("srcIp") String srcIp,
+                                  @PathParam("hostId") String hostId,
+                                  InputStream stream) {
+        MulticastRouteService service = get(MulticastRouteService.class);
+        Optional<McastRoute> route = getMcastRoute(group, srcIp);
+        if (route.isPresent()) {
+            ArrayNode jsonTree;
+            try {
+                jsonTree = (ArrayNode) mapper().readTree(stream).get(SOURCES);
+                Set<ConnectPoint> sources = new HashSet<>();
+                jsonTree.elements().forEachRemaining(src -> {
+                    sources.add(ConnectPoint.deviceConnectPoint(src.asText()));
+                });
+                if (!sources.isEmpty()) {
+                    service.addSources(route.get(), HostId.hostId(hostId), sources);
+                }
+            } catch (IOException e) {
+                throw new IllegalArgumentException(e);
+            }
+            return Response.ok().build();
+        }
+        return Response.noContent().build();
+    }
 
     /**
      * Adds a new sink for an existing host in a given multicast route.
@@ -467,23 +539,23 @@
     }
 
     /**
-     * Deletes a source connect point for a specific route.
+     * Deletes a source hostId for a specific route.
      * If the sources are empty the entire route is removed.
      *
-     * @param group group IP address
-     * @param srcIp source IP address
-     * @param srcCp source connect point
+     * @param group  group IP address
+     * @param srcIp  source IP address
+     * @param hostId source host id
      * @return 204 NO CONTENT
      */
     @DELETE
     @Consumes(MediaType.APPLICATION_JSON)
-    @Path("sources/{group}/{srcIp}/{srcCp}")
+    @Path("sources/{group}/{srcIp}/{hostId}")
     public Response deleteSource(@PathParam("group") String group,
                                  @PathParam("srcIp") String srcIp,
-                                 @PathParam("srcCp") String srcCp) {
+                                 @PathParam("hostId") String hostId) {
         Optional<McastRoute> route = getMcastRoute(group, srcIp);
         route.ifPresent(mcastRoute -> get(MulticastRouteService.class)
-                .removeSources(mcastRoute, ImmutableSet.of(ConnectPoint.deviceConnectPoint(srcCp))));
+                .removeSource(mcastRoute, HostId.hostId(hostId)));
         return Response.noContent().build();
     }
 
diff --git a/apps/mcast/web/src/main/resources/definitions/McastHostSourcesAdd.json b/apps/mcast/web/src/main/resources/definitions/McastHostSourcesAdd.json
new file mode 100644
index 0000000..07a51a6
--- /dev/null
+++ b/apps/mcast/web/src/main/resources/definitions/McastHostSourcesAdd.json
@@ -0,0 +1,22 @@
+{
+  "type": "object",
+  "title": "sources",
+  "required": [
+    "sources"
+  ],
+  "properties": {
+    "sources": {
+      "type": "array",
+      "xml": {
+        "name": "sources",
+        "wrapped": true
+      },
+      "items": {
+        "type": "string",
+        "example": "of:0000000000000206/8",
+        "description": "A source connect point for a host in the route"
+      },
+      "description": "Source connect points for a host in the route"
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/mcast/web/src/main/resources/definitions/McastRoute.json b/apps/mcast/web/src/main/resources/definitions/McastRoute.json
index 006ed70..513997f 100644
--- a/apps/mcast/web/src/main/resources/definitions/McastRoute.json
+++ b/apps/mcast/web/src/main/resources/definitions/McastRoute.json
@@ -28,10 +28,10 @@
       },
       "items": {
         "type": "string",
-        "example": "of:0000000000000206/8",
-        "description": "A source connect point for the route"
+        "example": "00:CC:00:00:00:02/None",
+        "description": "A host source for the route"
       },
-      "description": "Source connect points for the route"
+      "description": "Host source for the route"
     },
     "sinks": {
       "type": "array",
diff --git a/apps/mcast/web/src/main/resources/definitions/McastRouteBulk.json b/apps/mcast/web/src/main/resources/definitions/McastRouteBulk.json
index f32cc4f..1f5f741 100644
--- a/apps/mcast/web/src/main/resources/definitions/McastRouteBulk.json
+++ b/apps/mcast/web/src/main/resources/definitions/McastRouteBulk.json
@@ -27,10 +27,10 @@
             },
             "items": {
               "type": "string",
-              "example": "of:0000000000000206/8",
-              "description": "A source connect point for the route"
+              "example": "00:CC:00:00:00:02/None",
+              "description": "A host source for the route"
             },
-            "description": "Source connect points for the route"
+            "description": "Host source for the route"
           },
           "sinks": {
             "type": "array",
diff --git a/apps/mcast/web/src/main/resources/definitions/McastSourcesAdd.json b/apps/mcast/web/src/main/resources/definitions/McastSourcesAdd.json
index 72910a9..d45ed19 100644
--- a/apps/mcast/web/src/main/resources/definitions/McastSourcesAdd.json
+++ b/apps/mcast/web/src/main/resources/definitions/McastSourcesAdd.json
@@ -13,10 +13,10 @@
       },
       "items": {
         "type": "string",
-        "example": "of:0000000000000206/8",
-        "description": "A source connect point for the route"
+        "example": "00:CC:00:00:00:02/None",
+        "description": "A host source for the route"
       },
-      "description": "Source connect points for the route"
+      "description": "Host source for the route"
     }
   }
 }
\ No newline at end of file
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java
index 3b659bd..289778e 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java
@@ -183,7 +183,9 @@
         // Get source, mcast group
         // FIXME To be addressed with multiple sources support
         final ConnectPoint source = mcastPrevUpdate.sources()
+                .values()
                 .stream()
+                .flatMap(Collection::stream)
                 .findFirst()
                 .orElse(null);
         IpAddress mcastIp = mcastPrevUpdate.route().group();
diff --git a/apps/t3/src/main/java/org/onosproject/t3/impl/McastGenerator.java b/apps/t3/src/main/java/org/onosproject/t3/impl/McastGenerator.java
index 59d5ba6..a5022a2 100644
--- a/apps/t3/src/main/java/org/onosproject/t3/impl/McastGenerator.java
+++ b/apps/t3/src/main/java/org/onosproject/t3/impl/McastGenerator.java
@@ -63,25 +63,27 @@
         mcastService.getRoutes().forEach(route -> {
             McastRouteData routeData = mcastService.routeData(route);
             IpAddress group = route.group();
-            routeData.sources().forEach(source -> {
-                TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
-                        .matchVlanId(vlanId)
-                        .matchInPort(source.port());
-                if (group.isIp4()) {
-                    selector.matchEthDst(IPV4_ADDRESS)
-                            .matchIPDst(group.toIpPrefix())
-                            .matchEthType(EthType.EtherType.IPV4.ethType().toShort());
-                } else {
-                    selector.matchEthDst(IPV6_ADDRESS)
-                            .matchIPv6Dst(group.toIpPrefix())
-                            .matchEthType(EthType.EtherType.IPV6.ethType().toShort());
-                }
-                try {
-                    yield(ImmutableSet.of(manager.trace(selector.build(), source)));
-                } catch (InterruptedException e) {
-                    log.warn("Interrupted generator", e.getMessage());
-                    log.debug("exception", e);
-                }
+            routeData.sources().forEach((host, sources) -> {
+                sources.forEach(source -> {
+                    TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                            .matchVlanId(vlanId)
+                            .matchInPort(source.port());
+                    if (group.isIp4()) {
+                        selector.matchEthDst(IPV4_ADDRESS)
+                                .matchIPDst(group.toIpPrefix())
+                                .matchEthType(EthType.EtherType.IPV4.ethType().toShort());
+                    } else {
+                        selector.matchEthDst(IPV6_ADDRESS)
+                                .matchIPv6Dst(group.toIpPrefix())
+                                .matchEthType(EthType.EtherType.IPV6.ethType().toShort());
+                    }
+                    try {
+                        yield(ImmutableSet.of(manager.trace(selector.build(), source)));
+                    } catch (InterruptedException e) {
+                        log.warn("Interrupted generator", e.getMessage());
+                        log.debug("exception", e);
+                    }
+                });
             });
 
         });