Bug 3302: fix for GroupTable
[groupbasedpolicy.git] / renderers / ofoverlay / src / main / java / org / opendaylight / groupbasedpolicy / renderer / ofoverlay / sf / ChainAction.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, 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.groupbasedpolicy.renderer.ofoverlay.sf;
10
11 /*
12  * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
13  *
14  * This program and the accompanying materials are made available under the
15  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
16  * and is available at http://www.eclipse.org/legal/epl-v10.html
17  */
18
19 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.ChainActionFlows.createChainTunnelFlows;
20 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxSetNsiAction;
21 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxSetNspAction;
22
23 import java.util.List;
24 import java.util.Map;
25
26 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfWriter;
29 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfContext;
30 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.PolicyEnforcer.NetworkElements;
31 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.PolicyEnforcer.PolicyPair;
32 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sfcutils.SfcIidFactory;
33 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sfcutils.SfcNshHeader;
34 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sfcutils.SfcNshHeader.SfcNshHeaderBuilder;
35 import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
36 import org.opendaylight.sfc.provider.api.SfcProviderRenderedPathAPI;
37 import org.opendaylight.sfc.provider.api.SfcProviderServiceChainAPI;
38 import org.opendaylight.sfc.provider.api.SfcProviderServicePathAPI;
39 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.CreateRenderedPathInput;
40 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.CreateRenderedPathInputBuilder;
41 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.path.first.hop.info.RenderedServicePathFirstHop;
42 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.RenderedServicePath;
43 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.rendered.service.path.RenderedServicePathHop;
44 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChain;
45 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.ServiceFunctionPaths;
46 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPath;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ActionDefinitionId;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ActionName;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.Description;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ParameterName;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definition.Parameter.IsRequired;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definition.Parameter.Type;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definition.ParameterBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definitions.ActionDefinition;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definitions.ActionDefinitionBuilder;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.subject.feature.instances.ActionInstance;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeVxlanGpe;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 import com.google.common.base.Optional;
67 import com.google.common.collect.ImmutableList;
68 import com.google.common.collect.Iterables;
69
70 /**
71  * Chain action for the OpenFlow Overlay renderer
72  * TODO: separate the generic definition from the concrete
73  * implementation for the OpenFlow Overlay renderer
74  */
75 public class ChainAction extends Action {
76
77     private static final Logger LOG = LoggerFactory.getLogger(ChainAction.class);
78
79     public static final ActionDefinitionId ID = new ActionDefinitionId("3d886be7-059f-4c4f-bbef-0356bea40933");
80
81     public static final Integer CHAIN_CONDITION_GROUP = 0xfffffe;
82
83     protected static final String TYPE = "type";
84
85     // the chain action
86     public static final String SFC_CHAIN_ACTION = "chain";
87     // the parameter used for storing the chain name
88     public static final String SFC_CHAIN_NAME = "sfc-chain-name";
89
90     protected static final ActionDefinition DEF = new ActionDefinitionBuilder().setId(ID)
91         .setName(new ActionName(SFC_CHAIN_ACTION))
92         .setDescription(new Description("Send the traffic through a Service Function Chain"))
93         .setParameter(
94                 (ImmutableList.of(new ParameterBuilder().setName(new ParameterName(SFC_CHAIN_NAME))
95                     .setDescription(new Description("The named chain to match against"))
96                     .setIsRequired(IsRequired.Required)
97                     .setType(Type.String)
98                     .build())))
99         .build();
100
101     @Override
102     public ActionDefinitionId getId() {
103         return ID;
104     }
105
106     @Override
107     public ActionDefinition getActionDef() {
108         return DEF;
109     }
110
111     @Override
112     public List<ActionBuilder> updateAction(List<ActionBuilder> actions, Map<String, Object> params, Integer order,
113             NetworkElements netElements, PolicyPair policyPair, OfWriter ofWriter, OfContext ctx, Direction direction) {
114         /*
115          * Get the named chain
116          */
117         String chainName = null;
118         if (params != null) {
119             LOG.debug("updateAction: Searching for named chain");
120             for (String name : params.keySet()) {
121                 if (name instanceof String) {
122                     if (name.equals(SFC_CHAIN_NAME)) {
123                         chainName = (String) params.get(name);
124                         if (chainName == null) {
125                             LOG.error("updateAction: Chain name was null");
126                             return null;
127                         }
128                     }
129                 }
130             }
131         } else {
132             LOG.error("updateAction: Parameters null for chain action");
133             return null;
134         }
135
136         if (chainName == null) {
137             LOG.error("updateAction: Chain name was null");
138             return null;
139         }
140
141         /*
142          * If path is symmetrical then there are two RSPs.
143          * if srcEp is in consumer EPG use "rspName"
144          * else srcEp is in provider EPG, "rspName-Reverse".
145          */
146         ServiceFunctionPath sfcPath = getSfcPath(chainName);
147         if (sfcPath == null || sfcPath.getName() == null) {
148             LOG.error("updateAction: SFC Path was invalid. Either null or name was null.", sfcPath);
149             return null;
150         }
151         // Find existing RSP based on following naming convention, else create it.
152         String rspName = sfcPath.getName() + "-gbp-rsp";
153         ReadOnlyTransaction rTx = ctx.getDataBroker().newReadOnlyTransaction();
154         RenderedServicePath renderedServicePath;
155         RenderedServicePath rsp = getRspByName(rspName, rTx);
156         if (rsp == null) {
157             renderedServicePath = createRsp(sfcPath, rspName);
158             if (renderedServicePath != null) {
159                 LOG.info("updateAction: Could not find RSP {} for Chain {}, created.", rspName, chainName);
160             } else {
161                 LOG.error("updateAction: Could not create RSP {} for Chain {}", rspName, chainName);
162                 return null;
163             }
164         } else {
165             renderedServicePath = rsp;
166         }
167
168         try {
169             if (sfcPath.isSymmetric() && direction.equals(Direction.Out)){
170                 rspName = rspName + "-Reverse";
171                 rsp = getRspByName(rspName, rTx);
172                 if (rsp == null) {
173                     LOG.info("updateAction: Could not find Reverse RSP {} for Chain {}", rspName, chainName);
174                     renderedServicePath = createSymmetricRsp(renderedServicePath);
175                     if (renderedServicePath == null) {
176                         LOG.error("updateAction: Could not create RSP {} for Chain {}", rspName, chainName);
177                         return null;
178                     }
179                 } else {
180                     renderedServicePath = rsp;
181                 }
182             }
183         } catch (Exception e) {
184             LOG.error("updateAction: Attemping to determine if srcEp {} was consumer.", netElements.getSrcEp().getKey(), e);
185             return null;
186         }
187
188         RenderedServicePathFirstHop rspFirstHop = SfcProviderRenderedPathAPI.readRenderedServicePathFirstHop(rspName);
189         if (!isValidRspFirstHop(rspFirstHop)) {
190             // Errors logged in method.
191             return null;
192         }
193
194         NodeId tunnelDestNodeId=netElements.getDstNodeId();
195
196         Long returnVnid = (long) netElements.getSrcEpOrds().getTunnelId();
197
198         IpAddress tunnelDest = ctx.getSwitchManager().getTunnelIP(tunnelDestNodeId, TunnelTypeVxlanGpe.class);
199         if (tunnelDest == null || tunnelDest.getIpv4Address() == null) {
200             LOG.error("updateAction: Invalid tunnelDest for NodeId: {}", tunnelDestNodeId);
201             return null;
202         }
203
204         RenderedServicePathHop firstRspHop = renderedServicePath.getRenderedServicePathHop().get(0);
205         RenderedServicePathHop lastRspHop = Iterables.getLast(renderedServicePath.getRenderedServicePathHop());
206         SfcNshHeader sfcNshHeader = new SfcNshHeaderBuilder().setNshTunIpDst(rspFirstHop.getIp().getIpv4Address())
207             .setNshTunUdpPort(rspFirstHop.getPort())
208             .setNshNsiToChain(firstRspHop.getServiceIndex())
209             .setNshNspToChain(renderedServicePath.getPathId())
210             .setNshNsiFromChain((short) (lastRspHop.getServiceIndex().intValue() - 1))
211             .setNshNspFromChain(renderedServicePath.getPathId())
212             .setNshMetaC1(SfcNshHeader.convertIpAddressToLong(tunnelDest.getIpv4Address()))
213             .setNshMetaC2(returnVnid)
214             .build();
215
216         // Cannot set all actions here. Some actions are destination specific, and we don't know
217         // a destination is to be
218         // chained until we reach this point. Need to write match/action in External Table for
219         // chained packets.
220         actions = addActionBuilder(actions, nxSetNsiAction(sfcNshHeader.getNshNsiToChain()), order);
221         actions = addActionBuilder(actions, nxSetNspAction(sfcNshHeader.getNshNspToChain()), order);
222         createChainTunnelFlows(sfcNshHeader, netElements, ofWriter, ctx);
223         return actions;
224     }
225
226     private RenderedServicePath createRsp(ServiceFunctionPath sfcPath, String rspName) {
227         CreateRenderedPathInput rspInput = new CreateRenderedPathInputBuilder().setParentServiceFunctionPath(
228                 sfcPath.getName())
229             .setName(rspName)
230             .setSymmetric(sfcPath.isSymmetric())
231             .build();
232          return SfcProviderRenderedPathAPI.createRenderedServicePathAndState(sfcPath, rspInput);
233     }
234
235     private RenderedServicePath createSymmetricRsp(RenderedServicePath rsp) {
236         if (rsp == null) {
237             return null;
238         }
239         return SfcProviderRenderedPathAPI.createSymmetricRenderedServicePathAndState(rsp);
240     }
241
242     private boolean isValidRspFirstHop(RenderedServicePathFirstHop rspFirstHop) {
243         boolean valid = true;
244         if (rspFirstHop == null) {
245             LOG.error("isValidRspFirstHop: rspFirstHop is null.");
246             return false;
247         }
248         if (rspFirstHop.getIp() == null || rspFirstHop.getIp().getIpv4Address() == null
249                 || rspFirstHop.getIp().getIpv6Address() != null) {
250             LOG.error("isValidRspFirstHop: rspFirstHop has invalid IP address.");
251             valid = false;
252         }
253         if (rspFirstHop.getPort() == null) {
254             LOG.error("isValidRspFirstHop: rspFirstHop has no IP port .");
255             valid = false;
256         }
257         if (rspFirstHop.getPathId() == null) {
258             LOG.error("isValidRspFirstHop: rspFirstHop has no Path Id (NSP).");
259             valid = false;
260         }
261         if (rspFirstHop.getStartingIndex() == null) {
262             LOG.error("isValidRspFirstHop: rspFirstHop has no Starting Index (NSI)");
263             valid = false;
264         }
265         return valid;
266     }
267
268     private RenderedServicePath getRspByName(String rspName, ReadOnlyTransaction rTx) {
269         Optional<RenderedServicePath> optRsp = DataStoreHelper.readFromDs(LogicalDatastoreType.OPERATIONAL,
270                 SfcIidFactory.rspIid(rspName), rTx);
271         if (optRsp.isPresent()) {
272             return optRsp.get();
273         }
274         return null;
275     }
276
277     @Override
278     public boolean isValid(ActionInstance actionInstance) {
279         return isValidGbpChain(actionInstance.getParameterValue());
280     }
281
282     private boolean isValidGbpChain(List<ParameterValue> paramValue) {
283         ParameterValue pv = getChainNameParameter(paramValue);
284         if (pv == null) {
285             return false;
286         }
287         LOG.trace("isValidGbpChain: Invoking RPC for chain {}", pv.getStringValue());
288         ServiceFunctionChain chain = SfcProviderServiceChainAPI.readServiceFunctionChain(pv.getStringValue());
289         return (chain != null);
290     }
291
292     public ServiceFunctionPath getSfcPath(String chainName) {
293         ServiceFunctionPaths paths = SfcProviderServicePathAPI.readAllServiceFunctionPaths();
294         for (ServiceFunctionPath path : paths.getServiceFunctionPath()) {
295             if (path.getServiceChainName().equals(chainName)) {
296                 return path;
297             }
298         }
299         return null;
300     }
301
302     private ParameterValue getChainNameParameter(List<ParameterValue> paramValueList) {
303         if (paramValueList == null)
304             return null;
305         for (ParameterValue pv : paramValueList) {
306             if (pv.getName().getValue().equals(SFC_CHAIN_NAME)) {
307                 return pv;
308             }
309         }
310         return null;
311     }
312
313     private List<ActionBuilder> addActionBuilder(List<ActionBuilder> actions,
314             org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.Action action, Integer order) {
315         ActionBuilder ab = new ActionBuilder();
316         ab.setAction(action);
317         ab.setOrder(order);
318         actions.add(ab);
319         return actions;
320     }
321
322 }