Merge "Fix DMAC flows for Vlan E/W provider"
[netvirt.git] / vpnservice / elanmanager / elanmanager-impl / src / main / java / org / opendaylight / netvirt / elan / internal / ElanBridgeManager.java
1 /*
2  * Copyright (c) 2016 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.Optional;
11 import com.google.common.base.Splitter;
12 import com.google.common.base.Strings;
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 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
21 import org.opendaylight.genius.interfacemanager.globals.IfmConstants;
22 import org.opendaylight.ovsdb.utils.config.ConfigProperties;
23 import org.opendaylight.ovsdb.utils.mdsal.utils.MdsalUtils;
24 import org.opendaylight.ovsdb.utils.southbound.utils.SouthboundUtils;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.DatapathTypeBase;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.DatapathTypeNetdev;
27 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32  * This class provides functions for creating bridges via OVSDB, specifically the br-int bridge.
33  * Note and TODO: br-ex is temporary. vpnservice does not require it but for the time being it is
34  * left here because devstack expects it.
35  */
36 public class ElanBridgeManager {
37     private static final Logger LOG = LoggerFactory.getLogger(ElanBridgeManager.class);
38
39     public static final String PROVIDER_MAPPINGS_KEY = "provider_mappings";
40     private static final String INTEGRATION_BRIDGE = "br-int";
41     private static final String INT_SIDE_PATCH_PORT_SUFFIX = "-expatch";
42     private static final String EX_SIDE_PATCH_PORT_SUFFIX = "-patch";
43
44     private final MdsalUtils mdsalUtils;
45     final SouthboundUtils southboundUtils;
46     private Random random;
47
48     /**
49      * Construct a new ElanBridgeManager.
50      * @param dataBroker DataBroker
51      */
52     public ElanBridgeManager(DataBroker dataBroker) {
53         //TODO: ClusterAware!!!??
54         this.mdsalUtils = new MdsalUtils(dataBroker);
55         this.southboundUtils = new SouthboundUtils(mdsalUtils);
56         this.random = new Random(System.currentTimeMillis());
57     }
58
59     /**
60      * Is OVS running in userspace mode?
61      * @return true if the ovsdb.userspace.enabled variable is set to true
62      */
63     public boolean isUserSpaceEnabled() {
64         final String enabledPropertyStr = ConfigProperties.getProperty(this.getClass(), "ovsdb.userspace.enabled");
65         return enabledPropertyStr != null && enabledPropertyStr.equalsIgnoreCase("yes");
66     }
67
68     /**
69      * Is the Node object an OVSDB node.
70      * @param node unidentified node object
71      * @return true if the Node is an OVSDB node
72      */
73     public boolean isOvsdbNode(Node node) {
74         return southboundUtils.extractNodeAugmentation(node) != null;
75     }
76
77     /**
78      * Is this Node the integration bridge (br-int).
79      * @param node unidentified noe object
80      * @return true if the Node is a bridge and it is the integration bridge
81      */
82     public boolean isIntegrationBridge(Node node) {
83         if (!isBridgeNode(node)) {
84             return false;
85         }
86
87         String bridgeName = southboundUtils.extractBridgeName(node);
88         if (bridgeName == null) {
89             return false;
90         }
91
92         return bridgeName.equals(INTEGRATION_BRIDGE);
93     }
94
95     /**
96      * Is this node a bridge.
97      * @param node unidentified node object
98      * @return true if this node is a bridge
99      */
100     public boolean isBridgeNode(Node node) {
101         return southboundUtils.extractBridgeAugmentation(node) != null;
102     }
103
104     /**
105      * Advance the "preperation" of the OVSDB node. This re-entrant method advances the state of an OVSDB
106      * node towards the prepared state where all bridges and patch ports are created and active. This method
107      * should be invoked for the OVSDB node and the integration bridge node BUT IT IS SAFE TO INVOKE IT ON ANY NODE.
108      * @param node A node
109      * @param generateIntBridgeMac whether or not the int bridge's mac should be set to a random value
110      */
111     public void processNodePrep(Node node, boolean generateIntBridgeMac) {
112         if (isOvsdbNode(node)) {
113             ensureBridgesExist(node, generateIntBridgeMac);
114
115             //if br-int already exists, we can add provider networks
116             Node brIntNode = southboundUtils.readBridgeNode(node, INTEGRATION_BRIDGE);
117             if (brIntNode != null) {
118                 if (!addControllerToBridge(node, INTEGRATION_BRIDGE)) {
119                     LOG.error("Failed to set controller to existing integration bridge {}", brIntNode);
120                 }
121
122                 prepareIntegrationBridge(node, brIntNode);
123             }
124             return;
125         }
126
127         Node ovsdbNode = southboundUtils.readOvsdbNode(node);
128         if (ovsdbNode == null) {
129             LOG.error("Node is neither bridge nor ovsdb {}", node);
130             return;
131         }
132
133         if (isIntegrationBridge(node)) {
134             prepareIntegrationBridge(ovsdbNode, node);
135         }
136
137     }
138
139     private void prepareIntegrationBridge(Node ovsdbNode, Node brIntNode) {
140         Optional<Map<String, String>> providerMappings = getOpenvswitchOtherConfigMap(ovsdbNode, PROVIDER_MAPPINGS_KEY);
141
142         for (String value : providerMappings.or(Collections.emptyMap()).values()) {
143             if (southboundUtils.extractTerminationPointAugmentation(brIntNode, value) != null) {
144                 LOG.debug("prepareIntegrationBridge: port {} already exists on {}", value, INTEGRATION_BRIDGE);
145                 continue;
146             }
147
148             Node exBridgeNode = southboundUtils.readBridgeNode(ovsdbNode, value);
149             if (exBridgeNode != null) {
150                 LOG.debug("prepareIntegrationBridge: bridge {} found. Patching to {}", value, INTEGRATION_BRIDGE);
151                 patchBridgeToBrInt(brIntNode, exBridgeNode, value);
152             } else {
153                 LOG.debug("prepareIntegrationBridge: adding interface {} to {}", value, INTEGRATION_BRIDGE);
154                 if (!addPortToBridge(brIntNode, INTEGRATION_BRIDGE, value)) {
155                     LOG.error("Failed to add {} port to {}", value, brIntNode);
156                 }
157             }
158
159         }
160
161     }
162
163     private void patchBridgeToBrInt(Node intBridgeNode, Node exBridgeNode, String physnetBridgeName) {
164
165         String portNameInt = getIntSidePatchPortName(physnetBridgeName);
166         String portNameExt = getExSidePatchPortName(physnetBridgeName);
167         if (!addPatchPort(intBridgeNode, INTEGRATION_BRIDGE, portNameInt, portNameExt)) {
168             LOG.error("Failed to add patch port {} to {}", portNameInt, intBridgeNode);
169             return;
170         }
171
172         if (!addPatchPort(exBridgeNode, physnetBridgeName, portNameExt, portNameInt)) {
173             LOG.error("Failed to add patch port {} to {}", portNameExt, exBridgeNode);
174             return;
175         }
176     }
177
178     private void ensureBridgesExist(Node ovsdbNode, boolean generateIntBridgeMac) {
179
180         try {
181             createIntegrationBridge(ovsdbNode, generateIntBridgeMac);
182         } catch (Exception e) {
183             LOG.error("Error creating bridge on " + ovsdbNode, e);
184         }
185     }
186
187     private boolean createIntegrationBridge(Node ovsdbNode, boolean generateIntBridgeMac) {
188         LOG.debug("ElanBridgeManager.createIntegrationBridge, skipping if exists");
189         if (!addBridge(ovsdbNode, INTEGRATION_BRIDGE,
190                 generateIntBridgeMac ? generateRandomMac() : null)) {
191             LOG.warn("Integration Bridge Creation failed");
192             return false;
193         }
194         return true;
195     }
196
197     /**
198      * Add a bridge to the OVSDB node but check that it does not exist in the
199      * CONFIGURATION or OPERATIONAL md-sals first.
200      *
201      * @param ovsdbNode Which OVSDB node
202      * @param bridgeName Name of the bridge
203      * @param mac mac address to set on the bridge or null
204      * @return true if no errors occurred
205      */
206     public boolean addBridge(Node ovsdbNode, String bridgeName, String mac) {
207         boolean rv = true;
208         if (!southboundUtils.isBridgeOnOvsdbNode(ovsdbNode, bridgeName)
209                 || southboundUtils.getBridgeFromConfig(ovsdbNode, bridgeName) == null) {
210             Class<? extends DatapathTypeBase> dpType = null;
211             if (isUserSpaceEnabled()) {
212                 dpType = DatapathTypeNetdev.class;
213             }
214             rv = southboundUtils.addBridge(ovsdbNode, bridgeName,
215                     southboundUtils.getControllersFromOvsdbNode(ovsdbNode), dpType, mac);
216         }
217         return rv;
218     }
219
220     private boolean addControllerToBridge(Node ovsdbNode,String bridgeName) {
221         return southboundUtils.setBridgeController(ovsdbNode,
222                             bridgeName, southboundUtils.getControllersFromOvsdbNode(ovsdbNode));
223     }
224
225     /**
226      * Extract OpenvSwitch other-config to key value map.
227      * @param node OVSDB node
228      * @param key key to extract from other-config
229      * @return Optional of key-value Map
230      */
231     public Optional<Map<String, String>> getOpenvswitchOtherConfigMap(Node node, String key) {
232         String providerMappings = southboundUtils.getOpenvswitchOtherConfig(node, key);
233         return extractMultiKeyValueToMap(providerMappings);
234     }
235
236     /**
237      * Get the OVS node physical interface name from provider mappings.
238      * @param node OVSDB node
239      * @param physicalNetworkName name of physical network
240      * @return physical network name
241      */
242     public String getProviderMappingValue(Node node, String physicalNetworkName) {
243         Optional<Map<String, String>> providerMappings = getOpenvswitchOtherConfigMap(node, PROVIDER_MAPPINGS_KEY);
244         if (!providerMappings.isPresent()) {
245             LOG.trace("Physical network {} not found in {}", physicalNetworkName, PROVIDER_MAPPINGS_KEY);
246             return null;
247         }
248
249         return providerMappings.get().get(physicalNetworkName);
250     }
251
252     /**
253      * Get the name of the port in br-int for the given provider-mapping value. This is either a patch port to a bridge
254      * with providerMappingValue - patch-&lt;providerMappingValue&gt; or simply a port with the same name as
255      * providerMappingValue
256      * @param ovsdbNode ovsdbNode
257      * @param providerMappingValue this is the last part of provider_mappings=net_name:THIS
258      * @return the name of the port on br-int
259      */
260     public String getIntBridgePortNameFor(Node ovsdbNode, String providerMappingValue) {
261         String res = providerMappingValue;
262         if (southboundUtils.isBridgeOnOvsdbNode(ovsdbNode, providerMappingValue)) {
263             res = getIntSidePatchPortName(providerMappingValue);
264         }
265
266         return res;
267     }
268
269     /**
270      * Get the name of the patch-port which is patched to the bridge containing
271      * interfaceName.
272      *
273      * @param interfaceName The external interface
274      * @return interface name
275      */
276     public static String getIntSidePatchPortName(String interfaceName) {
277         return interfaceName + INT_SIDE_PATCH_PORT_SUFFIX;
278     }
279
280     private String getExSidePatchPortName(String physicalInterfaceName) {
281         return physicalInterfaceName + EX_SIDE_PATCH_PORT_SUFFIX;
282     }
283
284     /**
285      * Add a port to a bridge.
286      * @param node the bridge node
287      * @param bridgeName name of the bridge
288      * @param portName name of port to add
289      * @return true if successful in writing to mdsal
290      */
291     public boolean addPortToBridge(Node node, String bridgeName, String portName) {
292         boolean rv = true;
293
294         if (southboundUtils.extractTerminationPointAugmentation(node, portName) == null) {
295             rv = southboundUtils.addTerminationPoint(node, bridgeName, portName, null);
296
297             if (rv) {
298                 LOG.debug("addPortToBridge: node: {}, bridge: {}, portname: {} status: success",
299                         node.getNodeId().getValue(), bridgeName, portName);
300             } else {
301                 LOG.error("addPortToBridge: node: {}, bridge: {}, portname: {} status: FAILED",
302                         node.getNodeId().getValue(), bridgeName, portName);
303             }
304         } else {
305             LOG.trace("addPortToBridge: node: {}, bridge: {}, portname: {} status: not_needed",
306                     node.getNodeId().getValue(), bridgeName, portName);
307         }
308
309         return rv;
310     }
311
312     /**
313      * Add a patch port to a bridge.
314      * @param node the bridge node
315      * @param bridgeName name of the bridge
316      * @param portName name of the port
317      * @param peerPortName name of the port's peer (the other side)
318      * @return true if successful
319      */
320     public boolean addPatchPort(Node node, String bridgeName, String portName, String peerPortName) {
321         boolean rv = true;
322
323         if (southboundUtils.extractTerminationPointAugmentation(node, portName) == null) {
324             rv = southboundUtils.addPatchTerminationPoint(node, bridgeName, portName, peerPortName);
325
326             if (rv) {
327                 LOG.info("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: success",
328                         node.getNodeId().getValue(), bridgeName, portName, peerPortName);
329             } else {
330                 LOG.error("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: FAILED",
331                         node.getNodeId().getValue(), bridgeName, portName, peerPortName);
332             }
333         } else {
334             LOG.trace("addPatchPort: node: {}, bridge: {}, portname: {} peer: {} status: not_needed",
335                     node.getNodeId().getValue(), bridgeName, portName, peerPortName);
336         }
337
338         return rv;
339     }
340
341     private String generateRandomMac() {
342         byte[] macBytes = new byte[6];
343         random.nextBytes(macBytes);
344         macBytes[0] &= 0xfc; //the two low bits of the first byte need to be zero
345
346         StringBuilder stringBuilder = new StringBuilder();
347
348         int index = 0;
349         while (true) {
350             stringBuilder.append(String.format("%02x", macBytes[index++]));
351             if (index >= 6) {
352                 break;
353             }
354             stringBuilder.append(':');
355         }
356
357         return stringBuilder.toString();
358     }
359
360     private static Optional<Map<String, String>> extractMultiKeyValueToMap(String multiKeyValueStr) {
361         if (Strings.isNullOrEmpty(multiKeyValueStr)) {
362             return Optional.absent();
363         }
364
365         Map<String, String> valueMap = new HashMap<>();
366         Splitter splitter = Splitter.on(",");
367         for (String keyValue : splitter.split(multiKeyValueStr)) {
368             String[] split = keyValue.split(":", 2);
369             if (split != null && split.length == 2) {
370                 valueMap.put(split[0], split[1]);
371             }
372         }
373
374         return Optional.of(valueMap);
375     }
376
377     public Node getBridgeNode(BigInteger dpId) {
378         List<Node> ovsdbNodes = southboundUtils.getOvsdbNodes();
379         if (null == ovsdbNodes) {
380             LOG.debug("Could not find any (?) ovsdb nodes");
381             return null;
382         }
383
384         for (Node node : ovsdbNodes) {
385             if(!isIntegrationBridge(node)) {
386                 continue;
387             }
388
389             long nodeDpid = southboundUtils.getDataPathId(node);
390             if (dpId.equals(BigInteger.valueOf(nodeDpid))) {
391                 return node;
392             }
393         }
394
395         return null;
396     }
397
398     public String getProviderInterfaceName(BigInteger dpId, String physicalNetworkName) {
399         Node brNode;
400
401         brNode = getBridgeNode(dpId);
402         if (brNode == null) {
403             LOG.debug("Could not find bridge node for {}", dpId);
404             return null;
405         }
406
407         return getProviderInterfaceName(brNode, physicalNetworkName);
408     }
409
410     public String getProviderInterfaceName(Node bridgeNode, String physicalNetworkName) {
411         if (physicalNetworkName == null) {
412             return null;
413         }
414
415         String providerMappingValue = getProviderMappingValue(bridgeNode, physicalNetworkName);
416         if (providerMappingValue == null) {
417             LOG.trace("No provider mapping found for physicalNetworkName {} node {}", physicalNetworkName,
418                     bridgeNode.getNodeId().getValue());
419             return null;
420         }
421
422         return southboundUtils.getDataPathId(bridgeNode) + IfmConstants.OF_URI_SEPARATOR
423                 + getIntBridgePortNameFor(bridgeNode, providerMappingValue);
424     }
425 }