2 * Copyright (c) 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.intervpnlink;
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import java.math.BigInteger;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.List;
18 import java.util.function.Predicate;
19 import java.util.stream.Collectors;
20 import javax.annotation.PostConstruct;
21 import javax.annotation.PreDestroy;
22 import javax.inject.Inject;
23 import javax.inject.Singleton;
24 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
25 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
26 import org.opendaylight.genius.mdsalutil.MDSALUtil;
27 import org.opendaylight.genius.mdsalutil.NwConstants;
28 import org.opendaylight.netvirt.bgpmanager.api.IBgpManager;
29 import org.opendaylight.netvirt.fibmanager.api.FibHelper;
30 import org.opendaylight.netvirt.fibmanager.api.IFibManager;
31 import org.opendaylight.netvirt.fibmanager.api.RouteOrigin;
32 import org.opendaylight.netvirt.vpnmanager.VpnConstants;
33 import org.opendaylight.netvirt.vpnmanager.VpnUtil;
34 import org.opendaylight.netvirt.vpnmanager.api.InterfaceUtils;
35 import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.IVpnLinkService;
36 import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.InterVpnLinkCache;
37 import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.InterVpnLinkDataComposite;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.FibEntries;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.fibentries.VrfTables;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.fibentries.VrfTablesKey;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.vrfentries.VrfEntry;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.vrfentries.VrfEntryBuilder;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.vrfentries.VrfEntryKey;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.VpnMaps;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.vpnmaps.VpnMap;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.l3.attributes.Routes;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.Routers;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.Router;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
52 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 public class IVpnLinkServiceImpl implements IVpnLinkService, AutoCloseable {
59 private static final Logger LOG = LoggerFactory.getLogger(IVpnLinkServiceImpl.class);
61 private final DataBroker dataBroker;
62 private final IdManagerService idManager;
63 private final IBgpManager bgpManager;
64 private final IFibManager fibManager;
65 private final InterVpnLinkCache interVpnLinkCache;
68 public IVpnLinkServiceImpl(final DataBroker dataBroker, final IdManagerService idMgr, final IBgpManager bgpMgr,
69 final IFibManager fibMgr, final InterVpnLinkCache interVpnLinkCache) {
70 this.dataBroker = dataBroker;
71 this.idManager = idMgr;
72 this.bgpManager = bgpMgr;
73 this.fibManager = fibMgr;
74 this.interVpnLinkCache = interVpnLinkCache;
79 LOG.info("{} start", getClass().getSimpleName());
88 public void leakRoute(String vpnName, String prefix, List<String> nextHopList, int label, int addOrRemove) {
89 LOG.trace("leakRoute: vpnName={} prefix={} nhList={} label={}", vpnName, prefix, nextHopList, label);
90 Optional<InterVpnLinkDataComposite> optIVpnLink = interVpnLinkCache.getInterVpnLinkByVpnId(vpnName);
91 if (!optIVpnLink.isPresent()) {
92 LOG.debug("Vpn {} not involved in any InterVpnLink", vpnName);
95 leakRoute(optIVpnLink.get(), vpnName, prefix, nextHopList, label, addOrRemove);
98 // TODO Clean up the exception handling
99 @SuppressWarnings("checkstyle:IllegalCatch")
100 private void leakRoute(InterVpnLinkDataComposite interVpnLink, String vpnName, String prefix,
101 List<String> nextHopList, int label, int addOrRemove) {
103 String dstVpnName = interVpnLink.getOtherVpnName(vpnName);
105 LOG.trace("leakingRoute: from VPN={} to VPN={}: prefix={} nhList={} label={}",
106 vpnName, dstVpnName, prefix, nextHopList, label);
108 // For leaking, we need the InterVpnLink to be active.
109 if (addOrRemove == NwConstants.ADD_FLOW && !interVpnLink.isActive()) {
110 LOG.warn("Cannot leak route [prefix={}, label={}] from VPN {} to VPN {} because "
111 + "InterVpnLink {} is not active",
112 prefix, label, vpnName, dstVpnName, interVpnLink.getInterVpnLinkName());
116 String dstVpnRd = VpnUtil.getVpnRd(dataBroker, dstVpnName);
117 if (addOrRemove == NwConstants.ADD_FLOW) {
118 LOG.debug("Leaking route (prefix={}, nexthop={}) from Vpn={} to Vpn={} (RD={})",
119 prefix, nextHopList, vpnName, dstVpnName, dstVpnRd);
120 String key = dstVpnRd + VpnConstants.SEPARATOR + prefix;
121 long leakedLabel = VpnUtil.getUniqueId(idManager, VpnConstants.VPN_IDPOOL_NAME, key);
122 String leakedNexthop = interVpnLink.getEndpointIpAddr(vpnName);
123 fibManager.addOrUpdateFibEntry(dataBroker, dstVpnRd, null /*macAddress*/, prefix,
124 Collections.singletonList(leakedNexthop), VrfEntry.EncapType.Mplsgre,
125 (int) leakedLabel, 0 /*l3vni*/, null /*gatewayMacAddress*/,
126 null /*parentVpnRd*/, RouteOrigin.INTERVPN, null /*writeConfigTxn*/);
128 List<String> ivlNexthops =
129 interVpnLink.getEndpointDpnsByVpnName(dstVpnName).stream()
130 .map(dpnId -> InterfaceUtils.getEndpointIpAddressForDPN(dataBroker, dpnId))
131 .collect(Collectors.toList());
133 bgpManager.advertisePrefix(dstVpnRd, null /*macAddress*/, prefix, ivlNexthops,
134 VrfEntry.EncapType.Mplsgre, (int)leakedLabel, 0 /*l3vni*/, 0 /*l2vni*/,
135 null /*gwMacAddress*/);
136 } catch (Exception e) {
137 LOG.error("Exception while advertising prefix {} on vpnRd {} for intervpn link", prefix, dstVpnRd, e);
140 LOG.debug("Removing leaked route to {} from VPN {}", prefix, dstVpnName);
141 fibManager.removeFibEntry(dataBroker, dstVpnRd, prefix, null /*writeConfigTxn*/);
142 bgpManager.withdrawPrefix(dstVpnRd, prefix);
146 // TODO Clean up the exception handling
147 @SuppressWarnings("checkstyle:IllegalCatch")
149 public void leakRoute(InterVpnLinkDataComposite interVpnLink, String srcVpnUuid, String dstVpnUuid,
150 String prefix, Long label, RouteOrigin forcedOrigin, int addOrRemove) {
151 String ivpnLinkName = interVpnLink.getInterVpnLinkName();
152 // The source VPN must participate in the InterVpnLink
153 Preconditions.checkArgument(interVpnLink.isVpnLinked(srcVpnUuid),
154 "The source VPN {} does not participate in the interVpnLink {}",
155 srcVpnUuid, ivpnLinkName);
156 // The destination VPN must participate in the InterVpnLink
157 Preconditions.checkArgument(interVpnLink.isVpnLinked(dstVpnUuid),
158 "The destination VPN {} does not participate in the interVpnLink {}",
159 dstVpnUuid, ivpnLinkName);
161 String endpointIp = interVpnLink.getOtherEndpointIpAddr(dstVpnUuid);
162 String leakedOrigin = forcedOrigin != null ? forcedOrigin.getValue() : RouteOrigin.INTERVPN.getValue();
163 FibHelper.buildRoutePath(endpointIp, label);
164 VrfEntry newVrfEntry =
165 new VrfEntryBuilder().setKey(new VrfEntryKey(prefix)).setDestPrefix(prefix)
166 .setRoutePaths(Collections.singletonList(FibHelper.buildRoutePath(endpointIp, label)))
167 .setOrigin(leakedOrigin).build();
169 String dstVpnRd = VpnUtil.getVpnRd(dataBroker, dstVpnUuid);
170 InstanceIdentifier<VrfEntry> newVrfEntryIid =
171 InstanceIdentifier.builder(FibEntries.class)
172 .child(VrfTables.class, new VrfTablesKey(dstVpnRd))
173 .child(VrfEntry.class, new VrfEntryKey(newVrfEntry.getDestPrefix()))
175 VpnUtil.asyncWrite(dataBroker, LogicalDatastoreType.CONFIGURATION, newVrfEntryIid, newVrfEntry);
177 // Finally, route is advertised it to the DC-GW. But while in the FibEntries the nexthop is the other
178 // endpoint's IP, in the DC-GW the nexthop for those prefixes are the IPs of those DPNs where the target
179 // VPN has been instantiated
180 List<BigInteger> srcDpnList = interVpnLink.getEndpointDpnsByVpnName(srcVpnUuid);
181 List<String> nexthops =
182 srcDpnList.stream().map(dpnId -> InterfaceUtils.getEndpointIpAddressForDPN(dataBroker, dpnId))
183 .collect(Collectors.toList());
185 LOG.debug("Advertising route in VPN={} [prefix={} label={} nexthops={}] to DC-GW",
186 dstVpnRd, newVrfEntry.getDestPrefix(), label.intValue(), nexthops);
188 bgpManager.advertisePrefix(dstVpnRd, null /*macAddress*/, prefix, nexthops,
189 VrfEntry.EncapType.Mplsgre, label.intValue(), 0 /*l3vni*/, 0 /*l2vni*/,
190 null /*gwMacAddress*/);
191 } catch (Exception e) {
192 LOG.error("Exception while advertising prefix {} on vpnRd {} for intervpn link", prefix, dstVpnRd, e);
197 public void leakRouteIfNeeded(String vpnName, String prefix, List<String> nextHopList, int label,
198 RouteOrigin origin, int addOrRemove) {
200 Optional<InterVpnLinkDataComposite> optIVpnLink = interVpnLinkCache.getInterVpnLinkByVpnId(vpnName);
201 if (!optIVpnLink.isPresent()) {
202 LOG.debug("Vpn {} not involved in any InterVpnLink", vpnName);
205 InterVpnLinkDataComposite ivpnLink = optIVpnLink.get();
206 if (addOrRemove == NwConstants.ADD_FLOW && !ivpnLink.isActive()) {
207 // Note: for the removal case it is not necessary that ivpnlink is ACTIVE
208 LOG.debug("Route to {} in VPN {} cannot be leaked because InterVpnLink {} is not ACTIVE",
209 prefix, vpnName, ivpnLink.getInterVpnLinkName());
215 if (!ivpnLink.isBgpRoutesLeaking()) {
216 LOG.debug("BGP route to {} not leaked because bgp-routes-leaking is disabled", prefix);
219 leakRoute(vpnName, prefix, nextHopList, label, addOrRemove);
222 /* NOTE: There are 2 types of static routes depending on the next hop:
223 + static route when next hop is a VM, the DC-GW or a DPNIP
224 + static route when next hop is an Inter-VPN Link
225 Only the 1st type should be considered since the 2nd has a special treatment */
226 if (!ivpnLink.isStaticRoutesLeaking()) {
227 LOG.debug("Static route to {} not leaked because static-routes-leaking is disabled", prefix);
230 leakRoute(vpnName, prefix, nextHopList, label, addOrRemove);
233 if (!ivpnLink.isConnectedRoutesLeaking()) {
234 LOG.debug("Connected route to {} not leaked because connected-routes-leaking is disabled", prefix);
237 leakRoute(vpnName, prefix, nextHopList, label, addOrRemove);
240 LOG.warn("origin {} not considered in Route-leaking", origin.getValue());
246 public void exchangeRoutes(InterVpnLinkDataComposite ivpnLink) {
247 if (!ivpnLink.isComplete()) {
251 // The type of routes to exchange depend on the leaking flags that have been activated
252 List<RouteOrigin> originsToConsider = new ArrayList<>();
253 if (ivpnLink.isBgpRoutesLeaking()) {
254 originsToConsider.add(RouteOrigin.BGP);
256 if (ivpnLink.isStaticRoutesLeaking()) {
257 originsToConsider.add(RouteOrigin.STATIC);
259 if (ivpnLink.isConnectedRoutesLeaking()) {
260 originsToConsider.add(RouteOrigin.CONNECTED);
263 String vpn1Uuid = ivpnLink.getFirstEndpointVpnUuid().get();
264 String vpn2Uuid = ivpnLink.getSecondEndpointVpnUuid().get();
266 if (! originsToConsider.isEmpty()) {
267 // 1st Endpoint ==> 2nd endpoint
268 leakRoutes(ivpnLink, vpn1Uuid, vpn2Uuid, originsToConsider, NwConstants.ADD_FLOW);
271 // 2nd Endpoint ==> 1st endpoint
272 leakRoutes(ivpnLink, vpn2Uuid, vpn1Uuid, originsToConsider, NwConstants.ADD_FLOW);
275 // Static routes in Vpn1 pointing to Vpn2's endpoint
276 leakExtraRoutesToVpnEndpoint(ivpnLink, vpn1Uuid, vpn2Uuid);
278 // Static routes in Vpn2 pointing to Vpn1's endpoint
279 leakExtraRoutesToVpnEndpoint(ivpnLink, vpn2Uuid, vpn1Uuid);
283 * Checks if there are static routes in Vpn1 whose nexthop is Vpn2's endpoint.
284 * Those routes must be leaked to Vpn1.
290 private void leakExtraRoutesToVpnEndpoint(InterVpnLinkDataComposite vpnLink, String vpn1Uuid, String vpn2Uuid) {
292 String vpn1Rd = VpnUtil.getVpnRd(dataBroker, vpn1Uuid);
293 String vpn2Endpoint = vpnLink.getOtherEndpointIpAddr(vpn2Uuid);
294 List<VrfEntry> allVpnVrfEntries = VpnUtil.getAllVrfEntries(dataBroker, vpn1Rd);
295 for (VrfEntry vrfEntry : allVpnVrfEntries) {
296 vrfEntry.getRoutePaths().stream()
297 .filter(routePath -> routePath.getNexthopAddress().equals(vpn2Endpoint))
298 .forEach(routePath -> {
299 // Vpn1 has a route pointing to Vpn2's endpoint. Forcing the leaking of the route will update
300 // the BGP accordingly
301 long label = VpnUtil.getUniqueId(idManager, VpnConstants.VPN_IDPOOL_NAME,
302 VpnUtil.getNextHopLabelKey(vpn1Rd, vrfEntry.getDestPrefix()));
303 if (label == VpnConstants.INVALID_LABEL) {
304 LOG.error("Unable to fetch label from Id Manager. Bailing out of leaking extra routes for "
305 + "InterVpnLink {} rd {} prefix {}",
306 vpnLink.getInterVpnLinkName(), vpn1Rd, vrfEntry.getDestPrefix());
308 leakRoute(vpnLink, vpn2Uuid, vpn1Uuid, vrfEntry.getDestPrefix(), label,
309 RouteOrigin.value(vrfEntry.getOrigin()), NwConstants.ADD_FLOW);
316 private void leakRoutes(InterVpnLinkDataComposite vpnLink, String srcVpnUuid, String dstVpnUuid,
317 List<RouteOrigin> originsToConsider, int addOrRemove) {
318 String srcVpnRd = VpnUtil.getVpnRd(dataBroker, srcVpnUuid);
319 String dstVpnRd = VpnUtil.getVpnRd(dataBroker, dstVpnUuid);
320 List<VrfEntry> srcVpnRemoteVrfEntries = VpnUtil.getVrfEntriesByOrigin(dataBroker, srcVpnRd, originsToConsider);
321 for (VrfEntry vrfEntry : srcVpnRemoteVrfEntries) {
322 long label = VpnUtil.getUniqueId(idManager, VpnConstants.VPN_IDPOOL_NAME,
323 VpnUtil.getNextHopLabelKey(dstVpnRd, vrfEntry.getDestPrefix()));
324 if (label == VpnConstants.INVALID_LABEL) {
325 LOG.error("Unable to fetch label from Id Manager. Bailing out of leaking routes for InterVpnLink {} "
327 vpnLink.getInterVpnLinkName(), dstVpnRd, vrfEntry.getDestPrefix());
330 leakRoute(vpnLink, srcVpnUuid, dstVpnUuid, vrfEntry.getDestPrefix(), label, null /*NotForcedOrigin*/,
335 private Map<String, String> buildRouterXL3VPNMap() {
336 InstanceIdentifier<VpnMaps> vpnMapsIdentifier = InstanceIdentifier.builder(VpnMaps.class).build();
337 Optional<VpnMaps> optVpnMaps =
338 MDSALUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, vpnMapsIdentifier);
339 if (!optVpnMaps.isPresent()) {
340 LOG.info("Could not retrieve VpnMaps object from Configurational DS");
341 return new HashMap<>();
343 Predicate<VpnMap> isExternalVpn =
344 (vpnMap) -> vpnMap.getRouterId() != null
345 && ! vpnMap.getVpnId().getValue().equalsIgnoreCase(vpnMap.getRouterId().getValue());
347 return optVpnMaps.get().getVpnMap().stream()
348 .filter(isExternalVpn)
349 .collect(Collectors.toMap(v -> v.getRouterId().getValue(),
350 v -> v.getVpnId().getValue()));
355 public void handleStaticRoutes(InterVpnLinkDataComposite ivpnLink) {
357 * Checks if there are any static routes pointing to any of both
358 * InterVpnLink's endpoints. Goes through all routers checking if they have
359 * a route whose nexthop is an InterVpnLink endpoint
362 // Map that corresponds a routerId with the L3VPN that it's been assigned to.
363 Map<String, String> routerXL3VpnMap = buildRouterXL3VPNMap();
365 // Retrieving all Routers
366 InstanceIdentifier<Routers> routersIid = InstanceIdentifier.builder(Neutron.class)
367 .child(Routers.class).build();
368 Optional<Routers> routerOpData = MDSALUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, routersIid);
369 if (!routerOpData.isPresent()) {
373 List<Router> routers = routerOpData.get().getRouter();
374 for (Router router : routers) {
375 String vpnId = routerXL3VpnMap.get(router.getUuid().getValue());
377 LOG.warn("Could not find suitable VPN for router {}", router.getUuid());
378 continue; // with next router
380 List<Routes> routerRoutes = router.getRoutes();
381 if (routerRoutes != null) {
382 for (Routes route : routerRoutes) {
383 handleStaticRoute(vpnId, route, ivpnLink);
390 * Takes care of an static route to see if flows related to interVpnLink
391 * must be installed in tables 20 and 17
393 * @param vpnId Vpn to which the route belongs
394 * @param route Route to handle. Will only be considered if its nexthop is the VPN's endpoint IpAddress
395 * at the other side of the InterVpnLink
398 @SuppressWarnings("checkstyle:IllegalCatch")
399 private void handleStaticRoute(String vpnId, Routes route, InterVpnLinkDataComposite ivpnLink) {
401 IpAddress nhIpAddr = route.getNexthop();
402 String routeNextHop = nhIpAddr.getIpv4Address() != null ? nhIpAddr.getIpv4Address().getValue()
403 : nhIpAddr.getIpv6Address().getValue();
404 String destination = String.valueOf(route.getDestination().getValue());
406 // is nexthop the other endpoint's IP
407 String otherEndpoint = ivpnLink.getOtherEndpoint(vpnId);
408 if (!routeNextHop.equals(otherEndpoint)) {
409 LOG.debug("VPN {}: Route to {} nexthop={} points to an InterVpnLink endpoint, but its not "
410 + "the other endpoint. Other endpoint is {}",
411 vpnId, destination, routeNextHop, otherEndpoint);
415 // Lets work: 1) write Fibentry, 2) advertise to BGP and 3) check if it must be leaked
416 String vpnRd = VpnUtil.getVpnRd(dataBroker, vpnId);
418 LOG.warn("Could not find Route-Distinguisher for VpnName {}", vpnId);
422 int label = VpnUtil.getUniqueId(idManager, VpnConstants.VPN_IDPOOL_NAME,
423 VpnUtil.getNextHopLabelKey(vpnId, destination));
426 InterVpnLinkUtil.handleStaticRoute(ivpnLink, vpnId, destination, routeNextHop, label,
427 dataBroker, fibManager, bgpManager);
428 } catch (Exception e) {
429 LOG.error("Exception while advertising prefix for intervpn link, {}", e);