Recover the OVS dataplane ports in case conf.db is corrupted
Change-Id: Id58ffadcfa559fa697486adce27088b87d7377e7
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackComputeNodeCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackComputeNodeCompleter.java
new file mode 100644
index 0000000..069383b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackComputeNodeCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+import static org.onosproject.cli.AbstractShellCommand.get;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+
+/**
+ * Openstack host completer.
+ */
+@Service
+public class OpenstackComputeNodeCompleter implements Completer {
+
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ StringsCompleter delegate = new StringsCompleter();
+ OpenstackNodeService osNodeService = get(OpenstackNodeService.class);
+
+ Set<String> hostnames = osNodeService.completeNodes(COMPUTE).stream()
+ .map(OpenstackNode::hostname)
+ .collect(Collectors.toSet());
+ SortedSet<String> strings = delegate.getStrings();
+
+ strings.addAll(hostnames);
+
+ return delegate.complete(session, commandLine, candidates);
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackDetachedPortListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackDetachedPortListCommand.java
new file mode 100644
index 0000000..735c321
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackDetachedPortListCommand.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Port;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.ifaceNameFromOsPortId;
+
+/**
+ * Shows the list of the openvswitch ports detached from real interfaces.
+ */
+@Service
+@Command(scope = "onos", name = "openstack-detached-ports",
+ description = "Shows the detached VM's tap port list.")
+public class OpenstackDetachedPortListCommand extends AbstractShellCommand {
+
+ private static final String PORT_NAME = "portName";
+ private static final String FORMAT = "%-25s%-25s%-25s";
+
+ @Option(name = "-a", aliases = "--all", description = "Apply this command to all nodes",
+ required = false, multiValued = false)
+ private boolean isAll = false;
+
+ @Argument(index = 0, name = "hostnames", description = "Hostname(s) to apply this command",
+ required = false, multiValued = true)
+ @Completion(OpenstackComputeNodeCompleter.class)
+ private String[] hostnames = null;
+
+ @Override
+ protected void doExecute() {
+ OpenstackNodeService nodeService = get(OpenstackNodeService.class);
+ OpenstackNetworkService networkService = get(OpenstackNetworkService.class);
+ DeviceService deviceService = get(DeviceService.class);
+
+ if (isAll) {
+ hostnames = nodeService.completeNodes().stream()
+ .map(OpenstackNode::hostname).toArray(String[]::new);
+ }
+
+ if (hostnames == null) {
+ print("Please specify one of hostname or --all options.");
+ return;
+ }
+
+ print(FORMAT, "Hostname", "Integration Bridge", "Detached Port");
+
+ for (String hostname : hostnames) {
+ networkService.ports().forEach(p -> {
+ if (hostname.equals(p.getHostId())) {
+ OpenstackNode osNode = nodeService.node(p.getHostId());
+ if (osNode != null) {
+ Set<String> detachedPortNames =
+ detachedOvsPort(p,
+ deviceService.getPorts(osNode.intgBridge()));
+ detachedPortNames.forEach(dp ->
+ print(FORMAT, hostname, osNode.intgBridge().toString(), dp)
+ );
+ }
+ }
+ });
+ }
+ }
+
+ private Set<String> detachedOvsPort(Port osPort,
+ List<org.onosproject.net.Port> ovsPorts) {
+ Set<String> portNames = ovsPorts.stream()
+ .filter(ovsPort -> ovsPort.annotations() != null ||
+ ovsPort.annotations().keys().contains(PORT_NAME))
+ .map(ovsPort -> ovsPort.annotations().value(PORT_NAME))
+ .collect(Collectors.toSet());
+
+ String tapPort = ifaceNameFromOsPortId(osPort.getId());
+ Set<String> detachedPortNames = Sets.newConcurrentHashSet();
+
+ if (!portNames.contains(tapPort)) {
+ detachedPortNames.add(tapPort);
+ }
+
+ return ImmutableSet.copyOf(detachedPortNames);
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRecoverPortsCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRecoverPortsCommand.java
new file mode 100644
index 0000000..80d7db6
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRecoverPortsCommand.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.ovsdb.controller.OvsdbClientService;
+import org.onosproject.ovsdb.controller.OvsdbController;
+import org.onosproject.ovsdb.controller.OvsdbInterface;
+import org.openstack4j.model.network.Port;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getOvsdbClient;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.ifaceNameFromOsPortId;
+import static org.onosproject.openstacknode.api.Constants.INTEGRATION_BRIDGE;
+import static org.onosproject.ovsdb.rfc.table.Interface.InterfaceColumn.EXTERNALIDS;
+
+/**
+ * Recovers the openvswitch tap ports.
+ */
+@Service
+@Command(scope = "onos", name = "openstack-recover-ports",
+ description = "Recovers VM's tap ports detached from OpenvSwitch.")
+public class OpenstackRecoverPortsCommand extends AbstractShellCommand {
+
+ private static final int OVS_DB_PORT = 6640;
+
+ private static final String ATTACHED_MAC = "attached-mac";
+ private static final String IFACE_ID = "iface-id";
+ private static final String IFACE_STATUS = "iface-status";
+ private static final String VM_ID = "vm-id";
+
+ private static final String PORT_NAME = "portName";
+
+ @Option(name = "-a", aliases = "--all", description = "Apply this command to all nodes",
+ required = false, multiValued = false)
+ private boolean isAll = false;
+
+ @Argument(index = 0, name = "hostnames", description = "Hostname(s) to apply this command",
+ required = false, multiValued = true)
+ @Completion(OpenstackComputeNodeCompleter.class)
+ private String[] hostnames = null;
+
+ @Override
+ protected void doExecute() {
+ OvsdbController controller = get(OvsdbController.class);
+ OpenstackNodeService nodeService = get(OpenstackNodeService.class);
+ OpenstackNetworkService networkService = get(OpenstackNetworkService.class);
+ DeviceService deviceService = get(DeviceService.class);
+
+ if (isAll) {
+ hostnames = nodeService.completeNodes().stream()
+ .map(OpenstackNode::hostname).toArray(String[]::new);
+ }
+
+ if (hostnames == null) {
+ print("Please specify one of hostname or --all options.");
+ return;
+ }
+
+ for (String hostname : hostnames) {
+ networkService.ports().forEach(p -> {
+ if (hostname.equals(p.getHostId())) {
+ OpenstackNode osNode = nodeService.node(p.getHostId());
+ if (osNode != null) {
+ Set<String> recoveredPortNames =
+ recoverOvsPort(controller, OVS_DB_PORT, osNode, p,
+ deviceService.getPorts(osNode.intgBridge()));
+ recoveredPortNames.forEach(pn -> print(pn + " is recovered!"));
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Recovers the openvswitch port from conf.db corruption.
+ *
+ * @param controller ovsdb controller
+ * @param ovsdbPort ovsdb port number
+ * @param node openstack node
+ * @param osPort an openstack port
+ * @param ovsPorts set of openvswitch ports
+ *
+ * @return a set of recovered port name
+ */
+ private Set<String> recoverOvsPort(OvsdbController controller, int ovsdbPort,
+ OpenstackNode node, Port osPort,
+ List<org.onosproject.net.Port> ovsPorts) {
+ OvsdbClientService client = getOvsdbClient(node, ovsdbPort, controller);
+
+ if (client == null) {
+ return ImmutableSet.of();
+ }
+
+ Set<String> portNames = ovsPorts.stream()
+ .filter(ovsPort -> ovsPort.annotations() != null ||
+ ovsPort.annotations().keys().contains(PORT_NAME))
+ .map(ovsPort -> ovsPort.annotations().value(PORT_NAME))
+ .collect(Collectors.toSet());
+
+ String tapPort = ifaceNameFromOsPortId(osPort.getId());
+ Set<String> recoveredPortNames = Sets.newConcurrentHashSet();
+ if (!portNames.contains(tapPort)) {
+ Map<String, String> extIdMap =
+ ImmutableMap.of(ATTACHED_MAC, osPort.getMacAddress(),
+ IFACE_ID, osPort.getId(), IFACE_STATUS,
+ StringUtils.lowerCase(osPort.getState().name()),
+ VM_ID, osPort.getDeviceId());
+
+ OvsdbInterface ovsIface = OvsdbInterface.builder()
+ .name(tapPort)
+ .options(ImmutableMap.of())
+ .data(ImmutableMap.of(EXTERNALIDS, extIdMap))
+ .build();
+ client.createInterface(INTEGRATION_BRIDGE, ovsIface);
+ recoveredPortNames.add(tapPort);
+ }
+ return ImmutableSet.copyOf(recoveredPortNames);
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
index ef1b4d0..5402ca5 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
@@ -25,6 +25,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@@ -70,6 +71,9 @@
import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
import org.onosproject.openstacknode.api.OpenstackNode;
import org.onosproject.openstacknode.api.OpenstackSshAuth;
+import org.onosproject.ovsdb.controller.OvsdbClientService;
+import org.onosproject.ovsdb.controller.OvsdbController;
+import org.onosproject.ovsdb.controller.OvsdbNodeId;
import org.openstack4j.api.OSClient;
import org.openstack4j.api.client.IOSClientBuilder;
import org.openstack4j.api.exceptions.AuthenticationException;
@@ -182,6 +186,8 @@
private static final long WAIT_OUTPUT_STREAM_SECOND = 2;
private static final int SSH_PORT = 22;
+ private static final int TAP_PORT_LENGTH = 11;
+
/**
* Prevents object instantiation from external.
*/
@@ -1216,6 +1222,34 @@
.delete();
}
+ /**
+ * Gets the ovsdb client with supplied openstack node.
+ *
+ * @param node openstack node
+ * @param ovsdbPort openvswitch DB port number
+ * @param controller openvswitch DB controller instance
+ * @return ovsdb client instance
+ */
+ public static OvsdbClientService getOvsdbClient(OpenstackNode node, int ovsdbPort,
+ OvsdbController controller) {
+ OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
+ return controller.getOvsdbClient(ovsdb);
+ }
+
+ /**
+ * Obtains the name of interface attached to the openstack VM.
+ *
+ * @param portId openstack port identifier
+ * @return name of interface
+ */
+ public static String ifaceNameFromOsPortId(String portId) {
+ if (portId != null) {
+ return PORT_NAME_PREFIX_VM + StringUtils.substring(portId, 0, TAP_PORT_LENGTH);
+ }
+
+ return null;
+ }
+
private static Router getRouterFromSubnet(Subnet subnet,
OpenstackRouterService osRouterService) {
RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()