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