Bug 8860 : Populate elantag at time of elanInstance creation
[netvirt.git] / vpnservice / elanmanager / elanmanager-impl / src / main / java / org / opendaylight / netvirt / elan / utils / TransportZoneNotificationUtil.java
1 /*
2  * Copyright (c) 2017 HPE 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.utils;
9
10 import com.google.common.base.Optional;
11 import com.google.common.collect.MapDifference;
12 import com.google.common.collect.MapDifference.ValueDifference;
13 import com.google.common.collect.Maps;
14
15 import java.math.BigInteger;
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.stream.Collectors;
23
24 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
25 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
26 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
27 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
28 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
29 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
30 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
31 import org.opendaylight.netvirt.elan.internal.ElanBridgeManager;
32 import org.opendaylight.netvirt.elanmanager.api.IElanService;
33 import org.opendaylight.ovsdb.utils.mdsal.utils.MdsalUtils;
34 import org.opendaylight.ovsdb.utils.southbound.utils.SouthboundUtils;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.meta.rev160406.BridgeRefInfo;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.meta.rev160406.bridge.ref.info.BridgeRefEntry;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.meta.rev160406.bridge.ref.info.BridgeRefEntryKey;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeVxlan;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.DpnEndpoints;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.DPNTEPsInfo;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.DPNTEPsInfoKey;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.dpn.teps.info.TunnelEndPoints;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.dpn.teps.info.tunnel.end.points.TzMembership;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.TransportZones;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.TransportZone;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.TransportZoneBuilder;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.TransportZoneKey;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.Subnets;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.SubnetsBuilder;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.SubnetsKey;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.subnets.Vteps;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.subnets.VtepsBuilder;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.subnets.VtepsKey;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.config.rev150710.ElanConfig;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.interfaces.ElanInterface;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.vpn.to.dpn.list.VpnInterfaces;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
60 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
61 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 public class TransportZoneNotificationUtil {
66     private static final Logger LOG = LoggerFactory.getLogger(TransportZoneNotificationUtil.class);
67     private static final String TUNNEL_PORT = "tunnel_port";
68     private static final String LOCAL_IP = "local_ip";
69     private static final String LOCAL_IPS = "local_ips";
70     private static final char IP_NETWORK_ZONE_NAME_DELIMITER = '-';
71     private static final String ALL_SUBNETS_GW = "0.0.0.0";
72     private static final String ALL_SUBNETS = "0.0.0.0/0";
73     private final DataBroker dataBroker;
74     private final MdsalUtils mdsalUtils;
75     private final SouthboundUtils southBoundUtils;
76     private final IElanService elanService;
77     private final ElanConfig elanConfig;
78     private final ElanBridgeManager elanBridgeManager;
79
80     public TransportZoneNotificationUtil(final DataBroker dbx, final IInterfaceManager interfaceManager,
81             final IElanService elanService, final ElanConfig elanConfig, final ElanBridgeManager elanBridgeManager) {
82         this.dataBroker = dbx;
83         this.mdsalUtils = new MdsalUtils(dbx);
84         this.elanService = elanService;
85         this.elanConfig = elanConfig;
86         this.elanBridgeManager = elanBridgeManager;
87         southBoundUtils = new SouthboundUtils(mdsalUtils);
88     }
89
90     public boolean shouldCreateVtep(List<VpnInterfaces> vpnInterfaces) {
91         if (vpnInterfaces == null || vpnInterfaces.isEmpty()) {
92             return false;
93         }
94
95         for (VpnInterfaces vpnInterface : vpnInterfaces) {
96             String interfaceName = vpnInterface.getInterfaceName();
97
98             ElanInterface elanInt = elanService.getElanInterfaceByElanInterfaceName(interfaceName);
99             if (elanInt == null) {
100                 continue;
101             }
102
103             if (ElanUtils.isVxlanNetwork(dataBroker, elanInt.getElanInstanceName())) {
104                 return true;
105             } else {
106                 LOG.debug("Non-VXLAN elanInstance: " + elanInt.getElanInstanceName());
107             }
108         }
109
110         return false;
111     }
112
113     private TransportZone createZone(String subnetIp, String zoneName) {
114         List<Subnets> subnets = new ArrayList<>();
115         subnets.add(buildSubnets(subnetIp));
116         TransportZoneBuilder tzb = new TransportZoneBuilder().setKey(new TransportZoneKey(zoneName))
117                 .setTunnelType(TunnelTypeVxlan.class).setZoneName(zoneName).setSubnets(subnets);
118         return tzb.build();
119     }
120
121     private void updateTransportZone(TransportZone zone, BigInteger dpnId, WriteTransaction tx)
122             throws TransactionCommitFailedException {
123         InstanceIdentifier<TransportZone> path = InstanceIdentifier.builder(TransportZones.class)
124                 .child(TransportZone.class, new TransportZoneKey(zone.getZoneName())).build();
125
126         if (tx == null) {
127             SingleTransactionDataBroker.syncUpdate(dataBroker, LogicalDatastoreType.CONFIGURATION, path, zone);
128         } else {
129             tx.merge(LogicalDatastoreType.CONFIGURATION, path, zone);
130         }
131         LOG.info("Transport zone {} updated due to dpn {} handling.", zone.getZoneName(), dpnId);
132     }
133
134     public void updateTransportZone(String zoneNamePrefix, BigInteger dpnId) {
135         Map<String, String> localIps = getDpnLocalIps(dpnId);
136         if (localIps != null && !localIps.isEmpty()) {
137             LOG.debug("Will use local_ips for transport zone update for dpn {} and zone name prefix {}", dpnId,
138                     zoneNamePrefix);
139             for (String localIp : localIps.keySet()) {
140                 String underlayNetworkName = localIps.get(localIp);
141                 String zoneName = getTzNameForUnderlayNetwork(zoneNamePrefix, underlayNetworkName);
142                 updateTransportZone(zoneName, dpnId, localIp);
143             }
144         } else {
145             updateTransportZone(zoneNamePrefix, dpnId, getDpnLocalIp(dpnId));
146         }
147     }
148
149     private void updateTransportZone(String zoneName, BigInteger dpnId, String localIp) {
150         updateTransportZone(zoneName, dpnId, localIp, null);
151     }
152
153     @SuppressWarnings("checkstyle:IllegalCatch")
154     private void updateTransportZone(String zoneName, BigInteger dpnId, String localIp, WriteTransaction tx) {
155         InstanceIdentifier<TransportZone> inst = InstanceIdentifier.create(TransportZones.class)
156                 .child(TransportZone.class, new TransportZoneKey(zoneName));
157
158         // FIXME: Read this through a cache
159         TransportZone zone = mdsalUtils.read(LogicalDatastoreType.CONFIGURATION, inst);
160
161         if (zone == null) {
162             zone = createZone(ALL_SUBNETS, zoneName);
163         }
164
165         try {
166             if (addVtep(zone, ALL_SUBNETS, dpnId, localIp)) {
167                 updateTransportZone(zone, dpnId, tx);
168             }
169         } catch (Exception e) {
170             LOG.error("Failed to add tunnels for dpn {} in zone {}", dpnId, zoneName, e);
171         }
172     }
173
174     /**
175      * Update transport zones based on local_ips TEP ips mapping to underlay
176      * networks.<br>
177      * Deleted local_ips will be removed from the VTEP list of the corresponding
178      * transport zones.<br>
179      * Added local_ips will be added to all transport zones currently associated
180      * with the TEP<br>
181      * local_ips for whom the underlay network mapping has been changed will be
182      * updated in the VTEP lists of the corresponding transport zones.
183      *
184      * @param origNode
185      *            original OVS node
186      * @param updatedNode
187      *            updated OVS node
188      * @param managerNodeId
189      *            uuid of the OVS manager node
190      */
191     public void handleOvsdbNodeUpdate(Node origNode, Node updatedNode, String managerNodeId) {
192         Map<String,
193                 String> origLocalIpMap = java.util.Optional
194                         .ofNullable(elanBridgeManager.getOpenvswitchOtherConfigMap(origNode, LOCAL_IPS))
195                         .orElse(Collections.emptyMap());
196         Map<String,
197                 String> updatedLocalIpMap = java.util.Optional
198                         .ofNullable(elanBridgeManager.getOpenvswitchOtherConfigMap(updatedNode, LOCAL_IPS))
199                         .orElse(Collections.emptyMap());
200         MapDifference<String, String> mapDiff = Maps.difference(origLocalIpMap, updatedLocalIpMap);
201         if (mapDiff.areEqual()) {
202             return;
203         }
204
205         java.util.Optional<BigInteger> dpIdOpt = elanBridgeManager.getDpIdFromManagerNodeId(managerNodeId);
206         if (!dpIdOpt.isPresent()) {
207             LOG.debug("No DPN id found for node {}", managerNodeId);
208             return;
209         }
210
211         BigInteger dpId = dpIdOpt.get();
212         Optional<DPNTEPsInfo> dpnTepsInfoOpt = getDpnTepsInfo(dpId);
213         if (!dpnTepsInfoOpt.isPresent()) {
214             LOG.debug("No DPNTEPsInfo found for DPN id {}", dpId);
215             return;
216         }
217
218         List<TunnelEndPoints> tunnelEndPoints = dpnTepsInfoOpt.get().getTunnelEndPoints();
219         if (tunnelEndPoints == null || tunnelEndPoints.isEmpty()) {
220             LOG.debug("No tunnel endpoints defined for DPN id {}", dpId);
221             return;
222         }
223
224         Set<String> zonePrefixes = new HashSet<>();
225         Map<String, List<String>> tepTzMap = tunnelEndPoints.stream().collect(Collectors
226                 .toMap(tep -> String.valueOf(tep.getIpAddress().getValue()), this::getTepTransportZoneNames));
227         LOG.trace("Transport zone prefixes {}", tepTzMap);
228
229         WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
230         handleRemovedLocalIps(mapDiff.entriesOnlyOnLeft(), dpId, zonePrefixes, tepTzMap, tx);
231         handleChangedLocalIps(mapDiff.entriesDiffering(), dpId, zonePrefixes, tepTzMap, tx);
232         handleAddedLocalIps(mapDiff.entriesOnlyOnRight(), dpId, zonePrefixes, tx);
233         tx.submit();
234     }
235
236     private void handleAddedLocalIps(Map<String, String> addedEntries, BigInteger dpId, Set<String> zonePrefixes,
237             WriteTransaction tx) {
238         if (addedEntries == null || addedEntries.isEmpty()) {
239             LOG.trace("No added local_ips found for DPN {}", dpId);
240             return;
241         }
242
243         LOG.debug("Added local_ips {} on DPN {}", addedEntries.keySet(), dpId);
244         addedEntries.forEach((ipAddress, underlayNetworkName) -> zonePrefixes.forEach(zonePrefix -> {
245             String zoneName = getTzNameForUnderlayNetwork(zonePrefix, underlayNetworkName);
246             updateTransportZone(zoneName, dpId, ipAddress, tx);
247         }));
248     }
249
250     private void handleChangedLocalIps(Map<String, ValueDifference<String>> changedEntries, BigInteger dpId,
251             Set<String> zonePrefixes, Map<String, List<String>> tepTzMap, WriteTransaction tx) {
252         if (changedEntries == null || changedEntries.isEmpty()) {
253             LOG.trace("No changed local_ips found for DPN {}", dpId);
254             return;
255         }
256
257         LOG.debug("Changing underlay network mapping for local_ips {} on DPN {}", changedEntries.keySet(), dpId);
258         changedEntries.forEach((ipAddress, underlayNetworkDiff) -> {
259             List<String> zoneNames = tepTzMap.get(ipAddress);
260             if (zoneNames != null) {
261                 for (String zoneName : zoneNames) {
262                     String removedUnderlayNetwork = underlayNetworkDiff.leftValue();
263                     String addedUnderlayNetwork = underlayNetworkDiff.rightValue();
264                     Optional<String> zonePrefixOpt = getZonePrefixForUnderlayNetwork(zoneName, removedUnderlayNetwork);
265                     if (zonePrefixOpt.isPresent()) {
266                         String zonePrefix = zonePrefixOpt.get();
267                         removeVtep(zoneName, dpId, ipAddress, tx);
268                         zonePrefixes.add(zonePrefix);
269                         String newZoneName = getTzNameForUnderlayNetwork(zonePrefix, addedUnderlayNetwork);
270                         updateTransportZone(newZoneName, dpId, ipAddress, tx);
271                     }
272                 }
273             }
274         });
275     }
276
277     private void handleRemovedLocalIps(Map<String, String> removedEntries, BigInteger dpId, Set<String> zonePrefixes,
278             Map<String, List<String>> tepTzMap, WriteTransaction tx) {
279         if (removedEntries == null || removedEntries.isEmpty()) {
280             LOG.trace("No removed local_ips found on DPN {}", dpId);
281             return;
282         }
283
284         LOG.debug("Removed local_ips {} for DPN {}", removedEntries.keySet(), dpId);
285         removedEntries.forEach((ipAddress, underlayNetworkName) -> {
286             List<String> zoneNames = tepTzMap.get(ipAddress);
287             if (zoneNames != null) {
288                 for (String zoneName : zoneNames) {
289                     Optional<String> zonePrefix = getZonePrefixForUnderlayNetwork(zoneName, underlayNetworkName);
290                     if (zonePrefix.isPresent()) {
291                         removeVtep(zoneName, dpId, ipAddress, tx);
292                         zonePrefixes.add(zonePrefix.get());
293                     }
294                 }
295             }
296         });
297     }
298
299     private List<String> getTepTransportZoneNames(TunnelEndPoints tep) {
300         List<TzMembership> tzMembershipList = tep.getTzMembership();
301         if (tzMembershipList == null) {
302             LOG.debug("No TZ membership exist for TEP ip {}", tep.getIpAddress().getValue());
303             return Collections.emptyList();
304         }
305
306         return tzMembershipList.stream().map(TzMembership::getZoneName).distinct()
307                 .collect(Collectors.toList());
308     }
309
310     private Optional<DPNTEPsInfo> getDpnTepsInfo(BigInteger dpId) {
311         InstanceIdentifier<DPNTEPsInfo> identifier = InstanceIdentifier.builder(DpnEndpoints.class)
312                 .child(DPNTEPsInfo.class, new DPNTEPsInfoKey(dpId)).build();
313         try {
314             return SingleTransactionDataBroker.syncReadOptional(dataBroker, LogicalDatastoreType.CONFIGURATION,
315                     identifier);
316         } catch (ReadFailedException e) {
317             LOG.warn("Failed to read DPNTEPsInfo for DPN id {}", dpId);
318             return Optional.absent();
319         }
320     }
321
322     /**
323      * Tries to add a vtep for a transport zone.
324      *
325      * @return Whether a vtep was added or not.
326      */
327     private boolean addVtep(TransportZone zone, String subnetIp, BigInteger dpnId, String localIp) {
328         Subnets subnets = getOrAddSubnet(zone.getSubnets(), subnetIp);
329         for (Vteps existingVtep : subnets.getVteps()) {
330             if (existingVtep.getDpnId().equals(dpnId)) {
331                 return false;
332             }
333         }
334
335         if (localIp != null) {
336             IpAddress nodeIp = new IpAddress(localIp.toCharArray());
337             VtepsBuilder vtepsBuilder = new VtepsBuilder().setDpnId(dpnId).setIpAddress(nodeIp)
338                     .setPortname(TUNNEL_PORT).setOptionOfTunnel(elanConfig.isUseOfTunnels());
339             subnets.getVteps().add(vtepsBuilder.build());
340             return true;
341         }
342
343         return false;
344     }
345
346     private void removeVtep(String zoneName, BigInteger dpId, String localIp, WriteTransaction tx) {
347         InstanceIdentifier<Vteps> path = InstanceIdentifier.builder(TransportZones.class)
348                 .child(TransportZone.class, new TransportZoneKey(zoneName))
349                 .child(Subnets.class, new SubnetsKey(new IpPrefix(ALL_SUBNETS.toCharArray())))
350                 .child(Vteps.class, new VtepsKey(dpId, TUNNEL_PORT)).build();
351         if (tx == null) {
352             try {
353                 SingleTransactionDataBroker.syncDelete(dataBroker, LogicalDatastoreType.CONFIGURATION, path);
354             } catch (TransactionCommitFailedException e) {
355                 LOG.error("Failed to remove VTEP {} from transport-zone {} for DPN {}", localIp, zoneName, dpId);
356             }
357         } else {
358             tx.delete(LogicalDatastoreType.CONFIGURATION, path);
359         }
360     }
361
362     // search for relevant subnets for the given subnetIP, add one if it is
363     // necessary
364     private Subnets getOrAddSubnet(List<Subnets> subnets, String subnetIp) {
365         IpPrefix subnetPrefix = new IpPrefix(subnetIp.toCharArray());
366
367         if (subnets != null) {
368             for (Subnets subnet : subnets) {
369                 if (subnet.getPrefix().equals(subnetPrefix)) {
370                     return subnet;
371                 }
372             }
373         }
374
375         Subnets retSubnet = buildSubnets(subnetIp);
376         subnets.add(retSubnet);
377
378         return retSubnet;
379     }
380
381     private Subnets buildSubnets(String subnetIp) {
382         SubnetsBuilder subnetsBuilder = new SubnetsBuilder().setDeviceVteps(new ArrayList<>())
383                 .setGatewayIp(new IpAddress(ALL_SUBNETS_GW.toCharArray()))
384                 .setKey(new SubnetsKey(new IpPrefix(subnetIp.toCharArray()))).setVlanId(0)
385                 .setVteps(new ArrayList<Vteps>());
386         return subnetsBuilder.build();
387     }
388
389     private String getDpnLocalIp(BigInteger dpId) {
390         Optional<Node> node = getPortsNode(dpId);
391
392         if (node.isPresent()) {
393             String localIp = southBoundUtils.getOpenvswitchOtherConfig(node.get(), LOCAL_IP);
394             if (localIp == null) {
395                 LOG.error("missing local_ip key in ovsdb:openvswitch-other-configs in operational"
396                         + " network-topology for node: " + node.get().getNodeId().getValue());
397             } else {
398                 return localIp;
399             }
400         }
401
402         return null;
403     }
404
405     private Map<String, String> getDpnLocalIps(BigInteger dpId) {
406         // Example of local IPs from other_config:
407         // local_ips="10.0.43.159:MPLS,11.11.11.11:DSL,ip:underlay-network"
408         Optional<Node> node;
409         node = getPortsNode(dpId);
410         if (node.isPresent()) {
411             return elanBridgeManager.getOpenvswitchOtherConfigMap(node.get(), LOCAL_IPS);
412         }
413
414         return null;
415     }
416
417     @SuppressWarnings("unchecked")
418     private Optional<Node> getPortsNode(BigInteger dpnId) {
419         InstanceIdentifier<BridgeRefEntry> bridgeRefInfoPath = InstanceIdentifier.create(BridgeRefInfo.class)
420                 .child(BridgeRefEntry.class, new BridgeRefEntryKey(dpnId));
421
422         // FIXME: Read this through a cache
423         BridgeRefEntry bridgeRefEntry = mdsalUtils.read(LogicalDatastoreType.OPERATIONAL, bridgeRefInfoPath);
424         if (bridgeRefEntry == null) {
425             LOG.error("no bridge ref entry found for dpnId: " + dpnId);
426             return Optional.absent();
427         }
428
429         InstanceIdentifier<Node> nodeId = ((InstanceIdentifier<OvsdbBridgeAugmentation>) bridgeRefEntry
430                 .getBridgeReference().getValue()).firstIdentifierOf(Node.class);
431
432         // FIXME: Read this through a cache
433         Node node = mdsalUtils.read(LogicalDatastoreType.OPERATIONAL, nodeId);
434
435         if (node == null) {
436             LOG.error("missing node for dpnId: " + dpnId);
437             return Optional.absent();
438         }
439
440         return Optional.of(node);
441     }
442
443     private String getTzNameForUnderlayNetwork(String zoneNamePrefix, String underlayNetworkName) {
444         return zoneNamePrefix + IP_NETWORK_ZONE_NAME_DELIMITER + underlayNetworkName;
445     }
446
447     private Optional<String> getZonePrefixForUnderlayNetwork(String zoneName, String underlayNetworkName) {
448         String[] zoneParts = zoneName.split(IP_NETWORK_ZONE_NAME_DELIMITER + underlayNetworkName);
449         return zoneParts.length == 2 ? Optional.of(zoneParts[0]) : Optional.absent();
450     }
451 }