Freeze upstream versions
[netvirt.git] / neutronvpn / impl / src / main / java / org / opendaylight / netvirt / neutronvpn / NeutronRouterChangeListener.java
1 /*
2  * Copyright © 2016, 2017 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.Collections;
12 import java.util.HashMap;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Objects;
16 import java.util.Optional;
17 import javax.annotation.PreDestroy;
18 import javax.inject.Inject;
19 import javax.inject.Singleton;
20 import org.opendaylight.genius.mdsalutil.NwConstants;
21 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
22 import org.opendaylight.infrautils.utils.concurrent.Executors;
23 import org.opendaylight.mdsal.binding.api.DataBroker;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.serviceutils.tools.listener.AbstractAsyncDataTreeChangeListener;
26 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.l3.attributes.Routes;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.Routers;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.Router;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.router.external_gateway_info.ExternalFixedIps;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netvirt.inter.vpn.link.rev160311.inter.vpn.link.states.InterVpnLinkState;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netvirt.inter.vpn.link.rev160311.inter.vpn.links.InterVpnLink;
34 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 @Singleton
39 public class NeutronRouterChangeListener extends AbstractAsyncDataTreeChangeListener<Router> {
40     private static final Logger LOG = LoggerFactory.getLogger(NeutronRouterChangeListener.class);
41     private final DataBroker dataBroker;
42     private final NeutronvpnManager nvpnManager;
43     private final NeutronvpnNatManager nvpnNatManager;
44     private final NeutronSubnetGwMacResolver gwMacResolver;
45     private final NeutronvpnUtils neutronvpnUtils;
46     private final JobCoordinator jobCoordinator;
47
48     @Inject
49     public NeutronRouterChangeListener(final DataBroker dataBroker, final NeutronvpnManager neutronvpnManager,
50                                        final NeutronvpnNatManager neutronvpnNatManager,
51                                        final NeutronSubnetGwMacResolver gwMacResolver,
52                                        final NeutronvpnUtils neutronvpnUtils,
53                                        final JobCoordinator jobCoordinator) {
54         super(dataBroker, LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Neutron.class)
55                 .child(Routers.class).child(Router.class),
56                 Executors.newSingleThreadExecutor("NeutronRouterChangeListener", LOG));
57         this.dataBroker = dataBroker;
58         nvpnManager = neutronvpnManager;
59         nvpnNatManager = neutronvpnNatManager;
60         this.gwMacResolver = gwMacResolver;
61         this.neutronvpnUtils = neutronvpnUtils;
62         this.jobCoordinator = jobCoordinator;
63     }
64
65     public void init() {
66         LOG.info("{} init", getClass().getSimpleName());
67     }
68
69     @Override
70     @PreDestroy
71     public void close() {
72         super.close();
73         Executors.shutdownAndAwaitTermination(getExecutorService());
74     }
75
76     @Override
77     public void add(InstanceIdentifier<Router> identifier, Router input) {
78         LOG.trace("Adding Router : key: {}, value={}", identifier, input);
79         neutronvpnUtils.addToRouterCache(input);
80         // Create internal VPN
81         nvpnManager.createL3InternalVpn(input.getUuid(), null, null, null, null, null, input.getUuid(), null);
82         jobCoordinator.enqueueJob(input.getUuid().toString(), () -> {
83             nvpnNatManager.handleExternalNetworkForRouter(null, input);
84             return Collections.emptyList();
85         });
86         gwMacResolver.sendArpRequestsToExtGateways(input);
87     }
88
89     @Override
90     public void remove(InstanceIdentifier<Router> identifier, Router input) {
91         LOG.trace("Removing router : key: {}, value={}", identifier, input);
92         Uuid routerId = input.getUuid();
93         // Handle router deletion for the NAT service
94         /*External Router and networks is handled before deleting the internal VPN, as there is dependency
95         on vpn operational data to release Lport tag in case of L3VPN over VxLAN*/
96         if (input.getExternalGatewayInfo() != null) {
97             Uuid extNetId = input.getExternalGatewayInfo().getExternalNetworkId();
98             List<ExternalFixedIps> externalFixedIps = new ArrayList<ExternalFixedIps>(input
99                     .getExternalGatewayInfo().nonnullExternalFixedIps().values());
100             jobCoordinator.enqueueJob(input.getUuid().toString(), () -> {
101                 nvpnNatManager.removeExternalNetworkFromRouter(extNetId, input, externalFixedIps);
102                 return Collections.emptyList();
103             });
104         }
105         //NOTE: Pass an empty routerSubnetIds list, as router interfaces
106         //will be removed from VPN by invocations from NeutronPortChangeListener
107         List<Uuid> routerSubnetIds = new ArrayList<>();
108         nvpnManager.handleNeutronRouterDeleted(routerId, routerSubnetIds);
109
110         neutronvpnUtils.removeFromRouterCache(input);
111         nvpnNatManager.removeNeutronRouterDpns(input);
112     }
113
114     @Override
115     public void update(InstanceIdentifier<Router> identifier, Router original, Router update) {
116         LOG.trace("Updating Router : key: {}, original value={}, update value={}", identifier, original, update);
117         if (Objects.equals(original, update)) {
118             return;
119         }
120         neutronvpnUtils.addToRouterCache(update);
121         Uuid routerId = update.getUuid();
122         neutronvpnUtils.addToRouterCache(update);
123         Uuid vpnId = neutronvpnUtils.getVpnForRouter(routerId, true);
124         // internal vpn always present in case external vpn not found
125         if (vpnId == null) {
126             vpnId = routerId;
127         }
128         List<Routes> oldRoutes = new ArrayList<>(original.nonnullRoutes().values());
129         List<Routes> newRoutes = new ArrayList<>(update.nonnullRoutes().values());
130         if (!oldRoutes.equals(newRoutes)) {
131             Iterator<Routes> iterator = newRoutes.iterator();
132             while (iterator.hasNext()) {
133                 Routes route = iterator.next();
134                 if (oldRoutes.remove(route)) {
135                     iterator.remove();
136                 }
137             }
138
139             LOG.debug("Updating Router : AddRoutes {}, DeleteRoutes {}", newRoutes, oldRoutes);
140             if (!oldRoutes.isEmpty()) {
141                 handleChangedRoutes(vpnId, oldRoutes, NwConstants.DEL_FLOW);
142             }
143
144             //After initial extra-route configuration using cmd-"neutron router-update RouterA destination=IP-A,
145             // nexthop=prefix-A",if another update is done using command - "neutron router-update RouterA
146             // destination=IP-A,nexthop=prefix-B",neutron router listener calls update on prefix-A as well as prefix-B.
147             // On prefix-A , secondary adj (IP-A) is removed ,where as its added on prefix-B. This back-to-back update
148             // creates race-condition in Vrf Engine ,leading inconsistencies in l3nexthop, VpnExtraRoute,
149             // VpnInterfaceOp DS. Hence a temporary fix of 2sec delay is introduced in neutron.
150             // A better fix/design need to be thought to avoid race condition
151             try {
152                 Thread.sleep(2000); // sleep for 2sec
153             } catch (java.lang.InterruptedException e) {
154                 LOG.error("Exception while sleeping", e);
155             }
156
157             if (!newRoutes.isEmpty()) {
158                 handleChangedRoutes(vpnId, newRoutes, NwConstants.ADD_FLOW);
159             }
160         }
161
162         jobCoordinator.enqueueJob(update.getUuid().toString(), () -> {
163             nvpnNatManager.handleExternalNetworkForRouter(original, update);
164             return Collections.emptyList();
165         });
166         gwMacResolver.sendArpRequestsToExtGateways(update);
167     }
168
169     private void handleChangedRoutes(Uuid vpnName, List<Routes> routes, int addedOrRemoved) {
170         // Some routes may point to an InterVpnLink's endpoint, lets treat them differently
171         List<Routes> interVpnLinkRoutes = new ArrayList<>();
172         List<Routes> otherRoutes = new ArrayList<>();
173         HashMap<String, InterVpnLink> nexthopsXinterVpnLinks = new HashMap<>();
174         for (Routes route : routes) {
175             String nextHop = route.getNexthop().stringValue();
176             // Nexthop is another VPN?
177             Optional<InterVpnLink> interVpnLink = neutronvpnUtils.getInterVpnLinkByEndpointIp(nextHop);
178             if (interVpnLink.isPresent()) {
179                 Optional<InterVpnLinkState> interVpnLinkState =
180                         neutronvpnUtils.getInterVpnLinkState(interVpnLink.get().getName());
181                 if (interVpnLinkState.isPresent() && interVpnLinkState.get().getState()
182                         == InterVpnLinkState.State.Active) {
183                     interVpnLinkRoutes.add(route);
184                     nexthopsXinterVpnLinks.put(nextHop, interVpnLink.get());
185                 } else {
186                     LOG.error("Failed installing route to {}. Reason: InterVPNLink {} is not Active",
187                             route.getDestination().stringValue(), interVpnLink.get().getName());
188                 }
189             } else {
190                 otherRoutes.add(route);
191             }
192         }
193
194         if (addedOrRemoved == NwConstants.ADD_FLOW) {
195             nvpnManager.addInterVpnRoutes(vpnName, interVpnLinkRoutes, nexthopsXinterVpnLinks);
196             nvpnManager.updateVpnInterfaceWithExtraRouteAdjacency(vpnName, otherRoutes);
197         } else {
198             nvpnManager.removeAdjacencyforExtraRoute(vpnName, otherRoutes);
199             nvpnManager.removeInterVpnRoutes(vpnName, interVpnLinkRoutes, nexthopsXinterVpnLinks);
200         }
201     }
202 }