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