blob: f224dbc5c310790627bd9ef83971976e2e4f4f7d [file] [log] [blame]
Jian Li138f51f2021-01-06 03:29:58 +09001/*
2 * Copyright 2021-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.kubevirtnode.impl;
17
Jian Lie7a702f2021-01-28 16:14:54 +090018import com.google.common.collect.Lists;
19import org.onlab.packet.IpAddress;
Jian Li138f51f2021-01-06 03:29:58 +090020import org.onlab.util.Tools;
21import org.onosproject.cfg.ComponentConfigService;
22import org.onosproject.cluster.ClusterService;
23import org.onosproject.cluster.LeadershipService;
24import org.onosproject.cluster.NodeId;
25import org.onosproject.core.ApplicationId;
26import org.onosproject.core.CoreService;
Jian Lie7a702f2021-01-28 16:14:54 +090027import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
Jian Li138f51f2021-01-06 03:29:58 +090028import org.onosproject.kubevirtnode.api.KubevirtNode;
29import org.onosproject.kubevirtnode.api.KubevirtNodeAdminService;
30import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
31import org.onosproject.kubevirtnode.api.KubevirtNodeHandler;
32import org.onosproject.kubevirtnode.api.KubevirtNodeListener;
33import org.onosproject.kubevirtnode.api.KubevirtNodeState;
34import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
35import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.Port;
38import org.onosproject.net.behaviour.BridgeConfig;
39import org.onosproject.net.behaviour.BridgeDescription;
40import org.onosproject.net.behaviour.BridgeName;
41import org.onosproject.net.behaviour.ControllerInfo;
42import org.onosproject.net.behaviour.DefaultBridgeDescription;
43import org.onosproject.net.behaviour.DefaultPatchDescription;
44import org.onosproject.net.behaviour.DefaultTunnelDescription;
45import org.onosproject.net.behaviour.InterfaceConfig;
46import org.onosproject.net.behaviour.PatchDescription;
47import org.onosproject.net.behaviour.TunnelDescription;
48import org.onosproject.net.behaviour.TunnelEndPoints;
49import org.onosproject.net.behaviour.TunnelKey;
50import org.onosproject.net.device.DeviceAdminService;
51import org.onosproject.net.device.DeviceEvent;
52import org.onosproject.net.device.DeviceListener;
53import org.onosproject.net.device.DeviceService;
54import org.onosproject.ovsdb.controller.OvsdbClientService;
55import org.onosproject.ovsdb.controller.OvsdbController;
56import org.osgi.service.component.ComponentContext;
57import org.osgi.service.component.annotations.Activate;
58import org.osgi.service.component.annotations.Component;
59import org.osgi.service.component.annotations.Deactivate;
60import org.osgi.service.component.annotations.Modified;
61import org.osgi.service.component.annotations.Reference;
62import org.osgi.service.component.annotations.ReferenceCardinality;
63import org.slf4j.Logger;
64
65import java.util.Dictionary;
66import java.util.List;
67import java.util.Objects;
68import java.util.Set;
69import java.util.concurrent.ExecutorService;
70import java.util.stream.Collectors;
71
72import static java.lang.Thread.sleep;
73import static java.util.concurrent.Executors.newSingleThreadExecutor;
74import static org.onlab.packet.TpPort.tpPort;
75import static org.onlab.util.Tools.groupedThreads;
76import static org.onosproject.kubevirtnode.api.Constants.BRIDGE_PREFIX;
77import static org.onosproject.kubevirtnode.api.Constants.FLOW_KEY;
78import static org.onosproject.kubevirtnode.api.Constants.GENEVE;
79import static org.onosproject.kubevirtnode.api.Constants.GRE;
80import static org.onosproject.kubevirtnode.api.Constants.INTEGRATION_BRIDGE;
81import static org.onosproject.kubevirtnode.api.Constants.INTEGRATION_TO_PHYSICAL_PREFIX;
Daniel Parke7e3d6a2021-03-10 07:49:11 +090082import static org.onosproject.kubevirtnode.api.Constants.INTEGRATION_TO_TUNNEL;
Jian Li138f51f2021-01-06 03:29:58 +090083import static org.onosproject.kubevirtnode.api.Constants.PHYSICAL_TO_INTEGRATION_SUFFIX;
Jian Lib5ab63c2021-02-03 17:54:28 +090084import static org.onosproject.kubevirtnode.api.Constants.TENANT_BRIDGE_PREFIX;
Jian Li138f51f2021-01-06 03:29:58 +090085import static org.onosproject.kubevirtnode.api.Constants.TUNNEL_BRIDGE;
Daniel Parke7e3d6a2021-03-10 07:49:11 +090086import static org.onosproject.kubevirtnode.api.Constants.TUNNEL_TO_INTEGRATION;
Jian Li138f51f2021-01-06 03:29:58 +090087import static org.onosproject.kubevirtnode.api.Constants.VXLAN;
Daniel Parke7e3d6a2021-03-10 07:49:11 +090088import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Jian Li138f51f2021-01-06 03:29:58 +090089import static org.onosproject.kubevirtnode.api.KubevirtNodeService.APP_ID;
90import static org.onosproject.kubevirtnode.api.KubevirtNodeState.COMPLETE;
91import static org.onosproject.kubevirtnode.api.KubevirtNodeState.DEVICE_CREATED;
92import static org.onosproject.kubevirtnode.api.KubevirtNodeState.INCOMPLETE;
93import static org.onosproject.kubevirtnode.api.KubevirtNodeState.INIT;
94import static org.onosproject.kubevirtnode.impl.OsgiPropertyConstants.AUTO_RECOVERY;
95import static org.onosproject.kubevirtnode.impl.OsgiPropertyConstants.AUTO_RECOVERY_DEFAULT;
96import static org.onosproject.kubevirtnode.impl.OsgiPropertyConstants.OVSDB_PORT;
97import static org.onosproject.kubevirtnode.impl.OsgiPropertyConstants.OVSDB_PORT_NUM_DEFAULT;
98import static org.onosproject.kubevirtnode.util.KubevirtNodeUtil.addOrRemoveSystemInterface;
99import static org.onosproject.kubevirtnode.util.KubevirtNodeUtil.getBooleanProperty;
100import static org.onosproject.kubevirtnode.util.KubevirtNodeUtil.getOvsdbClient;
101import static org.onosproject.kubevirtnode.util.KubevirtNodeUtil.isOvsdbConnected;
102import static org.onosproject.kubevirtnode.util.KubevirtNodeUtil.structurePortName;
103import static org.onosproject.net.AnnotationKeys.PORT_NAME;
104import static org.slf4j.LoggerFactory.getLogger;
105
106/**
107 * Service bootstraps kubernetes node based on its type.
108 */
109@Component(immediate = true,
110 property = {
111 OVSDB_PORT + ":Integer=" + OVSDB_PORT_NUM_DEFAULT,
112 AUTO_RECOVERY + ":Boolean=" + AUTO_RECOVERY_DEFAULT
113 }
114)
115public class DefaultKubevirtNodeHandler implements KubevirtNodeHandler {
116
117 private final Logger log = getLogger(getClass());
118
119 private static final String DEFAULT_OF_PROTO = "tcp";
120 private static final int DEFAULT_OFPORT = 6653;
121 private static final int DPID_BEGIN = 3;
122 private static final int NETWORK_BEGIN = 3;
123 private static final long SLEEP_SHORT_MS = 1000; // we wait 1s
Jian Lib9eb11d2021-02-19 18:13:10 +0900124 private static final long SLEEP_MID_MS = 2000; // we wait 2s
125 private static final long SLEEP_LONG_MS = 5000; // we wait 5s
Jian Li138f51f2021-01-06 03:29:58 +0900126
127 @Reference(cardinality = ReferenceCardinality.MANDATORY)
128 protected CoreService coreService;
129
130 @Reference(cardinality = ReferenceCardinality.MANDATORY)
131 protected LeadershipService leadershipService;
132
133 @Reference(cardinality = ReferenceCardinality.MANDATORY)
134 protected ClusterService clusterService;
135
136 @Reference(cardinality = ReferenceCardinality.MANDATORY)
137 protected DeviceService deviceService;
138
139 @Reference(cardinality = ReferenceCardinality.MANDATORY)
140 protected DeviceAdminService deviceAdminService;
141
142 @Reference(cardinality = ReferenceCardinality.MANDATORY)
143 protected OvsdbController ovsdbController;
144
145 @Reference(cardinality = ReferenceCardinality.MANDATORY)
146 protected KubevirtNodeAdminService nodeAdminService;
147
148 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lie7a702f2021-01-28 16:14:54 +0900149 protected KubevirtApiConfigService apiConfigService;
150
151 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li138f51f2021-01-06 03:29:58 +0900152 protected ComponentConfigService componentConfigService;
153
154 /** OVSDB server listen port. */
155 private int ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
156
157 /** Indicates whether auto-recover kubernetes node status on switch re-conn event. */
158 private boolean autoRecovery = AUTO_RECOVERY_DEFAULT;
159
160 private final ExecutorService eventExecutor = newSingleThreadExecutor(
161 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
162
163 private final DeviceListener ovsdbListener = new InternalOvsdbListener();
164 private final DeviceListener bridgeListener = new InternalBridgeListener();
165 private final KubevirtNodeListener kubevirtNodeListener = new InternalKubevirtNodeListener();
166
167 private ApplicationId appId;
168 private NodeId localNode;
169
170 @Activate
171 protected void activate() {
172 appId = coreService.getAppId(APP_ID);
173 localNode = clusterService.getLocalNode().id();
174
175 componentConfigService.registerProperties(getClass());
176 leadershipService.runForLeadership(appId.name());
177 deviceService.addListener(ovsdbListener);
178 deviceService.addListener(bridgeListener);
179 nodeAdminService.addListener(kubevirtNodeListener);
180
181 log.info("Started");
182 }
183
184 @Deactivate
185 protected void deactivate() {
186 nodeAdminService.removeListener(kubevirtNodeListener);
187 deviceService.removeListener(bridgeListener);
188 deviceService.removeListener(ovsdbListener);
189 componentConfigService.unregisterProperties(getClass(), false);
190 leadershipService.withdraw(appId.name());
191 eventExecutor.shutdown();
192
193 log.info("Stopped");
194 }
195
196 @Modified
197 protected void modified(ComponentContext context) {
198 readComponentConfiguration(context);
199
200 log.info("Modified");
201 }
202
203 @Override
204 public void processInitState(KubevirtNode node) {
205 if (!isOvsdbConnected(node, ovsdbPortNum, ovsdbController, deviceService)) {
206 ovsdbController.connect(node.managementIp(), tpPort(ovsdbPortNum));
207 return;
208 }
209 if (!deviceService.isAvailable(node.intgBridge())) {
210 createBridge(node, INTEGRATION_BRIDGE, node.intgBridge());
211 }
212
213 if (!deviceService.isAvailable(node.tunBridge())) {
214 createBridge(node, TUNNEL_BRIDGE, node.tunBridge());
215 }
216 }
217
218 @Override
219 public void processDeviceCreatedState(KubevirtNode node) {
220 try {
221 if (!isOvsdbConnected(node, ovsdbPortNum, ovsdbController, deviceService)) {
222 ovsdbController.connect(node.managementIp(), tpPort(ovsdbPortNum));
223 return;
224 }
225
226 // create patch ports between integration to other bridges
Jian Li543fe852021-02-04 17:25:01 +0900227 // for now, we do not directly connect br-int with br-tun,
228 // as br-int only deals with FLAT and VLAN network
229 // createPatchInterfaces(node);
Jian Li138f51f2021-01-06 03:29:58 +0900230
231 if (node.dataIp() != null && !isIntfEnabled(node, VXLAN)) {
232 createVxlanTunnelInterface(node);
233 }
234
235 if (node.dataIp() != null && !isIntfEnabled(node, GRE)) {
236 createGreTunnelInterface(node);
237 }
238
239 if (node.dataIp() != null && !isIntfEnabled(node, GENEVE)) {
240 createGeneveTunnelInterface(node);
241 }
Jian Li138f51f2021-01-06 03:29:58 +0900242 } catch (Exception e) {
243 log.error("Exception occurred because of {}", e);
244 }
245 }
246
247 @Override
248 public void processCompleteState(KubevirtNode node) {
249 // do something if needed
250 }
251
252 @Override
253 public void processIncompleteState(KubevirtNode node) {
254 // do something if needed
255 }
256
257 @Override
258 public void processOnBoardedState(KubevirtNode node) {
259 // do something if needed
260 }
261
262 /**
263 * Extracts properties from the component configuration context.
264 *
265 * @param context the component context
266 */
267 private void readComponentConfiguration(ComponentContext context) {
268 Dictionary<?, ?> properties = context.getProperties();
269
270 Integer ovsdbPortConfigured = Tools.getIntegerProperty(properties, OVSDB_PORT);
271 if (ovsdbPortConfigured == null) {
272 ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
273 log.info("OVSDB port is NOT configured, default value is {}", ovsdbPortNum);
274 } else {
275 ovsdbPortNum = ovsdbPortConfigured;
276 log.info("Configured. OVSDB port is {}", ovsdbPortNum);
277 }
278
279 Boolean autoRecoveryConfigured =
280 getBooleanProperty(properties, AUTO_RECOVERY);
281 if (autoRecoveryConfigured == null) {
282 autoRecovery = AUTO_RECOVERY_DEFAULT;
283 log.info("Auto recovery flag is NOT " +
284 "configured, default value is {}", autoRecovery);
285 } else {
286 autoRecovery = autoRecoveryConfigured;
287 log.info("Configured. Auto recovery flag is {}", autoRecovery);
288 }
289 }
290
291 /**
292 * Creates a bridge with a given name on a given kubernetes node.
293 *
294 * @param node kubevirt node
295 * @param bridgeName bridge name
296 * @param devId device identifier
297 */
298 private void createBridge(KubevirtNode node, String bridgeName, DeviceId devId) {
299 Device device = deviceService.getDevice(node.ovsdb());
300
Jian Lie7a702f2021-01-28 16:14:54 +0900301 IpAddress serverIp = apiConfigService.apiConfig().ipAddress();
302 ControllerInfo controlInfo = new ControllerInfo(serverIp, DEFAULT_OFPORT, DEFAULT_OF_PROTO);
303 List<ControllerInfo> controllers = Lists.newArrayList(controlInfo);
Jian Li138f51f2021-01-06 03:29:58 +0900304
305 String dpid = devId.toString().substring(DPID_BEGIN);
306
307 BridgeDescription.Builder builder = DefaultBridgeDescription.builder()
308 .name(bridgeName)
309 .failMode(BridgeDescription.FailMode.SECURE)
310 .datapathId(dpid)
311 .disableInBand()
312 .controllers(controllers);
313
314 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
315 bridgeConfig.addBridge(builder.build());
316 }
317
318 /**
319 * Creates a VXLAN tunnel interface in a given kubevirt node.
320 *
321 * @param node kubevirt node
322 */
323 private void createVxlanTunnelInterface(KubevirtNode node) {
324 createTunnelInterface(node, VXLAN, VXLAN);
325 }
326
327 /**
328 * Creates a GRE tunnel interface in a given kubevirt node.
329 *
330 * @param node kubevirt node
331 */
332 private void createGreTunnelInterface(KubevirtNode node) {
333 createTunnelInterface(node, GRE, GRE);
334 }
335
336 /**
337 * Creates a GENEVE tunnel interface in a given kubevirt node.
338 *
339 * @param node kubevirt node
340 */
341 private void createGeneveTunnelInterface(KubevirtNode node) {
342 createTunnelInterface(node, GENEVE, GENEVE);
343 }
344
345 /**
346 * Creates a tunnel interface in a given kubernetes node.
347 *
348 * @param node kubevirt node
Jian Lib9eb11d2021-02-19 18:13:10 +0900349 * @param type kubevirt type
350 * @param intfName tunnel interface name
Jian Li138f51f2021-01-06 03:29:58 +0900351 */
352 private void createTunnelInterface(KubevirtNode node,
353 String type, String intfName) {
354 if (isIntfEnabled(node, intfName)) {
355 return;
356 }
357
358 Device device = deviceService.getDevice(node.ovsdb());
359 if (device == null || !device.is(InterfaceConfig.class)) {
360 log.error("Failed to create tunnel interface on {}", node.ovsdb());
361 return;
362 }
363
364 TunnelDescription tunnelDesc = buildTunnelDesc(type, intfName);
365
366 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
367 ifaceConfig.addTunnelMode(intfName, tunnelDesc);
368 }
369
370 /**
371 * Builds tunnel description according to the network type.
372 *
373 * @param type network type
Jian Lib9eb11d2021-02-19 18:13:10 +0900374 * @param intfName tunnel interface
Jian Li138f51f2021-01-06 03:29:58 +0900375 * @return tunnel description
376 */
377 private TunnelDescription buildTunnelDesc(String type, String intfName) {
378 TunnelKey<String> key = new TunnelKey<>(FLOW_KEY);
379 if (VXLAN.equals(type) || GRE.equals(type) || GENEVE.equals(type)) {
380 TunnelDescription.Builder tdBuilder =
381 DefaultTunnelDescription.builder()
382 .deviceId(TUNNEL_BRIDGE)
383 .ifaceName(intfName)
384 .remote(TunnelEndPoints.flowTunnelEndpoint())
385 .key(key);
386
387 switch (type) {
388 case VXLAN:
389 tdBuilder.type(TunnelDescription.Type.VXLAN);
390 break;
391 case GRE:
392 tdBuilder.type(TunnelDescription.Type.GRE);
393 break;
394 case GENEVE:
395 tdBuilder.type(TunnelDescription.Type.GENEVE);
396 break;
397 default:
398 return null;
399 }
400
401 return tdBuilder.build();
402 }
403 return null;
404 }
405
406 /**
407 * Checks whether a given network interface in a given kubernetes node
408 * is enabled or not.
409 *
410 * @param node kubevirt node
411 * @param intf network interface name
412 * @return true if the given interface is enabled, false otherwise
413 */
414 private boolean isIntfEnabled(KubevirtNode node, String intf) {
415 return deviceService.isAvailable(node.tunBridge()) &&
416 deviceService.getPorts(node.tunBridge()).stream()
417 .anyMatch(port -> Objects.equals(
418 port.annotations().value(PORT_NAME), intf) &&
419 port.isEnabled());
420 }
421
Jian Li138f51f2021-01-06 03:29:58 +0900422 /**
423 * Bootstraps a new kubevirt node.
424 *
425 * @param node kubevirt node
426 */
427 private void bootstrapNode(KubevirtNode node) {
428 if (isCurrentStateDone(node)) {
429 setState(node, node.state().nextState());
430 } else {
431 log.trace("Processing {} state for {}", node.state(), node.hostname());
432 node.state().process(this, node);
433 }
434 }
435
436 /**
437 * Removes the existing kubevirt node.
438 *
439 * @param node kubevirt node
440 */
441 private void removeNode(KubevirtNode node) {
442 OvsdbClientService client = getOvsdbClient(node, ovsdbPortNum, ovsdbController);
443 if (client == null) {
444 log.info("Failed to get ovsdb client");
445 return;
446 }
447
448 // unprovision physical interfaces from the node
449 // this procedure includes detaching physical port from physical bridge,
450 // remove patch ports from br-int, removing physical bridge
451 unprovisionPhysicalInterfaces(node);
452
453 // delete tunnel bridge from the node
454 client.dropBridge(TUNNEL_BRIDGE);
455
456 // delete integration bridge from the node
457 client.dropBridge(INTEGRATION_BRIDGE);
458 }
459
460 /**
461 * Checks whether all requirements for this state are fulfilled or not.
462 *
463 * @param node kubevirt node
464 * @return true if all requirements are fulfilled, false otherwise
465 */
466 private boolean isCurrentStateDone(KubevirtNode node) {
467 switch (node.state()) {
468 case INIT:
469 return isInitStateDone(node);
470 case DEVICE_CREATED:
471 return isDeviceCreatedStateDone(node);
472 case COMPLETE:
473 case INCOMPLETE:
474 case ON_BOARDED:
475 // always return false
476 // run init CLI to re-trigger node bootstrap
477 return false;
478 default:
479 return true;
480 }
481 }
482
483 private boolean isInitStateDone(KubevirtNode node) {
484 if (!isOvsdbConnected(node, ovsdbPortNum,
485 ovsdbController, deviceService)) {
486 return false;
487 }
488
489 try {
490 // we need to wait a while, in case interfaces and bridges
491 // creation requires some time
492 sleep(SLEEP_SHORT_MS);
493 } catch (InterruptedException e) {
494 log.error("Exception caused during init state checking...");
495 }
496
497 cleanPhysicalInterfaces(node);
498
Jian Lib9eb11d2021-02-19 18:13:10 +0900499 // provision new physical interfaces on the given node
500 // this includes creating physical bridge, attaching physical port
501 // to physical bridge, adding patch ports to both physical bridge and br-int
502 provisionPhysicalInterfaces(node);
503
Daniel Parke7e3d6a2021-03-10 07:49:11 +0900504 if (node.type() == GATEWAY) {
505 createPatchInterfaceBetweenBrIntBrTun(node);
506 }
507
Jian Li138f51f2021-01-06 03:29:58 +0900508 return node.intgBridge() != null && node.tunBridge() != null &&
509 deviceService.isAvailable(node.intgBridge()) &&
510 deviceService.isAvailable(node.tunBridge());
511 }
512
513 private boolean isDeviceCreatedStateDone(KubevirtNode node) {
514
515 try {
516 // we need to wait a while, in case tunneling ports
517 // creation requires some time
Jian Lib9eb11d2021-02-19 18:13:10 +0900518 sleep(SLEEP_MID_MS);
Jian Li138f51f2021-01-06 03:29:58 +0900519 } catch (InterruptedException e) {
520 log.error("Exception caused during init state checking...");
521 }
522
523 if (node.dataIp() != null && !isIntfEnabled(node, VXLAN)) {
Jian Lib9eb11d2021-02-19 18:13:10 +0900524 log.warn("VXLAN interface is not enabled!");
Jian Li138f51f2021-01-06 03:29:58 +0900525 return false;
526 }
527 if (node.dataIp() != null && !isIntfEnabled(node, GRE)) {
Jian Lib9eb11d2021-02-19 18:13:10 +0900528 log.warn("GRE interface is not enabled!");
Jian Li138f51f2021-01-06 03:29:58 +0900529 return false;
530 }
531 if (node.dataIp() != null && !isIntfEnabled(node, GENEVE)) {
Jian Lib9eb11d2021-02-19 18:13:10 +0900532 log.warn("GENEVE interface is not enabled!");
Jian Li138f51f2021-01-06 03:29:58 +0900533 return false;
534 }
535
536 for (KubevirtPhyInterface phyIntf : node.phyIntfs()) {
537 if (phyIntf == null) {
Jian Lib9eb11d2021-02-19 18:13:10 +0900538 log.warn("Physnet interface is invalid");
Jian Li138f51f2021-01-06 03:29:58 +0900539 return false;
540 }
541
Jian Lib9eb11d2021-02-19 18:13:10 +0900542 try {
543 // we need to wait a while, in case tunneling ports
544 // creation requires some time
545 sleep(SLEEP_LONG_MS);
546 } catch (InterruptedException e) {
547 log.error("Exception caused during init state checking...");
548 }
549
Jian Li138f51f2021-01-06 03:29:58 +0900550 String bridgeName = BRIDGE_PREFIX + phyIntf.network();
551 String patchPortName = structurePortName(
552 INTEGRATION_TO_PHYSICAL_PREFIX + phyIntf.network());
553
554 if (!(hasPhyBridge(node, bridgeName) &&
555 hasPhyPatchPort(node, patchPortName) &&
556 hasPhyIntf(node, phyIntf.intf()))) {
Jian Lib9eb11d2021-02-19 18:13:10 +0900557 log.warn("PhyBridge {}", hasPhyBridge(node, bridgeName));
558 log.warn("hasPhyPatchPort {}", hasPhyPatchPort(node, patchPortName));
559 log.warn("hasPhyIntf {}", hasPhyIntf(node, phyIntf.intf()));
Jian Li138f51f2021-01-06 03:29:58 +0900560 return false;
561 }
562 }
563
Daniel Parke7e3d6a2021-03-10 07:49:11 +0900564 if (node.type() == GATEWAY) {
565 if (!(hasPhyIntf(node, INTEGRATION_TO_TUNNEL) &&
566 hasPhyIntf(node, TUNNEL_TO_INTEGRATION))) {
567 log.warn("IntToTunPort {}", hasPhyIntf(node, INTEGRATION_TO_TUNNEL));
568 log.warn("TunToIntPort {}", hasPhyIntf(node, TUNNEL_TO_INTEGRATION));
569 return false;
570 }
571 }
Jian Li138f51f2021-01-06 03:29:58 +0900572 return true;
573 }
574
575 /**
576 * Configures the kubernetes node with new state.
577 *
578 * @param node kubevirt node
579 * @param newState a new state
580 */
581 private void setState(KubevirtNode node, KubevirtNodeState newState) {
582 if (node.state() == newState) {
583 return;
584 }
585 KubevirtNode updated = node.updateState(newState);
586 nodeAdminService.updateNode(updated);
587 log.info("Changed {} state: {}", node.hostname(), newState);
588 }
589
590 private void provisionPhysicalInterfaces(KubevirtNode node) {
591 node.phyIntfs().forEach(pi -> {
592 String bridgeName = BRIDGE_PREFIX + pi.network();
593 String patchPortName =
594 structurePortName(INTEGRATION_TO_PHYSICAL_PREFIX + pi.network());
595
596 if (!hasPhyBridge(node, bridgeName)) {
597 createPhysicalBridge(node, pi);
598 createPhysicalPatchPorts(node, pi);
599 attachPhysicalPort(node, pi);
Jian Lib9eb11d2021-02-19 18:13:10 +0900600
601 log.info("Creating physnet bridge {}", bridgeName);
602 log.info("Creating patch ports for physnet {}", bridgeName);
Jian Li138f51f2021-01-06 03:29:58 +0900603 } else {
604 // in case physical bridge exists, but patch port is missing on br-int,
605 // we will add patch port to connect br-int with physical bridge
606 if (!hasPhyPatchPort(node, patchPortName)) {
607 createPhysicalPatchPorts(node, pi);
Jian Lib9eb11d2021-02-19 18:13:10 +0900608
609 log.info("Creating patch ports for physnet {}", bridgeName);
Jian Li138f51f2021-01-06 03:29:58 +0900610 }
611 }
612 });
613 }
614
615 private void cleanPhysicalInterfaces(KubevirtNode node) {
616 Device device = deviceService.getDevice(node.ovsdb());
617
618 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
619
620 Set<String> bridgeNames = bridgeConfig.getBridges().stream()
621 .map(BridgeDescription::name).collect(Collectors.toSet());
622
623 Set<String> phyNetworkNames = node.phyIntfs().stream()
624 .map(pi -> BRIDGE_PREFIX + pi.network()).collect(Collectors.toSet());
625
626 // we remove existing physical bridges and patch ports, if the physical
627 // bridges are not defined in kubevirt node
628 for (String brName : bridgeNames) {
Jian Lib9eb11d2021-02-19 18:13:10 +0900629 // integration bridge and tunnel bridge should NOT be treated as
630 // physical bridges
631 if (brName.equals(INTEGRATION_BRIDGE) ||
632 brName.equals(TUNNEL_BRIDGE) ||
633 brName.startsWith(TENANT_BRIDGE_PREFIX)) {
634 continue;
635 }
636
Jian Li138f51f2021-01-06 03:29:58 +0900637 if (!phyNetworkNames.contains(brName)) {
Jian Li138f51f2021-01-06 03:29:58 +0900638 removePhysicalPatchPorts(node, brName.substring(NETWORK_BEGIN));
639 removePhysicalBridge(node, brName.substring(NETWORK_BEGIN));
Jian Lib9eb11d2021-02-19 18:13:10 +0900640 log.info("Removing physical bridge {}...", brName);
Jian Li138f51f2021-01-06 03:29:58 +0900641 }
642 }
643 }
644
Daniel Parke7e3d6a2021-03-10 07:49:11 +0900645
646 private void createPatchInterfaceBetweenBrIntBrTun(KubevirtNode node) {
647 Device device = deviceService.getDevice(node.ovsdb());
648
649 if (device == null || !device.is(InterfaceConfig.class)) {
650 log.error("Failed to create patch interface on {}", node.ovsdb());
651 return;
652 }
653
654 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
655
656 // int bridge -> tunnel bridge
657 PatchDescription brIntTunPatchDesc =
658 DefaultPatchDescription.builder()
659 .deviceId(INTEGRATION_BRIDGE)
660 .ifaceName(INTEGRATION_TO_TUNNEL)
661 .peer(TUNNEL_TO_INTEGRATION)
662 .build();
663
664 ifaceConfig.addPatchMode(INTEGRATION_TO_TUNNEL, brIntTunPatchDesc);
665
666 // tunnel bridge -> int bridge
667 PatchDescription brTunIntPatchDesc =
668 DefaultPatchDescription.builder()
669 .deviceId(TUNNEL_BRIDGE)
670 .ifaceName(TUNNEL_TO_INTEGRATION)
671 .peer(INTEGRATION_TO_TUNNEL)
672 .build();
673
674 ifaceConfig.addPatchMode(TUNNEL_TO_INTEGRATION, brTunIntPatchDesc);
675 }
676
Jian Li138f51f2021-01-06 03:29:58 +0900677 private void unprovisionPhysicalInterfaces(KubevirtNode node) {
678 node.phyIntfs().forEach(pi -> {
679 detachPhysicalPort(node, pi.network(), pi.intf());
680 removePhysicalPatchPorts(node, pi.network());
681 removePhysicalBridge(node, pi.network());
682 });
683 }
684
685 private boolean hasPhyBridge(KubevirtNode node, String bridgeName) {
686 BridgeConfig bridgeConfig =
687 deviceService.getDevice(node.ovsdb()).as(BridgeConfig.class);
688 return bridgeConfig.getBridges().stream()
689 .anyMatch(br -> br.name().equals(bridgeName));
690 }
691
692 private boolean hasPhyPatchPort(KubevirtNode node, String patchPortName) {
693 List<Port> ports = deviceService.getPorts(node.intgBridge());
694 return ports.stream().anyMatch(p ->
695 p.annotations().value(PORT_NAME).equals(patchPortName));
696 }
697
698 private boolean hasPhyIntf(KubevirtNode node, String intfName) {
699 BridgeConfig bridgeConfig =
700 deviceService.getDevice(node.ovsdb()).as(BridgeConfig.class);
701 return bridgeConfig.getPorts().stream()
702 .anyMatch(p -> p.annotations().value(PORT_NAME).equals(intfName));
703 }
704
705 private void createPhysicalBridge(KubevirtNode osNode,
706 KubevirtPhyInterface phyInterface) {
707 Device device = deviceService.getDevice(osNode.ovsdb());
708
709 String bridgeName = BRIDGE_PREFIX + phyInterface.network();
710
711 BridgeDescription.Builder builder = DefaultBridgeDescription.builder()
712 .name(bridgeName)
713 .mcastSnoopingEnable();
714
715 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
716 bridgeConfig.addBridge(builder.build());
717 }
718
719 private void removePhysicalBridge(KubevirtNode node, String network) {
720 Device device = deviceService.getDevice(node.ovsdb());
721
722 BridgeName bridgeName = BridgeName.bridgeName(BRIDGE_PREFIX + network);
723
724 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
725 bridgeConfig.deleteBridge(bridgeName);
726 }
727
728 private void createPhysicalPatchPorts(KubevirtNode node,
729 KubevirtPhyInterface phyInterface) {
730 Device device = deviceService.getDevice(node.ovsdb());
731
732 if (device == null || !device.is(InterfaceConfig.class)) {
733 log.error("Failed to create patch interface on {}", node.ovsdb());
734 return;
735 }
736
737 String physicalDeviceId = BRIDGE_PREFIX + phyInterface.network();
738
739 String intToPhyPatchPort = structurePortName(
740 INTEGRATION_TO_PHYSICAL_PREFIX + phyInterface.network());
741 String phyToIntPatchPort = structurePortName(
742 phyInterface.network() + PHYSICAL_TO_INTEGRATION_SUFFIX);
743
744 // integration bridge -> physical bridge
745 PatchDescription intToPhyPatchDesc =
746 DefaultPatchDescription.builder()
747 .deviceId(INTEGRATION_BRIDGE)
748 .ifaceName(intToPhyPatchPort)
749 .peer(phyToIntPatchPort)
750 .build();
751
752 // physical bridge -> integration bridge
753 PatchDescription phyToIntPatchDesc =
754 DefaultPatchDescription.builder()
755 .deviceId(physicalDeviceId)
756 .ifaceName(phyToIntPatchPort)
757 .peer(intToPhyPatchPort)
758 .build();
759
760 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
761 ifaceConfig.addPatchMode(INTEGRATION_TO_PHYSICAL_PREFIX +
762 phyInterface.network(), intToPhyPatchDesc);
763 ifaceConfig.addPatchMode(phyInterface.network() +
764 PHYSICAL_TO_INTEGRATION_SUFFIX, phyToIntPatchDesc);
765
766 addOrRemoveSystemInterface(node, physicalDeviceId,
767 phyInterface.intf(), deviceService, true);
768 }
769
770 private void removePhysicalPatchPorts(KubevirtNode node, String network) {
771 Device device = deviceService.getDevice(node.ovsdb());
772
773 if (device == null || !device.is(InterfaceConfig.class)) {
774 log.error("Failed to remove patch interface on {}", node.ovsdb());
775 return;
776 }
777
778 String intToPhyPatchPort = structurePortName(
779 INTEGRATION_TO_PHYSICAL_PREFIX + network);
780
781 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
782 ifaceConfig.removePatchMode(intToPhyPatchPort);
783 }
784
785 private void attachPhysicalPort(KubevirtNode node,
786 KubevirtPhyInterface phyInterface) {
787
788 String physicalDeviceId = BRIDGE_PREFIX + phyInterface.network();
789
790 addOrRemoveSystemInterface(node, physicalDeviceId,
791 phyInterface.intf(), deviceService, true);
792 }
793
794 private void detachPhysicalPort(KubevirtNode node, String network, String portName) {
795 String physicalDeviceId = BRIDGE_PREFIX + network;
796
797 addOrRemoveSystemInterface(node, physicalDeviceId, portName, deviceService, false);
798 }
799
800 /**
801 * An internal OVSDB listener. This listener is used for listening the
802 * network facing events from OVSDB device. If a new OVSDB device is detected,
803 * ONOS tries to bootstrap the kubernetes node.
804 */
805 private class InternalOvsdbListener implements DeviceListener {
806
807 @Override
808 public boolean isRelevant(DeviceEvent event) {
809 return event.subject().type() == Device.Type.CONTROLLER;
810 }
811
812 private boolean isRelevantHelper() {
813 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
814 }
815
816 @Override
817 public void event(DeviceEvent event) {
818 Device device = event.subject();
819
820 switch (event.type()) {
821 case DEVICE_AVAILABILITY_CHANGED:
822 case DEVICE_ADDED:
823 eventExecutor.execute(() -> {
824
825 if (!isRelevantHelper()) {
826 return;
827 }
828
829 KubevirtNode node = nodeAdminService.node(device.id());
830
831 if (node == null) {
832 return;
833 }
834
835 if (deviceService.isAvailable(device.id())) {
836 log.debug("OVSDB {} detected", device.id());
837 bootstrapNode(node);
838 }
839 });
840 break;
841 case PORT_ADDED:
842 case PORT_REMOVED:
843 case DEVICE_REMOVED:
844 default:
845 // do nothing
846 break;
847 }
848 }
849 }
850
851 /**
852 * An internal integration bridge listener. This listener is used for
853 * listening the events from integration bridge. To listen the events from
854 * other types of bridge such as provider bridge or tunnel bridge, we need
855 * to augment KubevirtNodeService.node() method.
856 */
857 private class InternalBridgeListener implements DeviceListener {
858
859 @Override
860 public boolean isRelevant(DeviceEvent event) {
861 return event.subject().type() == Device.Type.SWITCH;
862 }
863
864 private boolean isRelevantHelper() {
865 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
866 }
867
868 @Override
869 public void event(DeviceEvent event) {
870 Device device = event.subject();
871 Port port = event.port();
872
873 switch (event.type()) {
874 case DEVICE_AVAILABILITY_CHANGED:
875 case DEVICE_ADDED:
876 eventExecutor.execute(() -> processDeviceAddition(device));
877 break;
878 case PORT_UPDATED:
879 case PORT_ADDED:
880 eventExecutor.execute(() -> processPortAddition(device, port));
881 break;
882 case PORT_REMOVED:
883 eventExecutor.execute(() -> processPortRemoval(device, port));
884 break;
885 case DEVICE_REMOVED:
886 default:
887 // do nothing
888 break;
889 }
890 }
891
892 void processDeviceAddition(Device device) {
893 if (!isRelevantHelper()) {
894 return;
895 }
896
897 KubevirtNode node = nodeAdminService.node(device.id());
898
899 if (node == null) {
900 return;
901 }
902
903 if (deviceService.isAvailable(device.id())) {
904 log.debug("Bridge created on {}", node.hostname());
905 bootstrapNode(node);
906 } else if (node.state() == COMPLETE) {
907 log.info("Device {} disconnected", device.id());
908 setState(node, INCOMPLETE);
909 }
910
911 if (autoRecovery) {
912 if (node.state() == INCOMPLETE || node.state() == DEVICE_CREATED) {
913 log.info("Device {} is reconnected", device.id());
914 nodeAdminService.updateNode(node.updateState(INIT));
915 }
916 }
917 }
918
919 void processPortAddition(Device device, Port port) {
920 if (!isRelevantHelper()) {
921 return;
922 }
923
924 KubevirtNode node = nodeAdminService.nodeByTunBridge(device.id());
925
926 if (node == null) {
927 return;
928 }
929
930 String portName = port.annotations().value(PORT_NAME);
931 if (node.state() == DEVICE_CREATED && (
932 Objects.equals(portName, VXLAN) ||
933 Objects.equals(portName, GRE) ||
934 Objects.equals(portName, GENEVE))) {
935 log.info("Interface {} added or updated to {}",
936 portName, device.id());
937 bootstrapNode(node);
938 }
939 }
940
941 void processPortRemoval(Device device, Port port) {
942 if (!isRelevantHelper()) {
943 return;
944 }
945
946 KubevirtNode node = nodeAdminService.node(device.id());
947
948 if (node == null) {
949 return;
950 }
951
952 String portName = port.annotations().value(PORT_NAME);
953 if (node.state() == COMPLETE && (
954 Objects.equals(portName, VXLAN) ||
955 Objects.equals(portName, GRE) ||
956 Objects.equals(portName, GENEVE))) {
957 log.warn("Interface {} removed from {}", portName, device.id());
958 setState(node, INCOMPLETE);
959 }
960 }
961 }
962
963 /**
964 * An internal kubevirt node listener.
965 * The notification is triggered by KubevirtNodeStore.
966 */
967 private class InternalKubevirtNodeListener implements KubevirtNodeListener {
968
969 private boolean isRelevantHelper() {
970 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
971 }
972
973 @Override
974 public void event(KubevirtNodeEvent event) {
975 switch (event.type()) {
976 case KUBEVIRT_NODE_CREATED:
977 case KUBEVIRT_NODE_UPDATED:
978 eventExecutor.execute(() -> {
979 if (!isRelevantHelper()) {
980 return;
981 }
982 bootstrapNode(event.subject());
983 });
984 break;
985 case KUBEVIRT_NODE_REMOVED:
986 eventExecutor.execute(() -> {
987 if (!isRelevantHelper()) {
988 return;
989 }
990 removeNode(event.subject());
991 });
992 break;
993 case KUBEVIRT_NODE_INCOMPLETE:
994 default:
995 break;
996 }
997 }
998 }
999}