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