[ONOS-6150] OpenstackNode application support creation of vlan interface on open vswitch

Change-Id: I4342a8bd5f8a0a802e05f6b89a7962e5d3c9c9af
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
index 2cafcd6..3f5adee 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
@@ -427,7 +427,7 @@
                             // don't need to add gateway here and there
                             GatewayNode gnode = GatewayNode.builder()
                                     .gatewayDeviceId(osNode.intBridge())
-                                    .dataIpAddress(osNode.dataIp().getIp4Address())
+                                    .dataIpAddress(osNode.dataIp().get().getIp4Address())
                                     .uplinkIntf(osNode.externalPortName().get())
                                     .build();
                             gatewayService.addGatewayNode(gnode);
diff --git a/apps/openstacknode/network-cfg.json b/apps/openstacknode/network-cfg.json
index daecd85..6cca7ed 100644
--- a/apps/openstacknode/network-cfg.json
+++ b/apps/openstacknode/network-cfg.json
@@ -2,41 +2,35 @@
     "apps" : {
         "org.onosproject.openstacknode" : {
             "openstacknode" : {
-                "nodes" : [
-                    {
-                        "hostname" : "compute-01",
-                        "type" : "COMPUTE",
-                        "managementIp" : "10.203.25.244",
-                        "dataIp" : "10.134.34.222",
-                        "integrationBridge" : "of:00000000000000a1"
-                    },
-                    {
-                        "hostname" : "compute-02",
-                        "type" : "COMPUTE",
-                        "managementIp" : "10.203.229.42",
-                        "dataIp" : "10.134.34.223",
-                        "integrationBridge" : "of:00000000000000a2"
-                    },
-                    {
-                        "hostname" : "gateway-01",
-                        "type" : "GATEWAY",
-                        "managementIp" : "10.203.198.125",
-                        "dataIp" : "10.134.33.208",
-                        "integrationBridge" : "of:00000000000000a3",
-                        "routerBridge" : "of:00000000000000b3",
-                        "uplinkPort" : "veth1",
-                        "routerController" : "172.17.0.2"
-                    },
-                    {
-                        "hostname" : "gateway-02",
-                        "type" : "GATEWAY",
-                        "managementIp" : "10.203.198.131",
-                        "dataIp" : "10.134.33.209",
-                        "integrationBridge" : "of:00000000000000a4",
-                        "routerBridge" : "of:00000000000000b4",
-                        "uplinkPort" : "veth1",
-                        "routerController" : "172.17.0.2"
-                    }
+                 "nodes" : [
+                        {
+                                "hostname" : "compute-01",
+                                "type" : "COMPUTE",
+                                "managementIp" : "172.16.130.4",
+                                "dataIp" : "172.16.130.4",
+                                "vlanPort" : "eth2",
+                                "integrationBridge" : "of:00000000000000a1"
+                        },
+                        {
+                                "hostname" : "compute-02",
+                                "type" : "COMPUTE",
+                                "managementIp" : "172.16.130.6",
+                                "dataIp" : "172.16.130.6",
+                                "vlanPort" : "eth2",
+                                "integrationBridge" : "of:00000000000000a2"
+                        },
+                        {
+                                "hostname" : "gateway-01",
+                                "type" : "GATEWAY",
+                                "managementIp" : "172.16.130.8",
+                                "dataIp" : "172.16.130.7",
+                                "vlanPort" : "eth2",
+                                "integrationBridge" : "of:00000000000000a3",
+                                "routerBridge" : "of:00000000000000b1",
+                                "uplinkPort" : "quagga-router",
+                                "routerController" : "172.17.0.2"
+
+                        }
                 ]
             }
         }
@@ -51,16 +45,6 @@
             "basic" : {
                 "driver" : "sona"
             }
-        },
-        "of:00000000000000b1" : {
-            "basic" : {
-                "driver" : "softrouter"
-            }
-        },
-        "of:00000000000000b2" : {
-            "basic" : {
-                "driver" : "softrouter"
-            }
         }
     }
 }
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java
index 2d73a85..0ff91a9 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java
@@ -39,12 +39,13 @@
     private final String hostname;
     private final NodeType type;
     private final IpAddress managementIp;
-    private final IpAddress dataIp;
+    private final Optional<IpAddress> dataIp;
     private final DeviceId integrationBridge;
     private final Optional<DeviceId> routerBridge;
     private final Optional<String> uplink;
     // TODO remove this when we use single ONOS cluster for both openstackNode and vRouter
     private final Optional<IpAddress> routerController;
+    private final Optional<String> vlanPort;
     private final NodeState state;
 
     public static final Comparator<OpenstackNode> OPENSTACK_NODE_COMPARATOR =
@@ -53,11 +54,12 @@
     private OpenstackNode(String hostname,
                           NodeType type,
                           IpAddress managementIp,
-                          IpAddress dataIp,
+                          Optional<IpAddress> dataIp,
                           DeviceId integrationBridge,
                           Optional<DeviceId> routerBridge,
                           Optional<String> uplink,
                           Optional<IpAddress> routerController,
+                          Optional<String> vlanPort,
                           NodeState state) {
         this.hostname = hostname;
         this.type = type;
@@ -67,6 +69,7 @@
         this.routerBridge = routerBridge;
         this.uplink = uplink;
         this.routerController = routerController;
+        this.vlanPort = vlanPort;
         this.state = state;
     }
 
@@ -86,6 +89,7 @@
                 node.routerBridge,
                 node.uplink,
                 node.routerController,
+                node.vlanPort,
                 state);
     }
 
@@ -119,9 +123,9 @@
     /**
      * Returns the data network IP address of the node.
      *
-     * @return data network ip address
+     * @return data network ip address; or empty value
      */
-    public IpAddress dataIp() {
+    public Optional<IpAddress> dataIp() {
         return dataIp;
     }
 
@@ -166,6 +170,15 @@
     }
 
     /**
+     * Returns the vlan interface name.
+     *
+     * @return vlan interface name; or empty value
+     */
+    public Optional<String> vlanPort() {
+        return vlanPort;
+    }
+
+    /**
      * Returns the init state of the node.
      *
      * @return init state
@@ -212,7 +225,8 @@
                     Objects.equals(integrationBridge, that.integrationBridge) &&
                     Objects.equals(routerBridge, that.routerBridge) &&
                     Objects.equals(uplink, that.uplink) &&
-                    Objects.equals(routerController, that.routerController)) {
+                    Objects.equals(routerController, that.routerController) &&
+                    Objects.equals(vlanPort, that.vlanPort)) {
                 return true;
             }
         }
@@ -228,7 +242,8 @@
                 integrationBridge,
                 routerBridge,
                 uplink,
-                routerController);
+                routerController,
+                vlanPort);
     }
 
     @Override
@@ -242,6 +257,7 @@
                 .add("routerBridge", routerBridge)
                 .add("uplink", uplink)
                 .add("routerController", routerController)
+                .add("vlanport", vlanPort)
                 .add("state", state)
                 .toString();
     }
@@ -262,10 +278,11 @@
         private String hostname;
         private NodeType type;
         private IpAddress managementIp;
-        private IpAddress dataIp;
+        private Optional<IpAddress> dataIp = Optional.empty();
         private DeviceId integrationBridge;
         private Optional<DeviceId> routerBridge = Optional.empty();
         private Optional<String> uplink = Optional.empty();
+        private Optional<String> vlanPort = Optional.empty();
         // TODO remove this when we use single ONOS cluster for both openstackNode and vRouter
         private Optional<IpAddress> routerController = Optional.empty();
         private NodeState state = INIT;
@@ -282,6 +299,7 @@
             checkNotNull(routerBridge);
             checkNotNull(uplink);
             checkNotNull(routerController);
+            checkNotNull(vlanPort);
 
             if (type == NodeType.GATEWAY) {
                 checkArgument(routerBridge.isPresent());
@@ -297,6 +315,7 @@
                     routerBridge,
                     uplink,
                     routerController,
+                    vlanPort,
                     state);
         }
 
@@ -340,7 +359,7 @@
          * @return openstack node builder
          */
         public Builder dataIp(IpAddress dataIp) {
-            this.dataIp = dataIp;
+            this.dataIp = Optional.ofNullable(dataIp);
             return this;
         }
 
@@ -390,6 +409,17 @@
         }
 
         /**
+         * Returns node builder with the vlan interface name.
+         *
+         * @param vlanPort vlan interface name
+         * @return openstack node builder
+         */
+        public Builder vlanPort(String vlanPort) {
+            this.vlanPort = Optional.ofNullable(vlanPort);
+            return this;
+        }
+
+        /**
          * Returns node builder with the init state.
          *
          * @param state node init state
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java
index c20e678..0f264e9 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java
@@ -27,6 +27,7 @@
 import org.onosproject.net.config.Config;
 
 import static org.onosproject.net.config.Config.FieldPresence.MANDATORY;
+import static org.onosproject.net.config.Config.FieldPresence.OPTIONAL;
 import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
 
 /**
@@ -46,6 +47,7 @@
     private static final String UPLINK_PORT_NAME = "uplinkPort";
     // TODO remove this when vRouter supports multiple switches
     private static final String ROUTER_CONTROLLER = "routerController";
+    private static final String VLAN_PORT_NAME = "vlanPort";
 
     @Override
     public boolean isValid() {
@@ -57,6 +59,10 @@
         }
 
         for (JsonNode node : object.get(NODES)) {
+            if (get(node, DATA_IP) == null && get(node, VLAN_PORT_NAME) == null) {
+                final String msg = "There is neither tunnel interface nor vlan port";
+                throw new IllegalArgumentException(msg);
+            }
             ObjectNode osNode = (ObjectNode) node;
             result &= hasOnlyFields(osNode,
                     HOST_NAME,
@@ -66,14 +72,16 @@
                     INTEGRATION_BRIDGE,
                     ROUTER_BRIDGE,
                     UPLINK_PORT_NAME,
-                    ROUTER_CONTROLLER
+                    ROUTER_CONTROLLER,
+                    VLAN_PORT_NAME
             );
 
             result &= isString(osNode, HOST_NAME, MANDATORY);
             result &= isString(osNode, TYPE, MANDATORY);
             result &= isIpAddress(osNode, MANAGEMENT_IP, MANDATORY);
-            result &= result && isIpAddress(osNode, DATA_IP, MANDATORY);
             result &= isString(osNode, INTEGRATION_BRIDGE, MANDATORY);
+            result &= isString(osNode, VLAN_PORT_NAME, OPTIONAL);
+            result &= isIpAddress(osNode, DATA_IP, OPTIONAL);
 
             DeviceId.deviceId(osNode.get(INTEGRATION_BRIDGE).asText());
             NodeType.valueOf(osNode.get(TYPE).asText());
@@ -95,16 +103,22 @@
      */
     public Set<OpenstackNode> openstackNodes() {
         Set<OpenstackNode> nodes = Sets.newHashSet();
-
         for (JsonNode node : object.get(NODES)) {
             NodeType type = NodeType.valueOf(get(node, TYPE));
             OpenstackNode.Builder nodeBuilder = OpenstackNode.builder()
                     .integrationBridge(DeviceId.deviceId(get(node, INTEGRATION_BRIDGE)))
-                    .dataIp(IpAddress.valueOf(get(node, DATA_IP)))
                     .managementIp(IpAddress.valueOf(get(node, MANAGEMENT_IP)))
                     .type(type)
                     .hostname(get(node, HOST_NAME));
 
+            if (get(node, DATA_IP) != null) {
+                nodeBuilder.dataIp(IpAddress.valueOf(get(node, DATA_IP)));
+            }
+
+            if (get(node, VLAN_PORT_NAME) != null) {
+                nodeBuilder.vlanPort(get(node, VLAN_PORT_NAME));
+            }
+
             if (type.equals(GATEWAY)) {
                 nodeBuilder.routerBridge(DeviceId.deviceId(get(node, ROUTER_BRIDGE)))
                         .uplink(get(node, UPLINK_PORT_NAME))
@@ -116,6 +130,10 @@
     }
 
     private String get(JsonNode jsonNode, String path) {
-        return jsonNode.get(path).asText();
+        JsonNode jNode = jsonNode.get(path);
+        if (jNode == null || jNode.isMissingNode()) {
+            return null;
+        }
+        return jNode.asText();
     }
 }
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java
index 09b4f52..5276c58 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java
@@ -253,9 +253,17 @@
             connectOvsdb(node);
             return;
         }
+
         process(new OpenstackNodeEvent(DEVICE_CREATED, node));
 
-        createTunnelInterface(node);
+        if (node.dataIp().isPresent()) {
+            createTunnelInterface(node);
+        }
+
+        if (node.vlanPort().isPresent()) {
+            addVlanPort(node);
+        }
+
         if (node.type().equals(NodeType.GATEWAY)) {
             createPatchInterface(node);
             addUplink(node);
@@ -294,7 +302,7 @@
             log.warn("Failed to get node for {}", deviceId);
             return Optional.empty();
         }
-        return Optional.of(node.dataIp());
+        return node.dataIp();
     }
 
     @Override
@@ -348,7 +356,11 @@
             return INIT;
         }
 
-        if (!isIfaceCreated(node.ovsdbId(), DEFAULT_TUNNEL)) {
+        if (node.dataIp().isPresent() && !isIfaceCreated(node.ovsdbId(), DEFAULT_TUNNEL)) {
+            return DEVICE_CREATED;
+        }
+
+        if (node.vlanPort().isPresent() && !isIfaceCreated(node.ovsdbId(), node.vlanPort().get())) {
             return DEVICE_CREATED;
         }
 
@@ -491,6 +503,22 @@
                              node.uplink().get());
     }
 
+    private void addVlanPort(OpenstackNode node) {
+        if (isIfaceCreated(node.ovsdbId(), node.vlanPort().get())) {
+            return;
+        }
+
+        Device device = deviceService.getDevice(node.ovsdbId());
+        if (device == null || !device.is(BridgeConfig.class)) {
+            log.error("Failed to add port {} on {}", node.vlanPort().get(), node.ovsdbId());
+            return;
+        }
+
+        BridgeConfig bridgeConfig =  device.as(BridgeConfig.class);
+        bridgeConfig.addPort(BridgeName.bridgeName(INTEGRATION_BRIDGE),
+                node.vlanPort().get());
+    }
+
     private boolean isOvsdbConnected(OpenstackNode node) {
         OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
         OvsdbClientService client = ovsdbController.getOvsdbClient(ovsdb);
@@ -504,7 +532,9 @@
     }
 
     private Set<String> systemIfaces(OpenstackNode node) {
-        Set<String> ifaces = Sets.newHashSet(DEFAULT_TUNNEL);
+        Set<String> ifaces = Sets.newHashSet();
+        node.dataIp().ifPresent(ip -> ifaces.add(DEFAULT_TUNNEL));
+        node.vlanPort().ifPresent(p -> ifaces.add(p));
         if (node.type().equals(NodeType.GATEWAY)) {
             ifaces.add(PATCH_INTG_BRIDGE);
             ifaces.add(PATCH_ROUT_BRIDGE);
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java
index 84803c1..4d54abd 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java
@@ -74,7 +74,8 @@
                     deviceService.isAvailable(device.id()),
                     device.annotations());
 
-            print(getPortState(deviceService, node.intBridge(), DEFAULT_TUNNEL));
+            node.dataIp().ifPresent(ip -> print(getPortState(deviceService, node.intBridge(), DEFAULT_TUNNEL)));
+            node.vlanPort().ifPresent(p -> print(getPortState(deviceService, node.intBridge(), p)));
         } else {
             print("%s %s=%s is not available",
                     MSG_NO,
diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
index 2649b05..a20d278 100644
--- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
+++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
@@ -44,11 +44,13 @@
             print("%s", json(nodes));
         } else {
             for (OpenstackNode node : nodes) {
-                print("hostname=%s, type=%s, managementIp=%s, dataIp=%s, intBridge=%s, routerBridge=%s init=%s",
+                print("hostname=%s, type=%s, managementIp=%s, dataIp=%s, vlanPort=%s," +
+                        "intBridge=%s, routerBridge=%s init=%s",
                         node.hostname(),
                         node.type(),
                         node.managementIp(),
                         node.dataIp(),
+                        node.vlanPort(),
                         node.intBridge(),
                         node.routerBridge(),
                         node.state());
@@ -66,6 +68,7 @@
                     .put("type", node.type().name())
                     .put("managementIp", node.managementIp().toString())
                     .put("dataIp", node.dataIp().toString())
+                    .put("vlanPort", node.vlanPort().toString())
                     .put("intBridge", node.intBridge().toString())
                     .put("routerBridge", node.routerBridge().toString())
                     .put("state", node.state().name()));