CORD-77 Dynamic Access Agent Config

This commit depends on https://gerrit.opencord.org/#/c/56/

Change-Id: I6084621c36046ae8b6262cab52c49825d3e0d0d1
diff --git a/apps/segmentrouting/BUCK b/apps/segmentrouting/BUCK
index f532bea..d24cde8 100644
--- a/apps/segmentrouting/BUCK
+++ b/apps/segmentrouting/BUCK
@@ -2,12 +2,18 @@
     '//lib:CORE_DEPS',
     '//lib:org.apache.karaf.shell.console',
     '//lib:javax.ws.rs-api',
+    '//lib:cord-config',
     '//cli:onos-cli',
     '//core/store/serializers:onos-core-serializers',
     '//incubator/api:onos-incubator-api',
     '//utils/rest:onlab-rest',
 ]
 
+BUNDLES = [
+    '//apps/segmentrouting:onos-apps-segmentrouting',
+    '//lib:cord-config'
+]
+
 TEST_DEPS = [
     '//lib:TEST_ADAPTERS',
 ]
@@ -21,5 +27,6 @@
     title = 'Segment Routing App',
     category = 'Traffic Steering',
     url = 'http://onosproject.org',
+    included_bundles = BUNDLES,
     description = 'Segment routing application.',
 )
diff --git a/apps/segmentrouting/app.xml b/apps/segmentrouting/app.xml
new file mode 100644
index 0000000..2e90cd6
--- /dev/null
+++ b/apps/segmentrouting/app.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-present Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<app name="org.onosproject.segmentrouting" origin="ON.Lab" version="${project.version}"
+     title="Segment Routing App" category="Traffic Steering" url="http://onosproject.org"
+     featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
+     features="${project.artifactId}" apps="">
+    <description>${project.description}</description>
+    <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
+    <!-- TODO: Replace this with variable -->
+    <artifact>mvn:org.opencord/cord-config/1.0-SNAPSHOT</artifact>
+</app>
diff --git a/apps/segmentrouting/features.xml b/apps/segmentrouting/features.xml
new file mode 100644
index 0000000..9e23b2c
--- /dev/null
+++ b/apps/segmentrouting/features.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ Copyright 2016-present Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+    <feature name="${project.artifactId}" version="${project.version}"
+             description="${project.description}">
+        <feature>onos-api</feature>
+        <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
+        <!-- TODO: Replace this with variable -->
+        <bundle>mvn:org.opencord/cord-config/1.0-SNAPSHOT</bundle>
+    </feature>
+</features>
diff --git a/apps/segmentrouting/pom.xml b/apps/segmentrouting/pom.xml
index 0abe235..d78981d 100644
--- a/apps/segmentrouting/pom.xml
+++ b/apps/segmentrouting/pom.xml
@@ -31,11 +31,6 @@
     <description>Segment routing application</description>
 
     <properties>
-        <onos.app.name>org.onosproject.segmentrouting</onos.app.name>
-        <onos.app.title>Segment Routing App</onos.app.title>
-        <onos.app.category>Traffic Steering</onos.app.category>
-        <onos.app.url>http://onosproject.org</onos.app.url>
-        <onos.app.readme>Segment routing application.</onos.app.readme>
         <web.context>/onos/segmentrouting</web.context>
         <api.version>1.0.0</api.version>
         <api.title>ONOS Segment Routing REST API</api.title>
@@ -71,6 +66,12 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.opencord</groupId>
+            <artifactId>cord-config</artifactId>
+            <!-- TODO: Replace this with variable -->
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
             <groupId>javax.ws.rs</groupId>
             <artifactId>javax.ws.rs-api</artifactId>
             <version>2.0.1</version>
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/CordConfigHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/CordConfigHandler.java
new file mode 100644
index 0000000..6710330
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/CordConfigHandler.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.segmentrouting;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.provider.ProviderId;
+import org.opencord.cordconfig.CordConfigEvent;
+import org.opencord.cordconfig.access.AccessAgentData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Optional;
+
+/**
+ * Handles access agent config event which is required for CORD integration.
+ */
+public class CordConfigHandler {
+    private static Logger log = LoggerFactory.getLogger(CordConfigHandler.class);
+    private final SegmentRoutingManager srManager;
+
+    /**
+     * Constructs the CordConfigHandler.
+     *
+     * @param srManager Segment Routing manager
+     */
+    public CordConfigHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+    }
+
+    /**
+     * Read initial access agent config for given device.
+     *
+     * @param deviceId ID of the device to be initialized
+     */
+    public void init(DeviceId deviceId) {
+        // Try to read access agent config
+        Optional<AccessAgentData> accessAgent =
+                srManager.cordConfigService.getAccessAgent(deviceId);
+
+        if (!accessAgent.isPresent()) {
+            log.debug("No access agent config on {}. Skip.", deviceId);
+            return;
+        }
+
+        processAccessAgentAdded(accessAgent.get());
+    }
+
+    // TODO javadoc
+    protected void processAccessAgentAddedEvent(CordConfigEvent event) {
+        log.debug("processAccessAgentAdded: {}, {}", event.subject(), event.prevSubject());
+        processAccessAgentAdded((AccessAgentData) event.subject());
+    }
+
+    protected void processAccessAgentUpdatedEvent(CordConfigEvent event) {
+        log.debug("processAccessAgentUpdated: {}, {}", event.subject(), event.prevSubject());
+        processAccessAgentRemoved((AccessAgentData) event.prevSubject());
+        processAccessAgentAdded((AccessAgentData) event.subject());
+    }
+
+    protected void processAccessAgentRemovedEvent(CordConfigEvent event) {
+        log.debug("processAccessAgentRemoved: {}, {}", event.subject(), event.prevSubject());
+        processAccessAgentRemoved((AccessAgentData) event.prevSubject());
+    }
+
+    protected void processAccessAgentAdded(AccessAgentData accessAgentData) {
+        if (!srManager.mastershipService.isLocalMaster(accessAgentData.deviceId())) {
+            log.debug("Not the master of {}. Abort.", accessAgentData.deviceId());
+            return;
+        }
+
+        // Do not proceed if vtn location is missing
+        if (!accessAgentData.getVtnLocation().isPresent()) {
+            log.warn("accessAgentData does not contain vtn location. Abort.");
+            return;
+        }
+
+        MacAddress agentMac = accessAgentData.getAgentMac();
+        ConnectPoint agentLocation = accessAgentData.getVtnLocation().get();
+
+        // Do not proceed if agent port doesn't have subnet configured
+        Ip4Prefix agentSubnet = srManager.deviceConfiguration
+                .getPortSubnet(agentLocation.deviceId(), agentLocation.port());
+        if (agentSubnet == null) {
+            log.warn("Agent port does not have subnet configuration. Abort.");
+            return;
+        }
+
+        // Add host information for agent
+        log.info("push host info for agent {}", agentMac);
+        srManager.hostHandler.processHostAdded(createHost(agentMac, agentLocation));
+
+        accessAgentData.getOltMacInfo().forEach((connectPoint, macAddress) -> {
+            // Do not proceed if olt port has subnet configured
+            Ip4Prefix oltSubnet = srManager.deviceConfiguration
+                    .getPortSubnet(connectPoint.deviceId(), connectPoint.port());
+            if (oltSubnet != null) {
+                log.warn("OLT port has subnet configuration. Abort.");
+                return;
+            }
+
+            // Add olt to the subnet of agent
+            log.info("push subnet for olt {}", agentSubnet);
+            srManager.deviceConfiguration.addSubnet(connectPoint, agentSubnet);
+            srManager.routingRulePopulator.populateRouterMacVlanFilters(connectPoint.deviceId());
+
+            // Add host information for olt
+            log.info("push host info for olt {}", macAddress);
+            srManager.hostHandler.processHostAdded(createHost(macAddress, connectPoint));
+        });
+    }
+
+    protected void processAccessAgentRemoved(AccessAgentData accessAgentData) {
+        if (!srManager.mastershipService.isLocalMaster(accessAgentData.deviceId())) {
+            log.debug("Not the master of {}. Abort.", accessAgentData.deviceId());
+            return;
+        }
+
+        // Do not proceed if vtn location is missing
+        if (!accessAgentData.getVtnLocation().isPresent()) {
+            log.warn("accessAgentData does not contain vtn location. Abort.");
+            return;
+        }
+
+        MacAddress agentMac = accessAgentData.getAgentMac();
+        ConnectPoint agentLocation = accessAgentData.getVtnLocation().get();
+
+        // Do not proceed if olt port doesn't have subnet configured
+        Ip4Prefix agentSubnet = srManager.deviceConfiguration
+                .getPortSubnet(agentLocation.deviceId(), agentLocation.port());
+        if (agentSubnet == null) {
+            log.warn("Agent port does not have subnet configuration. Abort.");
+            return;
+        }
+
+        // Remove host information for agent
+        log.info("delete host info for agent {}", agentMac);
+        srManager.hostHandler.processHostRemoved(createHost(agentMac, agentLocation));
+
+        accessAgentData.getOltMacInfo().forEach((connectPoint, macAddress) -> {
+            // Do not proceed if agent port doesn't have subnet configured
+            Ip4Prefix oltSubnet = srManager.deviceConfiguration
+                    .getPortSubnet(connectPoint.deviceId(), connectPoint.port());
+            if (oltSubnet == null) {
+                log.warn("OLT port does not have subnet configuration. Abort.");
+                return;
+            }
+
+            // Remove host information for olt
+            log.info("delete host info for olt {}", macAddress);
+            srManager.hostHandler.processHostRemoved(createHost(macAddress, connectPoint));
+
+            // Remove olt to the subnet of agent
+            log.info("delete subnet for olt {}", agentSubnet);
+            srManager.deviceConfiguration.removeSubnet(connectPoint, agentSubnet);
+            srManager.routingRulePopulator.populateRouterMacVlanFilters(connectPoint.deviceId());
+        });
+    }
+
+    private Host createHost(MacAddress macAddress, ConnectPoint location) {
+        return new DefaultHost(
+                new ProviderId("host", "org.onosproject.segmentrouting"),
+                HostId.hostId(macAddress),
+                macAddress,
+                VlanId.NONE,
+                new HostLocation(location, System.currentTimeMillis()),
+                ImmutableSet.of());
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index d5c93fd..c82b675 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -71,15 +71,15 @@
             if (!deviceId.equals(devId)) {
                 return;
             }
-            processHostAddedEventInternal(host);
+            processHostAdded(host);
         });
     }
 
     protected void processHostAddedEvent(HostEvent event) {
-        processHostAddedEventInternal(event.subject());
+        processHostAdded(event.subject());
     }
 
-    private void processHostAddedEventInternal(Host host) {
+    protected void processHostAdded(Host host) {
         MacAddress mac = host.mac();
         VlanId vlanId = host.vlan();
         HostLocation location = host.location();
@@ -116,15 +116,19 @@
     }
 
     protected void processHostRemoveEvent(HostEvent event) {
-        MacAddress mac = event.subject().mac();
-        VlanId vlanId = event.subject().vlan();
-        HostLocation location = event.subject().location();
+        processHostRemoved(event.subject());
+    }
+
+    protected void processHostRemoved(Host host) {
+        MacAddress mac = host.mac();
+        VlanId vlanId = host.vlan();
+        HostLocation location = host.location();
         DeviceId deviceId = location.deviceId();
         PortNumber port = location.port();
-        Set<IpAddress> ips = event.subject().ipAddresses();
+        Set<IpAddress> ips = host.ipAddresses();
         log.debug("Host {}/{} is removed from {}:{}", mac, vlanId, deviceId, port);
 
-        if (accepted(event.subject())) {
+        if (accepted(host)) {
             // Revoke bridging table entry
             ForwardingObjective.Builder fob =
                     hostFwdObjBuilder(deviceId, mac, vlanId, port);
@@ -133,9 +137,9 @@
                 return;
             }
             ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Host rule for {} revoked", event.subject()),
+                    (objective) -> log.debug("Host rule for {} revoked", host),
                     (objective, error) ->
-                            log.warn("Failed to revoke host rule for {}: {}", event.subject(), error));
+                            log.warn("Failed to revoke host rule for {}: {}", host, error));
             flowObjectiveService.forward(deviceId, fob.remove(context));
 
             // Revoke IP table entry
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 2b87fca..c115704 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -82,6 +82,9 @@
 import org.onosproject.store.service.EventuallyConsistentMapBuilder;
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.WallClockTimestamp;
+import org.opencord.cordconfig.CordConfigEvent;
+import org.opencord.cordconfig.CordConfigListener;
+import org.opencord.cordconfig.CordConfigService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -147,6 +150,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected TopologyService topologyService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordConfigService cordConfigService;
+
     protected ArpHandler arpHandler = null;
     protected IcmpHandler icmpHandler = null;
     protected IpHandler ipHandler = null;
@@ -163,11 +169,13 @@
     private AppConfigHandler appCfgHandler = null;
     protected XConnectHandler xConnectHandler = null;
     private McastHandler mcastHandler = null;
-    private HostHandler hostHandler = null;
+    protected HostHandler hostHandler = null;
+    private CordConfigHandler cordConfigHandler = null;
     private InternalEventHandler eventHandler = new InternalEventHandler();
     private final InternalHostListener hostListener = new InternalHostListener();
     private final InternalConfigListener cfgListener = new InternalConfigListener(this);
     private final InternalMcastListener mcastListener = new InternalMcastListener();
+    private final InternalCordConfigListener cordConfigListener = new InternalCordConfigListener();
 
     private ScheduledExecutorService executorService = Executors
             .newScheduledThreadPool(1);
@@ -324,6 +332,7 @@
         xConnectHandler = new XConnectHandler(this);
         mcastHandler = new McastHandler(this);
         hostHandler = new HostHandler(this);
+        cordConfigHandler = new CordConfigHandler(this);
 
         cfgService.addListener(cfgListener);
         cfgService.registerConfigFactory(deviceConfigFactory);
@@ -335,6 +344,7 @@
         linkService.addListener(linkListener);
         deviceService.addListener(deviceListener);
         multicastRouteService.addListener(mcastListener);
+        cordConfigService.addListener(cordConfigListener);
 
         // Request ARP packet-in
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
@@ -379,6 +389,7 @@
         linkService.removeListener(linkListener);
         deviceService.removeListener(deviceListener);
         multicastRouteService.removeListener(mcastListener);
+        cordConfigService.removeListener(cordConfigListener);
 
         processor = null;
         linkListener = null;
@@ -394,7 +405,6 @@
         log.info("Stopped");
     }
 
-
     @Override
     public List<Tunnel> getTunnels() {
         return tunnelHandler.getTunnels();
@@ -818,6 +828,7 @@
         if (mastershipService.isLocalMaster(deviceId)) {
             hostHandler.readInitialHosts(deviceId);
             xConnectHandler.init(deviceId);
+            cordConfigHandler.init(deviceId);
             DefaultGroupHandler groupHandler = groupHandlerMap.get(deviceId);
             groupHandler.createGroupsFromSubnetConfig();
             routingRulePopulator.populateSubnetBroadcastRule(deviceId);
@@ -1000,4 +1011,26 @@
             }
         }
     }
+
+    private class InternalCordConfigListener implements CordConfigListener {
+        @Override
+        public void event(CordConfigEvent event) {
+            switch (event.type()) {
+                case ACCESS_AGENT_ADDED:
+                    cordConfigHandler.processAccessAgentAddedEvent(event);
+                    break;
+                case ACCESS_AGENT_UPDATED:
+                    cordConfigHandler.processAccessAgentUpdatedEvent(event);
+                    break;
+                case ACCESS_AGENT_REMOVED:
+                    cordConfigHandler.processAccessAgentRemovedEvent(event);
+                    break;
+                case ACCESS_DEVICE_ADDED:
+                case ACCESS_DEVICE_UPDATED:
+                case ACCESS_DEVICE_REMOVED:
+                default:
+                    break;
+            }
+        }
+    }
 }
diff --git a/lib/BUCK b/lib/BUCK
index d37e540..bd0f965 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -1,4 +1,4 @@
-# ***** This file was auto-generated at Wed Jun 01 16:54:27 PDT 2016. Do not edit this file manually. *****
+# ***** This file was auto-generated at Wed Jun 15 12:09:22 PDT 2016. Do not edit this file manually. *****
 osgi_feature_group(
   name = 'COMPILE',
   visibility = ['PUBLIC'],
@@ -1022,6 +1022,15 @@
 )
 
 remote_jar (
+  name = 'cord-config',
+  out = 'cord-config-1.0-20160615.190726-9.jar',
+  url = 'https://oss.sonatype.org/content/repositories/snapshots/org/opencord/cord-config/1.0-SNAPSHOT/cord-config-1.0-20160615.190726-9.jar',
+  sha1 = '790bc0cf8ffe3f872370dd197cf225e76d93002d',
+  maven_coords = 'org.opencord:cord-config:1.0-SNAPSHOT',
+  visibility = [ 'PUBLIC' ],
+)
+
+remote_jar (
   name = 'openstack4j-core',
   out = 'openstack4j-core-2.11.jar',
   url = 'mvn:org.pacesys:openstack4j-core:jar:2.11',
diff --git a/lib/deps.json b/lib/deps.json
index 2bd3cb9..e9bf1ba 100644
--- a/lib/deps.json
+++ b/lib/deps.json
@@ -200,6 +200,10 @@
       "uri": "mvn:com.btisystems.mibbler.mibs:rfc:1.0-SNAPSHOT",
       "repo": "https://oss.sonatype.org/content/repositories/snapshots"
     },
+    "cord-config": {
+      "uri": "mvn:org.opencord:cord-config:1.0-SNAPSHOT",
+      "repo": "https://oss.sonatype.org/content/repositories/snapshots"
+    },
     // Openstack4j related jars
     "openstack4j-core": "mvn:org.pacesys:openstack4j-core:2.11",
     "openstack4j-http-connector": "mvn:org.pacesys.openstack4j.connectors:openstack4j-http-connector:2.11",