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 static org.opendaylight.mdsal.binding.util.Datastore.CONFIGURATION;
12 import com.google.common.collect.MapDifference;
13 import com.google.common.collect.MapDifference.ValueDifference;
14 import com.google.common.collect.Maps;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.HashSet;
18 import java.util.List;
20 import java.util.Map.Entry;
21 import java.util.Objects;
22 import java.util.Optional;
24 import java.util.concurrent.ExecutionException;
25 import java.util.stream.Collectors;
26 import javax.inject.Inject;
27 import javax.inject.Singleton;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
31 import org.opendaylight.infrautils.utils.concurrent.LoggingFutures;
32 import org.opendaylight.mdsal.binding.api.DataBroker;
33 import org.opendaylight.mdsal.binding.util.Datastore.Configuration;
34 import org.opendaylight.mdsal.binding.util.ManagedNewTransactionRunner;
35 import org.opendaylight.mdsal.binding.util.ManagedNewTransactionRunnerImpl;
36 import org.opendaylight.mdsal.binding.util.TypedReadTransaction;
37 import org.opendaylight.mdsal.binding.util.TypedReadWriteTransaction;
38 import org.opendaylight.mdsal.binding.util.TypedWriteTransaction;
39 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
40 import org.opendaylight.mdsal.common.api.ReadFailedException;
41 import org.opendaylight.netvirt.elan.cache.ElanInstanceCache;
42 import org.opendaylight.netvirt.elan.internal.ElanBridgeManager;
43 import org.opendaylight.netvirt.elanmanager.api.IElanService;
44 import org.opendaylight.ovsdb.utils.mdsal.utils.MdsalUtils;
45 import org.opendaylight.ovsdb.utils.southbound.utils.SouthboundUtils;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.meta.rev160406.BridgeRefInfo;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.meta.rev160406.bridge.ref.info.BridgeRefEntry;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.meta.rev160406.bridge.ref.info.BridgeRefEntryKey;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeVxlan;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.DpnEndpoints;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.DPNTEPsInfo;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.DPNTEPsInfoKey;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.dpn.teps.info.TunnelEndPoints;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.op.rev160406.dpn.endpoints.dpn.teps.info.tunnel.end.points.TzMembership;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.TransportZones;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.TransportZone;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.TransportZoneBuilder;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.TransportZoneKey;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.Vteps;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rev160406.transport.zones.transport.zone.VtepsKey;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.config.rev150710.ElanConfig;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.interfaces.ElanInterface;
63 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;
64 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
65 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
66 import org.opendaylight.yangtools.yang.common.Uint64;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
71 public class TransportZoneNotificationUtil {
72 private static final Logger LOG = LoggerFactory.getLogger(TransportZoneNotificationUtil.class);
73 private static final String TUNNEL_PORT = "tunnel_port";
74 private static final String LOCAL_IP = "local_ip";
75 private static final String LOCAL_IPS = "local_ips";
76 private static final char IP_NETWORK_ZONE_NAME_DELIMITER = '-';
77 private static final String ALL_SUBNETS_GW = "0.0.0.0";
78 private static final String ALL_SUBNETS = "0.0.0.0/0";
79 private final ManagedNewTransactionRunner txRunner;
80 private final SingleTransactionDataBroker singleTxBroker;
81 private final SouthboundUtils southBoundUtils;
82 private final IElanService elanService;
83 private final ElanConfig elanConfig;
84 private final ElanBridgeManager elanBridgeManager;
85 private final ElanInstanceCache elanInstanceCache;
88 public TransportZoneNotificationUtil(final DataBroker dbx,
89 final IElanService elanService, final ElanConfig elanConfig, final ElanBridgeManager elanBridgeManager,
90 final ElanInstanceCache elanInstanceCache) {
91 this.txRunner = new ManagedNewTransactionRunnerImpl(dbx);
92 this.singleTxBroker = new SingleTransactionDataBroker(dbx);
93 this.elanService = elanService;
94 this.elanConfig = elanConfig;
95 this.elanBridgeManager = elanBridgeManager;
96 this.elanInstanceCache = elanInstanceCache;
97 southBoundUtils = new SouthboundUtils(new MdsalUtils(dbx));
100 public boolean shouldCreateVtep(List<VpnInterfaces> vpnInterfaces) {
101 if (vpnInterfaces == null || vpnInterfaces.isEmpty()) {
105 for (VpnInterfaces vpnInterface : vpnInterfaces) {
106 String interfaceName = vpnInterface.getInterfaceName();
108 ElanInterface elanInt = elanService.getElanInterfaceByElanInterfaceName(interfaceName);
109 if (elanInt == null) {
113 if (ElanUtils.isVxlanNetworkOrVxlanSegment(
114 elanInstanceCache.get(elanInt.getElanInstanceName()).orElse(null))) {
117 LOG.debug("Non-VXLAN elanInstance: {}", elanInt.getElanInstanceName());
124 private static TransportZone createZone(String subnetIp, String zoneName) {
125 TransportZoneBuilder tzb = new TransportZoneBuilder().withKey(new TransportZoneKey(zoneName))
126 .setTunnelType(TunnelTypeVxlan.class).setZoneName(zoneName);
130 private static void updateTransportZone(TransportZone zone, Uint64 dpnId,
131 @NonNull TypedWriteTransaction<Configuration> tx) {
132 InstanceIdentifier<TransportZone> path = InstanceIdentifier.builder(TransportZones.class)
133 .child(TransportZone.class, new TransportZoneKey(zone.getZoneName())).build();
135 tx.merge(path, zone);
136 LOG.info("Transport zone {} updated due to dpn {} handling.", zone.getZoneName(), dpnId);
139 public void updateTransportZone(String zoneNamePrefix, Uint64 dpnId) {
140 LoggingFutures.addErrorLogging(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION, tx -> {
141 Map<String, String> localIps = getDpnLocalIps(dpnId);
142 if (!localIps.isEmpty()) {
143 LOG.debug("Will use local_ips for transport zone update for dpn {} and zone name prefix {}", dpnId,
145 for (Entry<String, String> entry : localIps.entrySet()) {
146 String localIp = entry.getKey();
147 String underlayNetworkName = entry.getValue();
148 String zoneName = getTzNameForUnderlayNetwork(zoneNamePrefix, underlayNetworkName);
149 updateTransportZone(zoneName, dpnId, localIp, tx);
152 updateTransportZone(zoneNamePrefix, dpnId, getDpnLocalIp(dpnId), tx);
154 }), LOG, "Error updating transport zone");
157 @SuppressWarnings("checkstyle:IllegalCatch")
158 private void updateTransportZone(String zoneName, Uint64 dpnId, @Nullable String localIp,
159 @NonNull TypedReadWriteTransaction<Configuration> tx)
160 throws ExecutionException, InterruptedException {
161 InstanceIdentifier<TransportZone> inst = InstanceIdentifier.create(TransportZones.class)
162 .child(TransportZone.class, new TransportZoneKey(zoneName));
164 // FIXME: Read this through a cache
165 TransportZone zone = tx.read(inst).get().orElse(null);
168 zone = createZone(ALL_SUBNETS, zoneName);
172 if (addVtep(zone, ALL_SUBNETS, dpnId, localIp)) {
173 updateTransportZone(zone, dpnId, tx);
175 } catch (Exception e) {
176 LOG.error("Failed to add tunnels for dpn {} in zone {}", dpnId, zoneName, e);
180 private static void deleteTransportZone(TransportZone zone, Uint64 dpnId,
181 @NonNull TypedWriteTransaction<Configuration> tx) {
182 InstanceIdentifier<TransportZone> path = InstanceIdentifier.builder(TransportZones.class)
183 .child(TransportZone.class, new TransportZoneKey(zone.getZoneName())).build();
185 LOG.info("Transport zone {} deleted due to dpn {} handling.", zone.getZoneName(), dpnId);
188 public void deleteTransportZone(String zoneNamePrefix, Uint64 dpnId) {
189 LoggingFutures.addErrorLogging(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION, tx -> {
190 Map<String, String> localIps = getDpnLocalIps(dpnId);
191 if (!localIps.isEmpty()) {
192 LOG.debug("Will use local_ips for transport zone delete for dpn {} and zone name prefix {}", dpnId,
194 for (String underlayNetworkName : localIps.values()) {
195 String zoneName = getTzNameForUnderlayNetwork(zoneNamePrefix, underlayNetworkName);
196 deleteTransportZone(zoneName, dpnId, tx);
199 deleteTransportZone(zoneNamePrefix, dpnId, tx);
201 }), LOG, "Error deleting transport zone");
204 @SuppressWarnings("checkstyle:IllegalCatch")
205 private static void deleteTransportZone(String zoneName, Uint64 dpnId,
206 @NonNull TypedReadWriteTransaction<Configuration> tx) throws ExecutionException, InterruptedException {
207 InstanceIdentifier<TransportZone> inst = InstanceIdentifier.create(TransportZones.class)
208 .child(TransportZone.class, new TransportZoneKey(zoneName));
210 // FIXME: Read this through a cache
211 TransportZone zone = tx.read(inst).get().orElse(null);
214 deleteTransportZone(zone, dpnId, tx);
215 } catch (Exception e) {
216 LOG.error("Failed to remove tunnels for dpn {} in zone {}", dpnId, zoneName, e);
223 * Update transport zones based on local_ips TEP ips mapping to underlay
225 * Deleted local_ips will be removed from the VTEP list of the corresponding
226 * transport zones.<br>
227 * Added local_ips will be added to all transport zones currently associated
229 * local_ips for whom the underlay network mapping has been changed will be
230 * updated in the VTEP lists of the corresponding transport zones.
236 * @param managerNodeId
237 * uuid of the OVS manager node
239 public void handleOvsdbNodeUpdate(Node origNode, Node updatedNode, String managerNodeId) {
241 String> origLocalIpMap = java.util.Optional
242 .ofNullable(elanBridgeManager.getOpenvswitchOtherConfigMap(origNode, LOCAL_IPS))
243 .orElse(Collections.emptyMap());
245 String> updatedLocalIpMap = java.util.Optional
246 .ofNullable(elanBridgeManager.getOpenvswitchOtherConfigMap(updatedNode, LOCAL_IPS))
247 .orElse(Collections.emptyMap());
248 MapDifference<String, String> mapDiff = Maps.difference(origLocalIpMap, updatedLocalIpMap);
249 if (mapDiff.areEqual()) {
253 java.util.Optional<Uint64> dpIdOpt = elanBridgeManager.getDpIdFromManagerNodeId(managerNodeId);
254 if (!dpIdOpt.isPresent()) {
255 LOG.debug("No DPN id found for node {}", managerNodeId);
259 LoggingFutures.addErrorLogging(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION, tx -> {
260 Uint64 dpId = dpIdOpt.get();
261 Optional<DPNTEPsInfo> dpnTepsInfoOpt = getDpnTepsInfo(dpId, tx);
262 if (!dpnTepsInfoOpt.isPresent()) {
263 LOG.debug("No DPNTEPsInfo found for DPN id {}", dpId);
267 List<TunnelEndPoints> tunnelEndPoints = dpnTepsInfoOpt.get().getTunnelEndPoints();
268 if (tunnelEndPoints == null || tunnelEndPoints.isEmpty()) {
269 LOG.debug("No tunnel endpoints defined for DPN id {}", dpId);
273 Set<String> zonePrefixes = new HashSet<>();
274 Map<String, List<String>> tepTzMap = tunnelEndPoints.stream().collect(Collectors
275 .toMap(tep -> tep.getIpAddress().stringValue(), this::getTepTransportZoneNames));
276 LOG.trace("Transport zone prefixes {}", tepTzMap);
278 handleRemovedLocalIps(mapDiff.entriesOnlyOnLeft(), dpId, zonePrefixes, tepTzMap, tx);
279 handleChangedLocalIps(mapDiff.entriesDiffering(), dpId, zonePrefixes, tepTzMap, tx);
280 handleAddedLocalIps(mapDiff.entriesOnlyOnRight(), dpId, zonePrefixes, tx);
281 }), LOG, "Error handling OVSDB node update");
284 private void handleAddedLocalIps(Map<String, String> addedEntries, Uint64 dpId, Set<String> zonePrefixes,
285 TypedReadWriteTransaction<Configuration> tx) throws ExecutionException, InterruptedException {
286 if (addedEntries == null || addedEntries.isEmpty()) {
287 LOG.trace("No added local_ips found for DPN {}", dpId);
291 LOG.debug("Added local_ips {} on DPN {}", addedEntries.keySet(), dpId);
292 for (Map.Entry<String, String> addedEntry : addedEntries.entrySet()) {
293 String ipAddress = addedEntry.getKey();
294 String underlayNetworkName = addedEntry.getValue();
295 for (String zonePrefix : zonePrefixes) {
296 String zoneName = getTzNameForUnderlayNetwork(zonePrefix, underlayNetworkName);
297 updateTransportZone(zoneName, dpId, ipAddress, tx);
302 private void handleChangedLocalIps(Map<String, ValueDifference<String>> changedEntries, Uint64 dpId,
303 Set<String> zonePrefixes, Map<String, List<String>> tepTzMap,
304 @NonNull TypedReadWriteTransaction<Configuration> tx) throws ExecutionException, InterruptedException {
305 if (changedEntries == null || changedEntries.isEmpty()) {
306 LOG.trace("No changed local_ips found for DPN {}", dpId);
310 LOG.debug("Changing underlay network mapping for local_ips {} on DPN {}", changedEntries.keySet(), dpId);
311 for (Map.Entry<String, ValueDifference<String>> changedEntry : changedEntries.entrySet()) {
312 String ipAddress = changedEntry.getKey();
313 ValueDifference<String> underlayNetworkDiff = changedEntry.getValue();
314 List<String> zoneNames = tepTzMap.get(ipAddress);
315 if (zoneNames != null) {
316 for (String zoneName : zoneNames) {
317 String removedUnderlayNetwork = underlayNetworkDiff.leftValue();
318 String addedUnderlayNetwork = underlayNetworkDiff.rightValue();
319 Optional<String> zonePrefixOpt = getZonePrefixForUnderlayNetwork(zoneName, removedUnderlayNetwork);
320 if (zonePrefixOpt.isPresent()) {
321 String zonePrefix = zonePrefixOpt.get();
322 removeVtep(zoneName, dpId, tx);
323 zonePrefixes.add(zonePrefix);
324 String newZoneName = getTzNameForUnderlayNetwork(zonePrefix, addedUnderlayNetwork);
325 updateTransportZone(newZoneName, dpId, ipAddress, tx);
332 private static void handleRemovedLocalIps(Map<String, String> removedEntries, Uint64 dpId,
333 Set<String> zonePrefixes, Map<String, List<String>> tepTzMap,
334 @NonNull TypedWriteTransaction<Configuration> tx) {
335 if (removedEntries == null || removedEntries.isEmpty()) {
336 LOG.trace("No removed local_ips found on DPN {}", dpId);
340 LOG.debug("Removed local_ips {} for DPN {}", removedEntries.keySet(), dpId);
341 removedEntries.forEach((ipAddress, underlayNetworkName) -> {
342 List<String> zoneNames = tepTzMap.get(ipAddress);
343 if (zoneNames != null) {
344 for (String zoneName : zoneNames) {
345 Optional<String> zonePrefix = getZonePrefixForUnderlayNetwork(zoneName, underlayNetworkName);
346 if (zonePrefix.isPresent()) {
347 removeVtep(zoneName, dpId, tx);
348 zonePrefixes.add(zonePrefix.get());
355 private List<String> getTepTransportZoneNames(TunnelEndPoints tep) {
356 List<TzMembership> tzMembershipList = new ArrayList<TzMembership>(tep.nonnullTzMembership().values());
357 if (tzMembershipList == null) {
358 LOG.debug("No TZ membership exist for TEP ip {}", tep.getIpAddress().stringValue());
359 return Collections.emptyList();
362 return tzMembershipList.stream().map(TzMembership::getZoneName).distinct()
363 .collect(Collectors.toList());
366 private static Optional<DPNTEPsInfo> getDpnTepsInfo(Uint64 dpId, TypedReadTransaction<Configuration> tx) {
367 InstanceIdentifier<DPNTEPsInfo> identifier = InstanceIdentifier.builder(DpnEndpoints.class)
368 .child(DPNTEPsInfo.class, new DPNTEPsInfoKey(dpId)).build();
370 return tx.read(identifier).get();
371 } catch (InterruptedException | ExecutionException e) {
372 LOG.warn("Failed to read DPNTEPsInfo for DPN id {}", dpId);
373 return Optional.empty();
378 * Tries to add a vtep for a transport zone.
380 * @return Whether a vtep was added or not.
382 private boolean addVtep(TransportZone zone, String subnetIp, Uint64 dpnId, @Nullable String localIp) {
383 for (Vteps existingVtep : new ArrayList<Vteps>(zone.nonnullVteps().values())) {
384 if (Objects.equals(existingVtep.getDpnId(), dpnId)) {
389 if (localIp != null) {
390 //This seems to be a unused code, creating unused objects
391 //Check if any assignment needed here.
392 /*IpAddress nodeIp = IpAddressBuilder.getDefaultInstance(localIp);
393 VtepsBuilder vtepsBuilder = new VtepsBuilder().setDpnId(dpnId).setIpAddress(nodeIp)
394 .setOptionOfTunnel(elanConfig.isUseOfTunnels());
395 zone.getVteps().add(vtepsBuilder.build());*/
402 private static void removeVtep(String zoneName, Uint64 dpId, @NonNull TypedWriteTransaction<Configuration> tx) {
403 InstanceIdentifier<Vteps> path = InstanceIdentifier.builder(TransportZones.class)
404 .child(TransportZone.class, new TransportZoneKey(zoneName))
405 .child(Vteps.class, new VtepsKey(dpId)).build();
410 private String getDpnLocalIp(Uint64 dpId) throws ReadFailedException {
411 Optional<Node> node = getPortsNode(dpId);
413 if (node.isPresent()) {
414 String localIp = southBoundUtils.getOpenvswitchOtherConfig(node.get(), LOCAL_IP);
415 if (localIp == null) {
416 LOG.error("missing local_ip key in ovsdb:openvswitch-other-configs in operational"
417 + " network-topology for node: {}", node.get().getNodeId().getValue());
427 private Map<String, String> getDpnLocalIps(Uint64 dpId) throws ReadFailedException {
428 // Example of local IPs from other_config:
429 // local_ips="10.0.43.159:MPLS,11.11.11.11:DSL,ip:underlay-network"
430 return getPortsNode(dpId).map(
431 node -> elanBridgeManager.getOpenvswitchOtherConfigMap(node, LOCAL_IPS)).orElse(Collections.emptyMap());
434 @SuppressWarnings("unchecked")
435 private Optional<Node> getPortsNode(Uint64 dpnId) throws ReadFailedException {
436 InstanceIdentifier<BridgeRefEntry> bridgeRefInfoPath = InstanceIdentifier.create(BridgeRefInfo.class)
437 .child(BridgeRefEntry.class, new BridgeRefEntryKey(dpnId));
440 // FIXME: Read this through a cache
441 Optional<BridgeRefEntry> optionalBridgeRefEntry =
443 .syncReadOptional(LogicalDatastoreType.OPERATIONAL, bridgeRefInfoPath);
444 if (!optionalBridgeRefEntry.isPresent()) {
445 LOG.error("no bridge ref entry found for dpnId {}", dpnId);
446 return Optional.empty();
449 InstanceIdentifier<Node> nodeId =
450 optionalBridgeRefEntry.get().getBridgeReference().getValue()
451 .firstIdentifierOf(Node.class);
453 // FIXME: Read this through a cache
454 Optional<Node> optionalNode = singleTxBroker
455 .syncReadOptional(LogicalDatastoreType.OPERATIONAL, nodeId);
456 if (!optionalNode.isPresent()) {
457 LOG.error("missing node for dpnId {}", dpnId);
460 } catch (ExecutionException | InterruptedException e) {
461 LOG.error("Exception while getting ports for Node {}", dpnId, e);
463 return Optional.empty();
466 private static String getTzNameForUnderlayNetwork(String zoneNamePrefix, String underlayNetworkName) {
467 return zoneNamePrefix + IP_NETWORK_ZONE_NAME_DELIMITER + underlayNetworkName;
470 private static Optional<String> getZonePrefixForUnderlayNetwork(String zoneName, String underlayNetworkName) {
471 String[] zoneParts = zoneName.split(IP_NETWORK_ZONE_NAME_DELIMITER + underlayNetworkName);
472 return zoneParts.length == 2 ? Optional.of(zoneParts[0]) : Optional.empty();