blob: 28b41cfce74262695143a61b29b7afc1a266bbd6 [file] [log] [blame]
Daniel Parka7d6e9f2016-01-18 17:54:14 +09001/*
2 * Copyright 2016 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.openstacknode;
17
18import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.Sets;
20import 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;
26import org.onlab.util.ItemNotFoundException;
27import org.onlab.util.KryoNamespace;
28import org.onosproject.cluster.ClusterService;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.core.CoreService;
31import org.onosproject.net.DefaultAnnotations;
32import org.onosproject.net.Device;
33import org.onosproject.net.DeviceId;
34import org.onosproject.net.Port;
35import org.onosproject.net.behaviour.BridgeConfig;
36import org.onosproject.net.behaviour.BridgeName;
37import org.onosproject.net.behaviour.ControllerInfo;
38import org.onosproject.net.behaviour.DefaultTunnelDescription;
39import org.onosproject.net.behaviour.TunnelConfig;
40import org.onosproject.net.behaviour.TunnelDescription;
41import org.onosproject.net.behaviour.TunnelName;
42import org.onosproject.net.config.ConfigFactory;
43import org.onosproject.net.config.NetworkConfigEvent;
44import org.onosproject.net.config.NetworkConfigListener;
45import org.onosproject.net.config.NetworkConfigRegistry;
46import org.onosproject.net.config.NetworkConfigService;
47import org.onosproject.net.config.basics.SubjectFactories;
48import org.onosproject.net.device.DeviceAdminService;
49import org.onosproject.net.device.DeviceEvent;
50import org.onosproject.net.device.DeviceListener;
51import org.onosproject.net.device.DeviceService;
52import org.onosproject.net.driver.DriverHandler;
53import org.onosproject.net.driver.DriverService;
54import org.onosproject.ovsdb.controller.OvsdbClientService;
55import org.onosproject.ovsdb.controller.OvsdbController;
56import org.onosproject.ovsdb.controller.OvsdbNodeId;
57import org.onosproject.store.serializers.KryoNamespaces;
58import org.onosproject.store.service.ConsistentMap;
59import org.onosproject.store.service.Serializer;
60import org.onosproject.store.service.StorageService;
61import org.slf4j.Logger;
62
63import static org.onlab.util.Tools.groupedThreads;
64import static org.onosproject.net.Device.Type.SWITCH;
65import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
66import static org.slf4j.LoggerFactory.getLogger;
67
68import java.util.ArrayList;
69import java.util.List;
70import java.util.Map;
71import java.util.concurrent.ExecutorService;
72import java.util.concurrent.Executors;
73import java.util.stream.Collectors;
74
75import static com.google.common.base.Preconditions.checkNotNull;
76
77
78/**
79 * Initializes devices in compute/gateway nodes according to there type.
80 */
81@Component(immediate = true)
82@Service
83public class OpenstackNodeManager implements OpenstackNodeService {
84 protected final Logger log = getLogger(getClass());
85 private static final int NUM_THREADS = 1;
86 private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
87 .register(KryoNamespaces.API)
88 .register(OpenstackNode.class)
89 .register(OpenstackNodeType.class)
90 .register(NodeState.class);
91 private static final String DEFAULT_BRIDGE = "br-int";
92 private static final String DEFAULT_TUNNEL = "vxlan";
93 private static final String PORT_NAME = "portName";
94 private static final String OPENSTACK_NODESTORE = "openstacknode-nodestore";
95 private static final String OPENSTACK_NODEMANAGER_ID = "org.onosproject.openstacknode";
96
97 private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS
98 = ImmutableMap.of("key", "flow", "remote_ip", "flow");
99
100 private static final int DPID_BEGIN = 3;
101 private static final int OFPORT = 6653;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected CoreService coreService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected DeviceService deviceService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected OvsdbController controller;
111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected ClusterService clusterService;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected DriverService driverService;
117
118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected DeviceAdminService adminService;
120
121 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
122 protected StorageService storageService;
123
124 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
125 protected NetworkConfigService configService;
126
127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
128 protected NetworkConfigRegistry configRegistry;
129
130 private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
131 private final BridgeHandler bridgeHandler = new BridgeHandler();
132 private final NetworkConfigListener configListener = new InternalConfigListener();
133 private final ConfigFactory configFactory =
134 new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, OpenstackNodeConfig.class, "openstacknode") {
135 @Override
136 public OpenstackNodeConfig createConfig() {
137 return new OpenstackNodeConfig();
138 }
139 };
140
141 private final ExecutorService eventExecutor = Executors
142 .newFixedThreadPool(NUM_THREADS, groupedThreads("onos/openstacknode", "event-handler"));
143
144 private final DeviceListener deviceListener = new InternalDeviceListener();
145
146 private ApplicationId appId;
147 private ConsistentMap<OpenstackNode, NodeState> nodeStore;
148
149 private enum NodeState {
150
151 INIT {
152 @Override
153 public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
154 openstackNodeManager.connect(node);
155 }
156 },
157 OVSDB_CONNECTED {
158 @Override
159 public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
160 if (!openstackNodeManager.getOvsdbConnectionState(node)) {
161 openstackNodeManager.connect(node);
162 } else {
163 openstackNodeManager.createIntegrationBridge(node);
164 }
165 }
166 },
167 BRIDGE_CREATED {
168 @Override
169 public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
170 if (!openstackNodeManager.getOvsdbConnectionState(node)) {
171 openstackNodeManager.connect(node);
172 } else {
173 openstackNodeManager.createTunnelInterface(node);
174 }
175 }
176 },
177 COMPLETE {
178 @Override
179 public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
180 openstackNodeManager.postInit(node);
181 }
182 },
183 INCOMPLETE {
184 @Override
185 public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
186 }
187 };
188
189 public abstract void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node);
190 }
191
192 @Activate
193 protected void activate() {
194 appId = coreService.registerApplication(OPENSTACK_NODEMANAGER_ID);
195 nodeStore = storageService.<OpenstackNode, NodeState>consistentMapBuilder()
196 .withSerializer(Serializer.using(NODE_SERIALIZER.build()))
197 .withName(OPENSTACK_NODESTORE)
198 .withApplicationId(appId)
199 .build();
200
201 deviceService.addListener(deviceListener);
202 configRegistry.registerConfigFactory(configFactory);
203 configService.addListener(configListener);
204 readConfiguration();
205
206 log.info("Started");
207 }
208
209 @Deactivate
210 protected void deactivate() {
211 deviceService.removeListener(deviceListener);
212 eventExecutor.shutdown();
213 nodeStore.clear();
214
215 configRegistry.unregisterConfigFactory(configFactory);
216 configService.removeListener(configListener);
217
218 log.info("Stopped");
219 }
220
221
222 @Override
223 public void addNode(OpenstackNode node) {
224 checkNotNull(node, "Node cannot be null");
225
226 nodeStore.putIfAbsent(node, checkNodeState(node));
227
228 NodeState state = checkNodeState(node);
229 state.process(this, node);
230 }
231
232 @Override
233 public void deleteNode(OpenstackNode node) {
234 checkNotNull(node, "Node cannot be null");
235
236 if (getOvsdbConnectionState(node)) {
237 disconnect(node);
238 }
239
240 nodeStore.remove(node);
241 }
242
243 @Override
244 public List<OpenstackNode> getNodes(OpenstackNodeType openstackNodeType) {
245 List<OpenstackNode> nodes = new ArrayList<>();
246 nodes.addAll(nodeStore.keySet().stream().filter(node -> node.openstackNodeType()
247 .equals(openstackNodeType)).collect(Collectors.toList()));
248 return nodes;
249 }
250
251 private List<OpenstackNode> getNodesAll() {
252 List<OpenstackNode> nodes = new ArrayList<>();
253 nodes.addAll(nodeStore.keySet());
254 return nodes;
255 }
256
257 @Override
258 public boolean isComplete(OpenstackNode node) {
259 checkNotNull(node, "Node cannot be null");
260
261 if (!nodeStore.containsKey(node)) {
262 log.warn("Node {} does not exist", node.hostName());
263 return false;
264 } else if (nodeStore.get(node).equals(NodeState.COMPLETE)) {
265 return true;
266 }
267 return false;
268 }
269
270 /**
271 * Checks current state of a given openstack node and returns it.
272 *
273 * @param node openstack node
274 * @return node state
275 */
276 private NodeState checkNodeState(OpenstackNode node) {
277 checkNotNull(node, "Node cannot be null");
278
279 if (checkIntegrationBridge(node) && checkTunnelInterface(node)) {
280 return NodeState.COMPLETE;
281 } else if (checkIntegrationBridge(node)) {
282 return NodeState.BRIDGE_CREATED;
283 } else if (getOvsdbConnectionState(node)) {
284 return NodeState.OVSDB_CONNECTED;
285 } else {
286 return NodeState.INIT;
287 }
288 }
289
290
291 /**
292 * Checks if integration bridge exists and available.
293 *
294 * @param node openstack node
295 * @return true if the bridge is available, false otherwise
296 */
297 private boolean checkIntegrationBridge(OpenstackNode node) {
298 return (deviceService.getDevice(node.intBrId()) != null
299 && deviceService.isAvailable(node.intBrId()));
300 }
301 /**
302 * Checks if tunnel interface exists.
303 *
304 * @param node openstack node
305 * @return true if the interface exists, false otherwise
306 */
307 private boolean checkTunnelInterface(OpenstackNode node) {
308 checkNotNull(node, "Node cannot be null");
309 return deviceService.getPorts(node.intBrId())
310 .stream()
311 .filter(p -> p.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL) && p.isEnabled())
312 .findAny().isPresent();
313 }
314
315 /**
316 * Returns connection state of OVSDB server for a given node.
317 *
318 * @param node openstack node
319 * @return true if it is connected, false otherwise
320 */
321 private boolean getOvsdbConnectionState(OpenstackNode node) {
322 checkNotNull(node, "Node cannot be null");
323
324 OvsdbClientService ovsdbClient = getOvsdbClient(node);
325 return deviceService.isAvailable(node.ovsdbId()) &&
326 ovsdbClient != null && ovsdbClient.isConnected();
327 }
328
329 /**
330 * Returns OVSDB client for a given node.
331 *
332 * @param node openstack node
333 * @return OVSDB client, or null if it fails to get OVSDB client
334 */
335 private OvsdbClientService getOvsdbClient(OpenstackNode node) {
336 checkNotNull(node, "Node cannot be null");
337
338 OvsdbClientService ovsdbClient = controller.getOvsdbClient(
339 new OvsdbNodeId(node.ovsdbIp(), node.ovsdbPort().toInt()));
340 if (ovsdbClient == null) {
341 log.debug("Couldn't find OVSDB client for {}", node.hostName());
342 }
343 return ovsdbClient;
344 }
345
346 /**
347 * Connects to OVSDB server for a given node.
348 *
349 * @param node openstack node
350 */
351 private void connect(OpenstackNode node) {
352 checkNotNull(node, "Node cannot be null");
353
354 if (!nodeStore.containsKey(node)) {
355 log.warn("Node {} does not exist", node.hostName());
356 return;
357 }
358
359 if (!getOvsdbConnectionState(node)) {
360 controller.connect(node.ovsdbIp(), node.ovsdbPort());
361 }
362 }
363
364 /**
365 * Creates an integration bridge for a given node.
366 *
367 * @param node openstack node
368 */
369 private void createIntegrationBridge(OpenstackNode node) {
370 if (checkIntegrationBridge(node)) {
371 return;
372 }
373
374 List<ControllerInfo> controllers = new ArrayList<>();
375 Sets.newHashSet(clusterService.getNodes()).stream()
376 .forEach(controller -> {
377 ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
378 controllers.add(ctrlInfo);
379 });
380 String dpid = node.intBrId().toString().substring(DPID_BEGIN);
381
382 try {
383 DriverHandler handler = driverService.createHandler(node.ovsdbId());
384 BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class);
385 bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
386 } catch (ItemNotFoundException e) {
387 log.warn("Failed to create integration bridge on {}", node.ovsdbId());
388 }
389 }
390
391 /**
392 * Creates tunnel interface to the integration bridge for a given node.
393 *
394 * @param node openstack node
395 */
396 private void createTunnelInterface(OpenstackNode node) {
397 if (checkTunnelInterface(node)) {
398 return;
399 }
400
401 DefaultAnnotations.Builder optionBuilder = DefaultAnnotations.builder();
402 for (String key : DEFAULT_TUNNEL_OPTIONS.keySet()) {
403 optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
404 }
405 TunnelDescription description =
406 new DefaultTunnelDescription(null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
407 optionBuilder.build());
408 try {
409 DriverHandler handler = driverService.createHandler(node.ovsdbId());
410 TunnelConfig tunnelConfig = handler.behaviour(TunnelConfig.class);
411 tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
412 } catch (ItemNotFoundException e) {
413 log.warn("Failed to create tunnel interface on {}", node.ovsdbId());
414 }
415 }
416
417 /**
418 * Performs tasks after node initialization.
419 * First disconnect unnecessary OVSDB connection and then installs flow rules
420 * for existing VMs if there are any.
421 *
422 * @param node openstack node
423 */
424 private void postInit(OpenstackNode node) {
425 disconnect(node);
426 log.info("Finished initializing {}", node.hostName());
427 }
428
429 /**
430 * Sets a new state for a given openstack node.
431 *
432 * @param node openstack node
433 * @param newState new node state
434 */
435 private void setNodeState(OpenstackNode node, NodeState newState) {
436 checkNotNull(node, "Node cannot be null");
437
438 log.debug("Changed {} state: {}", node.hostName(), newState.toString());
439
440 nodeStore.put(node, newState);
441 newState.process(this, node);
442 }
443
444 /**
445 * Returns openstack node associated with a given OVSDB device.
446 *
447 * @param ovsdbId OVSDB device id
448 * @return openstack node, null if it fails to find the node
449 */
450 private OpenstackNode getNodeByOvsdbId(DeviceId ovsdbId) {
451
452 return getNodesAll().stream()
453 .filter(node -> node.ovsdbId().equals(ovsdbId))
454 .findFirst().orElse(null);
455 }
456
457 /**
458 * Returns openstack node associated with a given integration bridge.
459 *
460 * @param bridgeId device id of integration bridge
461 * @return openstack node, null if it fails to find the node
462 */
463 private OpenstackNode getNodeByBridgeId(DeviceId bridgeId) {
464 return getNodesAll().stream()
465 .filter(node -> node.intBrId().equals(bridgeId))
466 .findFirst().orElse(null);
467 }
468 /**
469 * Disconnects OVSDB server for a given node.
470 *
471 * @param node openstack node
472 */
473 private void disconnect(OpenstackNode node) {
474 checkNotNull(node, "Node cannot be null");
475
476 if (!nodeStore.containsKey(node)) {
477 log.warn("Node {} does not exist", node.hostName());
478 return;
479 }
480
481 if (getOvsdbConnectionState(node)) {
482 OvsdbClientService ovsdbClient = getOvsdbClient(node);
483 ovsdbClient.disconnect();
484 }
485 }
486
487 private class InternalDeviceListener implements DeviceListener {
488
489 @Override
490 public void event(DeviceEvent event) {
491
492 Device device = event.subject();
493 ConnectionHandler<Device> handler =
494 (device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
495
496 switch (event.type()) {
497 case PORT_ADDED:
498 eventExecutor.submit(() -> bridgeHandler.portAdded(event.port()));
499 break;
500 case PORT_UPDATED:
501 if (!event.port().isEnabled()) {
502 eventExecutor.submit(() -> bridgeHandler.portRemoved(event.port()));
503 }
504 break;
505 case DEVICE_ADDED:
506 case DEVICE_AVAILABILITY_CHANGED:
507 if (deviceService.isAvailable(device.id())) {
508 eventExecutor.submit(() -> handler.connected(device));
509 } else {
510 eventExecutor.submit(() -> handler.disconnected(device));
511 }
512 break;
513 default:
514 log.debug("Unsupported event type {}", event.type().toString());
515 break;
516 }
517 }
518 }
519
520 private class OvsdbHandler implements ConnectionHandler<Device> {
521
522 @Override
523 public void connected(Device device) {
524 OpenstackNode node = getNodeByOvsdbId(device.id());
525 if (node != null) {
526 setNodeState(node, checkNodeState(node));
527 }
528 }
529
530 @Override
531 public void disconnected(Device device) {
532 if (!deviceService.isAvailable(device.id())) {
533 adminService.removeDevice(device.id());
534 }
535 }
536 }
537
538 private class BridgeHandler implements ConnectionHandler<Device> {
539
540 @Override
541 public void connected(Device device) {
542 OpenstackNode node = getNodeByBridgeId(device.id());
543 if (node != null) {
544 setNodeState(node, checkNodeState(node));
545 }
546 }
547
548 @Override
549 public void disconnected(Device device) {
550 OpenstackNode node = getNodeByBridgeId(device.id());
551 if (node != null) {
552 log.debug("Integration Bridge is disconnected from {}", node.hostName());
553 setNodeState(node, NodeState.INCOMPLETE);
554 }
555 }
556
557 /**
558 * Handles port added situation.
559 * If the added port is tunnel port, proceed remaining node initialization.
560 * Otherwise, do nothing.
561 *
562 * @param port port
563 */
564 public void portAdded(Port port) {
565 if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) {
566 return;
567 }
568
569 OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id());
570 if (node != null) {
571 setNodeState(node, checkNodeState(node));
572 }
573 }
574
575 /**
576 * Handles port removed situation.
577 * If the removed port is tunnel port, proceed remaining node initialization.
578 * Others, do nothing.
579 *
580 * @param port port
581 */
582 public void portRemoved(Port port) {
583 if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) {
584 return;
585 }
586
587 OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id());
588 if (node != null) {
589 log.info("Tunnel interface is removed from {}", node.hostName());
590 setNodeState(node, NodeState.INCOMPLETE);
591 }
592 }
593 }
594
595
596 private void readConfiguration() {
597 OpenstackNodeConfig config =
598 configService.getConfig(appId, OpenstackNodeConfig.class);
599
600 if (config == null) {
601 log.error("No configuration found");
602 return;
603 }
604
605 config.openstackNodes().stream().forEach(node -> addNode(node));
606 log.info("Node configured");
607 }
608
609 private class InternalConfigListener implements NetworkConfigListener {
610
611 @Override
612 public void event(NetworkConfigEvent event) {
613 if (!event.configClass().equals(OpenstackNodeConfig.class)) {
614 return;
615 }
616
617 switch (event.type()) {
618 case CONFIG_ADDED:
619 case CONFIG_UPDATED:
620 eventExecutor.execute(OpenstackNodeManager.this::readConfiguration);
621 break;
622 default:
623 break;
624 }
625 }
626 }
627
628
629}
630