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