Fixup Augmentable and Identifiable methods changing
[netvirt.git] / sfc / classifier / impl / src / main / java / org / opendaylight / netvirt / sfc / classifier / service / domain / impl / ConfigurationClassifierImpl.java
1 /*
2  * Copyright (c) 2017 Ericsson 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.netvirt.sfc.classifier.service.domain.impl;
10
11 import com.google.common.base.Strings;
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Objects;
19 import java.util.Optional;
20 import java.util.Set;
21 import java.util.stream.Collectors;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
24 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
25 import org.opendaylight.genius.mdsalutil.MDSALUtil;
26 import org.opendaylight.netvirt.sfc.classifier.providers.GeniusProvider;
27 import org.opendaylight.netvirt.sfc.classifier.providers.NetvirtProvider;
28 import org.opendaylight.netvirt.sfc.classifier.providers.OpenFlow13Provider;
29 import org.opendaylight.netvirt.sfc.classifier.providers.SfcProvider;
30 import org.opendaylight.netvirt.sfc.classifier.service.domain.ClassifierEntry;
31 import org.opendaylight.netvirt.sfc.classifier.service.domain.api.ClassifierRenderableEntry;
32 import org.opendaylight.netvirt.sfc.classifier.service.domain.api.ClassifierState;
33 import org.opendaylight.netvirt.sfc.classifier.utils.AclMatches;
34 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.RenderedServicePath;
35 import org.opendaylight.yang.gen.v1.urn.ericsson.params.xml.ns.yang.sfc.sff.logical.rev160620.DpnIdType;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.AccessLists;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.Acl;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.AccessListEntries;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.access.list.entries.Ace;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160218.access.lists.acl.access.list.entries.ace.Matches;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.InterfaceKey;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.get.dpn._interface.list.output.Interfaces;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.sfc.acl.rev150105.NetvirtsfcAclActions;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.sfc.acl.rev150105.NeutronNetwork;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.sfc.acl.rev150105.NeutronPorts;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.sfc.acl.rev150105.RedirectToSfc;
48 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class ConfigurationClassifierImpl implements ClassifierState {
53
54     private final SfcProvider sfcProvider;
55     private final DataBroker dataBroker;
56     private final GeniusProvider geniusProvider;
57     private final NetvirtProvider netvirtProvider;
58     private static final Logger LOG = LoggerFactory.getLogger(ConfigurationClassifierImpl.class);
59     private static final String LOCAL_HOST_IP = "127.0.0.1";
60
61     public ConfigurationClassifierImpl(GeniusProvider geniusProvider,
62                                        NetvirtProvider netvirtProvider,
63                                        SfcProvider sfcProvider,
64                                        DataBroker dataBroker) {
65         this.geniusProvider = geniusProvider;
66         this.netvirtProvider = netvirtProvider;
67         this.sfcProvider = sfcProvider;
68         this.dataBroker = dataBroker;
69     }
70
71     @Override
72     public Set<ClassifierRenderableEntry> getAllEntries() {
73         return readAcls().stream()
74                 .map(Acl::getAccessListEntries)
75                 .filter(Objects::nonNull)
76                 .map(AccessListEntries::getAce)
77                 .filter(Objects::nonNull)
78                 .flatMap(List::stream)
79                 .map(this::getEntriesForAce)
80                 .flatMap(Set::stream)
81                 .collect(Collectors.toSet());
82     }
83
84     private List<Acl> readAcls() {
85         InstanceIdentifier<AccessLists> aclsIID = InstanceIdentifier.builder(AccessLists.class).build();
86         Optional<AccessLists> acls;
87         acls = MDSALUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, aclsIID).toJavaUtil();
88         LOG.trace("Acls read from datastore: {}", acls);
89         return acls.map(AccessLists::getAcl).orElse(Collections.emptyList());
90     }
91
92     private Set<ClassifierRenderableEntry> getEntriesForAce(Ace ace) {
93         String ruleName = ace.getRuleName();
94         LOG.debug("Generating classifier entries for Ace: {}", ruleName);
95         LOG.trace("Ace details: {}", ace);
96
97         Optional<NetvirtsfcAclActions> sfcActions = Optional.ofNullable(ace.getActions())
98                 .map(actions -> actions.augmentation(RedirectToSfc.class));
99         String rspName = sfcActions.map(NetvirtsfcAclActions::getRspName).map(Strings::emptyToNull).orElse(null);
100         String sfpName = sfcActions.map(NetvirtsfcAclActions::getSfpName).map(Strings::emptyToNull).orElse(null);
101
102         if (rspName == null && sfpName == null) {
103             LOG.debug("Ace {} ignored: no valid SFC redirect action", ruleName);
104             return Collections.emptySet();
105         }
106         if (rspName != null && sfpName != null) {
107             LOG.warn("Ace {} ignored: both SFP and a RSP as redirect actions not supported", ruleName);
108             return Collections.emptySet();
109         }
110
111         Matches matches = ace.getMatches();
112         if (matches == null) {
113             LOG.warn("Ace {} ignored: no matches", ruleName);
114             return Collections.emptySet();
115         }
116
117         NeutronNetwork network = matches.augmentation(NeutronNetwork.class);
118         if (sfpName != null && network != null) {
119             LOG.warn("Ace {} ignored: SFP redirect action with neutron network match not supported", ruleName);
120             return Collections.emptySet();
121         }
122
123         String sourcePort = Optional.ofNullable(matches.augmentation(NeutronPorts.class))
124                 .map(NeutronPorts::getSourcePortUuid)
125                 .map(Strings::emptyToNull)
126                 .orElse(null);
127         String destinationPort = Optional.ofNullable(matches.augmentation(NeutronPorts.class))
128                 .map(NeutronPorts::getDestinationPortUuid)
129                 .map(Strings::emptyToNull)
130                 .orElse(null);
131
132         if (rspName != null) {
133             return getEntriesForRspRedirect(ruleName, sourcePort, destinationPort, network, rspName, matches);
134         }
135
136         return getEntriesForSfpRedirect(ruleName, sourcePort, destinationPort, sfpName, matches);
137     }
138
139     private Set<ClassifierRenderableEntry> getEntriesForRspRedirect(
140             String ruleName,
141             String sourcePort,
142             String destinationPort,
143             NeutronNetwork neutronNetwork,
144             String rspName,
145             Matches matches) {
146
147         RenderedServicePath rsp = sfcProvider.getRenderedServicePath(rspName).orElse(null);
148         if (rsp == null) {
149             LOG.debug("Ace {} ignored: RSP {} not yet available", ruleName, rspName);
150             return Collections.emptySet();
151         }
152
153         if (destinationPort != null) {
154             LOG.warn("Ace {}: destination port is ignored combined with RSP redirect");
155         }
156
157         List<String> interfaces = new ArrayList<>();
158         if (neutronNetwork != null) {
159             interfaces.addAll(netvirtProvider.getLogicalInterfacesFromNeutronNetwork(neutronNetwork));
160         }
161         if (sourcePort != null) {
162             interfaces.add(sourcePort);
163         }
164
165         if (interfaces.isEmpty()) {
166             LOG.debug("Ace {} ignored: no interfaces to match against", ruleName);
167             return Collections.emptySet();
168         }
169
170         return this.buildEntries(ruleName, interfaces, matches, rsp);
171     }
172
173     private Set<ClassifierRenderableEntry> getEntriesForSfpRedirect(
174             String ruleName,
175             String srcPort,
176             String dstPort,
177             String sfpName,
178             Matches matches) {
179
180         if (srcPort == null && dstPort == null) {
181             LOG.warn("Ace {} ignored: no source or destination port to match against", ruleName);
182             return Collections.emptySet();
183         }
184
185         if (Objects.equals(srcPort, dstPort)) {
186             LOG.warn("Ace {} ignored: equal source and destination port not supported", ruleName);
187             return Collections.emptySet();
188         }
189
190         List<RenderedServicePath> rsps = sfcProvider.readServicePathState(sfpName)
191                 .orElse(Collections.emptyList())
192                 .stream()
193                 .map(sfcProvider::getRenderedServicePath)
194                 .filter(Optional::isPresent)
195                 .map(Optional::get)
196                 .collect(Collectors.toList());
197
198         // The classifier might be configured at the same time as the SFP.
199         // The RSPs that are automatically added from that SFP might still
200         // be missing. It will be handled on a later listener event.
201         if (rsps.isEmpty()) {
202             LOG.debug("Ace {} ignored: no RSPs for SFP {} yet available", ruleName, sfpName);
203             return Collections.emptySet();
204         }
205
206         // An SFP will have two RSPs associated if symmetric, one otherwise.
207         if (rsps.size() > 2) {
208             LOG.warn("Ace {} ignored: more than two RSPs associated to SFP {} not supported", ruleName, sfpName);
209             return Collections.emptySet();
210         }
211
212         RenderedServicePath forwardRsp = rsps.stream()
213                 .filter(rsp -> !rsp.isReversePath())
214                 .findAny()
215                 .orElse(null);
216         RenderedServicePath reverseRsp = rsps.stream()
217                 .filter(RenderedServicePath::isReversePath)
218                 .filter(rsp -> forwardRsp != null && rsp.getSymmetricPathId().equals(forwardRsp.getPathId()))
219                 .findAny()
220                 .orElse(null);
221
222         if (srcPort != null && forwardRsp == null) {
223             LOG.debug("Ace {} ignored: no forward RSP yet available for SFP {} and source port {}",
224                     ruleName, sfpName, srcPort);
225             return Collections.emptySet();
226         }
227
228         if (dstPort != null && reverseRsp == null) {
229             LOG.debug("Ace {} ignored: no reverse RSP yet available for SFP {} and destination port {}",
230                     ruleName, sfpName, dstPort);
231             return Collections.emptySet();
232         }
233
234         Set<ClassifierRenderableEntry> entries = new HashSet<>();
235         if (srcPort != null) {
236             entries.addAll(this.buildEntries(ruleName, Collections.singletonList(srcPort), matches, forwardRsp));
237         }
238         if (dstPort != null) {
239             Matches invertedMatches = AclMatches.invertMatches(matches);
240             entries.addAll(
241                     this.buildEntries(ruleName, Collections.singletonList(dstPort), invertedMatches, reverseRsp));
242         }
243
244         return entries;
245     }
246
247     private Set<ClassifierRenderableEntry> buildEntries(
248             String ruleName,
249             @NonNull List<String> interfaces,
250             @NonNull Matches matches,
251             @NonNull RenderedServicePath rsp) {
252
253         String rspName = rsp.getName().getValue();
254         Long nsp = rsp.getPathId();
255         Short nsi = rsp.getStartingIndex();
256         Short nsl = rsp.getRenderedServicePathHop() == null ? null : (short) rsp.getRenderedServicePathHop().size();
257
258         if (nsp == null || nsi == null || nsl == null) {
259             LOG.warn("Ace {} RSP {} ignored: no valid NSI or NSP or length", ruleName, rspName);
260             return Collections.emptySet();
261         }
262
263         DpnIdType firstHopDpn = sfcProvider.getFirstHopIngressInterfaceFromRsp(rsp)
264                 .flatMap(geniusProvider::getDpnIdFromInterfaceName)
265                 .orElse(null);
266
267         if (firstHopDpn == null) {
268             LOG.warn("Ace {} RSP {} ignored: no valid first hop DPN", ruleName, rspName);
269             return Collections.emptySet();
270         }
271
272         String lastHopInterface = sfcProvider.getLastHopEgressInterfaceFromRsp(rsp).orElse(null);
273         if (lastHopInterface == null) {
274             LOG.warn("Ace {} RSP {} ignored: has no valid last hop interface", ruleName, rspName);
275             return Collections.emptySet();
276         }
277
278         DpnIdType lastHopDpn = geniusProvider.getDpnIdFromInterfaceName(lastHopInterface).orElse(null);
279         if (lastHopDpn == null) {
280             LOG.warn("Ace {} RSP {} ignored: has no valid last hop DPN", ruleName, rspName);
281             return Collections.emptySet();
282         }
283
284         Map<NodeId, List<InterfaceKey>> nodeToInterfaces = new HashMap<>();
285         for (String iface : interfaces) {
286             geniusProvider.getNodeIdFromLogicalInterface(iface).ifPresent(nodeId ->
287                     nodeToInterfaces.computeIfAbsent(nodeId, key -> new ArrayList<>()).add(new InterfaceKey(iface)));
288         }
289
290         LOG.trace("Ace {} RSP {}: got classifier nodes and interfaces: {}", ruleName, rspName, nodeToInterfaces);
291
292         String firstHopIp = geniusProvider.getIpFromDpnId(firstHopDpn).orElse(null);
293         Set<ClassifierRenderableEntry> entries = new HashSet<>();
294         nodeToInterfaces.forEach((nodeId, ifaces) -> {
295             // Get node info
296             DpnIdType nodeDpn = new DpnIdType(OpenFlow13Provider.getDpnIdFromNodeId(nodeId));
297             String nodeIp = geniusProvider.getIpFromDpnId(nodeDpn).orElse(LOCAL_HOST_IP);
298
299             if (firstHopIp == null && !nodeDpn.equals(firstHopDpn)) {
300                 LOG.warn("Ace {} RSP {} classifier {} ignored: no IP to reach first hop DPN {}",
301                         ruleName, rspName, nodeId, firstHopDpn);
302                 return;
303             }
304
305             // Add entries that are not based on ingress or egress interface
306             entries.add(ClassifierEntry.buildNodeEntry(nodeId));
307             entries.add(ClassifierEntry.buildPathEntry(
308                     nodeId,
309                     nsp,
310                     nsi,
311                     nsl,
312                     nodeDpn.equals(firstHopDpn) ? null : firstHopIp));
313
314             // Add entries based on ingress interface
315             ifaces.forEach(interfaceKey -> {
316                 entries.add(ClassifierEntry.buildIngressEntry(interfaceKey));
317                 entries.add(ClassifierEntry.buildMatchEntry(
318                         nodeId,
319                         geniusProvider.getNodeConnectorIdFromInterfaceName(interfaceKey.getName()).get(),
320                         matches,
321                         nsp,
322                         nsi));
323             });
324
325             // To handle chain egress when origin, last SF and destination are on the same node,
326             // we need to bind to the SF interface so that SFC pipeline to classifier pipeline
327             // hand-off can happen through the dispatcher table
328             if (nodeDpn.equals(lastHopDpn)) {
329                 entries.add(ClassifierEntry.buildIngressEntry(new InterfaceKey(lastHopInterface)));
330             }
331
332             // Egress services must bind to egress ports. Since we dont know before-hand what
333             // the egress ports will be, we will bind on all switch ports. If the packet
334             // doesnt have NSH, it will be returned to the the egress dispatcher table.
335             List<Interfaces> interfaceUuidStrList = geniusProvider.getInterfacesFromNode(nodeId);
336             interfaceUuidStrList.forEach(interfaceUuidStr -> {
337                 InterfaceKey interfaceKey = new InterfaceKey(interfaceUuidStr.getInterfaceName());
338                 Optional<String> remoteIp = geniusProvider.getRemoteIpAddress(interfaceUuidStr.getInterfaceName());
339                 entries.add(ClassifierEntry.buildEgressEntry(interfaceKey, remoteIp.orElse(nodeIp)));
340             });
341         });
342
343         return entries;
344     }
345 }