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