blob: cec3d2cfc692f5e0128be3db2f79415a9b8ee017 [file] [log] [blame]
Jian Lif16e8852019-01-22 22:55:31 +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.k8snode.impl;
17
18import org.onlab.util.Tools;
19import org.onosproject.cfg.ComponentConfigService;
20import org.onosproject.cluster.ClusterService;
21import org.onosproject.cluster.LeadershipService;
22import org.onosproject.cluster.NodeId;
23import org.onosproject.core.ApplicationId;
24import org.onosproject.core.CoreService;
25import org.onosproject.k8snode.api.K8sNode;
26import org.onosproject.k8snode.api.K8sNodeAdminService;
27import org.onosproject.k8snode.api.K8sNodeEvent;
28import org.onosproject.k8snode.api.K8sNodeHandler;
29import org.onosproject.k8snode.api.K8sNodeListener;
30import org.onosproject.k8snode.api.K8sNodeService;
31import org.onosproject.k8snode.api.K8sNodeState;
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.BridgeDescription;
37import org.onosproject.net.behaviour.ControllerInfo;
38import org.onosproject.net.behaviour.DefaultBridgeDescription;
Jian Libf562c22019-04-15 18:07:14 +090039import org.onosproject.net.behaviour.DefaultPatchDescription;
Jian Lif16e8852019-01-22 22:55:31 +090040import org.onosproject.net.behaviour.DefaultTunnelDescription;
41import org.onosproject.net.behaviour.InterfaceConfig;
Jian Libf562c22019-04-15 18:07:14 +090042import org.onosproject.net.behaviour.PatchDescription;
Jian Lif16e8852019-01-22 22:55:31 +090043import org.onosproject.net.behaviour.TunnelDescription;
44import org.onosproject.net.behaviour.TunnelEndPoints;
Jian Lie2a04ce2020-07-01 19:07:02 +090045import org.onosproject.net.behaviour.TunnelKey;
Jian Lif16e8852019-01-22 22:55:31 +090046import org.onosproject.net.device.DeviceAdminService;
47import org.onosproject.net.device.DeviceEvent;
48import org.onosproject.net.device.DeviceListener;
49import org.onosproject.net.device.DeviceService;
50import org.onosproject.ovsdb.controller.OvsdbClientService;
51import org.onosproject.ovsdb.controller.OvsdbController;
52import org.osgi.service.component.ComponentContext;
53import org.osgi.service.component.annotations.Activate;
54import org.osgi.service.component.annotations.Component;
55import org.osgi.service.component.annotations.Deactivate;
56import org.osgi.service.component.annotations.Modified;
57import org.osgi.service.component.annotations.Reference;
58import org.osgi.service.component.annotations.ReferenceCardinality;
59import org.slf4j.Logger;
60
61import java.util.Dictionary;
62import java.util.List;
63import java.util.Objects;
64import java.util.concurrent.ExecutorService;
65import java.util.stream.Collectors;
66
Jian Li4604b7f2020-01-03 18:42:30 +090067import static java.lang.Thread.sleep;
Jian Lif16e8852019-01-22 22:55:31 +090068import static java.util.concurrent.Executors.newSingleThreadExecutor;
69import static org.onlab.packet.TpPort.tpPort;
70import static org.onlab.util.Tools.groupedThreads;
71import static org.onosproject.k8snode.api.Constants.GENEVE;
Jian Lif16e8852019-01-22 22:55:31 +090072import static org.onosproject.k8snode.api.Constants.GRE;
Jian Lif16e8852019-01-22 22:55:31 +090073import static org.onosproject.k8snode.api.Constants.VXLAN;
Jian Lie2a04ce2020-07-01 19:07:02 +090074import static org.onosproject.k8snode.api.K8sApiConfig.Mode.NORMAL;
Jian Lif16e8852019-01-22 22:55:31 +090075import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
76import static org.onosproject.k8snode.api.K8sNodeState.COMPLETE;
77import static org.onosproject.k8snode.api.K8sNodeState.DEVICE_CREATED;
78import static org.onosproject.k8snode.api.K8sNodeState.INCOMPLETE;
79import static org.onosproject.k8snode.impl.OsgiPropertyConstants.AUTO_RECOVERY;
80import static org.onosproject.k8snode.impl.OsgiPropertyConstants.AUTO_RECOVERY_DEFAULT;
81import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT;
82import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT_NUM_DEFAULT;
83import static org.onosproject.k8snode.util.K8sNodeUtil.getBooleanProperty;
84import static org.onosproject.k8snode.util.K8sNodeUtil.getOvsdbClient;
85import static org.onosproject.k8snode.util.K8sNodeUtil.isOvsdbConnected;
86import static org.onosproject.net.AnnotationKeys.PORT_NAME;
87import static org.slf4j.LoggerFactory.getLogger;
88
89/**
90 * Service bootstraps kubernetes node based on its type.
91 */
92@Component(immediate = true,
93 property = {
94 OVSDB_PORT + ":Integer=" + OVSDB_PORT_NUM_DEFAULT,
95 AUTO_RECOVERY + ":Boolean=" + AUTO_RECOVERY_DEFAULT
96 }
97)
98public class DefaultK8sNodeHandler implements K8sNodeHandler {
99
100 private final Logger log = getLogger(getClass());
101
102 private static final String DEFAULT_OF_PROTO = "tcp";
103 private static final int DEFAULT_OFPORT = 6653;
104 private static final int DPID_BEGIN = 3;
Jian Li4604b7f2020-01-03 18:42:30 +0900105 private static final long SLEEP_MS = 3000; // we wait 3s
Jian Lif16e8852019-01-22 22:55:31 +0900106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
108 protected CoreService coreService;
109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY)
111 protected LeadershipService leadershipService;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY)
114 protected ClusterService clusterService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY)
117 protected DeviceService deviceService;
118
119 @Reference(cardinality = ReferenceCardinality.MANDATORY)
120 protected DeviceAdminService deviceAdminService;
121
122 @Reference(cardinality = ReferenceCardinality.MANDATORY)
123 protected OvsdbController ovsdbController;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY)
126 protected K8sNodeService k8sNodeService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY)
129 protected K8sNodeAdminService k8sNodeAdminService;
130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY)
132 protected ComponentConfigService componentConfigService;
133
134 /** OVSDB server listen port. */
135 private int ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
136
137 /** Indicates whether auto-recover kubernetes node status on switch re-conn event. */
138 private boolean autoRecovery = AUTO_RECOVERY_DEFAULT;
139
140 private final ExecutorService eventExecutor = newSingleThreadExecutor(
141 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
142
143 private final DeviceListener ovsdbListener = new InternalOvsdbListener();
144 private final DeviceListener bridgeListener = new InternalBridgeListener();
145 private final K8sNodeListener k8sNodeListener = new InternalK8sNodeListener();
146
147 private ApplicationId appId;
148 private NodeId localNode;
149
150 @Activate
151 protected void activate() {
152 appId = coreService.getAppId(APP_ID);
153 localNode = clusterService.getLocalNode().id();
154
155 componentConfigService.registerProperties(getClass());
156 leadershipService.runForLeadership(appId.name());
157 deviceService.addListener(ovsdbListener);
158 deviceService.addListener(bridgeListener);
159 k8sNodeService.addListener(k8sNodeListener);
160
161 log.info("Started");
162 }
163
164 @Deactivate
165 protected void deactivate() {
166 k8sNodeService.removeListener(k8sNodeListener);
167 deviceService.removeListener(bridgeListener);
168 deviceService.removeListener(ovsdbListener);
169 componentConfigService.unregisterProperties(getClass(), false);
170 leadershipService.withdraw(appId.name());
171 eventExecutor.shutdown();
172
173 log.info("Stopped");
174 }
175
176 @Modified
177 protected void modified(ComponentContext context) {
178 readComponentConfiguration(context);
179
180 log.info("Modified");
181 }
182
183 @Override
184 public void processInitState(K8sNode k8sNode) {
185 if (!isOvsdbConnected(k8sNode, ovsdbPortNum, ovsdbController, deviceService)) {
186 ovsdbController.connect(k8sNode.managementIp(), tpPort(ovsdbPortNum));
187 return;
188 }
189 if (!deviceService.isAvailable(k8sNode.intgBridge())) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900190 createBridge(k8sNode, k8sNode.intgBridgeName(), k8sNode.intgBridge());
Jian Lif16e8852019-01-22 22:55:31 +0900191 }
Jian Libf562c22019-04-15 18:07:14 +0900192 if (!deviceService.isAvailable(k8sNode.extBridge())) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900193 createBridge(k8sNode, k8sNode.extBridgeName(), k8sNode.extBridge());
Jian Libf562c22019-04-15 18:07:14 +0900194 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900195 if (!deviceService.isAvailable(k8sNode.localBridge())) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900196 createBridge(k8sNode, k8sNode.localBridgeName(), k8sNode.localBridge());
197 }
198
199 if (k8sNode.mode() == NORMAL) {
200 if (!deviceService.isAvailable(k8sNode.tunBridge())) {
201 createBridge(k8sNode, k8sNode.tunBridgeName(), k8sNode.tunBridge());
202 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900203 }
Jian Lif16e8852019-01-22 22:55:31 +0900204 }
205
206 @Override
207 public void processDeviceCreatedState(K8sNode k8sNode) {
208 try {
209 if (!isOvsdbConnected(k8sNode, ovsdbPortNum, ovsdbController, deviceService)) {
210 ovsdbController.connect(k8sNode.managementIp(), tpPort(ovsdbPortNum));
211 return;
212 }
213
Jian Libf562c22019-04-15 18:07:14 +0900214 // create patch ports between integration and external bridges
215 createPatchInterfaces(k8sNode);
216
Jian Lie2a04ce2020-07-01 19:07:02 +0900217 if (k8sNode.mode() == NORMAL) {
218 if (k8sNode.dataIp() != null &&
219 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
220 createVxlanTunnelInterface(k8sNode);
221 }
Jian Lif16e8852019-01-22 22:55:31 +0900222
Jian Lie2a04ce2020-07-01 19:07:02 +0900223 if (k8sNode.dataIp() != null &&
224 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
225 createGreTunnelInterface(k8sNode);
226 }
Jian Lif16e8852019-01-22 22:55:31 +0900227
Jian Lie2a04ce2020-07-01 19:07:02 +0900228 if (k8sNode.dataIp() != null &&
229 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
230 createGeneveTunnelInterface(k8sNode);
231 }
Jian Lif16e8852019-01-22 22:55:31 +0900232 }
233 } catch (Exception e) {
234 log.error("Exception occurred because of {}", e);
235 }
236 }
237
238 @Override
239 public void processCompleteState(K8sNode k8sNode) {
240 // do something if needed
241 }
242
243 @Override
244 public void processIncompleteState(K8sNode k8sNode) {
245 // do something if needed
246 }
247
Jian Li0ee8d0e2019-12-18 11:35:05 +0900248 @Override
249 public void processPreOnBoardState(K8sNode k8sNode) {
250 processInitState(k8sNode);
251 processDeviceCreatedState(k8sNode);
252 }
253
254 @Override
255 public void processOnBoardedState(K8sNode k8sNode) {
256 // do something if needed
257 }
258
Jian Li3b640af2020-01-02 23:57:13 +0900259 @Override
260 public void processPostOnBoardState(K8sNode k8sNode) {
261 // do something if needed
262 }
263
Jian Lif16e8852019-01-22 22:55:31 +0900264 /**
265 * Extracts properties from the component configuration context.
266 *
267 * @param context the component context
268 */
269 private void readComponentConfiguration(ComponentContext context) {
270 Dictionary<?, ?> properties = context.getProperties();
271
272 Integer ovsdbPortConfigured = Tools.getIntegerProperty(properties, OVSDB_PORT);
273 if (ovsdbPortConfigured == null) {
274 ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
275 log.info("OVSDB port is NOT configured, default value is {}", ovsdbPortNum);
276 } else {
277 ovsdbPortNum = ovsdbPortConfigured;
278 log.info("Configured. OVSDB port is {}", ovsdbPortNum);
279 }
280
281 Boolean autoRecoveryConfigured =
282 getBooleanProperty(properties, AUTO_RECOVERY);
283 if (autoRecoveryConfigured == null) {
284 autoRecovery = AUTO_RECOVERY_DEFAULT;
285 log.info("Auto recovery flag is NOT " +
286 "configured, default value is {}", autoRecovery);
287 } else {
288 autoRecovery = autoRecoveryConfigured;
289 log.info("Configured. Auto recovery flag is {}", autoRecovery);
290 }
291 }
292
293 /**
294 * Creates a bridge with a given name on a given kubernetes node.
295 *
296 * @param k8sNode kubernetes node
297 * @param bridgeName bridge name
298 * @param devId device identifier
299 */
300 private void createBridge(K8sNode k8sNode, String bridgeName, DeviceId devId) {
301 Device device = deviceService.getDevice(k8sNode.ovsdb());
302
303 List<ControllerInfo> controllers = clusterService.getNodes().stream()
304 .map(n -> new ControllerInfo(n.ip(), DEFAULT_OFPORT, DEFAULT_OF_PROTO))
305 .collect(Collectors.toList());
306
307 String dpid = devId.toString().substring(DPID_BEGIN);
308
309 BridgeDescription.Builder builder = DefaultBridgeDescription.builder()
310 .name(bridgeName)
311 .failMode(BridgeDescription.FailMode.SECURE)
312 .datapathId(dpid)
313 .disableInBand()
314 .controllers(controllers);
315
316 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
317 bridgeConfig.addBridge(builder.build());
318 }
319
320 /**
321 * Creates a VXLAN tunnel interface in a given kubernetes node.
322 *
323 * @param k8sNode kubernetes node
324 */
325 private void createVxlanTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900326 createTunnelInterface(k8sNode, VXLAN, k8sNode.vxlanPortName());
Jian Lif16e8852019-01-22 22:55:31 +0900327 }
328
329 /**
330 * Creates a GRE tunnel interface in a given kubernetes node.
331 *
332 * @param k8sNode kubernetes node
333 */
334 private void createGreTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900335 createTunnelInterface(k8sNode, GRE, k8sNode.grePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900336 }
337
338 /**
339 * Creates a GENEVE tunnel interface in a given kubernetes node.
340 *
341 * @param k8sNode kubernetes node
342 */
343 private void createGeneveTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900344 createTunnelInterface(k8sNode, GENEVE, k8sNode.genevePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900345 }
346
Jian Libf562c22019-04-15 18:07:14 +0900347 private void createPatchInterfaces(K8sNode k8sNode) {
348 Device device = deviceService.getDevice(k8sNode.ovsdb());
349 if (device == null || !device.is(InterfaceConfig.class)) {
350 log.error("Failed to create patch interface on {}", k8sNode.ovsdb());
351 return;
352 }
353
Jian Li1a2eb5d2019-08-27 02:07:05 +0900354 // integration bridge -> external bridge
355 PatchDescription brIntExtPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900356 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900357 .deviceId(k8sNode.intgBridgeName())
358 .ifaceName(k8sNode.intgToExtPatchPortName())
359 .peer(k8sNode.extToIntgPatchPortName())
360 .build();
Jian Lie2a04ce2020-07-01 19:07:02 +0900361
362 // integration bridge -> tunnel bridge
363 PatchDescription brIntTunPatchDesc =
364 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900365 .deviceId(k8sNode.intgBridgeName())
366 .ifaceName(k8sNode.intgToTunPatchPortName())
367 .peer(k8sNode.tunToIntgPatchPortName())
368 .build();
Jian Libf562c22019-04-15 18:07:14 +0900369
Jian Li1a2eb5d2019-08-27 02:07:05 +0900370 // external bridge -> integration bridge
371 PatchDescription brExtIntPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900372 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900373 .deviceId(k8sNode.extBridgeName())
374 .ifaceName(k8sNode.extToIntgPatchPortName())
375 .peer(k8sNode.intgToExtPatchPortName())
376 .build();
Jian Libf562c22019-04-15 18:07:14 +0900377
Jian Li1a2eb5d2019-08-27 02:07:05 +0900378 // integration bridge -> local bridge
379 PatchDescription brIntLocalPatchDesc =
380 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900381 .deviceId(k8sNode.intgBridgeName())
382 .ifaceName(k8sNode.intgToLocalPatchPortName())
383 .peer(k8sNode.localToIntgPatchPortName())
384 .build();
Jian Li1a2eb5d2019-08-27 02:07:05 +0900385
386 // local bridge -> integration bridge
387 PatchDescription brLocalIntPatchDesc =
388 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900389 .deviceId(k8sNode.localBridgeName())
390 .ifaceName(k8sNode.localToIntgPatchPortName())
391 .peer(k8sNode.intgToLocalPatchPortName())
392 .build();
Jian Li1a2eb5d2019-08-27 02:07:05 +0900393
Jian Libf562c22019-04-15 18:07:14 +0900394 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
Jian Lie2a04ce2020-07-01 19:07:02 +0900395 ifaceConfig.addPatchMode(k8sNode.intgToExtPatchPortName(), brIntExtPatchDesc);
396 ifaceConfig.addPatchMode(k8sNode.extToIntgPatchPortName(), brExtIntPatchDesc);
397 ifaceConfig.addPatchMode(k8sNode.intgToLocalPatchPortName(), brIntLocalPatchDesc);
398 ifaceConfig.addPatchMode(k8sNode.localToIntgPatchPortName(), brLocalIntPatchDesc);
399 ifaceConfig.addPatchMode(k8sNode.intgToTunPatchPortName(), brIntTunPatchDesc);
400
401 if (k8sNode.mode() == NORMAL) {
402 // tunnel bridge -> integration bridge
403 PatchDescription brTunIntPatchDesc =
404 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900405 .deviceId(k8sNode.tunBridgeName())
406 .ifaceName(k8sNode.tunToIntgPatchPortName())
407 .peer(k8sNode.intgToTunPatchPortName())
408 .build();
Jian Lie2a04ce2020-07-01 19:07:02 +0900409
410 ifaceConfig.addPatchMode(k8sNode.tunToIntgPatchPortName(), brTunIntPatchDesc);
411 }
Jian Libf562c22019-04-15 18:07:14 +0900412 }
413
Jian Lif16e8852019-01-22 22:55:31 +0900414 /**
415 * Creates a tunnel interface in a given kubernetes node.
416 *
417 * @param k8sNode kubernetes node
418 */
419 private void createTunnelInterface(K8sNode k8sNode,
420 String type, String intfName) {
421 if (isIntfEnabled(k8sNode, intfName)) {
422 return;
423 }
424
425 Device device = deviceService.getDevice(k8sNode.ovsdb());
426 if (device == null || !device.is(InterfaceConfig.class)) {
427 log.error("Failed to create tunnel interface on {}", k8sNode.ovsdb());
428 return;
429 }
430
Jian Lie2a04ce2020-07-01 19:07:02 +0900431 TunnelDescription tunnelDesc = buildTunnelDesc(k8sNode, type, intfName);
Jian Lif16e8852019-01-22 22:55:31 +0900432
433 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
434 ifaceConfig.addTunnelMode(intfName, tunnelDesc);
435 }
436
437 /**
438 * Builds tunnel description according to the network type.
439 *
440 * @param type network type
441 * @return tunnel description
442 */
Jian Lie2a04ce2020-07-01 19:07:02 +0900443 private TunnelDescription buildTunnelDesc(K8sNode k8sNode,
444 String type, String intfName) {
445 TunnelKey<String> key = new TunnelKey<>(k8sNode.tunnelKey());
Jian Lif16e8852019-01-22 22:55:31 +0900446 if (VXLAN.equals(type) || GRE.equals(type) || GENEVE.equals(type)) {
447 TunnelDescription.Builder tdBuilder =
448 DefaultTunnelDescription.builder()
Jian Lie2a04ce2020-07-01 19:07:02 +0900449 .deviceId(k8sNode.tunBridgeName())
Jian Lif16e8852019-01-22 22:55:31 +0900450 .ifaceName(intfName)
451 .remote(TunnelEndPoints.flowTunnelEndpoint())
Jian Lie2a04ce2020-07-01 19:07:02 +0900452 .key(key);
Jian Lif16e8852019-01-22 22:55:31 +0900453
454 switch (type) {
455 case VXLAN:
456 tdBuilder.type(TunnelDescription.Type.VXLAN);
457 break;
458 case GRE:
459 tdBuilder.type(TunnelDescription.Type.GRE);
460 break;
461 case GENEVE:
462 tdBuilder.type(TunnelDescription.Type.GENEVE);
463 break;
464 default:
465 return null;
466 }
467
468 return tdBuilder.build();
469 }
470 return null;
471 }
472
473 /**
474 * Checks whether a given network interface in a given kubernetes node
475 * is enabled or not.
476 *
477 * @param k8sNode kubernetes node
478 * @param intf network interface name
479 * @return true if the given interface is enabled, false otherwise
480 */
481 private boolean isIntfEnabled(K8sNode k8sNode, String intf) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900482 return deviceService.isAvailable(k8sNode.tunBridge()) &&
483 deviceService.getPorts(k8sNode.tunBridge()).stream()
Jian Lif16e8852019-01-22 22:55:31 +0900484 .anyMatch(port -> Objects.equals(
485 port.annotations().value(PORT_NAME), intf) &&
486 port.isEnabled());
487 }
488
489 /**
490 * Checks whether all requirements for this state are fulfilled or not.
491 *
492 * @param k8sNode kubernetes node
493 * @return true if all requirements are fulfilled, false otherwise
494 */
495 private boolean isCurrentStateDone(K8sNode k8sNode) {
496 switch (k8sNode.state()) {
497 case INIT:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900498 return isInitStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900499 case DEVICE_CREATED:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900500 return isDeviceCreatedStateDone(k8sNode);
501 case PRE_ON_BOARD:
502 return isInitStateDone(k8sNode) && isDeviceCreatedStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900503 case COMPLETE:
504 case INCOMPLETE:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900505 case ON_BOARDED:
Jian Li4604b7f2020-01-03 18:42:30 +0900506 case POST_ON_BOARD:
Jian Lif16e8852019-01-22 22:55:31 +0900507 // always return false
508 // run init CLI to re-trigger node bootstrap
509 return false;
510 default:
511 return true;
512 }
513 }
514
Jian Li0ee8d0e2019-12-18 11:35:05 +0900515 private boolean isInitStateDone(K8sNode k8sNode) {
516 if (!isOvsdbConnected(k8sNode, ovsdbPortNum,
517 ovsdbController, deviceService)) {
518 return false;
519 }
520
Jian Li4604b7f2020-01-03 18:42:30 +0900521 try {
522 // we need to wait a while, in case interface and bridge
523 // creation requires some time
524 sleep(SLEEP_MS);
525 } catch (InterruptedException e) {
526 log.error("Exception caused during init state checking...");
527 }
528
Jian Lie2a04ce2020-07-01 19:07:02 +0900529 boolean result = k8sNode.intgBridge() != null && k8sNode.extBridge() != null &&
Jian Li0ee8d0e2019-12-18 11:35:05 +0900530 deviceService.isAvailable(k8sNode.intgBridge()) &&
531 deviceService.isAvailable(k8sNode.extBridge()) &&
532 deviceService.isAvailable(k8sNode.localBridge());
Jian Lie2a04ce2020-07-01 19:07:02 +0900533
534 if (k8sNode.mode() == NORMAL) {
535 return result && deviceService.isAvailable(k8sNode.tunBridge());
536 } else {
537 return result;
538 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900539 }
540
541 private boolean isDeviceCreatedStateDone(K8sNode k8sNode) {
Jian Li4604b7f2020-01-03 18:42:30 +0900542
543 try {
544 // we need to wait a while, in case interface and bridge
545 // creation requires some time
546 sleep(SLEEP_MS);
547 } catch (InterruptedException e) {
548 log.error("Exception caused during init state checking...");
549 }
550
Jian Lie2a04ce2020-07-01 19:07:02 +0900551 if (k8sNode.mode() == NORMAL) {
552 if (k8sNode.dataIp() != null &&
553 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
554 return false;
555 }
556 if (k8sNode.dataIp() != null &&
557 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
558 return false;
559 }
560 if (k8sNode.dataIp() != null &&
561 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
562 return false;
563 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900564 }
565
566 return true;
567 }
568
Jian Lif16e8852019-01-22 22:55:31 +0900569 /**
570 * Configures the kubernetes node with new state.
571 *
572 * @param k8sNode kubernetes node
573 * @param newState a new state
574 */
575 private void setState(K8sNode k8sNode, K8sNodeState newState) {
576 if (k8sNode.state() == newState) {
577 return;
578 }
579 K8sNode updated = k8sNode.updateState(newState);
580 k8sNodeAdminService.updateNode(updated);
581 log.info("Changed {} state: {}", k8sNode.hostname(), newState);
582 }
583
584 /**
585 * Bootstraps a new kubernetes node.
586 *
587 * @param k8sNode kubernetes node
588 */
589 private void bootstrapNode(K8sNode k8sNode) {
590 if (isCurrentStateDone(k8sNode)) {
591 setState(k8sNode, k8sNode.state().nextState());
592 } else {
593 log.trace("Processing {} state for {}", k8sNode.state(),
594 k8sNode.hostname());
595 k8sNode.state().process(this, k8sNode);
596 }
597 }
598
599 private void processK8sNodeRemoved(K8sNode k8sNode) {
600 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
601 if (client == null) {
602 log.info("Failed to get ovsdb client");
603 return;
604 }
605
606 // delete integration bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900607 client.dropBridge(k8sNode.intgBridgeName());
Jian Lif16e8852019-01-22 22:55:31 +0900608
Jian Libf562c22019-04-15 18:07:14 +0900609 // delete external bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900610 client.dropBridge(k8sNode.extBridgeName());
Jian Libf562c22019-04-15 18:07:14 +0900611
Jian Li1a2eb5d2019-08-27 02:07:05 +0900612 // delete local bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900613 client.dropBridge(k8sNode.localBridgeName());
614
615 if (k8sNode.mode() == NORMAL) {
616 // delete tunnel bridge from the node
617 client.dropBridge(k8sNode.tunBridgeName());
618 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900619
Jian Lif16e8852019-01-22 22:55:31 +0900620 // disconnect ovsdb
621 client.disconnect();
622 }
623
624 /**
625 * An internal OVSDB listener. This listener is used for listening the
626 * network facing events from OVSDB device. If a new OVSDB device is detected,
627 * ONOS tries to bootstrap the kubernetes node.
628 */
629 private class InternalOvsdbListener implements DeviceListener {
630
631 @Override
632 public boolean isRelevant(DeviceEvent event) {
633 return event.subject().type() == Device.Type.CONTROLLER;
634 }
635
636 private boolean isRelevantHelper() {
637 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
638 }
639
640 @Override
641 public void event(DeviceEvent event) {
642 Device device = event.subject();
643
644 switch (event.type()) {
645 case DEVICE_AVAILABILITY_CHANGED:
646 case DEVICE_ADDED:
647 eventExecutor.execute(() -> {
648
649 if (!isRelevantHelper()) {
650 return;
651 }
652
653 K8sNode k8sNode = k8sNodeService.node(device.id());
654
655 if (k8sNode == null) {
656 return;
657 }
658
659 if (deviceService.isAvailable(device.id())) {
660 log.debug("OVSDB {} detected", device.id());
661 bootstrapNode(k8sNode);
662 }
663 });
664 break;
665 case PORT_ADDED:
666 case PORT_REMOVED:
667 case DEVICE_REMOVED:
668 default:
669 // do nothing
670 break;
671 }
672 }
673 }
674
675 /**
676 * An internal integration bridge listener. This listener is used for
677 * listening the events from integration bridge. To listen the events from
678 * other types of bridge such as provider bridge or tunnel bridge, we need
679 * to augment K8sNodeService.node() method.
680 */
681 private class InternalBridgeListener implements DeviceListener {
682
683 @Override
684 public boolean isRelevant(DeviceEvent event) {
685 return event.subject().type() == Device.Type.SWITCH;
686 }
687
688 private boolean isRelevantHelper() {
689 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
690 }
691
692 @Override
693 public void event(DeviceEvent event) {
694 Device device = event.subject();
695
696 switch (event.type()) {
697 case DEVICE_AVAILABILITY_CHANGED:
698 case DEVICE_ADDED:
699 eventExecutor.execute(() -> {
700
701 if (!isRelevantHelper()) {
702 return;
703 }
704
705 K8sNode k8sNode = k8sNodeService.node(device.id());
706
707 if (k8sNode == null) {
708 return;
709 }
710
Jian Libf562c22019-04-15 18:07:14 +0900711 // TODO: also need to check the external bridge's availability
Jian Li1a2eb5d2019-08-27 02:07:05 +0900712 // TODO: also need to check the local bridge's availability
Jian Lif16e8852019-01-22 22:55:31 +0900713 if (deviceService.isAvailable(device.id())) {
Jian Libf562c22019-04-15 18:07:14 +0900714 log.debug("Integration bridge created on {}",
715 k8sNode.hostname());
Jian Lif16e8852019-01-22 22:55:31 +0900716 bootstrapNode(k8sNode);
717 } else if (k8sNode.state() == COMPLETE) {
718 log.info("Device {} disconnected", device.id());
719 setState(k8sNode, INCOMPLETE);
720 }
721
722 if (autoRecovery) {
723 if (k8sNode.state() == INCOMPLETE ||
724 k8sNode.state() == DEVICE_CREATED) {
725 log.info("Device {} is reconnected", device.id());
726 k8sNodeAdminService.updateNode(
727 k8sNode.updateState(K8sNodeState.INIT));
728 }
729 }
730 });
731 break;
732 case PORT_UPDATED:
733 case PORT_ADDED:
734 eventExecutor.execute(() -> {
735
736 if (!isRelevantHelper()) {
737 return;
738 }
739
740 K8sNode k8sNode = k8sNodeService.node(device.id());
741
742 if (k8sNode == null) {
743 return;
744 }
745
746 Port port = event.port();
747 String portName = port.annotations().value(PORT_NAME);
748 if (k8sNode.state() == DEVICE_CREATED && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900749 Objects.equals(portName, k8sNode.vxlanPortName()) ||
750 Objects.equals(portName, k8sNode.grePortName()) ||
751 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900752 log.info("Interface {} added or updated to {}",
753 portName, device.id());
754 bootstrapNode(k8sNode);
755 }
756 });
757 break;
758 case PORT_REMOVED:
759 eventExecutor.execute(() -> {
760
761 if (!isRelevantHelper()) {
762 return;
763 }
764
765 K8sNode k8sNode = k8sNodeService.node(device.id());
766
767 if (k8sNode == null) {
768 return;
769 }
770
771 Port port = event.port();
772 String portName = port.annotations().value(PORT_NAME);
773 if (k8sNode.state() == COMPLETE && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900774 Objects.equals(portName, k8sNode.vxlanPortName()) ||
775 Objects.equals(portName, k8sNode.grePortName()) ||
776 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900777 log.warn("Interface {} removed from {}",
778 portName, event.subject().id());
779 setState(k8sNode, INCOMPLETE);
780 }
781 });
782 break;
783 case DEVICE_REMOVED:
784 default:
785 // do nothing
786 break;
787 }
788 }
789 }
790
791 /**
792 * An internal kubernetes node listener.
793 * The notification is triggered by KubernetesNodeStore.
794 */
795 private class InternalK8sNodeListener implements K8sNodeListener {
796
797 private boolean isRelevantHelper() {
798 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
799 }
800
801 @Override
802 public void event(K8sNodeEvent event) {
803 switch (event.type()) {
804 case K8S_NODE_CREATED:
805 case K8S_NODE_UPDATED:
806 eventExecutor.execute(() -> {
807
808 if (!isRelevantHelper()) {
809 return;
810 }
811
812 bootstrapNode(event.subject());
813 });
814 break;
815 case K8S_NODE_REMOVED:
816 eventExecutor.execute(() -> {
817
818 if (!isRelevantHelper()) {
819 return;
820 }
821
822 processK8sNodeRemoved(event.subject());
823 });
824 break;
825 case K8S_NODE_INCOMPLETE:
826 default:
827 break;
828 }
829 }
830 }
831}