2 * Copyright (c) 2017 Ericsson India Global Services Pvt Ltd. and others. All rights reserved.
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
9 package org.opendaylight.netvirt.cloudservicechain;
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;
57 public class VPNServiceChainHandler implements AutoCloseable {
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;
67 public VPNServiceChainHandler(final DataBroker db, final IMdsalApiManager mdsalManager,
68 final IVpnFootprintService vpnFootprintService, final IInterfaceManager ifaceMgr,
69 final JobCoordinator jobCoordinator) {
71 this.mdsalManager = mdsalManager;
72 this.vpnFootprintService = vpnFootprintService;
73 this.interfaceManager = ifaceMgr;
74 this.jobCoordinator = jobCoordinator;
87 * Getting the VpnInstance from RouteDistinguisher.
89 * @param rd the Route-Distinguisher
90 * @return an Object that holds the Operational info of the VPN
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);
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.
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
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.
116 // - Match: cgnatLabel Instr: lportTag=vpnPseudoPortTag + SI=SCF + GOTO 17
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",
128 VpnInstanceOpDataEntry vpnInstance = getVpnInstance(rd);
129 if (vpnInstance == null) {
130 LOG.warn("Could not find a suitable VpnInstance for Route-Distinguisher={}", rd);
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);
144 RemoveVpnPseudoPortDataJob removeVpnPseudoPortDataTask = new RemoveVpnPseudoPortDataJob(dataBroker, rd);
145 jobCoordinator.enqueueJob(removeVpnPseudoPortDataTask.getDsJobCoordinatorKey(),
146 removeVpnPseudoPortDataTask);
149 for (VpnToDpnList dpnInVpn : vpnToDpnList) {
150 BigInteger dpnId = dpnInVpn.getDpnId();
151 programVpnToScfPipelineOnDpn(dpnId, vrfEntries, tableId, (int) scfTag, lportTag, addOrRemove);
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);
160 mdsalManager.removeFlow(dpnId, flow);
162 dpnInVpn.getVpnInterfaces().forEach(vpnIf -> {
163 if (addOrRemove == NwConstants.ADD_FLOW) {
164 bindScfOnVpnInterface(vpnIf.getInterfaceName(), (int) scfTag);
166 unbindScfOnVpnInterface(vpnIf.getInterfaceName());
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,
179 VpnServiceChainUtils.programLPortDispatcherFlowForVpnToScf(mdsalManager, dpnId, lportTag, scfTag,
180 tableIdToGoTo, addOrRemove);
185 * L3VPN Service chaining: It moves traffic from a ServiceChain to a L3VPN.
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
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:
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
207 LOG.info("L3VPN: Service Chaining programScfToVpnPipeline [Started]: Parameters Vpn Name: {} ", vpnName);
208 String rd = VpnServiceChainUtils.getVpnRd(dataBroker, vpnName);
210 if (rd == null || rd.isEmpty()) {
211 LOG.warn("programScfToVpnPipeline: Could not find Router-distinguisher for VPN {}. No further actions",
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) {
221 if (addOrRemove == NwConstants.ADD_FLOW
222 || addOrRemove == NwConstants.DEL_FLOW && isLastServiceChain) {
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());
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));
235 for (BigInteger dpn : dpns) {
236 VpnServiceChainUtils.programLPortDispatcherFlowForScfToVpn(mdsalManager, vpnId, dpn,
237 vpnPseudoLportTag, addOrRemove);
240 LOG.debug("Could not find VpnToDpn list for VPN {} with rd {}", vpnName, rd);
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,
249 vpnFootprintService.updateVpnToDpnMapping(BigInteger.valueOf(dpnId), vpnName, rd, intfName,
250 null/*ipAddressSourceValuePair*/, addOrRemove == NwConstants.ADD_FLOW);
252 LOG.info("L3VPN: Service Chaining programScfToVpnPipeline [End]");
256 * Removes all Flows in LFIB and LPortDispatcher that are related to this VpnPseudoLPort.
258 * @param vpnInstanceName vpn Instance name
259 * @param vpnPseudoLportTag vpnPseudoLPort tag
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.
268 String rd = VpnServiceChainUtils.getVpnRd(dataBroker, vpnInstanceName);
269 List<VrfEntry> vrfEntries = null;
271 vrfEntries = VpnServiceChainUtils.getAllVrfEntries(dataBroker, rd);
273 boolean cleanLFib = vrfEntries != null && !vrfEntries.isEmpty();
275 List<BigInteger> operativeDPNs = NWUtil.getOperativeDPNs(dataBroker);
276 for (BigInteger dpnId : operativeDPNs) {
278 VpnServiceChainUtils.programLFibEntriesForSCF(mdsalManager, dpnId, vrfEntries, vpnPseudoLportTag,
279 NwConstants.DEL_FLOW);
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);
293 RemoveVpnPseudoPortDataJob removeVpnPseudoPortDataTask = new RemoveVpnPseudoPortDataJob(dataBroker, rd);
294 jobCoordinator.enqueueJob(removeVpnPseudoPortDataTask.getDsJobCoordinatorKey(),
295 removeVpnPseudoPortDataTask);
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);
304 return SingleTransactionDataBroker.syncReadOptional(dataBroker,
305 LogicalDatastoreType.CONFIGURATION, boundServicesIId)
307 } catch (InterruptedException | ExecutionException e) {
308 LOG.warn("Error while reading [{}]", boundServicesIId, e);
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);
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,
330 interfaceManager.bindService(ifName, ServiceModeIngress.class, boundServices);
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());
340 interfaceManager.unbindService(ifName, ServiceModeIngress.class, boundService);
343 public Optional<VpnToPseudoPortData> getScfInfoForVpn(String vpnName) throws ReadFailedException {
344 String vpnRd = VpnServiceChainUtils.getVpnRd(dataBroker, vpnName);
346 LOG.trace("Checking if Vpn {} participates in SC. Could not find its RD", vpnName);
347 return Optional.empty();
350 return VpnServiceChainUtils.getVpnPseudoPortData(dataBroker, vpnRd);