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