Support new actions in new ovs nsh patches
[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 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.ChainActionFlows.createChainTunnelFlows;
12 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxPushNshAction;
13 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxLoadNshMdtypeAction;
14 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxLoadNshNpAction;
15 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxSetNsiAction;
16 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxSetNspAction;
17
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Map;
21
22 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.groupbasedpolicy.api.ValidationResult;
25 import org.opendaylight.groupbasedpolicy.api.sf.ChainActionDefinition;
26 import org.opendaylight.groupbasedpolicy.dto.ValidationResultBuilder;
27 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfContext;
28 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfWriter;
29 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.mapper.policyenforcer.NetworkElements;
30 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sfcutils.SfcIidFactory;
31 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sfcutils.SfcNshHeader;
32 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sfcutils.SfcNshHeader.SfcNshHeaderBuilder;
33 import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
34 import org.opendaylight.groupbasedpolicy.util.IetfModelCodec;
35 import org.opendaylight.sfc.provider.api.SfcProviderRenderedPathAPI;
36 import org.opendaylight.sfc.provider.api.SfcProviderServiceChainAPI;
37 import org.opendaylight.sfc.provider.api.SfcProviderServicePathAPI;
38 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.RspName;
39 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SfcName;
40 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.CreateRenderedPathInput;
41 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.CreateRenderedPathInputBuilder;
42 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.path.first.hop.info.RenderedServicePathFirstHop;
43 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.RenderedServicePath;
44 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.rendered.service.path.RenderedServicePathHop;
45 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChain;
46 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.ServiceFunctionPaths;
47 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPath;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ActionDefinitionId;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ParameterName;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definitions.ActionDefinition;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.subject.feature.instances.ActionInstance;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.capabilities.supported.action.definition.SupportedParameterValues;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.capabilities.supported.action.definition.SupportedParameterValuesBuilder;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeVxlanGpe;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.google.common.base.Optional;
64 import com.google.common.collect.ImmutableList;
65 import com.google.common.collect.Iterables;
66
67 /**
68  * <h1>Chain action for the OpenFlow Overlay renderer</h1>
69  * TODO: separate the generic definition from the concrete<br>
70  * implementation for the OpenFlow Overlay renderer
71  * <p>
72  *
73  * see {@link org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.ChainActionFlows}
74  *
75  */
76 public class ChainAction extends Action {
77
78     private static final Logger LOG = LoggerFactory.getLogger(ChainAction.class);
79     private List<String> resolvedSymmetricChains = new ArrayList<>();;
80
81     public void setResolvedSymmetricChains(List<String> resolvedSymmetricChains) {
82         this.resolvedSymmetricChains = resolvedSymmetricChains;
83     }
84
85     @Override
86     public ActionDefinitionId getId() {
87         return ChainActionDefinition.ID;
88     }
89
90     @Override
91     public ActionDefinition getActionDef() {
92         return ChainActionDefinition.DEFINITION;
93     }
94
95     @Override
96     public List<ActionBuilder> updateAction(List<ActionBuilder> actions, Map<String, Object> params, Integer order,
97                                             NetworkElements netElements, OfWriter ofWriter,
98                                             OfContext ctx, Direction direction) {
99         /*
100          * Get the named chain
101          */
102         String chainName = null;
103         if (params != null) {
104             LOG.debug("updateAction: Searching for named chain");
105             for (String name : params.keySet()) {
106                     if (name.equals(ChainActionDefinition.SFC_CHAIN_NAME)) {
107                         chainName = (String) params.get(name);
108                         if (chainName == null) {
109                             LOG.error("updateAction: Chain name was null");
110                             return null;
111                         }
112                     }
113             }
114         } else {
115             LOG.error("updateAction: Parameters null for chain action");
116             return null;
117         }
118
119         if (chainName == null) {
120             LOG.error("updateAction: Chain name was null");
121             return null;
122         }
123
124         Long tunnelId;
125
126         /*
127          * If path is symmetrical then there are two RSPs.
128          * if srcEp is in consumer EPG use "rspName"
129          * else srcEp is in provider EPG, "rspName-Reverse".
130          */
131         ServiceFunctionPath sfcPath = getSfcPath(new SfcName(chainName));
132         if (sfcPath == null || sfcPath.getName() == null) {
133             LOG.error("updateAction: SFC Path was invalid. Either null or name was null.", sfcPath);
134             return null;
135         }
136         // TODO Need helper function to get getTenantName() that returns Name or UUID if Name is
137         // null
138
139         String tenantName = netElements.getSrcEp().getTenant().getValue();
140         // Find existing RSP based on following naming convention, else create it.
141         RspName rspName = new RspName(sfcPath.getName().getValue() + tenantName + "-gbp-rsp");
142         ReadOnlyTransaction rTx = ctx.getDataBroker().newReadOnlyTransaction();
143         RenderedServicePath renderedServicePath;
144         RenderedServicePath rsp = getRspByName(rspName, rTx);
145         tunnelId = (long) netElements.getSrcEpOrdinals().getTunnelId();
146         if (rsp == null) {
147             renderedServicePath = createRsp(sfcPath, rspName);
148             if (renderedServicePath != null) {
149                 LOG.info("updateAction: Could not find RSP {} for Chain {}, created.", rspName, chainName);
150             } else {
151                 LOG.error("updateAction: Could not create RSP {} for Chain {}", rspName, chainName);
152                 return null;
153             }
154         } else {
155             renderedServicePath = rsp;
156         }
157
158         try {
159         if (sfcPath.isSymmetric() && resolvedSymmetricChains.contains(chainName)) {
160                 rspName = new RspName(rspName.getValue() + "-Reverse");
161                 rsp = getRspByName(rspName, rTx);
162                 tunnelId = (long) netElements.getDstEpOrdinals().getTunnelId();
163                 if (rsp == null) {
164                     LOG.info("updateAction: Could not find Reverse RSP {} for Chain {}", rspName, chainName);
165                     renderedServicePath = createSymmetricRsp(renderedServicePath);
166                     if (renderedServicePath == null) {
167                         LOG.error("updateAction: Could not create RSP {} for Chain {}", rspName, chainName);
168                         return null;
169                     }
170                 } else {
171                     renderedServicePath = rsp;
172                 }
173             }
174         } catch (Exception e) {
175             LOG.error("updateAction: Attempting to determine if srcEp {} was consumer.", netElements.getSrcEp().getKey(),
176                     e);
177             return null;
178         }
179
180         RenderedServicePathFirstHop rspFirstHop = SfcProviderRenderedPathAPI.readRenderedServicePathFirstHop(rspName);
181         if (!isValidRspFirstHop(rspFirstHop)) {
182             // Errors logged in method.
183             return null;
184         }
185
186         NodeId tunnelDestNodeId = netElements.getDstNodeId();
187
188         IpAddress tunnelDest = ctx.getSwitchManager().getTunnelIP(tunnelDestNodeId, TunnelTypeVxlanGpe.class);
189         if (tunnelDest == null || tunnelDest.getIpv4Address() == null) {
190             LOG.error("updateAction: Invalid tunnelDest for NodeId: {}", tunnelDestNodeId);
191             return null;
192         }
193
194         RenderedServicePathHop firstRspHop = renderedServicePath.getRenderedServicePathHop().get(0);
195         RenderedServicePathHop lastRspHop = Iterables.getLast(renderedServicePath.getRenderedServicePathHop());
196         SfcNshHeader sfcNshHeader = new SfcNshHeaderBuilder().setNshTunIpDst(IetfModelCodec.ipv4Address2010(rspFirstHop.getIp().getIpv4Address()))
197             .setNshTunUdpPort(IetfModelCodec.portNumber2010(rspFirstHop.getPort()))
198             .setNshNsiToChain(firstRspHop.getServiceIndex())
199             .setNshNspToChain(renderedServicePath.getPathId())
200             .setNshNsiFromChain((short) (lastRspHop.getServiceIndex().intValue() - 1))
201             .setNshNspFromChain(renderedServicePath.getPathId())
202             .setNshMetaC1(SfcNshHeader.convertIpAddressToLong(tunnelDest.getIpv4Address()))
203             .setNshMetaC2(tunnelId)
204             .build();
205
206         createChainTunnelFlows(sfcNshHeader, netElements, ofWriter, ctx, direction);
207
208         if (direction.equals(Direction.Out) ) {
209             actions = addActionBuilder(actions, nxPushNshAction(), order);
210             actions = addActionBuilder(actions, nxLoadNshMdtypeAction(Short.valueOf((short)0x1)), order);
211             actions = addActionBuilder(actions, nxLoadNshNpAction(Short.valueOf((short)0x3)), order);
212             actions = addActionBuilder(actions, nxSetNsiAction(sfcNshHeader.getNshNsiToChain()), order);
213             actions = addActionBuilder(actions, nxSetNspAction(sfcNshHeader.getNshNspToChain()), order);
214         } else {
215             return null;
216         }
217         return actions;
218     }
219
220     private RenderedServicePath createRsp(ServiceFunctionPath sfcPath, RspName rspName) {
221         CreateRenderedPathInput rspInput =
222                 new CreateRenderedPathInputBuilder().setParentServiceFunctionPath(sfcPath.getName().getValue())
223                     .setName(rspName.getValue())
224                     .setSymmetric(sfcPath.isSymmetric())
225                     .build();
226         return SfcProviderRenderedPathAPI.createRenderedServicePathAndState(sfcPath, rspInput);
227     }
228
229     private RenderedServicePath createSymmetricRsp(RenderedServicePath rsp) {
230         if (rsp == null) {
231             return null;
232         }
233         return SfcProviderRenderedPathAPI.createSymmetricRenderedServicePathAndState(rsp);
234     }
235
236     private boolean isValidRspFirstHop(RenderedServicePathFirstHop rspFirstHop) {
237         boolean valid = true;
238         if (rspFirstHop == null) {
239             LOG.error("isValidRspFirstHop: rspFirstHop is null.");
240             return false;
241         }
242         if (rspFirstHop.getIp() == null || rspFirstHop.getIp().getIpv4Address() == null
243                 || rspFirstHop.getIp().getIpv6Address() != null) {
244             LOG.error("isValidRspFirstHop: rspFirstHop has invalid IP address.");
245             valid = false;
246         }
247         if (rspFirstHop.getPort() == null) {
248             LOG.error("isValidRspFirstHop: rspFirstHop has no IP port .");
249             valid = false;
250         }
251         if (rspFirstHop.getPathId() == null) {
252             LOG.error("isValidRspFirstHop: rspFirstHop has no Path Id (NSP).");
253             valid = false;
254         }
255         if (rspFirstHop.getStartingIndex() == null) {
256             LOG.error("isValidRspFirstHop: rspFirstHop has no Starting Index (NSI)");
257             valid = false;
258         }
259         return valid;
260     }
261
262     private RenderedServicePath getRspByName(RspName rspName, ReadOnlyTransaction rTx) {
263         Optional<RenderedServicePath> optRsp =
264                 DataStoreHelper.readFromDs(LogicalDatastoreType.OPERATIONAL, SfcIidFactory.rspIid(rspName), rTx);
265         if (optRsp.isPresent()) {
266             return optRsp.get();
267         }
268         return null;
269     }
270
271     @Override
272     public ValidationResult validate(ActionInstance actionInstance) {
273         return isValidGbpChain(actionInstance.getParameterValue());
274     }
275
276     private ValidationResult isValidGbpChain(List<ParameterValue> paramValue) {
277         ParameterValue pv = getChainNameParameter(paramValue);
278         if (pv == null) {
279             return new ValidationResultBuilder().failed().setMessage(
280                     "Chain parameter {" + paramValue + "} not found!").build();
281         }
282         SfcName sfcName = new SfcName(pv.getStringValue());
283         LOG.trace("isValidGbpChain: Invoking RPC for chain {}", pv.getStringValue());
284         ServiceFunctionChain chain = SfcProviderServiceChainAPI.readServiceFunctionChain(sfcName);
285         if (chain != null){
286             return new ValidationResultBuilder().success().build();
287         } else {
288             return new ValidationResultBuilder().failed().setMessage(
289                     "Chain named {" + pv.getStringValue() + "} not found in config DS.").build();
290         }
291     }
292
293     public static ServiceFunctionPath getSfcPath(SfcName chainName) {
294         ServiceFunctionPaths paths = SfcProviderServicePathAPI.readAllServiceFunctionPaths();
295         if (paths != null) {
296             for (ServiceFunctionPath path : paths.getServiceFunctionPath()) {
297                 if (path.getServiceChainName().equals(chainName)) {
298                     return path;
299                 }
300             }
301         }
302         return null;
303     }
304
305     private ParameterValue getChainNameParameter(List<ParameterValue> paramValueList) {
306         if (paramValueList == null)
307             return null;
308         for (ParameterValue pv : paramValueList) {
309             if (pv.getName().getValue().equals(ChainActionDefinition.SFC_CHAIN_NAME)) {
310                 return pv;
311             }
312         }
313         return null;
314     }
315
316     private List<ActionBuilder> addActionBuilder(List<ActionBuilder> actions,
317             org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.Action action, Integer order) {
318         ActionBuilder ab = new ActionBuilder();
319         ab.setAction(action);
320         ab.setOrder(order);
321         actions.add(ab);
322         return actions;
323     }
324
325     @Override
326     public List<SupportedParameterValues> getSupportedParameterValues() {
327         // supported parameter does not contain parameter type - it means all strings are supported
328         return ImmutableList.<SupportedParameterValues>of(new SupportedParameterValuesBuilder()
329             .setParameterName(new ParameterName(ChainActionDefinition.SFC_CHAIN_NAME)).build());
330     }
331 }