blob: 428688b5c5db849e6078bb7b65a0d5e0050edea8 [file] [log] [blame]
jaegonkimdcf7c822019-02-06 15:00:14 +09001/*
2 * Copyright 2019-present Open Networking Foundation
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.ofoverlay.impl;
17
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.TextNode;
21import com.google.common.collect.Lists;
22import com.google.common.collect.Sets;
23import org.onlab.packet.Ip4Address;
24import org.onlab.packet.Ip6Address;
25import org.onlab.packet.IpAddress;
26import org.onlab.packet.TpPort;
27import org.onosproject.cluster.ClusterService;
28import org.onosproject.event.Event;
29import org.onosproject.net.Device;
30import org.onosproject.net.DeviceId;
31import org.onosproject.net.Port;
32import org.onosproject.net.behaviour.BridgeConfig;
33import org.onosproject.net.behaviour.BridgeDescription;
34import org.onosproject.net.behaviour.BridgeName;
35import org.onosproject.net.behaviour.ControlProtocolVersion;
36import org.onosproject.net.behaviour.ControllerInfo;
37import org.onosproject.net.behaviour.DefaultBridgeDescription;
38import org.onosproject.net.behaviour.DefaultTunnelDescription;
39import org.onosproject.net.behaviour.InterfaceConfig;
40import org.onosproject.net.behaviour.TunnelDescription;
41import org.onosproject.net.behaviour.TunnelEndPoints;
42import org.onosproject.net.behaviour.TunnelKeys;
43import org.onosproject.net.device.DeviceAdminService;
44import org.onosproject.net.device.DeviceEvent;
45import org.onosproject.net.device.DeviceService;
46import org.onosproject.net.driver.Behaviour;
47import org.onosproject.net.driver.DriverHandler;
48import org.onosproject.net.driver.DriverService;
49import org.onosproject.ofoverlay.impl.util.NetworkAddress;
50import org.onosproject.ofoverlay.impl.util.OvsDatapathType;
51import org.onosproject.ofoverlay.impl.util.OvsVersion;
52import org.onosproject.ofoverlay.impl.util.SshUtil;
53import org.onosproject.ovsdb.controller.OvsdbClientService;
54import org.onosproject.ovsdb.controller.OvsdbController;
55import org.onosproject.ovsdb.controller.OvsdbNodeId;
56import org.onosproject.workflow.api.AbstractWorklet;
57import org.onosproject.workflow.api.JsonDataModel;
58import org.onosproject.workflow.api.WorkflowContext;
59import org.onosproject.workflow.api.WorkflowException;
60import org.onosproject.workflow.model.accessinfo.SshAccessInfo;
61import org.slf4j.Logger;
62import org.slf4j.LoggerFactory;
63
64import java.util.ArrayList;
65import java.util.Collection;
66import java.util.Collections;
67import java.util.List;
68import java.util.Objects;
69import java.util.Optional;
70import java.util.stream.Collectors;
71
72import static org.onosproject.net.AnnotationKeys.PORT_NAME;
73import static org.onosproject.workflow.api.CheckCondition.check;
74
75/**
76 * Class for defining OVS workflows.
77 */
78public final class Ovs {
79
80 private static final Logger log = LoggerFactory.getLogger(Ovs.class);
81
82 private static final String MODEL_MGMT_IP = "/mgmtIp";
83 private static final String MODEL_OVSDB_PORT = "/ovsdbPort";
84 private static final String MODEL_OVS_VERSION = "/ovsVersion";
85 private static final String MODEL_OVS_DATAPATH_TYPE = "/ovsDatapathType";
86 private static final String MODEL_SSH_ACCESSINFO = "/sshAccessInfo";
87 private static final String MODEL_OF_DEVID_OVERLAY_BRIDGE = "/ofDevIdBrInt";
88 private static final String MODEL_OF_DEVID_UNDERLAY_BRIDGE = "/ofDevIdBrPhy";
89 private static final String MODEL_PHY_PORTS = "/physicalPorts";
90 private static final String MODEL_VTEP_IP = "/vtepIp";
91
92 private static final String BRIDGE_OVERLAY = "br-int";
93 private static final String BRIDGE_UNDERLAY = "br-phy";
94
95 private static final int DEVID_IDX_BRIDGE_OVERLAY = 0;
96 private static final int DEVID_IDX_BRIDGE_UNDERLAY_NOVA = 1;
97
98 private static final ControlProtocolVersion BRIDGE_DEFAULT_OF_VERSION = ControlProtocolVersion.OF_1_3;
99 private static final int OPENFLOW_PORT = 6653;
100 private static final String OPENFLOW_CHANNEL_PROTO = "tcp";
101 private static final String OVSDB_DEVICE_PREFIX = "ovsdb:";
102
103 private static final long TIMEOUT_DEVICE_CREATION_MS = 60000L;
104 private static final long TIMEOUT_PORT_ADDITION_MS = 120000L;
105
106
107 /**
108 * Utility class for OVS workflow.
109 */
110 public static final class OvsUtil {
111
112 private OvsUtil() {
113
114 }
115
116 private static final String OPENFLOW_DEVID_FORMAT = "of:%08x%08x";
117
118 /**
119 * Builds Open-flow device id with ip address, and index.
120 * @param addr ip address
121 * @param index index
122 * @return created device id
123 */
124 public static DeviceId buildOfDeviceId(IpAddress addr, int index) {
125 if (addr.isIp4()) {
126 Ip4Address v4Addr = addr.getIp4Address();
127 return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, v4Addr.toInt(), index));
128 } else if (addr.isIp6()) {
129 Ip6Address v6Addr = addr.getIp6Address();
130 return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, v6Addr.hashCode(), index));
131 } else {
132 return DeviceId.deviceId(String.format(OPENFLOW_DEVID_FORMAT, addr.hashCode(), index));
133 }
134 }
135
136 /**
137 * Builds OVS data path type.
138 * @param strOvsDatapathType string ovs data path type
139 * @return ovs data path type
140 * @throws WorkflowException workflow exception
141 */
142 public static final OvsDatapathType buildOvsDatapathType(String strOvsDatapathType) throws WorkflowException {
143 try {
144 return OvsDatapathType.valueOf(strOvsDatapathType.toUpperCase());
145 } catch (IllegalArgumentException e) {
146 throw new WorkflowException(e);
147 }
148 }
149
150 /**
151 * Gets OVSDB behavior.
152 * @param context workflow context
153 * @param mgmtIp management ip
154 * @param behaviourClass behavior class
155 * @param <T> behavior class
156 * @return OVSDB behavior
157 * @throws WorkflowException workflow exception
158 */
159 public static final <T extends Behaviour> T getOvsdbBehaviour(WorkflowContext context, String mgmtIp,
160 Class<T> behaviourClass) throws WorkflowException {
161
162 DriverService driverService = context.getService(DriverService.class);
163
164 DeviceId devId = ovsdbDeviceId(mgmtIp);
165 DriverHandler handler = driverService.createHandler(devId);
166 if (Objects.isNull(handler)) {
167 throw new WorkflowException("Failed to get DriverHandler for " + devId);
168 }
169 T behaviour;
170 try {
171 behaviour = handler.behaviour(behaviourClass);
172 if (Objects.isNull(behaviour)) {
173 throw new WorkflowException("Failed to get " + behaviourClass + " for " + devId + "-" + handler);
174 }
175 } catch (IllegalArgumentException e) {
176 throw new WorkflowException("Failed to get " + behaviourClass + " for " + devId + "-" + handler);
177 }
178 return behaviour;
179 }
180
181 /**
182 * Gets bridge description.
183 * @param bridgeConfig bridge config
184 * @param bridgeName bridge name
185 * @return bridge description optional
186 */
187 public static final Optional<BridgeDescription> getBridgeDescription(BridgeConfig bridgeConfig,
188 String bridgeName) {
189 try {
190 Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
191 for (BridgeDescription br: bridges) {
192 if (Objects.equals(bridgeName, br.name())) {
193 return Optional.of(br);
194 }
195 }
196 } catch (Exception e) {
197 log.error("Exception : ", e);
198 }
199 return Optional.empty();
200 }
201
202 /**
203 * Builds OVSDB device id.
204 * @param mgmtIp management ip address string
205 * @return OVSDB device id
206 */
207 public static final DeviceId ovsdbDeviceId(String mgmtIp) {
208 return DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(mgmtIp));
209 }
210
211 /**
212 * Returns {@code true} if this bridge is available;
213 * returns {@code false} otherwise.
214 * @param context workflow context
215 * @param devId device id
216 * @return {@code true} if this bridge is available; {@code false} otherwise.
217 * @throws WorkflowException workflow exception
218 */
219 public static final boolean isAvailableBridge(WorkflowContext context, DeviceId devId)
220 throws WorkflowException {
221
222 if (Objects.isNull(devId)) {
223 throw new WorkflowException("Invalid device id in data model");
224 }
225
226 DeviceService deviceService = context.getService(DeviceService.class);
227 Device dev = deviceService.getDevice(devId);
228 if (Objects.isNull(dev)) {
229 return false;
230 }
231
232 return deviceService.isAvailable(devId);
233 }
234
235 /**
236 * Gets openflow controller information list.
237 * @param context workflow context
238 * @return openflow controller information list
239 * @throws WorkflowException workflow exception
240 */
241 public static final List<ControllerInfo> getOpenflowControllerInfoList(WorkflowContext context)
242 throws WorkflowException {
243 ClusterService clusterService = context.getService(ClusterService.class);
244 java.util.List<org.onosproject.net.behaviour.ControllerInfo> controllers = new ArrayList<>();
245 Sets.newHashSet(clusterService.getNodes()).forEach(
246 controller -> {
247 org.onosproject.net.behaviour.ControllerInfo ctrlInfo =
248 new org.onosproject.net.behaviour.ControllerInfo(controller.ip(),
249 OPENFLOW_PORT,
250 OPENFLOW_CHANNEL_PROTO);
251 controllers.add(ctrlInfo);
252 }
253 );
254 return controllers;
255 }
256
257 /**
258 * Creates bridge.
259 * @param bridgeConfig bridge config
260 * @param name bridge name to create
261 * @param dpid openflow data path id of bridge to create
262 * @param ofControllers openflow controller information list
263 * @param datapathType OVS data path type
264 */
265 public static final void createBridge(BridgeConfig bridgeConfig, String name, String dpid,
266 List<ControllerInfo> ofControllers, OvsDatapathType datapathType) {
267 BridgeDescription.Builder bridgeDescBuilder = DefaultBridgeDescription.builder()
268 .name(name)
269 .failMode(BridgeDescription.FailMode.SECURE)
270 .datapathId(dpid)
271 .disableInBand()
272 .controlProtocols(Collections.singletonList(BRIDGE_DEFAULT_OF_VERSION))
273 .controllers(ofControllers);
274
275 if (datapathType != null && !(datapathType.equals(OvsDatapathType.EMPTY))) {
276 bridgeDescBuilder.datapathType(datapathType.toString());
277 log.info("create {} with dataPathType {}", name, datapathType);
278 }
279
280 BridgeDescription bridgeDesc = bridgeDescBuilder.build();
281 bridgeConfig.addBridge(bridgeDesc);
282 }
283
284 /**
285 * Index of data path id in openflow device id.
286 */
287 private static final int DPID_BEGIN_INDEX = 3;
288
289 /**
290 * Gets bridge data path id.
291 * @param devId device id
292 * @return bridge data path id
293 */
294 public static final String bridgeDatapathId(DeviceId devId) {
295 return devId.toString().substring(DPID_BEGIN_INDEX);
296 }
297
298 /**
299 * Gets OVSDB client.
300 * @param context workflow context
301 * @param strMgmtIp management ip address
302 * @param intOvsdbPort OVSDB port
303 * @return ovsdb client
304 * @throws WorkflowException workflow exception
305 */
306 public static final OvsdbClientService getOvsdbClient(
307 WorkflowContext context, String strMgmtIp, int intOvsdbPort) throws WorkflowException {
308 IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
309 TpPort ovsdbPort = TpPort.tpPort(intOvsdbPort);
310 OvsdbController ovsdbController = context.getService(OvsdbController.class);
311 return ovsdbController.getOvsdbClient(new OvsdbNodeId(mgmtIp, ovsdbPort.toInt()));
312 }
313
314 /**
315 * Checks whether 2 controller informations include same controller information.
316 * @param a controller information list
317 * @param b controller information list
318 * @return {@code true} if 2 controller informations include same controller information
319 */
320 public static boolean isEqual(List<ControllerInfo> a, List<ControllerInfo> b) {
321 if (a == b) {
322 return true;
323 } else if (a == null) {
324 // equivalent to (a == null && b != null)
325 return false;
326 } else if (b == null) {
327 // equivalent to (a != null && b == null)
328 return false;
329 } else if (a.size() != b.size()) {
330 return false;
331 }
332
333 return a.containsAll(b);
334 }
335
336 /**
337 * Gets the name of the port.
338 * @param port port
339 * @return the name of the port
340 */
341 public static final String portName(Port port) {
342 return port.annotations().value(PORT_NAME);
343 }
344 }
345
346 /**
347 * Work-let class for creating OVSDB device.
348 */
349 public static class CreateOvsdbDevice extends AbstractWorklet {
350
351 @JsonDataModel(path = MODEL_MGMT_IP)
352 String strMgmtIp;
353
354 @JsonDataModel(path = MODEL_OVSDB_PORT)
355 Integer intOvsdbPort;
356
357 @Override
358 public boolean isNext(WorkflowContext context) throws WorkflowException {
359
360 OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
361 return ovsdbClient == null || !ovsdbClient.isConnected();
362 }
363
364 @Override
365 public void process(WorkflowContext context) throws WorkflowException {
366 IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
367 TpPort ovsdbPort = TpPort.tpPort(intOvsdbPort);
368 OvsdbController ovsdbController = context.getService(OvsdbController.class);
369 context.waitCompletion(DeviceEvent.class, OVSDB_DEVICE_PREFIX.concat(strMgmtIp),
370 () -> ovsdbController.connect(mgmtIp, ovsdbPort),
371 TIMEOUT_DEVICE_CREATION_MS
372 );
373 }
374
375 @Override
376 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
377 if (!(event instanceof DeviceEvent)) {
378 return false;
379 }
380 DeviceEvent deviceEvent = (DeviceEvent) event;
381 Device device = deviceEvent.subject();
382 switch (deviceEvent.type()) {
383 case DEVICE_ADDED:
384 case DEVICE_AVAILABILITY_CHANGED:
385 case DEVICE_UPDATED:
386 return context.getService(DeviceService.class).isAvailable(device.id());
387 default:
388 return false;
389 }
390 }
391
392 @Override
393 public void timeout(WorkflowContext context) throws WorkflowException {
394 if (!isNext(context)) {
395 context.completed(); //Complete the job of worklet by timeout
396 } else {
397 super.timeout(context);
398 }
399 }
400 }
401
402
403 /**
404 * Work-let class for removing OVSDB device.
405 */
406 public static class RemoveOvsdbDevice extends AbstractWorklet {
407
408 @JsonDataModel(path = MODEL_MGMT_IP)
409 String strMgmtIp;
410
411 @Override
412 public boolean isNext(WorkflowContext context) throws WorkflowException {
413
414 DeviceId devId = DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(strMgmtIp));
415
416 Device dev = context.getService(DeviceService.class).getDevice(devId);
417 return dev != null;
418 }
419
420 @Override
421 public void process(WorkflowContext context) throws WorkflowException {
422 IpAddress mgmtIp = IpAddress.valueOf(strMgmtIp);
423 check(mgmtIp != null, "mgmt ip is invalid");
424 DeviceId devId = DeviceId.deviceId(OVSDB_DEVICE_PREFIX.concat(strMgmtIp));
425 DeviceAdminService adminService = context.getService(DeviceAdminService.class);
426
427 context.waitCompletion(DeviceEvent.class, devId.toString(),
428 () -> adminService.removeDevice(devId),
429 TIMEOUT_DEVICE_CREATION_MS
430 );
431 }
432
433 @Override
434 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
435 if (!(event instanceof DeviceEvent)) {
436 return false;
437 }
438 DeviceEvent deviceEvent = (DeviceEvent) event;
439 switch (deviceEvent.type()) {
440 case DEVICE_REMOVED:
441 return !isNext(context);
442 default:
443 return false;
444 }
445 }
446
447 @Override
448 public void timeout(WorkflowContext context) throws WorkflowException {
449 if (!isNext(context)) {
450 context.completed(); //Complete worklet by timeout
451 } else {
452 super.timeout(context);
453 }
454 }
455 }
456
457 /**
458 * Work-let class for updating OVS version.
459 */
460 public static class UpdateOvsVersion extends AbstractWorklet {
461
462 @JsonDataModel(path = MODEL_OVS_VERSION, optional = true)
463 String strOvsVersion;
464
465 @JsonDataModel(path = MODEL_SSH_ACCESSINFO)
466 JsonNode strSshAccessInfo;
467
468 @Override
469 public boolean isNext(WorkflowContext context) throws WorkflowException {
470
471 return strOvsVersion == null;
472 }
473
474 @Override
475 public void process(WorkflowContext context) throws WorkflowException {
476
477 SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
478 check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
479
480 OvsVersion ovsVersion = SshUtil.exec(sshAccessInfo,
481 session -> SshUtil.fetchOvsVersion(session));
482
483 check(Objects.nonNull(ovsVersion), "Failed to fetch ovs version " + context.data());
484 strOvsVersion = ovsVersion.toString();
485
486 context.completed();
487 }
488 }
489
490 /**
491 * Work-let class for updating overlay bridge device id.
492 */
493 public static class UpdateOverlayBridgeId extends AbstractWorklet {
494
495 @JsonDataModel(path = MODEL_MGMT_IP)
496 String strMgmtIp;
497
498 @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
499 String strOfDevIdOverlay;
500
501 @Override
502 public boolean isNext(WorkflowContext context) throws WorkflowException {
503
504 return strOfDevIdOverlay == null;
505 }
506
507 @Override
508 public void process(WorkflowContext context) throws WorkflowException {
509
510 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
511 Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
512 if (optBd.isPresent()) {
513 Optional<DeviceId> optDevId = optBd.get().deviceId();
514 if (optDevId.isPresent()) {
515 log.info("Updates {} of device id with existing device id {}", BRIDGE_OVERLAY, optDevId.get());
516 strOfDevIdOverlay = optDevId.get().toString();
517 } else {
518 DeviceId newDevId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
519 log.info("Failed to find devId. Updates {} of device id with new device id {}",
520 BRIDGE_OVERLAY, newDevId);
521 strOfDevIdOverlay = newDevId.toString();
522 }
523 } else {
524 DeviceId newDevId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
525 log.info("Failed to find description. Updates {} of device id with new device id {}",
526 BRIDGE_OVERLAY, newDevId);
527 strOfDevIdOverlay = newDevId.toString();
528 }
529
530 context.completed();
531 }
532 }
533
534 /**
535 * Work-let class for creating overlay openflow bridge.
536 */
537 public static class CreateOverlayBridge extends AbstractWorklet {
538
539 @JsonDataModel(path = MODEL_MGMT_IP)
540 String strMgmtIp;
541
542 @JsonDataModel(path = MODEL_OVSDB_PORT)
543 Integer intOvsdbPort;
544
545 @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
546 String strOvsDatapath;
547
548 @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
549 String strOfDevIdOverlay;
550
551 @Override
552 public boolean isNext(WorkflowContext context) throws WorkflowException {
553
554 check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
555 return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(strOfDevIdOverlay));
556 }
557
558 @Override
559 public void process(WorkflowContext context) throws WorkflowException {
560
561 check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
562 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
563 List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
564 DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdOverlay);
565
566 if (ofControllers == null || ofControllers.size() == 0) {
567 throw new WorkflowException("Invalid of controllers");
568 }
569
570 Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_OVERLAY);
571 if (!optBd.isPresent()) {
572
573 // If bridge does not exist, just creates a new bridge.
574 context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
575 () -> OvsUtil.createBridge(bridgeConfig,
576 BRIDGE_OVERLAY,
577 OvsUtil.bridgeDatapathId(ofDeviceId),
578 ofControllers,
579 OvsUtil.buildOvsDatapathType(strOvsDatapath)),
580 TIMEOUT_DEVICE_CREATION_MS
581 );
582 return;
583
584 } else {
585 BridgeDescription bd = optBd.get();
586 if (OvsUtil.isEqual(ofControllers, bd.controllers())) {
587 log.error("{} has valid controller setting({})", BRIDGE_OVERLAY, bd.controllers());
588 context.completed();
589 return;
590 }
591
592 OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
593 if (ovsdbClient == null || !ovsdbClient.isConnected()) {
594 throw new WorkflowException("Invalid ovsdb client for " + strMgmtIp);
595 }
596
597 // If controller settings are not matched, set controller with valid controller information.
598 context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
599 () -> ovsdbClient.setControllersWithDeviceId(bd.deviceId().get(), ofControllers),
600 TIMEOUT_DEVICE_CREATION_MS
601 );
602 return;
603 }
604 }
605
606 @Override
607 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
608 if (!(event instanceof DeviceEvent)) {
609 return false;
610 }
611 DeviceEvent deviceEvent = (DeviceEvent) event;
612 Device device = deviceEvent.subject();
613 switch (deviceEvent.type()) {
614 case DEVICE_ADDED:
615 case DEVICE_AVAILABILITY_CHANGED:
616 case DEVICE_UPDATED:
617 return context.getService(DeviceService.class).isAvailable(device.id());
618 default:
619 return false;
620 }
621 }
622
623 @Override
624 public void timeout(WorkflowContext context) throws WorkflowException {
625 if (!isNext(context)) {
626 context.completed(); //Complete the job of worklet by timeout
627 } else {
628 super.timeout(context);
629 }
630 }
631 }
632
633 /**
634 * Work-let class for updating underlay bridge device id.
635 */
636 public static class UpdateUnderlayBridgeId extends AbstractWorklet {
637
638 @JsonDataModel(path = MODEL_MGMT_IP)
639 String strMgmtIp;
640
641 @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE, optional = true)
642 String strOfDevIdUnderlay;
643
644 @Override
645 public boolean isNext(WorkflowContext context) throws WorkflowException {
646
647 return strOfDevIdUnderlay == null;
648 }
649
650 @Override
651 public void process(WorkflowContext context) throws WorkflowException {
652
653 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
654 Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_UNDERLAY);
655 if (optBd.isPresent()) {
656 Optional<DeviceId> optDevId = optBd.get().deviceId();
657 if (optDevId.isPresent()) {
658 log.info("Updates {} of device id with existing device id {}", BRIDGE_UNDERLAY, optDevId.get());
659 strOfDevIdUnderlay = optDevId.get().toString();
660 } else {
661 DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp),
662 DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
663 log.info("Failed to find devId. Updates {} of device id with new device id {}",
664 BRIDGE_UNDERLAY, devId);
665 strOfDevIdUnderlay = devId.toString();
666 }
667 } else {
668 DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
669 log.info("Failed to find description. Updates {} of device id with new device id {}",
670 BRIDGE_UNDERLAY, devId);
671 strOfDevIdUnderlay = devId.toString();
672 }
673
674 context.completed();
675 }
676 }
677
678 /**
679 * Work-let class for creating underlay openflow bridge.
680 */
681 public static class CreateUnderlayBridge extends AbstractWorklet {
682
683 @JsonDataModel(path = MODEL_MGMT_IP)
684 String strMgmtIp;
685
686 @JsonDataModel(path = MODEL_OVSDB_PORT)
687 Integer intOvsdbPort;
688
689 @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
690 String strOvsDatapath;
691
692 @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE)
693 String strOfDevIdUnderlay;
694
695 @Override
696 public boolean isNext(WorkflowContext context) throws WorkflowException {
697
698 check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
699 return !OvsUtil.isAvailableBridge(context, DeviceId.deviceId(strOfDevIdUnderlay));
700 }
701
702 @Override
703 public void process(WorkflowContext context) throws WorkflowException {
704
705 check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
706 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
707 List<ControllerInfo> ofControllers = OvsUtil.getOpenflowControllerInfoList(context);
708 DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdUnderlay);
709
710 if (ofControllers == null || ofControllers.size() == 0) {
711 throw new WorkflowException("Invalid of controllers");
712 }
713
714 Optional<BridgeDescription> optBd = OvsUtil.getBridgeDescription(bridgeConfig, BRIDGE_UNDERLAY);
715 if (!optBd.isPresent()) {
716
717 // If bridge does not exist, just creates a new bridge.
718 context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
719 () -> OvsUtil.createBridge(bridgeConfig,
720 BRIDGE_UNDERLAY,
721 OvsUtil.bridgeDatapathId(ofDeviceId),
722 ofControllers,
723 OvsUtil.buildOvsDatapathType(strOvsDatapath)),
724 TIMEOUT_DEVICE_CREATION_MS
725 );
726 return;
727
728 } else {
729 BridgeDescription bd = optBd.get();
730 if (OvsUtil.isEqual(ofControllers, bd.controllers())) {
731 log.error("{} has valid controller setting({})", BRIDGE_UNDERLAY, bd.controllers());
732 context.completed();
733 return;
734 }
735
736 OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
737 if (ovsdbClient == null || !ovsdbClient.isConnected()) {
738 throw new WorkflowException("Invalid ovsdb client for " + strMgmtIp);
739 }
740
741 // If controller settings are not matched, set controller with valid controller information.
742 context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
743 () -> ovsdbClient.setControllersWithDeviceId(bd.deviceId().get(), ofControllers),
744 TIMEOUT_DEVICE_CREATION_MS
745 );
746 return;
747 }
748 }
749
750 @Override
751 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
752 if (!(event instanceof DeviceEvent)) {
753 return false;
754 }
755 DeviceEvent deviceEvent = (DeviceEvent) event;
756 Device device = deviceEvent.subject();
757 switch (deviceEvent.type()) {
758 case DEVICE_ADDED:
759 case DEVICE_AVAILABILITY_CHANGED:
760 case DEVICE_UPDATED:
761 return context.getService(DeviceService.class).isAvailable(device.id());
762 default:
763 return false;
764 }
765 }
766
767 @Override
768 public void timeout(WorkflowContext context) throws WorkflowException {
769 if (!isNext(context)) {
770 context.completed(); //Complete the job of worklet by timeout
771 } else {
772 super.timeout(context);
773 }
774 }
775
776 }
777
778 /**
779 * Work-let class for creating vxlan port on the overlay bridge.
780 */
781 public static class CreateOverlayBridgeVxlanPort extends AbstractWorklet {
782
783 @JsonDataModel(path = MODEL_MGMT_IP)
784 String strMgmtIp;
785
786 @JsonDataModel(path = MODEL_OF_DEVID_OVERLAY_BRIDGE, optional = true)
787 String strOfDevIdOverlay;
788
789 private static final String OVS_VXLAN_PORTNAME = "vxlan";
790
791 @Override
792 public boolean isNext(WorkflowContext context) throws WorkflowException {
793
794 check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
795 DeviceId deviceId = DeviceId.deviceId(strOfDevIdOverlay);
796 if (Objects.isNull(deviceId)) {
797 throw new WorkflowException("Invalid br-int bridge, before creating VXLAN port");
798 }
799
800 DeviceService deviceService = context.getService(DeviceService.class);
801 return !deviceService.getPorts(deviceId)
802 .stream()
803 .filter(port -> OvsUtil.portName(port).contains(OVS_VXLAN_PORTNAME) && port.isEnabled())
804 .findAny().isPresent();
805 }
806
807
808 @Override
809 public void process(WorkflowContext context) throws WorkflowException {
810
811 check(strOfDevIdOverlay != null, "invalid strOfDevIdOverlay");
812 TunnelDescription description = DefaultTunnelDescription.builder()
813 .deviceId(BRIDGE_OVERLAY)
814 .ifaceName(OVS_VXLAN_PORTNAME)
815 .type(TunnelDescription.Type.VXLAN)
816 .remote(TunnelEndPoints.flowTunnelEndpoint())
817 .key(TunnelKeys.flowTunnelKey())
818 .build();
819
820 DeviceId ofDeviceId = DeviceId.deviceId(strOfDevIdOverlay);
821 InterfaceConfig interfaceConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, InterfaceConfig.class);
822
823 context.waitCompletion(DeviceEvent.class, ofDeviceId.toString(),
824 () -> interfaceConfig.addTunnelMode(BRIDGE_OVERLAY, description),
825 TIMEOUT_DEVICE_CREATION_MS
826 );
827 }
828
829 @Override
830 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
831 if (!(event instanceof DeviceEvent)) {
832 return false;
833 }
834 DeviceEvent deviceEvent = (DeviceEvent) event;
835 switch (deviceEvent.type()) {
836 case PORT_ADDED:
837 return !isNext(context);
838 default:
839 return false;
840 }
841 }
842
843 @Override
844 public void timeout(WorkflowContext context) throws WorkflowException {
845 if (!isNext(context)) {
846 context.completed(); //Complete the job of worklet by timeout
847 } else {
848 super.timeout(context);
849 }
850 }
851 }
852
853 /**
854 * Work-let class for adding physical ports on the underlay openflow bridge.
855 */
856 public static class AddPhysicalPortsOnUnderlayBridge extends AbstractWorklet {
857
858 @JsonDataModel(path = MODEL_MGMT_IP)
859 String strMgmtIp;
860
861 @JsonDataModel(path = MODEL_OVSDB_PORT)
862 Integer intOvsdbPort;
863
864 @JsonDataModel(path = MODEL_OF_DEVID_UNDERLAY_BRIDGE, optional = true)
865 String strOfDevIdUnderlay;
866
867 @JsonDataModel(path = MODEL_OVS_DATAPATH_TYPE)
868 String strOvsDatapath;
869
870 @JsonDataModel(path = MODEL_PHY_PORTS)
871 ArrayNode arrNodePhysicalPorts;
872
873 @Override
874 public boolean isNext(WorkflowContext context) throws WorkflowException {
875 check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
876 DeviceId brphyDevId = DeviceId.deviceId(strOfDevIdUnderlay);
877 return !hasAllPhysicalPorts(context, brphyDevId);
878 }
879
880 @Override
881 public void process(WorkflowContext context) throws WorkflowException {
882 check(strOfDevIdUnderlay != null, "invalid strOfDevIdUnderlay");
883 DeviceId brphyDevId = DeviceId.deviceId(strOfDevIdUnderlay);
884
885 context.waitCompletion(DeviceEvent.class, brphyDevId.toString(),
886 () -> addPhysicalPorts(context, brphyDevId, BRIDGE_UNDERLAY, strOvsDatapath),
887 TIMEOUT_PORT_ADDITION_MS
888 );
889 }
890
891 @Override
892 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
893 if (!(event instanceof DeviceEvent)) {
894 return false;
895 }
896 DeviceEvent deviceEvent = (DeviceEvent) event;
897 switch (deviceEvent.type()) {
898 case PORT_ADDED:
899 return !isNext(context);
900 default:
901 return false;
902 }
903 }
904
905 @Override
906 public void timeout(WorkflowContext context) throws WorkflowException {
907 if (!isNext(context)) {
908 context.completed(); //Complete the job of worklet by timeout
909 } else {
910 super.timeout(context);
911 }
912 }
913
914 private final List<String> getPhysicalPorts(WorkflowContext context) throws WorkflowException {
915 List<String> ports = Lists.newArrayList();
916 for (JsonNode jsonNode : arrNodePhysicalPorts) {
917 check(jsonNode instanceof TextNode, "Invalid physical ports " + arrNodePhysicalPorts);
918 ports.add(jsonNode.asText());
919 }
920 return ports;
921 }
922
923 private final boolean hasAllPhysicalPorts(WorkflowContext context, DeviceId devId) throws WorkflowException {
924
925 List<Port> devPorts = context.getService(DeviceService.class).getPorts(devId);
926 check(devPorts != null, "Invalid device ports for " + devId);
927 List<String> physicalPorts = getPhysicalPorts(context);
928 check(physicalPorts != null, "Invalid physical ports" + context);
929
930 log.info("physicalPorts: {} for {}", physicalPorts, devId);
931 for (String port: physicalPorts) {
932 if (devPorts.stream().noneMatch(p -> OvsUtil.portName(p).contains(port))) {
933 return false;
934 }
935 }
936 return true;
937 }
938
939 private final boolean hasPort(WorkflowContext context, DeviceId devId, String portName)
940 throws WorkflowException {
941
942 List<Port> devPorts = context.getService(DeviceService.class).getPorts(devId);
943 check(devPorts != null, "Invalid device ports for " + devId);
944 return devPorts.stream().anyMatch(p -> OvsUtil.portName(p).contains(portName));
945 }
946
947 private final void addPhysicalPorts(WorkflowContext context, DeviceId devId, String bridgeName,
948 String strOvsDatapathType)
949 throws WorkflowException {
950 OvsdbClientService ovsdbClient = OvsUtil.getOvsdbClient(context, strMgmtIp, intOvsdbPort);
951 check(ovsdbClient != null, "Invalid ovsdb client");
952
953 List<String> physicalPorts = getPhysicalPorts(context);
954 check(physicalPorts != null, "Invalid physical ports");
955
956 OvsDatapathType datapathType = OvsUtil.buildOvsDatapathType(strOvsDatapathType);
957 check(datapathType != null, "Invalid data path type");
958
959 List<String> sortedPhyPorts = physicalPorts.stream().sorted().collect(Collectors.toList());
960
961 for (String port: sortedPhyPorts) {
962 if (hasPort(context, devId, port)) {
963 continue;
964 }
965 log.info("adding port {} on {}", port, devId);
966 switch (datapathType) {
967 case NETDEV:
968 throw new WorkflowException("NETDEV datapathType are not supported");
969 //break;
970 case SYSTEM:
971 default:
972 ovsdbClient.createPort(BridgeName.bridgeName(bridgeName).name(), port);
973 }
974 }
975 }
976 }
977
978 /**
979 * Work-let class for configure local ip of underlay openflow bridge.
980 */
981 public static class ConfigureUnderlayBridgeLocalIp extends AbstractWorklet {
982
983 @JsonDataModel(path = MODEL_SSH_ACCESSINFO)
984 JsonNode strSshAccessInfo;
985
986 @JsonDataModel(path = MODEL_VTEP_IP)
987 String strVtepIp;
988
989 @Override
990 public boolean isNext(WorkflowContext context) throws WorkflowException {
991
992 SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
993 check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
994
995 NetworkAddress vtepIp = NetworkAddress.valueOf(strVtepIp);
996 check(Objects.nonNull(vtepIp), "Invalid vtep ip " + context.data());
997
998 return !SshUtil.exec(sshAccessInfo,
999 session ->
1000 SshUtil.hasIpAddrOnInterface(session, BRIDGE_UNDERLAY, vtepIp)
1001 && SshUtil.isIpLinkUpOnInterface(session, BRIDGE_UNDERLAY)
1002 );
1003 }
1004
1005 @Override
1006 public void process(WorkflowContext context) throws WorkflowException {
1007
1008 SshAccessInfo sshAccessInfo = SshAccessInfo.valueOf(strSshAccessInfo);
1009 check(Objects.nonNull(sshAccessInfo), "Invalid ssh access info " + context.data());
1010
1011 NetworkAddress vtepIp = NetworkAddress.valueOf(strVtepIp);
1012 check(Objects.nonNull(vtepIp), "Invalid vtep ip " + context.data());
1013
1014 SshUtil.exec(sshAccessInfo,
1015 session -> {
1016 SshUtil.addIpAddrOnInterface(session, BRIDGE_UNDERLAY, vtepIp);
1017 SshUtil.setIpLinkUpOnInterface(session, BRIDGE_UNDERLAY);
1018 return "";
1019 });
1020
1021 context.completed();
1022 }
1023 }
1024
1025 /**
1026 * Work-let class for deleting overlay bridge config.
1027 */
1028 public static class DeleteOverlayBridgeConfig extends AbstractWorklet {
1029
1030 @JsonDataModel(path = MODEL_MGMT_IP)
1031 String strMgmtIp;
1032
1033 @Override
1034 public boolean isNext(WorkflowContext context) throws WorkflowException {
1035
1036 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
1037
1038 Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
1039 return bridges.stream()
1040 .anyMatch(bd -> Objects.equals(bd.name(), BRIDGE_OVERLAY));
1041 }
1042
1043 @Override
1044 public void process(WorkflowContext context) throws WorkflowException {
1045
1046 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
1047
1048 bridgeConfig.deleteBridge(BridgeName.bridgeName(BRIDGE_OVERLAY));
1049
1050 for (int i = 0; i < 10; i++) {
1051 if (!isNext(context)) {
1052 context.completed();
1053 return;
1054 }
1055 sleep(50);
1056 }
1057 throw new WorkflowException("Timeout happened for removing config");
1058 }
1059
1060 protected void sleep(long ms) {
1061 try {
1062 Thread.sleep(ms);
1063 } catch (InterruptedException e) {
1064 e.printStackTrace();
1065 }
1066 }
1067 }
1068
1069 /**
1070 * Work-let class for removing overlay bridge openflow device.
1071 */
1072 public static class RemoveOverlayBridgeOfDevice extends AbstractWorklet {
1073
1074 @JsonDataModel(path = MODEL_MGMT_IP)
1075 String strMgmtIp;
1076
1077 @Override
1078 public boolean isNext(WorkflowContext context) throws WorkflowException {
1079
1080 DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
1081
1082 Device dev = context.getService(DeviceService.class).getDevice(devId);
1083 return dev != null;
1084 }
1085
1086 @Override
1087 public void process(WorkflowContext context) throws WorkflowException {
1088
1089 DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_OVERLAY);
1090
1091 DeviceAdminService adminService = context.getService(DeviceAdminService.class);
1092
1093 context.waitCompletion(DeviceEvent.class, devId.toString(),
1094 () -> adminService.removeDevice(devId),
1095 TIMEOUT_DEVICE_CREATION_MS
1096 );
1097 }
1098
1099 @Override
1100 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
1101 if (!(event instanceof DeviceEvent)) {
1102 return false;
1103 }
1104 DeviceEvent deviceEvent = (DeviceEvent) event;
1105 switch (deviceEvent.type()) {
1106 case DEVICE_REMOVED:
1107 return !isNext(context);
1108 default:
1109 return false;
1110 }
1111 }
1112
1113 @Override
1114 public void timeout(WorkflowContext context) throws WorkflowException {
1115 if (!isNext(context)) {
1116 context.completed(); //Complete the job of worklet by timeout
1117 } else {
1118 super.timeout(context);
1119 }
1120 }
1121 }
1122
1123 /**
1124 * Work-let class for deleting underlay bridge config.
1125 */
1126 public static class DeleteUnderlayBridgeConfig extends AbstractWorklet {
1127
1128 @JsonDataModel(path = MODEL_MGMT_IP)
1129 String strMgmtIp;
1130
1131 @Override
1132 public boolean isNext(WorkflowContext context) throws WorkflowException {
1133
1134 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
1135
1136 Collection<BridgeDescription> bridges = bridgeConfig.getBridges();
1137 return bridges.stream()
1138 .anyMatch(bd -> Objects.equals(bd.name(), BRIDGE_UNDERLAY));
1139 }
1140
1141 @Override
1142 public void process(WorkflowContext context) throws WorkflowException {
1143
1144 BridgeConfig bridgeConfig = OvsUtil.getOvsdbBehaviour(context, strMgmtIp, BridgeConfig.class);
1145
1146 bridgeConfig.deleteBridge(BridgeName.bridgeName(BRIDGE_UNDERLAY));
1147
1148 for (int i = 0; i < 10; i++) {
1149 if (!isNext(context)) {
1150 context.completed();
1151 return;
1152 }
1153 sleep(50);
1154 }
1155 throw new WorkflowException("Timeout happened for removing config");
1156 }
1157
1158 protected void sleep(long ms) {
1159 try {
1160 Thread.sleep(ms);
1161 } catch (InterruptedException e) {
1162 e.printStackTrace();
1163 }
1164 }
1165 }
1166
1167 /**
1168 * Work-let class for removing underlay bridge openflow device.
1169 */
1170 public static class RemoveUnderlayBridgeOfDevice extends AbstractWorklet {
1171
1172 @JsonDataModel(path = MODEL_MGMT_IP)
1173 String strMgmtIp;
1174
1175 @Override
1176 public boolean isNext(WorkflowContext context) throws WorkflowException {
1177
1178 DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
1179
1180 Device dev = context.getService(DeviceService.class).getDevice(devId);
1181 return dev != null;
1182 }
1183
1184 @Override
1185 public void process(WorkflowContext context) throws WorkflowException {
1186
1187 DeviceId devId = OvsUtil.buildOfDeviceId(IpAddress.valueOf(strMgmtIp), DEVID_IDX_BRIDGE_UNDERLAY_NOVA);
1188
1189 DeviceAdminService adminService = context.getService(DeviceAdminService.class);
1190
1191 context.waitCompletion(DeviceEvent.class, devId.toString(),
1192 () -> adminService.removeDevice(devId),
1193 TIMEOUT_DEVICE_CREATION_MS
1194 );
1195 }
1196
1197 @Override
1198 public boolean isCompleted(WorkflowContext context, Event event)throws WorkflowException {
1199 if (!(event instanceof DeviceEvent)) {
1200 return false;
1201 }
1202 DeviceEvent deviceEvent = (DeviceEvent) event;
1203 switch (deviceEvent.type()) {
1204 case DEVICE_REMOVED:
1205 return !isNext(context);
1206 default:
1207 return false;
1208 }
1209 }
1210
1211 @Override
1212 public void timeout(WorkflowContext context) throws WorkflowException {
1213 if (!isNext(context)) {
1214 context.completed(); //Complete the job of worklet by timeout
1215 } else {
1216 super.timeout(context);
1217 }
1218 }
1219 }
1220}