blob: c390513b476b0dbd19d7153d5f0cd3cbe633c568 [file] [log] [blame]
Yoonseon Hana578d762017-05-08 13:42:02 -07001/*
2 * Copyright 2017-present 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
17package org.onosproject.incubator.store.virtual.impl;
18
Yoonseon Hana3277012017-05-22 12:26:21 -070019import com.google.common.collect.ImmutableList;
20import com.google.common.collect.ImmutableSet;
Yoonseon Hana578d762017-05-08 13:42:02 -070021import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
Yoonseon Hana3277012017-05-22 12:26:21 -070024import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
Yoonseon Hana578d762017-05-08 13:42:02 -070026import org.apache.felix.scr.annotations.Service;
Yoonseon Hana3277012017-05-22 12:26:21 -070027import org.joda.time.DateTime;
28import org.onlab.packet.IpAddress;
29import org.onosproject.cluster.ClusterEventListener;
30import org.onosproject.cluster.ClusterService;
31import org.onosproject.cluster.ControllerNode;
32import org.onosproject.cluster.DefaultControllerNode;
Yoonseon Hana578d762017-05-08 13:42:02 -070033import org.onosproject.cluster.NodeId;
34import org.onosproject.cluster.RoleInfo;
Jordan Haltermanf70bf462017-07-29 13:12:00 -070035import org.onosproject.core.Version;
36import org.onosproject.core.VersionService;
Yoonseon Hana578d762017-05-08 13:42:02 -070037import org.onosproject.incubator.net.virtual.NetworkId;
38import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
39import org.onosproject.mastership.MastershipEvent;
40import org.onosproject.mastership.MastershipStoreDelegate;
41import org.onosproject.mastership.MastershipTerm;
42import org.onosproject.net.DeviceId;
43import org.onosproject.net.MastershipRole;
44import org.slf4j.Logger;
45
Yoonseon Hana3277012017-05-22 12:26:21 -070046import java.util.ArrayList;
47import java.util.Collections;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.List;
51import java.util.Map;
52import java.util.Objects;
Yoonseon Hana578d762017-05-08 13:42:02 -070053import java.util.Set;
54import java.util.concurrent.CompletableFuture;
Yoonseon Hana3277012017-05-22 12:26:21 -070055import java.util.concurrent.atomic.AtomicInteger;
Yoonseon Hana578d762017-05-08 13:42:02 -070056
Yoonseon Hana3277012017-05-22 12:26:21 -070057import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
58import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
Yoonseon Hana578d762017-05-08 13:42:02 -070059import static org.slf4j.LoggerFactory.getLogger;
60
61/**
62 * Implementation of the virtual network mastership store to manage inventory of
63 * mastership using trivial in-memory implementation.
64 */
65@Component(immediate = true)
66@Service
67public class SimpleVirtualMastershipStore
68 extends AbstractVirtualStore<MastershipEvent, MastershipStoreDelegate>
69 implements VirtualNetworkMastershipStore {
70
71 private final Logger log = getLogger(getClass());
72
Yoonseon Hana3277012017-05-22 12:26:21 -070073 private static final int NOTHING = 0;
74 private static final int INIT = 1;
75
76 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
77 protected ClusterService clusterService;
78
Jordan Haltermanf70bf462017-07-29 13:12:00 -070079 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
80 protected VersionService versionService;
81
Yoonseon Hana3277012017-05-22 12:26:21 -070082 //devices mapped to their masters, to emulate multiple nodes
83 protected final Map<NetworkId, Map<DeviceId, NodeId>> masterMapByNetwork =
84 new HashMap<>();
85 //emulate backups with pile of nodes
86 protected final Map<NetworkId, Map<DeviceId, List<NodeId>>> backupsByNetwork =
87 new HashMap<>();
88 //terms
89 protected final Map<NetworkId, Map<DeviceId, AtomicInteger>> termMapByNetwork =
90 new HashMap<>();
91
Yoonseon Hana578d762017-05-08 13:42:02 -070092 @Activate
93 public void activate() {
Yoonseon Hana3277012017-05-22 12:26:21 -070094 if (clusterService == null) {
95 clusterService = createFakeClusterService();
96 }
Yoonseon Hana578d762017-05-08 13:42:02 -070097 log.info("Started");
98 }
99
100 @Deactivate
101 public void deactivate() {
102 log.info("Stopped");
103 }
104
105 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700106 public CompletableFuture<MastershipRole> requestRole(NetworkId networkId,
107 DeviceId deviceId) {
108 //query+possible reelection
109 NodeId node = clusterService.getLocalNode().id();
110 MastershipRole role = getRole(networkId, node, deviceId);
111
112 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
113
114 switch (role) {
115 case MASTER:
116 return CompletableFuture.completedFuture(MastershipRole.MASTER);
117 case STANDBY:
118 if (getMaster(networkId, deviceId) == null) {
119 // no master => become master
120 masterMap.put(deviceId, node);
121 incrementTerm(networkId, deviceId);
122 // remove from backup list
123 removeFromBackups(networkId, deviceId, node);
124 notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
125 getNodes(networkId, deviceId)));
126 return CompletableFuture.completedFuture(MastershipRole.MASTER);
127 }
128 return CompletableFuture.completedFuture(MastershipRole.STANDBY);
129 case NONE:
130 if (getMaster(networkId, deviceId) == null) {
131 // no master => become master
132 masterMap.put(deviceId, node);
133 incrementTerm(networkId, deviceId);
134 notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
135 getNodes(networkId, deviceId)));
136 return CompletableFuture.completedFuture(MastershipRole.MASTER);
137 }
138 // add to backup list
139 if (addToBackup(networkId, deviceId, node)) {
140 notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId,
141 getNodes(networkId, deviceId)));
142 }
143 return CompletableFuture.completedFuture(MastershipRole.STANDBY);
144 default:
145 log.warn("unknown Mastership Role {}", role);
146 }
147 return CompletableFuture.completedFuture(role);
Yoonseon Hana578d762017-05-08 13:42:02 -0700148 }
149
150 @Override
151 public MastershipRole getRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700152 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
153 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
154
155 //just query
156 NodeId current = masterMap.get(deviceId);
157 MastershipRole role;
158
159 if (current != null && current.equals(nodeId)) {
160 return MastershipRole.MASTER;
161 }
162
163 if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) {
164 role = MastershipRole.STANDBY;
165 } else {
166 role = MastershipRole.NONE;
167 }
168 return role;
Yoonseon Hana578d762017-05-08 13:42:02 -0700169 }
170
171 @Override
172 public NodeId getMaster(NetworkId networkId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700173 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
174 return masterMap.get(deviceId);
Yoonseon Hana578d762017-05-08 13:42:02 -0700175 }
176
177 @Override
178 public RoleInfo getNodes(NetworkId networkId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700179 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
180 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
181
182 return new RoleInfo(masterMap.get(deviceId),
183 backups.getOrDefault(deviceId, ImmutableList.of()));
Yoonseon Hana578d762017-05-08 13:42:02 -0700184 }
185
186 @Override
187 public Set<DeviceId> getDevices(NetworkId networkId, NodeId nodeId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700188 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
189
190 Set<DeviceId> ids = new HashSet<>();
191 for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
192 if (Objects.equals(d.getValue(), nodeId)) {
193 ids.add(d.getKey());
194 }
195 }
196 return ids;
Yoonseon Hana578d762017-05-08 13:42:02 -0700197 }
198
199 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700200 public synchronized CompletableFuture<MastershipEvent> setMaster(NetworkId networkId,
Yoonseon Hana578d762017-05-08 13:42:02 -0700201 NodeId nodeId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700202 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
203
204 MastershipRole role = getRole(networkId, nodeId, deviceId);
205 switch (role) {
206 case MASTER:
207 // no-op
208 return CompletableFuture.completedFuture(null);
209 case STANDBY:
210 case NONE:
211 NodeId prevMaster = masterMap.put(deviceId, nodeId);
212 incrementTerm(networkId, deviceId);
213 removeFromBackups(networkId, deviceId, nodeId);
214 addToBackup(networkId, deviceId, prevMaster);
215 break;
216 default:
217 log.warn("unknown Mastership Role {}", role);
218 return null;
219 }
220
221 return CompletableFuture.completedFuture(
222 new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(networkId, deviceId)));
Yoonseon Hana578d762017-05-08 13:42:02 -0700223 }
224
225 @Override
226 public MastershipTerm getTermFor(NetworkId networkId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700227 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
228 Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
229
230 if ((termMap.get(deviceId) == null)) {
231 return MastershipTerm.of(masterMap.get(deviceId), NOTHING);
232 }
233 return MastershipTerm.of(
234 masterMap.get(deviceId), termMap.get(deviceId).get());
Yoonseon Hana578d762017-05-08 13:42:02 -0700235 }
236
237 @Override
238 public CompletableFuture<MastershipEvent> setStandby(NetworkId networkId,
239 NodeId nodeId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700240 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
241
242 MastershipRole role = getRole(networkId, nodeId, deviceId);
243 switch (role) {
244 case MASTER:
245 NodeId backup = reelect(networkId, deviceId, nodeId);
246 if (backup == null) {
247 // no master alternative
248 masterMap.remove(deviceId);
249 // TODO: Should there be new event type for no MASTER?
250 return CompletableFuture.completedFuture(
251 new MastershipEvent(MASTER_CHANGED, deviceId,
252 getNodes(networkId, deviceId)));
253 } else {
254 NodeId prevMaster = masterMap.put(deviceId, backup);
255 incrementTerm(networkId, deviceId);
256 addToBackup(networkId, deviceId, prevMaster);
257 return CompletableFuture.completedFuture(
258 new MastershipEvent(MASTER_CHANGED, deviceId,
259 getNodes(networkId, deviceId)));
260 }
261
262 case STANDBY:
263 case NONE:
264 boolean modified = addToBackup(networkId, deviceId, nodeId);
265 if (modified) {
266 return CompletableFuture.completedFuture(
267 new MastershipEvent(BACKUPS_CHANGED, deviceId,
268 getNodes(networkId, deviceId)));
269 }
270 break;
271
272 default:
273 log.warn("unknown Mastership Role {}", role);
274 }
Yoonseon Hana578d762017-05-08 13:42:02 -0700275 return null;
276 }
277
Yoonseon Hana3277012017-05-22 12:26:21 -0700278
279 /**
280 * Dumbly selects next-available node that's not the current one.
281 * emulate leader election.
282 *
283 * @param networkId a virtual network identifier
284 * @param deviceId a virtual device identifier
285 * @param nodeId a nod identifier
286 * @return Next available node as a leader
287 */
288 private synchronized NodeId reelect(NetworkId networkId, DeviceId deviceId,
289 NodeId nodeId) {
290 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
291
292 List<NodeId> stbys = backups.getOrDefault(deviceId, Collections.emptyList());
293 NodeId backup = null;
294 for (NodeId n : stbys) {
295 if (!n.equals(nodeId)) {
296 backup = n;
297 break;
298 }
299 }
300 stbys.remove(backup);
301 return backup;
302 }
303
Yoonseon Hana578d762017-05-08 13:42:02 -0700304 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700305 public synchronized CompletableFuture<MastershipEvent>
306 relinquishRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
307 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
308
309 MastershipRole role = getRole(networkId, nodeId, deviceId);
310 switch (role) {
311 case MASTER:
312 NodeId backup = reelect(networkId, deviceId, nodeId);
313 masterMap.put(deviceId, backup);
314 incrementTerm(networkId, deviceId);
315 return CompletableFuture.completedFuture(
316 new MastershipEvent(MASTER_CHANGED, deviceId,
317 getNodes(networkId, deviceId)));
318
319 case STANDBY:
320 if (removeFromBackups(networkId, deviceId, nodeId)) {
321 return CompletableFuture.completedFuture(
322 new MastershipEvent(BACKUPS_CHANGED, deviceId,
323 getNodes(networkId, deviceId)));
324 }
325 break;
326
327 case NONE:
328 break;
329
330 default:
331 log.warn("unknown Mastership Role {}", role);
332 }
333 return CompletableFuture.completedFuture(null);
Yoonseon Hana578d762017-05-08 13:42:02 -0700334 }
335
336 @Override
337 public void relinquishAllRole(NetworkId networkId, NodeId nodeId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700338 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
339 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
Yoonseon Hana578d762017-05-08 13:42:02 -0700340
Yoonseon Hana3277012017-05-22 12:26:21 -0700341 List<CompletableFuture<MastershipEvent>> eventFutures = new ArrayList<>();
342 Set<DeviceId> toRelinquish = new HashSet<>();
343
344 masterMap.entrySet().stream()
345 .filter(entry -> nodeId.equals(entry.getValue()))
346 .forEach(entry -> toRelinquish.add(entry.getKey()));
347
348 backups.entrySet().stream()
349 .filter(entry -> entry.getValue().contains(nodeId))
350 .forEach(entry -> toRelinquish.add(entry.getKey()));
351
352 toRelinquish.forEach(deviceId -> eventFutures.add(
353 relinquishRole(networkId, nodeId, deviceId)));
354
355 eventFutures.forEach(future -> {
356 future.whenComplete((event, error) -> notifyDelegate(networkId, event));
357 });
358 }
359
360 /**
361 * Increase the term for a device, and store it.
362 *
363 * @param networkId a virtual network identifier
364 * @param deviceId a virtual device identifier
365 */
366 private synchronized void incrementTerm(NetworkId networkId, DeviceId deviceId) {
367 Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
368
369 AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING));
370 term.incrementAndGet();
371 termMap.put(deviceId, term);
372 }
373
374 /**
375 * Remove backup node for a device.
376 *
377 * @param networkId a virtual network identifier
378 * @param deviceId a virtual device identifier
379 * @param nodeId a node identifier
380 * @return True if success
381 */
382 private synchronized boolean removeFromBackups(NetworkId networkId,
383 DeviceId deviceId, NodeId nodeId) {
384 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
385
386 List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
387 boolean modified = stbys.remove(nodeId);
388 backups.put(deviceId, stbys);
389 return modified;
390 }
391
392 /**
393 * add to backup if not there already, silently ignores null node.
394 *
395 * @param networkId a virtual network identifier
396 * @param deviceId a virtual device identifier
397 * @param nodeId a node identifier
398 * @return True if success
399 */
400 private synchronized boolean addToBackup(NetworkId networkId,
401 DeviceId deviceId, NodeId nodeId) {
402 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
403
404 boolean modified = false;
405 List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
406 if (nodeId != null && !stbys.contains(nodeId)) {
407 stbys.add(nodeId);
408 backups.put(deviceId, stbys);
409 modified = true;
410 }
411 return modified;
412 }
413
414 /**
415 * Returns deviceId-master map for a specified virtual network.
416 *
417 * @param networkId a virtual network identifier
418 * @return DeviceId-master map of a given virtual network.
419 */
420 private Map<DeviceId, NodeId> getMasterMap(NetworkId networkId) {
421 return masterMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
422 }
423
424 /**
425 * Returns deviceId-backups map for a specified virtual network.
426 *
427 * @param networkId a virtual network identifier
428 * @return DeviceId-backups map of a given virtual network.
429 */
430 private Map<DeviceId, List<NodeId>> getBackups(NetworkId networkId) {
431 return backupsByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
432 }
433
434 /**
435 * Returns deviceId-terms map for a specified virtual network.
436 *
437 * @param networkId a virtual network identifier
438 * @return DeviceId-terms map of a given virtual network.
439 */
440 private Map<DeviceId, AtomicInteger> getTermMap(NetworkId networkId) {
441 return termMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
442 }
443
444 /**
445 * Returns a fake cluster service for a test purpose only.
446 *
447 * @return a fake cluster service
448 */
449 private ClusterService createFakeClusterService() {
450 // just for ease of unit test
451 final ControllerNode instance =
452 new DefaultControllerNode(new NodeId("local"),
453 IpAddress.valueOf("127.0.0.1"));
454
455 ClusterService faceClusterService = new ClusterService() {
456
457 private final DateTime creationTime = DateTime.now();
458
459 @Override
460 public ControllerNode getLocalNode() {
461 return instance;
462 }
463
464 @Override
465 public Set<ControllerNode> getNodes() {
466 return ImmutableSet.of(instance);
467 }
468
469 @Override
470 public ControllerNode getNode(NodeId nodeId) {
471 if (instance.id().equals(nodeId)) {
472 return instance;
473 }
474 return null;
475 }
476
477 @Override
478 public ControllerNode.State getState(NodeId nodeId) {
479 if (instance.id().equals(nodeId)) {
480 return ControllerNode.State.ACTIVE;
481 } else {
482 return ControllerNode.State.INACTIVE;
483 }
484 }
485
486 @Override
Jordan Haltermanf70bf462017-07-29 13:12:00 -0700487 public Version getVersion(NodeId nodeId) {
488 if (instance.id().equals(nodeId)) {
489 return versionService.version();
490 }
491 return null;
492 }
493
494 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700495 public DateTime getLastUpdated(NodeId nodeId) {
496 return creationTime;
497 }
498
499 @Override
500 public void addListener(ClusterEventListener listener) {
501 }
502
503 @Override
504 public void removeListener(ClusterEventListener listener) {
505 }
506 };
507 return faceClusterService;
Yoonseon Hana578d762017-05-08 13:42:02 -0700508 }
509}