Add INFO.yaml for GBP
[groupbasedpolicy.git] / renderers / ofoverlay / src / main / java / org / opendaylight / groupbasedpolicy / renderer / ofoverlay / SfcManager.java
1 /*
2  * Copyright (c) 2015 Intel, 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;
10
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import java.util.Collection;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Set;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ConcurrentMap;
22 import java.util.concurrent.ExecutorService;
23 import java.util.concurrent.Future;
24 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
25 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
27 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
28 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
29 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
30 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
31 import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
32 import org.opendaylight.groupbasedpolicy.api.sf.ChainActionDefinition;
33 import org.opendaylight.groupbasedpolicy.util.IetfModelCodec;
34 import org.opendaylight.sfc.provider.SfcProviderRpc;
35 import org.opendaylight.sfc.provider.api.SfcProviderServiceChainAPI;
36 import org.opendaylight.sfc.provider.api.SfcProviderServicePathAPI;
37 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SfcName;
38 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.ReadRenderedServicePathFirstHopInputBuilder;
39 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.ReadRenderedServicePathFirstHopOutput;
40 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.path.first.hop.info.RenderedServicePathFirstHop;
41 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChain;
42 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.ServiceFunctionPaths;
43 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPath;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ActionDefinitionId;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.SubjectFeatureDefinitions;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.Tenants;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definitions.ActionDefinition;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definitions.ActionDefinitionKey;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.Tenant;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.Policy;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.SubjectFeatureInstances;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.subject.feature.instances.ActionInstance;
55 import org.opendaylight.yangtools.concepts.ListenerRegistration;
56 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
57 import org.opendaylight.yangtools.yang.common.RpcResult;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 /**
62  * Manage the state exchanged with SFC
63  *
64  * For the Proof of Concept, this manages the
65  * RenderedServicePathFirstHop elements that
66  * are retrieved from SFC.
67  *
68  */
69 public class SfcManager implements AutoCloseable, DataTreeChangeListener<ActionInstance> {
70     private static final Logger LOG =
71             LoggerFactory.getLogger(SfcManager.class);
72
73     private final DataBroker dataBroker;
74     private final ExecutorService executor;
75     private final InstanceIdentifier<ActionInstance> allActionInstancesIid;
76     private final ListenerRegistration<?> actionListener;
77     private final SfcProviderRpc sfcProviderRpc;
78
79     /*
80      * local cache of the RSP first hops that we've requested from SFC,
81      * keyed by RSP name
82      */
83     private final ConcurrentMap<String, RenderedServicePathFirstHop> rspMap;
84
85     /*
86      *  TODO: these two String defs should move to the common
87      *        "chain" action, once we have it.
88      */
89     // the chain action
90     public static final String SFC_CHAIN_ACTION = "chain";
91     // the parameter used for storing the chain name
92     public static final String SFC_CHAIN_NAME = "sfc-chain-name";
93
94     private static enum ActionState {
95         ADD("add"),
96         CHANGE("change"),
97         DELETE("delete");
98
99         private String state;
100
101         ActionState(String state) {
102             this.state = state;
103         }
104
105         @Override
106         public String toString() {
107             return this.state;
108         }
109     }
110
111
112     public SfcManager(DataBroker dataBroker,
113                       RpcProviderRegistry rpcRegistry,
114                       ExecutorService executor) {
115         Preconditions.checkNotNull(dataBroker, "Databroker for SfcManager must not be null!");
116         this.dataBroker = dataBroker;
117         this.executor = executor;
118         this.sfcProviderRpc = new SfcProviderRpc(dataBroker);
119         /*
120          * Use thread-safe type only because we use an executor
121          */
122         this.rspMap = new ConcurrentHashMap<>();
123
124         /*
125          * For now, listen to all changes in rules
126          */
127         allActionInstancesIid =
128                 InstanceIdentifier.builder(Tenants.class)
129                     .child(Tenant.class)
130                     .child(Policy.class)
131                     .child(SubjectFeatureInstances.class)
132                     .child(ActionInstance.class)
133                     .build();
134         actionListener = dataBroker.registerDataTreeChangeListener(new DataTreeIdentifier<>(
135                 LogicalDatastoreType.CONFIGURATION, allActionInstancesIid), this);
136         LOG.debug("SfcManager: Started");
137     }
138
139     public Set<IpAddress> getSfcSourceIps() {
140         if (rspMap.isEmpty()) {
141             return null;
142         }
143
144         Set<IpAddress> ipAddresses = new HashSet<>();
145         for (RenderedServicePathFirstHop rsp: rspMap.values()) {
146             if (rsp.getIp() != null) {
147                 ipAddresses.add(IetfModelCodec.ipAddress2010(rsp.getIp()));
148             }
149         }
150         if (ipAddresses.isEmpty()) {
151             return null;
152         }
153         return ipAddresses;
154     }
155
156     @Override
157     public void onDataTreeChanged(Collection<DataTreeModification<ActionInstance>> changes) {
158         for (DataTreeModification<ActionInstance> change: changes) {
159             DataObjectModification<ActionInstance> rootNode = change.getRootNode();
160             final ActionInstance dataBefore = rootNode.getDataBefore();
161             final ActionInstance dataAfter = rootNode.getDataAfter();
162             switch (rootNode.getModificationType()) {
163                 case SUBTREE_MODIFIED:
164                 case WRITE:
165                     if (dataBefore == null) {
166                         LOG.debug("New ActionInstance created");
167                         executor.execute(new MatchActionDefTask(dataAfter, null, ActionState.ADD));
168                     } else {
169                         /*
170                          We may have some cleanup here.  If the reference to
171                          the Action Definition changed, or if the Action Instance's
172                          chain parameter  then we're no longer
173                          an action, and we may need to remove the RSP.
174                         */
175                         LOG.debug("ActionInstance updated");
176                         executor.execute(new MatchActionDefTask(dataAfter, dataBefore, ActionState.CHANGE));
177                     }
178                     break;
179                 case DELETE:
180                     LOG.debug("ActionInstance deleted");
181                     executor.execute(new MatchActionDefTask(null, dataBefore, ActionState.DELETE));
182                     break;
183                 default:
184                     break;
185             }
186         }
187     }
188
189     /**
190      * Private internal class that gets the action definition
191      * referenced by the instance. If the definition has an
192      * action of "chain" (or whatever we decide to use
193      * here), then we need to invoke the SFC API to go
194      * get the chain information, which we'll eventually
195      * use during policy resolution.
196      *
197      */
198     private class MatchActionDefTask implements Runnable,
199                      FutureCallback<Optional<ActionDefinition>> {
200         private final ActionState state;
201         private final ActionInstance actionInstance;
202         private final ActionInstance originalInstance;
203         private final InstanceIdentifier<ActionDefinition> adIid;
204         private final ActionDefinitionId id;
205
206         public MatchActionDefTask(ActionInstance actionInstance,
207                 ActionInstance originalInstance, ActionState state) {
208             this.actionInstance = actionInstance;
209             this.originalInstance = originalInstance;
210             if (actionInstance != null) {
211                 this.id = actionInstance.getActionDefinitionId();
212             } else {
213                 this.id = null;
214             }
215             this.state = state;
216
217             adIid = InstanceIdentifier.builder(SubjectFeatureDefinitions.class)
218                                       .child(ActionDefinition.class,
219                                              new ActionDefinitionKey(this.id))
220                                       .build();
221
222         }
223
224         /**
225          * Create read transaction with callback to look up
226          * the Action Definition that the Action Instance
227          * references.
228          */
229         @Override
230         public void run() {
231             ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
232             ListenableFuture<Optional<ActionDefinition>> dao =
233                     rot.read(LogicalDatastoreType.OPERATIONAL, adIid);
234             Futures.addCallback(dao, this, executor);
235
236         }
237
238         @Override
239         public void onFailure(Throwable arg0) {
240             LOG.error("Failure reading ActionDefinition {}", id.getValue());
241         }
242
243         /**
244          * An Action Definition exists - now we need to see
245          * if the Action Definition is for a chain action,
246          * and implement the appropriate behavior. If it's
247          * not a chain action, then we can ignore it.
248          *
249          * @param dao
250          */
251         @Override
252         public void onSuccess(Optional<ActionDefinition> dao) {
253             LOG.debug("Found ActionDefinition {}", id.getValue());
254             if (!dao.isPresent()) {
255                 return;
256             }
257
258             ActionDefinition ad = dao.get();
259             if (ad.getId().getValue().equals(ChainActionDefinition.ID.getValue())) {
260                 /*
261                  * We have the state we need:
262                  *  1) it's a "CHAIN" action
263                  *  2) the name is defined in the ActionInstance
264                  */
265                 switch (state) {
266                 case ADD:
267                     /*
268                      * Go get the RSP First Hop
269                      */
270                     getSfcChain();
271                     break;
272                 case CHANGE:
273                     /*
274                      * We only care if the named chain changes
275                      */
276                     changeSfcRsp();
277                     break;
278                 case DELETE:
279                     /*
280                      * If the instance is deleted, we need to remove
281                      * it from our map.
282                      */
283                     deleteSfcRsp();
284                     break;
285                 default:
286                     break;
287                 }
288             }
289         }
290
291         private ParameterValue getChainNameParameter(List<ParameterValue> pvl) {
292             if (pvl == null) {
293                 return null;
294             }
295             for (ParameterValue pv: actionInstance.getParameterValue()) {
296                 if (pv.getName().getValue().equals(SFC_CHAIN_NAME)) {
297                     return pv;
298                 }
299             }
300             return null;
301         }
302
303         private void changeSfcRsp() {
304             ParameterValue newPv =
305                     getChainNameParameter(actionInstance.getParameterValue());
306             ParameterValue origPv =
307                     getChainNameParameter(originalInstance.getParameterValue());
308             if (!newPv.getStringValue().equals(origPv.getStringValue())) {
309                 if (rspMap.containsKey(origPv.getStringValue())) {
310                     /*
311                      * Flow cleanup will happen as part of the
312                      * resolved policy
313                      *
314                      * TODO: can we guarantee that this
315                      *       happens after we remove the RSP?).
316                      */
317                     rspMap.remove(origPv.getStringValue());
318                 }
319                 addSfcRsp();
320             }
321         }
322
323         private void deleteSfcRsp() {
324             ParameterValue pv =
325                     getChainNameParameter(originalInstance.getParameterValue());
326             if (pv == null) {
327                 return;
328             }
329             rspMap.remove(pv.getStringValue());
330         }
331
332         /**
333          * Get the RenderedServicePathFirstHop from SFC
334          *
335          * TODO: what if SFC state isn't available at the time of
336          *       this call, but becomes available later?  Do we want
337          *       or need some sort of notification handler for this?
338          */
339         private void addSfcRsp() {
340             ParameterValue pv =
341                     getChainNameParameter(actionInstance.getParameterValue());
342             if (pv == null) {
343                 return;
344             }
345
346             LOG.trace("Invoking RPC for chain {}", pv.getStringValue());
347             ReadRenderedServicePathFirstHopInputBuilder builder =
348                 new ReadRenderedServicePathFirstHopInputBuilder()
349                        .setName(pv.getStringValue());
350             // TODO: make async
351             Future<RpcResult<ReadRenderedServicePathFirstHopOutput>> result =
352                 sfcProviderRpc.readRenderedServicePathFirstHop(builder.build());
353
354             try {
355                 RpcResult<ReadRenderedServicePathFirstHopOutput> output =
356                         result.get();
357                 if (output.isSuccessful()) {
358                     LOG.trace("RPC for chain {} succeeded!", pv.getStringValue());
359                     RenderedServicePathFirstHop rspFirstHop =
360                         output.getResult().getRenderedServicePathFirstHop();
361                     /*
362                      * We won't retry installation in the map
363                      * because the presumption is it's either
364                      * the same object or contain the same
365                      * state.
366                      */
367                     rspMap.putIfAbsent(pv.getStringValue(), rspFirstHop);
368                 }
369             } catch (Exception e) {
370                 LOG.warn("Failed ReadRenderedServicePathFirstHop RPC: {}", e);
371                 // TODO: proper exception handling
372             }
373         }
374
375         private void getSfcChain() {
376             ParameterValue pv =
377                     getChainNameParameter(actionInstance.getParameterValue());
378             if (pv == null) {
379                 return;
380             }
381
382             LOG.trace("Invoking RPC for chain {}", pv.getStringValue());
383             SfcName chainName=new SfcName(pv.getStringValue());
384             ServiceFunctionChain chain = SfcProviderServiceChainAPI.readServiceFunctionChain(chainName);
385             ServiceFunctionPaths paths = SfcProviderServicePathAPI.readAllServiceFunctionPaths();
386             for(ServiceFunctionPath path: paths.getServiceFunctionPath()) {
387                 if(path.getServiceChainName().equals(chainName)) {
388                     LOG.info("Found path {} for chain {}",path.getName(),path.getServiceChainName());
389                 }
390             }
391         }
392     }
393
394     /**
395      * Return the first hop information for the Rendered Service Path
396      *
397      * @param rspName the Rendered Service Path
398      * @return the first hop information for the Rendered Service Path
399      */
400     public RenderedServicePathFirstHop getRspFirstHop(String rspName) {
401         return rspMap.get(rspName);
402     }
403
404     @Override
405     public void close() throws Exception {
406         if (actionListener != null) {
407             actionListener.close();
408         }
409
410     }
411 }
412