blob: bec23dafdae6fe2a94308753f6ab59eed98ca6c4 [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;
62import org.onosproject.net.driver.DriverHandler;
63import org.onosproject.net.driver.DriverService;
64import org.onosproject.net.flow.FlowRuleService;
65import org.onosproject.net.group.GroupService;
66import org.onosproject.net.host.HostService;
67import org.onosproject.ovsdb.controller.OvsdbClientService;
68import org.onosproject.ovsdb.controller.OvsdbController;
69import org.onosproject.ovsdb.controller.OvsdbNodeId;
70import org.onosproject.store.serializers.KryoNamespaces;
71import org.onosproject.store.service.ConsistentMap;
Hyunsun Moon80b03872016-03-10 12:40:16 -080072import org.onosproject.store.service.MapEvent;
73import org.onosproject.store.service.MapEventListener;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080074import org.onosproject.store.service.Serializer;
75import org.onosproject.store.service.StorageService;
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -080076import org.onosproject.store.service.Versioned;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080077import org.slf4j.Logger;
78
79import java.util.ArrayList;
80import java.util.HashMap;
81import java.util.List;
82import java.util.Map;
Hyunsun Moon98025542016-03-08 04:36:02 -080083import java.util.Objects;
Hyunsun Moon133fd792016-02-09 01:55:48 -080084import java.util.Set;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080085import java.util.concurrent.ExecutorService;
Hyunsun Moonaf520d32016-03-07 16:37:17 -080086import java.util.stream.Collectors;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -080087
88import static com.google.common.base.Preconditions.checkNotNull;
89import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
90import static org.onlab.util.Tools.groupedThreads;
91import static org.onosproject.net.Device.Type.SWITCH;
92import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
93import static org.slf4j.LoggerFactory.getLogger;
94
95/**
96 * Reads node information from the network config file and handles the config
97 * update events.
98 * Only a leader controller performs the node addition or deletion.
99 */
100@Component(immediate = true)
101@Service(value = CordVtnNodeManager.class)
102public class CordVtnNodeManager {
103
104 protected final Logger log = getLogger(getClass());
105
106 private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
107 .register(KryoNamespaces.API)
Hyunsun Moon133fd792016-02-09 01:55:48 -0800108 .register(KryoNamespaces.MISC)
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800109 .register(CordVtnNode.class)
Hyunsun Moon133fd792016-02-09 01:55:48 -0800110 .register(NodeState.class)
111 .register(SshAccessInfo.class)
112 .register(NetworkAddress.class);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800113
114 private static final String DEFAULT_BRIDGE = "br-int";
115 private static final String DEFAULT_TUNNEL = "vxlan";
116 private static final String VPORT_PREFIX = "tap";
117 private static final String OK = "OK";
118 private static final String NO = "NO";
119
120 private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS = new HashMap<String, String>() {
121 {
122 put("key", "flow");
123 put("remote_ip", "flow");
124 }
125 };
126 private static final int DPID_BEGIN = 3;
127 private static final int OFPORT = 6653;
128
129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
130 protected CoreService coreService;
131
132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
133 protected NetworkConfigRegistry configRegistry;
134
135 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
136 protected NetworkConfigService configService;
137
138 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
139 protected StorageService storageService;
140
141 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
142 protected DeviceAdminService adminService;
143
144 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
145 protected OvsdbController controller;
146
147 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
148 protected ClusterService clusterService;
149
150 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
151 protected DriverService driverService;
152
153 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
154 protected DeviceService deviceService;
155
156 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
157 protected HostService hostService;
158
159 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
160 protected FlowRuleService flowRuleService;
161
162 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon98025542016-03-08 04:36:02 -0800163 protected LeadershipService leadershipService;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800164
165 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
166 protected GroupService groupService;
167
168 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
169 protected CordVtnService cordVtnService;
170
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800171 private final ExecutorService eventExecutor =
172 newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtncfg", "event-handler"));
173
174 private final NetworkConfigListener configListener = new InternalConfigListener();
175 private final DeviceListener deviceListener = new InternalDeviceListener();
Hyunsun Moon80b03872016-03-10 12:40:16 -0800176 private final MapEventListener<String, CordVtnNode> nodeStoreListener = new InternalMapListener();
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800177
178 private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
179 private final BridgeHandler bridgeHandler = new BridgeHandler();
180
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800181 private ConsistentMap<String, CordVtnNode> nodeStore;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800182 private CordVtnRuleInstaller ruleInstaller;
183 private ApplicationId appId;
Hyunsun Moon98025542016-03-08 04:36:02 -0800184 private NodeId localNodeId;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800185
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800186 private enum NodeState implements CordVtnNodeState {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800187
188 INIT {
189 @Override
190 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800191 if (!nodeManager.isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800192 nodeManager.connectOvsdb(node);
193 } else {
194 nodeManager.createIntegrationBridge(node);
195 }
196 }
197 },
198 BRIDGE_CREATED {
199 @Override
200 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800201 if (!nodeManager.isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800202 nodeManager.connectOvsdb(node);
203 } else {
204 nodeManager.createTunnelInterface(node);
Hyunsun Moon133fd792016-02-09 01:55:48 -0800205 nodeManager.addDataPlaneInterface(node);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800206 }
207 }
208 },
Hyunsun Moon133fd792016-02-09 01:55:48 -0800209 PORTS_ADDED {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800210 @Override
211 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800212 nodeManager.setIpAddress(node);
Hyunsun Moon177506f2016-01-21 00:54:52 -0800213 }
Hyunsun Moon177506f2016-01-21 00:54:52 -0800214 },
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800215 COMPLETE {
216 @Override
217 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
218 nodeManager.postInit(node);
219 }
220 },
221 INCOMPLETE {
222 @Override
223 public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
224 }
225 };
226
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800227 public abstract void process(CordVtnNodeManager nodeManager, CordVtnNode node);
228 }
229
230 @Activate
231 protected void active() {
232 appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
Hyunsun Moon98025542016-03-08 04:36:02 -0800233 localNodeId = clusterService.getLocalNode().id();
234 leadershipService.runForLeadership(appId.name());
235
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800236 nodeStore = storageService.<String, CordVtnNode>consistentMapBuilder()
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800237 .withSerializer(Serializer.using(NODE_SERIALIZER.build()))
238 .withName("cordvtn-nodestore")
239 .withApplicationId(appId)
240 .build();
241
242 ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
243 deviceService,
244 driverService,
245 groupService,
Hyunsun Moonfae776d2016-03-08 18:07:52 -0800246 configRegistry,
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800247 DEFAULT_TUNNEL);
248
Hyunsun Moon80b03872016-03-10 12:40:16 -0800249 nodeStore.addListener(nodeStoreListener);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800250 deviceService.addListener(deviceListener);
251 configService.addListener(configListener);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800252 }
253
254 @Deactivate
255 protected void deactivate() {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800256 configService.removeListener(configListener);
257 deviceService.removeListener(deviceListener);
258
Hyunsun Moon80b03872016-03-10 12:40:16 -0800259 nodeStore.removeListener(nodeStoreListener);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800260 nodeStore.clear();
Hyunsun Moon80b03872016-03-10 12:40:16 -0800261
Hyunsun Moon98025542016-03-08 04:36:02 -0800262 leadershipService.withdraw(appId.name());
Hyunsun Moon80b03872016-03-10 12:40:16 -0800263 eventExecutor.shutdown();
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800264 }
265
266 /**
Hyunsun Moon80b03872016-03-10 12:40:16 -0800267 * Adds or updates a new node to the service.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800268 *
269 * @param node cordvtn node
270 */
Hyunsun Moon80b03872016-03-10 12:40:16 -0800271 public void addOrUpdateNode(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800272 checkNotNull(node);
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800273 nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, getNodeState(node)));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800274 }
275
276 /**
277 * Deletes a node from the service.
278 *
279 * @param node cordvtn node
280 */
281 public void deleteNode(CordVtnNode node) {
282 checkNotNull(node);
283
Hyunsun Moon133fd792016-02-09 01:55:48 -0800284 if (isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800285 disconnectOvsdb(node);
286 }
287
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800288 nodeStore.remove(node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800289 }
290
291 /**
292 * Initiates node to serve virtual tenant network.
293 *
294 * @param node cordvtn node
295 */
Hyunsun Moon80b03872016-03-10 12:40:16 -0800296 private void initNode(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800297 checkNotNull(node);
298
Hyunsun Moon80b03872016-03-10 12:40:16 -0800299 NodeState state = (NodeState) node.state();
300 log.debug("Processing node: {} state: {}", node.hostname(), state);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800301
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800302 state.process(this, node);
303 }
304
305 /**
306 * Returns node initialization state.
307 *
308 * @param node cordvtn node
309 * @return true if initial node setup is completed, otherwise false
310 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800311 public boolean isNodeInitComplete(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800312 checkNotNull(node);
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800313 return nodeStore.containsKey(node.hostname()) && getNodeState(node).equals(NodeState.COMPLETE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800314 }
315
316 /**
Hyunsun Moon2062e7e2016-03-08 15:37:57 -0800317 * Flush flows installed by cordvtn.
318 */
319 public void flushRules() {
320 ruleInstaller.flushRules();
321 }
322
323 /**
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800324 * Returns if current node state saved in nodeStore is COMPLETE or not.
325 *
326 * @param node cordvtn node
327 * @return true if it's complete state, otherwise false
328 */
329 private boolean isNodeStateComplete(CordVtnNode node) {
330 checkNotNull(node);
331
332 // the state saved in nodeStore can be wrong if IP address settings are changed
333 // after the node init has been completed since there's no way to detect it
334 // getNodeState and checkNodeInitState always return correct answer but can be slow
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800335 Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
336 CordVtnNodeState state = versionedNode.value().state();
337 return state != null && state.equals(NodeState.COMPLETE);
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800338 }
339
340 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800341 * Returns detailed node initialization state.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800342 *
343 * @param node cordvtn node
344 * @return string including detailed node init state
345 */
346 public String checkNodeInitState(CordVtnNode node) {
347 checkNotNull(node);
348
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800349 if (!nodeStore.containsKey(node.hostname())) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800350 log.warn("Node {} does not exist, add node first", node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800351 return null;
352 }
353
Hyunsun Moon133fd792016-02-09 01:55:48 -0800354 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
355 if (session == null) {
356 log.debug("Failed to SSH to {}", node.hostname());
357 return null;
358 }
359
360 Set<IpAddress> intBrIps = RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800361 String result = String.format(
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800362 "br-int created and connected : %s (%s)%n" +
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800363 "VXLAN interface created : %s%n" +
Hyunsun Moon133fd792016-02-09 01:55:48 -0800364 "Data plane interface added : %s (%s)%n" +
365 "IP flushed from %s : %s%n" +
366 "Data plane IP added to br-int : %s (%s)%n" +
367 "Local management IP added to br-int : %s (%s)",
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800368 isBrIntCreated(node) ? OK : NO, node.intBrId(),
Hyunsun Moon133fd792016-02-09 01:55:48 -0800369 isTunnelIntfCreated(node) ? OK : NO,
370 isDataPlaneIntfAdded(node) ? OK : NO, node.dpIntf(),
371 node.dpIntf(),
372 RemoteIpCommandUtil.getCurrentIps(session, node.dpIntf()).isEmpty() ? OK : NO,
373 intBrIps.contains(node.dpIp().ip()) ? OK : NO, node.dpIp().cidr(),
374 intBrIps.contains(node.localMgmtIp().ip()) ? OK : NO, node.localMgmtIp().cidr());
375
376 RemoteIpCommandUtil.disconnect(session);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800377
378 return result;
379 }
380
381 /**
382 * Returns the number of the nodes known to the service.
383 *
384 * @return number of nodes
385 */
386 public int getNodeCount() {
387 return nodeStore.size();
388 }
389
390 /**
391 * Returns all nodes known to the service.
392 *
393 * @return list of nodes
394 */
395 public List<CordVtnNode> getNodes() {
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800396 return nodeStore.values().stream()
397 .map(Versioned::value)
398 .collect(Collectors.toList());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800399 }
400
401 /**
402 * Returns cordvtn node associated with a given OVSDB device.
403 *
404 * @param ovsdbId OVSDB device id
405 * @return cordvtn node, null if it fails to find the node
406 */
407 private CordVtnNode getNodeByOvsdbId(DeviceId ovsdbId) {
408 return getNodes().stream()
409 .filter(node -> node.ovsdbId().equals(ovsdbId))
410 .findFirst().orElse(null);
411 }
412
413 /**
414 * Returns cordvtn node associated with a given integration bridge.
415 *
416 * @param bridgeId device id of integration bridge
417 * @return cordvtn node, null if it fails to find the node
418 */
419 private CordVtnNode getNodeByBridgeId(DeviceId bridgeId) {
420 return getNodes().stream()
421 .filter(node -> node.intBrId().equals(bridgeId))
422 .findFirst().orElse(null);
423 }
424
425 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800426 * Sets a new state for a given cordvtn node.
427 *
428 * @param node cordvtn node
429 * @param newState new node state
430 */
431 private void setNodeState(CordVtnNode node, NodeState newState) {
432 checkNotNull(node);
433
Hyunsun Moon80b03872016-03-10 12:40:16 -0800434 log.debug("Changed {} state: {}", node.hostname(), newState);
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800435 nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, newState));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800436 }
437
438 /**
439 * Checks current state of a given cordvtn node and returns it.
440 *
441 * @param node cordvtn node
442 * @return node state
443 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800444 private NodeState getNodeState(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800445 checkNotNull(node);
446
Hyunsun Moon133fd792016-02-09 01:55:48 -0800447 if (isBrIntCreated(node) && isTunnelIntfCreated(node) &&
448 isDataPlaneIntfAdded(node) && isIpAddressSet(node)) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800449 return NodeState.COMPLETE;
Hyunsun Moon133fd792016-02-09 01:55:48 -0800450 } else if (isDataPlaneIntfAdded(node) && isTunnelIntfCreated(node)) {
451 return NodeState.PORTS_ADDED;
452 } else if (isBrIntCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800453 return NodeState.BRIDGE_CREATED;
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800454 } else {
455 return NodeState.INIT;
456 }
457 }
458
459 /**
460 * Performs tasks after node initialization.
461 * It disconnects unnecessary OVSDB connection and installs initial flow
462 * rules on the device.
463 *
464 * @param node cordvtn node
465 */
466 private void postInit(CordVtnNode node) {
467 disconnectOvsdb(node);
468
Hyunsun Moon133fd792016-02-09 01:55:48 -0800469 ruleInstaller.init(node.intBrId(), node.dpIntf(), node.dpIp().ip());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800470
471 // add existing hosts to the service
472 deviceService.getPorts(node.intBrId()).stream()
473 .filter(port -> getPortName(port).startsWith(VPORT_PREFIX) &&
474 port.isEnabled())
475 .forEach(port -> cordVtnService.addServiceVm(node, getConnectPoint(port)));
476
477 // remove stale hosts from the service
478 hostService.getHosts().forEach(host -> {
479 Port port = deviceService.getPort(host.location().deviceId(), host.location().port());
480 if (port == null) {
481 cordVtnService.removeServiceVm(getConnectPoint(host));
482 }
483 });
484
485 log.info("Finished init {}", node.hostname());
486 }
487
488 /**
489 * Returns port name.
490 *
491 * @param port port
492 * @return port name
493 */
494 private String getPortName(Port port) {
495 return port.annotations().value("portName");
496 }
497
498 /**
499 * Returns connection state of OVSDB server for a given node.
500 *
501 * @param node cordvtn node
502 * @return true if it is connected, false otherwise
503 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800504 private boolean isOvsdbConnected(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800505 checkNotNull(node);
506
507 OvsdbClientService ovsdbClient = getOvsdbClient(node);
508 return deviceService.isAvailable(node.ovsdbId()) &&
509 ovsdbClient != null && ovsdbClient.isConnected();
510 }
511
512 /**
513 * Connects to OVSDB server for a given node.
514 *
515 * @param node cordvtn node
516 */
517 private void connectOvsdb(CordVtnNode node) {
518 checkNotNull(node);
519
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800520 if (!nodeStore.containsKey(node.hostname())) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800521 log.warn("Node {} does not exist", node.hostname());
522 return;
523 }
524
Hyunsun Moon133fd792016-02-09 01:55:48 -0800525 if (!isOvsdbConnected(node)) {
526 controller.connect(node.hostMgmtIp().ip(), node.ovsdbPort());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800527 }
528 }
529
530 /**
531 * Disconnects OVSDB server for a given node.
532 *
533 * @param node cordvtn node
534 */
535 private void disconnectOvsdb(CordVtnNode node) {
536 checkNotNull(node);
537
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800538 if (!nodeStore.containsKey(node.hostname())) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800539 log.warn("Node {} does not exist", node.hostname());
540 return;
541 }
542
Hyunsun Moon133fd792016-02-09 01:55:48 -0800543 if (isOvsdbConnected(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800544 OvsdbClientService ovsdbClient = getOvsdbClient(node);
545 ovsdbClient.disconnect();
546 }
547 }
548
549 /**
550 * Returns OVSDB client for a given node.
551 *
552 * @param node cordvtn node
553 * @return OVSDB client, or null if it fails to get OVSDB client
554 */
555 private OvsdbClientService getOvsdbClient(CordVtnNode node) {
556 checkNotNull(node);
557
558 OvsdbClientService ovsdbClient = controller.getOvsdbClient(
Hyunsun Moon133fd792016-02-09 01:55:48 -0800559 new OvsdbNodeId(node.hostMgmtIp().ip(), node.ovsdbPort().toInt()));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800560 if (ovsdbClient == null) {
561 log.trace("Couldn't find OVSDB client for {}", node.hostname());
562 }
563 return ovsdbClient;
564 }
565
566 /**
567 * Creates an integration bridge for a given node.
568 *
569 * @param node cordvtn node
570 */
571 private void createIntegrationBridge(CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800572 if (isBrIntCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800573 return;
574 }
575
576 List<ControllerInfo> controllers = new ArrayList<>();
577 Sets.newHashSet(clusterService.getNodes()).stream()
578 .forEach(controller -> {
579 ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
580 controllers.add(ctrlInfo);
581 });
582
583 String dpid = node.intBrId().toString().substring(DPID_BEGIN);
584
585 try {
586 DriverHandler handler = driverService.createHandler(node.ovsdbId());
587 BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class);
588 bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
589 } catch (ItemNotFoundException e) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800590 log.warn("Failed to create integration bridge on {}", node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800591 }
592 }
593
594 /**
595 * Creates tunnel interface to the integration bridge for a given node.
596 *
597 * @param node cordvtn node
598 */
599 private void createTunnelInterface(CordVtnNode node) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800600 if (isTunnelIntfCreated(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800601 return;
602 }
603
604 DefaultAnnotations.Builder optionBuilder = DefaultAnnotations.builder();
605 for (String key : DEFAULT_TUNNEL_OPTIONS.keySet()) {
606 optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
607 }
608
609 TunnelDescription description = new DefaultTunnelDescription(
610 null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
611 optionBuilder.build());
612
613 try {
614 DriverHandler handler = driverService.createHandler(node.ovsdbId());
615 TunnelConfig tunnelConfig = handler.behaviour(TunnelConfig.class);
616 tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
617 } catch (ItemNotFoundException e) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800618 log.warn("Failed to create tunnel interface on {}", node.hostname());
619 }
620 }
621
622 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800623 * Adds data plane interface to a given node.
Hyunsun Moon177506f2016-01-21 00:54:52 -0800624 *
625 * @param node cordvtn node
626 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800627 private void addDataPlaneInterface(CordVtnNode node) {
628 if (isDataPlaneIntfAdded(node)) {
Hyunsun Moon177506f2016-01-21 00:54:52 -0800629 return;
630 }
631
632 try {
633 DriverHandler handler = driverService.createHandler(node.ovsdbId());
634 BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class);
Hyunsun Moon133fd792016-02-09 01:55:48 -0800635 bridgeConfig.addPort(BridgeName.bridgeName(DEFAULT_BRIDGE), node.dpIntf());
Hyunsun Moon177506f2016-01-21 00:54:52 -0800636 } catch (ItemNotFoundException e) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800637 log.warn("Failed to add {} on {}", node.dpIntf(), node.hostname());
638 }
639 }
640
641 /**
642 * Flushes IP address from data plane interface and adds data plane IP address
643 * to integration bridge.
644 *
645 * @param node cordvtn node
646 */
647 private void setIpAddress(CordVtnNode node) {
648 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
649 if (session == null) {
650 log.debug("Failed to SSH to {}", node.hostname());
651 return;
652 }
653
Hyunsun Moone9d4f4a2016-03-04 19:24:08 -0800654 RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE).stream()
655 .filter(ip -> !ip.equals(node.localMgmtIp().ip()))
656 .filter(ip -> !ip.equals(node.dpIp().ip()))
657 .forEach(ip -> RemoteIpCommandUtil.deleteIp(session, ip, DEFAULT_BRIDGE));
658
Hyunsun Moon133fd792016-02-09 01:55:48 -0800659 boolean result = RemoteIpCommandUtil.flushIp(session, node.dpIntf()) &&
660 RemoteIpCommandUtil.setInterfaceUp(session, node.dpIntf()) &&
661 RemoteIpCommandUtil.addIp(session, node.dpIp(), DEFAULT_BRIDGE) &&
662 RemoteIpCommandUtil.addIp(session, node.localMgmtIp(), DEFAULT_BRIDGE) &&
663 RemoteIpCommandUtil.setInterfaceUp(session, DEFAULT_BRIDGE);
664
665 RemoteIpCommandUtil.disconnect(session);
666
667 if (result) {
668 setNodeState(node, NodeState.COMPLETE);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800669 }
670 }
671
672 /**
673 * Checks if integration bridge exists and available.
674 *
675 * @param node cordvtn node
676 * @return true if the bridge is available, false otherwise
677 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800678 private boolean isBrIntCreated(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800679 return (deviceService.getDevice(node.intBrId()) != null
680 && deviceService.isAvailable(node.intBrId()));
681 }
682
683 /**
684 * Checks if tunnel interface exists.
685 *
686 * @param node cordvtn node
687 * @return true if the interface exists, false otherwise
688 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800689 private boolean isTunnelIntfCreated(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800690 return deviceService.getPorts(node.intBrId())
691 .stream()
692 .filter(p -> getPortName(p).contains(DEFAULT_TUNNEL) &&
693 p.isEnabled())
694 .findAny().isPresent();
695 }
696
697 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800698 * Checks if data plane interface exists.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800699 *
700 * @param node cordvtn node
701 * @return true if the interface exists, false otherwise
702 */
Hyunsun Moon133fd792016-02-09 01:55:48 -0800703 private boolean isDataPlaneIntfAdded(CordVtnNode node) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800704 return deviceService.getPorts(node.intBrId())
705 .stream()
Hyunsun Moon133fd792016-02-09 01:55:48 -0800706 .filter(p -> getPortName(p).contains(node.dpIntf()) &&
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800707 p.isEnabled())
708 .findAny().isPresent();
709 }
710
711 /**
Hyunsun Moon133fd792016-02-09 01:55:48 -0800712 * Checks if the IP addresses are correctly set.
713 *
714 * @param node cordvtn node
715 * @return true if the IP is set, false otherwise
716 */
717 private boolean isIpAddressSet(CordVtnNode node) {
718 Session session = RemoteIpCommandUtil.connect(node.sshInfo());
719 if (session == null) {
720 log.debug("Failed to SSH to {}", node.hostname());
721 return false;
722 }
723
724 Set<IpAddress> intBrIps = RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE);
725 boolean result = RemoteIpCommandUtil.getCurrentIps(session, node.dpIntf()).isEmpty() &&
726 RemoteIpCommandUtil.isInterfaceUp(session, node.dpIntf()) &&
727 intBrIps.contains(node.dpIp().ip()) &&
728 intBrIps.contains(node.localMgmtIp().ip()) &&
729 RemoteIpCommandUtil.isInterfaceUp(session, DEFAULT_BRIDGE);
730
731 RemoteIpCommandUtil.disconnect(session);
732 return result;
733 }
734
735 /**
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800736 * Returns connect point of a given port.
737 *
738 * @param port port
739 * @return connect point
740 */
741 private ConnectPoint getConnectPoint(Port port) {
742 return new ConnectPoint(port.element().id(), port.number());
743 }
744
745 /**
746 * Returns connect point of a given host.
747 *
748 * @param host host
749 * @return connect point
750 */
751 private ConnectPoint getConnectPoint(Host host) {
752 return new ConnectPoint(host.location().deviceId(), host.location().port());
753 }
754
755 private class OvsdbHandler implements ConnectionHandler<Device> {
756
757 @Override
758 public void connected(Device device) {
759 CordVtnNode node = getNodeByOvsdbId(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 if (!deviceService.isAvailable(device.id())) {
Hyunsun Moonaf520d32016-03-07 16:37:17 -0800770 log.debug("Device {} is disconnected", device.id());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800771 adminService.removeDevice(device.id());
772 }
773 }
774 }
775
776 private class BridgeHandler implements ConnectionHandler<Device> {
777
778 @Override
779 public void connected(Device device) {
780 CordVtnNode node = getNodeByBridgeId(device.id());
781 if (node != null) {
Hyunsun Moon133fd792016-02-09 01:55:48 -0800782 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800783 } else {
784 log.debug("{} is detected on unregistered node, ignore it.", device.id());
785 }
786 }
787
788 @Override
789 public void disconnected(Device device) {
790 CordVtnNode node = getNodeByBridgeId(device.id());
791 if (node != null) {
792 log.debug("Integration Bridge is disconnected from {}", node.hostname());
793 setNodeState(node, NodeState.INCOMPLETE);
794 }
795 }
796
797 /**
798 * Handles port added situation.
Hyunsun Moon133fd792016-02-09 01:55:48 -0800799 * If the added port is tunnel or data plane interface, proceed to the remaining
800 * node initialization. Otherwise, do nothing.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800801 *
802 * @param port port
803 */
804 public void portAdded(Port port) {
805 CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
806 String portName = getPortName(port);
807
808 if (node == null) {
809 log.debug("{} is added to unregistered node, ignore it.", portName);
810 return;
811 }
812
Hyunsun Moon80b03872016-03-10 12:40:16 -0800813 log.info("Port {} is added to {}", portName, node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800814
815 if (portName.startsWith(VPORT_PREFIX)) {
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800816 if (isNodeStateComplete(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800817 cordVtnService.addServiceVm(node, getConnectPoint(port));
818 } else {
819 log.debug("VM is detected on incomplete node, ignore it.", portName);
820 }
Hyunsun Moon133fd792016-02-09 01:55:48 -0800821 } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
822 setNodeState(node, getNodeState(node));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800823 }
824 }
825
826 /**
827 * Handles port removed situation.
Hyunsun Moon133fd792016-02-09 01:55:48 -0800828 * If the removed port is tunnel or data plane interface, proceed to the remaining
829 * node initialization.Others, do nothing.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800830 *
831 * @param port port
832 */
833 public void portRemoved(Port port) {
834 CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
835 String portName = getPortName(port);
836
837 if (node == null) {
838 return;
839 }
840
Hyunsun Moon80b03872016-03-10 12:40:16 -0800841 log.info("Port {} is removed from {}", portName, node.hostname());
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800842
843 if (portName.startsWith(VPORT_PREFIX)) {
Hyunsun Moon32f3b8e2016-03-02 19:27:26 -0800844 if (isNodeStateComplete(node)) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800845 cordVtnService.removeServiceVm(getConnectPoint(port));
846 } else {
847 log.debug("VM is vanished from incomplete node, ignore it.", portName);
848 }
Hyunsun Moon133fd792016-02-09 01:55:48 -0800849 } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800850 setNodeState(node, NodeState.INCOMPLETE);
851 }
852 }
853 }
854
855 private class InternalDeviceListener implements DeviceListener {
856
857 @Override
858 public void event(DeviceEvent event) {
859
Hyunsun Moon98025542016-03-08 04:36:02 -0800860 NodeId leaderNodeId = leadershipService.getLeader(appId.name());
861 if (!Objects.equals(localNodeId, leaderNodeId)) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800862 // do not allow to proceed without leadership
Hyunsun Moon98025542016-03-08 04:36:02 -0800863 return;
864 }
865
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800866 Device device = event.subject();
867 ConnectionHandler<Device> handler =
868 (device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
869
870 switch (event.type()) {
871 case PORT_ADDED:
Hyunsun Moon80b03872016-03-10 12:40:16 -0800872 eventExecutor.execute(() -> bridgeHandler.portAdded(event.port()));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800873 break;
874 case PORT_UPDATED:
875 if (!event.port().isEnabled()) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800876 eventExecutor.execute(() -> bridgeHandler.portRemoved(event.port()));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800877 }
878 break;
879 case DEVICE_ADDED:
880 case DEVICE_AVAILABILITY_CHANGED:
881 if (deviceService.isAvailable(device.id())) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800882 eventExecutor.execute(() -> handler.connected(device));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800883 } else {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800884 eventExecutor.execute(() -> handler.disconnected(device));
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800885 }
886 break;
887 default:
888 break;
889 }
890 }
891 }
892
893 /**
Hyunsun Moon746956f2016-01-24 21:47:06 -0800894 * Reads cordvtn nodes from config file.
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800895 */
896 private void readConfiguration() {
897 CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800898 if (config == null) {
Hyunsun Moon746956f2016-01-24 21:47:06 -0800899 log.debug("No configuration found");
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800900 return;
901 }
902
Hyunsun Moon80b03872016-03-10 12:40:16 -0800903 config.cordVtnNodes().forEach(this::addOrUpdateNode);
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800904 }
905
906 private class InternalConfigListener implements NetworkConfigListener {
907
908 @Override
909 public void event(NetworkConfigEvent event) {
Hyunsun Moon80b03872016-03-10 12:40:16 -0800910 NodeId leaderNodeId = leadershipService.getLeader(appId.name());
911 if (!Objects.equals(localNodeId, leaderNodeId)) {
912 // do not allow to proceed without leadership
913 return;
914 }
915
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800916 if (!event.configClass().equals(CordVtnConfig.class)) {
917 return;
918 }
919
920 switch (event.type()) {
921 case CONFIG_ADDED:
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800922 case CONFIG_UPDATED:
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800923 eventExecutor.execute(CordVtnNodeManager.this::readConfiguration);
924 break;
925 default:
926 break;
927 }
928 }
929 }
Hyunsun Moon80b03872016-03-10 12:40:16 -0800930
931 private class InternalMapListener implements MapEventListener<String, CordVtnNode> {
932
933 @Override
934 public void event(MapEvent<String, CordVtnNode> event) {
935 NodeId leaderNodeId = leadershipService.getLeader(appId.name());
936 if (!Objects.equals(localNodeId, leaderNodeId)) {
937 // do not allow to proceed without leadership
938 return;
939 }
940
941 CordVtnNode oldNode;
942 CordVtnNode newNode;
943
944 switch (event.type()) {
945 case UPDATE:
946 oldNode = event.oldValue().value();
947 newNode = event.newValue().value();
948
949 if (!newNode.equals(oldNode)) {
950 log.info("{} has been updated", newNode.hostname());
951 log.debug("New node: {}", newNode);
952 }
953 // perform init procedure based on current state on any updates,
954 // insert, or even if the node is the same for robustness since
955 // it's no harm to run the init procedure multiple times
956 eventExecutor.execute(() -> initNode(newNode));
957 break;
958 case INSERT:
959 newNode = event.newValue().value();
960 log.info("Added {}", newNode.hostname());
961 eventExecutor.execute(() -> initNode(newNode));
962 break;
963 case REMOVE:
964 oldNode = event.oldValue().value();
965 log.info("{} is removed", oldNode.hostname());
966 break;
967 default:
968 break;
969 }
970 }
971 }
Hyunsun Moonb77b60f2016-01-15 20:03:18 -0800972}