Bug-8053 OVS Bridge other_config:hwaddr not preserved for nodes not in config DS
[netvirt.git] / vpnservice / elanmanager / elanmanager-impl / src / main / java / org / opendaylight / netvirt / elan / internal / ElanBridgeManager.java
1 /*
2  * Copyright (c) 2016, 2017 Red Hat, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netvirt.elan.internal;
9
10 import com.google.common.base.Splitter;
11 import com.google.common.base.Strings;
12 import com.google.common.collect.Lists;
13
14 import java.math.BigInteger;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Random;
20
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;
39
40 /**
41  * This class provides functions for creating bridges via OVSDB, specifically the br-int bridge.
42  */
43 public class ElanBridgeManager implements IElanBridgeManager {
44     private static final Logger LOG = LoggerFactory.getLogger(ElanBridgeManager.class);
45
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";
56
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;
63
64
65     /**
66      * Construct a new ElanBridgeManager.
67      * @param dataBroker DataBroker
68      * @param elanConfig the elan configuration
69      * @param interfaceManager InterfaceManager
70      */
71     @Inject
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();
80     }
81
82     /**
83      * Is OVS running in userspace mode?
84      * @return true if the ovsdb.userspace.enabled variable is set to true
85      */
86     public boolean isUserSpaceEnabled() {
87         final String enabledPropertyStr = ConfigProperties.getProperty(this.getClass(), "ovsdb.userspace.enabled");
88         return enabledPropertyStr != null && enabledPropertyStr.equalsIgnoreCase("yes");
89     }
90
91     /**
92      * Is the Node object an OVSDB node.
93      * @param node unidentified node object
94      * @return true if the Node is an OVSDB node
95      */
96     public boolean isOvsdbNode(Node node) {
97         return southboundUtils.extractNodeAugmentation(node) != null;
98     }
99
100     /**
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
104      */
105     public boolean isIntegrationBridge(Node node) {
106         if (!isBridgeNode(node)) {
107             return false;
108         }
109
110         String bridgeName = southboundUtils.extractBridgeName(node);
111         if (bridgeName == null) {
112             return false;
113         }
114
115         return bridgeName.equals(INTEGRATION_BRIDGE);
116     }
117
118     /**
119      * Is this node a bridge.
120      * @param node unidentified node object
121      * @return true if this node is a bridge
122      */
123     public boolean isBridgeNode(Node node) {
124         return southboundUtils.extractBridgeAugmentation(node) != null;
125     }
126
127     /**
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.
131      * @param node A node
132      * @param generateIntBridgeMac whether or not the int bridge's mac should be set to a random value
133      */
134     public void processNodePrep(Node node, boolean generateIntBridgeMac) {
135         if (isOvsdbNode(node)) {
136             ensureBridgesExist(node, generateIntBridgeMac);
137
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);
143                 }
144
145                 prepareIntegrationBridge(node, brIntNode);
146             }
147             return;
148         }
149
150         Node ovsdbNode = southboundUtils.readOvsdbNode(node);
151         if (ovsdbNode == null) {
152             LOG.error("Node is neither bridge nor ovsdb {}", node);
153             return;
154         }
155
156         if (isIntegrationBridge(node)) {
157             prepareIntegrationBridge(ovsdbNode, node);
158         }
159
160     }
161
162     private void prepareIntegrationBridge(Node ovsdbNode, Node brIntNode) {
163         Map<String, String> providerMappings = getOpenvswitchOtherConfigMap(ovsdbNode, PROVIDER_MAPPINGS_KEY);
164
165         for (String value : providerMappings.values()) {
166             if (southboundUtils.extractTerminationPointAugmentation(brIntNode, value) != null) {
167                 LOG.debug("prepareIntegrationBridge: port {} already exists on {}", value, INTEGRATION_BRIDGE);
168                 continue;
169             }
170
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);
175             } else {
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);
179                 }
180             }
181
182         }
183
184     }
185
186     private void patchBridgeToBrInt(Node intBridgeNode, Node exBridgeNode, String physnetBridgeName) {
187
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);
192             return;
193         }
194
195         if (!addPatchPort(exBridgeNode, physnetBridgeName, portNameExt, portNameInt)) {
196             LOG.error("Failed to add patch port {} to {}", portNameExt, exBridgeNode);
197             return;
198         }
199     }
200
201     @SuppressWarnings("checkstyle:IllegalCatch")
202     private void ensureBridgesExist(Node ovsdbNode, boolean generateIntBridgeMac) {
203         try {
204             createIntegrationBridge(ovsdbNode, generateIntBridgeMac);
205         } catch (RuntimeException e) {
206             LOG.error("Error creating bridge on " + ovsdbNode, e);
207         }
208     }
209
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");
215             return false;
216         }
217
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");
222             return false;
223         }
224         return true;
225     }
226
227     private boolean ifaceTypesExist(Node ovsdbNode) {
228         OvsdbNodeAugmentation ovsdbNodeAugmentation = southboundUtils.extractNodeAugmentation(ovsdbNode);
229         return ovsdbNodeAugmentation != null && ovsdbNodeAugmentation.getInterfaceTypeEntry() != null
230                 && !ovsdbNodeAugmentation.getInterfaceTypeEntry().isEmpty();
231     }
232
233     /**
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.
237      *
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
242      */
243     public boolean addBridge(Node ovsdbNode, String bridgeName, String mac) {
244         boolean rv = true;
245         if (southboundUtils.getBridgeFromConfig(ovsdbNode, bridgeName) == null) {
246             Class<? extends DatapathTypeBase> dpType = null;
247             if (isUserSpaceEnabled()) {
248                 dpType = DatapathTypeNetdev.class;
249             }
250
251             List<BridgeOtherConfigs> otherConfigs = buildBridgeOtherConfigs(ovsdbNode, bridgeName, mac);
252
253             rv = southboundUtils.addBridge(ovsdbNode, bridgeName,
254                     southboundUtils.getControllersFromOvsdbNode(ovsdbNode), dpType, otherConfigs,
255                     maxBackoff, inactivityProbe);
256         }
257         return rv;
258     }
259
260     private List<BridgeOtherConfigs> buildBridgeOtherConfigs(Node ovsdbNode, String bridgeName, String mac) {
261         List<BridgeOtherConfigs> otherConfigs = null;
262
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);
268         }
269
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();
275             if (dpId != null) {
276                 otherConfigs = bridgeAug.getBridgeOtherConfigs();
277                 if (otherConfigs == null) {
278                     otherConfigs = Lists.newArrayList();
279                 }
280
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());
287                 }
288             }
289         } else  {
290             otherConfigs = Lists.newArrayList();
291             if (mac != null) {
292                 otherConfigs.add(new BridgeOtherConfigsBuilder()
293                                 .setBridgeOtherConfigKey(OTHER_CONFIG_HWADDR)
294                                 .setBridgeOtherConfigValue(mac).build());
295             }
296         }
297
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());
303         }
304
305         return otherConfigs;
306     }
307
308     private boolean addControllerToBridge(Node ovsdbNode,String bridgeName) {
309         return southboundUtils.setBridgeController(ovsdbNode,
310                 bridgeName, southboundUtils.getControllersFromOvsdbNode(ovsdbNode),
311                 maxBackoff, inactivityProbe);
312     }
313
314     /**
315      * {@inheritDoc}.
316      */
317     @Override
318     public Map<String, String> getOpenvswitchOtherConfigMap(Node node, String key) {
319         String providerMappings = southboundUtils.getOpenvswitchOtherConfig(node, key);
320         return extractMultiKeyValueToMap(providerMappings);
321     }
322
323     /**
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
328      */
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);
334         }
335
336         return providerMappingValue;
337     }
338
339     /**
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-&lt;providerMappingValue&gt; 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
346      */
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);
352         }
353
354         return res;
355     }
356
357     /**
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
360      *
361      * @param interfaceName The external interface
362      * @return interface name
363      */
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;
368         }
369
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);
373     }
374
375     private String getExSidePatchPortName(String physicalInterfaceName) {
376         return physicalInterfaceName + EX_SIDE_PATCH_PORT_SUFFIX;
377     }
378
379     /**
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
385      */
386     public boolean addPortToBridge(Node node, String bridgeName, String portName) {
387         boolean rv = true;
388
389         if (southboundUtils.extractTerminationPointAugmentation(node, portName) == null) {
390             rv = southboundUtils.addTerminationPoint(node, bridgeName, portName, null);
391
392             if (rv) {
393                 LOG.debug("addPortToBridge: node: {}, bridge: {}, portname: {} status: success",
394                         node.getNodeId().getValue(), bridgeName, portName);
395             } else {
396                 LOG.error("addPortToBridge: node: {}, bridge: {}, portname: {} status: FAILED",
397                         node.getNodeId().getValue(), bridgeName, portName);
398             }
399         } else {
400             LOG.trace("addPortToBridge: node: {}, bridge: {}, portname: {} status: not_needed",
401                     node.getNodeId().getValue(), bridgeName, portName);
402         }
403
404         return rv;
405     }
406
407     /**
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
414      */
415     public boolean addPatchPort(Node node, String bridgeName, String portName, String peerPortName) {
416         boolean rv = true;
417
418         if (southboundUtils.extractTerminationPointAugmentation(node, portName) == null) {
419             rv = southboundUtils.addPatchTerminationPoint(node, bridgeName, portName, peerPortName);
420
421             if (rv) {
422                 LOG.info("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: success",
423                         node.getNodeId().getValue(), bridgeName, portName, peerPortName);
424             } else {
425                 LOG.error("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: FAILED",
426                         node.getNodeId().getValue(), bridgeName, portName, peerPortName);
427             }
428         } else {
429             LOG.trace("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: not_needed",
430                     node.getNodeId().getValue(), bridgeName, portName, peerPortName);
431         }
432
433         return rv;
434     }
435
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
440
441         StringBuilder stringBuilder = new StringBuilder();
442
443         int index = 0;
444         while (true) {
445             stringBuilder.append(String.format("%02x", macBytes[index++]));
446             if (index >= 6) {
447                 break;
448             }
449             stringBuilder.append(':');
450         }
451
452         return stringBuilder.toString();
453     }
454
455     private static Map<String, String> extractMultiKeyValueToMap(String multiKeyValueStr) {
456         if (Strings.isNullOrEmpty(multiKeyValueStr)) {
457             return Collections.emptyMap();
458         }
459
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]);
466             }
467         }
468
469         return valueMap;
470     }
471
472     /**
473      * {@inheritDoc}.
474      */
475     @Override
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");
480             return null;
481         }
482
483         for (Node node : ovsdbNodes) {
484             if (!isIntegrationBridge(node)) {
485                 continue;
486             }
487
488             long nodeDpid = southboundUtils.getDataPathId(node);
489             if (dpId.equals(BigInteger.valueOf(nodeDpid))) {
490                 return node;
491             }
492         }
493
494         return null;
495     }
496
497     public String getProviderInterfaceName(BigInteger dpId, String physicalNetworkName) {
498         Node brNode;
499
500         brNode = getBridgeNode(dpId);
501         if (brNode == null) {
502             LOG.debug("Could not find bridge node for {}", dpId);
503             return null;
504         }
505
506         return getProviderInterfaceName(brNode, physicalNetworkName);
507     }
508
509     public String getProviderInterfaceName(Node bridgeNode, String physicalNetworkName) {
510         if (physicalNetworkName == null) {
511             return null;
512         }
513
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());
518             return null;
519         }
520
521         long dataPathId = southboundUtils.getDataPathId(bridgeNode);
522         if (dataPathId < 1) {
523             LOG.info("No DatapathID for node {} with physicalNetworkName {}",
524                     bridgeNode.getNodeId().getValue(), physicalNetworkName);
525             return null;
526         }
527
528         String portName = getIntBridgePortNameFor(bridgeNode, providerMappingValue);
529         String dpIdStr = String.valueOf(dataPathId);
530         return interfaceManager.getPortNameForInterface(dpIdStr, portName);
531     }
532
533     public boolean hasDatapathID(Node node) {
534         return southboundUtils.getDataPathId(node) > 0 ? true : false;
535     }
536
537     public Boolean isBridgeOnOvsdbNode(Node ovsdbNode, String bridgename) {
538         return southboundUtils.isBridgeOnOvsdbNode(ovsdbNode, bridgename);
539     }
540
541     public String getIntegrationBridgeName() {
542         return INTEGRATION_BRIDGE;
543     }
544 }