blob: a281e09bf3c2ee97a811809eba3fa6cfd5c13a09 [file] [log] [blame]
/*
* Copyright 2017-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.ovsdbrest;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Reference;
import org.onlab.packet.IpAddress;
import org.onlab.util.ItemNotFoundException;
import org.onosproject.cluster.ClusterService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.behaviour.BridgeConfig;
import org.onosproject.net.behaviour.BridgeDescription;
import org.onosproject.net.behaviour.BridgeName;
import org.onosproject.net.behaviour.ControllerConfig;
import org.onosproject.net.behaviour.ControllerInfo;
import org.onosproject.net.behaviour.DefaultBridgeDescription;
import org.onosproject.net.behaviour.DefaultPatchDescription;
import org.onosproject.net.behaviour.DefaultTunnelDescription;
import org.onosproject.net.behaviour.InterfaceConfig;
import org.onosproject.net.behaviour.PatchDescription;
import org.onosproject.net.behaviour.TunnelDescription;
import org.onosproject.net.behaviour.TunnelEndPoints;
import org.onosproject.net.behaviour.TunnelKey;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.device.DeviceAdminService;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.driver.DriverHandler;
import org.onosproject.net.driver.DriverService;
import org.onosproject.ovsdb.controller.OvsdbClientService;
import org.onosproject.ovsdb.controller.OvsdbController;
import org.onosproject.ovsdb.controller.OvsdbNodeId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.Optional;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicLong;
import static org.onosproject.ovsdbrest.OvsdbNodeConfig.OvsdbNode;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.ovsdbrest.OvsdbRestException.BridgeAlreadyExistsException;
import static org.onosproject.ovsdbrest.OvsdbRestException.BridgeNotFoundException;
import static org.onosproject.ovsdbrest.OvsdbRestException.OvsdbDeviceException;
/**
* Bridge and port controller.
*/
@Component(immediate = true)
@Service
public class OvsdbBridgeManager implements OvsdbBridgeService {
private final Logger log = LoggerFactory.getLogger(getClass());
private ApplicationId appId;
private static final int DPID_BEGIN = 4;
private static final int OFPORT = 6653;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterService clusterService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService configService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OvsdbController controller;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceAdminService adminService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DriverService driverService;
private Set<OvsdbNode> ovsdbNodes;
// {bridgeName: datapathId} structure to manage the creation/deletion of bridges
private Map<String, DeviceId> bridgeIds = Maps.newConcurrentMap();
private Map<OvsdbNode, Set<DeviceId>> ovsdbNodeDevIdsSetMap = Maps.newConcurrentMap();
private final ExecutorService eventExecutor =
newSingleThreadExecutor(groupedThreads("onos/ovsdb-rest-ctl", "event-handler", log));
private final NetworkConfigListener configListener = new InternalConfigListener();
private final AtomicLong datapathId = new AtomicLong(DPID_BEGIN);
private final ConfigFactory configFactory =
new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, OvsdbNodeConfig.class, "ovsdbrest") {
@Override
public OvsdbNodeConfig createConfig() {
return new OvsdbNodeConfig();
}
};
@Activate
protected void activate() {
appId = coreService.getAppId("org.onosproject.ovsdbrest");
configService.addListener(configListener);
configRegistry.registerConfigFactory(configFactory);
log.info("Started");
}
@Deactivate
protected void deactivate() {
configService.removeListener(configListener);
configRegistry.unregisterConfigFactory(configFactory);
eventExecutor.shutdown();
log.info("Stopped");
}
@Override
public void createBridge(IpAddress ovsdbAddress, String bridgeName)
throws OvsdbDeviceException, BridgeAlreadyExistsException {
OvsdbNode ovsdbNode;
log.debug("Creating bridge {} at {}", bridgeName, ovsdbAddress);
try {
// gets the target ovsdb node
ovsdbNode = ovsdbNodes.stream().filter(node -> node.ovsdbIp().equals(ovsdbAddress)).findFirst().get();
} catch (NoSuchElementException nsee) {
log.info(nsee.getMessage());
throw new OvsdbDeviceException(nsee.getMessage());
}
// construct a unique dev id'
DeviceId dpid = getNextUniqueDatapathId(datapathId);
if (isBridgeCreated(bridgeName)) {
log.warn("A bridge with this name already exists, aborting.");
throw new BridgeAlreadyExistsException();
}
List<ControllerInfo> controllers = new ArrayList<>();
Sets.newHashSet(clusterService.getNodes()).forEach(controller -> {
ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
controllers.add(ctrlInfo);
log.info("controller {}:{} added", ctrlInfo.ip().toString(), ctrlInfo.port());
});
try {
Device device = deviceService.getDevice(ovsdbNode.ovsdbId());
if (device == null) {
log.warn("Ovsdb device not found, aborting.");
throw new OvsdbDeviceException("Ovsdb device not found");
}
if (device.is(BridgeConfig.class)) {
BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
BridgeDescription bridgeDescription = DefaultBridgeDescription.builder()
.name(bridgeName)
.datapathId(dpid.toString())
.controllers(controllers)
.build();
bridgeConfig.addBridge(bridgeDescription);
bridgeIds.put(bridgeName, bridgeDescription.deviceId().get());
log.info("Correctly created bridge {} at {}", bridgeName, ovsdbAddress);
} else {
log.warn("The bridging behaviour is not supported in device {}", device.id());
throw new OvsdbDeviceException(
"The bridging behaviour is not supported in device " + device.id()
);
}
} catch (ItemNotFoundException e) {
log.warn("Failed to create integration bridge on {}", ovsdbNode.ovsdbIp());
throw new OvsdbDeviceException("Error with ovsdb device: item not found");
}
}
@Override
public void deleteBridge(IpAddress ovsdbAddress, String bridgeName)
throws OvsdbDeviceException, BridgeNotFoundException {
OvsdbNode ovsdbNode;
log.debug("Deleting bridge {} at {}", bridgeName, ovsdbAddress);
try {
// gets the target ovsdb node
ovsdbNode = ovsdbNodes.stream().filter(node -> node.ovsdbIp().equals(ovsdbAddress)).findFirst().get();
} catch (NoSuchElementException nsee) {
log.warn(nsee.getMessage());
throw new OvsdbDeviceException(nsee.getMessage());
}
DeviceId deviceId = bridgeIds.get(bridgeName);
if (deviceId == null) {
log.warn("No bridge with this name, aborting.");
throw new BridgeNotFoundException();
}
log.debug("Device id is: " + deviceId.toString());
try {
Device device = deviceService.getDevice(ovsdbNode.ovsdbId());
if (device == null) {
log.warn("Ovsdb device not found, aborting.");
throw new OvsdbDeviceException("Ovsdb device not found");
}
if (device.is(BridgeConfig.class)) {
// unregister bridge from its controllers
deviceId = DeviceId.deviceId(deviceId.uri());
DriverHandler h = driverService.createHandler(deviceId);
ControllerConfig controllerConfig = h.behaviour(ControllerConfig.class);
controllerConfig.setControllers(new ArrayList<>());
// remove bridge from ovsdb
BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
bridgeConfig.deleteBridge(BridgeName.bridgeName(bridgeName));
bridgeIds.remove(bridgeName);
// remove bridge from onos devices
adminService.removeDevice(deviceId);
log.info("Correctly deleted bridge {} at {}", bridgeName, ovsdbAddress);
} else {
log.warn("The bridging behaviour is not supported in device {}", device.id());
throw new OvsdbDeviceException(
"The bridging behaviour is not supported in device " + device.id()
);
}
} catch (ItemNotFoundException e) {
log.warn("Failed to delete bridge on {}", ovsdbNode.ovsdbIp());
throw new OvsdbDeviceException("Error with ovsdb device: item not found");
}
}
@Override
public void addPort(IpAddress ovsdbAddress, String bridgeName, String portName)
throws OvsdbDeviceException, BridgeNotFoundException {
OvsdbNode ovsdbNode;
log.debug("Adding port {} to bridge {} at {}", portName, bridgeName, ovsdbAddress);
try {
// gets the target ovsdb node
ovsdbNode = ovsdbNodes.stream().filter(node -> node.ovsdbIp().equals(ovsdbAddress)).findFirst().get();
} catch (NoSuchElementException nsee) {
log.warn(nsee.getMessage());
throw new OvsdbDeviceException(nsee.getMessage());
}
try {
Device device = deviceService.getDevice(ovsdbNode.ovsdbId());
log.debug("OvsdbNode.ovsdbId = " + ovsdbNode.ovsdbId());
if (device == null) {
log.warn("Ovsdb device not found, aborting.");
throw new OvsdbDeviceException("Ovsdb device not found");
}
if (device.is(BridgeConfig.class)) {
// add port to bridge through ovsdb
BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), portName);
log.info("Correctly added port {} to bridge {} at {}", portName, bridgeName, ovsdbAddress);
} else {
log.warn("The bridging behaviour is not supported in device {}", device.id());
throw new OvsdbDeviceException(
"The bridging behaviour is not supported in device " + device.id()
);
}
} catch (ItemNotFoundException e) {
log.warn("Failed to delete bridge on {}", ovsdbNode.ovsdbIp());
throw new OvsdbDeviceException("Error with ovsdb device: item not found");
}
}
@Override
public void removePort(IpAddress ovsdbAddress, String bridgeName, String portName)
throws OvsdbDeviceException, BridgeNotFoundException {
OvsdbNode ovsdbNode;
log.debug("Deleting port {} to bridge {} at {}", portName, bridgeName, ovsdbAddress);
try {
// gets the target ovsdb node
ovsdbNode = ovsdbNodes.stream().filter(node -> node.ovsdbIp().equals(ovsdbAddress)).findFirst().get();
} catch (NoSuchElementException nsee) {
log.warn(nsee.getMessage());
throw new OvsdbDeviceException(nsee.getMessage());
}
try {
Device device = deviceService.getDevice(ovsdbNode.ovsdbId());
if (device == null) {
log.warn("Ovsdb device not found, aborting.");
throw new OvsdbDeviceException("Ovsdb device not found");
}
if (device.is(BridgeConfig.class)) {
// delete port from bridge through ovsdb
BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), portName);
log.info("Correctly deleted port {} from bridge {} at {}", portName, bridgeName, ovsdbAddress);
} else {
log.warn("The bridging behaviour is not supported in device {}", device.id());
throw new OvsdbDeviceException(
"The bridging behaviour is not supported in device " + device.id()
);
}
} catch (ItemNotFoundException e) {
log.warn("Failed to delete bridge on {}", ovsdbNode.ovsdbIp());
throw new OvsdbDeviceException("Error with ovsdb device: item not found");
}
}
@Override
public void createPatchPeerPort(IpAddress ovsdbAddress, String bridgeName, String portName, String patchPeer)
throws OvsdbDeviceException {
OvsdbNode ovsdbNode;
log.debug("Setting port {} as peer of port {}", portName, patchPeer);
try {
// gets the target ovsdb node
ovsdbNode = ovsdbNodes.stream().filter(node -> node.ovsdbIp().equals(ovsdbAddress)).findFirst().get();
} catch (NoSuchElementException nsee) {
log.warn(nsee.getMessage());
throw new OvsdbDeviceException(nsee.getMessage());
}
Device device = deviceService.getDevice(ovsdbNode.ovsdbId());
log.debug("OvsdbNode.ovsdbId = " + ovsdbNode.ovsdbId());
if (device == null) {
log.warn("Ovsdb device not found, aborting.");
throw new OvsdbDeviceException("Ovsdb device not found");
}
if (device.is(InterfaceConfig.class)) {
InterfaceConfig interfaceConfig = device.as(InterfaceConfig.class);
// prepare patch
PatchDescription.Builder builder = DefaultPatchDescription.builder();
PatchDescription patchDescription = builder
.deviceId(bridgeName)
.ifaceName(portName)
.peer(patchPeer)
.build();
// add patch to port through ovsdb
interfaceConfig.addPatchMode(portName, patchDescription);
log.info("Correctly created port {} on device {} as peer of port {}", portName, bridgeName, patchPeer);
} else {
log.warn("The interface behaviour is not supported in device {}", device.id());
throw new OvsdbDeviceException(
"The interface behaviour is not supported in device " + device.id()
);
}
}
@Override
public void createGreTunnel(IpAddress ovsdbAddress, String bridgeName, String portName, IpAddress localIp,
IpAddress remoteIp, String key)
throws OvsdbDeviceException, BridgeNotFoundException {
OvsdbNode ovsdbNode;
log.debug("Setting up tunnel GRE from {} to {} with key {}",
localIp, remoteIp, key);
try {
// gets the target ovsdb node
ovsdbNode = ovsdbNodes.stream().filter(node -> node.ovsdbIp().equals(ovsdbAddress)).findFirst().get();
} catch (NoSuchElementException nsee) {
log.warn(nsee.getMessage());
throw new OvsdbDeviceException(nsee.getMessage());
}
try {
Device device = deviceService.getDevice(ovsdbNode.ovsdbId());
log.debug("OvsdbNode.ovsdbId = " + ovsdbNode.ovsdbId());
if (device == null) {
log.warn("Ovsdb device not found, aborting.");
throw new OvsdbDeviceException("Ovsdb device not found");
}
if (device.is(InterfaceConfig.class)) {
InterfaceConfig interfaceConfig = device.as(InterfaceConfig.class);
// prepare tunnel
TunnelDescription tunnelDescription = DefaultTunnelDescription.builder()
.deviceId(bridgeName)
.ifaceName(portName)
.type(TunnelDescription.Type.GRE)
.local(TunnelEndPoints.ipTunnelEndpoint(localIp))
.remote(TunnelEndPoints.ipTunnelEndpoint(remoteIp))
.key(new TunnelKey<>(key))
.build();
// create tunnel to port through ovsdb
interfaceConfig.addTunnelMode(portName, tunnelDescription);
log.info("Correctly added tunnel GRE from {} to {} with key {}",
localIp, remoteIp, key);
} else {
log.warn("The interface behaviour is not supported in device {}", device.id());
throw new OvsdbDeviceException(
"The interface behaviour is not supported in device " + device.id()
);
}
} catch (ItemNotFoundException e) {
log.warn("Failed to delete bridge on {}", ovsdbNode.ovsdbIp());
throw new OvsdbDeviceException("Error with ovsdb device: item not found");
}
}
@Override
public void deleteGreTunnel(IpAddress ovsdbAddress, String bridgeName, String portName)
throws OvsdbDeviceException {
OvsdbNode ovsdbNode;
log.debug("Deleting tunnel GRE from interface {}",
portName);
try {
// gets the target ovsdb node
ovsdbNode = ovsdbNodes.stream().filter(node -> node.ovsdbIp().equals(ovsdbAddress)).findFirst().get();
} catch (NoSuchElementException nsee) {
log.warn(nsee.getMessage());
throw new OvsdbDeviceException(nsee.getMessage());
}
try {
Device device = deviceService.getDevice(ovsdbNode.ovsdbId());
if (device == null) {
log.warn("Ovsdb device not found, aborting.");
throw new OvsdbDeviceException("Ovsdb device not found");
}
if (device.is(InterfaceConfig.class)) {
InterfaceConfig interfaceConfig = device.as(InterfaceConfig.class);
// remove tunnel through ovsdb
interfaceConfig.removeTunnelMode(portName);
log.info("Correctly deleted tunnel GRE from interface {}", portName);
} else {
log.warn("The interface behaviour is not supported in device {}", device.id());
throw new OvsdbDeviceException(
"The interface behaviour is not supported in device " + device.id()
);
}
} catch (ItemNotFoundException e) {
log.warn("Failed to delete bridge on {}", ovsdbNode.ovsdbIp());
throw new OvsdbDeviceException("Error with ovsdb device: item not found");
}
}
/**
* Performs the connection to ovsdb.
*
* @param node the ovsdb node, with IP address and port
*/
private void connectOvsdb(OvsdbNode node) {
if (!isOvsdbConnected(node)) {
log.info("connecting ovsdb at {}:{}", node.ovsdbIp(), node.ovsdbPort());
controller.connect(node.ovsdbIp(), node.ovsdbPort());
}
}
/**
* Gets an available datapath id for the new bridge.
*
* @param datapathId the integer used to generate ids
* @return the datapath id
*/
private DeviceId getNextUniqueDatapathId(AtomicLong datapathId) {
DeviceId dpid;
do {
String stringId = String.format("%16X", datapathId.getAndIncrement()).replace(' ', '0');
log.info("String id is: " + stringId);
dpid = DeviceId.deviceId(stringId);
} while (deviceService.getDevice(dpid) != null);
return dpid;
}
/**
* Checks if the bridge exists and is available.
*
* @return true if the bridge is available, false otherwise
*/
private boolean isBridgeCreated(String bridgeName) {
DeviceId deviceId = bridgeIds.get(bridgeName);
return (deviceId != null
&& deviceService.getDevice(deviceId) != null
&& deviceService.isAvailable(deviceId));
}
/**
* Returns connection state of OVSDB server for a given node.
*
* @return true if it is connected, false otherwise
*/
private boolean isOvsdbConnected(OvsdbNode node) {
OvsdbClientService ovsdbClient = getOvsdbClient(node);
return deviceService.isAvailable(node.ovsdbId()) &&
ovsdbClient != null && ovsdbClient.isConnected();
}
/**
* Returns OVSDB client for a given node.
*
* @return OVSDB client, or null if it fails to get OVSDB client
*/
private OvsdbClientService getOvsdbClient(OvsdbNode node) {
OvsdbClientService ovsdbClient = controller.getOvsdbClient(
new OvsdbNodeId(node.ovsdbIp(), node.ovsdbPort().toInt()));
if (ovsdbClient == null) {
log.trace("Couldn't find OVSDB client for {}", node.ovsdbId().toString());
}
return ovsdbClient;
}
/**
* Returns an ovsdb node associated with a given OVSDB device.
*
* @param ovsdbId OVSDB device id
* @return cordvtn node, null if it fails to find the node
*/
private OvsdbNode nodeByOvsdbId(DeviceId ovsdbId) {
return ovsdbNodes.stream()
.filter(node -> node.ovsdbId().equals(ovsdbId))
.findFirst().orElse(null);
}
/**
* Returns ovsdb node associated with a given integration bridge.
*
* @param bridgeId device id of the bridge
* @return ovsdb node, null if it fails to find the node
*/
private OvsdbNode nodeByBridgeId(DeviceId bridgeId) {
final Set<OvsdbNode> nodes = new HashSet<>();
ovsdbNodeDevIdsSetMap.forEach((node, set) -> {
if (set.contains(bridgeId)) {
nodes.add(node);
}
});
Optional<OvsdbNode> opt = nodes.stream().findAny();
if (opt.isPresent()) {
return opt.get();
} else {
return null;
}
}
private void readConfiguration() {
OvsdbNodeConfig config = configRegistry.getConfig(appId, OvsdbNodeConfig.class);
if (config == null) {
log.debug("No configuration found");
return;
}
ovsdbNodes = config.getNodes();
ovsdbNodes.forEach(this::connectOvsdb);
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (!event.configClass().equals(OvsdbNodeConfig.class)) {
return;
}
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
eventExecutor.execute(OvsdbBridgeManager.this::readConfiguration);
break;
default:
break;
}
}
}
}