blob: 46f56a6bcb902d3f3a8c674102299df24b53e318 [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);
Jian Li019ce6a2020-09-09 10:23:21 +0900411 } else {
412 // k8s integration bridge -> openstack integration bridge
413 PatchDescription k8sIntOsIntPatchDesc =
414 DefaultPatchDescription.builder()
415 .deviceId(k8sNode.intgBridgeName())
Jian Lia4d8fba2020-09-10 23:16:50 +0900416 .ifaceName(k8sNode.k8sIntgToOsPatchPortName())
Jian Li019ce6a2020-09-09 10:23:21 +0900417 .peer(k8sNode.osToK8sIntgPatchPortName())
418 .build();
Jian Lia4d8fba2020-09-10 23:16:50 +0900419 ifaceConfig.addPatchMode(k8sNode.k8sIntgToOsPatchPortName(), k8sIntOsIntPatchDesc);
420
421 // k8s external bridge -> openstack integration bridge
422 PatchDescription k8sExtOsIntPatchDesc =
423 DefaultPatchDescription.builder()
424 .deviceId(k8sNode.extBridgeName())
425 .ifaceName(k8sNode.k8sExtToOsPatchPortName())
426 .peer(k8sNode.osToK8sExtPatchPortName())
427 .build();
428 ifaceConfig.addPatchMode(k8sNode.k8sExtToOsPatchPortName(), k8sExtOsIntPatchDesc);
Jian Li019ce6a2020-09-09 10:23:21 +0900429
430 // external bridge -> router bridge
431 PatchDescription extRouterPatchDesc =
432 DefaultPatchDescription.builder()
433 .deviceId(k8sNode.extBridgeName())
434 .ifaceName(k8sNode.extToRouterPatchPortName())
435 .peer(k8sNode.routerToExtPatchPortName())
436 .build();
437 ifaceConfig.addPatchMode(k8sNode.extToRouterPatchPortName(), extRouterPatchDesc);
Jian Lie2a04ce2020-07-01 19:07:02 +0900438 }
Jian Libf562c22019-04-15 18:07:14 +0900439 }
440
Jian Lif16e8852019-01-22 22:55:31 +0900441 /**
442 * Creates a tunnel interface in a given kubernetes node.
443 *
444 * @param k8sNode kubernetes node
445 */
446 private void createTunnelInterface(K8sNode k8sNode,
447 String type, String intfName) {
448 if (isIntfEnabled(k8sNode, intfName)) {
449 return;
450 }
451
452 Device device = deviceService.getDevice(k8sNode.ovsdb());
453 if (device == null || !device.is(InterfaceConfig.class)) {
454 log.error("Failed to create tunnel interface on {}", k8sNode.ovsdb());
455 return;
456 }
457
Jian Lie2a04ce2020-07-01 19:07:02 +0900458 TunnelDescription tunnelDesc = buildTunnelDesc(k8sNode, type, intfName);
Jian Lif16e8852019-01-22 22:55:31 +0900459
460 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
461 ifaceConfig.addTunnelMode(intfName, tunnelDesc);
462 }
463
464 /**
465 * Builds tunnel description according to the network type.
466 *
467 * @param type network type
468 * @return tunnel description
469 */
Jian Lie2a04ce2020-07-01 19:07:02 +0900470 private TunnelDescription buildTunnelDesc(K8sNode k8sNode,
471 String type, String intfName) {
472 TunnelKey<String> key = new TunnelKey<>(k8sNode.tunnelKey());
Jian Lif16e8852019-01-22 22:55:31 +0900473 if (VXLAN.equals(type) || GRE.equals(type) || GENEVE.equals(type)) {
474 TunnelDescription.Builder tdBuilder =
475 DefaultTunnelDescription.builder()
Jian Lie2a04ce2020-07-01 19:07:02 +0900476 .deviceId(k8sNode.tunBridgeName())
Jian Lif16e8852019-01-22 22:55:31 +0900477 .ifaceName(intfName)
478 .remote(TunnelEndPoints.flowTunnelEndpoint())
Jian Lie2a04ce2020-07-01 19:07:02 +0900479 .key(key);
Jian Lif16e8852019-01-22 22:55:31 +0900480
481 switch (type) {
482 case VXLAN:
483 tdBuilder.type(TunnelDescription.Type.VXLAN);
484 break;
485 case GRE:
486 tdBuilder.type(TunnelDescription.Type.GRE);
487 break;
488 case GENEVE:
489 tdBuilder.type(TunnelDescription.Type.GENEVE);
490 break;
491 default:
492 return null;
493 }
494
495 return tdBuilder.build();
496 }
497 return null;
498 }
499
500 /**
501 * Checks whether a given network interface in a given kubernetes node
502 * is enabled or not.
503 *
504 * @param k8sNode kubernetes node
505 * @param intf network interface name
506 * @return true if the given interface is enabled, false otherwise
507 */
508 private boolean isIntfEnabled(K8sNode k8sNode, String intf) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900509 return deviceService.isAvailable(k8sNode.tunBridge()) &&
510 deviceService.getPorts(k8sNode.tunBridge()).stream()
Jian Lif16e8852019-01-22 22:55:31 +0900511 .anyMatch(port -> Objects.equals(
512 port.annotations().value(PORT_NAME), intf) &&
513 port.isEnabled());
514 }
515
516 /**
517 * Checks whether all requirements for this state are fulfilled or not.
518 *
519 * @param k8sNode kubernetes node
520 * @return true if all requirements are fulfilled, false otherwise
521 */
522 private boolean isCurrentStateDone(K8sNode k8sNode) {
523 switch (k8sNode.state()) {
524 case INIT:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900525 return isInitStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900526 case DEVICE_CREATED:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900527 return isDeviceCreatedStateDone(k8sNode);
528 case PRE_ON_BOARD:
529 return isInitStateDone(k8sNode) && isDeviceCreatedStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900530 case COMPLETE:
531 case INCOMPLETE:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900532 case ON_BOARDED:
Jian Li4604b7f2020-01-03 18:42:30 +0900533 case POST_ON_BOARD:
Jian Lif16e8852019-01-22 22:55:31 +0900534 // always return false
535 // run init CLI to re-trigger node bootstrap
536 return false;
537 default:
538 return true;
539 }
540 }
541
Jian Li0ee8d0e2019-12-18 11:35:05 +0900542 private boolean isInitStateDone(K8sNode k8sNode) {
543 if (!isOvsdbConnected(k8sNode, ovsdbPortNum,
544 ovsdbController, deviceService)) {
545 return false;
546 }
547
Jian Li4604b7f2020-01-03 18:42:30 +0900548 try {
549 // we need to wait a while, in case interface and bridge
550 // creation requires some time
551 sleep(SLEEP_MS);
552 } catch (InterruptedException e) {
553 log.error("Exception caused during init state checking...");
554 }
555
Jian Lie2a04ce2020-07-01 19:07:02 +0900556 boolean result = k8sNode.intgBridge() != null && k8sNode.extBridge() != null &&
Jian Li0ee8d0e2019-12-18 11:35:05 +0900557 deviceService.isAvailable(k8sNode.intgBridge()) &&
558 deviceService.isAvailable(k8sNode.extBridge()) &&
559 deviceService.isAvailable(k8sNode.localBridge());
Jian Lie2a04ce2020-07-01 19:07:02 +0900560
561 if (k8sNode.mode() == NORMAL) {
562 return result && deviceService.isAvailable(k8sNode.tunBridge());
563 } else {
564 return result;
565 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900566 }
567
568 private boolean isDeviceCreatedStateDone(K8sNode k8sNode) {
Jian Li4604b7f2020-01-03 18:42:30 +0900569
570 try {
571 // we need to wait a while, in case interface and bridge
572 // creation requires some time
573 sleep(SLEEP_MS);
574 } catch (InterruptedException e) {
575 log.error("Exception caused during init state checking...");
576 }
577
Jian Lie2a04ce2020-07-01 19:07:02 +0900578 if (k8sNode.mode() == NORMAL) {
579 if (k8sNode.dataIp() != null &&
580 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
581 return false;
582 }
583 if (k8sNode.dataIp() != null &&
584 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
585 return false;
586 }
587 if (k8sNode.dataIp() != null &&
588 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
589 return false;
590 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900591 }
592
593 return true;
594 }
595
Jian Lif16e8852019-01-22 22:55:31 +0900596 /**
597 * Configures the kubernetes node with new state.
598 *
599 * @param k8sNode kubernetes node
600 * @param newState a new state
601 */
602 private void setState(K8sNode k8sNode, K8sNodeState newState) {
603 if (k8sNode.state() == newState) {
604 return;
605 }
606 K8sNode updated = k8sNode.updateState(newState);
607 k8sNodeAdminService.updateNode(updated);
608 log.info("Changed {} state: {}", k8sNode.hostname(), newState);
609 }
610
611 /**
612 * Bootstraps a new kubernetes node.
613 *
614 * @param k8sNode kubernetes node
615 */
616 private void bootstrapNode(K8sNode k8sNode) {
617 if (isCurrentStateDone(k8sNode)) {
618 setState(k8sNode, k8sNode.state().nextState());
619 } else {
620 log.trace("Processing {} state for {}", k8sNode.state(),
621 k8sNode.hostname());
622 k8sNode.state().process(this, k8sNode);
623 }
624 }
625
626 private void processK8sNodeRemoved(K8sNode k8sNode) {
627 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
628 if (client == null) {
629 log.info("Failed to get ovsdb client");
630 return;
631 }
632
633 // delete integration bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900634 client.dropBridge(k8sNode.intgBridgeName());
Jian Lif16e8852019-01-22 22:55:31 +0900635
Jian Libf562c22019-04-15 18:07:14 +0900636 // delete external bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900637 client.dropBridge(k8sNode.extBridgeName());
Jian Libf562c22019-04-15 18:07:14 +0900638
Jian Li1a2eb5d2019-08-27 02:07:05 +0900639 // delete local bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900640 client.dropBridge(k8sNode.localBridgeName());
641
642 if (k8sNode.mode() == NORMAL) {
643 // delete tunnel bridge from the node
644 client.dropBridge(k8sNode.tunBridgeName());
645 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900646
Jian Lif16e8852019-01-22 22:55:31 +0900647 // disconnect ovsdb
648 client.disconnect();
649 }
650
651 /**
652 * An internal OVSDB listener. This listener is used for listening the
653 * network facing events from OVSDB device. If a new OVSDB device is detected,
654 * ONOS tries to bootstrap the kubernetes node.
655 */
656 private class InternalOvsdbListener implements DeviceListener {
657
658 @Override
659 public boolean isRelevant(DeviceEvent event) {
660 return event.subject().type() == Device.Type.CONTROLLER;
661 }
662
663 private boolean isRelevantHelper() {
664 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
665 }
666
667 @Override
668 public void event(DeviceEvent event) {
669 Device device = event.subject();
670
671 switch (event.type()) {
672 case DEVICE_AVAILABILITY_CHANGED:
673 case DEVICE_ADDED:
674 eventExecutor.execute(() -> {
675
676 if (!isRelevantHelper()) {
677 return;
678 }
679
680 K8sNode k8sNode = k8sNodeService.node(device.id());
681
682 if (k8sNode == null) {
683 return;
684 }
685
686 if (deviceService.isAvailable(device.id())) {
687 log.debug("OVSDB {} detected", device.id());
688 bootstrapNode(k8sNode);
689 }
690 });
691 break;
692 case PORT_ADDED:
693 case PORT_REMOVED:
694 case DEVICE_REMOVED:
695 default:
696 // do nothing
697 break;
698 }
699 }
700 }
701
702 /**
703 * An internal integration bridge listener. This listener is used for
704 * listening the events from integration bridge. To listen the events from
705 * other types of bridge such as provider bridge or tunnel bridge, we need
706 * to augment K8sNodeService.node() method.
707 */
708 private class InternalBridgeListener implements DeviceListener {
709
710 @Override
711 public boolean isRelevant(DeviceEvent event) {
712 return event.subject().type() == Device.Type.SWITCH;
713 }
714
715 private boolean isRelevantHelper() {
716 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
717 }
718
719 @Override
720 public void event(DeviceEvent event) {
721 Device device = event.subject();
722
723 switch (event.type()) {
724 case DEVICE_AVAILABILITY_CHANGED:
725 case DEVICE_ADDED:
726 eventExecutor.execute(() -> {
727
728 if (!isRelevantHelper()) {
729 return;
730 }
731
732 K8sNode k8sNode = k8sNodeService.node(device.id());
733
734 if (k8sNode == null) {
735 return;
736 }
737
Jian Libf562c22019-04-15 18:07:14 +0900738 // TODO: also need to check the external bridge's availability
Jian Li1a2eb5d2019-08-27 02:07:05 +0900739 // TODO: also need to check the local bridge's availability
Jian Lif16e8852019-01-22 22:55:31 +0900740 if (deviceService.isAvailable(device.id())) {
Jian Libf562c22019-04-15 18:07:14 +0900741 log.debug("Integration bridge created on {}",
742 k8sNode.hostname());
Jian Lif16e8852019-01-22 22:55:31 +0900743 bootstrapNode(k8sNode);
744 } else if (k8sNode.state() == COMPLETE) {
745 log.info("Device {} disconnected", device.id());
746 setState(k8sNode, INCOMPLETE);
747 }
748
749 if (autoRecovery) {
750 if (k8sNode.state() == INCOMPLETE ||
751 k8sNode.state() == DEVICE_CREATED) {
752 log.info("Device {} is reconnected", device.id());
753 k8sNodeAdminService.updateNode(
754 k8sNode.updateState(K8sNodeState.INIT));
755 }
756 }
757 });
758 break;
759 case PORT_UPDATED:
760 case PORT_ADDED:
761 eventExecutor.execute(() -> {
762
763 if (!isRelevantHelper()) {
764 return;
765 }
766
767 K8sNode k8sNode = k8sNodeService.node(device.id());
768
769 if (k8sNode == null) {
770 return;
771 }
772
773 Port port = event.port();
774 String portName = port.annotations().value(PORT_NAME);
775 if (k8sNode.state() == DEVICE_CREATED && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900776 Objects.equals(portName, k8sNode.vxlanPortName()) ||
777 Objects.equals(portName, k8sNode.grePortName()) ||
778 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900779 log.info("Interface {} added or updated to {}",
780 portName, device.id());
781 bootstrapNode(k8sNode);
782 }
783 });
784 break;
785 case PORT_REMOVED:
786 eventExecutor.execute(() -> {
787
788 if (!isRelevantHelper()) {
789 return;
790 }
791
792 K8sNode k8sNode = k8sNodeService.node(device.id());
793
794 if (k8sNode == null) {
795 return;
796 }
797
798 Port port = event.port();
799 String portName = port.annotations().value(PORT_NAME);
800 if (k8sNode.state() == COMPLETE && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900801 Objects.equals(portName, k8sNode.vxlanPortName()) ||
802 Objects.equals(portName, k8sNode.grePortName()) ||
803 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900804 log.warn("Interface {} removed from {}",
805 portName, event.subject().id());
806 setState(k8sNode, INCOMPLETE);
807 }
808 });
809 break;
810 case DEVICE_REMOVED:
811 default:
812 // do nothing
813 break;
814 }
815 }
816 }
817
818 /**
819 * An internal kubernetes node listener.
820 * The notification is triggered by KubernetesNodeStore.
821 */
822 private class InternalK8sNodeListener implements K8sNodeListener {
823
824 private boolean isRelevantHelper() {
825 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
826 }
827
828 @Override
829 public void event(K8sNodeEvent event) {
830 switch (event.type()) {
831 case K8S_NODE_CREATED:
832 case K8S_NODE_UPDATED:
833 eventExecutor.execute(() -> {
834
835 if (!isRelevantHelper()) {
836 return;
837 }
838
839 bootstrapNode(event.subject());
840 });
841 break;
842 case K8S_NODE_REMOVED:
843 eventExecutor.execute(() -> {
844
845 if (!isRelevantHelper()) {
846 return;
847 }
848
849 processK8sNodeRemoved(event.subject());
850 });
851 break;
852 case K8S_NODE_INCOMPLETE:
853 default:
854 break;
855 }
856 }
857 }
858}