blob: 0aece810b3a1f7e18f2116535852c178ee229f87 [file] [log] [blame]
Hyunsun Moonb77b60f2016-01-15 20:03:18 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Hyunsun Moonb77b60f2016-01-15 20:03:18 -08003 *
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 */
Hyunsun Moon7f4ed9d2016-04-14 16:13:42 -070016package org.onosproject.cordvtn.impl;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080017
18import com.google.common.collect.Sets;
Hyunsun Moon133fd792016-02-09 01:55:48 -080019import com.jcraft.jsch.Session;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080020import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
Hyunsun Moon133fd792016-02-09 01:55:48 -080026import org.onlab.packet.IpAddress;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080027import org.onlab.util.ItemNotFoundException;
28import org.onlab.util.KryoNamespace;
29import org.onosproject.cluster.ClusterService;
Hyunsun Moon98025542016-03-08 04:36:02 -080030import org.onosproject.cluster.LeadershipService;
31import org.onosproject.cluster.NodeId;
Hyunsun Moon7f4ed9d2016-04-14 16:13:42 -070032import org.onosproject.cordvtn.api.ConnectionHandler;
33import org.onosproject.cordvtn.api.CordVtnConfig;
34import org.onosproject.cordvtn.api.CordVtnNode;
35import org.onosproject.cordvtn.api.CordVtnNodeState;
36import org.onosproject.cordvtn.api.CordVtnService;
37import org.onosproject.cordvtn.api.NetworkAddress;
38import org.onosproject.cordvtn.api.SshAccessInfo;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080039import org.onosproject.core.ApplicationId;
40import org.onosproject.core.CoreService;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080041import org.onosproject.net.ConnectPoint;
42import org.onosproject.net.DefaultAnnotations;
43import org.onosproject.net.Device;
44import org.onosproject.net.DeviceId;
45import org.onosproject.net.Host;
46import org.onosproject.net.Port;
47import org.onosproject.net.behaviour.BridgeConfig;
48import org.onosproject.net.behaviour.BridgeName;
49import org.onosproject.net.behaviour.ControllerInfo;
50import org.onosproject.net.behaviour.DefaultTunnelDescription;
51import org.onosproject.net.behaviour.TunnelConfig;
52import org.onosproject.net.behaviour.TunnelDescription;
53import org.onosproject.net.behaviour.TunnelName;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080054import org.onosproject.net.config.NetworkConfigEvent;
55import org.onosproject.net.config.NetworkConfigListener;
56import org.onosproject.net.config.NetworkConfigRegistry;
57import org.onosproject.net.config.NetworkConfigService;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080058import org.onosproject.net.device.DeviceAdminService;
59import org.onosproject.net.device.DeviceEvent;
60import org.onosproject.net.device.DeviceListener;
61import org.onosproject.net.device.DeviceService;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080062import org.onosproject.net.flow.FlowRuleService;
63import org.onosproject.net.group.GroupService;
64import org.onosproject.net.host.HostService;
65import org.onosproject.ovsdb.controller.OvsdbClientService;
66import org.onosproject.ovsdb.controller.OvsdbController;
67import org.onosproject.ovsdb.controller.OvsdbNodeId;
68import org.onosproject.store.serializers.KryoNamespaces;
69import org.onosproject.store.service.ConsistentMap;
Hyunsun Moon80b03872016-03-10 12:40:16 -080070import org.onosproject.store.service.MapEvent;
71import org.onosproject.store.service.MapEventListener;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080072import org.onosproject.store.service.Serializer;
73import org.onosproject.store.service.StorageService;
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -080074import org.onosproject.store.service.Versioned;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080075import org.slf4j.Logger;
76
77import java.util.ArrayList;
78import java.util.HashMap;
79import java.util.List;
80import java.util.Map;
Hyunsun Moon98025542016-03-08 04:36:02 -080081import java.util.Objects;
Hyunsun Moon133fd792016-02-09 01:55:48 -080082import java.util.Set;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080083import java.util.concurrent.ExecutorService;
Hyunsun Moonaf520d32016-03-07 16:37:17 -080084import java.util.stream.Collectors;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080085
86import static com.google.common.base.Preconditions.checkNotNull;
87import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
88import static org.onlab.util.Tools.groupedThreads;
89import static org.onosproject.net.Device.Type.SWITCH;
90import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
91import static org.slf4j.LoggerFactory.getLogger;
92
93/**
94 * Reads node information from the network config file and handles the config
95 * update events.
96 * Only a leader controller performs the node addition or deletion.
97 */
98@Component(immediate = true)
99@Service(value = CordVtnNodeManager.class)
100public class CordVtnNodeManager {
101
102 protected final Logger log = getLogger(getClass());
103
104 private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
105 .register(KryoNamespaces.API)
Hyunsun Moon133fd792016-02-09 01:55:48 -0800106 .register(KryoNamespaces.MISC)
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800107 .register(CordVtnNode.class)
Hyunsun Moon133fd792016-02-09 01:55:48 -0800108 .register(NodeState.class)
109 .register(SshAccessInfo.class)
110 .register(NetworkAddress.class);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800111
112 private static final String DEFAULT_BRIDGE = "br-int";
113 private static final String DEFAULT_TUNNEL = "vxlan";
114 private static final String VPORT_PREFIX = "tap";
115 private static final String OK = "OK";
116 private static final String NO = "NO";
117
118 private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS = new HashMap<String, String>() {
119 {
120 put("key", "flow");
121 put("remote_ip", "flow");
122 }
123 };
124 private static final int DPID_BEGIN = 3;
125 private static final int OFPORT = 6653;
126
127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
128 protected CoreService coreService;
129
130 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
131 protected NetworkConfigRegistry configRegistry;
132
133 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
134 protected NetworkConfigService configService;
135
136 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
137 protected StorageService storageService;
138
139 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
140 protected DeviceAdminService adminService;
141
142 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
143 protected OvsdbController controller;
144
145 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
146 protected ClusterService clusterService;
147
148 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800149 protected DeviceService deviceService;
150
151 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
152 protected HostService hostService;
153
154 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
155 protected FlowRuleService flowRuleService;
156
157 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon98025542016-03-08 04:36:02 -0800158 protected LeadershipService leadershipService;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800159
160 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
161 protected GroupService groupService;
162
163 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
164 protected CordVtnService cordVtnService;
165
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800166 private final ExecutorService eventExecutor =
167 newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtncfg", "event-handler"));
168
169 private final NetworkConfigListener configListener = new InternalConfigListener();
170 private final DeviceListener deviceListener = new InternalDeviceListener();
Hyunsun Moon80b03872016-03-10 12:40:16 -0800171 private final MapEventListener<String, CordVtnNode> nodeStoreListener = new InternalMapListener();
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800172
173 private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
174 private final BridgeHandler bridgeHandler = new BridgeHandler();
175
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800176 private ConsistentMap<String, CordVtnNode> nodeStore;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800177 private CordVtnRuleInstaller ruleInstaller;
178 private ApplicationId appId;
Hyunsun Moon98025542016-03-08 04:36:02 -0800179 private NodeId localNodeId;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800180
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800181 private enum NodeState implements CordVtnNodeState {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800182
183 INIT {
184 @Override
185 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800186 if (!nodeManager.isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800187 nodeManager.connectOvsdb(node);
188 } else {
189 nodeManager.createIntegrationBridge(node);
190 }
191 }
192 },
193 BRIDGE_CREATED {
194 @Override
195 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800196 if (!nodeManager.isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800197 nodeManager.connectOvsdb(node);
198 } else {
199 nodeManager.createTunnelInterface(node);
Hyunsun Moon133fd792016-02-09 01:55:48 -0800200 nodeManager.addDataPlaneInterface(node);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800201 }
202 }
203 },
Hyunsun Moon133fd792016-02-09 01:55:48 -0800204 PORTS_ADDED {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800205 @Override
206 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800207 nodeManager.setIpAddress(node);
Hyunsun Moon177506f2016-01-21 00:54:52 -0800208 }
Hyunsun Moon177506f2016-01-21 00:54:52 -0800209 },
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800210 COMPLETE {
211 @Override
212 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
213 nodeManager.postInit(node);
214 }
215 },
216 INCOMPLETE {
217 @Override
218 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
219 }
220 };
221
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800222 public abstract void process(CordVtnNodeManager nodeManager, CordVtnNode node);
223 }
224
225 @Activate
226 protected void active() {
227 appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
Hyunsun Moon98025542016-03-08 04:36:02 -0800228 localNodeId = clusterService.getLocalNode().id();
229 leadershipService.runForLeadership(appId.name());
230
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800231 nodeStore = storageService.<String, CordVtnNode>consistentMapBuilder()
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800232 .withSerializer(Serializer.using(NODE_SERIALIZER.build()))
233 .withName("cordvtn-nodestore")
234 .withApplicationId(appId)
235 .build();
236
237 ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
238 deviceService,
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800239 groupService,
Hyunsun Moonfae776d2016-03-08 18:07:52 -0800240 configRegistry,
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800241 DEFAULT_TUNNEL);
242
Hyunsun Moon80b03872016-03-10 12:40:16 -0800243 nodeStore.addListener(nodeStoreListener);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800244 deviceService.addListener(deviceListener);
245 configService.addListener(configListener);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800246 }
247
248 @Deactivate
249 protected void deactivate() {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800250 configService.removeListener(configListener);
251 deviceService.removeListener(deviceListener);
252
Hyunsun Moon80b03872016-03-10 12:40:16 -0800253 nodeStore.removeListener(nodeStoreListener);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800254 nodeStore.clear();
Hyunsun Moon80b03872016-03-10 12:40:16 -0800255
Hyunsun Moon98025542016-03-08 04:36:02 -0800256 leadershipService.withdraw(appId.name());
Hyunsun Moon80b03872016-03-10 12:40:16 -0800257 eventExecutor.shutdown();
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800258 }
259
260 /**
Hyunsun Moon80b03872016-03-10 12:40:16 -0800261 * Adds or updates a new node to the service.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800262 *
263 * @param node cordvtn node
264 */
Hyunsun Moon80b03872016-03-10 12:40:16 -0800265 public void addOrUpdateNode(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800266 checkNotNull(node);
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800267 nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, getNodeState(node)));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800268 }
269
270 /**
271 * Deletes a node from the service.
272 *
273 * @param node cordvtn node
274 */
275 public void deleteNode(CordVtnNode node) {
276 checkNotNull(node);
277
Hyunsun Moon133fd792016-02-09 01:55:48 -0800278 if (isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800279 disconnectOvsdb(node);
280 }
281
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800282 nodeStore.remove(node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800283 }
284
285 /**
286 * Initiates node to serve virtual tenant network.
287 *
288 * @param node cordvtn node
289 */
Hyunsun Moon80b03872016-03-10 12:40:16 -0800290 private void initNode(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800291 checkNotNull(node);
292
Hyunsun Moon80b03872016-03-10 12:40:16 -0800293 NodeState state = (NodeState) node.state();
294 log.debug("Processing node: {} state: {}", node.hostname(), state);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800295
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800296 state.process(this, node);
297 }
298
299 /**
300 * Returns node initialization state.
301 *
302 * @param node cordvtn node
303 * @return true if initial node setup is completed, otherwise false
304 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800305 public boolean isNodeInitComplete(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800306 checkNotNull(node);
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800307 return nodeStore.containsKey(node.hostname()) && getNodeState(node).equals(NodeState.COMPLETE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800308 }
309
310 /**
Hyunsun Moon2062e7e2016-03-08 15:37:57 -0800311 * Flush flows installed by cordvtn.
312 */
313 public void flushRules() {
314 ruleInstaller.flushRules();
315 }
316
317 /**
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800318 * Returns if current node state saved in nodeStore is COMPLETE or not.
319 *
320 * @param node cordvtn node
321 * @return true if it's complete state, otherwise false
322 */
323 private boolean isNodeStateComplete(CordVtnNode node) {
324 checkNotNull(node);
325
326 // the state saved in nodeStore can be wrong if IP address settings are changed
327 // after the node init has been completed since there's no way to detect it
328 // getNodeState and checkNodeInitState always return correct answer but can be slow
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800329 Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
330 CordVtnNodeState state = versionedNode.value().state();
331 return state != null && state.equals(NodeState.COMPLETE);
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800332 }
333
334 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800335 * Returns detailed node initialization state.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800336 *
337 * @param node cordvtn node
338 * @return string including detailed node init state
339 */
340 public String checkNodeInitState(CordVtnNode node) {
341 checkNotNull(node);
342
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800343 if (!nodeStore.containsKey(node.hostname())) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800344 log.warn("Node {} does not exist, add node first", node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800345 return null;
346 }
347
Hyunsun Moon133fd792016-02-09 01:55:48 -0800348 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
349 if (session == null) {
350 log.debug("Failed to SSH to {}", node.hostname());
351 return null;
352 }
353
354 Set<IpAddress> intBrIps = RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800355 String result = String.format(
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800356 "br-int created and connected : %s (%s)%n" +
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800357 "VXLAN interface created : %s%n" +
Hyunsun Moon133fd792016-02-09 01:55:48 -0800358 "Data plane interface added : %s (%s)%n" +
359 "IP flushed from %s : %s%n" +
360 "Data plane IP added to br-int : %s (%s)%n" +
361 "Local management IP added to br-int : %s (%s)",
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800362 isBrIntCreated(node) ? OK : NO, node.intBrId(),
Hyunsun Moon133fd792016-02-09 01:55:48 -0800363 isTunnelIntfCreated(node) ? OK : NO,
364 isDataPlaneIntfAdded(node) ? OK : NO, node.dpIntf(),
365 node.dpIntf(),
366 RemoteIpCommandUtil.getCurrentIps(session, node.dpIntf()).isEmpty() ? OK : NO,
367 intBrIps.contains(node.dpIp().ip()) ? OK : NO, node.dpIp().cidr(),
368 intBrIps.contains(node.localMgmtIp().ip()) ? OK : NO, node.localMgmtIp().cidr());
369
370 RemoteIpCommandUtil.disconnect(session);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800371
372 return result;
373 }
374
375 /**
376 * Returns the number of the nodes known to the service.
377 *
378 * @return number of nodes
379 */
380 public int getNodeCount() {
381 return nodeStore.size();
382 }
383
384 /**
385 * Returns all nodes known to the service.
386 *
387 * @return list of nodes
388 */
389 public List<CordVtnNode> getNodes() {
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800390 return nodeStore.values().stream()
391 .map(Versioned::value)
392 .collect(Collectors.toList());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800393 }
394
395 /**
396 * Returns cordvtn node associated with a given OVSDB device.
397 *
398 * @param ovsdbId OVSDB device id
399 * @return cordvtn node, null if it fails to find the node
400 */
401 private CordVtnNode getNodeByOvsdbId(DeviceId ovsdbId) {
402 return getNodes().stream()
403 .filter(node -> node.ovsdbId().equals(ovsdbId))
404 .findFirst().orElse(null);
405 }
406
407 /**
408 * Returns cordvtn node associated with a given integration bridge.
409 *
410 * @param bridgeId device id of integration bridge
411 * @return cordvtn node, null if it fails to find the node
412 */
413 private CordVtnNode getNodeByBridgeId(DeviceId bridgeId) {
414 return getNodes().stream()
415 .filter(node -> node.intBrId().equals(bridgeId))
416 .findFirst().orElse(null);
417 }
418
419 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800420 * Sets a new state for a given cordvtn node.
421 *
422 * @param node cordvtn node
423 * @param newState new node state
424 */
425 private void setNodeState(CordVtnNode node, NodeState newState) {
426 checkNotNull(node);
427
Hyunsun Moon80b03872016-03-10 12:40:16 -0800428 log.debug("Changed {} state: {}", node.hostname(), newState);
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800429 nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, newState));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800430 }
431
432 /**
433 * Checks current state of a given cordvtn node and returns it.
434 *
435 * @param node cordvtn node
436 * @return node state
437 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800438 private NodeState getNodeState(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800439 checkNotNull(node);
440
Hyunsun Moon133fd792016-02-09 01:55:48 -0800441 if (isBrIntCreated(node) && isTunnelIntfCreated(node) &&
442 isDataPlaneIntfAdded(node) && isIpAddressSet(node)) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800443 return NodeState.COMPLETE;
Hyunsun Moon133fd792016-02-09 01:55:48 -0800444 } else if (isDataPlaneIntfAdded(node) && isTunnelIntfCreated(node)) {
445 return NodeState.PORTS_ADDED;
446 } else if (isBrIntCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800447 return NodeState.BRIDGE_CREATED;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800448 } else {
449 return NodeState.INIT;
450 }
451 }
452
453 /**
454 * Performs tasks after node initialization.
455 * It disconnects unnecessary OVSDB connection and installs initial flow
456 * rules on the device.
457 *
458 * @param node cordvtn node
459 */
460 private void postInit(CordVtnNode node) {
461 disconnectOvsdb(node);
462
Hyunsun Moon133fd792016-02-09 01:55:48 -0800463 ruleInstaller.init(node.intBrId(), node.dpIntf(), node.dpIp().ip());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800464
465 // add existing hosts to the service
466 deviceService.getPorts(node.intBrId()).stream()
467 .filter(port -> getPortName(port).startsWith(VPORT_PREFIX) &&
468 port.isEnabled())
469 .forEach(port -> cordVtnService.addServiceVm(node, getConnectPoint(port)));
470
471 // remove stale hosts from the service
472 hostService.getHosts().forEach(host -> {
473 Port port = deviceService.getPort(host.location().deviceId(), host.location().port());
474 if (port == null) {
475 cordVtnService.removeServiceVm(getConnectPoint(host));
476 }
477 });
478
479 log.info("Finished init {}", node.hostname());
480 }
481
482 /**
483 * Returns port name.
484 *
485 * @param port port
486 * @return port name
487 */
488 private String getPortName(Port port) {
489 return port.annotations().value("portName");
490 }
491
492 /**
493 * Returns connection state of OVSDB server for a given node.
494 *
495 * @param node cordvtn node
496 * @return true if it is connected, false otherwise
497 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800498 private boolean isOvsdbConnected(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800499 checkNotNull(node);
500
501 OvsdbClientService ovsdbClient = getOvsdbClient(node);
502 return deviceService.isAvailable(node.ovsdbId()) &&
503 ovsdbClient != null && ovsdbClient.isConnected();
504 }
505
506 /**
507 * Connects to OVSDB server for a given node.
508 *
509 * @param node cordvtn node
510 */
511 private void connectOvsdb(CordVtnNode node) {
512 checkNotNull(node);
513
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800514 if (!nodeStore.containsKey(node.hostname())) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800515 log.warn("Node {} does not exist", node.hostname());
516 return;
517 }
518
Hyunsun Moon133fd792016-02-09 01:55:48 -0800519 if (!isOvsdbConnected(node)) {
520 controller.connect(node.hostMgmtIp().ip(), node.ovsdbPort());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800521 }
522 }
523
524 /**
525 * Disconnects OVSDB server for a given node.
526 *
527 * @param node cordvtn node
528 */
529 private void disconnectOvsdb(CordVtnNode node) {
530 checkNotNull(node);
531
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800532 if (!nodeStore.containsKey(node.hostname())) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800533 log.warn("Node {} does not exist", node.hostname());
534 return;
535 }
536
Hyunsun Moon133fd792016-02-09 01:55:48 -0800537 if (isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800538 OvsdbClientService ovsdbClient = getOvsdbClient(node);
539 ovsdbClient.disconnect();
540 }
541 }
542
543 /**
544 * Returns OVSDB client for a given node.
545 *
546 * @param node cordvtn node
547 * @return OVSDB client, or null if it fails to get OVSDB client
548 */
549 private OvsdbClientService getOvsdbClient(CordVtnNode node) {
550 checkNotNull(node);
551
552 OvsdbClientService ovsdbClient = controller.getOvsdbClient(
Hyunsun Moon133fd792016-02-09 01:55:48 -0800553 new OvsdbNodeId(node.hostMgmtIp().ip(), node.ovsdbPort().toInt()));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800554 if (ovsdbClient == null) {
555 log.trace("Couldn't find OVSDB client for {}", node.hostname());
556 }
557 return ovsdbClient;
558 }
559
560 /**
561 * Creates an integration bridge for a given node.
562 *
563 * @param node cordvtn node
564 */
565 private void createIntegrationBridge(CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800566 if (isBrIntCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800567 return;
568 }
569
570 List<ControllerInfo> controllers = new ArrayList<>();
571 Sets.newHashSet(clusterService.getNodes()).stream()
572 .forEach(controller -> {
573 ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
574 controllers.add(ctrlInfo);
575 });
576
577 String dpid = node.intBrId().toString().substring(DPID_BEGIN);
578
579 try {
Jian Li7532eb12016-04-15 13:22:05 -0700580 Device device = deviceService.getDevice(node.ovsdbId());
581 if (device.is(BridgeConfig.class)) {
582 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
583 bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
584 } else {
585 log.warn("The bridging behaviour is not supported in device {}", device.id().toString());
586 }
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800587 } catch (ItemNotFoundException e) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800588 log.warn("Failed to create integration bridge on {}", node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800589 }
590 }
591
592 /**
593 * Creates tunnel interface to the integration bridge for a given node.
594 *
595 * @param node cordvtn node
596 */
597 private void createTunnelInterface(CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800598 if (isTunnelIntfCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800599 return;
600 }
601
602 DefaultAnnotations.Builder optionBuilder = DefaultAnnotations.builder();
603 for (String key : DEFAULT_TUNNEL_OPTIONS.keySet()) {
604 optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
605 }
606
607 TunnelDescription description = new DefaultTunnelDescription(
608 null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
609 optionBuilder.build());
610
611 try {
Jian Li7532eb12016-04-15 13:22:05 -0700612 Device device = deviceService.getDevice(node.ovsdbId());
613 if (device.is(TunnelConfig.class)) {
614 TunnelConfig tunnelConfig = device.as(TunnelConfig.class);
615 tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
616 } else {
617 log.warn("The tunneling behaviour is not supported in device {}", device.id().toString());
618 }
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800619 } catch (ItemNotFoundException e) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800620 log.warn("Failed to create tunnel interface on {}", node.hostname());
621 }
622 }
623
624 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800625 * Adds data plane interface to a given node.
Hyunsun Moon177506f2016-01-21 00:54:52 -0800626 *
627 * @param node cordvtn node
628 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800629 private void addDataPlaneInterface(CordVtnNode node) {
630 if (isDataPlaneIntfAdded(node)) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800631 return;
632 }
633
634 try {
Jian Li7532eb12016-04-15 13:22:05 -0700635 Device device = deviceService.getDevice(node.ovsdbId());
636 if (device.is(BridgeConfig.class)) {
637 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
638 bridgeConfig.addPort(BridgeName.bridgeName(DEFAULT_BRIDGE), node.dpIntf());
639 } else {
640 log.warn("The bridging behaviour is not supported in device {}", device.id().toString());
641 }
Hyunsun Moon177506f2016-01-21 00:54:52 -0800642 } catch (ItemNotFoundException e) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800643 log.warn("Failed to add {} on {}", node.dpIntf(), node.hostname());
644 }
645 }
646
647 /**
648 * Flushes IP address from data plane interface and adds data plane IP address
649 * to integration bridge.
650 *
651 * @param node cordvtn node
652 */
653 private void setIpAddress(CordVtnNode node) {
654 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
655 if (session == null) {
656 log.debug("Failed to SSH to {}", node.hostname());
657 return;
658 }
659
Hyunsun Moone9d4f4a2016-03-04 19:24:08 -0800660 RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE).stream()
661 .filter(ip -> !ip.equals(node.localMgmtIp().ip()))
662 .filter(ip -> !ip.equals(node.dpIp().ip()))
663 .forEach(ip -> RemoteIpCommandUtil.deleteIp(session, ip, DEFAULT_BRIDGE));
664
Hyunsun Moon133fd792016-02-09 01:55:48 -0800665 boolean result = RemoteIpCommandUtil.flushIp(session, node.dpIntf()) &&
666 RemoteIpCommandUtil.setInterfaceUp(session, node.dpIntf()) &&
667 RemoteIpCommandUtil.addIp(session, node.dpIp(), DEFAULT_BRIDGE) &&
668 RemoteIpCommandUtil.addIp(session, node.localMgmtIp(), DEFAULT_BRIDGE) &&
669 RemoteIpCommandUtil.setInterfaceUp(session, DEFAULT_BRIDGE);
670
671 RemoteIpCommandUtil.disconnect(session);
672
673 if (result) {
674 setNodeState(node, NodeState.COMPLETE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800675 }
676 }
677
678 /**
679 * Checks if integration bridge exists and available.
680 *
681 * @param node cordvtn node
682 * @return true if the bridge is available, false otherwise
683 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800684 private boolean isBrIntCreated(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800685 return (deviceService.getDevice(node.intBrId()) != null
686 && deviceService.isAvailable(node.intBrId()));
687 }
688
689 /**
690 * Checks if tunnel interface exists.
691 *
692 * @param node cordvtn node
693 * @return true if the interface exists, false otherwise
694 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800695 private boolean isTunnelIntfCreated(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800696 return deviceService.getPorts(node.intBrId())
697 .stream()
698 .filter(p -> getPortName(p).contains(DEFAULT_TUNNEL) &&
699 p.isEnabled())
700 .findAny().isPresent();
701 }
702
703 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800704 * Checks if data plane interface exists.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800705 *
706 * @param node cordvtn node
707 * @return true if the interface exists, false otherwise
708 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800709 private boolean isDataPlaneIntfAdded(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800710 return deviceService.getPorts(node.intBrId())
711 .stream()
Hyunsun Moon133fd792016-02-09 01:55:48 -0800712 .filter(p -> getPortName(p).contains(node.dpIntf()) &&
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800713 p.isEnabled())
714 .findAny().isPresent();
715 }
716
717 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800718 * Checks if the IP addresses are correctly set.
719 *
720 * @param node cordvtn node
721 * @return true if the IP is set, false otherwise
722 */
723 private boolean isIpAddressSet(CordVtnNode node) {
724 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
725 if (session == null) {
726 log.debug("Failed to SSH to {}", node.hostname());
727 return false;
728 }
729
730 Set<IpAddress> intBrIps = RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE);
731 boolean result = RemoteIpCommandUtil.getCurrentIps(session, node.dpIntf()).isEmpty() &&
732 RemoteIpCommandUtil.isInterfaceUp(session, node.dpIntf()) &&
733 intBrIps.contains(node.dpIp().ip()) &&
734 intBrIps.contains(node.localMgmtIp().ip()) &&
735 RemoteIpCommandUtil.isInterfaceUp(session, DEFAULT_BRIDGE);
736
737 RemoteIpCommandUtil.disconnect(session);
738 return result;
739 }
740
741 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800742 * Returns connect point of a given port.
743 *
744 * @param port port
745 * @return connect point
746 */
747 private ConnectPoint getConnectPoint(Port port) {
748 return new ConnectPoint(port.element().id(), port.number());
749 }
750
751 /**
752 * Returns connect point of a given host.
753 *
754 * @param host host
755 * @return connect point
756 */
757 private ConnectPoint getConnectPoint(Host host) {
758 return new ConnectPoint(host.location().deviceId(), host.location().port());
759 }
760
761 private class OvsdbHandler implements ConnectionHandler<Device> {
762
763 @Override
764 public void connected(Device device) {
765 CordVtnNode node = getNodeByOvsdbId(device.id());
766 if (node != null) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800767 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800768 } else {
769 log.debug("{} is detected on unregistered node, ignore it.", device.id());
770 }
771 }
772
773 @Override
774 public void disconnected(Device device) {
775 if (!deviceService.isAvailable(device.id())) {
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800776 log.debug("Device {} is disconnected", device.id());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800777 adminService.removeDevice(device.id());
778 }
779 }
780 }
781
782 private class BridgeHandler implements ConnectionHandler<Device> {
783
784 @Override
785 public void connected(Device device) {
786 CordVtnNode node = getNodeByBridgeId(device.id());
787 if (node != null) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800788 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800789 } else {
790 log.debug("{} is detected on unregistered node, ignore it.", device.id());
791 }
792 }
793
794 @Override
795 public void disconnected(Device device) {
796 CordVtnNode node = getNodeByBridgeId(device.id());
797 if (node != null) {
798 log.debug("Integration Bridge is disconnected from {}", node.hostname());
799 setNodeState(node, NodeState.INCOMPLETE);
800 }
801 }
802
803 /**
804 * Handles port added situation.
Hyunsun Moon133fd792016-02-09 01:55:48 -0800805 * If the added port is tunnel or data plane interface, proceed to the remaining
806 * node initialization. Otherwise, do nothing.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800807 *
808 * @param port port
809 */
810 public void portAdded(Port port) {
811 CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
812 String portName = getPortName(port);
813
814 if (node == null) {
815 log.debug("{} is added to unregistered node, ignore it.", portName);
816 return;
817 }
818
Hyunsun Moon80b03872016-03-10 12:40:16 -0800819 log.info("Port {} is added to {}", portName, node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800820
821 if (portName.startsWith(VPORT_PREFIX)) {
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800822 if (isNodeStateComplete(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800823 cordVtnService.addServiceVm(node, getConnectPoint(port));
824 } else {
825 log.debug("VM is detected on incomplete node, ignore it.", portName);
826 }
Hyunsun Moon133fd792016-02-09 01:55:48 -0800827 } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
828 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800829 }
830 }
831
832 /**
833 * Handles port removed situation.
Hyunsun Moon133fd792016-02-09 01:55:48 -0800834 * If the removed port is tunnel or data plane interface, proceed to the remaining
835 * node initialization.Others, do nothing.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800836 *
837 * @param port port
838 */
839 public void portRemoved(Port port) {
840 CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
841 String portName = getPortName(port);
842
843 if (node == null) {
844 return;
845 }
846
Hyunsun Moon80b03872016-03-10 12:40:16 -0800847 log.info("Port {} is removed from {}", portName, node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800848
849 if (portName.startsWith(VPORT_PREFIX)) {
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800850 if (isNodeStateComplete(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800851 cordVtnService.removeServiceVm(getConnectPoint(port));
852 } else {
853 log.debug("VM is vanished from incomplete node, ignore it.", portName);
854 }
Hyunsun Moon133fd792016-02-09 01:55:48 -0800855 } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800856 setNodeState(node, NodeState.INCOMPLETE);
857 }
858 }
859 }
860
861 private class InternalDeviceListener implements DeviceListener {
862
863 @Override
864 public void event(DeviceEvent event) {
865
Hyunsun Moon98025542016-03-08 04:36:02 -0800866 NodeId leaderNodeId = leadershipService.getLeader(appId.name());
867 if (!Objects.equals(localNodeId, leaderNodeId)) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800868 // do not allow to proceed without leadership
Hyunsun Moon98025542016-03-08 04:36:02 -0800869 return;
870 }
871
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800872 Device device = event.subject();
873 ConnectionHandler<Device> handler =
874 (device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
875
876 switch (event.type()) {
877 case PORT_ADDED:
Hyunsun Moon80b03872016-03-10 12:40:16 -0800878 eventExecutor.execute(() -> bridgeHandler.portAdded(event.port()));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800879 break;
880 case PORT_UPDATED:
881 if (!event.port().isEnabled()) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800882 eventExecutor.execute(() -> bridgeHandler.portRemoved(event.port()));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800883 }
884 break;
885 case DEVICE_ADDED:
886 case DEVICE_AVAILABILITY_CHANGED:
887 if (deviceService.isAvailable(device.id())) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800888 eventExecutor.execute(() -> handler.connected(device));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800889 } else {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800890 eventExecutor.execute(() -> handler.disconnected(device));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800891 }
892 break;
893 default:
894 break;
895 }
896 }
897 }
898
899 /**
Hyunsun Moon746956f2016-01-24 21:47:06 -0800900 * Reads cordvtn nodes from config file.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800901 */
902 private void readConfiguration() {
903 CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800904 if (config == null) {
Hyunsun Moon746956f2016-01-24 21:47:06 -0800905 log.debug("No configuration found");
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800906 return;
907 }
908
Hyunsun Moon80b03872016-03-10 12:40:16 -0800909 config.cordVtnNodes().forEach(this::addOrUpdateNode);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800910 }
911
912 private class InternalConfigListener implements NetworkConfigListener {
913
914 @Override
915 public void event(NetworkConfigEvent event) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800916 NodeId leaderNodeId = leadershipService.getLeader(appId.name());
917 if (!Objects.equals(localNodeId, leaderNodeId)) {
918 // do not allow to proceed without leadership
919 return;
920 }
921
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800922 if (!event.configClass().equals(CordVtnConfig.class)) {
923 return;
924 }
925
926 switch (event.type()) {
927 case CONFIG_ADDED:
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800928 case CONFIG_UPDATED:
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800929 eventExecutor.execute(CordVtnNodeManager.this::readConfiguration);
930 break;
931 default:
932 break;
933 }
934 }
935 }
Hyunsun Moon80b03872016-03-10 12:40:16 -0800936
937 private class InternalMapListener implements MapEventListener<String, CordVtnNode> {
938
939 @Override
940 public void event(MapEvent<String, CordVtnNode> event) {
941 NodeId leaderNodeId = leadershipService.getLeader(appId.name());
942 if (!Objects.equals(localNodeId, leaderNodeId)) {
943 // do not allow to proceed without leadership
944 return;
945 }
946
947 CordVtnNode oldNode;
948 CordVtnNode newNode;
949
950 switch (event.type()) {
951 case UPDATE:
952 oldNode = event.oldValue().value();
953 newNode = event.newValue().value();
954
955 if (!newNode.equals(oldNode)) {
956 log.info("{} has been updated", newNode.hostname());
957 log.debug("New node: {}", newNode);
958 }
959 // perform init procedure based on current state on any updates,
960 // insert, or even if the node is the same for robustness since
961 // it's no harm to run the init procedure multiple times
962 eventExecutor.execute(() -> initNode(newNode));
963 break;
964 case INSERT:
965 newNode = event.newValue().value();
966 log.info("Added {}", newNode.hostname());
967 eventExecutor.execute(() -> initNode(newNode));
968 break;
969 case REMOVE:
970 oldNode = event.oldValue().value();
971 log.info("{} is removed", oldNode.hostname());
972 break;
973 default:
974 break;
975 }
976 }
977 }
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800978}