blob: 32c823ea67ff5805c52492fdf11b86508e4ae638 [file] [log] [blame]
Yoonseon Hana578d762017-05-08 13:42:02 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Yoonseon Hana578d762017-05-08 13:42:02 -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 */
16
17package org.onosproject.incubator.store.virtual.impl;
18
Yoonseon Hana3277012017-05-22 12:26:21 -070019import com.google.common.collect.ImmutableList;
Jordan Halterman0a2bd452018-06-13 17:24:58 -070020import com.google.common.collect.ImmutableMap;
Yoonseon Hana3277012017-05-22 12:26:21 -070021import com.google.common.collect.ImmutableSet;
Yoonseon Hana578d762017-05-08 13:42:02 -070022import org.apache.felix.scr.annotations.Activate;
23import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Deactivate;
Yoonseon Hana3277012017-05-22 12:26:21 -070025import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
Yoonseon Hana578d762017-05-08 13:42:02 -070027import org.apache.felix.scr.annotations.Service;
Yoonseon Hana3277012017-05-22 12:26:21 -070028import org.onlab.packet.IpAddress;
29import org.onosproject.cluster.ClusterEventListener;
30import org.onosproject.cluster.ClusterService;
31import org.onosproject.cluster.ControllerNode;
32import org.onosproject.cluster.DefaultControllerNode;
Jordan Halterman00e92da2018-05-22 23:05:52 -070033import org.onosproject.cluster.Node;
Yoonseon Hana578d762017-05-08 13:42:02 -070034import org.onosproject.cluster.NodeId;
35import org.onosproject.cluster.RoleInfo;
Jordan Haltermanf70bf462017-07-29 13:12:00 -070036import org.onosproject.core.Version;
37import org.onosproject.core.VersionService;
Yoonseon Hana578d762017-05-08 13:42:02 -070038import org.onosproject.incubator.net.virtual.NetworkId;
39import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
40import org.onosproject.mastership.MastershipEvent;
Jordan Halterman0a2bd452018-06-13 17:24:58 -070041import org.onosproject.mastership.MastershipInfo;
Yoonseon Hana578d762017-05-08 13:42:02 -070042import org.onosproject.mastership.MastershipStoreDelegate;
43import org.onosproject.mastership.MastershipTerm;
44import org.onosproject.net.DeviceId;
45import org.onosproject.net.MastershipRole;
46import org.slf4j.Logger;
47
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070048import java.time.Instant;
Yoonseon Hana3277012017-05-22 12:26:21 -070049import java.util.ArrayList;
50import java.util.Collections;
51import java.util.HashMap;
52import java.util.HashSet;
53import java.util.List;
54import java.util.Map;
55import java.util.Objects;
Jordan Halterman0a2bd452018-06-13 17:24:58 -070056import java.util.Optional;
Yoonseon Hana578d762017-05-08 13:42:02 -070057import java.util.Set;
58import java.util.concurrent.CompletableFuture;
Yoonseon Hana3277012017-05-22 12:26:21 -070059import java.util.concurrent.atomic.AtomicInteger;
Yoonseon Hana578d762017-05-08 13:42:02 -070060
Yoonseon Hana3277012017-05-22 12:26:21 -070061import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
62import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
Yoonseon Hana578d762017-05-08 13:42:02 -070063import static org.slf4j.LoggerFactory.getLogger;
64
65/**
66 * Implementation of the virtual network mastership store to manage inventory of
67 * mastership using trivial in-memory implementation.
68 */
69@Component(immediate = true)
70@Service
71public class SimpleVirtualMastershipStore
72 extends AbstractVirtualStore<MastershipEvent, MastershipStoreDelegate>
73 implements VirtualNetworkMastershipStore {
74
75 private final Logger log = getLogger(getClass());
76
Yoonseon Hana3277012017-05-22 12:26:21 -070077 private static final int NOTHING = 0;
78 private static final int INIT = 1;
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected ClusterService clusterService;
82
Jordan Haltermanf70bf462017-07-29 13:12:00 -070083 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected VersionService versionService;
85
Yoonseon Hana3277012017-05-22 12:26:21 -070086 //devices mapped to their masters, to emulate multiple nodes
87 protected final Map<NetworkId, Map<DeviceId, NodeId>> masterMapByNetwork =
88 new HashMap<>();
89 //emulate backups with pile of nodes
90 protected final Map<NetworkId, Map<DeviceId, List<NodeId>>> backupsByNetwork =
91 new HashMap<>();
92 //terms
93 protected final Map<NetworkId, Map<DeviceId, AtomicInteger>> termMapByNetwork =
94 new HashMap<>();
95
Yoonseon Hana578d762017-05-08 13:42:02 -070096 @Activate
97 public void activate() {
Yoonseon Hana3277012017-05-22 12:26:21 -070098 if (clusterService == null) {
99 clusterService = createFakeClusterService();
100 }
Yoonseon Hana578d762017-05-08 13:42:02 -0700101 log.info("Started");
102 }
103
104 @Deactivate
105 public void deactivate() {
106 log.info("Stopped");
107 }
108
109 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700110 public CompletableFuture<MastershipRole> requestRole(NetworkId networkId,
111 DeviceId deviceId) {
112 //query+possible reelection
113 NodeId node = clusterService.getLocalNode().id();
114 MastershipRole role = getRole(networkId, node, deviceId);
115
116 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
117
118 switch (role) {
119 case MASTER:
120 return CompletableFuture.completedFuture(MastershipRole.MASTER);
121 case STANDBY:
122 if (getMaster(networkId, deviceId) == null) {
123 // no master => become master
124 masterMap.put(deviceId, node);
125 incrementTerm(networkId, deviceId);
126 // remove from backup list
127 removeFromBackups(networkId, deviceId, node);
128 notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700129 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700130 return CompletableFuture.completedFuture(MastershipRole.MASTER);
131 }
132 return CompletableFuture.completedFuture(MastershipRole.STANDBY);
133 case NONE:
134 if (getMaster(networkId, deviceId) == null) {
135 // no master => become master
136 masterMap.put(deviceId, node);
137 incrementTerm(networkId, deviceId);
138 notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700139 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700140 return CompletableFuture.completedFuture(MastershipRole.MASTER);
141 }
142 // add to backup list
143 if (addToBackup(networkId, deviceId, node)) {
144 notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700145 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700146 }
147 return CompletableFuture.completedFuture(MastershipRole.STANDBY);
148 default:
149 log.warn("unknown Mastership Role {}", role);
150 }
151 return CompletableFuture.completedFuture(role);
Yoonseon Hana578d762017-05-08 13:42:02 -0700152 }
153
154 @Override
155 public MastershipRole getRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700156 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
157 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
158
159 //just query
160 NodeId current = masterMap.get(deviceId);
161 MastershipRole role;
162
163 if (current != null && current.equals(nodeId)) {
164 return MastershipRole.MASTER;
165 }
166
167 if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) {
168 role = MastershipRole.STANDBY;
169 } else {
170 role = MastershipRole.NONE;
171 }
172 return role;
Yoonseon Hana578d762017-05-08 13:42:02 -0700173 }
174
175 @Override
176 public NodeId getMaster(NetworkId networkId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700177 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
178 return masterMap.get(deviceId);
Yoonseon Hana578d762017-05-08 13:42:02 -0700179 }
180
181 @Override
182 public RoleInfo getNodes(NetworkId networkId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700183 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
184 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
185
186 return new RoleInfo(masterMap.get(deviceId),
187 backups.getOrDefault(deviceId, ImmutableList.of()));
Yoonseon Hana578d762017-05-08 13:42:02 -0700188 }
189
190 @Override
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700191 public MastershipInfo getMastership(NetworkId networkId, DeviceId deviceId) {
192 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
193 Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
194 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
195 ImmutableMap.Builder<NodeId, MastershipRole> roleBuilder = ImmutableMap.builder();
196 NodeId master = masterMap.get(deviceId);
197 if (master != null) {
198 roleBuilder.put(master, MastershipRole.MASTER);
199 }
Ray Milkey30f35b22018-06-21 14:56:47 -0700200 backups.getOrDefault(deviceId, Collections.emptyList())
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700201 .forEach(nodeId -> roleBuilder.put(nodeId, MastershipRole.STANDBY));
202 clusterService.getNodes().stream()
203 .filter(node -> !masterMap.containsValue(node.id()))
Ray Milkey30f35b22018-06-21 14:56:47 -0700204 .filter(node -> !backups.get(deviceId).contains(node.id()))
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700205 .forEach(node -> roleBuilder.put(node.id(), MastershipRole.NONE));
206 return new MastershipInfo(
207 termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING)).get(),
208 Optional.ofNullable(master),
209 roleBuilder.build());
210 }
211
212 @Override
Yoonseon Hana578d762017-05-08 13:42:02 -0700213 public Set<DeviceId> getDevices(NetworkId networkId, NodeId nodeId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700214 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
215
216 Set<DeviceId> ids = new HashSet<>();
217 for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
218 if (Objects.equals(d.getValue(), nodeId)) {
219 ids.add(d.getKey());
220 }
221 }
222 return ids;
Yoonseon Hana578d762017-05-08 13:42:02 -0700223 }
224
225 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700226 public synchronized CompletableFuture<MastershipEvent> setMaster(NetworkId networkId,
Yoonseon Hana578d762017-05-08 13:42:02 -0700227 NodeId nodeId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700228 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
229
230 MastershipRole role = getRole(networkId, nodeId, deviceId);
231 switch (role) {
232 case MASTER:
233 // no-op
234 return CompletableFuture.completedFuture(null);
235 case STANDBY:
236 case NONE:
237 NodeId prevMaster = masterMap.put(deviceId, nodeId);
238 incrementTerm(networkId, deviceId);
239 removeFromBackups(networkId, deviceId, nodeId);
240 addToBackup(networkId, deviceId, prevMaster);
241 break;
242 default:
243 log.warn("unknown Mastership Role {}", role);
244 return null;
245 }
246
247 return CompletableFuture.completedFuture(
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700248 new MastershipEvent(MASTER_CHANGED, deviceId, getMastership(networkId, deviceId)));
Yoonseon Hana578d762017-05-08 13:42:02 -0700249 }
250
251 @Override
252 public MastershipTerm getTermFor(NetworkId networkId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700253 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
254 Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
255
256 if ((termMap.get(deviceId) == null)) {
257 return MastershipTerm.of(masterMap.get(deviceId), NOTHING);
258 }
259 return MastershipTerm.of(
260 masterMap.get(deviceId), termMap.get(deviceId).get());
Yoonseon Hana578d762017-05-08 13:42:02 -0700261 }
262
263 @Override
264 public CompletableFuture<MastershipEvent> setStandby(NetworkId networkId,
265 NodeId nodeId, DeviceId deviceId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700266 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
267
268 MastershipRole role = getRole(networkId, nodeId, deviceId);
269 switch (role) {
270 case MASTER:
271 NodeId backup = reelect(networkId, deviceId, nodeId);
272 if (backup == null) {
273 // no master alternative
274 masterMap.remove(deviceId);
275 // TODO: Should there be new event type for no MASTER?
276 return CompletableFuture.completedFuture(
277 new MastershipEvent(MASTER_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700278 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700279 } else {
280 NodeId prevMaster = masterMap.put(deviceId, backup);
281 incrementTerm(networkId, deviceId);
282 addToBackup(networkId, deviceId, prevMaster);
283 return CompletableFuture.completedFuture(
284 new MastershipEvent(MASTER_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700285 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700286 }
287
288 case STANDBY:
289 case NONE:
290 boolean modified = addToBackup(networkId, deviceId, nodeId);
291 if (modified) {
292 return CompletableFuture.completedFuture(
293 new MastershipEvent(BACKUPS_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700294 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700295 }
296 break;
297
298 default:
299 log.warn("unknown Mastership Role {}", role);
300 }
Yoonseon Hana578d762017-05-08 13:42:02 -0700301 return null;
302 }
303
Yoonseon Hana3277012017-05-22 12:26:21 -0700304
305 /**
306 * Dumbly selects next-available node that's not the current one.
307 * emulate leader election.
308 *
309 * @param networkId a virtual network identifier
310 * @param deviceId a virtual device identifier
311 * @param nodeId a nod identifier
312 * @return Next available node as a leader
313 */
314 private synchronized NodeId reelect(NetworkId networkId, DeviceId deviceId,
315 NodeId nodeId) {
316 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
317
318 List<NodeId> stbys = backups.getOrDefault(deviceId, Collections.emptyList());
319 NodeId backup = null;
320 for (NodeId n : stbys) {
321 if (!n.equals(nodeId)) {
322 backup = n;
323 break;
324 }
325 }
326 stbys.remove(backup);
327 return backup;
328 }
329
Yoonseon Hana578d762017-05-08 13:42:02 -0700330 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700331 public synchronized CompletableFuture<MastershipEvent>
332 relinquishRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
333 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
334
335 MastershipRole role = getRole(networkId, nodeId, deviceId);
336 switch (role) {
337 case MASTER:
338 NodeId backup = reelect(networkId, deviceId, nodeId);
339 masterMap.put(deviceId, backup);
340 incrementTerm(networkId, deviceId);
341 return CompletableFuture.completedFuture(
342 new MastershipEvent(MASTER_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700343 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700344
345 case STANDBY:
346 if (removeFromBackups(networkId, deviceId, nodeId)) {
347 return CompletableFuture.completedFuture(
348 new MastershipEvent(BACKUPS_CHANGED, deviceId,
Jordan Halterman0a2bd452018-06-13 17:24:58 -0700349 getMastership(networkId, deviceId)));
Yoonseon Hana3277012017-05-22 12:26:21 -0700350 }
351 break;
352
353 case NONE:
354 break;
355
356 default:
357 log.warn("unknown Mastership Role {}", role);
358 }
359 return CompletableFuture.completedFuture(null);
Yoonseon Hana578d762017-05-08 13:42:02 -0700360 }
361
362 @Override
363 public void relinquishAllRole(NetworkId networkId, NodeId nodeId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700364 Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
365 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
Yoonseon Hana578d762017-05-08 13:42:02 -0700366
Yoonseon Hana3277012017-05-22 12:26:21 -0700367 List<CompletableFuture<MastershipEvent>> eventFutures = new ArrayList<>();
368 Set<DeviceId> toRelinquish = new HashSet<>();
369
370 masterMap.entrySet().stream()
371 .filter(entry -> nodeId.equals(entry.getValue()))
372 .forEach(entry -> toRelinquish.add(entry.getKey()));
373
374 backups.entrySet().stream()
375 .filter(entry -> entry.getValue().contains(nodeId))
376 .forEach(entry -> toRelinquish.add(entry.getKey()));
377
378 toRelinquish.forEach(deviceId -> eventFutures.add(
379 relinquishRole(networkId, nodeId, deviceId)));
380
381 eventFutures.forEach(future -> {
382 future.whenComplete((event, error) -> notifyDelegate(networkId, event));
383 });
384 }
385
386 /**
387 * Increase the term for a device, and store it.
388 *
389 * @param networkId a virtual network identifier
390 * @param deviceId a virtual device identifier
391 */
392 private synchronized void incrementTerm(NetworkId networkId, DeviceId deviceId) {
393 Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
394
395 AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING));
396 term.incrementAndGet();
397 termMap.put(deviceId, term);
398 }
399
400 /**
401 * Remove backup node for a device.
402 *
403 * @param networkId a virtual network identifier
404 * @param deviceId a virtual device identifier
405 * @param nodeId a node identifier
406 * @return True if success
407 */
408 private synchronized boolean removeFromBackups(NetworkId networkId,
409 DeviceId deviceId, NodeId nodeId) {
410 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
411
412 List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
413 boolean modified = stbys.remove(nodeId);
414 backups.put(deviceId, stbys);
415 return modified;
416 }
417
418 /**
419 * add to backup if not there already, silently ignores null node.
420 *
421 * @param networkId a virtual network identifier
422 * @param deviceId a virtual device identifier
423 * @param nodeId a node identifier
424 * @return True if success
425 */
426 private synchronized boolean addToBackup(NetworkId networkId,
427 DeviceId deviceId, NodeId nodeId) {
428 Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
429
430 boolean modified = false;
431 List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
432 if (nodeId != null && !stbys.contains(nodeId)) {
433 stbys.add(nodeId);
434 backups.put(deviceId, stbys);
435 modified = true;
436 }
437 return modified;
438 }
439
440 /**
441 * Returns deviceId-master map for a specified virtual network.
442 *
443 * @param networkId a virtual network identifier
444 * @return DeviceId-master map of a given virtual network.
445 */
446 private Map<DeviceId, NodeId> getMasterMap(NetworkId networkId) {
447 return masterMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
448 }
449
450 /**
451 * Returns deviceId-backups map for a specified virtual network.
452 *
453 * @param networkId a virtual network identifier
454 * @return DeviceId-backups map of a given virtual network.
455 */
456 private Map<DeviceId, List<NodeId>> getBackups(NetworkId networkId) {
457 return backupsByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
458 }
459
460 /**
461 * Returns deviceId-terms map for a specified virtual network.
462 *
463 * @param networkId a virtual network identifier
464 * @return DeviceId-terms map of a given virtual network.
465 */
466 private Map<DeviceId, AtomicInteger> getTermMap(NetworkId networkId) {
467 return termMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
468 }
469
470 /**
471 * Returns a fake cluster service for a test purpose only.
472 *
473 * @return a fake cluster service
474 */
475 private ClusterService createFakeClusterService() {
476 // just for ease of unit test
477 final ControllerNode instance =
478 new DefaultControllerNode(new NodeId("local"),
479 IpAddress.valueOf("127.0.0.1"));
480
481 ClusterService faceClusterService = new ClusterService() {
482
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700483 private final Instant creationTime = Instant.now();
Yoonseon Hana3277012017-05-22 12:26:21 -0700484
485 @Override
486 public ControllerNode getLocalNode() {
487 return instance;
488 }
489
490 @Override
491 public Set<ControllerNode> getNodes() {
492 return ImmutableSet.of(instance);
493 }
494
495 @Override
Jordan Halterman00e92da2018-05-22 23:05:52 -0700496 public Set<Node> getConsensusNodes() {
497 return ImmutableSet.of();
498 }
499
500 @Override
Yoonseon Hana3277012017-05-22 12:26:21 -0700501 public ControllerNode getNode(NodeId nodeId) {
502 if (instance.id().equals(nodeId)) {
503 return instance;
504 }
505 return null;
506 }
507
508 @Override
509 public ControllerNode.State getState(NodeId nodeId) {
510 if (instance.id().equals(nodeId)) {
511 return ControllerNode.State.ACTIVE;
512 } else {
513 return ControllerNode.State.INACTIVE;
514 }
515 }
516
517 @Override
Jordan Haltermanf70bf462017-07-29 13:12:00 -0700518 public Version getVersion(NodeId nodeId) {
519 if (instance.id().equals(nodeId)) {
520 return versionService.version();
521 }
522 return null;
523 }
524
525 @Override
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700526 public Instant getLastUpdatedInstant(NodeId nodeId) {
Yoonseon Hana3277012017-05-22 12:26:21 -0700527 return creationTime;
528 }
529
530 @Override
531 public void addListener(ClusterEventListener listener) {
532 }
533
534 @Override
535 public void removeListener(ClusterEventListener listener) {
536 }
537 };
538 return faceClusterService;
Yoonseon Hana578d762017-05-08 13:42:02 -0700539 }
540}