MRI version bumpup for Aluminium
[netvirt.git] / cloud-servicechain / impl / src / main / java / org / opendaylight / netvirt / cloudservicechain / VPNServiceChainHandler.java
1 /*
2  * Copyright (c) 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
9 package org.opendaylight.netvirt.cloudservicechain;
10
11 import java.math.BigInteger;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.Optional;
18 import javax.annotation.PostConstruct;
19 import javax.annotation.PreDestroy;
20 import javax.inject.Inject;
21 import javax.inject.Singleton;
22 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
23 import org.opendaylight.genius.interfacemanager.globals.InterfaceServiceUtil;
24 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
25 import org.opendaylight.genius.mdsalutil.MDSALUtil;
26 import org.opendaylight.genius.mdsalutil.NWUtil;
27 import org.opendaylight.genius.mdsalutil.NwConstants;
28 import org.opendaylight.genius.mdsalutil.actions.ActionRegLoad;
29 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
30 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
31 import org.opendaylight.mdsal.binding.api.DataBroker;
32 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
33 import org.opendaylight.mdsal.common.api.ReadFailedException;
34 import org.opendaylight.netvirt.cloudservicechain.jobs.AddVpnPseudoPortDataJob;
35 import org.opendaylight.netvirt.cloudservicechain.jobs.RemoveVpnPseudoPortDataJob;
36 import org.opendaylight.netvirt.cloudservicechain.utils.VpnServiceChainUtils;
37 import org.opendaylight.netvirt.vpnmanager.api.IVpnFootprintService;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.ServiceModeIngress;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.service.bindings.services.info.BoundServices;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.cloud.servicechain.state.rev160711.vpn.to.pseudo.port.list.VpnToPseudoPortData;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.vrfentries.VrfEntry;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.VpnInstanceOpData;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntry;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntryKey;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.VpnToDpnList;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowjava.nx.match.rev140421.NxmNxReg2;
52 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 @Singleton
57 public class VPNServiceChainHandler implements AutoCloseable {
58
59     private static final Logger LOG = LoggerFactory.getLogger(VPNServiceChainHandler.class);
60     private final IMdsalApiManager mdsalManager;
61     private final DataBroker dataBroker;
62     private final IVpnFootprintService vpnFootprintService;
63     private final IInterfaceManager interfaceManager;
64     private final JobCoordinator jobCoordinator;
65
66     @Inject
67     public VPNServiceChainHandler(final DataBroker db, final IMdsalApiManager mdsalManager,
68                                   final IVpnFootprintService vpnFootprintService, final IInterfaceManager ifaceMgr,
69                                   final JobCoordinator jobCoordinator) {
70         this.dataBroker = db;
71         this.mdsalManager = mdsalManager;
72         this.vpnFootprintService = vpnFootprintService;
73         this.interfaceManager = ifaceMgr;
74         this.jobCoordinator = jobCoordinator;
75     }
76
77     @PostConstruct
78     public void init() {
79     }
80
81     @Override
82     @PreDestroy
83     public void close() {
84     }
85
86     /**
87      * Getting the VpnInstance from RouteDistinguisher.
88      *
89      * @param rd the Route-Distinguisher
90      * @return an Object that holds the Operational info of the VPN
91      */
92     protected VpnInstanceOpDataEntry getVpnInstance(String rd) {
93         InstanceIdentifier<VpnInstanceOpDataEntry> id = InstanceIdentifier.create(VpnInstanceOpData.class)
94                 .child(VpnInstanceOpDataEntry.class, new VpnInstanceOpDataEntryKey(rd));
95         return MDSALUtil.read(dataBroker, LogicalDatastoreType.OPERATIONAL, id).orElse(null);
96     }
97
98     /**
99      * Programs the necessary flows in LFIB and LPortDispatcher table so that
100      * the packets coming from a given VPN are delivered to a given
101      * ServiceChain Pipeline.
102      *
103      * @param vpnName Name of the VPN. Typically the UUID
104      * @param tableId Table to which the LPortDispatcher table sends the packet
105      *                 to (Uplink or Downlink Subsc table)
106      * @param scfTag Scf tag to the SCF to which the Vpn is linked to.
107      * @param lportTag VpnPseudo Port lportTag
108      * @param addOrRemove States if the VPN2SCF Pipeline must be installed or
109      *        removed
110      */
111     public void programVpnToScfPipeline(String vpnName, short tableId, long scfTag, int lportTag, int addOrRemove) {
112         // This entries must be created in the DPN where the CGNAT is installed. Since it is not possible
113         // to know where CGNAT is located, this entries are installed in all the VPN footprint.
114
115         //   LFIB:
116         //     - Match: cgnatLabel   Instr: lportTag=vpnPseudoPortTag + SI=SCF  +  GOTO 17
117         //   LportDisp:
118         //     - Match: vpnPseudoPortTag + SI==SCF   Instr:  scfTag  +  GOTO 70
119         LOG.info("programVpnToScfPipeline ({}) : Parameters VpnName:{} tableId:{} scftag:{}  lportTag:{}",
120                  addOrRemove == NwConstants.ADD_FLOW ? "Creation" : "Removal", vpnName, tableId, scfTag, lportTag);
121         String rd = VpnServiceChainUtils.getVpnRd(dataBroker, vpnName);
122         LOG.debug("Router distinguisher (rd):{}", rd);
123         if (rd == null || rd.isEmpty()) {
124             LOG.warn("programVpnToScfPipeline: Could not find Router-distinguisher for VPN {}. No further actions",
125                      vpnName);
126             return;
127         }
128         VpnInstanceOpDataEntry vpnInstance = getVpnInstance(rd);
129         if (vpnInstance == null) {
130             LOG.warn("Could not find a suitable VpnInstance for Route-Distinguisher={}", rd);
131             return;
132         }
133
134         // Find out the set of DPNs for the given VPN ID
135         Collection<VpnToDpnList> vpnToDpnList = vpnInstance.getVpnToDpnList();
136         List<VrfEntry> vrfEntries = VpnServiceChainUtils.getAllVrfEntries(dataBroker, rd);
137         if (vrfEntries != null) {
138             if (addOrRemove == NwConstants.ADD_FLOW) {
139                 AddVpnPseudoPortDataJob updateVpnToPseudoPortTask =
140                         new AddVpnPseudoPortDataJob(dataBroker, rd, lportTag, tableId, (int) scfTag);
141                 jobCoordinator.enqueueJob(updateVpnToPseudoPortTask.getDsJobCoordinatorKey(),
142                         updateVpnToPseudoPortTask);
143             } else {
144                 RemoveVpnPseudoPortDataJob removeVpnPseudoPortDataTask = new RemoveVpnPseudoPortDataJob(dataBroker, rd);
145                 jobCoordinator.enqueueJob(removeVpnPseudoPortDataTask.getDsJobCoordinatorKey(),
146                         removeVpnPseudoPortDataTask);
147             }
148
149             for (VpnToDpnList dpnInVpn : vpnToDpnList) {
150                 BigInteger dpnId = dpnInVpn.getDpnId();
151                 programVpnToScfPipelineOnDpn(dpnId, vrfEntries, tableId, (int) scfTag, lportTag, addOrRemove);
152
153                 if (dpnInVpn.getVpnInterfaces() != null) {
154                     long vpnId = vpnInstance.getVpnId();
155                     Flow flow = VpnServiceChainUtils.buildLPortDispFromScfToL3VpnFlow(vpnId, dpnId, lportTag,
156                             NwConstants.ADD_FLOW);
157                     if (addOrRemove == NwConstants.ADD_FLOW) {
158                         mdsalManager.installFlow(dpnId, flow);
159                     } else {
160                         mdsalManager.removeFlow(dpnId, flow);
161                     }
162                     dpnInVpn.getVpnInterfaces().forEach(vpnIf -> {
163                         if (addOrRemove == NwConstants.ADD_FLOW) {
164                             bindScfOnVpnInterface(vpnIf.getInterfaceName(), (int) scfTag);
165                         } else {
166                             unbindScfOnVpnInterface(vpnIf.getInterfaceName());
167                         }
168                     });
169                 }
170             }
171         }
172     }
173
174     public void programVpnToScfPipelineOnDpn(BigInteger dpnId, List<VrfEntry> vpnVrfEntries, short tableIdToGoTo,
175                                              int scfTag, int lportTag, int addOrRemove) {
176         VpnServiceChainUtils.programLFibEntriesForSCF(mdsalManager, dpnId, vpnVrfEntries, lportTag,
177                                                       addOrRemove);
178
179         VpnServiceChainUtils.programLPortDispatcherFlowForVpnToScf(mdsalManager, dpnId, lportTag, scfTag,
180                                                                    tableIdToGoTo, addOrRemove);
181     }
182
183
184     /**
185      * L3VPN Service chaining: It moves traffic from a ServiceChain to a L3VPN.
186      *
187      * @param vpnName Vpn Instance Name. Typicall the UUID
188      * @param scfTag ServiceChainForwarding Tag
189      * @param servChainTag ServiceChain Tag
190      * @param dpnId DpnId in which the egress pseudo logical port belongs
191      * @param vpnPseudoLportTag VpnPseudo Logical port tag
192      * @param isLastServiceChain Flag stating if there is no other ServiceChain
193      *        using this VpnPseudoPort
194      * @param addOrRemove States if pipeline must be installed or removed
195      */
196     public void programScfToVpnPipeline(String vpnName, long scfTag, int servChainTag, long dpnId,
197                                         int vpnPseudoLportTag, boolean isLastServiceChain, int addOrRemove) {
198         // These Flows must be installed in the DPN where the last SF in the ServiceChain is located
199         //   + ScForwardingTable (75):  (This one is created and maintained by ScHopManager)
200         //       - Match:  scfTag + servChainId + lportTagOfvVSF    Instr: VpnPseudoPortTag + SI=L3VPN + GOTO LPortDisp
201         // And these 2 flows must be installed in all Dpns where the Vpn is present:
202         //   + LPortDisp (17):
203         //       - Match:  VpnPseudoPortTag + SI==L3VPN    Instr: setVpnTag + GOTO FIB
204         //   + FIB (21): (one entry per VrfEntry, and it is maintained by FibManager)
205         //       - Match:  vrfTag==vpnTag + eth_type=IPv4  + dst_ip   Instr:  Output DC-GW
206         //
207         LOG.info("L3VPN: Service Chaining programScfToVpnPipeline [Started]: Parameters Vpn Name: {} ", vpnName);
208         String rd = VpnServiceChainUtils.getVpnRd(dataBroker, vpnName);
209
210         if (rd == null || rd.isEmpty()) {
211             LOG.warn("programScfToVpnPipeline: Could not find Router-distinguisher for VPN {}. No further actions",
212                      vpnName);
213             return;
214         }
215
216         VpnInstanceOpDataEntry vpnInstance = getVpnInstance(rd);
217         LOG.debug("programScfToVpnPipeline: rd={}, lportTag={} ", rd, vpnPseudoLportTag);
218         // Find out the set of DPNs for the given VPN ID
219         if (vpnInstance != null) {
220
221             if (addOrRemove == NwConstants.ADD_FLOW
222                    || addOrRemove == NwConstants.DEL_FLOW && isLastServiceChain) {
223
224                 Long vpnId = vpnInstance.getVpnId();
225                 List<VpnToDpnList> vpnToDpnList = vpnInstance.getVpnToDpnList();
226                 if (vpnToDpnList != null) {
227                     List<BigInteger> dpns = new ArrayList<>();
228                     for (VpnToDpnList dpnInVpn : vpnToDpnList) {
229                         dpns.add(dpnInVpn.getDpnId());
230                     }
231                     if (!dpns.contains(BigInteger.valueOf(dpnId))) {
232                         LOG.debug("Dpn {} is not included in the current VPN Footprint", dpnId);
233                         dpns.add(BigInteger.valueOf(dpnId));
234                     }
235                     for (BigInteger dpn : dpns) {
236                         VpnServiceChainUtils.programLPortDispatcherFlowForScfToVpn(mdsalManager, vpnId, dpn,
237                                 vpnPseudoLportTag, addOrRemove);
238                     }
239                 } else {
240                     LOG.debug("Could not find VpnToDpn list for VPN {} with rd {}", vpnName, rd);
241                 }
242             }
243
244             // We need to keep a fake VpnInterface in the DPN where the last vSF (before the VpnPseudoPort) is
245             // located, because in case the last real VpnInterface is removed from that DPN, we still need
246             // the Fib table programmed there
247             String intfName = VpnServiceChainUtils.buildVpnPseudoPortIfName(dpnId, scfTag, servChainTag,
248                                                                             vpnPseudoLportTag);
249             vpnFootprintService.updateVpnToDpnMapping(BigInteger.valueOf(dpnId), vpnName, rd, intfName,
250                     null/*ipAddressSourceValuePair*/, addOrRemove == NwConstants.ADD_FLOW);
251         }
252         LOG.info("L3VPN: Service Chaining programScfToVpnPipeline [End]");
253     }
254
255     /**
256      * Removes all Flows in LFIB and LPortDispatcher that are related to this VpnPseudoLPort.
257      *
258      * @param vpnInstanceName vpn Instance name
259      * @param vpnPseudoLportTag vpnPseudoLPort tag
260      */
261     public void removeVpnPseudoPortFlows(String vpnInstanceName, int vpnPseudoLportTag) {
262         // At VpnPseudoPort removal time the current Vpn footprint could not be enough, so let's try to
263         // remove all possible entries in all DPNs.
264         // TODO: Study how this could be enhanced. It could be done at ServiceChain removal, but that
265         // could imply check all ServiceChains ending in all DPNs in Vpn footprint to decide that if the entries
266         // can be removed, and that sounds even costlier than this.
267
268         String rd = VpnServiceChainUtils.getVpnRd(dataBroker, vpnInstanceName);
269         List<VrfEntry> vrfEntries = null;
270         if (rd != null) {
271             vrfEntries = VpnServiceChainUtils.getAllVrfEntries(dataBroker, rd);
272         }
273         boolean cleanLFib = vrfEntries != null && !vrfEntries.isEmpty();
274
275         List<BigInteger> operativeDPNs = NWUtil.getOperativeDPNs(dataBroker);
276         for (BigInteger dpnId : operativeDPNs) {
277             if (cleanLFib) {
278                 VpnServiceChainUtils.programLFibEntriesForSCF(mdsalManager, dpnId, vrfEntries, vpnPseudoLportTag,
279                                                               NwConstants.DEL_FLOW);
280             }
281
282             String vpnToScfflowRef = VpnServiceChainUtils.getL3VpnToScfLportDispatcherFlowRef(vpnPseudoLportTag);
283             Flow vpnToScfFlow = new FlowBuilder().setTableId(NwConstants.LPORT_DISPATCHER_TABLE)
284                                                  .setId(new FlowId(vpnToScfflowRef)).build();
285             mdsalManager.removeFlow(dpnId, vpnToScfFlow);
286             String scfToVpnFlowRef = VpnServiceChainUtils.getScfToL3VpnLportDispatcherFlowRef(vpnPseudoLportTag);
287             Flow scfToVpnFlow = new FlowBuilder().setTableId(NwConstants.LPORT_DISPATCHER_TABLE)
288                                                  .setId(new FlowId(scfToVpnFlowRef)).build();
289             mdsalManager.removeFlow(dpnId, scfToVpnFlow);
290         }
291
292         if (rd != null) {
293             RemoveVpnPseudoPortDataJob removeVpnPseudoPortDataTask = new RemoveVpnPseudoPortDataJob(dataBroker, rd);
294             jobCoordinator.enqueueJob(removeVpnPseudoPortDataTask.getDsJobCoordinatorKey(),
295                     removeVpnPseudoPortDataTask);
296         }
297     }
298
299     // TODO: Remove if [https://git.opendaylight.org/gerrit/#/c/51075/] lands on Genius
300     private boolean isServiceBoundOnInterface(short servicePriority, String interfaceName) {
301         InstanceIdentifier<BoundServices> boundServicesIId =
302             VpnServiceChainUtils.buildBoundServicesIid(servicePriority, interfaceName);
303         try {
304             return SingleTransactionDataBroker.syncReadOptional(dataBroker,
305                                                                 LogicalDatastoreType.CONFIGURATION, boundServicesIId)
306                                               .isPresent();
307         } catch (InterruptedException | ExecutionException e) {
308             LOG.warn("Error while reading [{}]", boundServicesIId, e);
309             return false;
310         }
311     }
312
313
314     public void bindScfOnVpnInterface(String ifName, int scfTag) {
315         LOG.debug("bind SCF tag {} on iface {}", scfTag, ifName);
316         if (isServiceBoundOnInterface(NwConstants.SCF_SERVICE_INDEX, ifName)) {
317             LOG.info("SCF is already bound on Interface {} for Ingress. Binding aborted", ifName);
318             return;
319         }
320         Action loadReg2Action = new ActionRegLoad(1, NxmNxReg2.class, 0, 31, scfTag).buildAction();
321         List<Instruction> instructions =
322             Arrays.asList(MDSALUtil.buildApplyActionsInstruction(Collections.singletonList(loadReg2Action)),
323                           MDSALUtil.buildAndGetGotoTableInstruction(NwConstants.SCF_DOWN_SUB_FILTER_TCP_BASED_TABLE,
324                                                                     1 /*instructionKey, not sure why it is needed*/));
325         BoundServices boundServices =
326             InterfaceServiceUtil.getBoundServices(ifName, NwConstants.SCF_SERVICE_INDEX,
327                                                   CloudServiceChainConstants.DEFAULT_SCF_FLOW_PRIORITY,
328                                                   CloudServiceChainConstants.COOKIE_SCF_BASE,
329                                                   instructions);
330         interfaceManager.bindService(ifName, ServiceModeIngress.class, boundServices);
331     }
332
333     public void unbindScfOnVpnInterface(String ifName) {
334         BoundServices boundService =
335             InterfaceServiceUtil.getBoundServices(ifName, NwConstants.SCF_SERVICE_INDEX,
336                                                   CloudServiceChainConstants.DEFAULT_SCF_FLOW_PRIORITY,
337                                                   CloudServiceChainConstants.COOKIE_SCF_BASE,
338                                                   /*instructions*/ Collections.emptyList());
339
340         interfaceManager.unbindService(ifName, ServiceModeIngress.class, boundService);
341     }
342
343     public Optional<VpnToPseudoPortData> getScfInfoForVpn(String vpnName) throws ReadFailedException {
344         String vpnRd = VpnServiceChainUtils.getVpnRd(dataBroker, vpnName);
345         if (vpnRd == null) {
346             LOG.trace("Checking if Vpn {} participates in SC. Could not find its RD", vpnName);
347             return Optional.empty();
348         }
349
350         return VpnServiceChainUtils.getVpnPseudoPortData(dataBroker, vpnRd);
351     }
352 }