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