blob: 7449bc3b7183349838fcd994bf65e67e098c663a [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
Jian Lidc1df642020-11-25 16:49:34 +090089
Jian Lif16e8852019-01-22 22:55:31 +090090/**
91 * Service bootstraps kubernetes node based on its type.
92 */
93@Component(immediate = true,
94 property = {
95 OVSDB_PORT + ":Integer=" + OVSDB_PORT_NUM_DEFAULT,
96 AUTO_RECOVERY + ":Boolean=" + AUTO_RECOVERY_DEFAULT
97 }
98)
99public class DefaultK8sNodeHandler implements K8sNodeHandler {
100
101 private final Logger log = getLogger(getClass());
102
103 private static final String DEFAULT_OF_PROTO = "tcp";
104 private static final int DEFAULT_OFPORT = 6653;
105 private static final int DPID_BEGIN = 3;
Jian Li4604b7f2020-01-03 18:42:30 +0900106 private static final long SLEEP_MS = 3000; // we wait 3s
Jian Lif16e8852019-01-22 22:55:31 +0900107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY)
109 protected CoreService coreService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY)
112 protected LeadershipService leadershipService;
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY)
115 protected ClusterService clusterService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY)
118 protected DeviceService deviceService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY)
121 protected DeviceAdminService deviceAdminService;
122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY)
124 protected OvsdbController ovsdbController;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY)
127 protected K8sNodeService k8sNodeService;
128
129 @Reference(cardinality = ReferenceCardinality.MANDATORY)
130 protected K8sNodeAdminService k8sNodeAdminService;
131
132 @Reference(cardinality = ReferenceCardinality.MANDATORY)
133 protected ComponentConfigService componentConfigService;
134
135 /** OVSDB server listen port. */
136 private int ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
137
138 /** Indicates whether auto-recover kubernetes node status on switch re-conn event. */
139 private boolean autoRecovery = AUTO_RECOVERY_DEFAULT;
140
141 private final ExecutorService eventExecutor = newSingleThreadExecutor(
142 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
143
144 private final DeviceListener ovsdbListener = new InternalOvsdbListener();
145 private final DeviceListener bridgeListener = new InternalBridgeListener();
146 private final K8sNodeListener k8sNodeListener = new InternalK8sNodeListener();
147
148 private ApplicationId appId;
149 private NodeId localNode;
150
151 @Activate
152 protected void activate() {
153 appId = coreService.getAppId(APP_ID);
154 localNode = clusterService.getLocalNode().id();
155
156 componentConfigService.registerProperties(getClass());
157 leadershipService.runForLeadership(appId.name());
158 deviceService.addListener(ovsdbListener);
159 deviceService.addListener(bridgeListener);
160 k8sNodeService.addListener(k8sNodeListener);
161
162 log.info("Started");
163 }
164
165 @Deactivate
166 protected void deactivate() {
167 k8sNodeService.removeListener(k8sNodeListener);
168 deviceService.removeListener(bridgeListener);
169 deviceService.removeListener(ovsdbListener);
170 componentConfigService.unregisterProperties(getClass(), false);
171 leadershipService.withdraw(appId.name());
172 eventExecutor.shutdown();
173
174 log.info("Stopped");
175 }
176
177 @Modified
178 protected void modified(ComponentContext context) {
179 readComponentConfiguration(context);
180
181 log.info("Modified");
182 }
183
184 @Override
185 public void processInitState(K8sNode k8sNode) {
186 if (!isOvsdbConnected(k8sNode, ovsdbPortNum, ovsdbController, deviceService)) {
187 ovsdbController.connect(k8sNode.managementIp(), tpPort(ovsdbPortNum));
188 return;
189 }
190 if (!deviceService.isAvailable(k8sNode.intgBridge())) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900191 createBridge(k8sNode, k8sNode.intgBridgeName(), k8sNode.intgBridge());
Jian Lif16e8852019-01-22 22:55:31 +0900192 }
Jian Libf562c22019-04-15 18:07:14 +0900193 if (!deviceService.isAvailable(k8sNode.extBridge())) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900194 createBridge(k8sNode, k8sNode.extBridgeName(), k8sNode.extBridge());
Jian Libf562c22019-04-15 18:07:14 +0900195 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900196 if (!deviceService.isAvailable(k8sNode.localBridge())) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900197 createBridge(k8sNode, k8sNode.localBridgeName(), k8sNode.localBridge());
198 }
199
200 if (k8sNode.mode() == NORMAL) {
201 if (!deviceService.isAvailable(k8sNode.tunBridge())) {
202 createBridge(k8sNode, k8sNode.tunBridgeName(), k8sNode.tunBridge());
203 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900204 }
Jian Lif16e8852019-01-22 22:55:31 +0900205 }
206
207 @Override
208 public void processDeviceCreatedState(K8sNode k8sNode) {
209 try {
210 if (!isOvsdbConnected(k8sNode, ovsdbPortNum, ovsdbController, deviceService)) {
211 ovsdbController.connect(k8sNode.managementIp(), tpPort(ovsdbPortNum));
212 return;
213 }
214
Jian Libf562c22019-04-15 18:07:14 +0900215 // create patch ports between integration and external bridges
216 createPatchInterfaces(k8sNode);
217
Jian Lie2a04ce2020-07-01 19:07:02 +0900218 if (k8sNode.mode() == NORMAL) {
219 if (k8sNode.dataIp() != null &&
220 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
221 createVxlanTunnelInterface(k8sNode);
222 }
Jian Lif16e8852019-01-22 22:55:31 +0900223
Jian Lie2a04ce2020-07-01 19:07:02 +0900224 if (k8sNode.dataIp() != null &&
225 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
226 createGreTunnelInterface(k8sNode);
227 }
Jian Lif16e8852019-01-22 22:55:31 +0900228
Jian Lie2a04ce2020-07-01 19:07:02 +0900229 if (k8sNode.dataIp() != null &&
230 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
231 createGeneveTunnelInterface(k8sNode);
232 }
Jian Lif16e8852019-01-22 22:55:31 +0900233 }
234 } catch (Exception e) {
235 log.error("Exception occurred because of {}", e);
236 }
237 }
238
239 @Override
240 public void processCompleteState(K8sNode k8sNode) {
241 // do something if needed
242 }
243
244 @Override
245 public void processIncompleteState(K8sNode k8sNode) {
246 // do something if needed
247 }
248
Jian Li0ee8d0e2019-12-18 11:35:05 +0900249 @Override
250 public void processPreOnBoardState(K8sNode k8sNode) {
251 processInitState(k8sNode);
252 processDeviceCreatedState(k8sNode);
253 }
254
255 @Override
256 public void processOnBoardedState(K8sNode k8sNode) {
257 // do something if needed
258 }
259
Jian Li3b640af2020-01-02 23:57:13 +0900260 @Override
261 public void processPostOnBoardState(K8sNode k8sNode) {
262 // do something if needed
263 }
264
Jian Lif16e8852019-01-22 22:55:31 +0900265 /**
266 * Extracts properties from the component configuration context.
267 *
268 * @param context the component context
269 */
270 private void readComponentConfiguration(ComponentContext context) {
271 Dictionary<?, ?> properties = context.getProperties();
272
273 Integer ovsdbPortConfigured = Tools.getIntegerProperty(properties, OVSDB_PORT);
274 if (ovsdbPortConfigured == null) {
275 ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
276 log.info("OVSDB port is NOT configured, default value is {}", ovsdbPortNum);
277 } else {
278 ovsdbPortNum = ovsdbPortConfigured;
279 log.info("Configured. OVSDB port is {}", ovsdbPortNum);
280 }
281
282 Boolean autoRecoveryConfigured =
283 getBooleanProperty(properties, AUTO_RECOVERY);
284 if (autoRecoveryConfigured == null) {
285 autoRecovery = AUTO_RECOVERY_DEFAULT;
286 log.info("Auto recovery flag is NOT " +
287 "configured, default value is {}", autoRecovery);
288 } else {
289 autoRecovery = autoRecoveryConfigured;
290 log.info("Configured. Auto recovery flag is {}", autoRecovery);
291 }
292 }
293
294 /**
295 * Creates a bridge with a given name on a given kubernetes node.
296 *
297 * @param k8sNode kubernetes node
298 * @param bridgeName bridge name
299 * @param devId device identifier
300 */
301 private void createBridge(K8sNode k8sNode, String bridgeName, DeviceId devId) {
302 Device device = deviceService.getDevice(k8sNode.ovsdb());
303
304 List<ControllerInfo> controllers = clusterService.getNodes().stream()
305 .map(n -> new ControllerInfo(n.ip(), DEFAULT_OFPORT, DEFAULT_OF_PROTO))
306 .collect(Collectors.toList());
307
308 String dpid = devId.toString().substring(DPID_BEGIN);
309
310 BridgeDescription.Builder builder = DefaultBridgeDescription.builder()
311 .name(bridgeName)
312 .failMode(BridgeDescription.FailMode.SECURE)
313 .datapathId(dpid)
314 .disableInBand()
315 .controllers(controllers);
316
317 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
318 bridgeConfig.addBridge(builder.build());
319 }
320
321 /**
322 * Creates a VXLAN tunnel interface in a given kubernetes node.
323 *
324 * @param k8sNode kubernetes node
325 */
326 private void createVxlanTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900327 createTunnelInterface(k8sNode, VXLAN, k8sNode.vxlanPortName());
Jian Lif16e8852019-01-22 22:55:31 +0900328 }
329
330 /**
331 * Creates a GRE tunnel interface in a given kubernetes node.
332 *
333 * @param k8sNode kubernetes node
334 */
335 private void createGreTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900336 createTunnelInterface(k8sNode, GRE, k8sNode.grePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900337 }
338
339 /**
340 * Creates a GENEVE tunnel interface in a given kubernetes node.
341 *
342 * @param k8sNode kubernetes node
343 */
344 private void createGeneveTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900345 createTunnelInterface(k8sNode, GENEVE, k8sNode.genevePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900346 }
347
Jian Libf562c22019-04-15 18:07:14 +0900348 private void createPatchInterfaces(K8sNode k8sNode) {
349 Device device = deviceService.getDevice(k8sNode.ovsdb());
350 if (device == null || !device.is(InterfaceConfig.class)) {
351 log.error("Failed to create patch interface on {}", k8sNode.ovsdb());
352 return;
353 }
354
Jian Li1a2eb5d2019-08-27 02:07:05 +0900355 // integration bridge -> external bridge
356 PatchDescription brIntExtPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900357 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900358 .deviceId(k8sNode.intgBridgeName())
359 .ifaceName(k8sNode.intgToExtPatchPortName())
360 .peer(k8sNode.extToIntgPatchPortName())
361 .build();
Jian Lie2a04ce2020-07-01 19:07:02 +0900362
363 // integration bridge -> tunnel bridge
364 PatchDescription brIntTunPatchDesc =
365 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900366 .deviceId(k8sNode.intgBridgeName())
367 .ifaceName(k8sNode.intgToTunPatchPortName())
368 .peer(k8sNode.tunToIntgPatchPortName())
369 .build();
Jian Libf562c22019-04-15 18:07:14 +0900370
Jian Li1a2eb5d2019-08-27 02:07:05 +0900371 // external bridge -> integration bridge
372 PatchDescription brExtIntPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900373 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900374 .deviceId(k8sNode.extBridgeName())
375 .ifaceName(k8sNode.extToIntgPatchPortName())
376 .peer(k8sNode.intgToExtPatchPortName())
377 .build();
Jian Libf562c22019-04-15 18:07:14 +0900378
Jian Li1a2eb5d2019-08-27 02:07:05 +0900379 // integration bridge -> local bridge
380 PatchDescription brIntLocalPatchDesc =
381 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900382 .deviceId(k8sNode.intgBridgeName())
383 .ifaceName(k8sNode.intgToLocalPatchPortName())
384 .peer(k8sNode.localToIntgPatchPortName())
385 .build();
Jian Li1a2eb5d2019-08-27 02:07:05 +0900386
387 // local bridge -> integration bridge
388 PatchDescription brLocalIntPatchDesc =
389 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900390 .deviceId(k8sNode.localBridgeName())
391 .ifaceName(k8sNode.localToIntgPatchPortName())
392 .peer(k8sNode.intgToLocalPatchPortName())
393 .build();
Jian Li1a2eb5d2019-08-27 02:07:05 +0900394
Jian Libf562c22019-04-15 18:07:14 +0900395 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
Jian Lie2a04ce2020-07-01 19:07:02 +0900396 ifaceConfig.addPatchMode(k8sNode.intgToExtPatchPortName(), brIntExtPatchDesc);
397 ifaceConfig.addPatchMode(k8sNode.extToIntgPatchPortName(), brExtIntPatchDesc);
398 ifaceConfig.addPatchMode(k8sNode.intgToLocalPatchPortName(), brIntLocalPatchDesc);
399 ifaceConfig.addPatchMode(k8sNode.localToIntgPatchPortName(), brLocalIntPatchDesc);
400 ifaceConfig.addPatchMode(k8sNode.intgToTunPatchPortName(), brIntTunPatchDesc);
401
402 if (k8sNode.mode() == NORMAL) {
403 // tunnel bridge -> integration bridge
404 PatchDescription brTunIntPatchDesc =
405 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900406 .deviceId(k8sNode.tunBridgeName())
407 .ifaceName(k8sNode.tunToIntgPatchPortName())
408 .peer(k8sNode.intgToTunPatchPortName())
409 .build();
Jian Lie2a04ce2020-07-01 19:07:02 +0900410
411 ifaceConfig.addPatchMode(k8sNode.tunToIntgPatchPortName(), brTunIntPatchDesc);
Jian Li019ce6a2020-09-09 10:23:21 +0900412 } else {
413 // k8s integration bridge -> openstack integration bridge
414 PatchDescription k8sIntOsIntPatchDesc =
415 DefaultPatchDescription.builder()
416 .deviceId(k8sNode.intgBridgeName())
Jian Lia4d8fba2020-09-10 23:16:50 +0900417 .ifaceName(k8sNode.k8sIntgToOsPatchPortName())
Jian Li019ce6a2020-09-09 10:23:21 +0900418 .peer(k8sNode.osToK8sIntgPatchPortName())
419 .build();
Jian Lia4d8fba2020-09-10 23:16:50 +0900420 ifaceConfig.addPatchMode(k8sNode.k8sIntgToOsPatchPortName(), k8sIntOsIntPatchDesc);
421
422 // k8s external bridge -> openstack integration bridge
423 PatchDescription k8sExtOsIntPatchDesc =
424 DefaultPatchDescription.builder()
425 .deviceId(k8sNode.extBridgeName())
426 .ifaceName(k8sNode.k8sExtToOsPatchPortName())
427 .peer(k8sNode.osToK8sExtPatchPortName())
428 .build();
429 ifaceConfig.addPatchMode(k8sNode.k8sExtToOsPatchPortName(), k8sExtOsIntPatchDesc);
Jian Li019ce6a2020-09-09 10:23:21 +0900430
431 // external bridge -> router bridge
432 PatchDescription extRouterPatchDesc =
433 DefaultPatchDescription.builder()
434 .deviceId(k8sNode.extBridgeName())
435 .ifaceName(k8sNode.extToRouterPatchPortName())
436 .peer(k8sNode.routerToExtPatchPortName())
437 .build();
438 ifaceConfig.addPatchMode(k8sNode.extToRouterPatchPortName(), extRouterPatchDesc);
Jian Lie2a04ce2020-07-01 19:07:02 +0900439 }
Jian Libf562c22019-04-15 18:07:14 +0900440 }
441
Jian Lif16e8852019-01-22 22:55:31 +0900442 /**
443 * Creates a tunnel interface in a given kubernetes node.
444 *
445 * @param k8sNode kubernetes node
446 */
447 private void createTunnelInterface(K8sNode k8sNode,
448 String type, String intfName) {
449 if (isIntfEnabled(k8sNode, intfName)) {
450 return;
451 }
452
453 Device device = deviceService.getDevice(k8sNode.ovsdb());
454 if (device == null || !device.is(InterfaceConfig.class)) {
455 log.error("Failed to create tunnel interface on {}", k8sNode.ovsdb());
456 return;
457 }
458
Jian Lie2a04ce2020-07-01 19:07:02 +0900459 TunnelDescription tunnelDesc = buildTunnelDesc(k8sNode, type, intfName);
Jian Lif16e8852019-01-22 22:55:31 +0900460
461 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
462 ifaceConfig.addTunnelMode(intfName, tunnelDesc);
463 }
464
465 /**
466 * Builds tunnel description according to the network type.
467 *
468 * @param type network type
469 * @return tunnel description
470 */
Jian Lie2a04ce2020-07-01 19:07:02 +0900471 private TunnelDescription buildTunnelDesc(K8sNode k8sNode,
472 String type, String intfName) {
473 TunnelKey<String> key = new TunnelKey<>(k8sNode.tunnelKey());
Jian Lif16e8852019-01-22 22:55:31 +0900474 if (VXLAN.equals(type) || GRE.equals(type) || GENEVE.equals(type)) {
475 TunnelDescription.Builder tdBuilder =
476 DefaultTunnelDescription.builder()
Jian Lie2a04ce2020-07-01 19:07:02 +0900477 .deviceId(k8sNode.tunBridgeName())
Jian Lif16e8852019-01-22 22:55:31 +0900478 .ifaceName(intfName)
479 .remote(TunnelEndPoints.flowTunnelEndpoint())
Jian Lie2a04ce2020-07-01 19:07:02 +0900480 .key(key);
Jian Lif16e8852019-01-22 22:55:31 +0900481
482 switch (type) {
483 case VXLAN:
484 tdBuilder.type(TunnelDescription.Type.VXLAN);
485 break;
486 case GRE:
487 tdBuilder.type(TunnelDescription.Type.GRE);
488 break;
489 case GENEVE:
490 tdBuilder.type(TunnelDescription.Type.GENEVE);
491 break;
492 default:
493 return null;
494 }
495
496 return tdBuilder.build();
497 }
498 return null;
499 }
500
501 /**
502 * Checks whether a given network interface in a given kubernetes node
503 * is enabled or not.
504 *
505 * @param k8sNode kubernetes node
506 * @param intf network interface name
507 * @return true if the given interface is enabled, false otherwise
508 */
509 private boolean isIntfEnabled(K8sNode k8sNode, String intf) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900510 return deviceService.isAvailable(k8sNode.tunBridge()) &&
511 deviceService.getPorts(k8sNode.tunBridge()).stream()
Jian Lif16e8852019-01-22 22:55:31 +0900512 .anyMatch(port -> Objects.equals(
513 port.annotations().value(PORT_NAME), intf) &&
514 port.isEnabled());
515 }
516
517 /**
518 * Checks whether all requirements for this state are fulfilled or not.
519 *
520 * @param k8sNode kubernetes node
521 * @return true if all requirements are fulfilled, false otherwise
522 */
523 private boolean isCurrentStateDone(K8sNode k8sNode) {
524 switch (k8sNode.state()) {
525 case INIT:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900526 return isInitStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900527 case DEVICE_CREATED:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900528 return isDeviceCreatedStateDone(k8sNode);
529 case PRE_ON_BOARD:
530 return isInitStateDone(k8sNode) && isDeviceCreatedStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900531 case COMPLETE:
532 case INCOMPLETE:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900533 case ON_BOARDED:
Jian Li4604b7f2020-01-03 18:42:30 +0900534 case POST_ON_BOARD:
Jian Lif16e8852019-01-22 22:55:31 +0900535 // always return false
536 // run init CLI to re-trigger node bootstrap
537 return false;
538 default:
539 return true;
540 }
541 }
542
Jian Li0ee8d0e2019-12-18 11:35:05 +0900543 private boolean isInitStateDone(K8sNode k8sNode) {
544 if (!isOvsdbConnected(k8sNode, ovsdbPortNum,
545 ovsdbController, deviceService)) {
546 return false;
547 }
548
Jian Li4604b7f2020-01-03 18:42:30 +0900549 try {
550 // we need to wait a while, in case interface and bridge
551 // creation requires some time
552 sleep(SLEEP_MS);
553 } catch (InterruptedException e) {
554 log.error("Exception caused during init state checking...");
555 }
556
Jian Lie2a04ce2020-07-01 19:07:02 +0900557 boolean result = k8sNode.intgBridge() != null && k8sNode.extBridge() != null &&
Jian Li0ee8d0e2019-12-18 11:35:05 +0900558 deviceService.isAvailable(k8sNode.intgBridge()) &&
559 deviceService.isAvailable(k8sNode.extBridge()) &&
560 deviceService.isAvailable(k8sNode.localBridge());
Jian Lie2a04ce2020-07-01 19:07:02 +0900561
562 if (k8sNode.mode() == NORMAL) {
563 return result && deviceService.isAvailable(k8sNode.tunBridge());
564 } else {
565 return result;
566 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900567 }
568
569 private boolean isDeviceCreatedStateDone(K8sNode k8sNode) {
Jian Li4604b7f2020-01-03 18:42:30 +0900570
571 try {
572 // we need to wait a while, in case interface and bridge
573 // creation requires some time
574 sleep(SLEEP_MS);
575 } catch (InterruptedException e) {
576 log.error("Exception caused during init state checking...");
577 }
578
Jian Lie2a04ce2020-07-01 19:07:02 +0900579 if (k8sNode.mode() == NORMAL) {
580 if (k8sNode.dataIp() != null &&
581 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
582 return false;
583 }
584 if (k8sNode.dataIp() != null &&
585 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
586 return false;
587 }
588 if (k8sNode.dataIp() != null &&
589 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
590 return false;
591 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900592 }
593
594 return true;
595 }
596
Jian Lif16e8852019-01-22 22:55:31 +0900597 /**
598 * Configures the kubernetes node with new state.
599 *
600 * @param k8sNode kubernetes node
601 * @param newState a new state
602 */
603 private void setState(K8sNode k8sNode, K8sNodeState newState) {
604 if (k8sNode.state() == newState) {
605 return;
606 }
607 K8sNode updated = k8sNode.updateState(newState);
608 k8sNodeAdminService.updateNode(updated);
609 log.info("Changed {} state: {}", k8sNode.hostname(), newState);
610 }
611
612 /**
613 * Bootstraps a new kubernetes node.
614 *
615 * @param k8sNode kubernetes node
616 */
617 private void bootstrapNode(K8sNode k8sNode) {
618 if (isCurrentStateDone(k8sNode)) {
619 setState(k8sNode, k8sNode.state().nextState());
620 } else {
621 log.trace("Processing {} state for {}", k8sNode.state(),
622 k8sNode.hostname());
623 k8sNode.state().process(this, k8sNode);
624 }
625 }
626
627 private void processK8sNodeRemoved(K8sNode k8sNode) {
628 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
629 if (client == null) {
630 log.info("Failed to get ovsdb client");
631 return;
632 }
633
Jian Lidc1df642020-11-25 16:49:34 +0900634 if (k8sNode.mode() == NORMAL) {
635 // delete tunnel bridge from the node
636 client.dropBridge(k8sNode.tunBridgeName());
637 } else {
638 // remove the patch ports direct to the integration bridge from tunnel bridge
639 removeTunnelPatchPort(k8sNode);
640 // remove the patch ports direct to the external bridge from the router bridge
641 removeRouterPatchPort(k8sNode);
642 // remove the patch ports directs to the openstack's br-int bridge from the int and ext bridges
643 removeOpenstackPatchPorts(k8sNode);
644 }
645
Jian Lif16e8852019-01-22 22:55:31 +0900646 // delete integration bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900647 client.dropBridge(k8sNode.intgBridgeName());
Jian Lif16e8852019-01-22 22:55:31 +0900648
Jian Libf562c22019-04-15 18:07:14 +0900649 // delete external bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900650 client.dropBridge(k8sNode.extBridgeName());
Jian Libf562c22019-04-15 18:07:14 +0900651
Jian Li1a2eb5d2019-08-27 02:07:05 +0900652 // delete local bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900653 client.dropBridge(k8sNode.localBridgeName());
654
Jian Lidc1df642020-11-25 16:49:34 +0900655 // disconnect ovsdb
656 // client.disconnect();
657 }
658
659 private void removeTunnelPatchPort(K8sNode k8sNode) {
660 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
661 if (client == null) {
662 log.info("Failed to get ovsdb client");
663 return;
Jian Lie2a04ce2020-07-01 19:07:02 +0900664 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900665
Jian Lidc1df642020-11-25 16:49:34 +0900666 client.dropInterface(k8sNode.tunToIntgPatchPortName());
667 }
668
669 private void removeRouterPatchPort(K8sNode k8sNode) {
670 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
671 if (client == null) {
672 log.info("Failed to get ovsdb client");
673 return;
674 }
675
676 client.dropInterface(k8sNode.routerToExtPatchPortName());
677 }
678
679 private void removeOpenstackPatchPorts(K8sNode k8sNode) {
680 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
681 if (client == null) {
682 log.info("Failed to get ovsdb client");
683 return;
684 }
685
686 // remove patch port attached at br-int peers with the k8s integration bridge
687 client.dropInterface(k8sNode.osToK8sIntgPatchPortName());
688
689 // remove patch port attached at br-int peers with the k8s external bridge
690 client.dropInterface(k8sNode.osToK8sExtPatchPortName());
Jian Lif16e8852019-01-22 22:55:31 +0900691 }
692
693 /**
694 * An internal OVSDB listener. This listener is used for listening the
695 * network facing events from OVSDB device. If a new OVSDB device is detected,
696 * ONOS tries to bootstrap the kubernetes node.
697 */
698 private class InternalOvsdbListener implements DeviceListener {
699
700 @Override
701 public boolean isRelevant(DeviceEvent event) {
702 return event.subject().type() == Device.Type.CONTROLLER;
703 }
704
705 private boolean isRelevantHelper() {
706 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
707 }
708
709 @Override
710 public void event(DeviceEvent event) {
711 Device device = event.subject();
712
713 switch (event.type()) {
714 case DEVICE_AVAILABILITY_CHANGED:
715 case DEVICE_ADDED:
716 eventExecutor.execute(() -> {
717
718 if (!isRelevantHelper()) {
719 return;
720 }
721
722 K8sNode k8sNode = k8sNodeService.node(device.id());
723
724 if (k8sNode == null) {
725 return;
726 }
727
728 if (deviceService.isAvailable(device.id())) {
729 log.debug("OVSDB {} detected", device.id());
730 bootstrapNode(k8sNode);
731 }
732 });
733 break;
734 case PORT_ADDED:
735 case PORT_REMOVED:
736 case DEVICE_REMOVED:
737 default:
738 // do nothing
739 break;
740 }
741 }
742 }
743
744 /**
745 * An internal integration bridge listener. This listener is used for
746 * listening the events from integration bridge. To listen the events from
747 * other types of bridge such as provider bridge or tunnel bridge, we need
748 * to augment K8sNodeService.node() method.
749 */
750 private class InternalBridgeListener implements DeviceListener {
751
752 @Override
753 public boolean isRelevant(DeviceEvent event) {
754 return event.subject().type() == Device.Type.SWITCH;
755 }
756
757 private boolean isRelevantHelper() {
758 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
759 }
760
761 @Override
762 public void event(DeviceEvent event) {
763 Device device = event.subject();
764
765 switch (event.type()) {
766 case DEVICE_AVAILABILITY_CHANGED:
767 case DEVICE_ADDED:
768 eventExecutor.execute(() -> {
769
770 if (!isRelevantHelper()) {
771 return;
772 }
773
774 K8sNode k8sNode = k8sNodeService.node(device.id());
775
776 if (k8sNode == null) {
777 return;
778 }
779
Jian Libf562c22019-04-15 18:07:14 +0900780 // TODO: also need to check the external bridge's availability
Jian Li1a2eb5d2019-08-27 02:07:05 +0900781 // TODO: also need to check the local bridge's availability
Jian Lif16e8852019-01-22 22:55:31 +0900782 if (deviceService.isAvailable(device.id())) {
Jian Libf562c22019-04-15 18:07:14 +0900783 log.debug("Integration bridge created on {}",
784 k8sNode.hostname());
Jian Lif16e8852019-01-22 22:55:31 +0900785 bootstrapNode(k8sNode);
786 } else if (k8sNode.state() == COMPLETE) {
787 log.info("Device {} disconnected", device.id());
788 setState(k8sNode, INCOMPLETE);
789 }
790
791 if (autoRecovery) {
792 if (k8sNode.state() == INCOMPLETE ||
793 k8sNode.state() == DEVICE_CREATED) {
794 log.info("Device {} is reconnected", device.id());
795 k8sNodeAdminService.updateNode(
796 k8sNode.updateState(K8sNodeState.INIT));
797 }
798 }
799 });
800 break;
801 case PORT_UPDATED:
802 case PORT_ADDED:
803 eventExecutor.execute(() -> {
804
805 if (!isRelevantHelper()) {
806 return;
807 }
808
809 K8sNode k8sNode = k8sNodeService.node(device.id());
810
811 if (k8sNode == null) {
812 return;
813 }
814
815 Port port = event.port();
816 String portName = port.annotations().value(PORT_NAME);
817 if (k8sNode.state() == DEVICE_CREATED && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900818 Objects.equals(portName, k8sNode.vxlanPortName()) ||
819 Objects.equals(portName, k8sNode.grePortName()) ||
820 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900821 log.info("Interface {} added or updated to {}",
822 portName, device.id());
823 bootstrapNode(k8sNode);
824 }
825 });
826 break;
827 case PORT_REMOVED:
828 eventExecutor.execute(() -> {
829
830 if (!isRelevantHelper()) {
831 return;
832 }
833
834 K8sNode k8sNode = k8sNodeService.node(device.id());
835
836 if (k8sNode == null) {
837 return;
838 }
839
840 Port port = event.port();
841 String portName = port.annotations().value(PORT_NAME);
842 if (k8sNode.state() == COMPLETE && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900843 Objects.equals(portName, k8sNode.vxlanPortName()) ||
844 Objects.equals(portName, k8sNode.grePortName()) ||
845 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900846 log.warn("Interface {} removed from {}",
847 portName, event.subject().id());
848 setState(k8sNode, INCOMPLETE);
849 }
850 });
851 break;
852 case DEVICE_REMOVED:
853 default:
854 // do nothing
855 break;
856 }
857 }
858 }
859
860 /**
861 * An internal kubernetes node listener.
862 * The notification is triggered by KubernetesNodeStore.
863 */
864 private class InternalK8sNodeListener implements K8sNodeListener {
865
866 private boolean isRelevantHelper() {
867 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
868 }
869
870 @Override
871 public void event(K8sNodeEvent event) {
872 switch (event.type()) {
873 case K8S_NODE_CREATED:
874 case K8S_NODE_UPDATED:
875 eventExecutor.execute(() -> {
Jian Lif16e8852019-01-22 22:55:31 +0900876 if (!isRelevantHelper()) {
877 return;
878 }
Jian Lif16e8852019-01-22 22:55:31 +0900879 bootstrapNode(event.subject());
880 });
881 break;
882 case K8S_NODE_REMOVED:
883 eventExecutor.execute(() -> {
Jian Lif16e8852019-01-22 22:55:31 +0900884 if (!isRelevantHelper()) {
885 return;
886 }
Jian Lif16e8852019-01-22 22:55:31 +0900887 processK8sNodeRemoved(event.subject());
888 });
889 break;
890 case K8S_NODE_INCOMPLETE:
891 default:
892 break;
893 }
894 }
895 }
896}