2 * Copyright (c) 2016, 2017 Red Hat, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netvirt.elan.internal;
10 import com.google.common.base.Splitter;
11 import com.google.common.base.Strings;
12 import com.google.common.collect.Lists;
14 import java.math.BigInteger;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.List;
19 import java.util.Random;
21 import javax.inject.Inject;
22 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
23 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
24 import org.opendaylight.netvirt.elanmanager.api.IElanBridgeManager;
25 import org.opendaylight.ovsdb.utils.config.ConfigProperties;
26 import org.opendaylight.ovsdb.utils.mdsal.utils.MdsalUtils;
27 import org.opendaylight.ovsdb.utils.southbound.utils.SouthboundUtils;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.config.rev150710.ElanConfig;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.DatapathId;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.DatapathTypeBase;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.DatapathTypeNetdev;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.bridge.attributes.BridgeOtherConfigs;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.bridge.attributes.BridgeOtherConfigsBuilder;
36 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * This class provides functions for creating bridges via OVSDB, specifically the br-int bridge.
43 public class ElanBridgeManager implements IElanBridgeManager {
44 private static final Logger LOG = LoggerFactory.getLogger(ElanBridgeManager.class);
46 public static final String PROVIDER_MAPPINGS_KEY = "provider_mappings";
47 private static final String INTEGRATION_BRIDGE = "br-int";
48 private static final String INT_SIDE_PATCH_PORT_SUFFIX = "-patch";
49 private static final String EX_SIDE_PATCH_PORT_SUFFIX = "-int-patch";
50 private static final String OTHER_CONFIG_PARAMETERS_DELIMITER = ",";
51 private static final String OTHER_CONFIG_KEY_VALUE_DELIMITER = ":";
52 private static final int MAX_LINUX_INTERFACE_NAME_LENGTH = 15;
53 private static final String OTHER_CONFIG_DATAPATH_ID = "datapath-id";
54 private static final String OTHER_CONFIG_HWADDR = "hwaddr";
55 private static final String OTHER_CONFIG_DISABLE_IN_BAND = "disable-in-band";
57 private final MdsalUtils mdsalUtils;
58 private final IInterfaceManager interfaceManager;
59 final SouthboundUtils southboundUtils;
60 private final Random random;
61 private final Long maxBackoff;
62 private final Long inactivityProbe;
66 * Construct a new ElanBridgeManager.
67 * @param dataBroker DataBroker
68 * @param elanConfig the elan configuration
69 * @param interfaceManager InterfaceManager
72 public ElanBridgeManager(DataBroker dataBroker, ElanConfig elanConfig, IInterfaceManager interfaceManager) {
73 //TODO: ClusterAware!!!??
74 this.mdsalUtils = new MdsalUtils(dataBroker);
75 this.interfaceManager = interfaceManager;
76 this.southboundUtils = new SouthboundUtils(mdsalUtils);
77 this.random = new Random(System.currentTimeMillis());
78 this.maxBackoff = elanConfig.getControllerMaxBackoff();
79 this.inactivityProbe = elanConfig.getControllerInactivityProbe();
83 * Is OVS running in userspace mode?
84 * @return true if the ovsdb.userspace.enabled variable is set to true
86 public boolean isUserSpaceEnabled() {
87 final String enabledPropertyStr = ConfigProperties.getProperty(this.getClass(), "ovsdb.userspace.enabled");
88 return enabledPropertyStr != null && enabledPropertyStr.equalsIgnoreCase("yes");
92 * Is the Node object an OVSDB node.
93 * @param node unidentified node object
94 * @return true if the Node is an OVSDB node
96 public boolean isOvsdbNode(Node node) {
97 return southboundUtils.extractNodeAugmentation(node) != null;
101 * Is this Node the integration bridge (br-int).
102 * @param node unidentified noe object
103 * @return true if the Node is a bridge and it is the integration bridge
105 public boolean isIntegrationBridge(Node node) {
106 if (!isBridgeNode(node)) {
110 String bridgeName = southboundUtils.extractBridgeName(node);
111 if (bridgeName == null) {
115 return bridgeName.equals(INTEGRATION_BRIDGE);
119 * Is this node a bridge.
120 * @param node unidentified node object
121 * @return true if this node is a bridge
123 public boolean isBridgeNode(Node node) {
124 return southboundUtils.extractBridgeAugmentation(node) != null;
128 * Advance the "preperation" of the OVSDB node. This re-entrant method advances the state of an OVSDB
129 * node towards the prepared state where all bridges and patch ports are created and active. This method
130 * should be invoked for the OVSDB node and the integration bridge node BUT IT IS SAFE TO INVOKE IT ON ANY NODE.
132 * @param generateIntBridgeMac whether or not the int bridge's mac should be set to a random value
134 public void processNodePrep(Node node, boolean generateIntBridgeMac) {
135 if (isOvsdbNode(node)) {
136 ensureBridgesExist(node, generateIntBridgeMac);
138 //if br-int already exists, we can add provider networks
139 Node brIntNode = southboundUtils.readBridgeNode(node, INTEGRATION_BRIDGE);
140 if (brIntNode != null) {
141 if (!addControllerToBridge(node, INTEGRATION_BRIDGE)) {
142 LOG.error("Failed to set controller to existing integration bridge {}", brIntNode);
145 prepareIntegrationBridge(node, brIntNode);
150 Node ovsdbNode = southboundUtils.readOvsdbNode(node);
151 if (ovsdbNode == null) {
152 LOG.error("Node is neither bridge nor ovsdb {}", node);
156 if (isIntegrationBridge(node)) {
157 prepareIntegrationBridge(ovsdbNode, node);
162 private void prepareIntegrationBridge(Node ovsdbNode, Node brIntNode) {
163 Map<String, String> providerMappings = getOpenvswitchOtherConfigMap(ovsdbNode, PROVIDER_MAPPINGS_KEY);
165 for (String value : providerMappings.values()) {
166 if (southboundUtils.extractTerminationPointAugmentation(brIntNode, value) != null) {
167 LOG.debug("prepareIntegrationBridge: port {} already exists on {}", value, INTEGRATION_BRIDGE);
171 Node exBridgeNode = southboundUtils.readBridgeNode(ovsdbNode, value);
172 if (exBridgeNode != null) {
173 LOG.debug("prepareIntegrationBridge: bridge {} found. Patching to {}", value, INTEGRATION_BRIDGE);
174 patchBridgeToBrInt(brIntNode, exBridgeNode, value);
176 LOG.debug("prepareIntegrationBridge: adding interface {} to {}", value, INTEGRATION_BRIDGE);
177 if (!addPortToBridge(brIntNode, INTEGRATION_BRIDGE, value)) {
178 LOG.error("Failed to add {} port to {}", value, brIntNode);
186 private void patchBridgeToBrInt(Node intBridgeNode, Node exBridgeNode, String physnetBridgeName) {
188 String portNameInt = getIntSidePatchPortName(physnetBridgeName);
189 String portNameExt = getExSidePatchPortName(physnetBridgeName);
190 if (!addPatchPort(intBridgeNode, INTEGRATION_BRIDGE, portNameInt, portNameExt)) {
191 LOG.error("Failed to add patch port {} to {}", portNameInt, intBridgeNode);
195 if (!addPatchPort(exBridgeNode, physnetBridgeName, portNameExt, portNameInt)) {
196 LOG.error("Failed to add patch port {} to {}", portNameExt, exBridgeNode);
201 @SuppressWarnings("checkstyle:IllegalCatch")
202 private void ensureBridgesExist(Node ovsdbNode, boolean generateIntBridgeMac) {
204 createIntegrationBridge(ovsdbNode, generateIntBridgeMac);
205 } catch (RuntimeException e) {
206 LOG.error("Error creating bridge on " + ovsdbNode, e);
210 private boolean createIntegrationBridge(Node ovsdbNode, boolean generateIntBridgeMac) {
211 // Make sure iface-type exist in Open_vSwitch table prior to br-int creation
212 // in order to allow mixed topology of both DPDK and non-DPDK OVS nodes
213 if (!ifaceTypesExist(ovsdbNode)) {
214 LOG.debug("Skipping integration bridge creation as if-types has not been initialized");
218 LOG.debug("ElanBridgeManager.createIntegrationBridge, skipping if exists");
219 if (!addBridge(ovsdbNode, INTEGRATION_BRIDGE,
220 generateIntBridgeMac ? generateRandomMac() : null)) {
221 LOG.warn("Integration Bridge Creation failed");
227 private boolean ifaceTypesExist(Node ovsdbNode) {
228 OvsdbNodeAugmentation ovsdbNodeAugmentation = southboundUtils.extractNodeAugmentation(ovsdbNode);
229 return ovsdbNodeAugmentation != null && ovsdbNodeAugmentation.getInterfaceTypeEntry() != null
230 && !ovsdbNodeAugmentation.getInterfaceTypeEntry().isEmpty();
234 * Add a bridge to the OVSDB node but check that it does not exist in the
235 * CONFIGURATION. If it already exists in OPERATIONAL, update it with all
236 * configurable parameters but make sure to maintain the same datapath-id.
238 * @param ovsdbNode Which OVSDB node
239 * @param bridgeName Name of the bridge
240 * @param mac mac address to set on the bridge or null
241 * @return true if no errors occurred
243 public boolean addBridge(Node ovsdbNode, String bridgeName, String mac) {
245 if (southboundUtils.getBridgeFromConfig(ovsdbNode, bridgeName) == null) {
246 Class<? extends DatapathTypeBase> dpType = null;
247 if (isUserSpaceEnabled()) {
248 dpType = DatapathTypeNetdev.class;
251 List<BridgeOtherConfigs> otherConfigs = buildBridgeOtherConfigs(ovsdbNode, bridgeName, mac);
253 rv = southboundUtils.addBridge(ovsdbNode, bridgeName,
254 southboundUtils.getControllersFromOvsdbNode(ovsdbNode), dpType, otherConfigs,
255 maxBackoff, inactivityProbe);
260 private List<BridgeOtherConfigs> buildBridgeOtherConfigs(Node ovsdbNode, String bridgeName, String mac) {
261 List<BridgeOtherConfigs> otherConfigs = null;
263 // First attempt to extract the bridge augmentation from operational...
264 Node bridgeNode = southboundUtils.getBridgeNode(ovsdbNode, bridgeName);
265 OvsdbBridgeAugmentation bridgeAug = null;
266 if (bridgeNode != null) {
267 bridgeAug = southboundUtils.extractBridgeAugmentation(bridgeNode);
270 // ...if present, it means this bridge already exists and we need to take
271 // care not to change the datapath id. We do this by explicitly setting
272 // other_config:datapath-id to the value reported in the augmentation.
273 if (bridgeAug != null) {
274 DatapathId dpId = bridgeAug.getDatapathId();
276 otherConfigs = bridgeAug.getBridgeOtherConfigs();
277 if (otherConfigs == null) {
278 otherConfigs = Lists.newArrayList();
281 if (!otherConfigs.stream().anyMatch(otherConfig ->
282 otherConfig.getBridgeOtherConfigKey().equals(OTHER_CONFIG_DATAPATH_ID))) {
283 String dpIdVal = dpId.getValue().replace(":", "");
284 otherConfigs.add(new BridgeOtherConfigsBuilder()
285 .setBridgeOtherConfigKey(OTHER_CONFIG_DATAPATH_ID)
286 .setBridgeOtherConfigValue(dpIdVal).build());
290 otherConfigs = Lists.newArrayList();
292 otherConfigs.add(new BridgeOtherConfigsBuilder()
293 .setBridgeOtherConfigKey(OTHER_CONFIG_HWADDR)
294 .setBridgeOtherConfigValue(mac).build());
298 if (!otherConfigs.stream().anyMatch(otherConfig ->
299 otherConfig.getBridgeOtherConfigKey().equals(OTHER_CONFIG_DISABLE_IN_BAND))) {
300 otherConfigs.add(new BridgeOtherConfigsBuilder()
301 .setBridgeOtherConfigKey(OTHER_CONFIG_DISABLE_IN_BAND)
302 .setBridgeOtherConfigValue("true").build());
308 private boolean addControllerToBridge(Node ovsdbNode,String bridgeName) {
309 return southboundUtils.setBridgeController(ovsdbNode,
310 bridgeName, southboundUtils.getControllersFromOvsdbNode(ovsdbNode),
311 maxBackoff, inactivityProbe);
318 public Map<String, String> getOpenvswitchOtherConfigMap(Node node, String key) {
319 String providerMappings = southboundUtils.getOpenvswitchOtherConfig(node, key);
320 return extractMultiKeyValueToMap(providerMappings);
324 * Get the OVS node physical interface name from provider mappings.
325 * @param node OVSDB node
326 * @param physicalNetworkName name of physical network
327 * @return physical network name
329 public String getProviderMappingValue(Node node, String physicalNetworkName) {
330 Map<String, String> providerMappings = getOpenvswitchOtherConfigMap(node, PROVIDER_MAPPINGS_KEY);
331 String providerMappingValue = providerMappings.get(physicalNetworkName);
332 if (providerMappingValue == null) {
333 LOG.trace("Physical network {} not found in {}", physicalNetworkName, PROVIDER_MAPPINGS_KEY);
336 return providerMappingValue;
340 * Get the name of the port in br-int for the given provider-mapping value. This is either a patch port to a bridge
341 * with providerMappingValue - patch-<providerMappingValue> or simply a port with the same name as
342 * providerMappingValue
343 * @param bridgeNode br-int Node
344 * @param providerMappingValue this is the last part of provider_mappings=net_name:THIS
345 * @return the name of the port on br-int
347 public String getIntBridgePortNameFor(Node bridgeNode, String providerMappingValue) {
348 String res = providerMappingValue;
349 Node managingNode = southboundUtils.readOvsdbNode(bridgeNode);
350 if (managingNode != null && southboundUtils.isBridgeOnOvsdbNode(managingNode, providerMappingValue)) {
351 res = getIntSidePatchPortName(providerMappingValue);
358 * Get the name of the patch-port which is patched to the bridge containing
359 * interfaceName. Patch port name is truncated to the maximum allowed characters
361 * @param interfaceName The external interface
362 * @return interface name
364 public String getIntSidePatchPortName(String interfaceName) {
365 String patchPortName = interfaceName + INT_SIDE_PATCH_PORT_SUFFIX;
366 if (patchPortName.length() <= MAX_LINUX_INTERFACE_NAME_LENGTH) {
367 return patchPortName;
370 LOG.debug("Patch port {} exceeds maximum allowed length. Truncating to {} characters",
371 patchPortName, MAX_LINUX_INTERFACE_NAME_LENGTH);
372 return patchPortName.substring(0, MAX_LINUX_INTERFACE_NAME_LENGTH - 1);
375 private String getExSidePatchPortName(String physicalInterfaceName) {
376 return physicalInterfaceName + EX_SIDE_PATCH_PORT_SUFFIX;
380 * Add a port to a bridge.
381 * @param node the bridge node
382 * @param bridgeName name of the bridge
383 * @param portName name of port to add
384 * @return true if successful in writing to mdsal
386 public boolean addPortToBridge(Node node, String bridgeName, String portName) {
389 if (southboundUtils.extractTerminationPointAugmentation(node, portName) == null) {
390 rv = southboundUtils.addTerminationPoint(node, bridgeName, portName, null);
393 LOG.debug("addPortToBridge: node: {}, bridge: {}, portname: {} status: success",
394 node.getNodeId().getValue(), bridgeName, portName);
396 LOG.error("addPortToBridge: node: {}, bridge: {}, portname: {} status: FAILED",
397 node.getNodeId().getValue(), bridgeName, portName);
400 LOG.trace("addPortToBridge: node: {}, bridge: {}, portname: {} status: not_needed",
401 node.getNodeId().getValue(), bridgeName, portName);
408 * Add a patch port to a bridge.
409 * @param node the bridge node
410 * @param bridgeName name of the bridge
411 * @param portName name of the port
412 * @param peerPortName name of the port's peer (the other side)
413 * @return true if successful
415 public boolean addPatchPort(Node node, String bridgeName, String portName, String peerPortName) {
418 if (southboundUtils.extractTerminationPointAugmentation(node, portName) == null) {
419 rv = southboundUtils.addPatchTerminationPoint(node, bridgeName, portName, peerPortName);
422 LOG.info("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: success",
423 node.getNodeId().getValue(), bridgeName, portName, peerPortName);
425 LOG.error("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: FAILED",
426 node.getNodeId().getValue(), bridgeName, portName, peerPortName);
429 LOG.trace("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: not_needed",
430 node.getNodeId().getValue(), bridgeName, portName, peerPortName);
436 private String generateRandomMac() {
437 byte[] macBytes = new byte[6];
438 random.nextBytes(macBytes);
439 macBytes[0] &= 0xfc; //the two low bits of the first byte need to be zero
441 StringBuilder stringBuilder = new StringBuilder();
445 stringBuilder.append(String.format("%02x", macBytes[index++]));
449 stringBuilder.append(':');
452 return stringBuilder.toString();
455 private static Map<String, String> extractMultiKeyValueToMap(String multiKeyValueStr) {
456 if (Strings.isNullOrEmpty(multiKeyValueStr)) {
457 return Collections.emptyMap();
460 Map<String, String> valueMap = new HashMap<>();
461 Splitter splitter = Splitter.on(OTHER_CONFIG_PARAMETERS_DELIMITER);
462 for (String keyValue : splitter.split(multiKeyValueStr)) {
463 String[] split = keyValue.split(OTHER_CONFIG_KEY_VALUE_DELIMITER, 2);
464 if (split.length == 2) {
465 valueMap.put(split[0], split[1]);
476 public Node getBridgeNode(BigInteger dpId) {
477 List<Node> ovsdbNodes = southboundUtils.getOvsdbNodes();
478 if (null == ovsdbNodes) {
479 LOG.debug("Could not find any (?) ovsdb nodes");
483 for (Node node : ovsdbNodes) {
484 if (!isIntegrationBridge(node)) {
488 long nodeDpid = southboundUtils.getDataPathId(node);
489 if (dpId.equals(BigInteger.valueOf(nodeDpid))) {
497 public String getProviderInterfaceName(BigInteger dpId, String physicalNetworkName) {
500 brNode = getBridgeNode(dpId);
501 if (brNode == null) {
502 LOG.debug("Could not find bridge node for {}", dpId);
506 return getProviderInterfaceName(brNode, physicalNetworkName);
509 public String getProviderInterfaceName(Node bridgeNode, String physicalNetworkName) {
510 if (physicalNetworkName == null) {
514 String providerMappingValue = getProviderMappingValue(bridgeNode, physicalNetworkName);
515 if (providerMappingValue == null) {
516 LOG.trace("No provider mapping found for physicalNetworkName {} node {}", physicalNetworkName,
517 bridgeNode.getNodeId().getValue());
521 long dataPathId = southboundUtils.getDataPathId(bridgeNode);
522 if (dataPathId < 1) {
523 LOG.info("No DatapathID for node {} with physicalNetworkName {}",
524 bridgeNode.getNodeId().getValue(), physicalNetworkName);
528 String portName = getIntBridgePortNameFor(bridgeNode, providerMappingValue);
529 String dpIdStr = String.valueOf(dataPathId);
530 return interfaceManager.getPortNameForInterface(dpIdStr, portName);
533 public boolean hasDatapathID(Node node) {
534 return southboundUtils.getDataPathId(node) > 0 ? true : false;
537 public Boolean isBridgeOnOvsdbNode(Node ovsdbNode, String bridgename) {
538 return southboundUtils.isBridgeOnOvsdbNode(ovsdbNode, bridgename);
541 public String getIntegrationBridgeName() {
542 return INTEGRATION_BRIDGE;