[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
diff --git a/apps/openstacknode/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacknode/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..f6bf14b
--- /dev/null
+++ b/apps/openstacknode/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ 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.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onosproject.openstacknode.cli.OpenstackNodeListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknode.cli.OpenstackNodeCheckCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknode.cli.OpenstackNodeInitCommand"/>
+ </command>
+ </command-bundle>
+</blueprint>
diff --git a/apps/openstacknode/app/src/main/webapp/WEB-INF/web.xml b/apps/openstacknode/app/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..583cd55
--- /dev/null
+++ b/apps/openstacknode/app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ id="ONOS" version="2.5">
+ <display-name>Openstack Node REST API v1.0</display-name>
+
+ <servlet>
+ <servlet-name>JAX-RS Service</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>jersey.config.server.provider.classnames</param-name>
+ <param-value>
+ org.onosproject.openstacknode.web.OpenstackNodeWebResource
+ </param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>JAX-RS Service</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+</web-app>