[ONOS-7732] Automating switch workflow: ofoverlay(Openflow overlay) workflow
Change-Id: I162a93465267547b3056f9096743b699aa4416b9
diff --git a/apps/workflow/BUILD b/apps/workflow/BUILD
index 118458e..e8b6a91 100644
--- a/apps/workflow/BUILD
+++ b/apps/workflow/BUILD
@@ -1,5 +1,6 @@
BUNDLES = [
"//apps/workflow/api:onos-apps-workflow-api",
+ "//apps/workflow/model:onos-apps-workflow-model",
"//apps/workflow/app:onos-apps-workflow-app",
]
diff --git a/apps/workflow/app/BUILD b/apps/workflow/app/BUILD
index 87df2e1..d5af5a2 100644
--- a/apps/workflow/app/BUILD
+++ b/apps/workflow/app/BUILD
@@ -1,5 +1,6 @@
COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + CLI + [
"//core/store/serializers:onos-core-serializers",
+ "//core/store/primitives:onos-core-primitives",
"//apps/workflow/api:onos-apps-workflow-api",
]
diff --git a/apps/workflow/model/BUILD b/apps/workflow/model/BUILD
new file mode 100644
index 0000000..016e14a
--- /dev/null
+++ b/apps/workflow/model/BUILD
@@ -0,0 +1,8 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + [
+ "//core/store/serializers:onos-core-serializers",
+ "//apps/workflow/api:onos-apps-workflow-api",
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/NetconfAccessInfo.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/NetconfAccessInfo.java
new file mode 100644
index 0000000..8e428e3
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/NetconfAccessInfo.java
@@ -0,0 +1,165 @@
+/*
+ * 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.workflow.model.accessinfo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.workflow.api.WorkflowException;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Class for NETCONF access information.
+ */
+public final class NetconfAccessInfo {
+
+ private static final String REMOTE_IP = "remoteIp";
+ private static final String PORT = "port";
+ private static final String USER = "user";
+ private static final String PASSWORD = "password";
+
+ private final IpAddress remoteIp;
+ private final TpPort port;
+ private final String user;
+ private final String password;
+
+ /**
+ * Constructor for a given NETCONF access information.
+ *
+ * @param remoteIp remote ip address
+ * @param port port number
+ * @param user user name
+ * @param password password
+ */
+ public NetconfAccessInfo(IpAddress remoteIp,
+ TpPort port,
+ String user,
+ String password) {
+ this.remoteIp = checkNotNull(remoteIp);
+ this.port = checkNotNull(port);
+ this.user = checkNotNull(user);
+ this.password = checkNotNull(password);
+ }
+
+ /**
+ * Builds NetconfAccessInfo from json.
+ * @param root json root node for NetconfAccessinfo
+ * @return NETCONF access information
+ * @throws WorkflowException workflow exception
+ */
+ public static NetconfAccessInfo valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(REMOTE_IP));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid remoteIp for " + root);
+ }
+ IpAddress remoteIp = IpAddress.valueOf(node.asText());
+
+ node = root.at(ptr(PORT));
+ if (node == null || !(node instanceof NumericNode)) {
+ throw new WorkflowException("invalid port for " + root);
+ }
+ TpPort tpPort = TpPort.tpPort(node.asInt());
+
+ node = root.at(ptr(USER));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid user for " + root);
+ }
+ String strUser = node.asText();
+
+ node = root.at(ptr(PASSWORD));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid password for " + root);
+ }
+ String strPassword = node.asText();
+
+ return new NetconfAccessInfo(remoteIp, tpPort, strUser, strPassword);
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ /**
+ * Returns the remote IP address.
+ *
+ * @return ip address
+ */
+ public IpAddress remoteIp() {
+ return this.remoteIp;
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return port
+ */
+ public TpPort port() {
+ return this.port;
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return user name
+ */
+ public String user() {
+ return this.user;
+ }
+
+ /**
+ * Returns the password.
+ * @return password
+ */
+ public String password() {
+ return this.password;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof NetconfAccessInfo) {
+ NetconfAccessInfo that = (NetconfAccessInfo) obj;
+ return Objects.equals(remoteIp, that.remoteIp) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(user, that.user);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(remoteIp, port, user);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("remoteIp", remoteIp)
+ .add("port", port)
+ .add("user", user)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/RestAccessInfo.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/RestAccessInfo.java
new file mode 100644
index 0000000..c13270d
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/RestAccessInfo.java
@@ -0,0 +1,167 @@
+/*
+ * 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.workflow.model.accessinfo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.workflow.api.WorkflowException;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Class for REST access information.
+ */
+public final class RestAccessInfo {
+
+ private static final String REMOTE_IP = "remoteIp";
+ private static final String PORT = "port";
+ private static final String USER = "user";
+ private static final String PASSWORD = "password";
+
+ private final IpAddress remoteIp;
+ private final TpPort port;
+ private final String user;
+ private String password;
+
+ /**
+ * Creates a new REST access information.
+ *
+ * @param remoteIp remote ip address
+ * @param port port number
+ * @param user user name
+ * @param password password
+ */
+ private RestAccessInfo(IpAddress remoteIp,
+ TpPort port,
+ String user,
+ String password) {
+ this.remoteIp = checkNotNull(remoteIp);
+ this.port = checkNotNull(port);
+ this.user = checkNotNull(user);
+ this.password = checkNotNull(password);
+ }
+
+ /**
+ * Builds RestAccessInfo from json.
+ * @param root json root node for RestAccessinfo
+ * @return REST access information
+ * @throws WorkflowException workflow exception
+ */
+ public static RestAccessInfo valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(REMOTE_IP));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid remoteIp for " + root);
+ }
+ IpAddress remoteIp = IpAddress.valueOf(node.asText());
+
+ node = root.at(ptr(PORT));
+ if (node == null || !(node instanceof NumericNode)) {
+ throw new WorkflowException("invalid port for " + root);
+ }
+ TpPort tpPort = TpPort.tpPort(node.asInt());
+
+ node = root.at(ptr(USER));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid user for " + root);
+ }
+ String strUser = node.asText();
+
+ node = root.at(ptr(PASSWORD));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid password for " + root);
+ }
+ String strPassword = node.asText();
+
+ return new RestAccessInfo(remoteIp, tpPort, strUser, strPassword);
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ /**
+ * Returns the remote IP address.
+ *
+ * @return ip address
+ */
+ public IpAddress remoteIp() {
+ return this.remoteIp;
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return REST port
+ */
+ public TpPort port() {
+ return this.port;
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return user name
+ */
+ public String user() {
+ return this.user;
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return password
+ */
+ public String password() {
+ return this.password;
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof RestAccessInfo) {
+ RestAccessInfo that = (RestAccessInfo) obj;
+ return Objects.equals(remoteIp, that.remoteIp) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(user, that.user);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(remoteIp, port, user);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("remoteIp", remoteIp)
+ .add("port", port)
+ .add("user", user)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/SshAccessInfo.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/SshAccessInfo.java
new file mode 100644
index 0000000..aec224a
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/SshAccessInfo.java
@@ -0,0 +1,183 @@
+/*
+ * 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.workflow.model.accessinfo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.workflow.api.WorkflowException;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Class for SSH access information.
+ */
+public final class SshAccessInfo {
+
+ private static final String REMOTE_IP = "remoteIp";
+ private static final String PORT = "port";
+ private static final String USER = "user";
+ private static final String PASSWORD = "password";
+ private static final String KEYFILE = "keyfile";
+
+ private final IpAddress remoteIp;
+ private final TpPort port;
+ private final String user;
+ private String password;
+ private final String privateKey;
+
+ /**
+ * Constructor for SSH access information.
+ *
+ * @param remoteIp ssh remote ip address
+ * @param port ssh port number
+ * @param user user name
+ * @param password password
+ * @param privateKey path of ssh private key
+ */
+ private SshAccessInfo(IpAddress remoteIp, TpPort port, String user, String password, String privateKey) {
+ this.remoteIp = checkNotNull(remoteIp);
+ this.port = checkNotNull(port);
+ this.user = checkNotNull(user);
+ this.password = password;
+ this.privateKey = checkNotNull(privateKey);
+ }
+
+ /**
+ * Builds SshAccessInfo from json.
+ * @param root json root node for SshAccessinfo
+ * @return SSH access information
+ * @throws WorkflowException workflow exception
+ */
+ public static SshAccessInfo valueOf(JsonNode root) throws WorkflowException {
+
+ JsonNode node = root.at(ptr(REMOTE_IP));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid remoteIp for " + root);
+ }
+ IpAddress sshIp = IpAddress.valueOf(node.asText());
+
+ node = root.at(ptr(PORT));
+ if (node == null || !(node instanceof NumericNode)) {
+ throw new WorkflowException("invalid port for " + root);
+ }
+ TpPort sshPort = TpPort.tpPort(node.asInt());
+
+ node = root.at(ptr(USER));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid user for " + root);
+ }
+ String sshUser = node.asText();
+
+ node = root.at(ptr(PASSWORD));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid password for " + root);
+ }
+ String sshPassword = node.asText();
+
+ node = root.at(ptr(KEYFILE));
+ if (node == null || !(node instanceof TextNode)) {
+ throw new WorkflowException("invalid keyfile for " + root);
+ }
+ String sshKeyfile = node.asText();
+
+ return new SshAccessInfo(sshIp, sshPort, sshUser, sshPassword, sshKeyfile);
+ }
+
+ private static String ptr(String field) {
+ return "/" + field;
+ }
+
+ /**
+ * Returns the remote IP address.
+ *
+ * @return ip address
+ */
+ public IpAddress remoteIp() {
+ return this.remoteIp;
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return ssh port
+ */
+ public TpPort port() {
+ return this.port;
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return user name
+ */
+ public String user() {
+ return this.user;
+ }
+
+ /**
+ * Returns the password.
+ * @return password
+ */
+ public String password() {
+ return this.password;
+ }
+
+ /**
+ * Returns the private key path.
+ *
+ * @return privateKey
+ */
+ public String privateKey() {
+ return privateKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof SshAccessInfo) {
+ SshAccessInfo that = (SshAccessInfo) obj;
+ return Objects.equals(remoteIp, that.remoteIp) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(user, that.user) &&
+ Objects.equals(privateKey, that.privateKey);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(remoteIp, port, user, privateKey);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("remoteIp", remoteIp)
+ .add("port", port)
+ .add("user", user)
+ .add("privateKey", privateKey)
+ .toString();
+ }
+}
diff --git a/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/package-info.java b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/package-info.java
new file mode 100644
index 0000000..bc88e3b
--- /dev/null
+++ b/apps/workflow/model/src/main/java/org/onosproject/workflow/model/accessinfo/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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 for workflow common data model - access info.
+ */
+package org.onosproject.workflow.model.accessinfo;
diff --git a/apps/workflow/ofoverlay/BUILD b/apps/workflow/ofoverlay/BUILD
new file mode 100644
index 0000000..cc75d82
--- /dev/null
+++ b/apps/workflow/ofoverlay/BUILD
@@ -0,0 +1,21 @@
+BUNDLES = [
+ "@jsch//jar",
+ "//protocols/ovsdb/api:onos-protocols-ovsdb-api",
+ "//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc",
+ "//apps/workflow/ofoverlay/app:onos-apps-workflow-ofoverlay-app",
+]
+
+onos_app(
+ app_name = "org.onosproject.workflow.ofoverlay",
+ category = "Utility",
+ description = "Openflow overaly application",
+ included_bundles = BUNDLES,
+ required_apps = [
+ "org.onosproject.openflow",
+ "org.onosproject.ovsdb",
+ "org.onosproject.drivers.ovsdb",
+ "org.onosproject.workflow",
+ ],
+ title = "Openflow overlay",
+ url = "http://onosproject.org",
+)
diff --git a/apps/workflow/ofoverlay/app/BUILD b/apps/workflow/ofoverlay/app/BUILD
new file mode 100644
index 0000000..d8a5fa0
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/BUILD
@@ -0,0 +1,13 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + CLI + [
+ "//core/store/serializers:onos-core-serializers",
+ "//core/store/primitives:onos-core-primitives",
+ "//apps/workflow/api:onos-apps-workflow-api",
+ "//apps/workflow/model:onos-apps-workflow-model",
+ "@jsch//jar",
+ "//protocols/ovsdb/api:onos-protocols-ovsdb-api",
+ "//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc",
+]
+
+osgi_jar_with_tests(
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/OfOverlayWorkflow.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/OfOverlayWorkflow.java
new file mode 100644
index 0000000..5fb8cd2
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/OfOverlayWorkflow.java
@@ -0,0 +1,156 @@
+/*
+ * 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.ofoverlay.impl;
+
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.workflow.api.ImmutableListWorkflow;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.WorkflowExecutionService;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.workflow.api.WorkplaceStore;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.concurrent.ScheduledExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Class for Open-flow overlay configuration workflow.
+ */
+@Component(immediate = true)
+public class OfOverlayWorkflow {
+
+ private static final Logger log = LoggerFactory.getLogger(OfOverlayWorkflow.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected WorkflowStore workflowStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected WorkplaceStore workplaceStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected WorkflowExecutionService workflowExecutionService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected DeviceService deviceService;
+
+ private ScheduledExecutorService eventMapTriggerExecutor;
+
+ @Activate
+ public void activate() {
+ log.info("Activated");
+
+ eventMapTriggerExecutor = newSingleThreadScheduledExecutor(
+ groupedThreads("onos/of-overlay", "eventmap-trigger-executor"));
+
+ registerWorkflows();
+
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Deactivated");
+ }
+
+ /**
+ * Registers workflows.
+ */
+ private void registerWorkflows() {
+ // registering class-loader
+ workflowStore.registerLocal(this.getClass().getClassLoader());
+
+ // registering new workflow definition
+ URI uri = URI.create("of-overlay.workflow-nova");
+ Workflow workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ //.attribute(WorkflowAttribute.REMOVE_AFTER_COMPLETE)
+ .chain(Ovs.CreateOvsdbDevice.class.getName())
+ .chain(Ovs.UpdateOvsVersion.class.getName())
+ .chain(Ovs.UpdateOverlayBridgeId.class.getName())
+ .chain(Ovs.CreateOverlayBridge.class.getName())
+ .chain(Ovs.UpdateUnderlayBridgeId.class.getName())
+ .chain(Ovs.CreateUnderlayBridge.class.getName())
+ .chain(Ovs.CreateOverlayBridgeVxlanPort.class.getName())
+ .chain(Ovs.AddPhysicalPortsOnUnderlayBridge.class.getName())
+ .chain(Ovs.ConfigureUnderlayBridgeLocalIp.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ uri = URI.create("of-overlay.clean-workflow-nova");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ //.attribute(WorkflowAttribute.REMOVE_AFTER_COMPLETE)
+ .chain(Ovs.DeleteOverlayBridgeConfig.class.getName())
+ .chain(Ovs.RemoveOverlayBridgeOfDevice.class.getName())
+ .chain(Ovs.DeleteUnderlayBridgeConfig.class.getName())
+ .chain(Ovs.RemoveUnderlayBridgeOfDevice.class.getName())
+ .chain(Ovs.RemoveOvsdbDevice.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ uri = URI.create("of-overlay.workflow-ovs-leaf");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(Ovs.CreateOvsdbDevice.class.getName())
+ .chain(Ovs.UpdateOvsVersion.class.getName())
+ .chain(Ovs.UpdateUnderlayBridgeId.class.getName())
+ .chain(Ovs.CreateUnderlayBridge.class.getName())
+ .chain(Ovs.AddPhysicalPortsOnUnderlayBridge.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ uri = URI.create("of-overlay.workflow-ovs-spine");
+ workflow = ImmutableListWorkflow.builder()
+ .id(uri)
+ .chain(Ovs.CreateOvsdbDevice.class.getName())
+ .chain(Ovs.UpdateOvsVersion.class.getName())
+ .chain(Ovs.UpdateUnderlayBridgeId.class.getName())
+ .chain(Ovs.CreateUnderlayBridge.class.getName())
+ .chain(Ovs.AddPhysicalPortsOnUnderlayBridge.class.getName())
+ .build();
+ workflowStore.register(workflow);
+
+ deviceService.addListener(
+ event -> {
+ // trigger EventTask for DeviceEvent
+ eventMapTriggerExecutor.submit(
+ () -> workflowExecutionService.eventMapTrigger(
+ event,
+ // event hint supplier
+ (ev) -> {
+ if (ev == null || ev.subject() == null) {
+ return null;
+ }
+ String hint = event.subject().id().toString();
+ log.debug("hint: {}", hint);
+ return hint;
+ }
+ )
+ );
+ }
+ );
+
+ }
+
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/Ovs.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/Ovs.java
new file mode 100644
index 0000000..428688b
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/Ovs.java
@@ -0,0 +1,1220 @@
+/*
+ * 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.ofoverlay.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.event.Event;
+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.ControlProtocolVersion;
+import org.onosproject.net.behaviour.ControllerInfo;
+import org.onosproject.net.behaviour.DefaultBridgeDescription;
+import org.onosproject.net.behaviour.DefaultTunnelDescription;
+import org.onosproject.net.behaviour.InterfaceConfig;
+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.DeviceService;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.ofoverlay.impl.util.NetworkAddress;
+import org.onosproject.ofoverlay.impl.util.OvsDatapathType;
+import org.onosproject.ofoverlay.impl.util.OvsVersion;
+import org.onosproject.ofoverlay.impl.util.SshUtil;
+import org.onosproject.ovsdb.controller.OvsdbClientService;
+import org.onosproject.ovsdb.controller.OvsdbController;
+import org.onosproject.ovsdb.controller.OvsdbNodeId;
+import org.onosproject.workflow.api.AbstractWorklet;
+import org.onosproject.workflow.api.JsonDataModel;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.model.accessinfo.SshAccessInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
+/**
+ * Class for defining OVS workflows.
+ */
+public final class Ovs {
+
+ private static final Logger log = LoggerFactory.getLogger(Ovs.class);
+
+ private static final String MODEL_MGMT_IP = "/mgmtIp";
+ private static final String MODEL_OVSDB_PORT = "/ovsdbPort";
+ private static final String MODEL_OVS_VERSION = "/ovsVersion";
+ private static final String MODEL_OVS_DATAPATH_TYPE = "/ovsDatapathType";
+ private static final String MODEL_SSH_ACCESSINFO = "/sshAccessInfo";
+ private static final String MODEL_OF_DEVID_OVERLAY_BRIDGE = "/ofDevIdBrInt";
+ private static final String MODEL_OF_DEVID_UNDERLAY_BRIDGE = "/ofDevIdBrPhy";
+ private static final String MODEL_PHY_PORTS = "/physicalPorts";
+ private static final String MODEL_VTEP_IP = "/vtepIp";
+
+ private static final String BRIDGE_OVERLAY = "br-int";
+ private static final String BRIDGE_UNDERLAY = "br-phy";
+
+ private static final int DEVID_IDX_BRIDGE_OVERLAY = 0;
+ private static final int DEVID_IDX_BRIDGE_UNDERLAY_NOVA = 1;
+
+ private static final ControlProtocolVersion BRIDGE_DEFAULT_OF_VERSION = ControlProtocolVersion.OF_1_3;
+ private static final int OPENFLOW_PORT = 6653;
+ private static final String OPENFLOW_CHANNEL_PROTO = "tcp";
+ private static final String OVSDB_DEVICE_PREFIX = "ovsdb:";
+
+ private static final long TIMEOUT_DEVICE_CREATION_MS = 60000L;
+ private static final long TIMEOUT_PORT_ADDITION_MS = 120000L;
+
+
+ /**
+ * Utility class for OVS workflow.
+ */
+ public static final class OvsUtil {
+
+ private OvsUtil() {
+
+ }
+
+ private static final String OPENFLOW_DEVID_FORMAT = "of:%08x%08x";
+
+ /**
+ * Builds Open-flow device id with ip address, and index.
+ * @param addr ip address
+ * @param index index
+ * @return created device id
+ */
+ public static DeviceId buildOfDeviceId(IpAddress addr, int index) {
+ if (addr.isIp4()) {
+ Ip4Address v4Addr = addr.getIp4Address();
+ return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, v4Addr.toInt(), index));
+ } else if (addr.isIp6()) {
+ Ip6Address v6Addr = addr.getIp6Address();
+ return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, v6Addr.hashCode(), index));
+ } else {
+ return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, addr.hashCode(), index));
+ }
+ }
+
+ /**
+ * Builds OVS data path type.
+ * @param strOvsDatapathType string ovs data path type
+ * @return ovs data path type
+ * @throws WorkflowException workflow exception
+ */
+ public static final OvsDatapathType buildOvsDatapathType(String strOvsDatapathType) throws WorkflowException {
+ try {
+ return OvsDatapathType.valueOf(strOvsDatapathType.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Gets OVSDB behavior.
+ * @param context workflow context
+ * @param mgmtIp management ip
+ * @param behaviourClass behavior class
+ * @param <T> behavior class
+ * @return OVSDB behavior
+ * @throws WorkflowException workflow exception
+ */
+ public static final <T extends Behaviour> T getOvsdbBehaviour(WorkflowContext context, String mgmtIp,
+ Class<T> behaviourClass) throws WorkflowException {
+
+ DriverService driverService = context.getService(DriverService.class);
+
+ DeviceId devId = ovsdbDeviceId(mgmtIp);
+ DriverHandler handler = driverService.createHandler(devId);
+ if (Objects.isNull(handler)) {
+ throw new WorkflowException("Failed to get DriverHandler for " + devId);
+ }
+ T behaviour;
+ try {
+ behaviour = handler.behaviour(behaviourClass);
+ if (Objects.isNull(behaviour)) {
+ throw new WorkflowException("Failed to get " + behaviourClass + " for " + devId + "-" + handler);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new WorkflowException("Failed to get " + behaviourClass + " for " + devId + "-" + handler);
+ }
+ return behaviour;
+ }
+
+ /**
+ * Gets bridge description.
+ * @param bridgeConfig bridge config
+ * @param bridgeName bridge name
+ * @return bridge description optional
+ */
+ public static final Optional<BridgeDescription> getBridgeDescription(BridgeConfig bridgeConfig,
+ String bridgeName) {
+ try {
+ Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
+ for (BridgeDescription br: bridges) {
+ if (Objects.equals(bridgeName, br.name())) {
+ return Optional.of(br);
+ }
+ }
+ } catch (Exception e) {
+ log.error("Exception : ", e);
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Builds OVSDB device id.
+ * @param mgmtIp management ip address string
+ * @return OVSDB device id
+ */
+ public static final DeviceId ovsdbDeviceId(String mgmtIp) {
+ return DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(mgmtIp));
+ }
+
+ /**
+ * Returns {@code true} if this bridge is available;
+ * returns {@code false} otherwise.
+ * @param context workflow context
+ * @param devId device id
+ * @return {@code true} if this bridge is available; {@code false} otherwise.
+ * @throws WorkflowException workflow exception
+ */
+ public static final boolean isAvailableBridge(WorkflowContext context, DeviceId devId)
+ throws WorkflowException {
+
+ if (Objects.isNull(devId)) {
+ throw new WorkflowException("Invalid device id in data model");
+ }
+
+ DeviceService deviceService = context.getService(DeviceService.class);
+ Device dev = deviceService.getDevice(devId);
+ if (Objects.isNull(dev)) {
+ return false;
+ }
+
+ return deviceService.isAvailable(devId);
+ }
+
+ /**
+ * Gets openflow controller information list.
+ * @param context workflow context
+ * @return openflow controller information list
+ * @throws WorkflowException workflow exception
+ */
+ public static final List<ControllerInfo> getOpenflowControllerInfoList(WorkflowContext context)
+ throws WorkflowException {
+ ClusterService clusterService = context.getService(ClusterService.class);
+ java.util.List<org.onosproject.net.behaviour.ControllerInfo> controllers = new ArrayList<>();
+ Sets.newHashSet(clusterService.getNodes()).forEach(
+ controller -> {
+ org.onosproject.net.behaviour.ControllerInfo ctrlInfo =
+ new org.onosproject.net.behaviour.ControllerInfo(controller.ip(),
+ OPENFLOW_PORT,
+ OPENFLOW_CHANNEL_PROTO);
+ controllers.add(ctrlInfo);
+ }
+ );
+ return controllers;
+ }
+
+ /**
+ * Creates bridge.
+ * @param bridgeConfig bridge config
+ * @param name bridge name to create
+ * @param dpid openflow data path id of bridge to create
+ * @param ofControllers openflow controller information list
+ * @param datapathType OVS data path type
+ */
+ public static final void createBridge(BridgeConfig bridgeConfig, String name, String dpid,
+ List<ControllerInfo> ofControllers, OvsDatapathType datapathType) {
+ BridgeDescription.Builder bridgeDescBuilder = DefaultBridgeDescription.builder()
+ .name(name)
+ .failMode(BridgeDescription.FailMode.SECURE)
+ .datapathId(dpid)
+ .disableInBand()
+ .controlProtocols(Collections.singletonList(BRIDGE_DEFAULT_OF_VERSION))
+ .controllers(ofControllers);
+
+ if (datapathType != null && !(datapathType.equals(OvsDatapathType.EMPTY))) {
+ bridgeDescBuilder.datapathType(datapathType.toString());
+ log.info("create {} with dataPathType {}", name, datapathType);
+ }
+
+ BridgeDescription bridgeDesc = bridgeDescBuilder.build();
+ bridgeConfig.addBridge(bridgeDesc);
+ }
+
+ /**
+ * Index of data path id in openflow device id.
+ */
+ private static final int DPID_BEGIN_INDEX = 3;
+
+ /**
+ * Gets bridge data path id.
+ * @param devId device id
+ * @return bridge data path id
+ */
+ public static final String bridgeDatapathId(DeviceId devId) {
+ return devId.toString().substring(DPID_BEGIN_INDEX);
+ }
+
+ /**
+ * Gets OVSDB client.
+ * @param context workflow context
+ * @param strMgmtIp management ip address
+ * @param intOvsdbPort OVSDB port
+ * @return ovsdb client
+ * @throws WorkflowException workflow exception
+ */
+ public static final OvsdbClientService getOvsdbClient(
+ WorkflowContext context, String strMgmtIp, int intOvsdbPort) throws WorkflowException {
+ IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
+ TpPort ovsdbPort = TpPort.tpPort(intOvsdbPort);
+ OvsdbController ovsdbController = context.getService(OvsdbController.class);
+ return ovsdbController.getOvsdbClient(new OvsdbNodeId(mgmtIp, ovsdbPort.toInt()));
+ }
+
+ /**
+ * Checks whether 2 controller informations include same controller information.
+ * @param a controller information list
+ * @param b controller information list
+ * @return {@code true} if 2 controller informations include same controller information
+ */
+ public static boolean isEqual(List<ControllerInfo> a, List<ControllerInfo> b) {
+ if (a == b) {
+ return true;
+ } else if (a == null) {
+ // equivalent to (a == null && b != null)
+ return false;
+ } else if (b == null) {
+ // equivalent to (a != null && b == null)
+ return false;
+ } else if (a.size() != b.size()) {
+ return false;
+ }
+
+ return a.containsAll(b);
+ }
+
+ /**
+ * Gets the name of the port.
+ * @param port port
+ * @return the name of the port
+ */
+ public static final String portName(Port port) {
+ return port.annotations().value(PORT_NAME);
+ }
+ }
+
+ /**
+ * Work-let class for creating OVSDB device.
+ */
+ public static class CreateOvsdbDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ return ovsdbClient == null || !ovsdbClient.isConnected();
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
+ TpPort ovsdbPort = TpPort.tpPort(intOvsdbPort);
+ OvsdbController ovsdbController = context.getService(OvsdbController.class);
+ context.waitCompletion(DeviceEvent.class, OVSDB_DEVICE_PREFIX.concat(strMgmtIp),
+ () -> ovsdbController.connect(mgmtIp, ovsdbPort),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ Device device = deviceEvent.subject();
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_UPDATED:
+ return context.getService(DeviceService.class).isAvailable(device.id());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+
+ /**
+ * Work-let class for removing OVSDB device.
+ */
+ public static class RemoveOvsdbDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(strMgmtIp));
+
+ Device dev = context.getService(DeviceService.class).getDevice(devId);
+ return dev != null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
+ check(mgmtIp != null, "mgmt ip is invalid");
+ DeviceId devId = DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(strMgmtIp));
+ DeviceAdminService adminService = context.getService(DeviceAdminService.class);
+
+ context.waitCompletion(DeviceEvent.class, devId.toString(),
+ () -> adminService.removeDevice(devId),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case DEVICE_REMOVED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for updating OVS version.
+ */
+ public static class UpdateOvsVersion extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_OVS_VERSION, optional = true)
+ String strOvsVersion;
+
+ @JsonDataModel(path = MODEL_SSH_ACCESSINFO)
+ JsonNode strSshAccessInfo;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ return strOvsVersion == null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
+ check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
+
+ OvsVersion ovsVersion = SshUtil.exec(sshAccessInfo,
+ session -> SshUtil.fetchOvsVersion(session));
+
+ check(Objects.nonNull(ovsVersion), "Failed to fetch ovs version " + context.data());
+ strOvsVersion = ovsVersion.toString();
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for updating overlay bridge device id.
+ */
+ public static class UpdateOverlayBridgeId extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
+ String strOfDevIdOverlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ return strOfDevIdOverlay == null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
+ if (optBd.isPresent()) {
+ Optional<DeviceId> optDevId = optBd.get().deviceId();
+ if (optDevId.isPresent()) {
+ log.info("Updates {} of device id with existing device id {}", BRIDGE_OVERLAY, optDevId.get());
+ strOfDevIdOverlay = optDevId.get().toString();
+ } else {
+ DeviceId newDevId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+ log.info("Failed to find devId. Updates {} of device id with new device id {}",
+ BRIDGE_OVERLAY, newDevId);
+ strOfDevIdOverlay = newDevId.toString();
+ }
+ } else {
+ DeviceId newDevId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+ log.info("Failed to find description. Updates {} of device id with new device id {}",
+ BRIDGE_OVERLAY, newDevId);
+ strOfDevIdOverlay = newDevId.toString();
+ }
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for creating overlay openflow bridge.
+ */
+ public static class CreateOverlayBridge extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
+ String strOvsDatapath;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
+ String strOfDevIdOverlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(strOfDevIdOverlay));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
+ DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdOverlay);
+
+ if (ofControllers == null || ofControllers.size() == 0) {
+ throw new WorkflowException("Invalid of controllers");
+ }
+
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
+ if (!optBd.isPresent()) {
+
+ // If bridge does not exist, just creates a new bridge.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> OvsUtil.createBridge(bridgeConfig,
+ BRIDGE_OVERLAY,
+ OvsUtil.bridgeDatapathId(ofDeviceId),
+ ofControllers,
+ OvsUtil.buildOvsDatapathType(strOvsDatapath)),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+
+ } else {
+ BridgeDescription bd = optBd.get();
+ if (OvsUtil.isEqual(ofControllers, bd.controllers())) {
+ log.error("{} has valid controller setting({})", BRIDGE_OVERLAY, bd.controllers());
+ context.completed();
+ return;
+ }
+
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ if (ovsdbClient == null || !ovsdbClient.isConnected()) {
+ throw new WorkflowException("Invalid ovsdb client for " + strMgmtIp);
+ }
+
+ // If controller settings are not matched, set controller with valid controller information.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> ovsdbClient.setControllersWithDeviceId(bd.deviceId().get(), ofControllers),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+ }
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ Device device = deviceEvent.subject();
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_UPDATED:
+ return context.getService(DeviceService.class).isAvailable(device.id());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for updating underlay bridge device id.
+ */
+ public static class UpdateUnderlayBridgeId extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE, optional = true)
+ String strOfDevIdUnderlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ return strOfDevIdUnderlay == null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_UNDERLAY);
+ if (optBd.isPresent()) {
+ Optional<DeviceId> optDevId = optBd.get().deviceId();
+ if (optDevId.isPresent()) {
+ log.info("Updates {} of device id with existing device id {}", BRIDGE_UNDERLAY, optDevId.get());
+ strOfDevIdUnderlay = optDevId.get().toString();
+ } else {
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp),
+ DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+ log.info("Failed to find devId. Updates {} of device id with new device id {}",
+ BRIDGE_UNDERLAY, devId);
+ strOfDevIdUnderlay = devId.toString();
+ }
+ } else {
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+ log.info("Failed to find description. Updates {} of device id with new device id {}",
+ BRIDGE_UNDERLAY, devId);
+ strOfDevIdUnderlay = devId.toString();
+ }
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for creating underlay openflow bridge.
+ */
+ public static class CreateUnderlayBridge extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
+ String strOvsDatapath;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE)
+ String strOfDevIdUnderlay;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(strOfDevIdUnderlay));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+ List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
+ DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdUnderlay);
+
+ if (ofControllers == null || ofControllers.size() == 0) {
+ throw new WorkflowException("Invalid of controllers");
+ }
+
+ Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_UNDERLAY);
+ if (!optBd.isPresent()) {
+
+ // If bridge does not exist, just creates a new bridge.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> OvsUtil.createBridge(bridgeConfig,
+ BRIDGE_UNDERLAY,
+ OvsUtil.bridgeDatapathId(ofDeviceId),
+ ofControllers,
+ OvsUtil.buildOvsDatapathType(strOvsDatapath)),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+
+ } else {
+ BridgeDescription bd = optBd.get();
+ if (OvsUtil.isEqual(ofControllers, bd.controllers())) {
+ log.error("{} has valid controller setting({})", BRIDGE_UNDERLAY, bd.controllers());
+ context.completed();
+ return;
+ }
+
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ if (ovsdbClient == null || !ovsdbClient.isConnected()) {
+ throw new WorkflowException("Invalid ovsdb client for " + strMgmtIp);
+ }
+
+ // If controller settings are not matched, set controller with valid controller information.
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> ovsdbClient.setControllersWithDeviceId(bd.deviceId().get(), ofControllers),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ return;
+ }
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ Device device = deviceEvent.subject();
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_UPDATED:
+ return context.getService(DeviceService.class).isAvailable(device.id());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+
+ }
+
+ /**
+ * Work-let class for creating vxlan port on the overlay bridge.
+ */
+ public static class CreateOverlayBridgeVxlanPort extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
+ String strOfDevIdOverlay;
+
+ private static final String OVS_VXLAN_PORTNAME = "vxlan";
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ DeviceId deviceId = DeviceId.deviceId(strOfDevIdOverlay);
+ if (Objects.isNull(deviceId)) {
+ throw new WorkflowException("Invalid br-int bridge, before creating VXLAN port");
+ }
+
+ DeviceService deviceService = context.getService(DeviceService.class);
+ return !deviceService.getPorts(deviceId)
+ .stream()
+ .filter(port -> OvsUtil.portName(port).contains(OVS_VXLAN_PORTNAME) && port.isEnabled())
+ .findAny().isPresent();
+ }
+
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
+ TunnelDescription description = DefaultTunnelDescription.builder()
+ .deviceId(BRIDGE_OVERLAY)
+ .ifaceName(OVS_VXLAN_PORTNAME)
+ .type(TunnelDescription.Type.VXLAN)
+ .remote(TunnelEndPoints.flowTunnelEndpoint())
+ .key(TunnelKeys.flowTunnelKey())
+ .build();
+
+ DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdOverlay);
+ InterfaceConfig interfaceConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, InterfaceConfig.class);
+
+ context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
+ () -> interfaceConfig.addTunnelMode(BRIDGE_OVERLAY, description),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case PORT_ADDED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for adding physical ports on the underlay openflow bridge.
+ */
+ public static class AddPhysicalPortsOnUnderlayBridge extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @JsonDataModel(path = MODEL_OVSDB_PORT)
+ Integer intOvsdbPort;
+
+ @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE, optional = true)
+ String strOfDevIdUnderlay;
+
+ @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
+ String strOvsDatapath;
+
+ @JsonDataModel(path = MODEL_PHY_PORTS)
+ ArrayNode arrNodePhysicalPorts;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ DeviceId brphyDevId = DeviceId.deviceId(strOfDevIdUnderlay);
+ return !hasAllPhysicalPorts(context, brphyDevId);
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+ check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
+ DeviceId brphyDevId = DeviceId.deviceId(strOfDevIdUnderlay);
+
+ context.waitCompletion(DeviceEvent.class, brphyDevId.toString(),
+ () -> addPhysicalPorts(context, brphyDevId, BRIDGE_UNDERLAY, strOvsDatapath),
+ TIMEOUT_PORT_ADDITION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case PORT_ADDED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+
+ private final List<String> getPhysicalPorts(WorkflowContext context) throws WorkflowException {
+ List<String> ports = Lists.newArrayList();
+ for (JsonNode jsonNode : arrNodePhysicalPorts) {
+ check(jsonNode instanceof TextNode, "Invalid physical ports " + arrNodePhysicalPorts);
+ ports.add(jsonNode.asText());
+ }
+ return ports;
+ }
+
+ private final boolean hasAllPhysicalPorts(WorkflowContext context, DeviceId devId) throws WorkflowException {
+
+ List<Port> devPorts = context.getService(DeviceService.class).getPorts(devId);
+ check(devPorts != null, "Invalid device ports for " + devId);
+ List<String> physicalPorts = getPhysicalPorts(context);
+ check(physicalPorts != null, "Invalid physical ports" + context);
+
+ log.info("physicalPorts: {} for {}", physicalPorts, devId);
+ for (String port: physicalPorts) {
+ if (devPorts.stream().noneMatch(p -> OvsUtil.portName(p).contains(port))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private final boolean hasPort(WorkflowContext context, DeviceId devId, String portName)
+ throws WorkflowException {
+
+ List<Port> devPorts = context.getService(DeviceService.class).getPorts(devId);
+ check(devPorts != null, "Invalid device ports for " + devId);
+ return devPorts.stream().anyMatch(p -> OvsUtil.portName(p).contains(portName));
+ }
+
+ private final void addPhysicalPorts(WorkflowContext context, DeviceId devId, String bridgeName,
+ String strOvsDatapathType)
+ throws WorkflowException {
+ OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
+ check(ovsdbClient != null, "Invalid ovsdb client");
+
+ List<String> physicalPorts = getPhysicalPorts(context);
+ check(physicalPorts != null, "Invalid physical ports");
+
+ OvsDatapathType datapathType = OvsUtil.buildOvsDatapathType(strOvsDatapathType);
+ check(datapathType != null, "Invalid data path type");
+
+ List<String> sortedPhyPorts = physicalPorts.stream().sorted().collect(Collectors.toList());
+
+ for (String port: sortedPhyPorts) {
+ if (hasPort(context, devId, port)) {
+ continue;
+ }
+ log.info("adding port {} on {}", port, devId);
+ switch (datapathType) {
+ case NETDEV:
+ throw new WorkflowException("NETDEV datapathType are not supported");
+ //break;
+ case SYSTEM:
+ default:
+ ovsdbClient.createPort(BridgeName.bridgeName(bridgeName).name(), port);
+ }
+ }
+ }
+ }
+
+ /**
+ * Work-let class for configure local ip of underlay openflow bridge.
+ */
+ public static class ConfigureUnderlayBridgeLocalIp extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_SSH_ACCESSINFO)
+ JsonNode strSshAccessInfo;
+
+ @JsonDataModel(path = MODEL_VTEP_IP)
+ String strVtepIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
+ check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
+
+ NetworkAddress vtepIp = NetworkAddress.valueOf(strVtepIp);
+ check(Objects.nonNull(vtepIp), "Invalid vtep ip " + context.data());
+
+ return !SshUtil.exec(sshAccessInfo,
+ session ->
+ SshUtil.hasIpAddrOnInterface(session, BRIDGE_UNDERLAY, vtepIp)
+ && SshUtil.isIpLinkUpOnInterface(session, BRIDGE_UNDERLAY)
+ );
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
+ check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
+
+ NetworkAddress vtepIp = NetworkAddress.valueOf(strVtepIp);
+ check(Objects.nonNull(vtepIp), "Invalid vtep ip " + context.data());
+
+ SshUtil.exec(sshAccessInfo,
+ session -> {
+ SshUtil.addIpAddrOnInterface(session, BRIDGE_UNDERLAY, vtepIp);
+ SshUtil.setIpLinkUpOnInterface(session, BRIDGE_UNDERLAY);
+ return "";
+ });
+
+ context.completed();
+ }
+ }
+
+ /**
+ * Work-let class for deleting overlay bridge config.
+ */
+ public static class DeleteOverlayBridgeConfig extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
+ return bridges.stream()
+ .anyMatch(bd -> Objects.equals(bd.name(), BRIDGE_OVERLAY));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ bridgeConfig.deleteBridge(BridgeName.bridgeName(BRIDGE_OVERLAY));
+
+ for (int i = 0; i < 10; i++) {
+ if (!isNext(context)) {
+ context.completed();
+ return;
+ }
+ sleep(50);
+ }
+ throw new WorkflowException("Timeout happened for removing config");
+ }
+
+ protected void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Work-let class for removing overlay bridge openflow device.
+ */
+ public static class RemoveOverlayBridgeOfDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+
+ Device dev = context.getService(DeviceService.class).getDevice(devId);
+ return dev != null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
+
+ DeviceAdminService adminService = context.getService(DeviceAdminService.class);
+
+ context.waitCompletion(DeviceEvent.class, devId.toString(),
+ () -> adminService.removeDevice(devId),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case DEVICE_REMOVED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+
+ /**
+ * Work-let class for deleting underlay bridge config.
+ */
+ public static class DeleteUnderlayBridgeConfig extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
+ return bridges.stream()
+ .anyMatch(bd -> Objects.equals(bd.name(), BRIDGE_UNDERLAY));
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
+
+ bridgeConfig.deleteBridge(BridgeName.bridgeName(BRIDGE_UNDERLAY));
+
+ for (int i = 0; i < 10; i++) {
+ if (!isNext(context)) {
+ context.completed();
+ return;
+ }
+ sleep(50);
+ }
+ throw new WorkflowException("Timeout happened for removing config");
+ }
+
+ protected void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Work-let class for removing underlay bridge openflow device.
+ */
+ public static class RemoveUnderlayBridgeOfDevice extends AbstractWorklet {
+
+ @JsonDataModel(path = MODEL_MGMT_IP)
+ String strMgmtIp;
+
+ @Override
+ public boolean isNext(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+
+ Device dev = context.getService(DeviceService.class).getDevice(devId);
+ return dev != null;
+ }
+
+ @Override
+ public void process(WorkflowContext context) throws WorkflowException {
+
+ DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
+
+ DeviceAdminService adminService = context.getService(DeviceAdminService.class);
+
+ context.waitCompletion(DeviceEvent.class, devId.toString(),
+ () -> adminService.removeDevice(devId),
+ TIMEOUT_DEVICE_CREATION_MS
+ );
+ }
+
+ @Override
+ public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
+ if (!(event instanceof DeviceEvent)) {
+ return false;
+ }
+ DeviceEvent deviceEvent = (DeviceEvent) event;
+ switch (deviceEvent.type()) {
+ case DEVICE_REMOVED:
+ return !isNext(context);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void timeout(WorkflowContext context) throws WorkflowException {
+ if (!isNext(context)) {
+ context.completed(); //Complete the job of worklet by timeout
+ } else {
+ super.timeout(context);
+ }
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/package-info.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/package-info.java
new file mode 100644
index 0000000..42214bc
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Open-flow overlay implementation package.
+ */
+package org.onosproject.ofoverlay.impl;
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/NetworkAddress.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/NetworkAddress.java
new file mode 100644
index 0000000..c8a8829
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/NetworkAddress.java
@@ -0,0 +1,112 @@
+/*
+ * 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.ofoverlay.impl.util;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Representation of a network address, which consists of IP address and prefix.
+ */
+public final class NetworkAddress {
+ private final IpAddress ip;
+ private final IpPrefix prefix;
+
+ /**
+ * Constructor for a given IP address and prefix.
+ *
+ * @param ip ip address
+ * @param prefix ip prefix
+ */
+ private NetworkAddress(IpAddress ip, IpPrefix prefix) {
+ this.ip = ip;
+ this.prefix = prefix;
+ }
+
+ /**
+ * Converts a CIDR notation string into a network address.
+ *
+ * @param cidr cidr
+ * @return network address
+ * @throws IllegalArgumentException if the cidr is not valid
+ */
+ public static NetworkAddress valueOf(String cidr) {
+ checkArgument(cidr.contains("/"));
+
+ IpAddress ipAddress = IpAddress.valueOf(cidr.split("/")[0]);
+ IpPrefix ipPrefix = IpPrefix.valueOf(cidr);
+
+ return new NetworkAddress(ipAddress, ipPrefix);
+ }
+
+ /**
+ * Returns the IP address value of the network address.
+ *
+ * @return ip address
+ */
+ public IpAddress ip() {
+ return this.ip;
+ }
+
+ /**
+ * Returns the IP prefix value of the network address.
+ *
+ * @return ip prefix
+ */
+ public IpPrefix prefix() {
+ return this.prefix;
+ }
+
+ /**
+ * Converts a network address to a CIDR notation.
+ *
+ * @return cidr notation string
+ */
+ public String cidr() {
+ return ip.toString() + "/" + prefix.prefixLength();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof NetworkAddress) {
+ NetworkAddress that = (NetworkAddress) obj;
+ return Objects.equals(ip, that.ip) && Objects.equals(prefix, that.prefix);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ip, prefix);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("IpAddress", ip)
+ .add("IpPrefix", prefix)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsDatapathType.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsDatapathType.java
new file mode 100644
index 0000000..0a64e93
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsDatapathType.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ofoverlay.impl.util;
+
+/**
+ * Enumeration for OVS data path type.
+ */
+public enum OvsDatapathType {
+ EMPTY(""),
+ NETDEV("netdev"),
+ SYSTEM("system");
+
+ private final String value;
+
+ /**
+ * Constructor for OvsDatapathType enumeration.
+ *
+ * @param value string OvsDatapathType
+ */
+ OvsDatapathType(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsVersion.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsVersion.java
new file mode 100644
index 0000000..015ac73
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/OvsVersion.java
@@ -0,0 +1,154 @@
+/*
+ * 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.ofoverlay.impl.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class for OVS Version.
+ */
+public final class OvsVersion {
+ protected static final Logger log = LoggerFactory.getLogger(OvsVersion.class);
+
+ private int[] versionElements = new int[] {0, 0, 0, Integer.MAX_VALUE};
+ private int depth = 0;
+
+ /**
+ * Constructor for OvsVersion.
+ * @param ovsVerStr OVS version string
+ */
+ private OvsVersion(String ovsVerStr) {
+
+ // Supporting
+ // 1.2.3 (depth = 3, public release)
+ // 1.2.3.4 (depth = 4, beta release)
+ Matcher m = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?").matcher(ovsVerStr);
+
+ if (!m.matches()) {
+ throw new IllegalArgumentException("Malformed OVS version");
+ }
+
+ versionElements[0] = Integer.parseInt(m.group(1));
+ versionElements[1] = Integer.parseInt(m.group(2));
+ versionElements[2] = Integer.parseInt(m.group(3));
+
+ if (m.group(4) == null) {
+ depth = 3;
+ return;
+ }
+
+ versionElements[3] = Integer.parseInt(m.group(5));
+ depth = 4;
+ }
+
+ /**
+ * Builder for OvsVersion.
+ * @param ovsVersionStr OVS version string
+ * @return ovs version
+ */
+ public static OvsVersion build(String ovsVersionStr) {
+ try {
+ return new OvsVersion(ovsVersionStr);
+ } catch (IllegalArgumentException e) {
+ log.error("Exception Occurred {}", e);
+ return null;
+ }
+ }
+
+ private int get(int level) {
+ return versionElements[level];
+ }
+
+ private int compare(OvsVersion tgt) {
+ //Comparison example
+ // 2.7.0 < 2.7.2
+ // 2.7.0 > 2.6.9.12
+ // 2.7.0 > 2.7.0.0 (because 2.7.0 is public release)
+ for (int i = 0; i < versionElements.length; i++) {
+ if (versionElements[i] == tgt.get(i)) {
+ continue;
+ } else if (versionElements[i] < tgt.get(i)) {
+ return (i + 1) * -1;
+ } else {
+ return (i + 1);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns whether this OVS version is equal to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is equal to the target OVS version
+ */
+ public boolean isEqOf(OvsVersion tgt) {
+ return (compare(tgt) == 0);
+ }
+
+ /**
+ * Returns whether this OVS version is prior to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is prior to the target OVS version
+ */
+ public boolean isPriorOf(OvsVersion tgt) {
+ return (compare(tgt) < 0);
+ }
+
+ /**
+ * Returns whether this OVS version is prior or equal to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is prior or equal to the target OVS version
+ */
+ public boolean isPriorOrEqOf(OvsVersion tgt) {
+ return (compare(tgt) <= 0);
+ }
+
+ /**
+ * Returns whether this OVS version is later to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is later to the target OVS version
+ */
+ public boolean isLaterOf(OvsVersion tgt) {
+ return (compare(tgt) > 0);
+ }
+
+ /**
+ * Returns whether this OVS version is later or equal to the target OVS version.
+ * @param tgt taret OVS version
+ * @return whether this OVS version is later or equal to the target OVS version
+ */
+ public boolean isLaterOrEqOf(OvsVersion tgt) {
+ return (compare(tgt) >= 0);
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuilder strbuild = new StringBuilder();
+ strbuild.append(versionElements[0]);
+
+ for (int i = 1; i < depth; i++) {
+ strbuild.append(".");
+ strbuild.append(versionElements[i]);
+ }
+ return strbuild.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshBehavior.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshBehavior.java
new file mode 100644
index 0000000..6189837
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshBehavior.java
@@ -0,0 +1,30 @@
+/*
+ * 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.ofoverlay.impl.util;
+
+import com.jcraft.jsch.Session;
+import org.onosproject.workflow.api.WorkflowException;
+
+/**
+ * Functional interface for ssh behavior.
+ * @param <R> return value of ssh behavior
+ */
+@FunctionalInterface
+public interface SshBehavior<R> {
+
+ R apply(Session session) throws WorkflowException;
+
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshUtil.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshUtil.java
new file mode 100644
index 0000000..0c6caff
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshUtil.java
@@ -0,0 +1,366 @@
+/*
+ * 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.ofoverlay.impl.util;
+
+import com.google.common.io.CharStreams;
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import org.onlab.packet.IpAddress;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.model.accessinfo.SshAccessInfo;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.onosproject.workflow.api.CheckCondition.check;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Class for SSH utilities.
+ */
+public final class SshUtil {
+
+ protected static final Logger log = getLogger(SshUtil.class);
+
+ private static final String STRICT_HOST_CHECKING = "StrictHostKeyChecking";
+ private static final String DEFAULT_STRICT_HOST_CHECKING = "no";
+ private static final int DEFAULT_SESSION_TIMEOUT = 30000; // milliseconds
+
+ private static final String SPACESEPERATOR = " ";
+
+ /**
+ * Default constructor.
+ */
+ private SshUtil() {
+ }
+
+ /**
+ * Creates a new session with a given ssh access information.
+ *
+ * @param sshInfo information to ssh to the remote server
+ * @return ssh session, or null
+ */
+ public static Session connect(SshAccessInfo sshInfo) {
+ Session session;
+
+ try {
+ JSch jsch = new JSch();
+ jsch.addIdentity(sshInfo.privateKey());
+
+ session = jsch.getSession(sshInfo.user(),
+ sshInfo.remoteIp().toString(),
+ sshInfo.port().toInt());
+ session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
+ session.connect(DEFAULT_SESSION_TIMEOUT);
+
+ } catch (JSchException e) {
+ log.warn("Failed to connect to {}", sshInfo.toString(), e);
+ session = authUserPwd(sshInfo);
+ }
+ return session;
+ }
+
+ /**
+ * Creates a new session with ssh access info.
+ *
+ * @param sshInfo information to ssh to the remote server
+ * @return ssh session, or null
+ */
+ public static Session authUserPwd(SshAccessInfo sshInfo) {
+ log.info("Retrying Session with {}", sshInfo);
+ try {
+ JSch jsch = new JSch();
+
+ Session session = jsch.getSession(sshInfo.user(),
+ sshInfo.remoteIp().toString(),
+ sshInfo.port().toInt());
+ session.setPassword(sshInfo.password());
+ session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
+ session.connect(DEFAULT_SESSION_TIMEOUT);
+
+ return session;
+ } catch (JSchException e) {
+ log.warn("Failed to connect to {} due to {}", sshInfo.toString(), e);
+ return null;
+ }
+ }
+
+ /**
+ * Closes a connection.
+ *
+ * @param session session ssh session
+ */
+ public static void disconnect(Session session) {
+ if (session.isConnected()) {
+ session.disconnect();
+ }
+ }
+
+ /**
+ * Fetches last term after executing command.
+ * @param session ssh session
+ * @param command command to execute
+ * @return last term, or null
+ */
+ public static String fetchLastTerm(Session session, String command) {
+ if (session == null || !session.isConnected()) {
+ log.error("Invalid session({})", session);
+ return null;
+ }
+
+ log.info("fetchLastTerm: ssh command {} to {}", command, session.getHost());
+
+ try {
+ Channel channel = session.openChannel("exec");
+ if (channel == null) {
+ log.error("Invalid channel of session({}) for command({})", session, command);
+ return null;
+ }
+
+ ((ChannelExec) channel).setCommand(command);
+ channel.setInputStream(null);
+ InputStream output = channel.getInputStream();
+ channel.connect();
+ String[] lineList = null;
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(output, StandardCharsets.UTF_8))) {
+ lineList = reader.lines().findFirst().get().split(SPACESEPERATOR);
+ } catch (IOException e) {
+ log.error("Exception in fetchLastTerm", e);
+ } finally {
+ channel.disconnect();
+ output.close();
+ }
+
+ if (lineList.length > 0) {
+ return lineList[lineList.length - 1];
+ } else {
+ return null;
+ }
+
+ } catch (JSchException | IOException e) {
+ log.error("Exception in fetchLastTerm", e);
+ return null;
+ }
+ }
+
+ /**
+ * Executes a given command. It opens exec channel for the command and closes
+ * the channel when it's done.
+ *
+ * @param session ssh connection to a remote server
+ * @param command command to execute
+ * @return command output string if the command succeeds, or null
+ */
+ public static String executeCommand(Session session, String command) {
+ if (session == null || !session.isConnected()) {
+ log.error("Invalid session({})", session);
+ return null;
+ }
+
+ log.info("executeCommand: ssh command {} to {}", command, session.getHost());
+
+ try {
+ Channel channel = session.openChannel("exec");
+
+ if (channel == null) {
+ log.debug("Invalid channel of session({}) for command({})", session, command);
+ return null;
+ }
+
+ ((ChannelExec) channel).setCommand(command);
+ channel.setInputStream(null);
+ InputStream output = channel.getInputStream();
+
+ channel.connect();
+ String result = CharStreams.toString(new InputStreamReader(output, StandardCharsets.UTF_8));
+ log.trace("SSH result(on {}): {}", session.getHost(), result);
+ channel.disconnect();
+
+ return result;
+ } catch (JSchException | IOException e) {
+ log.debug("Failed to execute command {} due to {}", command, e);
+ return null;
+ }
+ }
+
+ /**
+ * Fetches OVS version information.
+ * @param session Jsch session
+ * @return OVS version
+ * @throws WorkflowException workflow exception
+ */
+ public static OvsVersion fetchOvsVersion(Session session) throws WorkflowException {
+
+ OvsVersion devOvsVersion;
+
+ String ovsVersionStr = fetchLastTerm(session, "ovs-vswitchd --version");
+ if (ovsVersionStr == null) {
+ log.error("Failed to get ovs Version String for ssh session:{}", session);
+ throw new WorkflowException("Failed to get ovs Version String");
+ }
+
+ devOvsVersion = OvsVersion.build(ovsVersionStr);
+ if (devOvsVersion == null) {
+ log.error("Failed to build OVS version for {}", ovsVersionStr);
+ throw new WorkflowException("Failed to build OVS version");
+ }
+
+ return devOvsVersion;
+ }
+
+ private static final String IP_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
+
+ private static boolean isIPv6(String address) {
+ boolean isCorrect = true;
+ try {
+ IpAddress.valueOf(address);
+ } catch (IllegalArgumentException e) {
+ log.debug("Exception Occurred {}", e.toString());
+ isCorrect = false;
+ }
+ return isCorrect;
+ }
+
+ private static boolean isCidr(String s) {
+ String[] splits = s.split("/");
+ return splits.length == 2 &&
+ (splits[0].matches(IP_PATTERN) || isIPv6(splits[0]));
+ }
+
+ /**
+ * Adds IP address on the interface.
+ * @param session SSH session
+ * @param ifname interface name
+ * @param address network address
+ * @throws WorkflowException workflow exception
+ */
+ public static void addIpAddrOnInterface(Session session, String ifname, NetworkAddress address)
+ throws WorkflowException {
+
+ executeCommand(session, String.format("ip addr add %s dev %s", address.cidr(), ifname));
+
+ Set<NetworkAddress> result = getIpAddrOfInterface(session, ifname);
+ if (!result.contains(address)) {
+ throw new WorkflowException("Failed to set ip(" + address + ") on " + ifname + ", result: " + result);
+ }
+ }
+
+ /**
+ * Gets IP addresses of interface.
+ * @param session SSH session
+ * @param ifname interface name
+ * @return IP addresses of interface
+ */
+ public static Set<NetworkAddress> getIpAddrOfInterface(Session session, String ifname) {
+
+ String output = executeCommand(session, String.format("ip addr show %s", ifname));
+
+ if (output == null) {
+ return Collections.emptySet();
+ }
+
+ Set<NetworkAddress> result = Pattern.compile(" ")
+ .splitAsStream(output)
+ .filter(SshUtil::isCidr)
+ .map(NetworkAddress::valueOf)
+ .collect(Collectors.toSet());
+ return result;
+ }
+
+ /**
+ * Returns whether the interface has IP address.
+ * @param session SSH session
+ * @param ifname interface name
+ * @param addr network address
+ * @return whether the interface has IP address
+ */
+ public static boolean hasIpAddrOnInterface(Session session, String ifname, NetworkAddress addr) {
+
+ Set<NetworkAddress> phyBrIps = getIpAddrOfInterface(session, ifname);
+
+ return phyBrIps.stream()
+ .anyMatch(ip -> addr.ip().equals(ip.ip()));
+ }
+
+ /**
+ * Sets IP link UP on the interface.
+ * @param session SSH session
+ * @param ifname interface name
+ * @throws WorkflowException workflow exception
+ */
+ public static void setIpLinkUpOnInterface(Session session, String ifname)
+ throws WorkflowException {
+
+ executeCommand(session, String.format("ip link set %s up", ifname));
+
+ if (!isIpLinkUpOnInterface(session, ifname)) {
+ throw new WorkflowException("Failed to set UP on " + ifname);
+ }
+ }
+
+ /**
+ * Returns whether the link of the interface is up.
+ * @param session SSH session
+ * @param ifname interface name
+ * @return whether the link of the interface is up
+ */
+ public static boolean isIpLinkUpOnInterface(Session session, String ifname) {
+ String output = executeCommand(session, String.format("ip link show %s", ifname));
+
+ return output != null && output.contains("UP");
+ }
+
+ /**
+ * Executes SSH behavior.
+ * @param sshAccessInfo SSH Access information
+ * @param behavior SSH behavior
+ * @param <R> Return type of SSH behavior
+ * @return return of SSH behavior
+ * @throws WorkflowException workflow exception
+ */
+ public static <R> R exec(SshAccessInfo sshAccessInfo, SshBehavior<R> behavior)
+ throws WorkflowException {
+
+ check(sshAccessInfo != null, "Invalid sshAccessInfo");
+ Session session = connect(sshAccessInfo);
+ if (session == null || !session.isConnected()) {
+ log.error("Failed to get session for ssh:{}", sshAccessInfo);
+ throw new WorkflowException("Failed to get session for ssh:" + sshAccessInfo);
+ }
+
+ try {
+ return behavior.apply(session);
+ } finally {
+ disconnect(session);
+ }
+ }
+
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshkeyExchange.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshkeyExchange.java
new file mode 100644
index 0000000..5615112
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/SshkeyExchange.java
@@ -0,0 +1,198 @@
+/*
+ * 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.ofoverlay.impl.util;
+
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SftpATTRS;
+import com.jcraft.jsch.SftpException;
+import org.onlab.packet.IpAddress;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Class for SSH key exchange.
+ */
+public class SshkeyExchange {
+
+ protected static final Logger log = getLogger(SshkeyExchange.class);
+ private static final String HOME_ENV = System.getProperty("user.home");
+ private static final String USER_ENV = System.getProperty("user.name");
+ private static final String PUBLIC_KEY = "/.ssh/id_rsa.pub";
+ private static final String PRIVATE_KEY = "/.ssh/id_rsa";
+ private static final String SFTP_CHANNEL = "sftp";
+ private static final String SSH_HOME = "/.ssh/";
+ private static final String SSH_AUTH_KEY = "/.ssh/authorized_keys";
+ private static final int SFTPPORT = 22;
+ private static final int DIR_PER = 448;
+ private static final int FILE_PER = 384;
+ private static final int KEY_SIZE = 2048;
+ private static final int DEFAULT_SESSION_TIMEOUT = 30000; // milliseconds
+
+ private Session getJschSession(String user, String host, String password) {
+ java.util.Properties config = new java.util.Properties();
+ config.put("StrictHostKeyChecking", "no");
+ Session session;
+ try {
+ session = new JSch().getSession(user, host, SFTPPORT);
+ session.setPassword(password);
+ session.setConfig(config);
+ } catch (JSchException e) {
+ log.error("Exception in getJschSession", e);
+ return null;
+ }
+
+ return session;
+ }
+
+ private boolean generateKeyPair() {
+ KeyPair kpair;
+ StringBuilder command = new StringBuilder()
+ .append("chmod 600 ")
+ .append(HOME_ENV)
+ .append(PRIVATE_KEY);
+ try {
+ kpair = KeyPair.genKeyPair(new JSch(), KeyPair.RSA, KEY_SIZE);
+ kpair.writePrivateKey(HOME_ENV + PRIVATE_KEY);
+ kpair.writePublicKey(HOME_ENV + PUBLIC_KEY, USER_ENV);
+ Runtime.getRuntime().exec(command.toString());
+ kpair.dispose();
+ } catch (JSchException | IOException e) {
+ log.error("Exception in generateKeyPair", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Exchanges SSH key.
+ * @param host SSH server host
+ * @param user user
+ * @param password password
+ * @return SSH key exchange success or not
+ */
+ public boolean sshAutoKeyExchange(IpAddress host, String user, String password) {
+
+ Session session = getJschSession(user, host.toString(), password);
+ boolean returnFlag;
+ File publickeyPath = new File(HOME_ENV + PUBLIC_KEY);
+ if (session == null) {
+ log.error("Error While establishing SFTP connection with {}", host.toString());
+ return false;
+ }
+ Channel channel;
+ String remoteHome;
+ ChannelSftp sftp;
+ FileInputStream fis = null;
+ SftpATTRS attrs = null;
+ try {
+ session.connect(DEFAULT_SESSION_TIMEOUT);
+ channel = session.openChannel(SFTP_CHANNEL);
+ if (channel == null) {
+ log.error("SFTP channel open failed for {}", host.toString());
+ return false;
+ }
+ channel.connect();
+ sftp = (ChannelSftp) channel;
+ remoteHome = sftp.getHome();
+ // checking key pair existance
+
+ if (!publickeyPath.exists()) {
+ File dirs = new File(HOME_ENV + SSH_HOME);
+ if (!dirs.exists() && !dirs.mkdirs()) {
+ log.error("{} not exists and unable to create ", dirs.getPath());
+ return false;
+ } else if (!generateKeyPair()) {
+ log.error("SSH Key pair generation failed");
+ return false;
+ }
+ }
+
+ // checking for authenticate_keys file existance
+ fis = new FileInputStream(publickeyPath);
+ try {
+ sftp.lstat(remoteHome + SSH_HOME);
+ } catch (SftpException e) {
+ sftp.mkdir(remoteHome + SSH_HOME);
+ sftp.chmod(700, remoteHome + SSH_HOME);
+ }
+ try {
+ attrs = sftp.lstat(remoteHome + SSH_AUTH_KEY);
+ } catch (SftpException e) {
+ log.info("authorized_keys file does not exist at remote device ,"
+ + "a new file will be created");
+ }
+
+ if (attrs != null) {
+ sftp.get(remoteHome + SSH_AUTH_KEY, HOME_ENV + "/tempauthorized_keys");
+
+ String pubKey;
+ try (Stream<String> st = Files.lines(Paths.get(HOME_ENV + PUBLIC_KEY))) {
+ pubKey = st.collect(Collectors.joining());
+ }
+
+ String authKey;
+ try (Stream<String> st = Files.lines(Paths.get(HOME_ENV + "/tempauthorized_keys"))) {
+ authKey = st.collect(Collectors.joining());
+ }
+
+ if (authKey.contains(pubKey)) {
+ log.info("Skipping key append to server as Key is already added");
+ } else {
+ sftp.put(fis, remoteHome + SSH_AUTH_KEY, ChannelSftp.APPEND);
+ log.info("Public key appended to server");
+ }
+ } else {
+
+ sftp.put(fis, remoteHome + SSH_AUTH_KEY, ChannelSftp.APPEND);
+ // Give proper permission to file and directory.
+ sftp.chmod(DIR_PER, remoteHome + SSH_HOME);
+ sftp.chmod(FILE_PER, remoteHome + SSH_AUTH_KEY);
+ log.info("Public key appended to server");
+ }
+
+ sftp.exit();
+ session.disconnect();
+ returnFlag = true;
+ } catch (JSchException | SftpException | IOException e) {
+ log.error("Exception occured because of {} ", e);
+ returnFlag = false;
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ log.info("Error closing public key file");
+ }
+ }
+ }
+ return returnFlag;
+
+ }
+}
diff --git a/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/package-info.java b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/package-info.java
new file mode 100644
index 0000000..c76ba58
--- /dev/null
+++ b/apps/workflow/ofoverlay/app/src/main/java/org/onosproject/ofoverlay/impl/util/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Utility classes of ofoverlay.
+ */
+package org.onosproject.ofoverlay.impl.util;
\ No newline at end of file
diff --git a/apps/workflow/ofoverlay/test-cfg/empty.json b/apps/workflow/ofoverlay/test-cfg/empty.json
new file mode 100755
index 0000000..2691a31
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/empty.json
@@ -0,0 +1,12 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ ]
+
+ }
+
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/test-cfg/network-cfg-cr-wf.json b/apps/workflow/ofoverlay/test-cfg/network-cfg-cr-wf.json
new file mode 100755
index 0000000..19e2669
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/network-cfg-cr-wf.json
@@ -0,0 +1,317 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-000",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.8",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.8",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova0_1" ],
+ "vtepIp" : "120.0.0.200/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-001",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.9",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.9",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova1_1" ],
+ "vtepIp" : "120.0.0.201/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-002",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.10",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.10",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova2_1" ],
+ "vtepIp" : "120.0.0.202/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-003",
+ "id" : "of-overlay.workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.11",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.11",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "nova3_1" ],
+ "vtepIp" : "120.0.0.203/24",
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-000",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.4",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.4",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf0_0", "leaf0_1", "leaf0_nova00" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-001",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.5",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.5",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf1_0", "leaf1_1", "leaf1_nova10" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-002",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.6",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.6",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf2_0", "leaf2_1", "leaf2_nova20" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Leaf-003",
+ "id" : "of-overlay.workflow-ovs-leaf",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.7",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.7",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "leaf3_0", "leaf3_1", "leaf3_nova30" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Spine-000",
+ "id" : "of-overlay.workflow-ovs-spine",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.2",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.2",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "spine0_0", "spine0_1", "spine0_2", "spine0_3" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Spine-001",
+ "id" : "of-overlay.workflow-ovs-spine",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.3",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.3",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ },
+
+ "ovsDatapathType" : "system",
+ "physicalPorts" : [ "spine1_0", "spine1_1", "spine1_2", "spine1_3" ],
+
+ "annotations" : {
+ "rackId" : 1,
+ "rackPosition" : 3
+ }
+
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ }
+
+ ]
+ }
+ }
+ }
+}
diff --git a/apps/workflow/ofoverlay/test-cfg/network-cfg-rm-wf.json b/apps/workflow/ofoverlay/test-cfg/network-cfg-rm-wf.json
new file mode 100755
index 0000000..be554a9
--- /dev/null
+++ b/apps/workflow/ofoverlay/test-cfg/network-cfg-rm-wf.json
@@ -0,0 +1,98 @@
+{
+ "apps": {
+ "org.onosproject.workflow": {
+ "workflow" : {
+ "rpc" : [
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-000",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.8",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.8",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-001",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.9",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.9",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-002",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.10",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.10",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ },
+ {
+ "op" : "workflow.invoke",
+ "params" : {
+ "workplace" : "Nova-003",
+ "id" : "of-overlay.clean-workflow-nova",
+ "data" : {
+
+ "mgmtIp" : "192.168.10.11",
+ "ovsdbPort" : 6641,
+
+ "sshAccessInfo" : {
+ "remoteIp" : "192.168.10.11",
+ "port" : 22,
+ "user" : "root",
+ "password" : "iloveyou",
+ "keyfile" : "~/.ssh/id_rsa"
+ }
+ }
+ },
+ "id" : "00001@10.0.0.1"
+ }
+
+
+ ]
+
+ }
+
+ }
+ }
+}