blob: ea3e7ebaf4e409b76a704c5ffdec6d6a963e5835 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2014-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 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.store.cluster.impl;
tom2d7c65f2014-09-23 01:09:35 -070017
HIGUCHI Yuta1979f552015-12-28 21:24:26 -080018import com.google.common.base.MoreObjects;
Jonathan Hart4a4d18f2015-03-26 12:16:16 -070019import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Maps;
tom2d7c65f2014-09-23 01:09:35 -070021import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
sangyun-hanf98df542016-03-24 20:28:03 +090024import org.apache.felix.scr.annotations.Modified;
25import org.apache.felix.scr.annotations.Property;
Madan Jampaniafeebbd2015-05-19 15:26:01 -070026import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
tom2d7c65f2014-09-23 01:09:35 -070028import org.apache.felix.scr.annotations.Service;
Madan Jampani7d2fab22015-03-18 17:21:57 -070029import org.joda.time.DateTime;
Ayaka Koshibedd91b842015-03-02 14:48:47 -080030import org.onlab.packet.IpAddress;
31import org.onlab.util.KryoNamespace;
sangyun-hanf98df542016-03-24 20:28:03 +090032import org.onlab.util.Tools;
Brian O'Connorabafb502014-12-02 22:26:20 -080033import org.onosproject.cluster.ClusterEvent;
Madan Jampaniec1df022015-10-13 21:23:03 -070034import org.onosproject.cluster.ClusterMetadataService;
Brian O'Connorabafb502014-12-02 22:26:20 -080035import org.onosproject.cluster.ClusterStore;
36import org.onosproject.cluster.ClusterStoreDelegate;
37import org.onosproject.cluster.ControllerNode;
Ayaka Koshibedd91b842015-03-02 14:48:47 -080038import org.onosproject.cluster.ControllerNode.State;
Jonathan Hart4a4d18f2015-03-26 12:16:16 -070039import org.onosproject.cluster.DefaultControllerNode;
Brian O'Connorabafb502014-12-02 22:26:20 -080040import org.onosproject.cluster.NodeId;
Ayaka Koshibedd91b842015-03-02 14:48:47 -080041import org.onosproject.store.AbstractStore;
Madan Jampanic26eede2015-04-16 11:42:16 -070042import org.onosproject.store.cluster.messaging.Endpoint;
Madan Jampaniafeebbd2015-05-19 15:26:01 -070043import org.onosproject.store.cluster.messaging.MessagingService;
Ayaka Koshibedd91b842015-03-02 14:48:47 -080044import org.onosproject.store.serializers.KryoNamespaces;
45import org.onosproject.store.serializers.KryoSerializer;
sangyun-hanf98df542016-03-24 20:28:03 +090046import org.osgi.service.component.ComponentContext;
Ayaka Koshibedd91b842015-03-02 14:48:47 -080047import org.slf4j.Logger;
tom2d7c65f2014-09-23 01:09:35 -070048
sangyun-hanf98df542016-03-24 20:28:03 +090049import java.util.Dictionary;
Jonathan Hart4a4d18f2015-03-26 12:16:16 -070050import java.util.Map;
Thomas Vachuskafba7f3d2016-03-23 15:46:25 -070051import java.util.Objects;
Jonathan Hart4a4d18f2015-03-26 12:16:16 -070052import java.util.Set;
53import java.util.concurrent.ExecutorService;
54import java.util.concurrent.Executors;
55import java.util.concurrent.ScheduledExecutorService;
56import java.util.concurrent.TimeUnit;
Madan Jampanid36def02016-01-13 11:21:56 -080057import java.util.function.BiConsumer;
Jonathan Hart4a4d18f2015-03-26 12:16:16 -070058import java.util.stream.Collectors;
59
sangyun-hanf98df542016-03-24 20:28:03 +090060import static com.google.common.base.Preconditions.checkArgument;
Jonathan Hart4a4d18f2015-03-26 12:16:16 -070061import static com.google.common.base.Preconditions.checkNotNull;
62import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuskafba7f3d2016-03-23 15:46:25 -070063import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ACTIVATED;
64import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_DEACTIVATED;
65import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_READY;
Jonathan Hart4a4d18f2015-03-26 12:16:16 -070066import static org.slf4j.LoggerFactory.getLogger;
tom2d7c65f2014-09-23 01:09:35 -070067
tom2d7c65f2014-09-23 01:09:35 -070068@Component(immediate = true)
69@Service
Ayaka Koshibedd91b842015-03-02 14:48:47 -080070/**
71 * Distributed cluster nodes store that employs an accrual failure
72 * detector to identify cluster member up/down status.
73 */
tom0755a362014-09-24 11:54:43 -070074public class DistributedClusterStore
Ayaka Koshibedd91b842015-03-02 14:48:47 -080075 extends AbstractStore<ClusterEvent, ClusterStoreDelegate>
tomb41d1ac2014-09-24 01:51:24 -070076 implements ClusterStore {
tom2d7c65f2014-09-23 01:09:35 -070077
Thomas Vachuska8dc1a692015-03-31 01:01:37 -070078 private static final Logger log = getLogger(DistributedClusterStore.class);
tom2d7c65f2014-09-23 01:09:35 -070079
Thomas Vachuskade563cf2015-04-01 00:28:50 -070080 public static final String HEARTBEAT_MESSAGE = "onos-cluster-heartbeat";
81
sangyun-hanf98df542016-03-24 20:28:03 +090082 private static final int DEFAULT_HEARTBEAT_INTERVAL = 100;
83 @Property(name = "heartbeatInterval", intValue = DEFAULT_HEARTBEAT_INTERVAL,
84 label = "Interval time to send heartbeat to other controller nodes (millisecond)")
85 private int heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL;
86
87 private static final int DEFAULT_PHI_FAILURE_THRESHOLD = 10;
88 @Property(name = "phiFailureThreshold", intValue = DEFAULT_PHI_FAILURE_THRESHOLD,
89 label = "the value of Phi threshold to detect accrual failure")
90 private int phiFailureThreshold = DEFAULT_PHI_FAILURE_THRESHOLD;
tom2d7c65f2014-09-23 01:09:35 -070091
Ayaka Koshibedd91b842015-03-02 14:48:47 -080092 private static final KryoSerializer SERIALIZER = new KryoSerializer() {
93 @Override
94 protected void setupKryoPool() {
95 serializerPool = KryoNamespace.newBuilder()
Thomas Vachuska8dc1a692015-03-31 01:01:37 -070096 .register(KryoNamespaces.API)
97 .register(HeartbeatMessage.class)
98 .build()
99 .populate(1);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800100 }
101 };
102
103 private static final String INSTANCE_ID_NULL = "Instance ID cannot be null";
104
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800105 private final Map<NodeId, ControllerNode> allNodes = Maps.newConcurrentMap();
106 private final Map<NodeId, State> nodeStates = Maps.newConcurrentMap();
Madan Jampani7d2fab22015-03-18 17:21:57 -0700107 private final Map<NodeId, DateTime> nodeStateLastUpdatedTimes = Maps.newConcurrentMap();
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800108
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800109 private ScheduledExecutorService heartBeatSender = Executors.newSingleThreadScheduledExecutor(
110 groupedThreads("onos/cluster/membership", "heartbeat-sender"));
111 private ExecutorService heartBeatMessageHandler = Executors.newSingleThreadExecutor(
112 groupedThreads("onos/cluster/membership", "heartbeat-receiver"));
113
114 private PhiAccrualFailureDetector failureDetector;
115
116 private ControllerNode localNode;
117
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampaniec1df022015-10-13 21:23:03 -0700119 protected ClusterMetadataService clusterMetadataService;
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700120
121 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
122 protected MessagingService messagingService;
123
tom2d7c65f2014-09-23 01:09:35 -0700124 @Activate
125 public void activate() {
Madan Jampaniec1df022015-10-13 21:23:03 -0700126 localNode = clusterMetadataService.getLocalNode();
Thomas Vachuska8dc1a692015-03-31 01:01:37 -0700127
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800128 messagingService.registerHandler(HEARTBEAT_MESSAGE,
Thomas Vachuska8dc1a692015-03-31 01:01:37 -0700129 new HeartbeatMessageHandler(), heartBeatMessageHandler);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800130
131 failureDetector = new PhiAccrualFailureDetector();
132
133 heartBeatSender.scheduleWithFixedDelay(this::heartbeat, 0,
sangyun-hanf98df542016-03-24 20:28:03 +0900134 heartbeatInterval, TimeUnit.MILLISECONDS);
tomb41d1ac2014-09-24 01:51:24 -0700135
136 log.info("Started");
137 }
138
tom2d7c65f2014-09-23 01:09:35 -0700139 @Deactivate
140 public void deactivate() {
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700141 messagingService.unregisterHandler(HEARTBEAT_MESSAGE);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800142 heartBeatSender.shutdownNow();
143 heartBeatMessageHandler.shutdownNow();
144
tom2d7c65f2014-09-23 01:09:35 -0700145 log.info("Stopped");
146 }
147
sangyun-hanf98df542016-03-24 20:28:03 +0900148 @Modified
149 public void modified(ComponentContext context) {
150 readComponentConfiguration(context);
151 restartHeartbeatSender();
152 }
153
tom2d7c65f2014-09-23 01:09:35 -0700154 @Override
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800155 public void setDelegate(ClusterStoreDelegate delegate) {
156 checkNotNull(delegate, "Delegate cannot be null");
157 this.delegate = delegate;
158 }
159
160 @Override
161 public void unsetDelegate(ClusterStoreDelegate delegate) {
162 this.delegate = null;
163 }
164
165 @Override
166 public boolean hasDelegate() {
167 return this.delegate != null;
168 }
169
170 @Override
tom2d7c65f2014-09-23 01:09:35 -0700171 public ControllerNode getLocalNode() {
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800172 return localNode;
tom2d7c65f2014-09-23 01:09:35 -0700173 }
174
175 @Override
176 public Set<ControllerNode> getNodes() {
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800177 return ImmutableSet.copyOf(allNodes.values());
tom2d7c65f2014-09-23 01:09:35 -0700178 }
179
180 @Override
181 public ControllerNode getNode(NodeId nodeId) {
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800182 checkNotNull(nodeId, INSTANCE_ID_NULL);
183 return allNodes.get(nodeId);
tom2d7c65f2014-09-23 01:09:35 -0700184 }
185
186 @Override
tomb41d1ac2014-09-24 01:51:24 -0700187 public State getState(NodeId nodeId) {
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800188 checkNotNull(nodeId, INSTANCE_ID_NULL);
HIGUCHI Yuta1979f552015-12-28 21:24:26 -0800189 return MoreObjects.firstNonNull(nodeStates.get(nodeId), State.INACTIVE);
tomb41d1ac2014-09-24 01:51:24 -0700190 }
191
192 @Override
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800193 public void markFullyStarted(boolean started) {
194 updateState(localNode.id(), started ? State.READY : State.ACTIVE);
195 }
196
197 @Override
Pavlin Radoslavov444b5192014-10-28 10:45:19 -0700198 public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
sangyun-hanf98df542016-03-24 20:28:03 +0900199 checkNotNull(nodeId, INSTANCE_ID_NULL);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800200 ControllerNode node = new DefaultControllerNode(nodeId, ip, tcpPort);
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700201 addNode(node);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800202 return node;
tomee49c372014-09-26 15:14:50 -0700203 }
204
205 @Override
tomb41d1ac2014-09-24 01:51:24 -0700206 public void removeNode(NodeId nodeId) {
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800207 checkNotNull(nodeId, INSTANCE_ID_NULL);
208 ControllerNode node = allNodes.remove(nodeId);
209 if (node != null) {
210 nodeStates.remove(nodeId);
Jonathan Hartf1141262015-04-23 11:27:07 -0700211 notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_REMOVED, node));
tomb41d1ac2014-09-24 01:51:24 -0700212 }
213 }
214
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700215 private void addNode(ControllerNode node) {
216 allNodes.put(node.id(), node);
Madan Jampaniec1df022015-10-13 21:23:03 -0700217 updateState(node.id(), node.equals(localNode) ? State.ACTIVE : State.INACTIVE);
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700218 notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_ADDED, node));
Thomas Vachuskade563cf2015-04-01 00:28:50 -0700219 }
220
Madan Jampani7d2fab22015-03-18 17:21:57 -0700221 private void updateState(NodeId nodeId, State newState) {
Thomas Vachuskafba7f3d2016-03-23 15:46:25 -0700222 State currentState = nodeStates.get(nodeId);
223 if (!Objects.equals(currentState, newState)) {
224 nodeStates.put(nodeId, newState);
225 nodeStateLastUpdatedTimes.put(nodeId, DateTime.now());
226 notifyStateChange(nodeId, currentState, newState);
227 }
Madan Jampani7d2fab22015-03-18 17:21:57 -0700228 }
229
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800230 private void heartbeat() {
231 try {
232 Set<ControllerNode> peers = allNodes.values()
233 .stream()
234 .filter(node -> !(node.id().equals(localNode.id())))
235 .collect(Collectors.toSet());
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800236 State state = nodeStates.get(localNode.id());
237 byte[] hbMessagePayload = SERIALIZER.encode(new HeartbeatMessage(localNode, state, peers));
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800238 peers.forEach((node) -> {
239 heartbeatToPeer(hbMessagePayload, node);
240 State currentState = nodeStates.get(node.id());
241 double phi = failureDetector.phi(node.id());
sangyun-hanf98df542016-03-24 20:28:03 +0900242 if (phi >= phiFailureThreshold) {
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800243 if (currentState.isActive()) {
Madan Jampani7d2fab22015-03-18 17:21:57 -0700244 updateState(node.id(), State.INACTIVE);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800245 }
246 } else {
247 if (currentState == State.INACTIVE) {
Madan Jampani7d2fab22015-03-18 17:21:57 -0700248 updateState(node.id(), State.ACTIVE);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800249 }
250 }
251 });
252 } catch (Exception e) {
253 log.debug("Failed to send heartbeat", e);
254 }
tomb41d1ac2014-09-24 01:51:24 -0700255 }
256
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800257 private void notifyStateChange(NodeId nodeId, State oldState, State newState) {
Thomas Vachuskafba7f3d2016-03-23 15:46:25 -0700258 if (oldState != newState) {
259 ControllerNode node = allNodes.get(nodeId);
260 ClusterEvent.Type type = newState == State.READY ? INSTANCE_READY :
261 newState == State.ACTIVE ? INSTANCE_ACTIVATED :
262 INSTANCE_DEACTIVATED;
263 notifyDelegate(new ClusterEvent(type, node));
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800264 }
tomb41d1ac2014-09-24 01:51:24 -0700265 }
266
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800267 private void heartbeatToPeer(byte[] messagePayload, ControllerNode peer) {
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700268 Endpoint remoteEp = new Endpoint(peer.ip(), peer.tcpPort());
Madan Jampani175e8fd2015-05-20 14:10:45 -0700269 messagingService.sendAsync(remoteEp, HEARTBEAT_MESSAGE, messagePayload).whenComplete((result, error) -> {
270 if (error != null) {
271 log.trace("Sending heartbeat to {} failed", remoteEp, error);
272 }
273 });
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800274 }
275
Madan Jampanid36def02016-01-13 11:21:56 -0800276 private class HeartbeatMessageHandler implements BiConsumer<Endpoint, byte[]> {
tomb41d1ac2014-09-24 01:51:24 -0700277 @Override
Madan Jampanid36def02016-01-13 11:21:56 -0800278 public void accept(Endpoint sender, byte[] message) {
Madan Jampanic26eede2015-04-16 11:42:16 -0700279 HeartbeatMessage hb = SERIALIZER.decode(message);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800280 failureDetector.report(hb.source().id());
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800281 updateState(hb.source().id(), hb.state);
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800282 hb.knownPeers().forEach(node -> {
283 allNodes.put(node.id(), node);
284 });
tomb41d1ac2014-09-24 01:51:24 -0700285 }
tom2d7c65f2014-09-23 01:09:35 -0700286 }
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800287
288 private static class HeartbeatMessage {
289 private ControllerNode source;
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800290 private State state;
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800291 private Set<ControllerNode> knownPeers;
292
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800293 public HeartbeatMessage(ControllerNode source, State state, Set<ControllerNode> members) {
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800294 this.source = source;
Thomas Vachuska7a8de842016-03-07 20:56:35 -0800295 this.state = state != null ? state : State.ACTIVE;
Ayaka Koshibedd91b842015-03-02 14:48:47 -0800296 this.knownPeers = ImmutableSet.copyOf(members);
297 }
298
299 public ControllerNode source() {
300 return source;
301 }
302
303 public Set<ControllerNode> knownPeers() {
304 return knownPeers;
305 }
306 }
307
Madan Jampani7d2fab22015-03-18 17:21:57 -0700308 @Override
309 public DateTime getLastUpdated(NodeId nodeId) {
310 return nodeStateLastUpdatedTimes.get(nodeId);
311 }
Thomas Vachuskade563cf2015-04-01 00:28:50 -0700312
sangyun-hanf98df542016-03-24 20:28:03 +0900313 /**
314 * Extracts properties from the component configuration context.
315 *
316 * @param context the component context
317 */
318 private void readComponentConfiguration(ComponentContext context) {
319 Dictionary<?, ?> properties = context.getProperties();
320
321 Integer newHeartbeatInterval = Tools.getIntegerProperty(properties,
322 "heartbeatInterval");
323 if (newHeartbeatInterval == null) {
324 setHeartbeatInterval(DEFAULT_HEARTBEAT_INTERVAL);
325 log.info("Heartbeat interval time is not configured, default value is {}",
326 DEFAULT_HEARTBEAT_INTERVAL);
327 } else {
328 setHeartbeatInterval(newHeartbeatInterval);
329 log.info("Configured. Heartbeat interval time is configured to {}",
330 heartbeatInterval);
331 }
332
333 Integer newPhiFailureThreshold = Tools.getIntegerProperty(properties,
334 "phiFailureThreshold");
335 if (newPhiFailureThreshold == null) {
336 setPhiFailureThreshold(DEFAULT_PHI_FAILURE_THRESHOLD);
337 log.info("Phi failure threshold is not configured, default value is {}",
338 DEFAULT_PHI_FAILURE_THRESHOLD);
339 } else {
340 setPhiFailureThreshold(newPhiFailureThreshold);
341 log.info("Configured. Phi failure threshold is configured to {}",
342 phiFailureThreshold);
343 }
344 }
345
346 /**
347 * Sets heartbeat interval between the termination of one execution of heartbeat
348 * and the commencement of the next.
349 *
350 * @param interval term between each heartbeat
351 */
352 private void setHeartbeatInterval(int interval) {
353 try {
354 checkArgument(interval > 0, "Interval must be greater than zero");
355 heartbeatInterval = interval;
356 } catch (IllegalArgumentException e) {
357 log.warn(e.getMessage());
358 heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL;
359 }
360 }
361
362 /**
363 * Sets Phi failure threshold.
364 * Phi is based on a paper titled: "The φ Accrual Failure Detector" by Hayashibara, et al.
365 *
366 * @param threshold
367 */
368 private void setPhiFailureThreshold(int threshold) {
369 phiFailureThreshold = threshold;
370 }
371
372 /**
373 * Restarts heartbeatSender executor.
374 *
375 */
376 private void restartHeartbeatSender() {
377 try {
378 ScheduledExecutorService prevSender = heartBeatSender;
379 heartBeatSender = Executors.newSingleThreadScheduledExecutor(
380 groupedThreads("onos/cluster/membership", "heartbeat-sender-%d"));
381 heartBeatSender.scheduleWithFixedDelay(this::heartbeat, 0,
382 heartbeatInterval, TimeUnit.MILLISECONDS);
383 prevSender.shutdown();
384 } catch (Exception e) {
385 log.warn(e.getMessage());
386 }
387 }
388
389 /**
390 * Gets current heartbeat interval.
391 *
392 * @return heartbeatInterval
393 */
394 private int getHeartbeatInterval() {
395 return heartbeatInterval;
396 }
397
398 /**
399 * Gets current Phi failure threshold for Accrual Failure Detector.
400 *
401 * @return phiFailureThreshold
402 */
403 private int getPhiFailureThreshold() {
404 return phiFailureThreshold;
405 }
406
407}