2 * Copyright (c) 2017 HPE 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.utils;
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;
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;
22 import java.util.stream.Collectors;
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;
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;
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);
90 public boolean shouldCreateVtep(List<VpnInterfaces> vpnInterfaces) {
91 if (vpnInterfaces == null || vpnInterfaces.isEmpty()) {
95 for (VpnInterfaces vpnInterface : vpnInterfaces) {
96 String interfaceName = vpnInterface.getInterfaceName();
98 ElanInterface elanInt = elanService.getElanInterfaceByElanInterfaceName(interfaceName);
99 if (elanInt == null) {
103 if (ElanUtils.isVxlanNetwork(dataBroker, elanInt.getElanInstanceName())) {
106 LOG.debug("Non-VXLAN elanInstance: " + elanInt.getElanInstanceName());
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);
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();
127 SingleTransactionDataBroker.syncUpdate(dataBroker, LogicalDatastoreType.CONFIGURATION, path, zone);
129 tx.merge(LogicalDatastoreType.CONFIGURATION, path, zone);
131 LOG.info("Transport zone {} updated due to dpn {} handling.", zone.getZoneName(), dpnId);
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,
139 for (String localIp : localIps.keySet()) {
140 String underlayNetworkName = localIps.get(localIp);
141 String zoneName = getTzNameForUnderlayNetwork(zoneNamePrefix, underlayNetworkName);
142 updateTransportZone(zoneName, dpnId, localIp);
145 updateTransportZone(zoneNamePrefix, dpnId, getDpnLocalIp(dpnId));
149 private void updateTransportZone(String zoneName, BigInteger dpnId, String localIp) {
150 updateTransportZone(zoneName, dpnId, localIp, null);
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));
158 // FIXME: Read this through a cache
159 TransportZone zone = mdsalUtils.read(LogicalDatastoreType.CONFIGURATION, inst);
162 zone = createZone(ALL_SUBNETS, zoneName);
166 if (addVtep(zone, ALL_SUBNETS, dpnId, localIp)) {
167 updateTransportZone(zone, dpnId, tx);
169 } catch (Exception e) {
170 LOG.error("Failed to add tunnels for dpn {} in zone {}", dpnId, zoneName, e);
175 * Update transport zones based on local_ips TEP ips mapping to underlay
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
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.
188 * @param managerNodeId
189 * uuid of the OVS manager node
191 public void handleOvsdbNodeUpdate(Node origNode, Node updatedNode, String managerNodeId) {
193 String> origLocalIpMap = java.util.Optional
194 .ofNullable(elanBridgeManager.getOpenvswitchOtherConfigMap(origNode, LOCAL_IPS))
195 .orElse(Collections.emptyMap());
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()) {
205 java.util.Optional<BigInteger> dpIdOpt = elanBridgeManager.getDpIdFromManagerNodeId(managerNodeId);
206 if (!dpIdOpt.isPresent()) {
207 LOG.debug("No DPN id found for node {}", managerNodeId);
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);
218 List<TunnelEndPoints> tunnelEndPoints = dpnTepsInfoOpt.get().getTunnelEndPoints();
219 if (tunnelEndPoints == null || tunnelEndPoints.isEmpty()) {
220 LOG.debug("No tunnel endpoints defined for DPN id {}", dpId);
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);
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);
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);
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);
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);
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);
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);
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());
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();
306 return tzMembershipList.stream().map(TzMembership::getZoneName).distinct()
307 .collect(Collectors.toList());
310 private Optional<DPNTEPsInfo> getDpnTepsInfo(BigInteger dpId) {
311 InstanceIdentifier<DPNTEPsInfo> identifier = InstanceIdentifier.builder(DpnEndpoints.class)
312 .child(DPNTEPsInfo.class, new DPNTEPsInfoKey(dpId)).build();
314 return SingleTransactionDataBroker.syncReadOptional(dataBroker, LogicalDatastoreType.CONFIGURATION,
316 } catch (ReadFailedException e) {
317 LOG.warn("Failed to read DPNTEPsInfo for DPN id {}", dpId);
318 return Optional.absent();
323 * Tries to add a vtep for a transport zone.
325 * @return Whether a vtep was added or not.
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)) {
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());
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();
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);
358 tx.delete(LogicalDatastoreType.CONFIGURATION, path);
362 // search for relevant subnets for the given subnetIP, add one if it is
364 private Subnets getOrAddSubnet(List<Subnets> subnets, String subnetIp) {
365 IpPrefix subnetPrefix = new IpPrefix(subnetIp.toCharArray());
367 if (subnets != null) {
368 for (Subnets subnet : subnets) {
369 if (subnet.getPrefix().equals(subnetPrefix)) {
375 Subnets retSubnet = buildSubnets(subnetIp);
376 subnets.add(retSubnet);
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();
389 private String getDpnLocalIp(BigInteger dpId) {
390 Optional<Node> node = getPortsNode(dpId);
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());
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"
409 node = getPortsNode(dpId);
410 if (node.isPresent()) {
411 return elanBridgeManager.getOpenvswitchOtherConfigMap(node.get(), LOCAL_IPS);
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));
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();
429 InstanceIdentifier<Node> nodeId = ((InstanceIdentifier<OvsdbBridgeAugmentation>) bridgeRefEntry
430 .getBridgeReference().getValue()).firstIdentifierOf(Node.class);
432 // FIXME: Read this through a cache
433 Node node = mdsalUtils.read(LogicalDatastoreType.OPERATIONAL, nodeId);
436 LOG.error("missing node for dpnId: " + dpnId);
437 return Optional.absent();
440 return Optional.of(node);
443 private String getTzNameForUnderlayNetwork(String zoneNamePrefix, String underlayNetworkName) {
444 return zoneNamePrefix + IP_NETWORK_ZONE_NAME_DELIMITER + underlayNetworkName;
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();