Freeze upstream versions
[netvirt.git] / neutronvpn / impl / src / main / java / org / opendaylight / netvirt / neutronvpn / NeutronBgpvpnChangeListener.java
1 /*
2  * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. 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.neutronvpn;
9
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;
15 import java.util.Set;
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;
40
41 @Singleton
42 public class NeutronBgpvpnChangeListener extends AbstractAsyncDataTreeChangeListener<Bgpvpn> {
43
44     private static final Logger LOG = LoggerFactory.getLogger(NeutronBgpvpnChangeListener.class);
45
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;
51
52     @Inject
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);
65         init();
66     }
67
68     public void init() {
69         LOG.info("{} init", getClass().getSimpleName());
70     }
71
72     @Override
73     @PreDestroy
74     public void close() {
75         super.close();
76         Executors.shutdownAndAwaitTermination(getExecutorService());
77     }
78
79     private boolean isBgpvpnTypeL3(Class<? extends BgpvpnTypeBase> bgpvpnType) {
80         if (BgpvpnTypeL3.class.equals(bgpvpnType)) {
81             return true;
82         } else {
83             LOG.warn("CRUD operations supported only for L3 type Bgpvpn");
84             return false;
85         }
86     }
87
88     @Override
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);
93
94         String vpnName = input.getUuid().getValue();
95         if (!isBgpvpnTypeL3(input.getType())) {
96             LOG.warn("BGPVPN type for VPN {} is not L3", vpnName);
97             return;
98         }
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);
103                 return;
104             }
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<>();
111
112             if (inputRouteList != null && !inputRouteList.isEmpty()) {
113                 inputImportRouteSet.addAll(inputRouteList);
114                 inputExportRouteSet.addAll(inputRouteList);
115             }
116             if (inputImportRouteList != null && !inputImportRouteList.isEmpty()) {
117                 inputImportRouteSet.addAll(inputImportRouteList);
118             }
119             if (inputExportRouteList != null && !inputExportRouteList.isEmpty()) {
120                 inputExportRouteSet.addAll(inputExportRouteList);
121             }
122             List<String> importRouteTargets = new ArrayList<>(inputImportRouteSet);
123             List<String> exportRouteTargets = new ArrayList<>(inputExportRouteSet);
124             boolean rdIrtErtStringsValid;
125
126             List<String> rdList = input.getRouteDistinguishers();
127
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());
139                     return;
140                 }
141                 String primaryRd = neutronvpnUtils.getVpnRd(vpnName);
142                 if (primaryRd == null) {
143                     primaryRd = rdList.get(0);
144                 }
145
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);
150                     return;
151                 }
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);
156                     return;
157                 }
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);
162                     return;
163                 }
164                 List<Uuid> unpRtrs = neutronBgpvpnUtils.getUnprocessedRoutersForBgpvpn(input.getUuid());
165                 List<Uuid> unpNets = neutronBgpvpnUtils.getUnprocessedNetworksForBgpvpn(input.getUuid());
166
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);
173                     } else {
174                         unpRtrs = new ArrayList<>(inputRouters);
175                     }
176                 }
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);
180                     return;
181                 }
182                 List<Uuid> inputNetworks = input.getNetworks();
183                 if (inputNetworks != null && !inputNetworks.isEmpty()) {
184                     if (unpNets != null) {
185                         unpNets.addAll(inputNetworks);
186                     } else {
187                         unpNets = new ArrayList<>(inputNetworks);
188                     }
189                 }
190                 try {
191                     nvpnManager.createVpn(input.getUuid(), input.getName(), input.getTenantId(), rdList,
192                             importRouteTargets, exportRouteTargets, unpRtrs, unpNets, false /* isL2Vpn */,
193                             0 /* l3vni */);
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);
198                 }
199             } else {
200                 LOG.error("add: RD is absent for BGPVPN {}", vpnName);
201             }
202         }
203     }
204
205     @Override
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);
212             return;
213         }
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);
218                 return;
219             }
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);
226                 return;
227             }
228             nvpnManager.removeVpn(input.getUuid());
229         }
230     }
231
232     @Override
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)) {
236             return;
237         }
238         String vpnName = update.getUuid().getValue();
239         if (!isBgpvpnTypeL3(update.getType())) {
240             LOG.warn("BGPVPN type for VPN {} is not L3", vpnName);
241             return;
242         }
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());
253             return;
254         }
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);
259                 return;
260             }
261             handleVpnInstanceUpdate(original.getUuid().getValue(), original.getRouteDistinguishers(),
262                     update.getRouteDistinguishers());
263
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);
270
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);
276         }
277     }
278
279     protected void handleVpnInstanceUpdate(String vpnInstanceName, final List<String> originalRds,
280             List<String> updateRDs) throws UnsupportedOperationException {
281         if (updateRDs == null || updateRDs.isEmpty()) {
282             return;
283         }
284         int oldRdsCount = originalRds.size();
285
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");
291             }
292         }
293         if (updateRDs.size() == oldRdsCount) {
294             LOG.debug("There is no update in the List of Route Distinguisher for the VpnInstance:{}", vpnInstanceName);
295             return;
296         }
297         LOG.debug("update the VpnInstance:{} with the List of RDs: {}", vpnInstanceName, updateRDs);
298         nvpnManager.updateVpnInstanceWithRDs(vpnInstanceName, updateRDs);
299     }
300
301     /**
302      * Handle networks update.
303      *
304      * @deprecated Retaining method for backward compatibility. Below method needs to be removed once
305      *             updated to latest BGPVPN API's.
306      *
307      * @param vpnId the vpn id
308      * @param oldNetworks the old networks
309      * @param newNetworks the new networks
310      */
311     @Deprecated
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);
321                             iter.remove();
322                         }
323                     }
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);
331                         }
332                     }
333
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);
341                         }
342                     }
343                 }
344             } else {
345                 //add new Networks
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);
351                 }
352             }
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);
359             }
360         }
361     }
362
363     /**
364      * Handle routers update.
365      *
366      * @deprecated Retaining method for backward compatibility. Below method needs to be removed once
367      *             updated to latest BGPVPN API's.
368      *
369      * @param vpnId the vpn id
370      * @param oldRouters the old routers
371      * @param newRouters the new routers
372      */
373     @Deprecated
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));
384             }
385             /* dissociate old router */
386             oldRoutersCopy.forEach(r -> {
387                 nvpnManager.dissociateRouterFromVpn(vpnId, r);
388             });
389         }
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());
395                 return;
396             } else {
397                 for (Uuid routerId : newRouters) {
398                     if (oldRouters != null && oldRouters.contains(routerId)) {
399                         continue;
400                     }
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.
404                      */
405                     if (oldRouters == null || oldRouters.isEmpty()) {
406                         nvpnManager.associateRouterToVpn(vpnId, routerId);
407                     } else if (validateRouteInfo(routerId)) {
408                         nvpnManager.associateRouterToVpn(vpnId, routerId);
409                     }
410                 }
411             }
412         }
413     }
414
415     private boolean validateRouteInfo(Uuid routerID) {
416         Uuid assocVPNId;
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());
420             return false;
421         }
422         return true;
423     }
424 }