OPNFLWPLUG-1032: Neon-MRI: Bump odlparent, yangtools, mdsal
[openflowplugin.git] / applications / forwardingrules-manager / src / main / java / org / opendaylight / openflowplugin / applications / frm / impl / FlowForwarder.java
1 /**
2  * Copyright (c) 2014, 2017 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 package org.opendaylight.openflowplugin.applications.frm.impl;
9
10 import static org.opendaylight.openflowplugin.applications.frm.util.FrmUtil.buildGroupInstanceIdentifier;
11 import static org.opendaylight.openflowplugin.applications.frm.util.FrmUtil.getActiveBundle;
12 import static org.opendaylight.openflowplugin.applications.frm.util.FrmUtil.getFlowId;
13 import static org.opendaylight.openflowplugin.applications.frm.util.FrmUtil.getNodeIdFromNodeIdentifier;
14 import static org.opendaylight.openflowplugin.applications.frm.util.FrmUtil.isFlowDependentOnGroup;
15 import static org.opendaylight.openflowplugin.applications.frm.util.FrmUtil.isGroupExistsOnDevice;
16
17 import com.google.common.base.Optional;
18 import com.google.common.base.Preconditions;
19 import com.google.common.util.concurrent.FutureCallback;
20 import com.google.common.util.concurrent.Futures;
21 import com.google.common.util.concurrent.ListenableFuture;
22 import com.google.common.util.concurrent.MoreExecutors;
23 import com.google.common.util.concurrent.SettableFuture;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.Future;
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
28 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.infrautils.utils.concurrent.JdkFutures;
31 import org.opendaylight.openflowplugin.applications.frm.ForwardingRulesManager;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.StaleFlow;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.StaleFlowBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.StaleFlowKey;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowTableRef;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInputBuilder;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutput;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowInput;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowInputBuilder;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutput;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.OriginalFlowBuilder;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.UpdatedFlowBuilder;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowRef;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupInput;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupInputBuilder;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupOutput;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupRef;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.extension.onf.rev170124.BundleId;
63 import org.opendaylight.yangtools.concepts.ListenerRegistration;
64 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
65 import org.opendaylight.yangtools.yang.common.RpcError;
66 import org.opendaylight.yangtools.yang.common.RpcResult;
67 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 /**
72  * FlowForwarder It implements
73  * {@link org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener}
74  * for WildCardedPath to {@link Flow} and ForwardingRulesCommiter interface for
75  * methods: add, update and remove {@link Flow} processing for
76  * {@link org.opendaylight.controller.md.sal.binding.api.DataTreeModification}.
77  */
78 public class FlowForwarder extends AbstractListeningCommiter<Flow> {
79
80     private static final Logger LOG = LoggerFactory.getLogger(FlowForwarder.class);
81
82     private static final String GROUP_EXISTS_IN_DEVICE_ERROR = "GROUPEXISTS";
83
84     private ListenerRegistration<FlowForwarder> listenerRegistration;
85     private final BundleFlowForwarder bundleFlowForwarder;
86
87     public FlowForwarder(final ForwardingRulesManager manager, final DataBroker db) {
88         super(manager, db);
89         bundleFlowForwarder = new BundleFlowForwarder(manager);
90     }
91
92     @SuppressWarnings("IllegalCatch")
93     @Override
94     public void registerListener() {
95         final DataTreeIdentifier<Flow> treeId = new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION,
96                 getWildCardPath());
97         try {
98             listenerRegistration = dataBroker.registerDataTreeChangeListener(treeId, FlowForwarder.this);
99         } catch (final Exception e) {
100             LOG.warn("FRM Flow DataTreeChange listener registration fail!");
101             LOG.debug("FRM Flow DataTreeChange listener registration fail ..", e);
102             throw new IllegalStateException("FlowForwarder startup fail! System needs restart.", e);
103         }
104     }
105
106
107     @Override
108     public  void deregisterListener() {
109         close();
110     }
111
112     @Override
113     public void close() {
114         if (listenerRegistration != null) {
115             listenerRegistration.close();
116             listenerRegistration = null;
117         }
118     }
119
120     @Override
121     public void remove(final InstanceIdentifier<Flow> identifier, final Flow removeDataObj,
122             final InstanceIdentifier<FlowCapableNode> nodeIdent) {
123
124         final TableKey tableKey = identifier.firstKeyOf(Table.class);
125         if (tableIdValidationPrecondition(tableKey, removeDataObj)) {
126             BundleId bundleId = getActiveBundle(nodeIdent, provider);
127             if (bundleId != null) {
128                 bundleFlowForwarder.remove(identifier, removeDataObj, nodeIdent, bundleId);
129             } else {
130                 final RemoveFlowInputBuilder builder = new RemoveFlowInputBuilder(removeDataObj);
131                 builder.setFlowRef(new FlowRef(identifier));
132                 builder.setNode(new NodeRef(nodeIdent.firstIdentifierOf(Node.class)));
133                 builder.setFlowTable(new FlowTableRef(nodeIdent.child(Table.class, tableKey)));
134
135                 // This method is called only when a given flow object has been
136                 // removed from datastore. So FRM always needs to set strict flag
137                 // into remove-flow input so that only a flow entry associated with
138                 // a given flow object is removed.
139                 builder.setTransactionUri(new Uri(provider.getNewTransactionId())).setStrict(Boolean.TRUE);
140                 final Future<RpcResult<RemoveFlowOutput>> resultFuture =
141                         provider.getSalFlowService().removeFlow(builder.build());
142                 JdkFutures.addErrorLogging(resultFuture, LOG, "removeFlow");
143             }
144         }
145     }
146
147     // TODO: Pull this into ForwardingRulesCommiter and override it here
148
149     @Override
150     public Future<RpcResult<RemoveFlowOutput>> removeWithResult(final InstanceIdentifier<Flow> identifier,
151             final Flow removeDataObj, final InstanceIdentifier<FlowCapableNode> nodeIdent) {
152
153         Future<RpcResult<RemoveFlowOutput>> resultFuture = SettableFuture.create();
154         final TableKey tableKey = identifier.firstKeyOf(Table.class);
155         if (tableIdValidationPrecondition(tableKey, removeDataObj)) {
156             final RemoveFlowInputBuilder builder = new RemoveFlowInputBuilder(removeDataObj);
157             builder.setFlowRef(new FlowRef(identifier));
158             builder.setNode(new NodeRef(nodeIdent.firstIdentifierOf(Node.class)));
159             builder.setFlowTable(new FlowTableRef(nodeIdent.child(Table.class, tableKey)));
160
161             // This method is called only when a given flow object has been
162             // removed from datastore. So FRM always needs to set strict flag
163             // into remove-flow input so that only a flow entry associated with
164             // a given flow object is removed.
165             builder.setTransactionUri(new Uri(provider.getNewTransactionId())).setStrict(Boolean.TRUE);
166             resultFuture = provider.getSalFlowService().removeFlow(builder.build());
167         }
168
169         return resultFuture;
170     }
171
172     @Override
173     public void update(final InstanceIdentifier<Flow> identifier, final Flow original, final Flow update,
174             final InstanceIdentifier<FlowCapableNode> nodeIdent) {
175
176         final TableKey tableKey = identifier.firstKeyOf(Table.class);
177         if (tableIdValidationPrecondition(tableKey, update)) {
178             BundleId bundleId = getActiveBundle(nodeIdent, provider);
179             if (bundleId != null) {
180                 bundleFlowForwarder.update(identifier, original, update, nodeIdent, bundleId);
181             } else {
182                 final NodeId nodeId = getNodeIdFromNodeIdentifier(nodeIdent);
183                 nodeConfigurator.enqueueJob(nodeId.getValue(), () -> {
184                     final UpdateFlowInputBuilder builder = new UpdateFlowInputBuilder();
185                     builder.setNode(new NodeRef(nodeIdent.firstIdentifierOf(Node.class)));
186                     builder.setFlowRef(new FlowRef(identifier));
187                     builder.setTransactionUri(new Uri(provider.getNewTransactionId()));
188
189                     // This method is called only when a given flow object in datastore
190                     // has been updated. So FRM always needs to set strict flag into
191                     // update-flow input so that only a flow entry associated with
192                     // a given flow object is updated.
193                     builder.setUpdatedFlow(new UpdatedFlowBuilder(update).setStrict(Boolean.TRUE).build());
194                     builder.setOriginalFlow(new OriginalFlowBuilder(original).setStrict(Boolean.TRUE).build());
195
196                     Long groupId = isFlowDependentOnGroup(update);
197                     ListenableFuture<RpcResult<UpdateFlowOutput>> future = Futures.immediateFuture(null);
198                     if (groupId != null) {
199                         LOG.trace("The flow {} is dependent on group {}. Checking if the group is already present",
200                                 getFlowId(new FlowRef(identifier)), groupId);
201                         if (isGroupExistsOnDevice(nodeIdent, groupId, provider)) {
202                             LOG.trace("The dependent group {} is already programmed. Updating the flow {}", groupId,
203                                     getFlowId(new FlowRef(identifier)));
204                             future = provider.getSalFlowService().updateFlow(builder.build());
205                             JdkFutures.addErrorLogging(future, LOG, "updateFlow");
206                         } else {
207                             LOG.trace("The dependent group {} isn't programmed yet. Pushing the group", groupId);
208                             ListenableFuture<RpcResult<AddGroupOutput>> groupFuture = pushDependentGroup(nodeIdent,
209                                     groupId);
210                             Futures.addCallback(groupFuture,
211                                     new UpdateFlowCallBack(builder.build(), nodeId, future, groupId),
212                                     MoreExecutors.directExecutor());
213                         }
214                     } else {
215                         LOG.trace("The flow {} is not dependent on any group. Updating the flow",
216                                 getFlowId(new FlowRef(identifier)));
217                         future = provider.getSalFlowService().updateFlow(builder.build());
218                         JdkFutures.addErrorLogging(future, LOG, "updateFlow");
219                     }
220                     return future;
221                 });
222             }
223         }
224     }
225
226     @Override
227     public Future<? extends RpcResult<?>> add(final InstanceIdentifier<Flow> identifier, final Flow addDataObj,
228             final InstanceIdentifier<FlowCapableNode> nodeIdent) {
229
230         final TableKey tableKey = identifier.firstKeyOf(Table.class);
231         if (tableIdValidationPrecondition(tableKey, addDataObj)) {
232             BundleId bundleId = getActiveBundle(nodeIdent, provider);
233             if (bundleId != null) {
234                 return bundleFlowForwarder.add(identifier, addDataObj, nodeIdent, bundleId);
235             } else {
236                 final NodeId nodeId = getNodeIdFromNodeIdentifier(nodeIdent);
237                 nodeConfigurator.enqueueJob(nodeId.getValue(), () -> {
238                     final AddFlowInputBuilder builder = new AddFlowInputBuilder(addDataObj);
239
240                     builder.setNode(new NodeRef(nodeIdent.firstIdentifierOf(Node.class)));
241                     builder.setFlowRef(new FlowRef(identifier));
242                     builder.setFlowTable(new FlowTableRef(nodeIdent.child(Table.class, tableKey)));
243                     builder.setTransactionUri(new Uri(provider.getNewTransactionId()));
244                     Long groupId = isFlowDependentOnGroup(addDataObj);
245                     ListenableFuture<RpcResult<AddFlowOutput>> future = SettableFuture.create();
246                     if (groupId != null) {
247                         LOG.trace("The flow {} is dependent on group {}. Checking if the group is already present",
248                                 getFlowId(new FlowRef(identifier)), groupId);
249                         if (isGroupExistsOnDevice(nodeIdent, groupId, provider)) {
250                             LOG.trace("The dependent group {} is already programmed. Adding the flow {}", groupId,
251                                     getFlowId(new FlowRef(identifier)));
252                             future = provider.getSalFlowService().addFlow(builder.build());
253                         } else {
254                             LOG.trace("The dependent group {} isn't programmed yet. Pushing the group", groupId);
255                             ListenableFuture<RpcResult<AddGroupOutput>> groupFuture = pushDependentGroup(nodeIdent,
256                                     groupId);
257                             Futures.addCallback(groupFuture, new AddFlowCallBack(builder.build(), nodeId, future,
258                                             groupId),
259                                     MoreExecutors.directExecutor());
260                         }
261                     } else {
262                         LOG.trace("The flow {} is not dependent on any group. Adding the flow",
263                                 getFlowId(new FlowRef(identifier)));
264                         future = provider.getSalFlowService().addFlow(builder.build());
265                     }
266                     return future;
267                 });
268             }
269         }
270         return Futures.immediateFuture(null);
271     }
272
273     @Override
274     public void createStaleMarkEntity(InstanceIdentifier<Flow> identifier, Flow del,
275             InstanceIdentifier<FlowCapableNode> nodeIdent) {
276         LOG.debug("Creating Stale-Mark entry for the switch {} for flow {} ", nodeIdent.toString(), del.toString());
277         StaleFlow staleFlow = makeStaleFlow(identifier, del, nodeIdent);
278         persistStaleFlow(staleFlow, nodeIdent);
279     }
280
281     @Override
282     protected InstanceIdentifier<Flow> getWildCardPath() {
283         return InstanceIdentifier.create(Nodes.class).child(Node.class).augmentation(FlowCapableNode.class)
284                 .child(Table.class).child(Flow.class);
285     }
286
287     private static boolean tableIdValidationPrecondition(final TableKey tableKey, final Flow flow) {
288         Preconditions.checkNotNull(tableKey, "TableKey can not be null or empty!");
289         Preconditions.checkNotNull(flow, "Flow can not be null or empty!");
290         if (!tableKey.getId().equals(flow.getTableId())) {
291             LOG.warn("TableID in URI tableId={} and in palyload tableId={} is not same.", flow.getTableId(),
292                     tableKey.getId());
293             return false;
294         }
295         return true;
296     }
297
298     private StaleFlow makeStaleFlow(InstanceIdentifier<Flow> identifier, Flow del,
299             InstanceIdentifier<FlowCapableNode> nodeIdent) {
300         StaleFlowBuilder staleFlowBuilder = new StaleFlowBuilder(del);
301         return staleFlowBuilder.setId(del.getId()).build();
302     }
303
304     private void persistStaleFlow(StaleFlow staleFlow, InstanceIdentifier<FlowCapableNode> nodeIdent) {
305         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
306         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, getStaleFlowInstanceIdentifier(staleFlow, nodeIdent),
307                 staleFlow, false);
308
309         ListenableFuture<Void> submitFuture = writeTransaction.submit();
310         handleStaleFlowResultFuture(submitFuture);
311     }
312
313     private void handleStaleFlowResultFuture(ListenableFuture<Void> submitFuture) {
314         Futures.addCallback(submitFuture, new FutureCallback<Void>() {
315             @Override
316             public void onSuccess(Void result) {
317                 LOG.debug("Stale Flow creation success");
318             }
319
320             @Override
321             public void onFailure(Throwable throwable) {
322                 LOG.error("Stale Flow creation failed {}", throwable);
323             }
324         }, MoreExecutors.directExecutor());
325
326     }
327
328     private InstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight
329         .flow.inventory.rev130819.tables.table.StaleFlow> getStaleFlowInstanceIdentifier(
330             StaleFlow staleFlow, InstanceIdentifier<FlowCapableNode> nodeIdent) {
331         return nodeIdent.child(Table.class, new TableKey(staleFlow.getTableId())).child(
332                 org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.StaleFlow.class,
333                 new StaleFlowKey(new FlowId(staleFlow.getId())));
334     }
335
336     private ListenableFuture<RpcResult<AddGroupOutput>> pushDependentGroup(
337             final InstanceIdentifier<FlowCapableNode> nodeIdent, final Long groupId) {
338
339         //TODO This read to the DS might have a performance impact.
340         //if the dependent group is not installed than we should just cache the parent group,
341         //till we receive the dependent group DTCN and then push it.
342
343         InstanceIdentifier<Group> groupIdent = buildGroupInstanceIdentifier(nodeIdent, groupId);
344         ListenableFuture<RpcResult<AddGroupOutput>> resultFuture;
345         LOG.info("Reading the group from config inventory: {}", groupId);
346         try {
347             Optional<Group> group;
348             group = provider.getReadTranaction().read(LogicalDatastoreType.CONFIGURATION, groupIdent).get();
349             if (group.isPresent()) {
350                 final AddGroupInputBuilder builder = new AddGroupInputBuilder(group.get());
351                 builder.setNode(new NodeRef(nodeIdent.firstIdentifierOf(Node.class)));
352                 builder.setGroupRef(new GroupRef(nodeIdent));
353                 builder.setTransactionUri(new Uri(provider.getNewTransactionId()));
354                 AddGroupInput addGroupInput = builder.build();
355                 resultFuture = this.provider.getSalGroupService().addGroup(addGroupInput);
356             } else {
357                 resultFuture = Futures.immediateFuture(RpcResultBuilder.<AddGroupOutput>failed()
358                         .withError(RpcError.ErrorType.APPLICATION,
359                                 "Group " + groupId + " not present in the config inventory").build());
360             }
361         } catch (InterruptedException | ExecutionException e) {
362             LOG.error("Error while reading group from config datastore for the group ID {}", groupId, e);
363             resultFuture = Futures.immediateFuture(RpcResultBuilder.<AddGroupOutput>failed()
364                     .withError(RpcError.ErrorType.APPLICATION,
365                             "Error while reading group " + groupId + " from inventory").build());
366         }
367         return resultFuture;
368     }
369
370     private final class AddFlowCallBack implements FutureCallback<RpcResult<AddGroupOutput>> {
371         private final AddFlowInput addFlowInput;
372         private final NodeId nodeId;
373         private ListenableFuture<RpcResult<AddFlowOutput>> future;
374         private final Long groupId;
375
376         private AddFlowCallBack(final AddFlowInput addFlowInput, final NodeId nodeId,
377                 ListenableFuture<RpcResult<AddFlowOutput>> future, Long groupId) {
378             this.addFlowInput = addFlowInput;
379             this.nodeId = nodeId;
380             this.future = future;
381             this.groupId = groupId;
382         }
383
384         @Override
385         public void onSuccess(RpcResult<AddGroupOutput> rpcResult) {
386             if (rpcResult.isSuccessful()) {
387                 provider.getDevicesGroupRegistry().storeGroup(nodeId, groupId);
388                 future = provider.getSalFlowService().addFlow(addFlowInput);
389                 LOG.debug("Flow add with id {} finished without error for node {}",
390                         getFlowId(addFlowInput.getFlowRef()), nodeId);
391             } else {
392                 if (rpcResult.getErrors().size() == 1
393                         && rpcResult.getErrors().iterator().next().getMessage()
394                         .contains(GROUP_EXISTS_IN_DEVICE_ERROR)) {
395                     provider.getDevicesGroupRegistry().storeGroup(nodeId, groupId);
396                     future = provider.getSalFlowService().addFlow(addFlowInput);
397                     LOG.debug("Group {} already programmed in the device. Adding the flow {}", groupId,
398                             getFlowId(addFlowInput.getFlowRef()));
399                 } else {
400                     LOG.error("Flow add with id {} failed for node {} with error {}",
401                             getFlowId(addFlowInput.getFlowRef()), nodeId, rpcResult.getErrors().toString());
402                     future = Futures.immediateFuture(null);
403                 }
404             }
405         }
406
407         @Override
408         public void onFailure(Throwable throwable) {
409             LOG.error("Service call for adding flow with id {} failed for node {} with error {}",
410                     getFlowId(addFlowInput.getFlowRef()), nodeId, throwable.getCause());
411             Futures.immediateFailedFuture(null);
412         }
413     }
414
415     private final class UpdateFlowCallBack implements FutureCallback<RpcResult<AddGroupOutput>> {
416         private final UpdateFlowInput updateFlowInput;
417         private final NodeId nodeId;
418         private ListenableFuture<RpcResult<UpdateFlowOutput>> future;
419         private final Long groupId;
420
421         private UpdateFlowCallBack(final UpdateFlowInput updateFlowInput, final NodeId nodeId,
422                 ListenableFuture<RpcResult<UpdateFlowOutput>> future, Long groupId) {
423             this.updateFlowInput = updateFlowInput;
424             this.nodeId = nodeId;
425             this.future = future;
426             this.groupId = groupId;
427         }
428
429         @Override
430         public void onSuccess(RpcResult<AddGroupOutput> rpcResult) {
431             if (rpcResult.isSuccessful()) {
432                 provider.getDevicesGroupRegistry().storeGroup(nodeId, groupId);
433                 future = provider.getSalFlowService().updateFlow(updateFlowInput);
434                 LOG.debug("Flow update with id {} finished without error for node {}",
435                         getFlowId(updateFlowInput.getFlowRef()), nodeId);
436             } else {
437                 if (rpcResult.getErrors().size() == 1
438                         && rpcResult.getErrors().iterator().next().getMessage()
439                         .contains(GROUP_EXISTS_IN_DEVICE_ERROR)) {
440                     provider.getDevicesGroupRegistry().storeGroup(nodeId, groupId);
441                     future = provider.getSalFlowService().updateFlow(updateFlowInput);
442                     LOG.debug("Group {} already programmed in the device. Updating the flow {}", groupId,
443                             getFlowId(updateFlowInput.getFlowRef()));
444                 } else {
445                     LOG.error("Flow update with id {} failed for node {} with error {}",
446                             getFlowId(updateFlowInput.getFlowRef()), nodeId, rpcResult.getErrors().toString());
447                     future = Futures.immediateFuture(null);
448                 }
449             }
450         }
451
452         @Override
453         public void onFailure(Throwable throwable) {
454             LOG.error("Service call for updating flow with id {} failed for node {} with error {}",
455                     getFlowId(updateFlowInput.getFlowRef()), nodeId, throwable);
456             future = Futures.immediateFailedFuture(null);
457         }
458     }
459 }