CORD fabric app

Change-Id: I2d261762b432170463e1dbc40432193cad28c9b2
diff --git a/apps/cordfabric/pom.xml b/apps/cordfabric/pom.xml
new file mode 100644
index 0000000..008702a
--- /dev/null
+++ b/apps/cordfabric/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onos-apps</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.2.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>onos-app-cordfabric</artifactId>
+
+    <packaging>bundle</packaging>
+    <description>Simple fabric application for CORD</description>
+
+    <properties>
+        <onos.app.name>org.onosproject.cordfabric</onos.app.name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/apps/cordfabric/src/main/java/org/onosproject/cordfabric/CordFabricManager.java b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/CordFabricManager.java
new file mode 100644
index 0000000..a6f9d43
--- /dev/null
+++ b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/CordFabricManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2015 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.cordfabric;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * CORD fabric application.
+ */
+@Service
+@Component(immediate = true)
+public class CordFabricManager implements FabricService {
+
+    private final Logger log = getLogger(getClass());
+
+    private ApplicationId appId;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    private static final int PRIORITY = 1000;
+
+    private final Multimap<VlanId, ConnectPoint> vlans = HashMultimap.create();
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onosproject.cordfabric");
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public void addVlan(VlanId vlanId, List<ConnectPoint> ports) {
+        checkNotNull(vlanId);
+        checkNotNull(ports);
+        checkArgument(ports.size() > 1);
+        verifyPorts(ports);
+
+        removeVlan(vlanId);
+
+        ports.forEach(cp -> {
+            if (vlans.put(vlanId, cp)) {
+                addForwarding(vlanId, cp.deviceId(), cp.port(),
+                              ports.stream()
+                                      .filter(p -> p != cp)
+                                      .map(ConnectPoint::port)
+                                      .collect(Collectors.toList()));
+            }
+        });
+    }
+
+    @Override
+    public void removeVlan(VlanId vlanId) {
+        vlans.removeAll(vlanId)
+                .forEach(cp -> removeForwarding(vlanId, cp.deviceId(), cp.port()));
+    }
+
+    @Override
+    public Multimap<VlanId, ConnectPoint> getVlans() {
+        return Multimaps.unmodifiableMultimap(vlans);
+    }
+
+    private static void verifyPorts(List<ConnectPoint> ports) {
+        DeviceId deviceId = ports.get(0).deviceId();
+        for (ConnectPoint connectPoint : ports) {
+            if (!connectPoint.deviceId().equals(deviceId)) {
+                throw new IllegalArgumentException("Ports must all be on the same device");
+            }
+        }
+    }
+
+    private void addForwarding(VlanId vlanId, DeviceId deviceId, PortNumber inPort,
+                               List<PortNumber> outPorts) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(vlanId)
+                .matchInPort(inPort)
+                .build();
+
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+
+        outPorts.forEach(p -> treatmentBuilder.setOutput(p));
+
+        ForwardingObjective objective = DefaultForwardingObjective.builder()
+                .fromApp(appId)
+                .makePermanent()
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .withPriority(PRIORITY)
+                .withSelector(selector)
+                .withTreatment(treatmentBuilder.build())
+                .add(new ObjectiveHandler());
+
+        flowObjectiveService.forward(deviceId, objective);
+    }
+
+    private void removeForwarding(VlanId vlanId, DeviceId deviceId, PortNumber inPort) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(vlanId)
+                .matchInPort(inPort)
+                .build();
+
+        ForwardingObjective objective = DefaultForwardingObjective.builder()
+                .fromApp(appId)
+                .makePermanent()
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .withPriority(PRIORITY)
+                .withSelector(selector)
+                .withTreatment(DefaultTrafficTreatment.builder().build())
+                .remove(new ObjectiveHandler());
+
+        flowObjectiveService.forward(deviceId, objective);
+    }
+
+    private static class ObjectiveHandler implements ObjectiveContext {
+        private static Logger log = LoggerFactory.getLogger(ObjectiveHandler.class);
+
+        @Override
+        public void onSuccess(Objective objective) {
+            log.info("Flow objective operation successful: {}", objective);
+        }
+
+        @Override
+        public void onError(Objective objective, ObjectiveError error) {
+            log.info("Flow objective operation failed: {}", objective);
+        }
+    }
+}
diff --git a/apps/cordfabric/src/main/java/org/onosproject/cordfabric/FabricService.java b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/FabricService.java
new file mode 100644
index 0000000..60ece29
--- /dev/null
+++ b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/FabricService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 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.cordfabric;
+
+import com.google.common.collect.Multimap;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.List;
+
+/**
+ * Service used to interact with fabric.
+ */
+public interface FabricService {
+
+    /**
+     * Remaps a vlan to the specified ports. The specified ports will be the
+     * only ports in this vlan once the operation completes.
+     *
+     * @param vlanId vlan ID to add/modify
+     * @param ports list of ports to add to the vlan
+     */
+    void addVlan(VlanId vlanId, List<ConnectPoint> ports);
+
+    /**
+     * Removes a vlan from all ports in the fabric.
+     *
+     * @param vlanId ID of vlan to remove
+     */
+    void removeVlan(VlanId vlanId);
+
+    /**
+     * Returns the vlan to port mapping for all vlans/ports configured in the
+     * fabric.
+     *
+     * @return mapping of vlan to port
+     */
+    Multimap<VlanId, ConnectPoint> getVlans();
+}
diff --git a/apps/cordfabric/src/main/java/org/onosproject/cordfabric/cli/FabricAddCommand.java b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/cli/FabricAddCommand.java
new file mode 100644
index 0000000..cedde04
--- /dev/null
+++ b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/cli/FabricAddCommand.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 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.cordfabric.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cordfabric.FabricService;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adds a vlan to the fabric.
+ */
+@Command(scope = "onos", name = "add-fabric-vlan",
+        description = "Adds a VLAN to the fabric")
+public class FabricAddCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "vlanid", description = "VLAN ID",
+            required = true, multiValued = false)
+    private String vlanIdString = null;
+
+    @Argument(index = 1, name = "ports",
+            description = "List of ports in the VLAN",
+            required = true, multiValued = true)
+    private String[] portStrings = null;
+
+    @Override
+    protected void execute() {
+        FabricService service = AbstractShellCommand.get(FabricService.class);
+
+        VlanId vlan = VlanId.vlanId(Short.parseShort(vlanIdString));
+
+        if (portStrings.length < 2) {
+            throw new IllegalArgumentException("Must have at least 2 ports");
+        }
+
+        List<ConnectPoint> ports = new ArrayList<>(portStrings.length);
+
+        for (String portString : portStrings) {
+            ports.add(ConnectPoint.deviceConnectPoint(portString));
+        }
+
+        service.addVlan(vlan, ports);
+    }
+}
diff --git a/apps/cordfabric/src/main/java/org/onosproject/cordfabric/cli/FabricShowCommand.java b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/cli/FabricShowCommand.java
new file mode 100644
index 0000000..1db4222
--- /dev/null
+++ b/apps/cordfabric/src/main/java/org/onosproject/cordfabric/cli/FabricShowCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 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.cordfabric.cli;
+
+import com.google.common.collect.Multimap;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cordfabric.FabricService;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Shows the vlans in the fabric.
+ */
+@Command(scope = "onos", name = "fabric",
+        description = "Shows the fabric vlans")
+public class FabricShowCommand extends AbstractShellCommand {
+
+    private static final String VLAN_HEADER_LINE_FORMAT = "VLAN %s";
+    private static final String PORT_LINE_FORMAT = "\t%s";
+
+    @Override
+    protected void execute() {
+        FabricService service = AbstractShellCommand.get(FabricService.class);
+
+        Multimap<VlanId, ConnectPoint> vlans = service.getVlans();
+
+        vlans.keySet().forEach(vlanId -> {
+            print(VLAN_HEADER_LINE_FORMAT, vlanId);
+            vlans.get(vlanId).forEach(cp -> print(PORT_LINE_FORMAT, cp));
+        });
+    }
+}
diff --git a/apps/cordfabric/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/cordfabric/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..9e43327
--- /dev/null
+++ b/apps/cordfabric/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ Copyright 2015 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.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.cordfabric.cli.FabricShowCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.cordfabric.cli.FabricAddCommand"/>
+            <completers>
+                <ref component-id="placeholderCompleter"/>
+                <ref component-id="connectPointCompleter"/>
+            </completers>
+        </command>
+    </command-bundle>
+
+    <bean id="connectPointCompleter" class="org.onosproject.cli.net.ConnectPointCompleter"/>
+    <bean id="placeholderCompleter" class="org.onosproject.cli.PlaceholderCompleter"/>
+</blueprint>
diff --git a/apps/pom.xml b/apps/pom.xml
index dbed6e7..c78e780 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -47,6 +47,7 @@
         <module>bgprouter</module>
         <module>test</module>
         <module>segmentrouting</module>
+        <module>cordfabric</module>
     </modules>
 
     <properties>
diff --git a/cli/src/main/java/org/onosproject/cli/PlaceholderCompleter.java b/cli/src/main/java/org/onosproject/cli/PlaceholderCompleter.java
new file mode 100644
index 0000000..dbd2a97
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/PlaceholderCompleter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 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.cli;
+
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * A completer that can be used as a placeholder for arguments that don't
+ * need/want completers.
+ */
+public class PlaceholderCompleter extends AbstractCompleter {
+
+    @Override
+    public int complete(String s, int i, List<String> list) {
+        // Populate a string completer with what the user has typed so far
+        StringsCompleter delegate = new StringsCompleter();
+        SortedSet<String> strings = delegate.getStrings();
+        if (s != null) {
+            strings.add(s);
+        }
+        return delegate.complete(s, i, list);
+    }
+}