blob: 2a8caf0b3a1fdfafef91906f739d82e4ea2b0d95 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07003 *
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 */
Thomas Vachuskac97aa612015-06-23 16:00:18 -070016package org.onosproject.store.trivial;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070017
Madan Jampani7d2fab22015-03-18 17:21:57 -070018import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
19import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070020import static org.slf4j.LoggerFactory.getLogger;
21
Ayaka Koshibef9b02fc2014-10-15 17:07:05 -070022import java.util.ArrayList;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070023import java.util.Collections;
Ayaka Koshibeb70d34b2014-09-25 15:43:01 -070024import java.util.HashMap;
Ayaka Koshibe406d0102014-09-24 16:08:12 -070025import java.util.HashSet;
Ayaka Koshibe45503ce2014-10-14 11:26:45 -070026import java.util.List;
Ayaka Koshibe406d0102014-09-24 16:08:12 -070027import java.util.Map;
Thomas Vachuskaa68be812014-11-27 11:49:54 -080028import java.util.Objects;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070029import java.util.Set;
Madan Jampanif7536ab2015-05-07 23:23:23 -070030import java.util.concurrent.CompletableFuture;
Ayaka Koshibeb70d34b2014-09-25 15:43:01 -070031import java.util.concurrent.atomic.AtomicInteger;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070032
33import org.apache.felix.scr.annotations.Activate;
34import org.apache.felix.scr.annotations.Component;
35import org.apache.felix.scr.annotations.Deactivate;
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -080036import org.apache.felix.scr.annotations.Reference;
37import org.apache.felix.scr.annotations.ReferenceCardinality;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070038import org.apache.felix.scr.annotations.Service;
Madan Jampani7d2fab22015-03-18 17:21:57 -070039import org.joda.time.DateTime;
40import org.onlab.packet.IpAddress;
Brian O'Connorabafb502014-12-02 22:26:20 -080041import org.onosproject.cluster.ClusterEventListener;
42import org.onosproject.cluster.ClusterService;
43import org.onosproject.cluster.ControllerNode;
44import org.onosproject.cluster.ControllerNode.State;
45import org.onosproject.cluster.DefaultControllerNode;
46import org.onosproject.cluster.NodeId;
47import org.onosproject.cluster.RoleInfo;
Jordan Haltermanf70bf462017-07-29 13:12:00 -070048import org.onosproject.core.Version;
49import org.onosproject.core.VersionService;
Brian O'Connorabafb502014-12-02 22:26:20 -080050import org.onosproject.mastership.MastershipEvent;
51import org.onosproject.mastership.MastershipStore;
52import org.onosproject.mastership.MastershipStoreDelegate;
53import org.onosproject.mastership.MastershipTerm;
54import org.onosproject.net.DeviceId;
55import org.onosproject.net.MastershipRole;
56import org.onosproject.store.AbstractStore;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070057import org.slf4j.Logger;
58
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -080059import com.google.common.collect.ImmutableList;
60import com.google.common.collect.ImmutableSet;
Ayaka Koshibefc981cf2014-10-21 12:44:17 -070061
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070062/**
63 * Manages inventory of controller mastership over devices using
Ayaka Koshibe406d0102014-09-24 16:08:12 -070064 * trivial, non-distributed in-memory structures implementation.
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070065 */
66@Component(immediate = true)
67@Service
tomf80c9722014-09-24 14:49:18 -070068public class SimpleMastershipStore
69 extends AbstractStore<MastershipEvent, MastershipStoreDelegate>
70 implements MastershipStore {
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070071
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070072 private final Logger log = getLogger(getClass());
73
Yuta HIGUCHIdfe6e3b2014-10-30 11:31:51 -070074 private static final int NOTHING = 0;
75 private static final int INIT = 1;
76
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -080077 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected ClusterService clusterService;
Ayaka Koshibe406d0102014-09-24 16:08:12 -070079
Jordan Haltermanf70bf462017-07-29 13:12:00 -070080 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected VersionService versionService;
82
Ayaka Koshibe406d0102014-09-24 16:08:12 -070083 //devices mapped to their masters, to emulate multiple nodes
Ayaka Koshibe3de43ca2014-09-26 16:40:23 -070084 protected final Map<DeviceId, NodeId> masterMap = new HashMap<>();
Ayaka Koshibe971a38a2014-09-30 11:56:23 -070085 //emulate backups with pile of nodes
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -080086 protected final Map<DeviceId, List<NodeId>> backups = new HashMap<>();
Ayaka Koshibed9f693e2014-09-29 18:04:54 -070087 //terms
Ayaka Koshibeb70d34b2014-09-25 15:43:01 -070088 protected final Map<DeviceId, AtomicInteger> termMap = new HashMap<>();
Ayaka Koshibea7f044e2014-09-23 16:56:20 -070089
90 @Activate
91 public void activate() {
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -080092 if (clusterService == null) {
93 // just for ease of unit test
94 final ControllerNode instance =
95 new DefaultControllerNode(new NodeId("local"),
96 IpAddress.valueOf("127.0.0.1"));
97
98 clusterService = new ClusterService() {
99
Madan Jampani7d2fab22015-03-18 17:21:57 -0700100 private final DateTime creationTime = DateTime.now();
101
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800102 @Override
103 public ControllerNode getLocalNode() {
104 return instance;
105 }
106
107 @Override
108 public Set<ControllerNode> getNodes() {
109 return ImmutableSet.of(instance);
110 }
111
112 @Override
113 public ControllerNode getNode(NodeId nodeId) {
114 if (instance.id().equals(nodeId)) {
115 return instance;
116 }
117 return null;
118 }
119
120 @Override
121 public State getState(NodeId nodeId) {
122 if (instance.id().equals(nodeId)) {
123 return State.ACTIVE;
124 } else {
125 return State.INACTIVE;
126 }
127 }
128
129 @Override
Jordan Haltermanf70bf462017-07-29 13:12:00 -0700130 public Version getVersion(NodeId nodeId) {
131 if (instance.id().equals(nodeId)) {
132 return versionService.version();
133 }
134 return null;
135 }
136
137 @Override
Madan Jampani7d2fab22015-03-18 17:21:57 -0700138 public DateTime getLastUpdated(NodeId nodeId) {
139 return creationTime;
140 }
141
142 @Override
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800143 public void addListener(ClusterEventListener listener) {
144 }
145
146 @Override
147 public void removeListener(ClusterEventListener listener) {
148 }
149 };
150 }
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700151 log.info("Started");
152 }
153
154 @Deactivate
155 public void deactivate() {
156 log.info("Stopped");
157 }
158
159 @Override
Madan Jampanif7536ab2015-05-07 23:23:23 -0700160 public synchronized CompletableFuture<MastershipEvent> setMaster(NodeId nodeId, DeviceId deviceId) {
Ayaka Koshibe406d0102014-09-24 16:08:12 -0700161
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800162 MastershipRole role = getRole(nodeId, deviceId);
163 switch (role) {
164 case MASTER:
165 // no-op
Madan Jampanif7536ab2015-05-07 23:23:23 -0700166 return CompletableFuture.completedFuture(null);
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800167 case STANDBY:
168 case NONE:
169 NodeId prevMaster = masterMap.put(deviceId, nodeId);
170 incrementTerm(deviceId);
171 removeFromBackups(deviceId, nodeId);
172 addToBackup(deviceId, prevMaster);
173 break;
174 default:
175 log.warn("unknown Mastership Role {}", role);
176 return null;
Ayaka Koshibe406d0102014-09-24 16:08:12 -0700177 }
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700178
Madan Jampanif7536ab2015-05-07 23:23:23 -0700179 return CompletableFuture.completedFuture(
180 new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700181 }
182
183 @Override
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700184 public NodeId getMaster(DeviceId deviceId) {
Ayaka Koshibe406d0102014-09-24 16:08:12 -0700185 return masterMap.get(deviceId);
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700186 }
187
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800188 // synchronized for atomic read
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700189 @Override
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800190 public synchronized RoleInfo getNodes(DeviceId deviceId) {
191 return new RoleInfo(masterMap.get(deviceId),
192 backups.getOrDefault(deviceId, ImmutableList.of()));
Ayaka Koshibe45503ce2014-10-14 11:26:45 -0700193 }
194
195 @Override
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700196 public Set<DeviceId> getDevices(NodeId nodeId) {
Ayaka Koshibe406d0102014-09-24 16:08:12 -0700197 Set<DeviceId> ids = new HashSet<>();
198 for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
Thomas Vachuskaa68be812014-11-27 11:49:54 -0800199 if (Objects.equals(d.getValue(), nodeId)) {
Ayaka Koshibe406d0102014-09-24 16:08:12 -0700200 ids.add(d.getKey());
201 }
202 }
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800203 return ids;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700204 }
205
206 @Override
Madan Jampanide003d92015-05-11 17:14:20 -0700207 public synchronized CompletableFuture<MastershipRole> requestRole(DeviceId deviceId) {
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700208 //query+possible reelection
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800209 NodeId node = clusterService.getLocalNode().id();
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700210 MastershipRole role = getRole(node, deviceId);
211
212 switch (role) {
213 case MASTER:
Madan Jampanide003d92015-05-11 17:14:20 -0700214 return CompletableFuture.completedFuture(MastershipRole.MASTER);
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700215 case STANDBY:
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800216 if (getMaster(deviceId) == null) {
217 // no master => become master
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700218 masterMap.put(deviceId, node);
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800219 incrementTerm(deviceId);
220 // remove from backup list
221 removeFromBackups(deviceId, node);
222 notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId,
223 getNodes(deviceId)));
Madan Jampanide003d92015-05-11 17:14:20 -0700224 return CompletableFuture.completedFuture(MastershipRole.MASTER);
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700225 }
Madan Jampanide003d92015-05-11 17:14:20 -0700226 return CompletableFuture.completedFuture(MastershipRole.STANDBY);
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800227 case NONE:
228 if (getMaster(deviceId) == null) {
229 // no master => become master
230 masterMap.put(deviceId, node);
231 incrementTerm(deviceId);
232 notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId,
233 getNodes(deviceId)));
Madan Jampanide003d92015-05-11 17:14:20 -0700234 return CompletableFuture.completedFuture(MastershipRole.MASTER);
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800235 }
236 // add to backup list
237 if (addToBackup(deviceId, node)) {
238 notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId,
239 getNodes(deviceId)));
240 }
Madan Jampanide003d92015-05-11 17:14:20 -0700241 return CompletableFuture.completedFuture(MastershipRole.STANDBY);
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700242 default:
243 log.warn("unknown Mastership Role {}", role);
244 }
Madan Jampanide003d92015-05-11 17:14:20 -0700245 return CompletableFuture.completedFuture(role);
tomb41d1ac2014-09-24 01:51:24 -0700246 }
247
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800248 // add to backup if not there already, silently ignores null node
249 private synchronized boolean addToBackup(DeviceId deviceId, NodeId nodeId) {
250 boolean modified = false;
251 List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
252 if (nodeId != null && !stbys.contains(nodeId)) {
253 stbys.add(nodeId);
254 modified = true;
255 }
256 backups.put(deviceId, stbys);
257 return modified;
258 }
259
260 private synchronized boolean removeFromBackups(DeviceId deviceId, NodeId node) {
261 List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
262 boolean modified = stbys.remove(node);
263 backups.put(deviceId, stbys);
264 return modified;
265 }
266
267 private synchronized void incrementTerm(DeviceId deviceId) {
268 AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING));
269 term.incrementAndGet();
270 termMap.put(deviceId, term);
271 }
272
tomb41d1ac2014-09-24 01:51:24 -0700273 @Override
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700274 public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700275 //just query
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700276 NodeId current = masterMap.get(deviceId);
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700277 MastershipRole role;
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700278
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800279 if (current != null && current.equals(nodeId)) {
280 return MastershipRole.MASTER;
281 }
282
283 if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) {
284 role = MastershipRole.STANDBY;
Ayaka Koshibe406d0102014-09-24 16:08:12 -0700285 } else {
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800286 role = MastershipRole.NONE;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700287 }
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700288 return role;
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700289 }
290
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800291 // synchronized for atomic read
Ayaka Koshibeb70d34b2014-09-25 15:43:01 -0700292 @Override
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800293 public synchronized MastershipTerm getTermFor(DeviceId deviceId) {
Yuta HIGUCHIdfe6e3b2014-10-30 11:31:51 -0700294 if ((termMap.get(deviceId) == null)) {
295 return MastershipTerm.of(masterMap.get(deviceId), NOTHING);
Ayaka Koshibeb70d34b2014-09-25 15:43:01 -0700296 }
297 return MastershipTerm.of(
298 masterMap.get(deviceId), termMap.get(deviceId).get());
299 }
300
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700301 @Override
Madan Jampanif7536ab2015-05-07 23:23:23 -0700302 public synchronized CompletableFuture<MastershipEvent> setStandby(NodeId nodeId, DeviceId deviceId) {
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700303 MastershipRole role = getRole(nodeId, deviceId);
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800304 switch (role) {
305 case MASTER:
306 NodeId backup = reelect(deviceId, nodeId);
307 if (backup == null) {
308 // no master alternative
309 masterMap.remove(deviceId);
310 // TODO: Should there be new event type for no MASTER?
Madan Jampanif7536ab2015-05-07 23:23:23 -0700311 return CompletableFuture.completedFuture(
312 new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800313 } else {
314 NodeId prevMaster = masterMap.put(deviceId, backup);
315 incrementTerm(deviceId);
316 addToBackup(deviceId, prevMaster);
Madan Jampanif7536ab2015-05-07 23:23:23 -0700317 return CompletableFuture.completedFuture(
318 new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700319 }
Yuta HIGUCHI1b3f4db2014-11-18 11:04:31 -0800320
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800321 case STANDBY:
322 case NONE:
323 boolean modified = addToBackup(deviceId, nodeId);
324 if (modified) {
Madan Jampanif7536ab2015-05-07 23:23:23 -0700325 return CompletableFuture.completedFuture(
326 new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId)));
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800327 }
Yuta HIGUCHI1b3f4db2014-11-18 11:04:31 -0800328 break;
329
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800330 default:
331 log.warn("unknown Mastership Role {}", role);
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700332 }
333 return null;
334 }
335
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700336 //dumbly selects next-available node that's not the current one
337 //emulate leader election
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800338 private synchronized NodeId reelect(DeviceId did, NodeId nodeId) {
339 List<NodeId> stbys = backups.getOrDefault(did, Collections.emptyList());
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700340 NodeId backup = null;
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800341 for (NodeId n : stbys) {
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700342 if (!n.equals(nodeId)) {
343 backup = n;
344 break;
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700345 }
346 }
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800347 stbys.remove(backup);
Ayaka Koshibe971a38a2014-09-30 11:56:23 -0700348 return backup;
Ayaka Koshibed9f693e2014-09-29 18:04:54 -0700349 }
350
Ayaka Koshibec4047702014-10-07 14:43:52 -0700351 @Override
Madan Jampanif7536ab2015-05-07 23:23:23 -0700352 public synchronized CompletableFuture<MastershipEvent> relinquishRole(NodeId nodeId, DeviceId deviceId) {
Ayaka Koshibeb62aab52014-10-24 13:15:25 -0700353 MastershipRole role = getRole(nodeId, deviceId);
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800354 switch (role) {
355 case MASTER:
356 NodeId backup = reelect(deviceId, nodeId);
357 masterMap.put(deviceId, backup);
358 incrementTerm(deviceId);
Madan Jampanif7536ab2015-05-07 23:23:23 -0700359 return CompletableFuture.completedFuture(
360 new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
Yuta HIGUCHI1b3f4db2014-11-18 11:04:31 -0800361
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800362 case STANDBY:
363 if (removeFromBackups(deviceId, nodeId)) {
Madan Jampanif7536ab2015-05-07 23:23:23 -0700364 return CompletableFuture.completedFuture(
365 new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId)));
Ayaka Koshibeb62aab52014-10-24 13:15:25 -0700366 }
Yuta HIGUCHI1b3f4db2014-11-18 11:04:31 -0800367 break;
368
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800369 case NONE:
Yuta HIGUCHI1b3f4db2014-11-18 11:04:31 -0800370 break;
371
Yuta HIGUCHI0c6e1842014-11-05 22:34:23 -0800372 default:
373 log.warn("unknown Mastership Role {}", role);
Ayaka Koshibeb62aab52014-10-24 13:15:25 -0700374 }
Madan Jampanif7536ab2015-05-07 23:23:23 -0700375 return CompletableFuture.completedFuture(null);
Ayaka Koshibec4047702014-10-07 14:43:52 -0700376 }
HIGUCHI Yuta59f02292015-02-25 19:51:48 -0800377
378 @Override
379 public synchronized void relinquishAllRole(NodeId nodeId) {
Madan Jampanif7536ab2015-05-07 23:23:23 -0700380 List<CompletableFuture<MastershipEvent>> eventFutures = new ArrayList<>();
HIGUCHI Yuta59f02292015-02-25 19:51:48 -0800381 Set<DeviceId> toRelinquish = new HashSet<>();
382
383 masterMap.entrySet().stream()
384 .filter(entry -> nodeId.equals(entry.getValue()))
385 .forEach(entry -> toRelinquish.add(entry.getKey()));
386
387 backups.entrySet().stream()
388 .filter(entry -> entry.getValue().contains(nodeId))
389 .forEach(entry -> toRelinquish.add(entry.getKey()));
390
Sho SHIMIZUfeafdca2015-09-11 15:02:21 -0700391 toRelinquish.forEach(deviceId -> eventFutures.add(relinquishRole(nodeId, deviceId)));
HIGUCHI Yuta59f02292015-02-25 19:51:48 -0800392
Madan Jampanif7536ab2015-05-07 23:23:23 -0700393 eventFutures.forEach(future -> {
Sho SHIMIZUfeafdca2015-09-11 15:02:21 -0700394 future.whenComplete((event, error) -> notifyDelegate(event));
Madan Jampanif7536ab2015-05-07 23:23:23 -0700395 });
HIGUCHI Yuta59f02292015-02-25 19:51:48 -0800396 }
Ayaka Koshibea7f044e2014-09-23 16:56:20 -0700397}