blob: 923173c98ea67b83692f19af43467be9747a7340 [file] [log] [blame]
/*
* 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.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.api.StaticDataModel;
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.Set;
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 BRIDGE_NAME = "/bridgeName";
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_BRIDGE = "/ofDevId";
private static final String MODEL_OF_DEVID_FOR_OVERLAY_UNDERLAY_BRIDGE = "/ofDevIdBrIntBrPhy";
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();
}
public static BridgeDescription getBridgeNames(BridgeConfig bridgeConfig) {
Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
if (bridges.size() > 0) {
for (BridgeDescription description : bridges) {
return description;
}
}
return null;
}
/**
* 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 UpdateBridgeId extends AbstractWorklet {
@JsonDataModel(path = MODEL_MGMT_IP)
String strMgmtIp;
@JsonDataModel(path = MODEL_OF_DEVID_BRIDGE, optional = true)
ObjectNode strOfDevId;
@Override
public boolean isNext(WorkflowContext context) throws WorkflowException {
return strOfDevId == null;
}
@Override
public void process(WorkflowContext context) throws WorkflowException {
BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
BridgeDescription bridgeName = OvsUtil.getBridgeNames(bridgeConfig);
strOfDevId = JsonNodeFactory.instance.objectNode();
if (Objects.nonNull(bridgeName)) {
Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, bridgeName.name());
if (optBd.isPresent()) {
String ofDevIdOverlay = strOfDevId.get(BRIDGE_OVERLAY).asText();
String ofDevIdUnderlay = strOfDevId.get(BRIDGE_UNDERLAY).asText();
if (Objects.nonNull(ofDevIdOverlay) || Objects.nonNull(ofDevIdUnderlay)) {
strOfDevId.put(BRIDGE_OVERLAY, OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY).toString());
strOfDevId.put(BRIDGE_UNDERLAY, OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA).toString());
} else {
strOfDevId.put(BRIDGE_OVERLAY, OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY).toString());
strOfDevId.put(BRIDGE_UNDERLAY, OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA).toString());
log.info("Failed to find devId. Updates of device id with new device id {}", strOfDevId);
}
}
} else {
strOfDevId.put(BRIDGE_OVERLAY, OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY).toString());
strOfDevId.put(BRIDGE_UNDERLAY, OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA).toString());
log.info("Failed to find description. Updates of device id with new device id {}",
strOfDevId);
}
context.completed();
}
}
public static class CreateBridge extends AbstractWorklet {
@StaticDataModel(path = BRIDGE_NAME)
String bridgeName;
@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_BRIDGE, optional = true)
ObjectNode strOfDevId;
@Override
public boolean isNext(WorkflowContext context) throws WorkflowException {
check(strOfDevId != null, "invalid strOfDevIdUnderlay");
String bridgeId = strOfDevId.get(bridgeName).asText();
return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(bridgeId));
}
@Override
public void process(WorkflowContext context) throws WorkflowException {
check(strOfDevId != null, "invalid strOfDevIdOverlay");
BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
String bridge = strOfDevId.get(bridgeName).asText();
DeviceId ofDeviceId = DeviceId.deviceId(bridge);
if (ofControllers == null || ofControllers.size() == 0) {
throw new WorkflowException("Invalid of controllers");
}
Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, bridgeName);
if (!optBd.isPresent()) {
// If bridge does not exist, just creates a new bridge.
context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
() -> OvsUtil.createBridge(bridgeConfig,
bridgeName,
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({})", bridgeName, 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 overlay openflow bridge.
*/
public static class CreateOverlayBridgeMultiEvent 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_BRIDGE, optional = true)
ObjectNode strOfDevIdOverlay;
@Override
public boolean isNext(WorkflowContext context) throws WorkflowException {
check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
String bridge = strOfDevIdOverlay.get(BRIDGE_OVERLAY).asText();
return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(bridge));
}
@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);
String bridge = strOfDevIdOverlay.get(BRIDGE_OVERLAY).asText();
DeviceId ofDeviceId = DeviceId.deviceId(bridge);
if (ofControllers == null || ofControllers.size() == 0) {
throw new WorkflowException("Invalid of controllers");
}
Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
if (!optBd.isPresent()) {
Set<String> eventHints = Sets.newHashSet(ofDeviceId.toString());
context.waitAnyCompletion(DeviceEvent.class, eventHints,
() -> 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.
Set<String> eventHints = Sets.newHashSet(ofDeviceId.toString());
context.waitAnyCompletion(DeviceEvent.class, eventHints,
() -> 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_BRIDGE, optional = true)
ObjectNode strOfDevIdOverlay;
private static final String OVS_VXLAN_PORTNAME = "vxlan";
@Override
public boolean isNext(WorkflowContext context) throws WorkflowException {
check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
String bridge = strOfDevIdOverlay.get(BRIDGE_OVERLAY).asText();
DeviceId deviceId = DeviceId.deviceId(bridge);
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();
String bridge = strOfDevIdOverlay.get(BRIDGE_OVERLAY).asText();
DeviceId ofDeviceId = DeviceId.deviceId(bridge);
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_BRIDGE, optional = true)
ObjectNode 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");
String bridge = strOfDevIdUnderlay.get(BRIDGE_UNDERLAY).asText();
DeviceId brphyDevId = DeviceId.deviceId(bridge);
return !hasAllPhysicalPorts(context, brphyDevId);
}
@Override
public void process(WorkflowContext context) throws WorkflowException {
check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
String bridge = strOfDevIdUnderlay.get(BRIDGE_UNDERLAY).asText();
DeviceId brphyDevId = DeviceId.deviceId(bridge);
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);
}
}
}
/**
* Work-let class for removing underlay bridge and overlay openflow device.
*/
public static class RemoveBridgeOfDevice extends AbstractWorklet {
@JsonDataModel(path = MODEL_MGMT_IP)
String strMgmtIp;
@JsonDataModel(path = MODEL_OF_DEVID_FOR_OVERLAY_UNDERLAY_BRIDGE, optional = true)
ObjectNode ofDevId;
@Override
public boolean isNext(WorkflowContext context) throws WorkflowException {
boolean isOfDevicePresent = true;
if (ofDevId == null) {
ofDevId = JsonNodeFactory.instance.objectNode();
ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY), OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY).toString());
ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA), OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA).toString());
}
if (context.getService(DeviceService.class).
getDevice(DeviceId.deviceId(
ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY)).asText())) == null) {
isOfDevicePresent = false;
}
if (context.getService(DeviceService.class).
getDevice(DeviceId.deviceId(
ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA)).asText())) == null) {
isOfDevicePresent = false;
}
return isOfDevicePresent;
}
@Override
public void process(WorkflowContext context) throws WorkflowException {
DeviceAdminService adminService = context.getService(DeviceAdminService.class);
String ofDevIdOverlay;
String ofDevIdUnderlay;
if (ofDevId == null) {
ofDevId = JsonNodeFactory.instance.objectNode();
ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY), OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY).toString());
ofDevId.put(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA), OvsUtil.buildOfDeviceId(
IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA).toString());
}
ofDevIdOverlay = ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_OVERLAY)).asText();
ofDevIdUnderlay = ofDevId.get(String.valueOf(DEVID_IDX_BRIDGE_UNDERLAY_NOVA)).asText();
Set<String> eventHints = Sets.newHashSet(ofDevIdOverlay, ofDevIdUnderlay);
context.waitAnyCompletion(DeviceEvent.class, eventHints,
() -> {
adminService.removeDevice(DeviceId.deviceId(ofDevIdOverlay));
adminService.removeDevice(DeviceId.deviceId(ofDevIdUnderlay));
},
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:
log.trace("GOT DEVICE REMOVED EVENT FOR DEVICE {}", event.subject());
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);
}
}
}
}