CORD-348 multicast support in SegmentRouting and vRouter

In this submission:
* Setup/teardown multicast route according to SinkAdded/SinkRemoved event
    - ingressVlan and egressVlan is configurable through network config
* Change behavior of OFDPA VLAN assignment
    - Always use the VLAN in metadata if present
* Bugfix of writing immutable object

NOT in this submission (coming soon):
* Error handling (e.g. link/device failure recovery)

Change-Id: I9be11af04eb2d6456b865c7e59e96cc02370f846
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
index 473450d..f0649b9 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
@@ -29,11 +29,13 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.config.basics.McastConfig;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceEvent;
 import org.onosproject.incubator.net.intf.InterfaceListener;
@@ -44,9 +46,12 @@
 import org.onosproject.incubator.net.routing.RouteService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.SubjectFactories;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
@@ -102,6 +107,9 @@
     protected NetworkConfigService networkConfigService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry networkConfigRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService componentConfigService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -123,6 +131,7 @@
 
     private List<String> interfaces;
 
+    private ApplicationId coreAppId;
     private ApplicationId routerAppId;
 
     // Reference count for how many times a next hop is used by a route
@@ -138,13 +147,25 @@
     private InternalInterfaceListener internalInterfaceList = new InternalInterfaceListener();
     private InternalRouteListener routeListener = new InternalRouteListener();
 
+    private ConfigFactory<ApplicationId, McastConfig> mcastConfigFactory =
+            new ConfigFactory<ApplicationId, McastConfig>(SubjectFactories.APP_SUBJECT_FACTORY,
+                    McastConfig.class, "multicast") {
+                @Override
+                public McastConfig createConfig() {
+                    return new McastConfig();
+                }
+            };
+
     @Activate
     protected void activate(ComponentContext context) {
         componentConfigService.registerProperties(getClass());
         modified(context);
 
+        coreAppId = coreService.registerApplication(CoreService.CORE_APP_NAME);
         routerAppId = coreService.registerApplication(RoutingService.ROUTER_APP_ID);
 
+        networkConfigRegistry.registerConfigFactory(mcastConfigFactory);
+
         deviceListener = new InternalDeviceListener();
         deviceService.addListener(deviceListener);
 
@@ -368,6 +389,7 @@
             }
 
             createFilteringObjective(install, intf);
+            createMcastFilteringObjective(install, intf);
         }
     }
 
@@ -380,10 +402,14 @@
         }
 
         createFilteringObjective(install, intf);
+        createMcastFilteringObjective(install, intf);
     }
 
     //create filtering objective for interface
     private void createFilteringObjective(boolean install, Interface intf) {
+        VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
+                VlanId.vlanId(ASSIGNED_VLAN) :
+                egressVlan();
 
         FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
         // first add filter for the interface
@@ -393,12 +419,12 @@
         fob.withPriority(PRIORITY_OFFSET);
         if (intf.vlan() == VlanId.NONE) {
             TrafficTreatment tt = DefaultTrafficTreatment.builder()
-                    .pushVlan().setVlanId(VlanId.vlanId(ASSIGNED_VLAN)).build();
+                    .pushVlan().setVlanId(assignedVlan).build();
             fob.withMeta(tt);
         }
-
         fob.permit().fromApp(routerAppId);
         sendFilteringObjective(install, fob, intf);
+
         if (controlPlaneConnectPoint != null) {
             // then add the same mac/vlan filters for control-plane connect point
             fob.withKey(Criteria.matchInPort(controlPlaneConnectPoint.port()));
@@ -406,6 +432,27 @@
         }
     }
 
+    //create filtering objective for multicast traffic
+    private void createMcastFilteringObjective(boolean install, Interface intf) {
+        VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
+                VlanId.vlanId(ASSIGNED_VLAN) :
+                egressVlan();
+
+        FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
+        // first add filter for the interface
+        fob.withKey(Criteria.matchInPort(intf.connectPoint().port()))
+                .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
+                        MacAddress.IPV4_MULTICAST_MASK))
+                .addCondition(Criteria.matchVlanId(ingressVlan()));
+        fob.withPriority(PRIORITY_OFFSET);
+        TrafficTreatment tt = DefaultTrafficTreatment.builder()
+                .pushVlan().setVlanId(assignedVlan).build();
+        fob.withMeta(tt);
+
+        fob.permit().fromApp(routerAppId);
+        sendFilteringObjective(install, fob, intf);
+    }
+
     private void sendFilteringObjective(boolean install, FilteringObjective.Builder fob,
                                         Interface intf) {
 
@@ -419,6 +466,18 @@
         flowObjectiveService.filter(deviceId, filter);
     }
 
+    private VlanId ingressVlan() {
+        McastConfig mcastConfig =
+                networkConfigService.getConfig(coreAppId, McastConfig.class);
+        return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
+    }
+
+    private VlanId egressVlan() {
+        McastConfig mcastConfig =
+                networkConfigService.getConfig(coreAppId, McastConfig.class);
+        return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
+    }
+
     private class InternalRouteListener implements RouteListener {
         @Override
         public void event(RouteEvent event) {
@@ -490,7 +549,6 @@
     }
 
     private class InternalInterfaceListener implements InterfaceListener {
-
         @Override
         public void event(InterfaceEvent event) {
             Interface intf = event.subject();