Ignore unnecessary update() processing in NVPN MD-SAL listeners
[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.math.BigInteger;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Objects;
18 import java.util.Set;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.Future;
21 import javax.annotation.PreDestroy;
22 import javax.inject.Inject;
23 import javax.inject.Singleton;
24 import org.opendaylight.infrautils.utils.concurrent.Executors;
25 import org.opendaylight.mdsal.binding.api.DataBroker;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.netvirt.neutronvpn.api.utils.NeutronConstants;
28 import org.opendaylight.serviceutils.tools.listener.AbstractAsyncDataTreeChangeListener;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolInput;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolInputBuilder;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolOutput;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.vpnmaps.VpnMap;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.BgpvpnTypeBase;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.BgpvpnTypeL3;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.bgpvpns.attributes.Bgpvpns;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.bgpvpns.rev150903.bgpvpns.attributes.bgpvpns.Bgpvpn;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.opendaylight.yangtools.yang.common.RpcError;
42 import org.opendaylight.yangtools.yang.common.RpcResult;
43 import org.osgi.framework.BundleContext;
44 import org.osgi.framework.FrameworkUtil;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 @Singleton
49 public class NeutronBgpvpnChangeListener extends AbstractAsyncDataTreeChangeListener<Bgpvpn> {
50     private static final Logger LOG = LoggerFactory.getLogger(NeutronBgpvpnChangeListener.class);
51     private final DataBroker dataBroker;
52     private final NeutronvpnManager nvpnManager;
53     private final IdManagerService idManager;
54     private final NeutronvpnUtils neutronvpnUtils;
55     private final String adminRDValue;
56
57     @Inject
58     public NeutronBgpvpnChangeListener(final DataBroker dataBroker, final NeutronvpnManager neutronvpnManager,
59                                        final IdManagerService idManager, final NeutronvpnUtils neutronvpnUtils) {
60         super(dataBroker, LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Neutron.class)
61                 .child(Bgpvpns.class).child(Bgpvpn.class), Executors.newSingleThreadExecutor(
62                         "NeutronBgpvpnChangeListener", LOG));
63         this.dataBroker = dataBroker;
64         nvpnManager = neutronvpnManager;
65         this.idManager = idManager;
66         this.neutronvpnUtils = neutronvpnUtils;
67         BundleContext bundleContext = FrameworkUtil.getBundle(NeutronBgpvpnChangeListener.class).getBundleContext();
68         adminRDValue = bundleContext.getProperty(NeutronConstants.RD_PROPERTY_KEY);
69         init();
70     }
71
72     public void init() {
73         LOG.info("{} init", getClass().getSimpleName());
74         createIdPool();
75     }
76
77     @Override
78     @PreDestroy
79     public void close() {
80         super.close();
81         Executors.shutdownAndAwaitTermination(getExecutorService());
82     }
83
84     private boolean isBgpvpnTypeL3(Class<? extends BgpvpnTypeBase> bgpvpnType) {
85         if (BgpvpnTypeL3.class.equals(bgpvpnType)) {
86             return true;
87         } else {
88             LOG.warn("CRUD operations supported only for L3 type Bgpvpn");
89             return false;
90         }
91     }
92
93     @Override
94     // TODO Clean up the exception handling
95     @SuppressWarnings("checkstyle:IllegalCatch")
96     public void add(InstanceIdentifier<Bgpvpn> identifier, Bgpvpn input) {
97         LOG.trace("Adding Bgpvpn : key: {}, value={}", identifier, input);
98         String vpnName = input.getUuid().getValue();
99         if (isBgpvpnTypeL3(input.getType())) {
100             // handle route-target(s)
101             List<String> inputRouteList = input.getRouteTargets();
102             List<String> inputImportRouteList = input.getImportTargets();
103             List<String> inputExportRouteList = input.getExportTargets();
104             Set<String> inputImportRouteSet = new HashSet<>();
105             Set<String> inputExportRouteSet = new HashSet<>();
106
107             if (inputRouteList != null && !inputRouteList.isEmpty()) {
108                 inputImportRouteSet.addAll(inputRouteList);
109                 inputExportRouteSet.addAll(inputRouteList);
110             }
111             if (inputImportRouteList != null && !inputImportRouteList.isEmpty()) {
112                 inputImportRouteSet.addAll(inputImportRouteList);
113             }
114             if (inputExportRouteList != null && !inputExportRouteList.isEmpty()) {
115                 inputExportRouteSet.addAll(inputExportRouteList);
116             }
117             List<String> importRouteTargets = new ArrayList<>();
118             List<String> exportRouteTargets = new ArrayList<>();
119             importRouteTargets.addAll(inputImportRouteSet);
120             exportRouteTargets.addAll(inputExportRouteSet);
121
122             List<String> rd = input.getRouteDistinguishers() != null
123                     ? input.getRouteDistinguishers() : new ArrayList<>();
124
125             if (rd == null || rd.isEmpty()) {
126                 // generate new RD
127                 // TODO - commented out for now to avoid "Dead store to rd" violation.
128                 //rd = generateNewRD(input.getUuid());
129             } else {
130                 String[] rdParams = rd.get(0).split(":");
131                 if (rdParams[0].trim().equals(adminRDValue)) {
132                     LOG.error("AS specific part of RD should not be same as that defined by DC Admin. Error "
133                             + "encountered for BGPVPN {} with RD {}", vpnName, rd.get(0));
134                     return;
135                 }
136                 List<String> existingRDs = neutronvpnUtils.getExistingRDs();
137                 if (!Collections.disjoint(existingRDs, rd)) {
138                     LOG.error("Failed to create VPN {} as another VPN with the same RD {} already exists.", vpnName,
139                             rd);
140                     return;
141                 }
142                 List<Uuid> routersList = null;
143                 if (input.getRouters() != null && !input.getRouters().isEmpty()) {
144                     // try to take all routers
145                     routersList = input.getRouters();
146                 }
147                 if (routersList != null && routersList.size() > NeutronConstants.MAX_ROUTERS_PER_BGPVPN) {
148                     LOG.error("Creation of BGPVPN for rd {} failed: maximum allowed number of associated "
149                              + "routers is {}.", rd, NeutronConstants.MAX_ROUTERS_PER_BGPVPN);
150                     return;
151                 }
152                 List<Uuid> networkList = null;
153                 if (input.getNetworks() != null && !input.getNetworks().isEmpty()) {
154                     networkList = input.getNetworks();
155                 }
156                 if (!rd.isEmpty()) {
157                     try {
158                         nvpnManager.createVpn(input.getUuid(), input.getName(), input.getTenantId(), rd,
159                                 importRouteTargets, exportRouteTargets, routersList, networkList,
160                                 false /*isL2Vpn*/, 0 /*l3vni*/);
161                     } catch (Exception e) {
162                         LOG.error("Creation of BGPVPN {} failed", vpnName, e);
163                     }
164                 } else {
165                     LOG.error("Create BgpVPN with id {} failed due to missing RD value", vpnName);
166                 }
167             }
168         } else {
169             LOG.warn("BGPVPN type for VPN {} is not L3", vpnName);
170         }
171     }
172
173     @Override
174     public void remove(InstanceIdentifier<Bgpvpn> identifier, Bgpvpn input) {
175         LOG.trace("Removing Bgpvpn : key: {}, value={}", identifier, input);
176         Uuid vpnId = input.getUuid();
177         if (isBgpvpnTypeL3(input.getType())) {
178             VpnMap vpnMap = neutronvpnUtils.getVpnMap(vpnId);
179             if (vpnMap == null) {
180                 LOG.error("Failed to handle BGPVPN Remove for VPN {} as that VPN is not configured"
181                         + " yet as a VPN Instance", vpnId.getValue());
182                 return;
183             }
184             nvpnManager.removeVpn(input.getUuid());
185             // Release RD Id in pool
186             List<String> rd = input.getRouteDistinguishers();
187             if (rd == null || rd.isEmpty()) {
188                 int releasedId = neutronvpnUtils.releaseId(NeutronConstants.RD_IDPOOL_NAME, vpnId.getValue());
189                 if (releasedId == NeutronConstants.INVALID_ID) {
190                     LOG.error("NeutronBgpvpnChangeListener remove: Unable to release ID for key {}", vpnId.getValue());
191                 }
192             }
193         } else {
194             LOG.warn("BGPVPN type for VPN {} is not L3", vpnId.getValue());
195         }
196     }
197
198     @Override
199     public void update(InstanceIdentifier<Bgpvpn> identifier, Bgpvpn original, Bgpvpn update) {
200         LOG.trace("Update Bgpvpn : key: {}, value={}", identifier, update);
201         if (Objects.equals(original, update)) {
202             return;
203         }
204         Uuid vpnId = update.getUuid();
205         if (isBgpvpnTypeL3(update.getType())) {
206             try {
207                 handleVpnInstanceUpdate(original.getUuid().getValue(), original.getRouteDistinguishers(),
208                         update.getRouteDistinguishers());
209             } catch (UnsupportedOperationException e) {
210                 LOG.error("Error while processing Update Bgpvpn.", e);
211                 return;
212             }
213             List<Uuid> oldNetworks = original.getNetworks();
214             List<Uuid> newNetworks = update.getNetworks();
215             handleNetworksUpdate(vpnId, oldNetworks, newNetworks);
216             List<Uuid> oldRouters = original.getRouters();
217             List<Uuid> newRouters = update.getRouters();
218             handleRoutersUpdate(vpnId, oldRouters, newRouters);
219         } else {
220             LOG.warn("BGPVPN type for VPN {} is not L3", vpnId.getValue());
221         }
222     }
223
224     protected void handleVpnInstanceUpdate(String vpnInstanceName,final List<String> originalRds,
225                                            List<String> updateRDs) throws UnsupportedOperationException {
226         if (updateRDs == null || updateRDs.isEmpty()) {
227             return;
228         }
229         int oldRdsCount = originalRds.size();
230
231         for (String rd : originalRds) {
232             //If the existing rd is not present in the updateRds list, not allow to process the updateRDs.
233             if (!updateRDs.contains(rd)) {
234                 LOG.error("The existing RD {} not present in the updatedRDsList:{}", rd, updateRDs);
235                 throw new UnsupportedOperationException("The existing RD not present in the updatedRDsList");
236             }
237         }
238         if (updateRDs.size() == oldRdsCount) {
239             LOG.debug("There is no update in the List of Route Distinguisher for the VpnInstance:{}", vpnInstanceName);
240             return;
241         }
242         LOG.debug("update the VpnInstance:{} with the List of RDs: {}", vpnInstanceName, updateRDs);
243         nvpnManager.updateVpnInstanceWithRDs(vpnInstanceName, updateRDs);
244     }
245
246     protected void handleNetworksUpdate(Uuid vpnId, List<Uuid> oldNetworks, List<Uuid> newNetworks) {
247         if (newNetworks != null && !newNetworks.isEmpty()) {
248             if (oldNetworks != null && !oldNetworks.isEmpty()) {
249                 if (oldNetworks != newNetworks) {
250                     Iterator<Uuid> iter = newNetworks.iterator();
251                     while (iter.hasNext()) {
252                         Uuid net = iter.next();
253                         if (oldNetworks.contains(net)) {
254                             oldNetworks.remove(net);
255                             iter.remove();
256                         }
257                     }
258                     //clear removed networks
259                     if (!oldNetworks.isEmpty()) {
260                         LOG.trace("Removing old networks {} ", oldNetworks);
261                         List<String> errorMessages = nvpnManager.dissociateNetworksFromVpn(vpnId, oldNetworks);
262                         if (!errorMessages.isEmpty()) {
263                             LOG.error("handleNetworksUpdate: dissociate old Networks not part of bgpvpn update,"
264                                     + " from vpn {} failed due to {}", vpnId.getValue(), errorMessages);
265                         }
266                     }
267
268                     //add new (Delta) Networks
269                     if (!newNetworks.isEmpty()) {
270                         LOG.trace("Adding delta New networks {} ", newNetworks);
271                         List<String> errorMessages = nvpnManager.associateNetworksToVpn(vpnId, newNetworks);
272                         if (!errorMessages.isEmpty()) {
273                             LOG.error("handleNetworksUpdate: associate new Networks not part of original bgpvpn,"
274                                     + " to vpn {} failed due to {}", vpnId.getValue(), errorMessages);
275                         }
276                     }
277                 }
278             } else {
279                 //add new Networks
280                 LOG.trace("Adding New networks {} ", newNetworks);
281                 List<String> errorMessages = nvpnManager.associateNetworksToVpn(vpnId, newNetworks);
282                 if (!errorMessages.isEmpty()) {
283                     LOG.error("handleNetworksUpdate: associate new Networks to vpn {} failed due to {}",
284                             vpnId.getValue(), errorMessages);
285                 }
286             }
287         } else if (oldNetworks != null && !oldNetworks.isEmpty()) {
288             LOG.trace("Removing old networks {} ", oldNetworks);
289             List<String> errorMessages = nvpnManager.dissociateNetworksFromVpn(vpnId, oldNetworks);
290             if (!errorMessages.isEmpty()) {
291                 LOG.error("handleNetworksUpdate: dissociate old Networks from vpn {} failed due to {}",
292                         vpnId.getValue(), errorMessages);
293             }
294         }
295     }
296
297     protected void handleRoutersUpdate(Uuid vpnId, List<Uuid> oldRouters, List<Uuid> newRouters) {
298         // for dualstack case we can associate with one VPN instance maximum 2 routers: one with
299         // only IPv4 ports and one with only IPv6 ports, or only one router with IPv4/IPv6 ports
300         // TODO: check router ports ethertype to follow this restriction
301         if (oldRouters != null && !oldRouters.isEmpty()) {
302             //remove to oldRouters the newRouters if existing
303             List<Uuid> oldRoutersCopy = new ArrayList<>();
304             oldRoutersCopy.addAll(oldRouters);
305             if (newRouters != null) {
306                 newRouters.forEach(r -> oldRoutersCopy.remove(r));
307             }
308             /* dissociate old router */
309             oldRoutersCopy.forEach(r -> {
310                 nvpnManager.dissociateRouterFromVpn(vpnId, r);
311             });
312         }
313         if (newRouters != null && !newRouters.isEmpty()) {
314             if (newRouters.size() > NeutronConstants.MAX_ROUTERS_PER_BGPVPN) {
315                 LOG.debug("In handleRoutersUpdate: maximum allowed number of associated routers is 2. VPN: {} "
316                         + "is already associated with router: {} and with router: {}",
317                         vpnId, newRouters.get(0).getValue(), newRouters.get(1).getValue());
318                 return;
319             } else {
320                 for (Uuid routerId : newRouters) {
321                     if (oldRouters != null && oldRouters.contains(routerId)) {
322                         continue;
323                     }
324                     /* If the first time BGP-VPN is getting associated with router, then no need
325                        to validate if the router is already been associated with any other BGP-VPN.
326                        This will avoid unnecessary MD-SAL data store read operations in VPN-MAPS.
327                      */
328                     if (oldRouters == null || oldRouters.isEmpty()) {
329                         nvpnManager.associateRouterToVpn(vpnId, routerId);
330                     } else if (validateRouteInfo(routerId)) {
331                         nvpnManager.associateRouterToVpn(vpnId, routerId);
332                     }
333                 }
334             }
335         }
336     }
337
338     private void createIdPool() {
339         CreateIdPoolInput createPool = new CreateIdPoolInputBuilder().setPoolName(NeutronConstants.RD_IDPOOL_NAME)
340                 .setLow(NeutronConstants.RD_IDPOOL_START)
341                 .setHigh(new BigInteger(NeutronConstants.RD_IDPOOL_SIZE).longValue()).build();
342         try {
343             Future<RpcResult<CreateIdPoolOutput>> result = idManager.createIdPool(createPool);
344             Collection<RpcError> rpcErrors = null;
345             if (result != null && result.get() != null) {
346                 RpcResult<CreateIdPoolOutput> rpcResult = result.get();
347                 LOG.info("Created IdPool for Bgpvpn RD");
348                 if (rpcResult.isSuccessful()) {
349                     LOG.info("Created IdPool for Bgpvpn RD");
350                     return;
351                 }
352                 rpcErrors = rpcResult.getErrors();
353                 LOG.error("Failed to create ID pool for BGPVPN RD, result future returned {}", result);
354             }
355             LOG.error("createIdPool: Failed to create ID pool for BGPVPN RD, the call returned with RPC errors {}",
356                     rpcErrors != null ? rpcErrors : "RpcResult is null");
357         } catch (InterruptedException | ExecutionException e) {
358             LOG.error("Failed to create idPool for Bgpvpn RD", e);
359         }
360     }
361
362     private boolean validateRouteInfo(Uuid routerID) {
363         Uuid assocVPNId;
364         if ((assocVPNId = neutronvpnUtils.getVpnForRouter(routerID, true)) != null) {
365             LOG.warn("VPN router association failed due to router {} already associated to another VPN {}",
366                     routerID.getValue(), assocVPNId.getValue());
367             return false;
368         }
369         return true;
370     }
371
372 }