[ONOS-6632] Add REST APIs for configuring compute and gateway nodes.

 - network configuration is not working any more.

Change-Id: I24e4d8b7b8cc33d839328887b9c6190d714baeba
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java
new file mode 100644
index 0000000..6a27c0e
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java
@@ -0,0 +1,146 @@
+/*
+ * 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.openstacknode.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.Device;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.openstacknode.api.Constants.*;
+import static org.onosproject.openstacknode.api.OpenstackNode.NetworkMode.VLAN;
+import static org.onosproject.openstacknode.api.OpenstackNode.NetworkMode.VXLAN;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+
+/**
+ * Checks detailed node init state.
+ */
+@Command(scope = "onos", name = "openstack-node-check",
+        description = "Shows detailed node init state")
+public class OpenstackNodeCheckCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "hostname", description = "Hostname",
+            required = true, multiValued = false)
+    private String hostname = null;
+
+    private static final String MSG_OK = "OK";
+    private static final String MSG_NO = "NO";
+    private static final String BUCKET_FORMAT =
+            "   bucket=%s, bytes=%s, packets=%s, actions=%s";
+
+    @Override
+    protected void execute() {
+        OpenstackNodeService osNodeService = AbstractShellCommand.get(OpenstackNodeService.class);
+        DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);
+        GroupService groupService = AbstractShellCommand.get(GroupService.class);
+
+        OpenstackNode osNode = osNodeService.node(hostname);
+        if (osNode == null) {
+            print("Cannot find %s from registered nodes", hostname);
+            return;
+        }
+
+        print("[Integration Bridge Status]");
+        Device device = deviceService.getDevice(osNode.intgBridge());
+        if (device != null) {
+            print("%s %s=%s available=%s %s",
+                    deviceService.isAvailable(device.id()) ? MSG_OK : MSG_NO,
+                    INTEGRATION_BRIDGE,
+                    device.id(),
+                    deviceService.isAvailable(device.id()),
+                    device.annotations());
+            if (osNode.dataIp() != null) {
+                printPortState(deviceService, osNode.intgBridge(), DEFAULT_TUNNEL);
+            }
+            if (osNode.vlanIntf() != null) {
+                printPortState(deviceService, osNode.intgBridge(), osNode.vlanIntf());
+            }
+            printGatewayGroupState(osNodeService, groupService, osNode);
+        } else {
+            print("%s %s=%s is not available",
+                    MSG_NO,
+                    INTEGRATION_BRIDGE,
+                    osNode.intgBridge());
+        }
+    }
+
+    private void printPortState(DeviceService deviceService, DeviceId deviceId, String portName) {
+        Port port = deviceService.getPorts(deviceId).stream()
+                .filter(p -> p.annotations().value(PORT_NAME).equals(portName) &&
+                        p.isEnabled())
+                .findAny().orElse(null);
+
+        if (port != null) {
+            print("%s %s portNum=%s enabled=%s %s",
+                    port.isEnabled() ? MSG_OK : MSG_NO,
+                    portName,
+                    port.number(),
+                    port.isEnabled() ? Boolean.TRUE : Boolean.FALSE,
+                    port.annotations());
+        } else {
+            print("%s %s does not exist", MSG_NO, portName);
+        }
+    }
+
+    private void printGatewayGroupState(OpenstackNodeService osNodeService,
+                                        GroupService groupService, OpenstackNode osNode) {
+        if (osNode.type() == GATEWAY) {
+            return;
+        }
+        if (osNodeService.completeNodes(GATEWAY).isEmpty()) {
+            print("N/A No complete state gateway nodes exist");
+            return;
+        }
+        if (osNode.dataIp() != null) {
+            Group osGroup = groupService.getGroup(osNode.intgBridge(),
+                    osNode.gatewayGroupKey(VXLAN));
+            if (osGroup == null || osGroup.state() != Group.GroupState.ADDED) {
+                print("%s VXLAN gateway group does not exist", MSG_NO);
+            } else {
+                print("%s VXLAN group 0x%s added", MSG_OK, Integer.toHexString(osGroup.id().id()));
+                int i = 0;
+                for (GroupBucket bucket : osGroup.buckets().buckets()) {
+                    print(BUCKET_FORMAT, ++i, bucket.bytes(), bucket.packets(),
+                            bucket.treatment().allInstructions());
+                }
+            }
+        }
+        if (osNode.vlanIntf() != null) {
+            Group osGroup = groupService.getGroup(osNode.intgBridge(),
+                    osNode.gatewayGroupKey(VLAN));
+            if (osGroup == null || osGroup.state() != Group.GroupState.ADDED) {
+                print("\n%s VLAN gateway group does not exist", MSG_NO);
+            } else {
+                print("\n%s VLAN group 0x%s added", MSG_OK, Integer.toHexString(osGroup.id().id()));
+                int i = 0;
+                for (GroupBucket bucket : osGroup.buckets().buckets()) {
+                    print(BUCKET_FORMAT, ++i, bucket.bytes(), bucket.packets(),
+                            bucket.treatment().allInstructions());
+                }
+            }
+        }
+    }
+}
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeInitCommand.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeInitCommand.java
new file mode 100644
index 0000000..34f4a99
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeInitCommand.java
@@ -0,0 +1,91 @@
+/*
+ * 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.openstacknode.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Initializes nodes for OpenStack node service.
+ */
+@Command(scope = "onos", name = "openstack-node-init",
+        description = "Initializes nodes for OpenStack node service")
+public class OpenstackNodeInitCommand extends AbstractShellCommand {
+
+    @Option(name = "-a", aliases = "--all", description = "Apply this command to all nodes",
+            required = false, multiValued = false)
+    private boolean isAll = false;
+
+    @Option(name = "-i", aliases = "--incomplete",
+            description = "Apply this command to incomplete nodes",
+            required = false, multiValued = false)
+    private boolean isIncomplete = false;
+
+    @Argument(index = 0, name = "hostnames", description = "Hostname(s) to apply this command",
+            required = false, multiValued = true)
+    private String[] hostnames = null;
+
+    @Override
+    protected void execute() {
+        OpenstackNodeService osNodeService =
+                AbstractShellCommand.get(OpenstackNodeService.class);
+        OpenstackNodeAdminService osNodeAdminService =
+                AbstractShellCommand.get(OpenstackNodeAdminService.class);
+
+        if ((!isAll && !isIncomplete && hostnames == null) ||
+                (isAll && isIncomplete) ||
+                (isIncomplete && hostnames != null) ||
+                (hostnames != null && isAll)) {
+            print("Please specify one of hostname, --all, and --incomplete options.");
+            return;
+        }
+
+        if (isAll) {
+            List<String> osNodes = osNodeService.nodes().stream()
+                    .map(OpenstackNode::hostname)
+                    .collect(Collectors.toList());
+            hostnames = osNodes.toArray(new String[osNodes.size()]);
+        } else if (isIncomplete) {
+            List<String> osNodes = osNodeService.nodes().stream()
+                    .filter(osNode -> osNode.state() != NodeState.COMPLETE)
+                    .map(OpenstackNode::hostname)
+                    .collect(Collectors.toList());
+            hostnames = osNodes.toArray(new String[osNodes.size()]);
+        }
+
+        for (String hostname : hostnames) {
+            OpenstackNode osNode = osNodeService.node(hostname);
+            if (osNode == null) {
+                print("Unable to find %s", hostname);
+                continue;
+            }
+            print("Initializing %s", hostname);
+            OpenstackNode updated = osNode.updateState(NodeState.INIT);
+            osNodeAdminService.updateNode(updated);
+        }
+        print("Done.");
+    }
+}
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
new file mode 100644
index 0000000..ceed2a7
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
@@ -0,0 +1,84 @@
+/*
+ * 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.openstacknode.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Lists all nodes registered to the service.
+ */
+@Command(scope = "onos", name = "openstack-nodes",
+        description = "Lists all nodes registered in OpenStack node service")
+public class OpenstackNodeListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%-20s%-15s%-24s%-24s%-20s%-20s%-15s%s";
+
+    @Override
+    protected void execute() {
+        OpenstackNodeService osNodeService = AbstractShellCommand.get(OpenstackNodeService.class);
+        List<OpenstackNode> osNodes = Lists.newArrayList(osNodeService.nodes());
+        osNodes.sort(Comparator.comparing(OpenstackNode::hostname));
+
+        if (outputJson()) {
+            print("%s", json(osNodes));
+        } else {
+            print(FORMAT, "Hostname", "Type", "Integration Bridge", "Router Bridge",
+                    "Management IP", "Data IP", "VLAN Intf", "State");
+            for (OpenstackNode osNode : osNodes) {
+                print(FORMAT,
+                        osNode.hostname(),
+                        osNode.type(),
+                        osNode.intgBridge(),
+                        osNode.routerBridge() != null ? osNode.routerBridge() : "",
+                        osNode.managementIp(),
+                        osNode.dataIp() != null ? osNode.dataIp() : "",
+                        osNode.vlanIntf() != null ? osNode.vlanIntf() : "",
+                        osNode.state());
+            }
+            print("Total %s nodes", osNodeService.nodes().size());
+        }
+    }
+
+    private JsonNode json(List<OpenstackNode> osNodes) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+        for (OpenstackNode osNode : osNodes) {
+            result.add(mapper.createObjectNode()
+                    .put("hostname", osNode.hostname())
+                    .put("type", osNode.type().name())
+                    .put("integrationBridge", osNode.intgBridge().toString())
+                    .put("routerBridge", osNode.routerBridge().toString())
+                    .put("managementIp", osNode.managementIp().toString())
+                    .put("dataIp", osNode.dataIp().toString())
+                    .put("vlanIntf", osNode.vlanIntf())
+                    .put("tunnelPortNum", osNode.tunnelPortNum().toString())
+                    .put("vlanPortNum", osNode.vlanPortNum().toString())
+                    .put("state", osNode.state().name()));
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/package-info.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/package-info.java
new file mode 100644
index 0000000..6a6e636
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Console commands to manage OpenStack nodes.
+ */
+package org.onosproject.openstacknode.cli;
\ No newline at end of file
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
new file mode 100644
index 0000000..7313667
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onosproject.net.AnnotationKeys.PORT_MAC;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.openstacknode.api.Constants.DEFAULT_TUNNEL;
+import static org.onosproject.openstacknode.api.Constants.PATCH_INTG_BRIDGE;
+
+/**
+ * Representation of a openstack node.
+ */
+public class DefaultOpenstackNode implements OpenstackNode {
+
+    private final String hostname;
+    private final NodeType type;
+    private final DeviceId intgBridge;
+    private final DeviceId routerBridge;
+    private final IpAddress managementIp;
+    private final IpAddress dataIp;
+    private final String vlanIntf;
+    private final NodeState state;
+
+    protected DefaultOpenstackNode(String hostname,
+                                 NodeType type,
+                                 DeviceId intgBridge,
+                                 DeviceId routerBridge,
+                                 IpAddress managementIp,
+                                 IpAddress dataIp,
+                                 String vlanIntf,
+                                 NodeState state) {
+        this.hostname = hostname;
+        this.type = type;
+        this.intgBridge = intgBridge;
+        this.routerBridge = routerBridge;
+        this.managementIp = managementIp;
+        this.dataIp = dataIp;
+        this.vlanIntf = vlanIntf;
+        this.state = state;
+    }
+
+    @Override
+    public String hostname() {
+        return hostname;
+    }
+
+    @Override
+    public NodeType type() {
+        return type;
+    }
+
+    @Override
+    public DeviceId ovsdb() {
+        return DeviceId.deviceId("ovsdb:" + managementIp().toString());
+    }
+
+    @Override
+    public DeviceId intgBridge() {
+        return intgBridge;
+    }
+
+    @Override
+    public DeviceId routerBridge() {
+        return routerBridge;
+    }
+
+    @Override
+    public IpAddress managementIp() {
+        return managementIp;
+    }
+
+    @Override
+    public IpAddress dataIp() {
+        return dataIp;
+    }
+
+    @Override
+    public String vlanIntf() {
+        return vlanIntf;
+    }
+
+    @Override
+    public NodeState state() {
+        return state;
+    }
+
+    @Override
+    public GroupKey gatewayGroupKey(NetworkMode mode) {
+        return new DefaultGroupKey(intgBridge.toString().concat(mode.name()).getBytes());
+    }
+
+    @Override
+    public PortNumber tunnelPortNum() {
+        if (dataIp == null) {
+            return null;
+        }
+        DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
+        Port port = deviceService.getPorts(intgBridge).stream()
+                .filter(p -> p.isEnabled() &&
+                        Objects.equals(p.annotations().value(PORT_NAME), DEFAULT_TUNNEL))
+                .findAny().orElse(null);
+        return port != null ? port.number() : null;
+    }
+
+    @Override
+    public PortNumber vlanPortNum() {
+        if (vlanIntf == null) {
+            return null;
+        }
+        DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
+        Port port = deviceService.getPorts(intgBridge).stream()
+                .filter(p -> p.isEnabled() &&
+                        Objects.equals(p.annotations().value(PORT_NAME), vlanIntf))
+                .findAny().orElse(null);
+        return port != null ? port.number() : null;
+    }
+
+    @Override
+    public PortNumber patchPortNum() {
+        if (type == NodeType.COMPUTE) {
+            return null;
+        }
+        DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
+        Port port = deviceService.getPorts(intgBridge).stream()
+                .filter(p -> p.isEnabled() &&
+                        Objects.equals(p.annotations().value(PORT_NAME), PATCH_INTG_BRIDGE))
+                .findAny().orElse(null);
+        return port != null ? port.number() : null;
+    }
+
+    @Override
+    public MacAddress vlanPortMac() {
+        if (vlanIntf == null) {
+            return null;
+        }
+        DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
+        Port port = deviceService.getPorts(intgBridge).stream()
+                .filter(p -> p.annotations().value(PORT_NAME).equals(vlanIntf))
+                .findAny().orElse(null);
+        return port != null ? MacAddress.valueOf(port.annotations().value(PORT_MAC)) : null;
+    }
+
+    @Override
+    public GroupId gatewayGroupId(NetworkMode mode) {
+        return new GroupId(intgBridge.toString().concat(mode.name()).hashCode());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof DefaultOpenstackNode) {
+            DefaultOpenstackNode that = (DefaultOpenstackNode) obj;
+            if (Objects.equals(hostname, that.hostname) &&
+                    Objects.equals(type, that.type) &&
+                    Objects.equals(intgBridge, that.intgBridge) &&
+                    Objects.equals(routerBridge, that.routerBridge) &&
+                    Objects.equals(managementIp, that.managementIp) &&
+                    Objects.equals(dataIp, that.dataIp) &&
+                    Objects.equals(vlanIntf, that.vlanIntf)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(hostname,
+                type,
+                intgBridge,
+                routerBridge,
+                managementIp,
+                dataIp,
+                vlanIntf);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("hostname", hostname)
+                .add("type", type)
+                .add("integrationBridge", intgBridge)
+                .add("routerBridge", routerBridge)
+                .add("managementIp", managementIp)
+                .add("dataIp", dataIp)
+                .add("vlanIntf", vlanIntf)
+                .add("state", state)
+                .toString();
+    }
+
+    @Override
+    public OpenstackNode updateState(NodeState newState) {
+        return new Builder()
+                .type(type)
+                .hostname(hostname)
+                .intgBridge(intgBridge)
+                .routerBridge(routerBridge)
+                .managementIp(managementIp)
+                .dataIp(dataIp)
+                .vlanIntf(vlanIntf)
+                .state(newState)
+                .build();
+    }
+
+    /**
+     * Returns new builder instance.
+     *
+     * @return openstack node builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Returns new builder instance with the given node as a default value.
+     *
+     * @param osNode openstack node
+     * @return openstack node builder
+     */
+    public static Builder from(OpenstackNode osNode) {
+        return new Builder()
+                .hostname(osNode.hostname())
+                .type(osNode.type())
+                .intgBridge(osNode.intgBridge())
+                .routerBridge(osNode.routerBridge())
+                .managementIp(osNode.managementIp())
+                .dataIp(osNode.dataIp())
+                .vlanIntf(osNode.vlanIntf())
+                .state(osNode.state());
+    }
+
+    public static final class Builder implements OpenstackNode.Builder {
+
+        private String hostname;
+        private NodeType type;
+        private DeviceId intgBridge;
+        private DeviceId routerBridge;
+        private IpAddress managementIp;
+        private IpAddress dataIp;
+        private String vlanIntf;
+        private NodeState state;
+
+        private Builder() {
+        }
+
+        @Override
+        public DefaultOpenstackNode build() {
+            checkArgument(hostname != null, "Node hostname cannot be null");
+            checkArgument(type != null, "Node type cannot be null");
+            checkArgument(intgBridge != null, "Node integration bridge cannot be null");
+            checkArgument(managementIp != null, "Node management IP cannot be null");
+            checkArgument(state != null, "Node state cannot be null");
+
+            if (type == NodeType.GATEWAY && routerBridge == null) {
+                throw new IllegalArgumentException("Router bridge is required for gateway node");
+            }
+            if (dataIp == null && Strings.isNullOrEmpty(vlanIntf)) {
+                throw new IllegalArgumentException("Either data IP or VLAN interface is required");
+            }
+
+            return new DefaultOpenstackNode(hostname,
+                    type,
+                    intgBridge,
+                    routerBridge,
+                    managementIp,
+                    dataIp,
+                    vlanIntf,
+                    state);
+        }
+
+        @Override
+        public Builder hostname(String hostname) {
+            if (!Strings.isNullOrEmpty(hostname)) {
+                this.hostname = hostname;
+            }
+            return this;
+        }
+
+        @Override
+        public Builder type(NodeType type) {
+            this.type = type;
+            return this;
+        }
+
+        @Override
+        public Builder intgBridge(DeviceId intgBridge) {
+            this.intgBridge = intgBridge;
+            return this;
+        }
+
+        @Override
+        public Builder routerBridge(DeviceId routerBridge) {
+            this.routerBridge = routerBridge;
+            return this;
+        }
+
+        @Override
+        public Builder managementIp(IpAddress managementIp) {
+            this.managementIp = managementIp;
+            return this;
+        }
+
+        @Override
+        public Builder dataIp(IpAddress dataIp) {
+            this.dataIp = dataIp;
+            return this;
+        }
+
+        @Override
+        public Builder vlanIntf(String vlanIntf) {
+            this.vlanIntf = vlanIntf;
+            return this;
+        }
+
+        @Override
+        public Builder state(NodeState state) {
+            this.state = state;
+            return this;
+        }
+    }
+}
+
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
new file mode 100644
index 0000000..d3597ee
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
@@ -0,0 +1,804 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+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.onlab.packet.IpAddress;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.behaviour.BridgeConfig;
+import org.onosproject.net.behaviour.BridgeDescription;
+import org.onosproject.net.behaviour.BridgeName;
+import org.onosproject.net.behaviour.ControllerInfo;
+import org.onosproject.net.behaviour.DefaultBridgeDescription;
+import org.onosproject.net.behaviour.DefaultPatchDescription;
+import org.onosproject.net.behaviour.DefaultTunnelDescription;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.behaviour.InterfaceConfig;
+import org.onosproject.net.behaviour.PatchDescription;
+import org.onosproject.net.behaviour.TunnelDescription;
+import org.onosproject.net.behaviour.TunnelEndPoints;
+import org.onosproject.net.behaviour.TunnelKeys;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNode.NetworkMode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeHandler;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.ovsdb.controller.OvsdbClientService;
+import org.onosproject.ovsdb.controller.OvsdbController;
+import org.onosproject.ovsdb.controller.OvsdbNodeId;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.packet.TpPort.tpPort;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.onosproject.net.group.DefaultGroupBucket.createSelectGroupBucket;
+import static org.onosproject.openstacknode.api.Constants.*;
+import static org.onosproject.openstacknode.api.Constants.PATCH_INTG_BRIDGE;
+import static org.onosproject.openstacknode.api.NodeState.*;
+import static org.onosproject.openstacknode.api.OpenstackNode.NetworkMode.VLAN;
+import static org.onosproject.openstacknode.api.OpenstackNode.NetworkMode.VXLAN;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.onosproject.openstacknode.api.OpenstackNodeService.APP_ID;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Service bootstraps openstack node based on its type.
+ */
+@Component(immediate = true)
+public class DefaultOpenstackNodeHandler implements OpenstackNodeHandler {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String OVSDB_PORT = "ovsdbPortNum";
+    private static final int DEFAULT_OVSDB_PORT = 6640;
+    private static final String DEFAULT_OF_PROTO = "tcp";
+    private static final int DEFAULT_OFPORT = 6653;
+    private static final int DPID_BEGIN = 3;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceAdminService deviceAdminService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OvsdbController ovsdbController;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GroupService groupService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeAdminService osNodeAdminService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService componentConfigService;
+
+    @Property(name = OVSDB_PORT, intValue = DEFAULT_OVSDB_PORT,
+            label = "OVSDB server listen port")
+    private int ovsdbPort = DEFAULT_OVSDB_PORT;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+    private final DeviceListener ovsdbListener = new InternalOvsdbListener();
+    private final DeviceListener bridgeListener = new InternalBridgeListener();
+    private final GroupListener groupListener = new InternalGroupListener();
+    private final OpenstackNodeListener osNodeListener = new InternalOpenstackNodeListener();
+
+    private ApplicationId appId;
+    private NodeId localNode;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.getAppId(APP_ID);
+        localNode = clusterService.getLocalNode().id();
+
+        componentConfigService.registerProperties(getClass());
+        leadershipService.runForLeadership(appId.name());
+        groupService.addListener(groupListener);
+        deviceService.addListener(ovsdbListener);
+        deviceService.addListener(bridgeListener);
+        osNodeService.addListener(osNodeListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osNodeService.removeListener(osNodeListener);
+        deviceService.removeListener(bridgeListener);
+        deviceService.removeListener(ovsdbListener);
+        groupService.removeListener(groupListener);
+        componentConfigService.unregisterProperties(getClass(), false);
+        leadershipService.withdraw(appId.name());
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        int updatedOvsdbPort = Tools.getIntegerProperty(properties, OVSDB_PORT);
+        if (!Objects.equals(updatedOvsdbPort, ovsdbPort)) {
+            ovsdbPort = updatedOvsdbPort;
+        }
+
+        log.info("Modified");
+    }
+
+    @Override
+    public void processInitState(OpenstackNode osNode) {
+        if (!isOvsdbConnected(osNode)) {
+            ovsdbController.connect(osNode.managementIp(), tpPort(ovsdbPort));
+            return;
+        }
+        if (!deviceService.isAvailable(osNode.intgBridge())) {
+            createBridge(osNode, INTEGRATION_BRIDGE, osNode.intgBridge());
+        }
+        if (osNode.type() == GATEWAY &&
+                !isBridgeCreated(osNode.ovsdb(), ROUTER_BRIDGE)) {
+            createBridge(osNode, ROUTER_BRIDGE, osNode.routerBridge());
+        }
+    }
+
+    @Override
+    public void processDeviceCreatedState(OpenstackNode osNode) {
+        if (!isOvsdbConnected(osNode)) {
+            ovsdbController.connect(osNode.managementIp(), tpPort(ovsdbPort));
+            return;
+        }
+        if (osNode.type() == GATEWAY && (
+                !isIntfEnabled(osNode, PATCH_INTG_BRIDGE) ||
+                        !isIntfCreated(osNode, PATCH_ROUT_BRIDGE)
+        )) {
+            createPatchInterface(osNode);
+        }
+        if (osNode.dataIp() != null &&
+                !isIntfEnabled(osNode, DEFAULT_TUNNEL)) {
+            createTunnelInterface(osNode);
+        }
+        if (osNode.vlanIntf() != null &&
+                !isIntfEnabled(osNode, osNode.vlanIntf())) {
+            addSystemInterface(osNode, INTEGRATION_BRIDGE, osNode.vlanIntf());
+        }
+    }
+
+    @Override
+    public void processPortCreatedState(OpenstackNode osNode) {
+        switch (osNode.type()) {
+            case COMPUTE:
+                if (osNode.dataIp() != null) {
+                    addOrUpdateGatewayGroup(osNode,
+                            osNodeService.completeNodes(GATEWAY),
+                            VXLAN);
+                }
+                if (osNode.vlanIntf() != null) {
+                    addOrUpdateGatewayGroup(osNode,
+                            osNodeService.completeNodes(GATEWAY),
+                            VLAN);
+                }
+                break;
+            case GATEWAY:
+                Set<OpenstackNode> gateways =
+                        Sets.newHashSet(osNodeService.completeNodes(GATEWAY));
+                gateways.add(osNode);
+                osNodeService.completeNodes(COMPUTE).forEach(n -> {
+                    if (n.dataIp() != null) {
+                        addOrUpdateGatewayGroup(n, gateways, VXLAN);
+                    }
+                    if (n.vlanIntf() != null) {
+                        addOrUpdateGatewayGroup(n, gateways, VLAN);
+                    }
+                });
+                break;
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public void processCompleteState(OpenstackNode osNode) {
+        OvsdbClientService ovsdbClient = ovsdbController.getOvsdbClient(
+                new OvsdbNodeId(osNode.managementIp(), DEFAULT_OVSDB_PORT));
+        if (ovsdbClient != null && ovsdbClient.isConnected()) {
+            ovsdbClient.disconnect();
+        }
+    }
+
+    @Override
+    public void processIncompleteState(OpenstackNode osNode) {
+        if (osNode.type() == COMPUTE) {
+            if (osNode.dataIp() != null) {
+                groupService.removeGroup(osNode.intgBridge(), osNode.gatewayGroupKey(VXLAN), appId);
+            }
+            if (osNode.vlanIntf() != null) {
+                groupService.removeGroup(osNode.intgBridge(), osNode.gatewayGroupKey(VLAN), appId);
+            }
+        }
+        if (osNode.type() == GATEWAY) {
+            osNodeService.completeNodes(COMPUTE).forEach(n -> {
+                if (n.dataIp() != null) {
+                    addOrUpdateGatewayGroup(n,
+                            osNodeService.completeNodes(GATEWAY),
+                            VXLAN);
+                }
+                if (n.vlanIntf() != null) {
+                    addOrUpdateGatewayGroup(n,
+                            osNodeService.completeNodes(GATEWAY),
+                            VLAN);
+                }
+            });
+        }
+    }
+
+    private boolean isOvsdbConnected(OpenstackNode osNode) {
+        OvsdbNodeId ovsdb = new OvsdbNodeId(osNode.managementIp(), ovsdbPort);
+        OvsdbClientService client = ovsdbController.getOvsdbClient(ovsdb);
+        return deviceService.isAvailable(osNode.ovsdb()) &&
+                client != null &&
+                client.isConnected();
+    }
+
+    private void createBridge(OpenstackNode osNode, String bridgeName, DeviceId deviceId) {
+        Device device = deviceService.getDevice(osNode.ovsdb());
+        if (device == null || !device.is(BridgeConfig.class)) {
+            log.error("Failed to create integration bridge on {}", osNode.ovsdb());
+            return;
+        }
+
+        // TODO fix this when we use single ONOS cluster for both openstackNode and vRouter
+        Set<IpAddress> controllerIps;
+        if (bridgeName.equals(ROUTER_BRIDGE)) {
+            // TODO checks if empty controller does not break anything
+            controllerIps = ImmutableSet.of();
+        } else {
+            controllerIps = clusterService.getNodes().stream()
+                    .map(ControllerNode::ip)
+                    .collect(Collectors.toSet());
+        }
+
+        List<ControllerInfo> controllers = controllerIps.stream()
+                .map(ip -> new ControllerInfo(ip, DEFAULT_OFPORT, DEFAULT_OF_PROTO))
+                .collect(Collectors.toList());
+
+        String dpid = deviceId.toString().substring(DPID_BEGIN);
+        BridgeDescription bridgeDesc = DefaultBridgeDescription.builder()
+                .name(bridgeName)
+                .failMode(BridgeDescription.FailMode.SECURE)
+                .datapathId(dpid)
+                .disableInBand()
+                .controllers(controllers)
+                .build();
+
+        BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
+        bridgeConfig.addBridge(bridgeDesc);
+    }
+
+    private void addSystemInterface(OpenstackNode osNode, String bridgeName, String intfName) {
+        Device device = deviceService.getDevice(osNode.ovsdb());
+        if (device == null || !device.is(BridgeConfig.class)) {
+            return;
+        }
+        BridgeConfig bridgeConfig =  device.as(BridgeConfig.class);
+        bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
+    }
+
+    private void createTunnelInterface(OpenstackNode osNode) {
+        if (isIntfEnabled(osNode, DEFAULT_TUNNEL)) {
+            return;
+        }
+
+        Device device = deviceService.getDevice(osNode.ovsdb());
+        if (device == null || !device.is(InterfaceConfig.class)) {
+            log.error("Failed to create tunnel interface on {}", osNode.ovsdb());
+            return;
+        }
+
+        TunnelDescription tunnelDesc = DefaultTunnelDescription.builder()
+                .deviceId(INTEGRATION_BRIDGE)
+                .ifaceName(DEFAULT_TUNNEL)
+                .type(TunnelDescription.Type.VXLAN)
+                .remote(TunnelEndPoints.flowTunnelEndpoint())
+                .key(TunnelKeys.flowTunnelKey())
+                .build();
+
+        InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
+        ifaceConfig.addTunnelMode(DEFAULT_TUNNEL, tunnelDesc);
+    }
+
+    private void createPatchInterface(OpenstackNode osNode) {
+        checkArgument(osNode.type().equals(OpenstackNode.NodeType.GATEWAY));
+        if (isIntfEnabled(osNode, PATCH_INTG_BRIDGE) &&
+                isIntfCreated(osNode, PATCH_ROUT_BRIDGE)) {
+            return;
+        }
+
+        Device device = deviceService.getDevice(osNode.ovsdb());
+        if (device == null || !device.is(InterfaceConfig.class)) {
+            log.error("Failed to create patch interfaces on {}", osNode.hostname());
+            return;
+        }
+
+        PatchDescription patchIntg = DefaultPatchDescription.builder()
+                .deviceId(INTEGRATION_BRIDGE)
+                .ifaceName(PATCH_INTG_BRIDGE)
+                .peer(PATCH_ROUT_BRIDGE)
+                .build();
+
+        PatchDescription patchRout = DefaultPatchDescription.builder()
+                .deviceId(ROUTER_BRIDGE)
+                .ifaceName(PATCH_ROUT_BRIDGE)
+                .peer(PATCH_INTG_BRIDGE)
+                .build();
+
+        InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
+        ifaceConfig.addPatchMode(PATCH_INTG_BRIDGE, patchIntg);
+        ifaceConfig.addPatchMode(PATCH_ROUT_BRIDGE, patchRout);
+    }
+
+    private void addOrUpdateGatewayGroup(OpenstackNode osNode,
+                                         Set<OpenstackNode> gatewayNodes,
+                                         NetworkMode mode) {
+        GroupBuckets buckets = gatewayGroupBuckets(osNode, gatewayNodes, mode);
+        if (groupService.getGroup(osNode.intgBridge(), osNode.gatewayGroupKey(mode)) == null) {
+            GroupDescription groupDescription = new DefaultGroupDescription(
+                    osNode.intgBridge(),
+                    GroupDescription.Type.SELECT,
+                    buckets,
+                    osNode.gatewayGroupKey(mode),
+                    osNode.gatewayGroupId(mode).id(),
+                    appId);
+            groupService.addGroup(groupDescription);
+            log.debug("Created gateway group for {}", osNode.hostname());
+        } else {
+            groupService.setBucketsForGroup(
+                    osNode.intgBridge(),
+                    osNode.gatewayGroupKey(mode),
+                    buckets,
+                    osNode.gatewayGroupKey(mode),
+                    appId);
+            log.debug("Updated gateway group for {}", osNode.hostname());
+        }
+    }
+
+    private GroupBuckets gatewayGroupBuckets(OpenstackNode osNode,
+                                             Set<OpenstackNode> gatewayNodes,
+                                             NetworkMode mode) {
+        List<GroupBucket> bucketList = Lists.newArrayList();
+        switch (mode) {
+            case VXLAN:
+                gatewayNodes.stream().filter(n -> n.dataIp() != null).forEach(n -> {
+                    TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                            .extension(tunnelDstTreatment(osNode.intgBridge(),
+                                    n.dataIp()),
+                                    osNode.intgBridge())
+                            .setOutput(osNode.tunnelPortNum())
+                            .build();
+                    bucketList.add(createSelectGroupBucket(treatment));
+                });
+                return new GroupBuckets(bucketList);
+            case VLAN:
+                gatewayNodes.stream().filter(n -> n.vlanIntf() != null).forEach(n -> {
+                    TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                            .setEthDst(n.vlanPortMac())
+                            .setOutput(osNode.vlanPortNum())
+                            .build();
+                    bucketList.add(createSelectGroupBucket(treatment));
+                });
+                return new GroupBuckets(bucketList);
+            default:
+                return null;
+        }
+    }
+
+    private ExtensionTreatment tunnelDstTreatment(DeviceId deviceId, IpAddress remoteIp) {
+        Device device = deviceService.getDevice(deviceId);
+        if (device != null && !device.is(ExtensionTreatmentResolver.class)) {
+            log.error("The extension treatment is not supported");
+            return null;
+        }
+
+        ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
+        ExtensionTreatment treatment = resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+        try {
+            treatment.setPropertyValue("tunnelDst", remoteIp.getIp4Address());
+            return treatment;
+        } catch (ExtensionPropertyException e) {
+            log.warn("Failed to get tunnelDst extension treatment for {}", deviceId);
+            return null;
+        }
+    }
+
+    private boolean isBridgeCreated(DeviceId ovsdbId, String bridgeName) {
+        Device device = deviceService.getDevice(ovsdbId);
+        if (device == null || !deviceService.isAvailable(device.id()) ||
+                !device.is(BridgeConfig.class)) {
+            return false;
+        }
+        BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
+        return bridgeConfig.getBridges().stream()
+                .anyMatch(bridge -> bridge.name().equals(bridgeName));
+    }
+
+    private boolean isIntfEnabled(OpenstackNode osNode, String intf) {
+        if (!deviceService.isAvailable(osNode.intgBridge())) {
+            return false;
+        }
+        return deviceService.getPorts(osNode.intgBridge()).stream()
+                .anyMatch(port -> Objects.equals(
+                        port.annotations().value(PORT_NAME), intf) &&
+                        port.isEnabled());
+    }
+
+    private boolean isIntfCreated(OpenstackNode osNode, String intf) {
+        Device device = deviceService.getDevice(osNode.ovsdb());
+        if (device == null || !deviceService.isAvailable(osNode.ovsdb()) ||
+                !device.is(BridgeConfig.class)) {
+            return false;
+        }
+
+        BridgeConfig bridgeConfig =  device.as(BridgeConfig.class);
+        return bridgeConfig.getPorts().stream()
+                .anyMatch(port -> port.annotations().value(PORT_NAME).equals(intf));
+    }
+
+    private boolean isGroupCreated(OpenstackNode osNode) {
+        for (OpenstackNode gNode : osNodeService.completeNodes(GATEWAY)) {
+            if (!isGatewayBucketAdded(osNode, gNode)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isGatewayBucketAdded(OpenstackNode cNode, OpenstackNode gNode) {
+        if (cNode.dataIp() != null) {
+            Group osGroup = groupService.getGroup(cNode.intgBridge(),
+                    cNode.gatewayGroupKey(VXLAN));
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .extension(tunnelDstTreatment(gNode.intgBridge(),
+                            gNode.dataIp()),
+                            cNode.intgBridge())
+                    .setOutput(cNode.tunnelPortNum())
+                    .build();
+            GroupBucket bucket = createSelectGroupBucket(treatment);
+            if (osGroup == null || !osGroup.buckets().buckets().contains(bucket)) {
+                return false;
+            }
+        }
+        if (cNode.vlanIntf() != null) {
+            Group osGroup = groupService.getGroup(cNode.intgBridge(),
+                    cNode.gatewayGroupKey(VLAN));
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setEthDst(gNode.vlanPortMac())
+                    .setOutput(cNode.vlanPortNum())
+                    .build();
+            GroupBucket bucket = createSelectGroupBucket(treatment);
+            if (osGroup == null || !osGroup.buckets().buckets().contains(bucket)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isCurrentStateDone(OpenstackNode osNode) {
+        switch (osNode.state()) {
+            case INIT:
+                if (!deviceService.isAvailable(osNode.intgBridge())) {
+                    return false;
+                }
+                if (osNode.type() == GATEWAY &&
+                        !isBridgeCreated(osNode.ovsdb(), ROUTER_BRIDGE)) {
+                    return false;
+                }
+                return true;
+            case DEVICE_CREATED:
+                if (osNode.dataIp() != null &&
+                        !isIntfEnabled(osNode, DEFAULT_TUNNEL)) {
+                    return false;
+                }
+                if (osNode.vlanIntf() != null &&
+                        !isIntfEnabled(osNode, osNode.vlanIntf())) {
+                    return false;
+                }
+                if (osNode.type() == GATEWAY && (
+                        !isIntfEnabled(osNode, PATCH_INTG_BRIDGE) ||
+                        !isIntfCreated(osNode, PATCH_ROUT_BRIDGE))) {
+                    return false;
+                }
+                return true;
+            case PORT_CREATED:
+                if (osNode.type() == COMPUTE) {
+                    return isGroupCreated(osNode);
+                } else {
+                    for (OpenstackNode cNode : osNodeService.completeNodes(COMPUTE)) {
+                        if (!isGatewayBucketAdded(cNode, osNode)) {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+            case COMPLETE:
+                return false;
+            case INCOMPLETE:
+                // always return false
+                // run init CLI to re-trigger node bootstrap
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    private void setState(OpenstackNode osNode, NodeState newState) {
+        if (osNode.state() == newState) {
+            return;
+        }
+        OpenstackNode updated = osNode.updateState(newState);
+        osNodeAdminService.updateNode(updated);
+        log.info("Changed {} state: {}", osNode.hostname(), newState);
+    }
+
+    private void bootstrapNode(OpenstackNode osNode) {
+        if (isCurrentStateDone(osNode)) {
+            setState(osNode, osNode.state().nextState());
+        } else {
+            log.trace("Processing {} state for {}", osNode.state(), osNode.hostname());
+            osNode.state().process(this, osNode);
+        }
+    }
+
+    private class InternalOvsdbListener implements DeviceListener {
+
+        @Override
+        public boolean isRelevant(DeviceEvent event) {
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNode, leader) &&
+                    event.subject().type() == Device.Type.CONTROLLER &&
+                    osNodeService.node(event.subject().id()) != null;
+        }
+
+        @Override
+        public void event(DeviceEvent event) {
+            Device device = event.subject();
+            OpenstackNode osNode = osNodeService.node(device.id());
+
+            switch (event.type()) {
+                case DEVICE_AVAILABILITY_CHANGED:
+                case DEVICE_ADDED:
+                    eventExecutor.execute(() -> {
+                        if (deviceService.isAvailable(device.id())) {
+                            log.debug("OVSDB {} detected", device.id());
+                            bootstrapNode(osNode);
+                        } else if (osNode.state() == COMPLETE) {
+                            log.debug("Removing OVSDB {}", device.id());
+                            deviceAdminService.removeDevice(device.id());
+                        }
+                    });
+                    break;
+                case PORT_ADDED:
+                case PORT_REMOVED:
+                case DEVICE_REMOVED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+
+    private class InternalBridgeListener implements DeviceListener {
+
+        @Override
+        public boolean isRelevant(DeviceEvent event) {
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNode, leader) &&
+                    event.subject().type() == Device.Type.SWITCH &&
+                    osNodeService.node(event.subject().id()) != null;
+        }
+
+        @Override
+        public void event(DeviceEvent event) {
+            Device device = event.subject();
+            OpenstackNode osNode = osNodeService.node(device.id());
+
+            switch (event.type()) {
+                case DEVICE_AVAILABILITY_CHANGED:
+                case DEVICE_ADDED:
+                    eventExecutor.execute(() -> {
+                        if (deviceService.isAvailable(device.id())) {
+                            log.debug("Integration bridge created on {}", osNode.hostname());
+                            bootstrapNode(osNode);
+                        } else if (osNode.state() == COMPLETE) {
+                            log.warn("Device {} disconnected", device.id());
+                            setState(osNode, INCOMPLETE);
+                        }
+                    });
+                    break;
+                case PORT_ADDED:
+                    eventExecutor.execute(() -> {
+                        Port port = event.port();
+                        String portName = port.annotations().value(PORT_NAME);
+                        if (osNode.state() == DEVICE_CREATED && (
+                                Objects.equals(portName, DEFAULT_TUNNEL) ||
+                                Objects.equals(portName, osNode.vlanIntf()) ||
+                                Objects.equals(portName, PATCH_INTG_BRIDGE) ||
+                                Objects.equals(portName, PATCH_ROUT_BRIDGE))) {
+                            // FIXME we never gets PATCH_ROUTE_BRIDGE port added events as of now
+                            log.debug("Interface {} added to {}", portName, event.subject().id());
+                            bootstrapNode(osNode);
+                        }
+                    });
+                    break;
+                case PORT_REMOVED:
+                    eventExecutor.execute(() -> {
+                        Port port = event.port();
+                        String portName = port.annotations().value(PORT_NAME);
+                        if (osNode.state() == COMPLETE && (
+                                Objects.equals(portName, DEFAULT_TUNNEL) ||
+                                Objects.equals(portName, osNode.vlanIntf()) ||
+                                Objects.equals(portName, PATCH_INTG_BRIDGE) ||
+                                Objects.equals(portName, PATCH_ROUT_BRIDGE))) {
+                            log.warn("Interface {} removed from {}", portName, event.subject().id());
+                            setState(osNode, INCOMPLETE);
+                        }
+                    });
+                    break;
+                case PORT_UPDATED:
+                case DEVICE_REMOVED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+
+    private class InternalGroupListener implements GroupListener {
+
+        @Override
+        public boolean isRelevant(GroupEvent event) {
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNode, leader);
+        }
+
+        @Override
+        public void event(GroupEvent event) {
+            switch (event.type()) {
+                case GROUP_ADDED:
+                    eventExecutor.execute(() -> {
+                        log.trace("Group added, ID:{} state:{}", event.subject().id(),
+                                event.subject().state());
+                        processGroup(event.subject());
+                    });
+                    break;
+                case GROUP_UPDATED:
+                    eventExecutor.execute(() -> {
+                        log.trace("Group updated, ID:{} state:{}", event.subject().id(),
+                                event.subject().state());
+                        processGroup(event.subject());
+                    });
+                    break;
+                case GROUP_REMOVED:
+                    // TODO handle group removed
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void processGroup(Group group) {
+            OpenstackNode osNode = osNodeService.nodes(COMPUTE).stream()
+                    .filter(n -> n.state() == PORT_CREATED &&
+                            (n.gatewayGroupId(VXLAN).equals(group.id()) ||
+                            n.gatewayGroupId(VLAN).equals(group.id())))
+                    .findAny().orElse(null);
+            if (osNode != null) {
+                bootstrapNode(osNode);
+            }
+            osNodeService.nodes(GATEWAY).stream()
+                    .filter(gNode -> gNode.state() == PORT_CREATED)
+                    .forEach(DefaultOpenstackNodeHandler.this::bootstrapNode);
+        }
+    }
+
+    private class InternalOpenstackNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public boolean isRelevant(OpenstackNodeEvent event) {
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNode, leader);
+        }
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            switch (event.type()) {
+                case OPENSTACK_NODE_CREATED:
+                case OPENSTACK_NODE_UPDATED:
+                    eventExecutor.execute(() -> {
+                        bootstrapNode(event.subject());
+                    });
+                    break;
+                case OPENSTACK_NODE_COMPLETE:
+                    break;
+                case OPENSTACK_NODE_REMOVED:
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DistributedOpenstackNodeStore.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DistributedOpenstackNodeStore.java
new file mode 100644
index 0000000..f248367
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DistributedOpenstackNodeStore.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import com.google.common.collect.ImmutableSet;
+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.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeStore;
+import org.onosproject.openstacknode.api.OpenstackNodeStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknode.api.NodeState.COMPLETE;
+import static org.onosproject.openstacknode.api.NodeState.INCOMPLETE;
+import static org.onosproject.openstacknode.api.OpenstackNodeEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of openstack node store using consistent map.
+ */
+@Service
+@Component(immediate = true)
+public class DistributedOpenstackNodeStore
+        extends AbstractStore<OpenstackNodeEvent, OpenstackNodeStoreDelegate>
+        implements OpenstackNodeStore {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String ERR_NOT_FOUND = " does not exist";
+    private static final String ERR_DUPLICATE = " already exists";
+
+    private static final KryoNamespace SERIALIZER_OPENSTACK_NODE = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(OpenstackNode.class)
+            .register(DefaultOpenstackNode.class)
+            .register(OpenstackNode.NodeType.class)
+            .register(NodeState.class)
+            .build();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+    private final MapEventListener<String, OpenstackNode> osNodeMapListener =
+            new OpenstackNodeMapListener();
+    private ConsistentMap<String, OpenstackNode> osNodeStore;
+
+    @Activate
+    protected void activate() {
+        ApplicationId appId = coreService.registerApplication("org.onosproject.openstacknode");
+        osNodeStore = storageService.<String, OpenstackNode>consistentMapBuilder()
+                .withSerializer(Serializer.using(SERIALIZER_OPENSTACK_NODE))
+                .withName("openstack-nodestore")
+                .withApplicationId(appId)
+                .build();
+        osNodeStore.addListener(osNodeMapListener);
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osNodeStore.removeListener(osNodeMapListener);
+        eventExecutor.shutdown();
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createNode(OpenstackNode osNode) {
+        osNodeStore.compute(osNode.hostname(), (hostname, existing) -> {
+            final String error = osNode.hostname() + ERR_DUPLICATE;
+            checkArgument(existing == null, error);
+            return osNode;
+        });
+    }
+
+    @Override
+    public void updateNode(OpenstackNode osNode) {
+        osNodeStore.compute(osNode.hostname(), (hostname, existing) -> {
+            final String error = osNode.hostname() + ERR_NOT_FOUND;
+            checkArgument(existing != null, error);
+            return osNode;
+        });
+    }
+
+    @Override
+    public OpenstackNode removeNode(String hostname) {
+        Versioned<OpenstackNode> osNode = osNodeStore.remove(hostname);
+        if (osNode == null) {
+            final String error = hostname + ERR_NOT_FOUND;
+            throw new IllegalArgumentException(error);
+        }
+        return osNode.value();
+    }
+
+    @Override
+    public Set<OpenstackNode> nodes() {
+        Set<OpenstackNode> osNodes = osNodeStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osNodes);
+    }
+
+    @Override
+    public OpenstackNode node(String hostname) {
+        Versioned<OpenstackNode> osNode = osNodeStore.get(hostname);
+        return osNode == null ? null : osNode.value();
+    }
+
+    private class OpenstackNodeMapListener implements MapEventListener<String, OpenstackNode> {
+
+        @Override
+        public void event(MapEvent<String, OpenstackNode> event) {
+            switch (event.type()) {
+                case INSERT:
+                    log.debug("OpenStack node created {}", event.newValue());
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNodeEvent(
+                                OPENSTACK_NODE_CREATED,
+                                event.newValue().value()
+                        ));
+                    });
+                    break;
+                case UPDATE:
+                    log.debug("OpenStack node updated {}", event.newValue());
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNodeEvent(
+                                OPENSTACK_NODE_UPDATED,
+                                event.newValue().value()
+                        ));
+                        if (event.newValue().value().state() == COMPLETE) {
+                            notifyDelegate(new OpenstackNodeEvent(
+                                    OPENSTACK_NODE_COMPLETE,
+                                    event.newValue().value()
+                            ));
+                        } else if (event.newValue().value().state() == INCOMPLETE) {
+                            notifyDelegate(new OpenstackNodeEvent(
+                                    OPENSTACK_NODE_INCOMPLETE,
+                                    event.newValue().value()
+                            ));
+                        }
+                    });
+                    break;
+                case REMOVE:
+                    log.debug("OpenStack node removed {}", event.oldValue());
+                    eventExecutor.execute(() -> {
+                        notifyDelegate(new OpenstackNodeEvent(
+                                OPENSTACK_NODE_REMOVED,
+                                event.oldValue().value()
+                        ));
+                    });
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/OpenstackNodeManager.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/OpenstackNodeManager.java
new file mode 100644
index 0000000..ddd583e
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/OpenstackNodeManager.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2017-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.openstacknode.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+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.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.DeviceId;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.openstacknode.api.OpenstackNodeStore;
+import org.onosproject.openstacknode.api.OpenstackNodeStoreDelegate;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknode.api.NodeState.COMPLETE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Service administering the inventory of openstack nodes.
+ */
+@Service
+@Component(immediate = true)
+public class OpenstackNodeManager extends ListenerRegistry<OpenstackNodeEvent, OpenstackNodeListener>
+        implements OpenstackNodeService, OpenstackNodeAdminService {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String MSG_NODE = "OpenStack node %s %s";
+    private static final String MSG_CREATED = "created";
+    private static final String MSG_UPDATED = "updated";
+    private static final String MSG_REMOVED = "removed";
+
+    private static final String ERR_NULL_NODE = "OpenStack node cannot be null";
+    private static final String ERR_NULL_HOSTNAME = "OpenStack node hostname cannot be null";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeStore osNodeStore;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LeadershipService leadershipService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+    private final OpenstackNodeStoreDelegate delegate = new InternalNodeStoreDelegate();
+
+    private ApplicationId appId;
+    private NodeId localNode;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(APP_ID);
+        osNodeStore.setDelegate(delegate);
+
+        localNode = clusterService.getLocalNode().id();
+        leadershipService.runForLeadership(appId.name());
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        osNodeStore.unsetDelegate(delegate);
+
+        leadershipService.withdraw(appId.name());
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createNode(OpenstackNode osNode) {
+        checkNotNull(osNode, ERR_NULL_NODE);
+        osNodeStore.createNode(osNode);
+        log.info(String.format(MSG_NODE, osNode.hostname(), MSG_CREATED));
+    }
+
+    @Override
+    public void updateNode(OpenstackNode osNode) {
+        checkNotNull(osNode, ERR_NULL_NODE);
+        osNodeStore.updateNode(osNode);
+        log.info(String.format(MSG_NODE, osNode.hostname(), MSG_UPDATED));
+    }
+
+    @Override
+    public OpenstackNode removeNode(String hostname) {
+        checkArgument(!Strings.isNullOrEmpty(hostname), ERR_NULL_HOSTNAME);
+        OpenstackNode osNode = osNodeStore.removeNode(hostname);
+        log.info(String.format(MSG_NODE, hostname, MSG_REMOVED));
+        return osNode;
+    }
+
+    @Override
+    public Set<OpenstackNode> nodes() {
+        return osNodeStore.nodes();
+    }
+
+    @Override
+    public Set<OpenstackNode> nodes(OpenstackNode.NodeType type) {
+        Set<OpenstackNode> osNodes = osNodeStore.nodes().stream()
+                .filter(osNode -> Objects.equals(osNode.type(), type))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osNodes);
+    }
+
+    @Override
+    public Set<OpenstackNode> completeNodes() {
+        Set<OpenstackNode> osNodes = osNodeStore.nodes().stream()
+                .filter(osNode -> Objects.equals(osNode.state(), COMPLETE))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osNodes);
+    }
+
+    @Override
+    public Set<OpenstackNode> completeNodes(OpenstackNode.NodeType type) {
+        Set<OpenstackNode> osNodes = osNodeStore.nodes().stream()
+                .filter(osNode -> osNode.type() == type &&
+                        Objects.equals(osNode.state(), COMPLETE))
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(osNodes);
+    }
+
+    @Override
+    public OpenstackNode node(String hostname) {
+        return osNodeStore.node(hostname);
+    }
+
+    @Override
+    public OpenstackNode node(DeviceId deviceId) {
+        OpenstackNode result = osNodeStore.nodes().stream()
+                .filter(osNode -> Objects.equals(osNode.intgBridge(), deviceId) ||
+                        Objects.equals(osNode.ovsdb(), deviceId) ||
+                        Objects.equals(osNode.routerBridge(), deviceId))
+                .findFirst().orElse(null);
+        return result;
+    }
+
+    private class InternalNodeStoreDelegate implements OpenstackNodeStoreDelegate {
+
+        @Override
+        public void notify(OpenstackNodeEvent event) {
+            if (event != null) {
+                log.trace("send openstack node event {}", event);
+                process(event);
+            }
+        }
+    }
+}
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/package-info.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/package-info.java
new file mode 100644
index 0000000..4982c44
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-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.
+ */
+
+/**
+ * Application for bootstrapping Compute/Gateway Node in OpenStack.
+ */
+package org.onosproject.openstacknode.impl;
\ No newline at end of file
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/web/OpenstackNodeWebResource.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/web/OpenstackNodeWebResource.java
new file mode 100644
index 0000000..cce5134
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/web/OpenstackNodeWebResource.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2017-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.openstacknode.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Sets;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.openstacknode.impl.DefaultOpenstackNode;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+import java.util.Set;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static javax.ws.rs.core.Response.created;
+
+@Path("configure")
+public class OpenstackNodeWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE_NODE = "Received node %s request";
+    private static final String NODES = "nodes";
+
+    private final OpenstackNodeAdminService osNodeAdminService =
+            DefaultServiceDirectory.getService(OpenstackNodeAdminService.class);
+    private final OpenstackNodeService osNodeService =
+            DefaultServiceDirectory.getService(OpenstackNodeService.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createNodes(InputStream input) {
+        log.trace(String.format(MESSAGE_NODE, "CREATE"));
+
+        readNodeConfiguration(input).forEach(osNode -> {
+            OpenstackNode existing = osNodeService.node(osNode.hostname());
+            if (existing == null) {
+                osNodeAdminService.createNode(osNode);
+            }
+        });
+
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(NODES)
+                .path("NODE_ID");
+
+        return created(locationBuilder.build()).build();
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response updateNodes(InputStream input) {
+        log.trace(String.format(MESSAGE_NODE, "UPDATE"));
+
+        Set<OpenstackNode> nodes = readNodeConfiguration(input);
+        for (OpenstackNode osNode: nodes) {
+            OpenstackNode existing = osNodeService.node(osNode.hostname());
+            if (existing == null) {
+                log.warn("There is no node configuration to update : {}", osNode.hostname());
+                return Response.notModified().build();
+            } else if (!existing.equals(osNode)) {
+                osNodeAdminService.updateNode(osNode);
+            }
+        }
+
+        return Response.ok().build();
+    }
+
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteNodes(InputStream input) {
+        log.trace(String.format(MESSAGE_NODE, "DELETE"));
+
+        Set<OpenstackNode> nodes = readNodeConfiguration(input);
+        for (OpenstackNode osNode: nodes) {
+            OpenstackNode existing = osNodeService.node(osNode.hostname());
+            if (existing == null) {
+                log.warn("There is no node configuration to delete : {}", osNode.hostname());
+                return Response.notModified().build();
+            } else {
+                osNodeAdminService.removeNode(osNode.hostname());
+            }
+        }
+
+        return Response.ok().build();
+    }
+
+    private Set<OpenstackNode> readNodeConfiguration(InputStream input) {
+        Set<OpenstackNode> nodeSet = Sets.newHashSet();
+        try {
+             JsonNode jsonTree = mapper().enable(INDENT_OUTPUT).readTree(input);
+             ArrayNode nodes = (ArrayNode) jsonTree.path("nodes");
+             nodes.forEach(node -> {
+                 try {
+                     String hostname = node.get("hostname").asText();
+                     String type = node.get("type").asText();
+                     String mIp = node.get("managementIp").asText();
+                     String dIp = node.get("dataIp").asText();
+                     String iBridge = node.get("integrationBridge").asText();
+                     String rBridge = null;
+                     if (node.get("routerBridge") != null) {
+                         rBridge = node.get("routerBridge").asText();
+                     }
+                     DefaultOpenstackNode.Builder nodeBuilder = DefaultOpenstackNode.builder()
+                             .hostname(hostname)
+                             .type(OpenstackNode.NodeType.valueOf(type))
+                             .managementIp(IpAddress.valueOf(mIp))
+                             .dataIp(IpAddress.valueOf(dIp))
+                             .intgBridge(DeviceId.deviceId(iBridge))
+                             .state(NodeState.INIT);
+                     if (rBridge != null) {
+                         nodeBuilder.routerBridge(DeviceId.deviceId(rBridge));
+                     }
+                     log.trace("node is {}", nodeBuilder.build().toString());
+                     nodeSet.add(nodeBuilder.build());
+                 } catch (Exception e) {
+                     log.error(e.toString());
+                     throw  new IllegalArgumentException();
+                 }
+             });
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return nodeSet;
+    }
+}
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/web/package-info.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/web/package-info.java
new file mode 100644
index 0000000..85d6a61
--- /dev/null
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/web/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-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.
+ */
+
+/**
+ * Application for bootstrapping Compute/Gateway Node in OpenStack.
+ */
+package org.onosproject.openstacknode.web;
\ No newline at end of file