2 * Copyright (c) 2015 - 2017 Ericsson India Global Services Pvt Ltd. 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.vpnmanager;
10 import com.google.common.base.Optional;
11 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Objects;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.Future;
21 import java.util.concurrent.locks.ReentrantLock;
22 import javax.annotation.PostConstruct;
23 import javax.inject.Inject;
24 import javax.inject.Singleton;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
28 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
31 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
32 import org.opendaylight.genius.datastoreutils.listeners.DataTreeEventCallbackRegistrar;
33 import org.opendaylight.genius.infra.Datastore.Configuration;
34 import org.opendaylight.genius.infra.Datastore.Operational;
35 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
36 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
37 import org.opendaylight.genius.infra.TypedReadTransaction;
38 import org.opendaylight.genius.infra.TypedReadWriteTransaction;
39 import org.opendaylight.genius.infra.TypedWriteTransaction;
40 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
41 import org.opendaylight.genius.mdsalutil.FlowEntity;
42 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
43 import org.opendaylight.genius.mdsalutil.NwConstants;
44 import org.opendaylight.genius.mdsalutil.instructions.InstructionWriteMetadata;
45 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
46 import org.opendaylight.genius.utils.JvmGlobalLocks;
47 import org.opendaylight.infrautils.utils.concurrent.ListenableFutures;
48 import org.opendaylight.infrautils.utils.function.InterruptibleCheckedConsumer;
49 import org.opendaylight.netvirt.bgpmanager.api.IBgpManager;
50 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderInput;
51 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderInput.ArpReponderInputBuilder;
52 import org.opendaylight.netvirt.elan.arp.responder.ArpResponderUtil;
53 import org.opendaylight.netvirt.elanmanager.api.IElanService;
54 import org.opendaylight.netvirt.fibmanager.api.IFibManager;
55 import org.opendaylight.netvirt.fibmanager.api.RouteOrigin;
56 import org.opendaylight.netvirt.neutronvpn.api.enums.IpVersionChoice;
57 import org.opendaylight.netvirt.vpnmanager.api.IVpnManager;
58 import org.opendaylight.netvirt.vpnmanager.api.InterfaceUtils;
59 import org.opendaylight.netvirt.vpnmanager.api.VpnExtraRouteHelper;
60 import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.IVpnLinkService;
61 import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.InterVpnLinkCache;
62 import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.InterVpnLinkDataComposite;
63 import org.opendaylight.netvirt.vpnmanager.populator.input.L3vpnInput;
64 import org.opendaylight.netvirt.vpnmanager.populator.registry.L3vpnRegistry;
65 import org.opendaylight.serviceutils.upgrade.UpgradeState;
66 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.VpnInstances;
67 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.af.config.vpntargets.VpnTarget;
68 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.instances.VpnInstance;
69 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
70 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolInput;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolInputBuilder;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolOutput;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.dpn.interfaces.elan.dpn.interfaces.list.DpnInterfaces;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.vrfentries.VrfEntry;
80 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.SubnetsAssociatedToRouteTargets;
81 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.Adjacency;
82 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.subnets.associated.to.route.targets.RouteTarget;
83 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.subnets.associated.to.route.targets.RouteTargetKey;
84 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.subnets.associated.to.route.targets.route.target.AssociatedSubnet;
85 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.subnets.associated.to.route.targets.route.target.associated.subnet.AssociatedVpn;
86 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.to.extraroutes.vpn.extra.routes.Routes;
87 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPort;
88 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.subnetmaps.Subnetmap;
89 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
90 import org.opendaylight.yangtools.yang.common.RpcError;
91 import org.opendaylight.yangtools.yang.common.RpcResult;
92 import org.opendaylight.yangtools.yang.common.Uint32;
93 import org.opendaylight.yangtools.yang.common.Uint64;
94 import org.slf4j.Logger;
95 import org.slf4j.LoggerFactory;
98 public class VpnManagerImpl implements IVpnManager {
100 private static final Logger LOG = LoggerFactory.getLogger(VpnManagerImpl.class);
101 private final DataBroker dataBroker;
102 private final ManagedNewTransactionRunner txRunner;
103 private final IdManagerService idManager;
104 private final IMdsalApiManager mdsalManager;
105 private final IElanService elanService;
106 private final IInterfaceManager interfaceManager;
107 private final VpnSubnetRouteHandler vpnSubnetRouteHandler;
108 private final OdlInterfaceRpcService ifaceMgrRpcService;
109 private final IVpnLinkService ivpnLinkService;
110 private final IFibManager fibManager;
111 private final IBgpManager bgpManager;
112 private final InterVpnLinkCache interVpnLinkCache;
113 private final DataTreeEventCallbackRegistrar eventCallbacks;
114 private final UpgradeState upgradeState;
115 private final ItmRpcService itmRpcService;
116 private final VpnUtil vpnUtil;
119 public VpnManagerImpl(final DataBroker dataBroker,
120 final IdManagerService idManagerService,
121 final IMdsalApiManager mdsalManager,
122 final IElanService elanService,
123 final IInterfaceManager interfaceManager,
124 final VpnSubnetRouteHandler vpnSubnetRouteHandler,
125 final OdlInterfaceRpcService ifaceMgrRpcService,
126 final IVpnLinkService ivpnLinkService,
127 final IFibManager fibManager,
128 final IBgpManager bgpManager,
129 final InterVpnLinkCache interVpnLinkCache,
130 final DataTreeEventCallbackRegistrar dataTreeEventCallbackRegistrar,
131 final UpgradeState upgradeState,
132 final ItmRpcService itmRpcService,
133 final VpnUtil vpnUtil) {
134 this.dataBroker = dataBroker;
135 this.txRunner = new ManagedNewTransactionRunnerImpl(dataBroker);
136 this.idManager = idManagerService;
137 this.mdsalManager = mdsalManager;
138 this.elanService = elanService;
139 this.interfaceManager = interfaceManager;
140 this.vpnSubnetRouteHandler = vpnSubnetRouteHandler;
141 this.ifaceMgrRpcService = ifaceMgrRpcService;
142 this.ivpnLinkService = ivpnLinkService;
143 this.fibManager = fibManager;
144 this.bgpManager = bgpManager;
145 this.interVpnLinkCache = interVpnLinkCache;
146 this.eventCallbacks = dataTreeEventCallbackRegistrar;
147 this.upgradeState = upgradeState;
148 this.itmRpcService = itmRpcService;
149 this.vpnUtil = vpnUtil;
153 public void start() {
154 LOG.info("{} start", getClass().getSimpleName());
158 private void createIdPool() {
159 CreateIdPoolInput createPool = new CreateIdPoolInputBuilder()
160 .setPoolName(VpnConstants.VPN_IDPOOL_NAME)
161 .setLow(VpnConstants.VPN_IDPOOL_LOW)
162 .setHigh(VpnConstants.VPN_IDPOOL_HIGH)
165 Future<RpcResult<CreateIdPoolOutput>> result = idManager.createIdPool(createPool);
166 if (result != null && result.get().isSuccessful()) {
167 LOG.info("Created IdPool for VPN Service");
169 } catch (InterruptedException | ExecutionException e) {
170 LOG.error("Failed to create idPool for VPN Service", e);
173 // Now an IdPool for InterVpnLink endpoint's pseudo ports
174 CreateIdPoolInput createPseudoLporTagPool =
175 new CreateIdPoolInputBuilder().setPoolName(VpnConstants.PSEUDO_LPORT_TAG_ID_POOL_NAME)
176 .setLow(VpnConstants.LOWER_PSEUDO_LPORT_TAG)
177 .setHigh(VpnConstants.UPPER_PSEUDO_LPORT_TAG)
180 Future<RpcResult<CreateIdPoolOutput>> result = idManager.createIdPool(createPseudoLporTagPool);
181 if (result.get().isSuccessful()) {
182 LOG.debug("Created IdPool for Pseudo Port tags");
184 Collection<RpcError> errors = result.get().getErrors();
185 StringBuilder errMsg = new StringBuilder();
186 for (RpcError err : errors) {
187 errMsg.append(err.getMessage()).append("\n");
189 LOG.error("IdPool creation for PseudoPort tags failed. Reasons: {}", errMsg);
191 } catch (InterruptedException | ExecutionException e) {
192 LOG.error("Failed to create idPool for Pseudo Port tags", e);
197 public void addExtraRoute(String vpnName, String destination, String nextHop, String rd, @Nullable String routerID,
198 Uint32 l3vni, RouteOrigin origin, @Nullable String intfName, @Nullable Adjacency operationalAdj,
199 VrfEntry.EncapType encapType, Set<String> prefixListForRefreshFib,
200 @NonNull TypedWriteTransaction<Configuration> confTx) {
201 //add extra route to vpn mapping; advertise with nexthop as tunnel ip
202 vpnUtil.syncUpdate(LogicalDatastoreType.OPERATIONAL,
203 VpnExtraRouteHelper.getVpnToExtrarouteVrfIdIdentifier(vpnName, rd != null ? rd : routerID,
205 VpnUtil.getVpnToExtraroute(destination, Collections.singletonList(nextHop)));
208 if (intfName != null && !intfName.isEmpty()) {
209 dpnId = InterfaceUtils.getDpnForInterface(ifaceMgrRpcService, intfName);
210 String nextHopIp = InterfaceUtils.getEndpointIpAddressForDPN(dataBroker, dpnId);
211 if (nextHopIp == null || nextHopIp.isEmpty()) {
212 LOG.error("addExtraRoute: NextHop for interface {} is null / empty."
213 + " Failed advertising extra route for rd {} prefix {} dpn {}", intfName, rd, destination,
220 String primaryRd = vpnUtil.getPrimaryRd(vpnName);
222 // TODO: This is a limitation to be stated in docs. When configuring static route to go to
223 // another VPN, there can only be one nexthop or, at least, the nexthop to the interVpnLink should be in
225 Optional<InterVpnLinkDataComposite> optVpnLink = interVpnLinkCache.getInterVpnLinkByEndpoint(nextHop);
226 if (optVpnLink.isPresent() && optVpnLink.get().isActive()) {
227 InterVpnLinkDataComposite interVpnLink = optVpnLink.get();
228 // If the nexthop is the endpoint of Vpn2, then prefix must be advertised to Vpn1 in DC-GW, with nexthops
229 // pointing to the DPNs where Vpn1 is instantiated. LFIB in these DPNS must have a flow entry, with lower
230 // priority, where if Label matches then sets the lportTag of the Vpn2 endpoint and goes to LportDispatcher
231 // This is like leaking one of the Vpn2 routes towards Vpn1
232 String srcVpnUuid = interVpnLink.getVpnNameByIpAddress(nextHop);
233 String dstVpnUuid = interVpnLink.getOtherVpnNameByIpAddress(nextHop);
234 String dstVpnRd = vpnUtil.getVpnRd(dstVpnUuid);
235 Uint32 newLabel = vpnUtil.getUniqueId(VpnConstants.VPN_IDPOOL_NAME,
236 VpnUtil.getNextHopLabelKey(dstVpnRd, destination));
237 if (newLabel.longValue() == 0) {
238 LOG.error("addExtraRoute: Unable to fetch label from Id Manager. Bailing out of adding intervpnlink"
239 + " route for destination {}", destination);
242 ivpnLinkService.leakRoute(interVpnLink, srcVpnUuid, dstVpnUuid, destination, newLabel, RouteOrigin.STATIC);
244 Optional<Routes> optVpnExtraRoutes = VpnExtraRouteHelper
245 .getVpnExtraroutes(dataBroker, vpnName, rd != null ? rd : routerID, destination);
246 if (optVpnExtraRoutes.isPresent()) {
247 List<String> nhList = optVpnExtraRoutes.get().getNexthopIpList();
248 if (nhList != null && nhList.size() > 1) {
249 // If nhList is greater than one for vpnextraroute, a call to populatefib doesn't update vrfentry.
250 prefixListForRefreshFib.add(destination);
252 L3vpnInput input = new L3vpnInput().setNextHop(operationalAdj).setNextHopIp(nextHop)
253 .setL3vni(l3vni.longValue())
254 .setPrimaryRd(primaryRd).setVpnName(vpnName).setDpnId(dpnId)
255 .setEncapType(encapType).setRd(rd).setRouteOrigin(origin);
256 L3vpnRegistry.getRegisteredPopulator(encapType).populateFib(input, confTx);
263 public void delExtraRoute(String vpnName, String destination, String nextHop, String rd, @Nullable String routerID,
264 @Nullable String intfName, @NonNull TypedWriteTransaction<Configuration> confTx,
265 @NonNull TypedWriteTransaction<Operational> operTx) {
267 String tunnelIp = nextHop;
268 if (intfName != null && !intfName.isEmpty()) {
269 dpnId = InterfaceUtils.getDpnForInterface(ifaceMgrRpcService, intfName);
270 String nextHopIp = InterfaceUtils.getEndpointIpAddressForDPN(dataBroker, dpnId);
271 if (nextHopIp == null || nextHopIp.isEmpty()) {
272 LOG.error("delExtraRoute: NextHop for interface {} is null / empty."
273 + " Failed advertising extra route for rd {} prefix {} dpn {}", intfName, rd, destination,
276 tunnelIp = nextHopIp;
279 String primaryRd = vpnUtil.getVpnRd(vpnName);
280 removePrefixFromBGP(vpnName, primaryRd, rd, intfName, destination,
281 nextHop, tunnelIp, dpnId, confTx, operTx);
282 LOG.info("delExtraRoute: Removed extra route {} from interface {} for rd {}", destination, intfName, rd);
284 // add FIB route directly
285 fibManager.removeOrUpdateFibEntry(routerID, destination, tunnelIp, confTx);
286 LOG.info("delExtraRoute: Removed extra route {} from interface {} for rd {}", destination, intfName,
292 @SuppressWarnings("checkstyle:IllegalCatch")
293 public void removePrefixFromBGP(String vpnName, String primaryRd, String extraRouteRd, String vpnInterfaceName,
294 String prefix, String nextHop, String nextHopTunnelIp, Uint64 dpnId,
295 TypedWriteTransaction<Configuration> confTx,
296 TypedWriteTransaction<Operational> operTx) {
297 String vpnNamePrefixKey = VpnUtil.getVpnNamePrefixKey(vpnName, prefix);
298 // FIXME: separate out to somehow?
299 final ReentrantLock lock = JvmGlobalLocks.getLockForString(vpnNamePrefixKey);
300 LOG.info("removing prefix {} for nexthop {} in VPN {} rd {}", prefix, nextHop, vpnName, extraRouteRd);
303 if (vpnUtil.removeOrUpdateDSForExtraRoute(vpnName, primaryRd, extraRouteRd, vpnInterfaceName, prefix,
304 nextHop, nextHopTunnelIp, operTx)) {
307 fibManager.removeOrUpdateFibEntry(primaryRd, prefix, nextHopTunnelIp, confTx);
308 if (VpnUtil.isEligibleForBgp(extraRouteRd, vpnName, dpnId, null /*networkName*/)) {
309 // TODO: Might be needed to include nextHop here
310 bgpManager.withdrawPrefix(extraRouteRd, prefix);
312 LOG.info("removePrefixFromBGP: VPN WITHDRAW: Removed Fib Entry rd {} prefix {} nexthop {}", extraRouteRd,
314 } catch (RuntimeException e) {
315 LOG.error("removePrefixFromBGP: Delete prefix {} rd {} nextHop {} failed", prefix, extraRouteRd, nextHop);
322 public boolean isVPNConfigured() {
323 InstanceIdentifier<VpnInstances> vpnsIdentifier = InstanceIdentifier.builder(VpnInstances.class).build();
325 Optional<VpnInstances> optionalVpns =
326 SingleTransactionDataBroker.syncReadOptional(dataBroker, LogicalDatastoreType.CONFIGURATION,
328 if (!optionalVpns.isPresent()
329 || optionalVpns.get().getVpnInstance() == null
330 || optionalVpns.get().getVpnInstance().isEmpty()) {
331 LOG.trace("isVPNConfigured: No VPNs configured.");
334 } catch (ReadFailedException e) {
335 throw new RuntimeException("Error reading VPN " + vpnsIdentifier, e);
337 LOG.trace("isVPNConfigured: VPNs are configured on the system.");
342 public void addSubnetMacIntoVpnInstance(String vpnName, String subnetVpnName, String srcMacAddress,
343 Uint64 dpnId, TypedWriteTransaction<Configuration> confTx)
344 throws ExecutionException, InterruptedException {
345 setupSubnetMacInVpnInstance(vpnName, subnetVpnName, srcMacAddress, dpnId,
346 (vpnId, dpId, subnetVpnId) -> addGwMac(srcMacAddress, confTx, vpnId, dpId, subnetVpnId));
350 public void removeSubnetMacFromVpnInstance(String vpnName, String subnetVpnName, String srcMacAddress,
351 Uint64 dpnId, TypedReadWriteTransaction<Configuration> confTx)
352 throws ExecutionException, InterruptedException {
353 setupSubnetMacInVpnInstance(vpnName, subnetVpnName, srcMacAddress, dpnId,
354 (vpnId, dpId, subnetVpnId) -> removeGwMac(srcMacAddress, confTx, vpnId, dpId, subnetVpnId));
358 private interface VpnInstanceSubnetMacSetupMethod {
359 void process(Uint32 vpnId, Uint64 dpId, Uint32 subnetVpnId) throws InterruptedException, ExecutionException;
362 private void setupSubnetMacInVpnInstance(String vpnName, String subnetVpnName, String srcMacAddress,
363 Uint64 dpnId, VpnInstanceSubnetMacSetupMethod consumer)
364 throws ExecutionException, InterruptedException {
365 if (vpnName == null) {
366 LOG.warn("Cannot setup subnet MAC {} on DPN {}, null vpnName", srcMacAddress, dpnId);
370 Uint32 vpnId = vpnUtil.getVpnId(vpnName);
371 Uint32 subnetVpnId = vpnUtil.getVpnId(subnetVpnName);
372 if (dpnId.equals(Uint64.ZERO)) {
373 /* Apply the MAC on all DPNs in a VPN */
374 for (Uint64 dpId : vpnUtil.getDpnsOnVpn(vpnName)) {
375 consumer.process(vpnId, dpId, subnetVpnId);
378 consumer.process(vpnId, dpnId, subnetVpnId);
382 private void addGwMac(String srcMacAddress, TypedWriteTransaction<Configuration> tx, Uint32 vpnId, Uint64 dpId,
383 Uint32 subnetVpnId) {
384 FlowEntity flowEntity = vpnUtil.buildL3vpnGatewayFlow(dpId, srcMacAddress, vpnId, subnetVpnId);
385 mdsalManager.addFlow(tx, flowEntity);
388 // TODO skitt Fix the exception handling here
389 @SuppressWarnings("checkstyle:IllegalCatch")
390 @SuppressFBWarnings("REC_CATCH_EXCEPTION")
391 private void removeGwMac(String srcMacAddress, TypedReadWriteTransaction<Configuration> tx, Uint32 vpnId,
392 Uint64 dpId, Uint32 subnetVpnId) throws ExecutionException, InterruptedException {
393 mdsalManager.removeFlow(tx, dpId,
394 VpnUtil.getL3VpnGatewayFlowRef(NwConstants.L3_GW_MAC_TABLE, dpId, vpnId, srcMacAddress, subnetVpnId),
395 NwConstants.L3_GW_MAC_TABLE);
399 public void addRouterGwMacFlow(String routerName, String routerGwMac, Uint64 dpnId, Uuid extNetworkId,
400 String subnetVpnName, TypedWriteTransaction<Configuration> confTx)
401 throws ExecutionException, InterruptedException {
402 setupRouterGwMacFlow(routerName, routerGwMac, dpnId, extNetworkId,
403 vpnId -> addSubnetMacIntoVpnInstance(vpnId, subnetVpnName, routerGwMac, dpnId, confTx), "Installing");
407 public void removeRouterGwMacFlow(String routerName, String routerGwMac, Uint64 dpnId, Uuid extNetworkId,
408 String subnetVpnName, TypedReadWriteTransaction<Configuration> confTx)
409 throws ExecutionException, InterruptedException {
410 setupRouterGwMacFlow(routerName, routerGwMac, dpnId, extNetworkId,
411 vpnId -> removeSubnetMacFromVpnInstance(vpnId, subnetVpnName, routerGwMac, dpnId, confTx), "Removing");
414 private void setupRouterGwMacFlow(String routerName, String routerGwMac, Uint64 dpnId, Uuid extNetworkId,
415 InterruptibleCheckedConsumer<String, ExecutionException> consumer, String operation)
416 throws ExecutionException, InterruptedException {
417 if (routerGwMac == null) {
418 LOG.warn("Failed to handle router GW flow in GW-MAC table. MAC address is missing for router-id {}",
423 if (dpnId == null || Uint64.ZERO.equals(dpnId)) {
424 LOG.info("setupRouterGwMacFlow: DPN id is missing for router-id {}",
429 Uuid vpnId = vpnUtil.getExternalNetworkVpnId(extNetworkId);
431 LOG.warn("Network {} is not associated with VPN", extNetworkId.getValue());
435 LOG.info("{} router GW MAC flow for router-id {} on switch {}", operation, routerName, dpnId);
436 consumer.accept(vpnId.getValue());
440 public void addArpResponderFlowsToExternalNetworkIps(String id, Collection<String> fixedIps, String macAddress,
441 Uint64 dpnId, Uuid extNetworkId) {
443 if (dpnId == null || Uint64.ZERO.equals(dpnId)) {
444 LOG.warn("Failed to install arp responder flows for router {}. DPN id is missing.", id);
448 String extInterfaceName = elanService.getExternalElanInterface(extNetworkId.getValue(), dpnId);
449 if (extInterfaceName != null) {
450 doAddArpResponderFlowsToExternalNetworkIps(
451 id, fixedIps, macAddress, dpnId, extInterfaceName);
455 LOG.warn("Failed to install responder flows for {}. No external interface found for DPN id {}", id, dpnId);
457 if (!upgradeState.isUpgradeInProgress()) {
461 // The following through the end of the function deals with an upgrade scenario where the neutron configuration
462 // is restored before the OVS switches reconnect. In such a case, the elan-dpn-interfaces entries will be
463 // missing from the operational data store. In order to mitigate this we use DataTreeEventCallbackRegistrar
464 // to wait for the exact operational md-sal object we need to contain the external interface we need.
466 LOG.info("Upgrade in process, waiting for an external interface to appear on dpn {} for elan {}",
467 dpnId, extNetworkId.getValue());
469 InstanceIdentifier<DpnInterfaces> dpnInterfacesIid =
470 elanService.getElanDpnInterfaceOperationalDataPath(extNetworkId.getValue(), dpnId);
472 eventCallbacks.onAddOrUpdate(LogicalDatastoreType.OPERATIONAL, dpnInterfacesIid, (unused, alsoUnused) -> {
473 LOG.info("Reattempting write of arp responder for external interfaces for external network {}",
475 DpnInterfaces dpnInterfaces = elanService.getElanInterfaceInfoByElanDpn(extNetworkId.getValue(), dpnId);
476 if (dpnInterfaces == null) {
477 LOG.error("Could not retrieve DpnInterfaces for {}, {}", extNetworkId.getValue(), dpnId);
478 return DataTreeEventCallbackRegistrar.NextAction.UNREGISTER;
481 String extIfc = null;
482 @Nullable List<String> interfaces = dpnInterfaces.getInterfaces();
483 if (interfaces != null) {
484 for (String dpnInterface : interfaces) {
485 if (interfaceManager.isExternalInterface(dpnInterface)) {
486 extIfc = dpnInterface;
492 if (extIfc == null) {
493 if (upgradeState.isUpgradeInProgress()) {
494 LOG.info("External interface not found yet in elan {} on dpn {}, keep waiting",
495 extNetworkId.getValue(), dpnInterfaces);
496 return DataTreeEventCallbackRegistrar.NextAction.CALL_AGAIN;
498 return DataTreeEventCallbackRegistrar.NextAction.UNREGISTER;
502 final String extIfcFinal = extIfc;
503 doAddArpResponderFlowsToExternalNetworkIps(id, fixedIps, macAddress, dpnId, extIfcFinal);
505 return DataTreeEventCallbackRegistrar.NextAction.UNREGISTER;
511 public void addArpResponderFlowsToExternalNetworkIps(String id, Collection<String> fixedIps, String macAddress,
512 Uint64 dpnId, String extInterfaceName, int lportTag) {
513 if (fixedIps == null || fixedIps.isEmpty()) {
514 LOG.debug("No external IPs defined for {}", id);
518 LOG.info("Installing ARP responder flows for {} fixed-ips {} on switch {}", id, fixedIps, dpnId);
520 for (String fixedIp : fixedIps) {
521 IpVersionChoice ipVersionChoice = VpnUtil.getIpVersionFromString(fixedIp);
522 if (ipVersionChoice == IpVersionChoice.IPV6) {
525 installArpResponderFlowsToExternalNetworkIp(macAddress, dpnId, extInterfaceName, lportTag,
530 private void doAddArpResponderFlowsToExternalNetworkIps(String id, Collection<String> fixedIps, String macAddress,
531 Uint64 dpnId, String extInterfaceName) {
532 Interface extInterfaceState = InterfaceUtils.getInterfaceStateFromOperDS(dataBroker, extInterfaceName);
533 if (extInterfaceState == null) {
534 LOG.debug("No interface state found for interface {}. Delaying responder flows for {}", extInterfaceName,
539 Integer lportTag = extInterfaceState.getIfIndex();
540 if (lportTag == null) {
541 LOG.debug("No Lport tag found for interface {}. Delaying flows for router-id {}", extInterfaceName, id);
545 if (macAddress == null) {
547 LOG.debug("Failed to install arp responder flows for router-gateway-ip {} for router {}."
548 + "External Gw MacAddress is missing.", fixedIps, id);
552 addArpResponderFlowsToExternalNetworkIps(id, fixedIps, macAddress, dpnId, extInterfaceName, lportTag);
556 public void removeArpResponderFlowsToExternalNetworkIps(String id, Collection<String> fixedIps, String macAddress,
557 Uint64 dpnId, Uuid extNetworkId) {
559 if (dpnId == null || Uint64.ZERO.equals(dpnId)) {
560 LOG.warn("Failed to remove arp responder flows for router {}. DPN id is missing.", id);
564 String extInterfaceName = elanService.getExternalElanInterface(extNetworkId.getValue(), dpnId);
565 if (extInterfaceName == null) {
566 LOG.warn("Failed to remove responder flows for {}. No external interface found for DPN id {}", id, dpnId);
570 Interface extInterfaceState = InterfaceUtils.getInterfaceStateFromOperDS(dataBroker, extInterfaceName);
571 if (extInterfaceState == null) {
572 LOG.debug("No interface state found for interface {}. Delaying responder flows for {}", extInterfaceName,
577 Integer lportTag = extInterfaceState.getIfIndex();
578 if (lportTag == null) {
579 LOG.debug("No Lport tag found for interface {}. Delaying flows for router-id {}", extInterfaceName, id);
583 if (macAddress == null) {
585 LOG.debug("Failed to remove arp responder flows for router-gateway-ip {} for router {}."
586 + "External Gw MacAddress is missing.", fixedIps, id);
590 removeArpResponderFlowsToExternalNetworkIps(id, fixedIps, dpnId,
591 extInterfaceName, lportTag);
595 public void removeArpResponderFlowsToExternalNetworkIps(String id, Collection<String> fixedIps,
596 Uint64 dpnId, String extInterfaceName, int lportTag) {
597 if (fixedIps == null || fixedIps.isEmpty()) {
598 LOG.debug("No external IPs defined for {}", id);
602 LOG.info("Removing ARP responder flows for {} fixed-ips {} on switch {}", id, fixedIps, dpnId);
604 for (String fixedIp : fixedIps) {
605 removeArpResponderFlowsToExternalNetworkIp(dpnId, lportTag, fixedIp, extInterfaceName);
610 public String getPrimaryRdFromVpnInstance(VpnInstance vpnInstance) {
611 return VpnUtil.getPrimaryRd(vpnInstance);
614 private void installArpResponderFlowsToExternalNetworkIp(String macAddress, Uint64 dpnId,
615 String extInterfaceName, int lportTag, String fixedIp) {
616 // reset the split-horizon bit to allow traffic to be sent back to the
618 List<Instruction> instructions = new ArrayList<>();
620 new InstructionWriteMetadata(Uint64.ZERO,
621 MetaDataUtil.METADATA_MASK_SH_FLAG).buildInstruction(1));
623 ArpResponderUtil.getExtInterfaceInstructions(interfaceManager, itmRpcService, extInterfaceName,
624 fixedIp, macAddress));
625 ArpReponderInputBuilder builder = new ArpReponderInputBuilder().setDpId(dpnId.toJava())
626 .setInterfaceName(extInterfaceName).setSpa(fixedIp).setSha(macAddress).setLportTag(lportTag);
627 builder.setInstructions(instructions);
628 elanService.addArpResponderFlow(builder.buildForInstallFlow());
631 private void removeArpResponderFlowsToExternalNetworkIp(Uint64 dpnId, Integer lportTag, String fixedIp,
632 String extInterfaceName) {
633 ArpResponderInput arpInput = new ArpReponderInputBuilder()
634 .setDpId(dpnId.toJava()).setInterfaceName(extInterfaceName)
635 .setSpa(fixedIp).setLportTag(lportTag).buildForRemoveFlow();
636 elanService.removeArpResponderFlow(arpInput);
640 public void onSubnetAddedToVpn(Subnetmap subnetmap, boolean isBgpVpn, Long elanTag) {
641 vpnSubnetRouteHandler.onSubnetAddedToVpn(subnetmap, isBgpVpn, elanTag);
645 public void onSubnetDeletedFromVpn(Subnetmap subnetmap, boolean isBgpVpn) {
646 vpnSubnetRouteHandler.onSubnetDeletedFromVpn(subnetmap, isBgpVpn);
651 public VpnInstance getVpnInstance(DataBroker broker, String vpnInstanceName) {
652 return vpnUtil.getVpnInstance(vpnInstanceName);
656 public String getVpnRd(TypedReadTransaction<Configuration> confTx, String vpnName) {
657 return VpnUtil.getVpnRd(confTx, vpnName);
661 public String getVpnRd(DataBroker broker, String vpnName) {
662 return vpnUtil.getVpnRd(vpnName);
666 public VpnPortipToPort getNeutronPortFromVpnPortFixedIp(TypedReadTransaction<Configuration> confTx, String vpnName,
668 return VpnUtil.getNeutronPortFromVpnPortFixedIp(confTx, vpnName, fixedIp);
673 public VpnPortipToPort getNeutronPortFromVpnPortFixedIp(DataBroker broker, String vpnName, String fixedIp) {
674 return vpnUtil.getNeutronPortFromVpnPortFixedIp(vpnName, fixedIp);
678 public Set<VpnTarget> getRtListForVpn(String vpnName) {
679 return vpnUtil.getRtListForVpn(vpnName);
683 // Allow deprecated TransactionRunner calls for now
684 @SuppressWarnings("ForbidCertainMethod")
685 public void updateRouteTargetsToSubnetAssociation(Set<VpnTarget> routeTargets, String cidr, String vpnName) {
686 ListenableFutures.addErrorLogging(txRunner.callWithNewReadWriteTransactionAndSubmit(tx -> {
687 for (VpnTarget rt : routeTargets) {
688 String rtValue = rt.getVrfRTValue();
689 switch (rt.getVrfRTType()) {
690 case ImportExtcommunity:
691 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.IRT, cidr, vpnName,
692 tx, false/*isAssociationRemoved*/);
694 case ExportExtcommunity:
695 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.ERT, cidr, vpnName,
696 tx, false/*isAssociationRemoved*/);
699 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.IRT, cidr, vpnName,
700 tx, false/*isAssociationRemoved*/);
701 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.ERT, cidr, vpnName,
702 tx, false/*isAssociationRemoved*/);
705 LOG.error("updateRouteTargetsToSubnetAssociation: Invalid rt-type {} for vpn {}"
706 + " subnet-cidr {}", rt.getVrfRTType(), vpnName, cidr);
710 }), LOG, "updateRouteTargetsToSubnetAssociation: Failed for vpn {} subnet-cidr {}", vpnName, cidr);
711 LOG.info("updateRouteTargetsToSubnetAssociation: Updated RT to subnet association for vpn {} cidr {}",
716 // Allow deprecated TransactionRunner calls for now
717 @SuppressWarnings("ForbidCertainMethod")
718 public void removeRouteTargetsToSubnetAssociation(Set<VpnTarget> routeTargets, String cidr, String vpnName) {
719 ListenableFutures.addErrorLogging(txRunner.callWithNewReadWriteTransactionAndSubmit(tx -> {
720 for (VpnTarget rt : routeTargets) {
721 String rtValue = rt.getVrfRTValue();
722 switch (rt.getVrfRTType()) {
723 case ImportExtcommunity:
724 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.IRT, cidr, vpnName, tx,
725 true/*isAssociationRemoved*/);
727 case ExportExtcommunity:
728 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.ERT, cidr, vpnName, tx,
729 true/*isAssociationRemoved*/);
732 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.IRT, cidr, vpnName, tx,
733 true/*isAssociationRemoved*/);
734 addSubnetAssociationOperationToTx(rtValue, RouteTarget.RtType.ERT, cidr, vpnName, tx,
735 true/*isAssociationRemoved*/);
738 LOG.error("removeRouteTargetsToSubnetAssociation: Invalid route-target type {} for vpn {}"
739 + " subnet-cidr {}", rt.getVrfRTType(), vpnName, cidr);
743 }), LOG, "removeRouteTargetsToSubnetAssociation: Failed for vpn {} subnet-cidr {}", vpnName, cidr);
744 LOG.info("removeRouteTargetsToSubnetAssociation: vpn {} cidr {}", vpnName, cidr);
747 private boolean doesExistingVpnsHaveConflictingSubnet(Set<VpnTarget> routeTargets, String subnetCidr) {
748 Set<RouteTarget> routeTargetSet = vpnUtil.getRouteTargetSet(routeTargets);
749 for (RouteTarget routerTarget : routeTargetSet) {
750 if (routerTarget.getAssociatedSubnet() != null) {
751 for (int i = 0; i < routerTarget.getAssociatedSubnet().size(); i++) {
752 AssociatedSubnet associatedSubnet = routerTarget.getAssociatedSubnet().get(i);
753 if (VpnUtil.areSubnetsOverlapping(associatedSubnet.getCidr(), subnetCidr)) {
756 if (routerTarget.getRtType() == RouteTarget.RtType.ERT) {
757 /* Check if there are multiple exports for the input iRT value (iRT in routeTargets)
758 * Example : (1) iRT=A eRT=B subnet-range=S1; OK
759 * (2) iRT=A eRT=B subnet-range=S1; OK
760 * (3) iRT=B eRT=A subnet-range=S2; NOK
761 * Check if (1) and (2) are importing the same subnet-range routes to (3) */
762 List<AssociatedVpn> multipleAssociatedVpn = associatedSubnet.getAssociatedVpn();
763 if (multipleAssociatedVpn != null && multipleAssociatedVpn.size() > 1) {
764 LOG.error("doesExistingVpnsHaveConflictingSubnet: There is an indirect complete overlap"
765 + " for subnet CIDR {} for rt {} rtType {}", subnetCidr, routerTarget.getRt(),
766 routerTarget.getRtType());
769 for (int j = i + 1; j < routerTarget.getAssociatedSubnet().size(); j++) {
770 if (VpnUtil.areSubnetsOverlapping(associatedSubnet.getCidr(),
771 routerTarget.getAssociatedSubnet().get(j).getCidr())) {
772 LOG.error("doesExistingVpnsHaveConflictingSubnet: There is an indirect paartial"
773 + " overlap for subnet CIDR {} for rt {} rtType {}", subnetCidr,
774 routerTarget.getRt(), routerTarget.getRtType());
780 /* Check if there are indirect EXPORTS for the eRT value (eRT in routeTargets)
781 * Example : (1) iRT=A eRT=B subnet-range=S1; OK
782 * (2) iRT=B eRT=A subnet-range=S2; OK
783 * (3) iRT=A eRT=B subnet-range=S1; NOK
784 * If associatedSubnet is non-null for a routeTarget in (2),
785 * it may have already imported routes from (1) */
786 if (routerTarget.getRtType() == RouteTarget.RtType.IRT) {
788 Optional<RouteTarget> indirectRts = SingleTransactionDataBroker.syncReadOptional(dataBroker,
789 LogicalDatastoreType.OPERATIONAL, VpnUtil.getRouteTargetsIdentifier(
790 routerTarget.getRt(), RouteTarget.RtType.ERT));
791 if (indirectRts.isPresent() && indirectRts.get().getAssociatedSubnet() != null
792 && routerTarget.getAssociatedSubnet() != null) {
793 for (AssociatedSubnet associatedSubnet : indirectRts.get().getAssociatedSubnet()) {
794 if (VpnUtil.areSubnetsOverlapping(associatedSubnet.getCidr(), subnetCidr)) {
795 LOG.error("doesExistingVpnsHaveConflictingSubnet: There is an indirect overlap for"
796 + " subnet CIDR {} for rt {} rtType {}", subnetCidr, routerTarget.getRt(),
797 routerTarget.getRtType());
802 } catch (ReadFailedException e) {
803 LOG.error("doesExistingVpnsHaveConflictingSubnet: Failed to read route targets to subnet"
804 + "association for rt {} type {} subnet-cidr {}", routerTarget.getRt(),
805 RouteTarget.RtType.ERT, subnetCidr);
806 return true; //Fail subnet association to avoid further damage to the data-stores
810 LOG.info("doesExistingVpnsHaveConflictingSubnet: No prior associated subnets for rtVal {} rtType {}",
811 routerTarget.getRt(), routerTarget.getRtType());
817 private void addSubnetAssociationOperationToTx(String rt, RouteTarget.RtType rtType, String cidr,
818 String vpnName, ReadWriteTransaction tx,
819 boolean isAssociationRemoved)
820 throws InterruptedException, ExecutionException {
821 if (isAssociationRemoved) {
822 //Remove RT-Subnet-Vpn Association
823 Optional<AssociatedSubnet> associatedSubnet = tx.read(LogicalDatastoreType.OPERATIONAL,
824 VpnUtil.getAssociatedSubnetIdentifier(rt, rtType, cidr)).get();
825 boolean deleteParent = false;
826 if (associatedSubnet.isPresent()) {
827 List<AssociatedVpn> associatedVpns = new ArrayList<>(associatedSubnet.get().nonnullAssociatedVpn());
828 if (associatedVpns == null || associatedVpns.isEmpty()) {
831 for (Iterator<AssociatedVpn> iterator = associatedVpns.iterator(); iterator.hasNext();) {
832 AssociatedVpn associatedVpn = iterator.next();
833 if (Objects.equals(associatedVpn.getName(), vpnName)) {
838 if (associatedVpns.isEmpty()) {
844 deleteParentForSubnetToVpnAssocication(rt, rtType, cidr, tx);
846 //Some other VPNs are also part of this rtVal, rtType and subnetCidr combination.
847 //Delete only this AssociatedVpn Object
848 tx.delete(LogicalDatastoreType.OPERATIONAL,
849 VpnUtil.getAssociatedSubnetAndVpnIdentifier(rt, rtType, cidr, vpnName));
850 LOG.debug("addSubnetAssocOperationToTx: Removed vpn {} from association rt {} rtType {} cidr {}",
851 vpnName, rt, rtType, cidr);
854 //Add RT-Subnet-Vpn Association
855 tx.put(LogicalDatastoreType.OPERATIONAL,
856 VpnUtil.getAssociatedSubnetAndVpnIdentifier(rt, rtType, cidr, vpnName),
857 VpnUtil.buildAssociatedSubnetAndVpn(vpnName), true);
861 private void deleteParentForSubnetToVpnAssocication(String rt, RouteTarget.RtType rtType,
862 String cidr, ReadWriteTransaction tx)
863 throws InterruptedException, ExecutionException {
864 //Check if you need to delete rtVal+rtType or just the subnetCidr
865 InstanceIdentifier<RouteTarget> rtIdentifier = InstanceIdentifier
866 .builder(SubnetsAssociatedToRouteTargets.class).child(RouteTarget.class,
867 new RouteTargetKey(rt, rtType)).build();
868 Optional<RouteTarget> rtToSubnetsAssociation = tx.read(LogicalDatastoreType.OPERATIONAL,
870 if (rtToSubnetsAssociation.isPresent()) {
871 List<AssociatedSubnet> associatedSubnets = new ArrayList<>(rtToSubnetsAssociation.get()
872 .nonnullAssociatedSubnet());
873 if (associatedSubnets != null && !associatedSubnets.isEmpty()) {
874 for (Iterator<AssociatedSubnet> iterator = associatedSubnets.iterator(); iterator.hasNext(); ) {
875 if (Objects.equals(iterator.next().getCidr(), cidr)) {
880 if (associatedSubnets.isEmpty()) {
881 //The entire rt to subnet association is empty
882 //Delete the RouteTarget object
883 tx.delete(LogicalDatastoreType.OPERATIONAL, rtIdentifier);
884 LOG.debug("deleteParentForSubnetToVpnAssocication: Removed rt {} rtType {} from association,",
887 //Some other VPNs are also part of this rtVal, rtType combination
888 //Delete only this AssociatedSubnet
889 tx.delete(LogicalDatastoreType.OPERATIONAL, VpnUtil.getAssociatedSubnetIdentifier(rt, rtType,
891 LOG.debug("deleteParentForSubnetToVpnAssocication: Removed cidr {} from association rt {}"
892 + " rtType {}", cidr, rt, rtType);
899 public boolean checkForOverlappingSubnets(Uuid network, List<Subnetmap> subnetmapList, Uuid vpn,
900 Set<VpnTarget> routeTargets, List<String> failedNwList) {
901 for (int i = 0; i < subnetmapList.size(); i++) {
902 //Check if any other subnet that is already part of a different vpn with same rt, has overlapping CIDR
903 if (checkExistingSubnetWithSameRoutTargets(routeTargets, vpn, subnetmapList.get(i), failedNwList)) {
910 private boolean checkExistingSubnetWithSameRoutTargets(Set<VpnTarget> routeTargets, Uuid vpn, Subnetmap subnetmap,
911 List<String> failedNwList) {
912 String cidr = String.valueOf(subnetmap.getSubnetIp());
913 boolean subnetExistsWithSameRt = doesExistingVpnsHaveConflictingSubnet(routeTargets, cidr);
914 if (subnetExistsWithSameRt) {
915 failedNwList.add(String.format("Another subnet with the same/overlapping cidr %s as subnet %s"
916 + " is already associated to a vpn with routeTargets %s. Ignoring subnet addition to vpn"
917 + " %s", cidr, subnetmap.getId().getValue(), routeTargets.toString(), vpn.getValue()));
919 return subnetExistsWithSameRt;