blob: 4802db8598705245c9742ca27e30eeb1ef801f0a [file] [log] [blame]
Hyunsun Moonb77b60f2016-01-15 20:03:18 -08001/*
2 * Copyright 2014-2015 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 */
16package org.onosproject.cordvtn;
17
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;
30import org.onosproject.core.ApplicationId;
31import org.onosproject.core.CoreService;
32import org.onosproject.mastership.MastershipService;
33import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.DefaultAnnotations;
35import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.Host;
38import org.onosproject.net.Port;
39import org.onosproject.net.behaviour.BridgeConfig;
40import org.onosproject.net.behaviour.BridgeName;
41import org.onosproject.net.behaviour.ControllerInfo;
42import org.onosproject.net.behaviour.DefaultTunnelDescription;
43import org.onosproject.net.behaviour.TunnelConfig;
44import org.onosproject.net.behaviour.TunnelDescription;
45import org.onosproject.net.behaviour.TunnelName;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080046import org.onosproject.net.config.NetworkConfigEvent;
47import org.onosproject.net.config.NetworkConfigListener;
48import org.onosproject.net.config.NetworkConfigRegistry;
49import org.onosproject.net.config.NetworkConfigService;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080050import org.onosproject.net.device.DeviceAdminService;
51import org.onosproject.net.device.DeviceEvent;
52import org.onosproject.net.device.DeviceListener;
53import org.onosproject.net.device.DeviceService;
54import org.onosproject.net.driver.DriverHandler;
55import org.onosproject.net.driver.DriverService;
56import org.onosproject.net.flow.FlowRuleService;
57import org.onosproject.net.group.GroupService;
58import org.onosproject.net.host.HostService;
59import org.onosproject.ovsdb.controller.OvsdbClientService;
60import org.onosproject.ovsdb.controller.OvsdbController;
61import org.onosproject.ovsdb.controller.OvsdbNodeId;
62import org.onosproject.store.serializers.KryoNamespaces;
63import org.onosproject.store.service.ConsistentMap;
64import org.onosproject.store.service.Serializer;
65import org.onosproject.store.service.StorageService;
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -080066import org.onosproject.store.service.Versioned;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080067import org.slf4j.Logger;
68
69import java.util.ArrayList;
70import java.util.HashMap;
71import java.util.List;
72import java.util.Map;
Hyunsun Moon133fd792016-02-09 01:55:48 -080073import java.util.Set;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080074import java.util.concurrent.ExecutorService;
75
76import static com.google.common.base.Preconditions.checkNotNull;
77import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
78import static org.onlab.util.Tools.groupedThreads;
79import static org.onosproject.net.Device.Type.SWITCH;
80import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
81import static org.slf4j.LoggerFactory.getLogger;
82
83/**
84 * Reads node information from the network config file and handles the config
85 * update events.
86 * Only a leader controller performs the node addition or deletion.
87 */
88@Component(immediate = true)
89@Service(value = CordVtnNodeManager.class)
90public class CordVtnNodeManager {
91
92 protected final Logger log = getLogger(getClass());
93
94 private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
95 .register(KryoNamespaces.API)
Hyunsun Moon133fd792016-02-09 01:55:48 -080096 .register(KryoNamespaces.MISC)
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080097 .register(CordVtnNode.class)
Hyunsun Moon133fd792016-02-09 01:55:48 -080098 .register(NodeState.class)
99 .register(SshAccessInfo.class)
100 .register(NetworkAddress.class);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800101
102 private static final String DEFAULT_BRIDGE = "br-int";
103 private static final String DEFAULT_TUNNEL = "vxlan";
104 private static final String VPORT_PREFIX = "tap";
105 private static final String OK = "OK";
106 private static final String NO = "NO";
107
108 private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS = new HashMap<String, String>() {
109 {
110 put("key", "flow");
111 put("remote_ip", "flow");
112 }
113 };
114 private static final int DPID_BEGIN = 3;
115 private static final int OFPORT = 6653;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 protected CoreService coreService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121 protected NetworkConfigRegistry configRegistry;
122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 protected NetworkConfigService configService;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
127 protected StorageService storageService;
128
129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
130 protected DeviceAdminService adminService;
131
132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
133 protected OvsdbController controller;
134
135 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
136 protected ClusterService clusterService;
137
138 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
139 protected DriverService driverService;
140
141 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
142 protected DeviceService deviceService;
143
144 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
145 protected HostService hostService;
146
147 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
148 protected FlowRuleService flowRuleService;
149
150 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
151 protected MastershipService mastershipService;
152
153 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
154 protected GroupService groupService;
155
156 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
157 protected CordVtnService cordVtnService;
158
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800159 private final ExecutorService eventExecutor =
160 newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtncfg", "event-handler"));
161
162 private final NetworkConfigListener configListener = new InternalConfigListener();
163 private final DeviceListener deviceListener = new InternalDeviceListener();
164
165 private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
166 private final BridgeHandler bridgeHandler = new BridgeHandler();
167
168 private ConsistentMap<CordVtnNode, NodeState> nodeStore;
169 private CordVtnRuleInstaller ruleInstaller;
170 private ApplicationId appId;
171
172 private enum NodeState {
173
174 INIT {
175 @Override
176 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800177 if (!nodeManager.isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800178 nodeManager.connectOvsdb(node);
179 } else {
180 nodeManager.createIntegrationBridge(node);
181 }
182 }
183 },
184 BRIDGE_CREATED {
185 @Override
186 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800187 if (!nodeManager.isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800188 nodeManager.connectOvsdb(node);
189 } else {
190 nodeManager.createTunnelInterface(node);
Hyunsun Moon133fd792016-02-09 01:55:48 -0800191 nodeManager.addDataPlaneInterface(node);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800192 }
193 }
194 },
Hyunsun Moon133fd792016-02-09 01:55:48 -0800195 PORTS_ADDED {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800196 @Override
197 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800198 nodeManager.setIpAddress(node);
Hyunsun Moon177506f2016-01-21 00:54:52 -0800199 }
200
201 },
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800202 COMPLETE {
203 @Override
204 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
205 nodeManager.postInit(node);
206 }
207 },
208 INCOMPLETE {
209 @Override
210 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
211 }
212 };
213
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800214 public abstract void process(CordVtnNodeManager nodeManager, CordVtnNode node);
215 }
216
217 @Activate
218 protected void active() {
219 appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800220 nodeStore = storageService.<CordVtnNode, NodeState>consistentMapBuilder()
221 .withSerializer(Serializer.using(NODE_SERIALIZER.build()))
222 .withName("cordvtn-nodestore")
223 .withApplicationId(appId)
224 .build();
225
226 ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
227 deviceService,
228 driverService,
229 groupService,
230 mastershipService,
231 DEFAULT_TUNNEL);
232
233 deviceService.addListener(deviceListener);
234 configService.addListener(configListener);
Hyunsun Moon746956f2016-01-24 21:47:06 -0800235 readConfiguration();
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800236 }
237
238 @Deactivate
239 protected void deactivate() {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800240 configService.removeListener(configListener);
241 deviceService.removeListener(deviceListener);
242
243 eventExecutor.shutdown();
244 nodeStore.clear();
245 }
246
247 /**
248 * Adds a new node to the service.
249 *
250 * @param node cordvtn node
251 */
252 public void addNode(CordVtnNode node) {
253 checkNotNull(node);
254
Hyunsun Moon133fd792016-02-09 01:55:48 -0800255 nodeStore.putIfAbsent(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800256 initNode(node);
257 }
258
259 /**
260 * Deletes a node from the service.
261 *
262 * @param node cordvtn node
263 */
264 public void deleteNode(CordVtnNode node) {
265 checkNotNull(node);
266
Hyunsun Moon133fd792016-02-09 01:55:48 -0800267 if (isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800268 disconnectOvsdb(node);
269 }
270
271 nodeStore.remove(node);
272 }
273
274 /**
275 * Initiates node to serve virtual tenant network.
276 *
277 * @param node cordvtn node
278 */
279 public void initNode(CordVtnNode node) {
280 checkNotNull(node);
281
282 if (!nodeStore.containsKey(node)) {
283 log.warn("Node {} does not exist, add node first", node.hostname());
284 return;
285 }
286
Hyunsun Moon133fd792016-02-09 01:55:48 -0800287 NodeState state = getNodeState(node);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800288 state.process(this, node);
289 }
290
291 /**
292 * Returns node initialization state.
293 *
294 * @param node cordvtn node
295 * @return true if initial node setup is completed, otherwise false
296 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800297 public boolean isNodeInitComplete(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800298 checkNotNull(node);
Hyunsun Moon133fd792016-02-09 01:55:48 -0800299 return nodeStore.containsKey(node) && getNodeState(node).equals(NodeState.COMPLETE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800300 }
301
302 /**
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800303 * Returns if current node state saved in nodeStore is COMPLETE or not.
304 *
305 * @param node cordvtn node
306 * @return true if it's complete state, otherwise false
307 */
308 private boolean isNodeStateComplete(CordVtnNode node) {
309 checkNotNull(node);
310
311 // the state saved in nodeStore can be wrong if IP address settings are changed
312 // after the node init has been completed since there's no way to detect it
313 // getNodeState and checkNodeInitState always return correct answer but can be slow
314 Versioned<NodeState> state = nodeStore.get(node);
315 return state != null && state.value().equals(NodeState.COMPLETE);
316 }
317
318 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800319 * Returns detailed node initialization state.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800320 *
321 * @param node cordvtn node
322 * @return string including detailed node init state
323 */
324 public String checkNodeInitState(CordVtnNode node) {
325 checkNotNull(node);
326
Hyunsun Moon133fd792016-02-09 01:55:48 -0800327 if (!nodeStore.containsKey(node)) {
328 log.warn("Node {} does not exist, add node first", node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800329 return null;
330 }
331
Hyunsun Moon133fd792016-02-09 01:55:48 -0800332 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
333 if (session == null) {
334 log.debug("Failed to SSH to {}", node.hostname());
335 return null;
336 }
337
338 Set<IpAddress> intBrIps = RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800339 String result = String.format(
340 "Integration bridge created/connected : %s (%s)%n" +
341 "VXLAN interface created : %s%n" +
Hyunsun Moon133fd792016-02-09 01:55:48 -0800342 "Data plane interface added : %s (%s)%n" +
343 "IP flushed from %s : %s%n" +
344 "Data plane IP added to br-int : %s (%s)%n" +
345 "Local management IP added to br-int : %s (%s)",
346 isBrIntCreated(node) ? OK : NO, DEFAULT_BRIDGE,
347 isTunnelIntfCreated(node) ? OK : NO,
348 isDataPlaneIntfAdded(node) ? OK : NO, node.dpIntf(),
349 node.dpIntf(),
350 RemoteIpCommandUtil.getCurrentIps(session, node.dpIntf()).isEmpty() ? OK : NO,
351 intBrIps.contains(node.dpIp().ip()) ? OK : NO, node.dpIp().cidr(),
352 intBrIps.contains(node.localMgmtIp().ip()) ? OK : NO, node.localMgmtIp().cidr());
353
354 RemoteIpCommandUtil.disconnect(session);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800355
356 return result;
357 }
358
359 /**
360 * Returns the number of the nodes known to the service.
361 *
362 * @return number of nodes
363 */
364 public int getNodeCount() {
365 return nodeStore.size();
366 }
367
368 /**
369 * Returns all nodes known to the service.
370 *
371 * @return list of nodes
372 */
373 public List<CordVtnNode> getNodes() {
374 List<CordVtnNode> nodes = new ArrayList<>();
375 nodes.addAll(nodeStore.keySet());
376 return nodes;
377 }
378
379 /**
380 * Returns cordvtn node associated with a given OVSDB device.
381 *
382 * @param ovsdbId OVSDB device id
383 * @return cordvtn node, null if it fails to find the node
384 */
385 private CordVtnNode getNodeByOvsdbId(DeviceId ovsdbId) {
386 return getNodes().stream()
387 .filter(node -> node.ovsdbId().equals(ovsdbId))
388 .findFirst().orElse(null);
389 }
390
391 /**
392 * Returns cordvtn node associated with a given integration bridge.
393 *
394 * @param bridgeId device id of integration bridge
395 * @return cordvtn node, null if it fails to find the node
396 */
397 private CordVtnNode getNodeByBridgeId(DeviceId bridgeId) {
398 return getNodes().stream()
399 .filter(node -> node.intBrId().equals(bridgeId))
400 .findFirst().orElse(null);
401 }
402
403 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800404 * Sets a new state for a given cordvtn node.
405 *
406 * @param node cordvtn node
407 * @param newState new node state
408 */
409 private void setNodeState(CordVtnNode node, NodeState newState) {
410 checkNotNull(node);
411
412 log.debug("Changed {} state: {}", node.hostname(), newState.toString());
413
414 nodeStore.put(node, newState);
415 newState.process(this, node);
416 }
417
418 /**
419 * Checks current state of a given cordvtn node and returns it.
420 *
421 * @param node cordvtn node
422 * @return node state
423 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800424 private NodeState getNodeState(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800425 checkNotNull(node);
426
Hyunsun Moon133fd792016-02-09 01:55:48 -0800427 if (isBrIntCreated(node) && isTunnelIntfCreated(node) &&
428 isDataPlaneIntfAdded(node) && isIpAddressSet(node)) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800429 return NodeState.COMPLETE;
Hyunsun Moon133fd792016-02-09 01:55:48 -0800430 } else if (isDataPlaneIntfAdded(node) && isTunnelIntfCreated(node)) {
431 return NodeState.PORTS_ADDED;
432 } else if (isBrIntCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800433 return NodeState.BRIDGE_CREATED;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800434 } else {
435 return NodeState.INIT;
436 }
437 }
438
439 /**
440 * Performs tasks after node initialization.
441 * It disconnects unnecessary OVSDB connection and installs initial flow
442 * rules on the device.
443 *
444 * @param node cordvtn node
445 */
446 private void postInit(CordVtnNode node) {
447 disconnectOvsdb(node);
448
Hyunsun Moon133fd792016-02-09 01:55:48 -0800449 ruleInstaller.init(node.intBrId(), node.dpIntf(), node.dpIp().ip());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800450
451 // add existing hosts to the service
452 deviceService.getPorts(node.intBrId()).stream()
453 .filter(port -> getPortName(port).startsWith(VPORT_PREFIX) &&
454 port.isEnabled())
455 .forEach(port -> cordVtnService.addServiceVm(node, getConnectPoint(port)));
456
457 // remove stale hosts from the service
458 hostService.getHosts().forEach(host -> {
459 Port port = deviceService.getPort(host.location().deviceId(), host.location().port());
460 if (port == null) {
461 cordVtnService.removeServiceVm(getConnectPoint(host));
462 }
463 });
464
465 log.info("Finished init {}", node.hostname());
466 }
467
468 /**
469 * Returns port name.
470 *
471 * @param port port
472 * @return port name
473 */
474 private String getPortName(Port port) {
475 return port.annotations().value("portName");
476 }
477
478 /**
479 * Returns connection state of OVSDB server for a given node.
480 *
481 * @param node cordvtn node
482 * @return true if it is connected, false otherwise
483 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800484 private boolean isOvsdbConnected(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800485 checkNotNull(node);
486
487 OvsdbClientService ovsdbClient = getOvsdbClient(node);
488 return deviceService.isAvailable(node.ovsdbId()) &&
489 ovsdbClient != null && ovsdbClient.isConnected();
490 }
491
492 /**
493 * Connects to OVSDB server for a given node.
494 *
495 * @param node cordvtn node
496 */
497 private void connectOvsdb(CordVtnNode node) {
498 checkNotNull(node);
499
500 if (!nodeStore.containsKey(node)) {
501 log.warn("Node {} does not exist", node.hostname());
502 return;
503 }
504
Hyunsun Moon133fd792016-02-09 01:55:48 -0800505 if (!isOvsdbConnected(node)) {
506 controller.connect(node.hostMgmtIp().ip(), node.ovsdbPort());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800507 }
508 }
509
510 /**
511 * Disconnects OVSDB server for a given node.
512 *
513 * @param node cordvtn node
514 */
515 private void disconnectOvsdb(CordVtnNode node) {
516 checkNotNull(node);
517
518 if (!nodeStore.containsKey(node)) {
519 log.warn("Node {} does not exist", node.hostname());
520 return;
521 }
522
Hyunsun Moon133fd792016-02-09 01:55:48 -0800523 if (isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800524 OvsdbClientService ovsdbClient = getOvsdbClient(node);
525 ovsdbClient.disconnect();
526 }
527 }
528
529 /**
530 * Returns OVSDB client for a given node.
531 *
532 * @param node cordvtn node
533 * @return OVSDB client, or null if it fails to get OVSDB client
534 */
535 private OvsdbClientService getOvsdbClient(CordVtnNode node) {
536 checkNotNull(node);
537
538 OvsdbClientService ovsdbClient = controller.getOvsdbClient(
Hyunsun Moon133fd792016-02-09 01:55:48 -0800539 new OvsdbNodeId(node.hostMgmtIp().ip(), node.ovsdbPort().toInt()));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800540 if (ovsdbClient == null) {
541 log.trace("Couldn't find OVSDB client for {}", node.hostname());
542 }
543 return ovsdbClient;
544 }
545
546 /**
547 * Creates an integration bridge for a given node.
548 *
549 * @param node cordvtn node
550 */
551 private void createIntegrationBridge(CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800552 if (isBrIntCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800553 return;
554 }
555
556 List<ControllerInfo> controllers = new ArrayList<>();
557 Sets.newHashSet(clusterService.getNodes()).stream()
558 .forEach(controller -> {
559 ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
560 controllers.add(ctrlInfo);
561 });
562
563 String dpid = node.intBrId().toString().substring(DPID_BEGIN);
564
565 try {
566 DriverHandler handler = driverService.createHandler(node.ovsdbId());
567 BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class);
568 bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
569 } catch (ItemNotFoundException e) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800570 log.warn("Failed to create integration bridge on {}", node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800571 }
572 }
573
574 /**
575 * Creates tunnel interface to the integration bridge for a given node.
576 *
577 * @param node cordvtn node
578 */
579 private void createTunnelInterface(CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800580 if (isTunnelIntfCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800581 return;
582 }
583
584 DefaultAnnotations.Builder optionBuilder = DefaultAnnotations.builder();
585 for (String key : DEFAULT_TUNNEL_OPTIONS.keySet()) {
586 optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
587 }
588
589 TunnelDescription description = new DefaultTunnelDescription(
590 null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
591 optionBuilder.build());
592
593 try {
594 DriverHandler handler = driverService.createHandler(node.ovsdbId());
595 TunnelConfig tunnelConfig = handler.behaviour(TunnelConfig.class);
596 tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
597 } catch (ItemNotFoundException e) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800598 log.warn("Failed to create tunnel interface on {}", node.hostname());
599 }
600 }
601
602 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800603 * Adds data plane interface to a given node.
Hyunsun Moon177506f2016-01-21 00:54:52 -0800604 *
605 * @param node cordvtn node
606 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800607 private void addDataPlaneInterface(CordVtnNode node) {
608 if (isDataPlaneIntfAdded(node)) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800609 return;
610 }
611
612 try {
613 DriverHandler handler = driverService.createHandler(node.ovsdbId());
614 BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class);
Hyunsun Moon133fd792016-02-09 01:55:48 -0800615 bridgeConfig.addPort(BridgeName.bridgeName(DEFAULT_BRIDGE), node.dpIntf());
Hyunsun Moon177506f2016-01-21 00:54:52 -0800616 } catch (ItemNotFoundException e) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800617 log.warn("Failed to add {} on {}", node.dpIntf(), node.hostname());
618 }
619 }
620
621 /**
622 * Flushes IP address from data plane interface and adds data plane IP address
623 * to integration bridge.
624 *
625 * @param node cordvtn node
626 */
627 private void setIpAddress(CordVtnNode node) {
628 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
629 if (session == null) {
630 log.debug("Failed to SSH to {}", node.hostname());
631 return;
632 }
633
Hyunsun Moone9d4f4a2016-03-04 19:24:08 -0800634 RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE).stream()
635 .filter(ip -> !ip.equals(node.localMgmtIp().ip()))
636 .filter(ip -> !ip.equals(node.dpIp().ip()))
637 .forEach(ip -> RemoteIpCommandUtil.deleteIp(session, ip, DEFAULT_BRIDGE));
638
Hyunsun Moon133fd792016-02-09 01:55:48 -0800639 boolean result = RemoteIpCommandUtil.flushIp(session, node.dpIntf()) &&
640 RemoteIpCommandUtil.setInterfaceUp(session, node.dpIntf()) &&
641 RemoteIpCommandUtil.addIp(session, node.dpIp(), DEFAULT_BRIDGE) &&
642 RemoteIpCommandUtil.addIp(session, node.localMgmtIp(), DEFAULT_BRIDGE) &&
643 RemoteIpCommandUtil.setInterfaceUp(session, DEFAULT_BRIDGE);
644
645 RemoteIpCommandUtil.disconnect(session);
646
647 if (result) {
648 setNodeState(node, NodeState.COMPLETE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800649 }
650 }
651
652 /**
653 * Checks if integration bridge exists and available.
654 *
655 * @param node cordvtn node
656 * @return true if the bridge is available, false otherwise
657 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800658 private boolean isBrIntCreated(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800659 return (deviceService.getDevice(node.intBrId()) != null
660 && deviceService.isAvailable(node.intBrId()));
661 }
662
663 /**
664 * Checks if tunnel interface exists.
665 *
666 * @param node cordvtn node
667 * @return true if the interface exists, false otherwise
668 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800669 private boolean isTunnelIntfCreated(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800670 return deviceService.getPorts(node.intBrId())
671 .stream()
672 .filter(p -> getPortName(p).contains(DEFAULT_TUNNEL) &&
673 p.isEnabled())
674 .findAny().isPresent();
675 }
676
677 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800678 * Checks if data plane interface exists.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800679 *
680 * @param node cordvtn node
681 * @return true if the interface exists, false otherwise
682 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800683 private boolean isDataPlaneIntfAdded(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800684 return deviceService.getPorts(node.intBrId())
685 .stream()
Hyunsun Moon133fd792016-02-09 01:55:48 -0800686 .filter(p -> getPortName(p).contains(node.dpIntf()) &&
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800687 p.isEnabled())
688 .findAny().isPresent();
689 }
690
691 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800692 * Checks if the IP addresses are correctly set.
693 *
694 * @param node cordvtn node
695 * @return true if the IP is set, false otherwise
696 */
697 private boolean isIpAddressSet(CordVtnNode node) {
698 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
699 if (session == null) {
700 log.debug("Failed to SSH to {}", node.hostname());
701 return false;
702 }
703
704 Set<IpAddress> intBrIps = RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE);
705 boolean result = RemoteIpCommandUtil.getCurrentIps(session, node.dpIntf()).isEmpty() &&
706 RemoteIpCommandUtil.isInterfaceUp(session, node.dpIntf()) &&
707 intBrIps.contains(node.dpIp().ip()) &&
708 intBrIps.contains(node.localMgmtIp().ip()) &&
709 RemoteIpCommandUtil.isInterfaceUp(session, DEFAULT_BRIDGE);
710
711 RemoteIpCommandUtil.disconnect(session);
712 return result;
713 }
714
715 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800716 * Returns connect point of a given port.
717 *
718 * @param port port
719 * @return connect point
720 */
721 private ConnectPoint getConnectPoint(Port port) {
722 return new ConnectPoint(port.element().id(), port.number());
723 }
724
725 /**
726 * Returns connect point of a given host.
727 *
728 * @param host host
729 * @return connect point
730 */
731 private ConnectPoint getConnectPoint(Host host) {
732 return new ConnectPoint(host.location().deviceId(), host.location().port());
733 }
734
735 private class OvsdbHandler implements ConnectionHandler<Device> {
736
737 @Override
738 public void connected(Device device) {
739 CordVtnNode node = getNodeByOvsdbId(device.id());
740 if (node != null) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800741 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800742 } else {
743 log.debug("{} is detected on unregistered node, ignore it.", device.id());
744 }
745 }
746
747 @Override
748 public void disconnected(Device device) {
749 if (!deviceService.isAvailable(device.id())) {
750 adminService.removeDevice(device.id());
751 }
752 }
753 }
754
755 private class BridgeHandler implements ConnectionHandler<Device> {
756
757 @Override
758 public void connected(Device device) {
759 CordVtnNode node = getNodeByBridgeId(device.id());
760 if (node != null) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800761 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800762 } else {
763 log.debug("{} is detected on unregistered node, ignore it.", device.id());
764 }
765 }
766
767 @Override
768 public void disconnected(Device device) {
769 CordVtnNode node = getNodeByBridgeId(device.id());
770 if (node != null) {
771 log.debug("Integration Bridge is disconnected from {}", node.hostname());
772 setNodeState(node, NodeState.INCOMPLETE);
773 }
774 }
775
776 /**
777 * Handles port added situation.
Hyunsun Moon133fd792016-02-09 01:55:48 -0800778 * If the added port is tunnel or data plane interface, proceed to the remaining
779 * node initialization. Otherwise, do nothing.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800780 *
781 * @param port port
782 */
783 public void portAdded(Port port) {
784 CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
785 String portName = getPortName(port);
786
787 if (node == null) {
788 log.debug("{} is added to unregistered node, ignore it.", portName);
789 return;
790 }
791
792 log.debug("Port {} is added to {}", portName, node.hostname());
793
794 if (portName.startsWith(VPORT_PREFIX)) {
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800795 if (isNodeStateComplete(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800796 cordVtnService.addServiceVm(node, getConnectPoint(port));
797 } else {
798 log.debug("VM is detected on incomplete node, ignore it.", portName);
799 }
Hyunsun Moon133fd792016-02-09 01:55:48 -0800800 } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
801 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800802 }
803 }
804
805 /**
806 * Handles port removed situation.
Hyunsun Moon133fd792016-02-09 01:55:48 -0800807 * If the removed port is tunnel or data plane interface, proceed to the remaining
808 * node initialization.Others, do nothing.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800809 *
810 * @param port port
811 */
812 public void portRemoved(Port port) {
813 CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
814 String portName = getPortName(port);
815
816 if (node == null) {
817 return;
818 }
819
820 log.debug("Port {} is removed from {}", portName, node.hostname());
821
822 if (portName.startsWith(VPORT_PREFIX)) {
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800823 if (isNodeStateComplete(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800824 cordVtnService.removeServiceVm(getConnectPoint(port));
825 } else {
826 log.debug("VM is vanished from incomplete node, ignore it.", portName);
827 }
Hyunsun Moon133fd792016-02-09 01:55:48 -0800828 } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800829 setNodeState(node, NodeState.INCOMPLETE);
830 }
831 }
832 }
833
834 private class InternalDeviceListener implements DeviceListener {
835
836 @Override
837 public void event(DeviceEvent event) {
838
839 Device device = event.subject();
840 ConnectionHandler<Device> handler =
841 (device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
842
843 switch (event.type()) {
844 case PORT_ADDED:
845 eventExecutor.submit(() -> bridgeHandler.portAdded(event.port()));
846 break;
847 case PORT_UPDATED:
848 if (!event.port().isEnabled()) {
849 eventExecutor.submit(() -> bridgeHandler.portRemoved(event.port()));
850 }
851 break;
852 case DEVICE_ADDED:
853 case DEVICE_AVAILABILITY_CHANGED:
854 if (deviceService.isAvailable(device.id())) {
855 eventExecutor.submit(() -> handler.connected(device));
856 } else {
857 eventExecutor.submit(() -> handler.disconnected(device));
858 }
859 break;
860 default:
861 break;
862 }
863 }
864 }
865
866 /**
Hyunsun Moon746956f2016-01-24 21:47:06 -0800867 * Reads cordvtn nodes from config file.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800868 */
869 private void readConfiguration() {
870 CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800871 if (config == null) {
Hyunsun Moon746956f2016-01-24 21:47:06 -0800872 log.debug("No configuration found");
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800873 return;
874 }
875
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800876 config.cordVtnNodes().forEach(this::addNode);
Hyunsun Moon746956f2016-01-24 21:47:06 -0800877 // TODO remove nodes if needed
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800878 }
879
880 private class InternalConfigListener implements NetworkConfigListener {
881
882 @Override
883 public void event(NetworkConfigEvent event) {
884 if (!event.configClass().equals(CordVtnConfig.class)) {
885 return;
886 }
887
888 switch (event.type()) {
889 case CONFIG_ADDED:
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800890 case CONFIG_UPDATED:
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800891 eventExecutor.execute(CordVtnNodeManager.this::readConfiguration);
892 break;
893 default:
894 break;
895 }
896 }
897 }
898}