Adding auto-layout to the access-null simulation.
Enhancing the scale test for flows and routes.

Change-Id: Ib91720b409872e44eaff4263cf229bffa2e292fc
diff --git a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
index 103c9d3..f18b861 100644
--- a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
+++ b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
@@ -186,6 +186,7 @@
 
     @Deactivate
     protected void deactivate() {
+        componentConfigService.unregisterProperties(getClass(), false);
         cfgService.removeListener(cfgListener);
         factories.forEach(cfgService::unregisterConfigFactory);
         packetService.removeProcessor(processor);
diff --git a/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateFlows.java b/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateFlows.java
index 6515def..8ab8e8f 100644
--- a/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateFlows.java
+++ b/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateFlows.java
@@ -17,23 +17,25 @@
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.cli.AbstractShellCommand;
 
 /**
  * Creates the specified number of routes for scale testing.
  */
-@Command(scope = "onos", name = "scale-create-flows",
-        description = "Creates the specified number of flows for scale testing")
+@Command(scope = "onos", name = "scale-flows",
+        description = "Sets the specified number of flows for scale testing")
 public class CreateFlows extends AbstractShellCommand {
 
-    @Argument(index = 0, name = "flowCount", description = "Number of flows to create",
+    @Argument(index = 0, name = "flowCount", description = "Number of flows to maintain",
             required = true)
     int flowCount;
 
     @Override
     protected void execute() {
-        ScaleTestManager service = get(ScaleTestManager.class);
-        service.createFlows(flowCount);
+        ComponentConfigService service = get(ComponentConfigService.class);
+        service.setProperty("org.onosproject.routescale.ScaleManager",
+                            "flowCount", String.valueOf(flowCount));
     }
 
 }
diff --git a/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateRoutes.java b/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateRoutes.java
index b43d2bb..d738b74 100644
--- a/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateRoutes.java
+++ b/apps/test/route-scale/src/main/java/org/onosproject/routescale/CreateRoutes.java
@@ -17,23 +17,26 @@
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.cli.AbstractShellCommand;
 
 /**
  * Creates the specified number of routes for scale testing.
  */
-@Command(scope = "onos", name = "scale-create-routes",
-        description = "Creates the specified number of routes for scale testing")
+@Command(scope = "onos", name = "scale-routes",
+        description = "Sets the specified number of routes for scale testing")
 public class CreateRoutes extends AbstractShellCommand {
 
-    @Argument(index = 0, name = "routeCount", description = "Number of routes to create",
+    @Argument(index = 0, name = "routeCount", description = "Number of routes to maintain",
             required = true)
     int routeCount;
 
     @Override
     protected void execute() {
-        ScaleTestManager service = get(ScaleTestManager.class);
-        service.createRoutes(routeCount);
+        ComponentConfigService service = get(ComponentConfigService.class);
+        service.setProperty("org.onosproject.routescale.ScaleManager",
+                            "routeCount", String.valueOf(routeCount));
+
     }
 
 }
diff --git a/apps/test/route-scale/src/main/java/org/onosproject/routescale/ScaleTestManager.java b/apps/test/route-scale/src/main/java/org/onosproject/routescale/ScaleTestManager.java
index 65c1c7f..25550f8 100644
--- a/apps/test/route-scale/src/main/java/org/onosproject/routescale/ScaleTestManager.java
+++ b/apps/test/route-scale/src/main/java/org/onosproject/routescale/ScaleTestManager.java
@@ -21,6 +21,8 @@
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
@@ -29,29 +31,39 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onosproject.app.ApplicationService;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
+import org.onosproject.net.MastershipRole;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleOperations;
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.host.HostAdminService;
+import org.onosproject.net.link.LinkService;
 import org.onosproject.routeservice.Route;
 import org.onosproject.routeservice.RouteAdminService;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Dictionary;
 import java.util.List;
+import java.util.Objects;
 import java.util.Random;
 
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
+
 @Component(immediate = true)
 @Service(value = ScaleTestManager.class)
 public class ScaleTestManager {
@@ -68,11 +80,25 @@
     protected HostAdminService hostAdminService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected FlowRuleService flowRuleService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected RouteAdminService routeAdminService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService componentConfigService;
+
+    @Property(name = "flowCount", intValue = 0,
+            label = "Number of flows to be maintained in the system")
+    private int flowCount = 0;
+
+    @Property(name = "routeCount", intValue = 0,
+            label = "Number of routes to be maintained in the system")
+    private int routeCount = 0;
+
     private final Random random = new Random(System.currentTimeMillis());
 
     private ApplicationId appId;
@@ -80,38 +106,128 @@
     @Activate
     protected void activate() {
         appId = applicationService.getId("org.onosproject.routescale");
+        componentConfigService.registerProperties(getClass());
         log.info("Started");
     }
 
     @Deactivate
     protected void deactivate() {
+        componentConfigService.unregisterProperties(getClass(), false);
         log.info("Stopped");
     }
 
-    public void createFlows(int flowCount) {
-        for (Device device : deviceService.getAvailableDevices()) {
-            DeviceId id = device.id();
-            FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
+    @Modified
+    public void modified(ComponentContext context) {
+        if (context == null) {
+            return;
+        }
 
-            for (int i = 0; i < flowCount; i++) {
-                FlowRule.Builder frb = DefaultFlowRule.builder();
-                frb.fromApp(appId).makePermanent().withPriority(1000 + i);
-                TrafficSelector.Builder tsb = DefaultTrafficSelector.builder();
-                TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+        Dictionary<?, ?> properties = context.getProperties();
+        try {
+            String s = get(properties, "flowCount");
+            flowCount = isNullOrEmpty(s) ? flowCount : Integer.parseInt(s.trim());
 
-                tsb.matchEthType(Ethernet.TYPE_IPV4);
-                ttb.setEthDst(randomMac()).setEthSrc(randomMac());
-                ttb.setOutput(PortNumber.portNumber(random.nextInt(512)));
-                frb.withSelector(tsb.build()).withTreatment(ttb.build());
-                ops.add(frb.forDevice(id).build());
-            }
+            s = get(properties, "routeCount");
+            routeCount = isNullOrEmpty(s) ? routeCount : Integer.parseInt(s.trim());
 
-            flowRuleService.apply(ops.build());
+            log.info("Reconfigured; flowCount={}; routeCount={}", flowCount, routeCount);
 
+            adjustFlows();
+            adjustRoutes();
+
+        } catch (NumberFormatException | ClassCastException e) {
+            log.warn("Misconfigured", e);
         }
     }
 
-    public void createRoutes(int routeCount) {
+    private void adjustFlows() {
+        int deviceCount = deviceService.getAvailableDeviceCount();
+        if (deviceCount == 0) {
+            return;
+        }
+
+        int flowsPerDevice = flowCount / deviceCount;
+        for (Device device : deviceService.getAvailableDevices()) {
+            DeviceId id = device.id();
+            if (deviceService.getRole(id) != MastershipRole.MASTER ||
+                    flowsPerDevice == 0) {
+                continue;
+            }
+
+            int currentFlowCount = flowRuleService.getFlowRuleCount(id);
+            if (flowsPerDevice > currentFlowCount) {
+                addMoreFlows(flowsPerDevice, device, id, currentFlowCount);
+
+            } else if (flowsPerDevice < currentFlowCount) {
+                removeExcessFlows(flowsPerDevice, id, currentFlowCount);
+            }
+        }
+    }
+
+    private void addMoreFlows(int flowsPerDevice, Device device, DeviceId id,
+                              int currentFlowCount) {
+        int c = flowsPerDevice - currentFlowCount;
+        log.info("Adding {} flows for device {}", c, id);
+        List<PortNumber> ports = devicePorts(device);
+        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
+        for (int i = 0; i < c; i++) {
+            FlowRule.Builder frb = DefaultFlowRule.builder();
+            frb.fromApp(appId).makePermanent().withPriority(1000 + i);
+            TrafficSelector.Builder tsb = DefaultTrafficSelector.builder();
+            TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+
+            tsb.matchEthType(Ethernet.TYPE_IPV4);
+            ttb.setEthDst(randomMac()).setEthSrc(randomMac());
+            ttb.setOutput(randomPort(ports));
+            frb.withSelector(tsb.build()).withTreatment(ttb.build());
+            ops.add(frb.forDevice(id).build());
+        }
+        flowRuleService.apply(ops.build());
+    }
+
+    private void removeExcessFlows(int flowsPerDevice, DeviceId id,
+                                   int currentFlowCount) {
+        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
+        int c = flowsPerDevice - currentFlowCount;
+        log.info("Removing {} flows from device {}", c, id);
+        for (FlowEntry e : flowRuleService.getFlowEntries(id)) {
+            if (Objects.equals(e.appId(), appId.id()) && c > 0) {
+                ops.remove(e);
+                c--;
+            }
+        }
+        flowRuleService.apply(ops.build());
+    }
+
+    private void adjustRoutes() {
+        int currentRouteCount =
+                routeAdminService.getRouteTables().parallelStream()
+                        .mapToInt(t -> routeAdminService.getRoutes(t).size()).sum();
+        if (currentRouteCount < routeCount) {
+            createRoutes(routeCount - currentRouteCount);
+        } else if (currentRouteCount > routeCount) {
+            removeRoutes(currentRouteCount - routeCount);
+        }
+    }
+
+    // Returns a list of ports on the given device that have either links or
+    // hosts connected to them.
+    private List<PortNumber> devicePorts(Device device) {
+        DeviceId id = device.id();
+        ImmutableList.Builder<PortNumber> ports = ImmutableList.builder();
+        linkService.getDeviceEgressLinks(id).forEach(l -> ports.add(l.src().port()));
+        hostAdminService.getConnectedHosts(id)
+                .forEach(h -> h.locations().stream()
+                        .filter(l -> Objects.equals(id, l.elementId()))
+                        .findFirst()
+                        .ifPresent(l -> ports.add(l.port())));
+        return ports.build();
+    }
+
+    // Creates the specified number of random routes. Such routes are generated
+    // using random IP prefices with next hop being an IP address of a randomly
+    // chosen hosts.
+    private void createRoutes(int routeCount) {
         List<Host> hosts = ImmutableList.copyOf(hostAdminService.getHosts());
         ImmutableSet.Builder<Route> routes = ImmutableSet.builder();
         for (int i = 0; i < routeCount; i++) {
@@ -122,21 +238,34 @@
         routeAdminService.update(routes.build());
     }
 
+    // Removes the specified number of routes chosen at random.
+    private void removeRoutes(int routeCount) {
+        log.warn("Not implemented yet");
+    }
+
+    // Generates a random IP address.
     private IpAddress randomIp() {
         byte[] bytes = new byte[4];
         random.nextBytes(bytes);
         return IpAddress.valueOf(IpAddress.Version.INET, bytes, 0);
     }
 
+    // Generates a random MAC address.
+    private MacAddress randomMac() {
+        byte[] bytes = new byte[6];
+        random.nextBytes(bytes);
+        return MacAddress.valueOf(bytes);
+    }
+
+    // Returns IP address of a host randomly chosen from the specified list.
     private IpAddress randomIp(List<Host> hosts) {
         Host host = hosts.get(random.nextInt(hosts.size()));
         return host.ipAddresses().iterator().next();
     }
 
-    private MacAddress randomMac() {
-        byte[] bytes = new byte[6];
-        random.nextBytes(bytes);
-        return MacAddress.valueOf(bytes);
+    // Returns port number randomly chosen from the given list of port numbers.
+    private PortNumber randomPort(List<PortNumber> ports) {
+        return ports.get(random.nextInt(ports.size()));
     }
 
 }