Fix exception in addSwitch
[netvirt.git] / natservice / impl / src / main / java / org / opendaylight / netvirt / natservice / ha / WeightedCentralizedSwitchScheduler.java
1 /*
2  * Copyright (c) 2017 Red Hat, Inc. 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
9 package org.opendaylight.netvirt.natservice.ha;
10
11 import static org.opendaylight.genius.infra.Datastore.CONFIGURATION;
12 import static org.opendaylight.genius.infra.Datastore.OPERATIONAL;
13
14 import com.google.common.base.Optional;
15 import java.math.BigInteger;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ExecutionException;
24 import javax.inject.Inject;
25 import javax.inject.Singleton;
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
29 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
30 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
31 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
32 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
33 import org.opendaylight.infrautils.utils.concurrent.ListenableFutures;
34 import org.opendaylight.netvirt.natservice.api.CentralizedSwitchScheduler;
35 import org.opendaylight.netvirt.natservice.internal.NatUtil;
36 import org.opendaylight.netvirt.vpnmanager.api.IVpnFootprintService;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ExtRouters;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.NaptSwitches;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.Routers;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.routers.ExternalIps;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.napt.switches.RouterToNaptSwitch;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.napt.switches.RouterToNaptSwitchBuilder;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.napt.switches.RouterToNaptSwitchKey;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.NetworkAttributes;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.Subnetmaps;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.subnetmaps.Subnetmap;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.subnetmaps.SubnetmapKey;
50 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 @Singleton
55 public class WeightedCentralizedSwitchScheduler implements CentralizedSwitchScheduler {
56     private static final Logger LOG = LoggerFactory.getLogger(WeightedCentralizedSwitchScheduler.class);
57     private static final Integer INITIAL_SWITCH_WEIGHT = Integer.valueOf(0);
58
59     private final Map<BigInteger,Integer> switchWeightsMap = new ConcurrentHashMap<>();
60     private final Map<String,String> subnetIdToRouterPortMap = new ConcurrentHashMap<>();
61     private final Map<String,String> subnetIdToElanInstanceMap = new ConcurrentHashMap<>();
62     private final DataBroker dataBroker;
63     private final ManagedNewTransactionRunner txRunner;
64     private final OdlInterfaceRpcService interfaceManager;
65     private final IVpnFootprintService vpnFootprintService;
66
67     @Inject
68     public WeightedCentralizedSwitchScheduler(DataBroker dataBroker, OdlInterfaceRpcService interfaceManager,
69             IVpnFootprintService vpnFootprintService) {
70         this.dataBroker = dataBroker;
71         this.txRunner = new ManagedNewTransactionRunnerImpl(dataBroker);
72         this.interfaceManager = interfaceManager;
73         this.vpnFootprintService = vpnFootprintService;
74     }
75
76     @Override
77     public boolean scheduleCentralizedSwitch(Routers router) {
78         BigInteger nextSwitchId = getSwitchWithLowestWeight();
79         if (nextSwitchId == BigInteger.valueOf(0)) {
80             LOG.error("In scheduleCentralizedSwitch, unable to schedule the router {} as there is no available switch.",
81                     router.getRouterName());
82             return false;
83         }
84
85         LOG.info("scheduleCentralizedSwitch for router {} on switch {}", router.getRouterName(), nextSwitchId);
86         String routerName = router.getRouterName();
87         RouterToNaptSwitchBuilder routerToNaptSwitchBuilder =
88                 new RouterToNaptSwitchBuilder().setRouterName(routerName);
89         RouterToNaptSwitch id = routerToNaptSwitchBuilder.setPrimarySwitchId(nextSwitchId).build();
90         addToDpnMaps(routerName, router.getSubnetIds(), nextSwitchId);
91         try {
92             SingleTransactionDataBroker.syncWrite(dataBroker, LogicalDatastoreType.CONFIGURATION,
93                     getNaptSwitchesIdentifier(routerName), id);
94             switchWeightsMap.put(nextSwitchId,switchWeightsMap.get(nextSwitchId) + 1);
95
96         } catch (TransactionCommitFailedException e) {
97             LOG.error("ScheduleCentralizedSwitch failed for {}", routerName);
98         }
99         return true;
100
101     }
102
103     @Override
104     public boolean updateCentralizedSwitch(Routers oldRouter, Routers newRouter) {
105         LOG.info("updateCentralizedSwitch for router {}", newRouter.getRouterName());
106         String routerName = newRouter.getRouterName();
107         List<Uuid> addedSubnetIds = getUpdatedSubnetIds(newRouter.getSubnetIds(), oldRouter.getSubnetIds());
108         List<Uuid> deletedSubnetIds = getUpdatedSubnetIds(oldRouter.getSubnetIds(), newRouter.getSubnetIds());
109         BigInteger primarySwitchId = NatUtil.getPrimaryNaptfromRouterName(dataBroker, newRouter.getRouterName());
110         addToDpnMaps(routerName, addedSubnetIds, primarySwitchId);
111         deleteFromDpnMaps(routerName, deletedSubnetIds, primarySwitchId);
112         return true;
113     }
114
115     @Override
116     public boolean releaseCentralizedSwitch(Routers router) {
117         String routerName = router.getRouterName();
118         BigInteger primarySwitchId = NatUtil.getPrimaryNaptfromRouterName(dataBroker, routerName);
119         LOG.info("releaseCentralizedSwitch for router {} from switch {}", router.getRouterName(), primarySwitchId);
120         deleteFromDpnMaps(routerName, router.getSubnetIds(), primarySwitchId);
121         try {
122             SingleTransactionDataBroker.syncDelete(dataBroker, LogicalDatastoreType.CONFIGURATION,
123                     getNaptSwitchesIdentifier(routerName));
124             switchWeightsMap.put(primarySwitchId,switchWeightsMap.get(primarySwitchId) - 1);
125         } catch (TransactionCommitFailedException e) {
126             return false;
127         }
128         return true;
129     }
130
131     private void addToDpnMaps(String routerName, List<Uuid> addedSubnetIds, BigInteger primarySwitchId) {
132         if (addedSubnetIds == null || addedSubnetIds.isEmpty()) {
133             LOG.debug("addToDpnMaps no subnets associated with {}", routerName);
134             return;
135         }
136         Map<Uuid, Subnetmap> subnetMapEntries = new HashMap<>();
137         try {
138             String primaryRd = txRunner.applyWithNewReadWriteTransactionAndSubmit(CONFIGURATION, tx -> {
139                 for (Uuid subnetUuid : addedSubnetIds) {
140                     Subnetmap subnetMapEntry = tx.read(getSubnetMapIdentifier(subnetUuid)).get().orNull();
141                     subnetMapEntries.put(subnetUuid, subnetMapEntry);
142                     Uuid routerPortUuid = subnetMapEntry.getRouterInterfacePortId();
143                     subnetIdToRouterPortMap.put(subnetUuid.getValue(), routerPortUuid.getValue());
144                 }
145                 return NatUtil.getPrimaryRd(routerName, tx);
146             }).get();
147             ListenableFutures.addErrorLogging(txRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, tx -> {
148                 for (Uuid subnetUuid : addedSubnetIds) {
149                     Subnetmap subnetMapEntry = subnetMapEntries.get(subnetUuid);
150                     Uuid routerPortUuid = subnetMapEntry.getRouterInterfacePortId();
151                     vpnFootprintService.updateVpnToDpnMapping(primarySwitchId, routerName, primaryRd,
152                             routerPortUuid.getValue(), null, true);
153                     NatUtil.addToNeutronRouterDpnsMap(routerName, routerPortUuid.getValue(), primarySwitchId, tx);
154                     NatUtil.addToDpnRoutersMap(routerName, routerPortUuid.getValue(), primarySwitchId, tx);
155                     if (subnetMapEntry.getNetworkType().equals(NetworkAttributes.NetworkType.VLAN)) {
156                         String elanInstanceName = subnetMapEntry.getNetworkId().getValue();
157                         subnetIdToElanInstanceMap.put(subnetUuid.getValue(), elanInstanceName);
158                         NatUtil.addPseudoPortToElanDpn(elanInstanceName, elanInstanceName, primarySwitchId, dataBroker);
159                     }
160                 }
161             }), LOG, "Error adding subnets to DPN maps for {}", routerName);
162         } catch (InterruptedException | ExecutionException e) {
163             LOG.error("Error adding subnets to DPN maps for {}", routerName);
164         }
165     }
166
167
168
169     private void deleteFromDpnMaps(String routerName, List<Uuid> deletedSubnetIds, BigInteger primarySwitchId) {
170         if (deletedSubnetIds == null || deletedSubnetIds.isEmpty()) {
171             LOG.debug("deleteFromDpnMaps no subnets associated with {}", routerName);
172             return;
173         }
174         try {
175             String primaryRd = txRunner.applyWithNewReadWriteTransactionAndSubmit(CONFIGURATION,
176                 tx -> NatUtil.getPrimaryRd(routerName, tx)).get();
177             ListenableFutures.addErrorLogging(txRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, tx -> {
178                 for (Uuid subnetUuid : deletedSubnetIds) {
179                     String routerPort = subnetIdToRouterPortMap.remove(subnetUuid.getValue());
180                     if (routerPort == null) {
181                         LOG.error("The router port was not found for {}", subnetUuid.getValue());
182                         continue;
183                     }
184                     vpnFootprintService.updateVpnToDpnMapping(primarySwitchId, routerName, primaryRd,
185                             routerPort, null, false);
186                     NatUtil.removeFromNeutronRouterDpnsMap(routerName, primarySwitchId, tx);
187                     NatUtil.removeFromDpnRoutersMap(dataBroker, routerName, routerName, interfaceManager, tx);
188                     if (subnetIdToElanInstanceMap.containsKey(subnetUuid.getValue())) {
189                         String elanInstanceName = subnetIdToElanInstanceMap.remove(subnetUuid.getValue());
190                         NatUtil.removePseudoPortFromElanDpn(elanInstanceName, elanInstanceName, primarySwitchId,
191                                 dataBroker);
192                     }
193                 }
194             }), LOG, "Error deleting subnets from DPN maps for {}", routerName);
195         } catch (InterruptedException | ExecutionException e) {
196             LOG.error("Error deleting subnets from DPN maps for {}", routerName, e);
197         }
198     }
199
200     @Override
201     public boolean addSwitch(BigInteger dpnId) {
202         /* Initialize the switch in the map with weight 0 */
203         LOG.info("addSwitch: Adding {} dpnId to switchWeightsMap", dpnId);
204         boolean scheduleRouters = (switchWeightsMap.size() == 0) ? true : false;
205         switchWeightsMap.put(dpnId, INITIAL_SWITCH_WEIGHT);
206
207         if (scheduleRouters) {
208             Optional<ExtRouters> optRouters;
209             try {
210                 optRouters = SingleTransactionDataBroker.syncReadOptional(dataBroker,
211                         LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(ExtRouters.class));
212             } catch (ReadFailedException e) {
213                 LOG.error("addSwitch: Error reading external routers", e);
214                 return false;
215             }
216
217             if (optRouters.isPresent()) {
218                 // Get the list of routers and verify if any routers do not have primarySwitch allocated.
219                 for (Routers router : optRouters.get().getRouters()) {
220                     List<ExternalIps> externalIps = router.getExternalIps();
221                     if (router.isEnableSnat() && externalIps != null && !externalIps.isEmpty()) {
222                         // Check if the primarySwitch is allocated for the router.
223                         if (!isPrimarySwitchAllocatedForRouter(router.getRouterName())) {
224                             scheduleCentralizedSwitch(router);
225                         }
226                     }
227                 }
228             }
229         }
230         return true;
231     }
232
233     private boolean isPrimarySwitchAllocatedForRouter(String routerName) {
234         InstanceIdentifier<RouterToNaptSwitch> routerToNaptSwitch =
235                 NatUtil.buildNaptSwitchRouterIdentifier(routerName);
236         try {
237             RouterToNaptSwitch rtrToNapt = SingleTransactionDataBroker.syncRead(dataBroker,
238                     LogicalDatastoreType.CONFIGURATION, routerToNaptSwitch);
239             BigInteger dpnId = rtrToNapt.getPrimarySwitchId();
240             if (dpnId == null || dpnId.equals(BigInteger.ZERO)) {
241                 return false;
242             }
243         } catch (ReadFailedException e) {
244             LOG.error("isPrimarySwitchAllocatedForRouter: Error reading RouterToNaptSwitch model", e);
245             return false;
246         }
247         return true;
248     }
249
250     @Override
251     public boolean removeSwitch(BigInteger dpnId) {
252         LOG.info("removeSwitch: Removing {} dpnId to switchWeightsMap", dpnId);
253         if (!INITIAL_SWITCH_WEIGHT.equals(switchWeightsMap.get(dpnId))) {
254             NaptSwitches naptSwitches = getNaptSwitches(dataBroker);
255             for (RouterToNaptSwitch routerToNaptSwitch : naptSwitches.getRouterToNaptSwitch()) {
256                 if (dpnId.equals(routerToNaptSwitch.getPrimarySwitchId())) {
257                     Routers router = NatUtil.getRoutersFromConfigDS(dataBroker, routerToNaptSwitch.getRouterName());
258                     releaseCentralizedSwitch(router);
259                     switchWeightsMap.remove(dpnId);
260                     scheduleCentralizedSwitch(router);
261                     break;
262                 }
263             }
264         } else {
265             switchWeightsMap.remove(dpnId);
266         }
267         return true;
268     }
269
270     public static NaptSwitches getNaptSwitches(DataBroker dataBroker) {
271         InstanceIdentifier<NaptSwitches> id = InstanceIdentifier.builder(NaptSwitches.class).build();
272         return SingleTransactionDataBroker.syncReadOptionalAndTreatReadFailedExceptionAsAbsentOptional(dataBroker,
273                 LogicalDatastoreType.CONFIGURATION, id).orNull();
274     }
275
276     private BigInteger getSwitchWithLowestWeight() {
277         int lowestWeight = Integer.MAX_VALUE;
278         BigInteger nextSwitchId = BigInteger.valueOf(0);
279         for (Entry<BigInteger, Integer> entry : switchWeightsMap.entrySet()) {
280             BigInteger dpnId = entry.getKey();
281             Integer weight = entry.getValue();
282             if (lowestWeight > weight) {
283                 lowestWeight = weight;
284                 nextSwitchId =  dpnId;
285             }
286         }
287         LOG.info("getSwitchWithLowestWeight: switchWeightsMap {}, returning nextSwitchId {} ",
288                 switchWeightsMap, nextSwitchId);
289         return nextSwitchId;
290     }
291
292     private InstanceIdentifier<RouterToNaptSwitch> getNaptSwitchesIdentifier(String routerName) {
293         return InstanceIdentifier.builder(NaptSwitches.class)
294             .child(RouterToNaptSwitch.class, new RouterToNaptSwitchKey(routerName)).build();
295     }
296
297     private InstanceIdentifier<Subnetmap> getSubnetMapIdentifier(Uuid subnetId) {
298         return InstanceIdentifier.builder(Subnetmaps.class).child(Subnetmap.class,
299                 new SubnetmapKey(subnetId)).build();
300     }
301
302     @Override
303     public boolean getCentralizedSwitch(String routerName) {
304         // TODO Auto-generated method stub
305         return false;
306     }
307
308     public static List<Uuid> getUpdatedSubnetIds(
309             List<Uuid> updatedSubnetIds,
310             List<Uuid> currentSubnetIds) {
311         if (updatedSubnetIds == null) {
312             return null;
313         }
314         List<Uuid> newSubnetIds = new ArrayList<>(updatedSubnetIds);
315         if (currentSubnetIds == null) {
316             return newSubnetIds;
317         }
318         List<Uuid> origSubnetIds = new ArrayList<>(currentSubnetIds);
319         for (Iterator<Uuid> iterator = newSubnetIds.iterator(); iterator.hasNext();) {
320             Uuid updatedSubnetId = iterator.next();
321             for (Uuid currentSubnetId : origSubnetIds) {
322                 if (updatedSubnetId.getValue().equals(currentSubnetId.getValue())) {
323                     iterator.remove();
324                     break;
325                 }
326             }
327         }
328         return newSubnetIds;
329     }
330 }