2 * Copyright (c) 2016 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.neutronvpn;
10 import java.util.ArrayList;
11 import java.util.HashSet;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Objects;
16 import java.util.concurrent.TimeUnit;
17 import javax.annotation.PreDestroy;
18 import javax.inject.Inject;
19 import javax.inject.Singleton;
20 import org.opendaylight.infrautils.utils.concurrent.Executors;
21 import org.opendaylight.infrautils.utils.concurrent.NamedLocks;
22 import org.opendaylight.infrautils.utils.concurrent.NamedSimpleReentrantLock.AcquireResult;
23 import org.opendaylight.mdsal.binding.api.DataBroker;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.netvirt.neutronvpn.api.utils.NeutronConstants;
26 import org.opendaylight.serviceutils.tools.listener.AbstractAsyncDataTreeChangeListener;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.vpnmaps.VpnMap;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.BgpvpnTypeBase;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.BgpvpnTypeL3;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.bgpvpns.attributes.Bgpvpns;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.bgpvpns.attributes.bgpvpns.Bgpvpn;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
35 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
36 import org.osgi.framework.BundleContext;
37 import org.osgi.framework.FrameworkUtil;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 public class NeutronBgpvpnChangeListener extends AbstractAsyncDataTreeChangeListener<Bgpvpn> {
44 private static final Logger LOG = LoggerFactory.getLogger(NeutronBgpvpnChangeListener.class);
46 private final NeutronvpnManager nvpnManager;
47 private final IdManagerService idManager;
48 private final NeutronvpnUtils neutronvpnUtils;
49 private final NeutronBgpvpnUtils neutronBgpvpnUtils;
50 private final String adminRDValue;
53 public NeutronBgpvpnChangeListener(final DataBroker dataBroker, final NeutronvpnManager neutronvpnManager,
54 final IdManagerService idManager, final NeutronvpnUtils neutronvpnUtils,
55 final NeutronBgpvpnUtils neutronBgpvpnUtils) {
56 super(dataBroker, LogicalDatastoreType.CONFIGURATION,
57 InstanceIdentifier.create(Neutron.class).child(Bgpvpns.class).child(Bgpvpn.class),
58 Executors.newSingleThreadExecutor("NeutronBgpvpnChangeListener", LOG));
59 this.nvpnManager = neutronvpnManager;
60 this.idManager = idManager;
61 this.neutronvpnUtils = neutronvpnUtils;
62 this.neutronBgpvpnUtils = neutronBgpvpnUtils;
63 BundleContext bundleContext = FrameworkUtil.getBundle(NeutronBgpvpnChangeListener.class).getBundleContext();
64 adminRDValue = bundleContext.getProperty(NeutronConstants.RD_PROPERTY_KEY);
69 LOG.info("{} init", getClass().getSimpleName());
76 Executors.shutdownAndAwaitTermination(getExecutorService());
79 private boolean isBgpvpnTypeL3(Class<? extends BgpvpnTypeBase> bgpvpnType) {
80 if (BgpvpnTypeL3.class.equals(bgpvpnType)) {
83 LOG.warn("CRUD operations supported only for L3 type Bgpvpn");
89 // TODO Clean up the exception handling
90 @SuppressWarnings("checkstyle:IllegalCatch")
91 public void add(InstanceIdentifier<Bgpvpn> identifier, Bgpvpn input) {
92 LOG.trace("Adding Bgpvpn : key: {}, value={}", identifier, input);
94 String vpnName = input.getUuid().getValue();
95 if (!isBgpvpnTypeL3(input.getType())) {
96 LOG.warn("BGPVPN type for VPN {} is not L3", vpnName);
99 NamedLocks<String> vpnLock = neutronBgpvpnUtils.getVpnLock();
100 try (AcquireResult lock = vpnLock.tryAcquire(vpnName, NeutronConstants.LOCK_WAIT_TIME, TimeUnit.SECONDS)) {
101 if (!lock.wasAcquired()) {
102 LOG.error("Add BGPVPN: add bgpvpn failed for vpn : {} due to failure in acquiring lock", vpnName);
105 // handle route-target(s)
106 List<String> inputRouteList = input.getRouteTargets();
107 List<String> inputImportRouteList = input.getImportTargets();
108 List<String> inputExportRouteList = input.getExportTargets();
109 Set<String> inputImportRouteSet = new HashSet<>();
110 Set<String> inputExportRouteSet = new HashSet<>();
112 if (inputRouteList != null && !inputRouteList.isEmpty()) {
113 inputImportRouteSet.addAll(inputRouteList);
114 inputExportRouteSet.addAll(inputRouteList);
116 if (inputImportRouteList != null && !inputImportRouteList.isEmpty()) {
117 inputImportRouteSet.addAll(inputImportRouteList);
119 if (inputExportRouteList != null && !inputExportRouteList.isEmpty()) {
120 inputExportRouteSet.addAll(inputExportRouteList);
122 List<String> importRouteTargets = new ArrayList<>(inputImportRouteSet);
123 List<String> exportRouteTargets = new ArrayList<>(inputExportRouteSet);
124 boolean rdIrtErtStringsValid;
126 List<String> rdList = input.getRouteDistinguishers();
128 if (rdList != null && !rdList.isEmpty()) {
129 // get the primary RD for vpn instance, if exist
130 rdIrtErtStringsValid =
131 !(input.getRouteDistinguishers().stream().anyMatch(rdStr -> rdStr.contains(" ")));
132 rdIrtErtStringsValid =
133 rdIrtErtStringsValid && !(importRouteTargets.stream().anyMatch(irtStr -> irtStr.contains(" ")));
134 rdIrtErtStringsValid =
135 rdIrtErtStringsValid && !(exportRouteTargets.stream().anyMatch(ertStr -> ertStr.contains(" ")));
136 if (!rdIrtErtStringsValid) {
137 LOG.error("Error encountered for BGPVPN {} with RD {} as RD/iRT/eRT contains whitespace "
138 + "characters", vpnName, input.getRouteDistinguishers());
141 String primaryRd = neutronvpnUtils.getVpnRd(vpnName);
142 if (primaryRd == null) {
143 primaryRd = rdList.get(0);
146 String[] rdParams = primaryRd.split(":");
147 if (rdParams[0].trim().equals(adminRDValue)) {
148 LOG.error("AS specific part of RD should not be same as that defined by DC Admin. Error "
149 + "encountered for BGPVPN {} with RD {}", vpnName, primaryRd);
152 String vpnWithSameRd = neutronvpnUtils.getVpnForRD(primaryRd);
153 if (vpnWithSameRd != null) {
154 LOG.error("Creation of L3VPN failed for VPN {} as another VPN {} with the same RD {} "
155 + "is already configured", vpnName, vpnWithSameRd, primaryRd);
158 String existingOperationalVpn = neutronvpnUtils.getExistingOperationalVpn(primaryRd);
159 if (existingOperationalVpn != null) {
160 LOG.error("checkVpnCreation: Creation of L3VPN failed for VPN {} as another VPN {} with the "
161 + "same RD {} is still available.", vpnName, existingOperationalVpn, primaryRd);
164 List<Uuid> unpRtrs = neutronBgpvpnUtils.getUnprocessedRoutersForBgpvpn(input.getUuid());
165 List<Uuid> unpNets = neutronBgpvpnUtils.getUnprocessedNetworksForBgpvpn(input.getUuid());
167 // TODO: Currently handling routers and networks for backward compatibility. Below logic needs to be
168 // removed once updated to latest BGPVPN API's.
169 List<Uuid> inputRouters = input.getRouters();
170 if (inputRouters != null && !inputRouters.isEmpty()) {
171 if (unpRtrs != null) {
172 unpRtrs.addAll(inputRouters);
174 unpRtrs = new ArrayList<>(inputRouters);
177 if (unpRtrs != null && unpRtrs.size() > NeutronConstants.MAX_ROUTERS_PER_BGPVPN) {
178 LOG.error("Creation of BGPVPN for rd {} failed: maximum allowed number of associated "
179 + "routers is {}.", rdList, NeutronConstants.MAX_ROUTERS_PER_BGPVPN);
182 List<Uuid> inputNetworks = input.getNetworks();
183 if (inputNetworks != null && !inputNetworks.isEmpty()) {
184 if (unpNets != null) {
185 unpNets.addAll(inputNetworks);
187 unpNets = new ArrayList<>(inputNetworks);
191 nvpnManager.createVpn(input.getUuid(), input.getName(), input.getTenantId(), rdList,
192 importRouteTargets, exportRouteTargets, unpRtrs, unpNets, false /* isL2Vpn */,
194 neutronBgpvpnUtils.getUnProcessedRoutersMap().remove(input.getUuid());
195 neutronBgpvpnUtils.getUnProcessedNetworksMap().remove(input.getUuid());
196 } catch (Exception e) {
197 LOG.error("Creation of BGPVPN {} failed with error ", vpnName, e);
200 LOG.error("add: RD is absent for BGPVPN {}", vpnName);
206 public void remove(InstanceIdentifier<Bgpvpn> identifier, Bgpvpn input) {
207 LOG.trace("Removing Bgpvpn : key: {}, value={}", identifier, input);
208 Uuid vpnId = input.getUuid();
209 String vpnName = vpnId.getValue();
210 if (!isBgpvpnTypeL3(input.getType())) {
211 LOG.warn("BGPVPN type for VPN {} is not L3", vpnName);
214 NamedLocks<String> vpnLock = neutronBgpvpnUtils.getVpnLock();
215 try (AcquireResult lock = vpnLock.tryAcquire(vpnName, NeutronConstants.LOCK_WAIT_TIME, TimeUnit.SECONDS)) {
216 if (!lock.wasAcquired()) {
217 LOG.error("Remove BGPVPN: remove bgpvpn failed for vpn : {} due to failure in acquiring lock", vpnName);
220 neutronBgpvpnUtils.getUnProcessedRoutersMap().remove(input.getUuid());
221 neutronBgpvpnUtils.getUnProcessedNetworksMap().remove(input.getUuid());
222 VpnMap vpnMap = neutronvpnUtils.getVpnMap(vpnId);
223 if (vpnMap == null) {
224 LOG.error("Failed to handle BGPVPN Remove for VPN {} as that VPN is not configured"
225 + " yet as a VPN Instance", vpnName);
228 nvpnManager.removeVpn(input.getUuid());
233 public void update(InstanceIdentifier<Bgpvpn> identifier, Bgpvpn original, Bgpvpn update) {
234 LOG.trace("Update Bgpvpn : key: {}, value={}", identifier, update);
235 if (Objects.equals(original, update)) {
238 String vpnName = update.getUuid().getValue();
239 if (!isBgpvpnTypeL3(update.getType())) {
240 LOG.warn("BGPVPN type for VPN {} is not L3", vpnName);
243 boolean rdIrtErtStringsValid = true;
244 rdIrtErtStringsValid = rdIrtErtStringsValid
245 && !(update.getRouteDistinguishers().stream().anyMatch(rdStr -> rdStr.contains(" ")));
246 rdIrtErtStringsValid =
247 rdIrtErtStringsValid && !(update.getImportTargets().stream().anyMatch(irtStr -> irtStr.contains(" ")));
248 rdIrtErtStringsValid =
249 rdIrtErtStringsValid && !(update.getExportTargets().stream().anyMatch(ertStr -> ertStr.contains(" ")));
250 if (!rdIrtErtStringsValid) {
251 LOG.error("Error encountered for BGPVPN {} with RD {} as RD/iRT/eRT contains whitespace characters",
252 vpnName, update.getRouteDistinguishers());
255 NamedLocks<String> vpnLock = neutronBgpvpnUtils.getVpnLock();
256 try (AcquireResult lock = vpnLock.tryAcquire(vpnName, NeutronConstants.LOCK_WAIT_TIME, TimeUnit.SECONDS)) {
257 if (!lock.wasAcquired()) {
258 LOG.error("Update VPN: update failed for vpn : {} due to failure in acquiring lock", vpnName);
261 handleVpnInstanceUpdate(original.getUuid().getValue(), original.getRouteDistinguishers(),
262 update.getRouteDistinguishers());
264 // TODO: Currently handling routers and networks for backward compatibility. Below logic needs to be
265 // removed once updated to latest BGPVPN API's.
266 Uuid vpnId = update.getUuid();
267 List<Uuid> oldNetworks = new ArrayList<>(original.getNetworks());
268 List<Uuid> newNetworks = new ArrayList<>(update.getNetworks());
269 handleNetworksUpdate(vpnId, oldNetworks, newNetworks);
271 List<Uuid> oldRouters = original.getRouters();
272 List<Uuid> newRouters = update.getRouters();
273 handleRoutersUpdate(vpnId, oldRouters, newRouters);
274 } catch (UnsupportedOperationException e) {
275 LOG.error("Error while processing Update Bgpvpn.", e);
279 protected void handleVpnInstanceUpdate(String vpnInstanceName, final List<String> originalRds,
280 List<String> updateRDs) throws UnsupportedOperationException {
281 if (updateRDs == null || updateRDs.isEmpty()) {
284 int oldRdsCount = originalRds.size();
286 for (String rd : originalRds) {
287 // If the existing rd is not present in the updateRds list, not allow to process the updateRDs.
288 if (!updateRDs.contains(rd)) {
289 LOG.error("The existing RD {} not present in the updatedRDsList:{}", rd, updateRDs);
290 throw new UnsupportedOperationException("The existing RD not present in the updatedRDsList");
293 if (updateRDs.size() == oldRdsCount) {
294 LOG.debug("There is no update in the List of Route Distinguisher for the VpnInstance:{}", vpnInstanceName);
297 LOG.debug("update the VpnInstance:{} with the List of RDs: {}", vpnInstanceName, updateRDs);
298 nvpnManager.updateVpnInstanceWithRDs(vpnInstanceName, updateRDs);
302 * Handle networks update.
304 * @deprecated Retaining method for backward compatibility. Below method needs to be removed once
305 * updated to latest BGPVPN API's.
307 * @param vpnId the vpn id
308 * @param oldNetworks the old networks
309 * @param newNetworks the new networks
312 private void handleNetworksUpdate(Uuid vpnId, List<Uuid> oldNetworks, List<Uuid> newNetworks) {
313 if (newNetworks != null && !newNetworks.isEmpty()) {
314 if (oldNetworks != null && !oldNetworks.isEmpty()) {
315 if (oldNetworks != newNetworks) {
316 Iterator<Uuid> iter = newNetworks.iterator();
317 while (iter.hasNext()) {
318 Uuid net = iter.next();
319 if (oldNetworks.contains(net)) {
320 oldNetworks.remove(net);
324 //clear removed networks
325 if (!oldNetworks.isEmpty()) {
326 LOG.trace("Removing old networks {} ", oldNetworks);
327 List<String> errorMessages = nvpnManager.dissociateNetworksFromVpn(vpnId, oldNetworks);
328 if (!errorMessages.isEmpty()) {
329 LOG.error("handleNetworksUpdate: dissociate old Networks not part of bgpvpn update,"
330 + " from vpn {} failed due to {}", vpnId.getValue(), errorMessages);
334 //add new (Delta) Networks
335 if (!newNetworks.isEmpty()) {
336 LOG.trace("Adding delta New networks {} ", newNetworks);
337 List<String> errorMessages = nvpnManager.associateNetworksToVpn(vpnId, newNetworks);
338 if (!errorMessages.isEmpty()) {
339 LOG.error("handleNetworksUpdate: associate new Networks not part of original bgpvpn,"
340 + " to vpn {} failed due to {}", vpnId.getValue(), errorMessages);
346 LOG.trace("Adding New networks {} ", newNetworks);
347 List<String> errorMessages = nvpnManager.associateNetworksToVpn(vpnId, newNetworks);
348 if (!errorMessages.isEmpty()) {
349 LOG.error("handleNetworksUpdate: associate new Networks to vpn {} failed due to {}",
350 vpnId.getValue(), errorMessages);
353 } else if (oldNetworks != null && !oldNetworks.isEmpty()) {
354 LOG.trace("Removing old networks {} ", oldNetworks);
355 List<String> errorMessages = nvpnManager.dissociateNetworksFromVpn(vpnId, oldNetworks);
356 if (!errorMessages.isEmpty()) {
357 LOG.error("handleNetworksUpdate: dissociate old Networks from vpn {} failed due to {}",
358 vpnId.getValue(), errorMessages);
364 * Handle routers update.
366 * @deprecated Retaining method for backward compatibility. Below method needs to be removed once
367 * updated to latest BGPVPN API's.
369 * @param vpnId the vpn id
370 * @param oldRouters the old routers
371 * @param newRouters the new routers
374 private void handleRoutersUpdate(Uuid vpnId, List<Uuid> oldRouters, List<Uuid> newRouters) {
375 // for dualstack case we can associate with one VPN instance maximum 2 routers: one with
376 // only IPv4 ports and one with only IPv6 ports, or only one router with IPv4/IPv6 ports
377 // TODO: check router ports ethertype to follow this restriction
378 if (oldRouters != null && !oldRouters.isEmpty()) {
379 //remove to oldRouters the newRouters if existing
380 List<Uuid> oldRoutersCopy = new ArrayList<>();
381 oldRoutersCopy.addAll(oldRouters);
382 if (newRouters != null) {
383 newRouters.forEach(r -> oldRoutersCopy.remove(r));
385 /* dissociate old router */
386 oldRoutersCopy.forEach(r -> {
387 nvpnManager.dissociateRouterFromVpn(vpnId, r);
390 if (newRouters != null && !newRouters.isEmpty()) {
391 if (newRouters.size() > NeutronConstants.MAX_ROUTERS_PER_BGPVPN) {
392 LOG.debug("In handleRoutersUpdate: maximum allowed number of associated routers is 2. VPN: {} "
393 + "is already associated with router: {} and with router: {}",
394 vpnId, newRouters.get(0).getValue(), newRouters.get(1).getValue());
397 for (Uuid routerId : newRouters) {
398 if (oldRouters != null && oldRouters.contains(routerId)) {
401 /* If the first time BGP-VPN is getting associated with router, then no need
402 to validate if the router is already been associated with any other BGP-VPN.
403 This will avoid unnecessary MD-SAL data store read operations in VPN-MAPS.
405 if (oldRouters == null || oldRouters.isEmpty()) {
406 nvpnManager.associateRouterToVpn(vpnId, routerId);
407 } else if (validateRouteInfo(routerId)) {
408 nvpnManager.associateRouterToVpn(vpnId, routerId);
415 private boolean validateRouteInfo(Uuid routerID) {
417 if ((assocVPNId = neutronvpnUtils.getVpnForRouter(routerID, true)) != null) {
418 LOG.warn("VPN router association failed due to router {} already associated to another VPN {}",
419 routerID.getValue(), assocVPNId.getValue());