b394db5e4f329a7733652771e32f3ee776744e1c
[onosfw.git] /
1 /*
2  * Copyright 2015 Open Networking Laboratory
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.onosproject.segmentrouting.grouphandler;
17
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static org.slf4j.LoggerFactory.getLogger;
20
21 import java.net.URI;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30
31 import org.onlab.packet.Ip4Prefix;
32 import org.onlab.packet.IpPrefix;
33 import org.onlab.packet.MacAddress;
34 import org.onlab.packet.MplsLabel;
35 import org.onlab.util.KryoNamespace;
36 import org.onosproject.core.ApplicationId;
37 import org.onosproject.net.DeviceId;
38 import org.onosproject.net.Link;
39 import org.onosproject.net.PortNumber;
40 import org.onosproject.net.flow.DefaultTrafficTreatment;
41 import org.onosproject.net.flow.TrafficTreatment;
42 import org.onosproject.net.flowobjective.DefaultNextObjective;
43 import org.onosproject.net.flowobjective.FlowObjectiveService;
44 import org.onosproject.net.flowobjective.NextObjective;
45 import org.onosproject.net.flowobjective.Objective;
46 import org.onosproject.net.flowobjective.ObjectiveContext;
47 import org.onosproject.net.flowobjective.ObjectiveError;
48 import org.onosproject.net.group.DefaultGroupKey;
49 import org.onosproject.net.group.GroupKey;
50 import org.onosproject.net.link.LinkService;
51 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
52 import org.onosproject.segmentrouting.config.DeviceProperties;
53 import org.onosproject.store.service.EventuallyConsistentMap;
54 import org.slf4j.Logger;
55
56 /**
57  * Default ECMP group handler creation module. This component creates a set of
58  * ECMP groups for every neighbor that this device is connected to based on
59  * whether the current device is an edge device or a transit device.
60  */
61 public class DefaultGroupHandler {
62     protected static final Logger log = getLogger(DefaultGroupHandler.class);
63
64     protected final DeviceId deviceId;
65     protected final ApplicationId appId;
66     protected final DeviceProperties deviceConfig;
67     protected final List<Integer> allSegmentIds;
68     protected int nodeSegmentId = -1;
69     protected boolean isEdgeRouter = false;
70     protected MacAddress nodeMacAddr = null;
71     protected LinkService linkService;
72     protected FlowObjectiveService flowObjectiveService;
73
74     protected HashMap<DeviceId, Set<PortNumber>> devicePortMap = new HashMap<>();
75     protected HashMap<PortNumber, DeviceId> portDeviceMap = new HashMap<>();
76     //protected HashMap<NeighborSet, Integer> deviceNextObjectiveIds =
77     //        new HashMap<NeighborSet, Integer>();
78     protected EventuallyConsistentMap<
79         NeighborSetNextObjectiveStoreKey, Integer> nsNextObjStore = null;
80     protected EventuallyConsistentMap<
81             SubnetNextObjectiveStoreKey, Integer> subnetNextObjStore = null;
82
83     protected KryoNamespace.Builder kryo = new KryoNamespace.Builder()
84             .register(URI.class).register(HashSet.class)
85             .register(DeviceId.class).register(PortNumber.class)
86             .register(NeighborSet.class).register(PolicyGroupIdentifier.class)
87             .register(PolicyGroupParams.class)
88             .register(GroupBucketIdentifier.class)
89             .register(GroupBucketIdentifier.BucketOutputType.class);
90
91     protected DefaultGroupHandler(DeviceId deviceId, ApplicationId appId,
92                                   DeviceProperties config,
93                                   LinkService linkService,
94                                   FlowObjectiveService flowObjService,
95                                   EventuallyConsistentMap<
96                                           NeighborSetNextObjectiveStoreKey,
97                                           Integer> nsNextObjStore,
98                                   EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
99                                           Integer> subnetNextObjStore) {
100         this.deviceId = checkNotNull(deviceId);
101         this.appId = checkNotNull(appId);
102         this.deviceConfig = checkNotNull(config);
103         this.linkService = checkNotNull(linkService);
104         this.allSegmentIds = checkNotNull(config.getAllDeviceSegmentIds());
105         try {
106             this.nodeSegmentId = config.getSegmentId(deviceId);
107             this.isEdgeRouter = config.isEdgeDevice(deviceId);
108             this.nodeMacAddr = checkNotNull(config.getDeviceMac(deviceId));
109         } catch (DeviceConfigNotFoundException e) {
110             log.warn(e.getMessage()
111                     + " Skipping value assignment in DefaultGroupHandler");
112         }
113         this.flowObjectiveService = flowObjService;
114         this.nsNextObjStore = nsNextObjStore;
115         this.subnetNextObjStore = subnetNextObjStore;
116
117         populateNeighborMaps();
118     }
119
120     /**
121      * Creates a group handler object based on the type of device. If device is
122      * of edge type it returns edge group handler, else it returns transit group
123      * handler.
124      *
125      * @param deviceId device identifier
126      * @param appId application identifier
127      * @param config interface to retrieve the device properties
128      * @param linkService link service object
129      * @param flowObjService flow objective service object
130      * @param nsNextObjStore NeighborSet next objective store map
131      * @param subnetNextObjStore subnet next objective store map
132      * @throws DeviceConfigNotFoundException if the device configuration is not found
133      * @return default group handler type
134      */
135     public static DefaultGroupHandler createGroupHandler(DeviceId deviceId,
136                                                          ApplicationId appId,
137                                                          DeviceProperties config,
138                                                          LinkService linkService,
139                                                          FlowObjectiveService flowObjService,
140                                                          EventuallyConsistentMap<
141                                                                  NeighborSetNextObjectiveStoreKey,
142                                                                  Integer> nsNextObjStore,
143                                                          EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
144                                                                  Integer> subnetNextObjStore)
145                                                          throws DeviceConfigNotFoundException {
146         // handle possible exception in the caller
147         if (config.isEdgeDevice(deviceId)) {
148             return new DefaultEdgeGroupHandler(deviceId, appId, config,
149                                                linkService,
150                                                flowObjService,
151                                                nsNextObjStore,
152                                                subnetNextObjStore);
153         } else {
154             return new DefaultTransitGroupHandler(deviceId, appId, config,
155                                                   linkService,
156                                                   flowObjService,
157                                                   nsNextObjStore,
158                                                   subnetNextObjStore);
159         }
160     }
161
162     /**
163      * Creates the auto created groups for this device based on the current
164      * snapshot of the topology.
165      */
166     // Empty implementations to be overridden by derived classes
167     public void createGroups() {
168     }
169
170     /**
171      * Performs group creation or update procedures when a new link is
172      * discovered on this device.
173      *
174      * @param newLink new neighbor link
175      */
176     public void linkUp(Link newLink) {
177
178         if (newLink.type() != Link.Type.DIRECT) {
179             log.warn("linkUp: unknown link type");
180             return;
181         }
182
183         if (!newLink.src().deviceId().equals(deviceId)) {
184             log.warn("linkUp: deviceId{} doesn't match with link src{}",
185                      deviceId, newLink.src().deviceId());
186             return;
187         }
188
189         MacAddress dstMac;
190         try {
191             dstMac = deviceConfig.getDeviceMac(newLink.dst().deviceId());
192         } catch (DeviceConfigNotFoundException e) {
193             log.warn(e.getMessage() + " Aborting linkUp.");
194             return;
195         }
196
197         log.debug("Device {} linkUp at local port {} to neighbor {}", deviceId,
198                   newLink.src().port(), newLink.dst().deviceId());
199         addNeighborAtPort(newLink.dst().deviceId(),
200                           newLink.src().port());
201         /*if (devicePortMap.get(newLink.dst().deviceId()) == null) {
202             // New Neighbor
203             newNeighbor(newLink);
204         } else {
205             // Old Neighbor
206             newPortToExistingNeighbor(newLink);
207         }*/
208         Set<NeighborSet> nsSet = nsNextObjStore.keySet()
209                 .stream()
210                 .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
211                 .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
212                 .filter((ns) -> (ns.getDeviceIds()
213                         .contains(newLink.dst().deviceId())))
214                 .collect(Collectors.toSet());
215         log.trace("linkUp: nsNextObjStore contents for device {}:",
216                 deviceId,
217                 nsSet);
218         for (NeighborSet ns : nsSet) {
219             // Create the new bucket to be updated
220             TrafficTreatment.Builder tBuilder =
221                     DefaultTrafficTreatment.builder();
222             tBuilder.setOutput(newLink.src().port())
223                     .setEthDst(dstMac)
224                     .setEthSrc(nodeMacAddr);
225             if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
226                 tBuilder.pushMpls()
227                         .copyTtlOut()
228                         .setMpls(MplsLabel.mplsLabel(ns.getEdgeLabel()));
229             }
230
231             Integer nextId = nsNextObjStore.
232                     get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
233             if (nextId != null) {
234                 NextObjective.Builder nextObjBuilder = DefaultNextObjective
235                         .builder().withId(nextId)
236                         .withType(NextObjective.Type.HASHED).fromApp(appId);
237
238                 nextObjBuilder.addTreatment(tBuilder.build());
239
240                 log.debug("linkUp in device {}: Adding Bucket "
241                         + "with Port {} to next object id {}",
242                         deviceId,
243                         newLink.src().port(),
244                         nextId);
245                 NextObjective nextObjective = nextObjBuilder.
246                         add(new SRNextObjectiveContext(deviceId));
247                 flowObjectiveService.next(deviceId, nextObjective);
248             }
249         }
250     }
251
252     /**
253      * Performs group recovery procedures when a port goes down on this device.
254      *
255      * @param port port number that has gone down
256      */
257     public void portDown(PortNumber port) {
258         if (portDeviceMap.get(port) == null) {
259             log.warn("portDown: unknown port");
260             return;
261         }
262
263         MacAddress dstMac;
264         try {
265             dstMac = deviceConfig.getDeviceMac(portDeviceMap.get(port));
266         } catch (DeviceConfigNotFoundException e) {
267             log.warn(e.getMessage() + " Aborting portDown.");
268             return;
269         }
270
271         log.debug("Device {} portDown {} to neighbor {}", deviceId, port,
272                   portDeviceMap.get(port));
273         /*Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent(portDeviceMap
274                                                                                 .get(port),
275                                                                         devicePortMap
276                                                                                 .keySet());*/
277         Set<NeighborSet> nsSet = nsNextObjStore.keySet()
278                 .stream()
279                 .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
280                 .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
281                 .filter((ns) -> (ns.getDeviceIds()
282                         .contains(portDeviceMap.get(port))))
283                 .collect(Collectors.toSet());
284         log.trace("portDown: nsNextObjStore contents for device {}:",
285                   deviceId,
286                   nsSet);
287         for (NeighborSet ns : nsSet) {
288             // Create the bucket to be removed
289             TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
290                     .builder();
291             tBuilder.setOutput(port)
292                     .setEthDst(dstMac)
293                     .setEthSrc(nodeMacAddr);
294             if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
295                 tBuilder.pushMpls()
296                         .copyTtlOut()
297                         .setMpls(MplsLabel.mplsLabel(ns.getEdgeLabel()));
298             }
299
300             Integer nextId = nsNextObjStore.
301                     get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
302             if (nextId != null) {
303                 NextObjective.Builder nextObjBuilder = DefaultNextObjective
304                         .builder().withType(NextObjective.Type.SIMPLE).withId(nextId).fromApp(appId);
305
306                 nextObjBuilder.addTreatment(tBuilder.build());
307
308                 log.debug("portDown in device {}: Removing Bucket "
309                         + "with Port {} to next object id {}",
310                         deviceId,
311                         port,
312                         nextId);
313                 NextObjective nextObjective = nextObjBuilder.
314                         remove(new SRNextObjectiveContext(deviceId));
315
316                 flowObjectiveService.next(deviceId, nextObjective);
317             }
318
319         }
320
321         devicePortMap.get(portDeviceMap.get(port)).remove(port);
322         portDeviceMap.remove(port);
323     }
324
325     /**
326      * Returns the next objective associated with the neighborset.
327      * If there is no next objective for this neighborset, this API
328      * would create a next objective and return.
329      *
330      * @param ns neighborset
331      * @return int if found or -1
332      */
333     public int getNextObjectiveId(NeighborSet ns) {
334         Integer nextId = nsNextObjStore.
335                 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
336         if (nextId == null) {
337             log.trace("getNextObjectiveId in device{}: Next objective id "
338                     + "not found for {} and creating", deviceId, ns);
339             log.trace("getNextObjectiveId: nsNextObjStore contents for device {}: {}",
340                       deviceId,
341                       nsNextObjStore.entrySet()
342                       .stream()
343                       .filter((nsStoreEntry) ->
344                       (nsStoreEntry.getKey().deviceId().equals(deviceId)))
345                       .collect(Collectors.toList()));
346             createGroupsFromNeighborsets(Collections.singleton(ns));
347             nextId = nsNextObjStore.
348                     get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
349             if (nextId == null) {
350                 log.warn("getNextObjectiveId: unable to create next objective");
351                 return -1;
352             } else {
353                 log.debug("getNextObjectiveId in device{}: Next objective id {} "
354                     + "created for {}", deviceId, nextId, ns);
355             }
356         } else {
357             log.trace("getNextObjectiveId in device{}: Next objective id {} "
358                     + "found for {}", deviceId, nextId, ns);
359         }
360         return nextId;
361     }
362
363     /**
364      * Returns the next objective associated with the subnet.
365      * If there is no next objective for this subnet, this API
366      * would create a next objective and return.
367      *
368      * @param prefix subnet information
369      * @return int if found or -1
370      */
371     public int getSubnetNextObjectiveId(IpPrefix prefix) {
372         Integer nextId = subnetNextObjStore.
373                 get(new SubnetNextObjectiveStoreKey(deviceId, prefix));
374
375         return (nextId != null) ? nextId : -1;
376     }
377
378     /**
379      * Checks if the next objective ID (group) for the neighbor set exists or not.
380      *
381      * @param ns neighbor set to check
382      * @return true if it exists, false otherwise
383      */
384     public boolean hasNextObjectiveId(NeighborSet ns) {
385         Integer nextId = nsNextObjStore.
386                 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
387         if (nextId == null) {
388             return false;
389         }
390
391         return true;
392     }
393
394     // Empty implementation
395     protected void newNeighbor(Link newLink) {
396     }
397
398     // Empty implementation
399     protected void newPortToExistingNeighbor(Link newLink) {
400     }
401
402     // Empty implementation
403     protected Set<NeighborSet>
404         computeImpactedNeighborsetForPortEvent(DeviceId impactedNeighbor,
405                                                Set<DeviceId> updatedNeighbors) {
406         return null;
407     }
408
409     private void populateNeighborMaps() {
410         Set<Link> outgoingLinks = linkService.getDeviceEgressLinks(deviceId);
411         for (Link link : outgoingLinks) {
412             if (link.type() != Link.Type.DIRECT) {
413                 continue;
414             }
415             addNeighborAtPort(link.dst().deviceId(), link.src().port());
416         }
417     }
418
419     protected void addNeighborAtPort(DeviceId neighborId,
420                                      PortNumber portToNeighbor) {
421         // Update DeviceToPort database
422         log.debug("Device {} addNeighborAtPort: neighbor {} at port {}",
423                   deviceId, neighborId, portToNeighbor);
424         if (devicePortMap.get(neighborId) != null) {
425             devicePortMap.get(neighborId).add(portToNeighbor);
426         } else {
427             Set<PortNumber> ports = new HashSet<>();
428             ports.add(portToNeighbor);
429             devicePortMap.put(neighborId, ports);
430         }
431
432         // Update portToDevice database
433         if (portDeviceMap.get(portToNeighbor) == null) {
434             portDeviceMap.put(portToNeighbor, neighborId);
435         }
436     }
437
438     protected Set<Set<DeviceId>> getPowerSetOfNeighbors(Set<DeviceId> neighbors) {
439         List<DeviceId> list = new ArrayList<>(neighbors);
440         Set<Set<DeviceId>> sets = new HashSet<>();
441         // get the number of elements in the neighbors
442         int elements = list.size();
443         // the number of members of a power set is 2^n
444         // including the empty set
445         int powerElements = (1 << elements);
446
447         // run a binary counter for the number of power elements
448         // NOTE: Exclude empty set
449         for (long i = 1; i < powerElements; i++) {
450             Set<DeviceId> neighborSubSet = new HashSet<>();
451             for (int j = 0; j < elements; j++) {
452                 if ((i >> j) % 2 == 1) {
453                     neighborSubSet.add(list.get(j));
454                 }
455             }
456             sets.add(neighborSubSet);
457         }
458         return sets;
459     }
460
461     private boolean isSegmentIdSameAsNodeSegmentId(DeviceId deviceId, int sId) {
462         int segmentId;
463         try {
464             segmentId = deviceConfig.getSegmentId(deviceId);
465         } catch (DeviceConfigNotFoundException e) {
466             log.warn(e.getMessage() + " Aborting isSegmentIdSameAsNodeSegmentId.");
467             return false;
468         }
469
470         return segmentId == sId;
471     }
472
473     protected List<Integer> getSegmentIdsTobePairedWithNeighborSet(Set<DeviceId> neighbors) {
474
475         List<Integer> nsSegmentIds = new ArrayList<>();
476
477         // Always pair up with no edge label
478         // If (neighbors.size() == 1) {
479         nsSegmentIds.add(-1);
480         // }
481
482         // Filter out SegmentIds matching with the
483         // nodes in the combo
484         for (Integer sId : allSegmentIds) {
485             if (sId.equals(nodeSegmentId)) {
486                 continue;
487             }
488             boolean filterOut = false;
489             // Check if the edge label being set is of
490             // any node in the Neighbor set
491             for (DeviceId deviceId : neighbors) {
492                 if (isSegmentIdSameAsNodeSegmentId(deviceId, sId)) {
493                     filterOut = true;
494                     break;
495                 }
496             }
497             if (!filterOut) {
498                 nsSegmentIds.add(sId);
499             }
500         }
501         return nsSegmentIds;
502     }
503
504     /**
505      * Creates Groups from a set of NeighborSet given.
506      *
507      * @param nsSet a set of NeighborSet
508      */
509     public void createGroupsFromNeighborsets(Set<NeighborSet> nsSet) {
510         for (NeighborSet ns : nsSet) {
511             int nextId = flowObjectiveService.allocateNextId();
512             NextObjective.Builder nextObjBuilder = DefaultNextObjective
513                     .builder().withId(nextId)
514                     .withType(NextObjective.Type.HASHED).fromApp(appId);
515             for (DeviceId d : ns.getDeviceIds()) {
516                 if (devicePortMap.get(d) == null) {
517                     log.warn("Device {} is not in the port map yet", d);
518                     return;
519                 } else if (devicePortMap.get(d).size() == 0) {
520                     log.warn("There are no ports for "
521                             + "the Device {} in the port map yet", d);
522                     return;
523                 }
524
525                 MacAddress deviceMac;
526                 try {
527                     deviceMac = deviceConfig.getDeviceMac(d);
528                 } catch (DeviceConfigNotFoundException e) {
529                     log.warn(e.getMessage() + " Aborting createGroupsFromNeighborsets.");
530                     return;
531                 }
532
533                 for (PortNumber sp : devicePortMap.get(d)) {
534                     TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
535                             .builder();
536                     tBuilder.setOutput(sp)
537                             .setEthDst(deviceMac)
538                             .setEthSrc(nodeMacAddr);
539                     if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
540                         tBuilder.pushMpls()
541                                 .copyTtlOut()
542                                 .setMpls(MplsLabel.mplsLabel(ns.getEdgeLabel()));
543                     }
544                     nextObjBuilder.addTreatment(tBuilder.build());
545                 }
546             }
547
548             NextObjective nextObj = nextObjBuilder.
549                     add(new SRNextObjectiveContext(deviceId));
550             flowObjectiveService.next(deviceId, nextObj);
551             log.debug("createGroupsFromNeighborsets: Submited "
552                             + "next objective {} in device {}",
553                     nextId, deviceId);
554             nsNextObjStore.put(new NeighborSetNextObjectiveStoreKey(deviceId, ns),
555                                nextId);
556         }
557     }
558
559     public void createGroupsFromSubnetConfig() {
560         Map<Ip4Prefix, List<PortNumber>> subnetPortMap =
561                 this.deviceConfig.getSubnetPortsMap(this.deviceId);
562
563         // Construct a broadcast group for each subnet
564         subnetPortMap.forEach((subnet, ports) -> {
565             SubnetNextObjectiveStoreKey key =
566                     new SubnetNextObjectiveStoreKey(deviceId, subnet);
567
568             if (subnetNextObjStore.containsKey(key)) {
569                 log.debug("Broadcast group for device {} and subnet {} exists",
570                           deviceId, subnet);
571                 return;
572             }
573
574             int nextId = flowObjectiveService.allocateNextId();
575
576             NextObjective.Builder nextObjBuilder = DefaultNextObjective
577                     .builder().withId(nextId)
578                     .withType(NextObjective.Type.BROADCAST).fromApp(appId);
579
580             ports.forEach(port -> {
581                 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
582                 tBuilder.popVlan();
583                 tBuilder.setOutput(port);
584                 nextObjBuilder.addTreatment(tBuilder.build());
585             });
586
587             NextObjective nextObj = nextObjBuilder.add();
588             flowObjectiveService.next(deviceId, nextObj);
589             log.debug("createGroupFromSubnetConfig: Submited "
590                               + "next objective {} in device {}",
591                       nextId, deviceId);
592
593             subnetNextObjStore.put(key, nextId);
594         });
595     }
596
597     public GroupKey getGroupKey(Object obj) {
598         return new DefaultGroupKey(kryo.build().serialize(obj));
599     }
600
601     /**
602      * Removes groups for the next objective ID given.
603      *
604      * @param objectiveId next objective ID to remove
605      * @return true if succeeds, false otherwise
606      */
607     public boolean removeGroup(int objectiveId) {
608
609         if (nsNextObjStore.containsValue(objectiveId)) {
610             NextObjective.Builder nextObjBuilder = DefaultNextObjective
611                     .builder().withId(objectiveId)
612                     .withType(NextObjective.Type.HASHED).fromApp(appId);
613             NextObjective nextObjective = nextObjBuilder.
614                     remove(new SRNextObjectiveContext(deviceId));
615             flowObjectiveService.next(deviceId, nextObjective);
616
617             for (Map.Entry<NeighborSetNextObjectiveStoreKey, Integer> entry: nsNextObjStore.entrySet()) {
618                 if (entry.getValue().equals(objectiveId)) {
619                     nsNextObjStore.remove(entry.getKey());
620                     break;
621                 }
622             }
623             return true;
624         }
625
626         return false;
627     }
628
629     protected static class SRNextObjectiveContext implements ObjectiveContext {
630         final DeviceId deviceId;
631
632         SRNextObjectiveContext(DeviceId deviceId) {
633             this.deviceId = deviceId;
634         }
635         @Override
636         public void onSuccess(Objective objective) {
637             log.debug("Next objective operation successful in device {}",
638                       deviceId);
639         }
640
641         @Override
642         public void onError(Objective objective, ObjectiveError error) {
643             log.warn("Next objective {} operation failed with error: {} in device {}",
644                      objective, error, deviceId);
645         }
646     }
647 }