blob: 03b80bea131d26229fa85a53ebfa35af9fc5792b [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;
Jian Liacf88ae2021-08-26 22:04:08 +090025import org.onosproject.k8snode.api.K8sApiConfigService;
Jian Lif16e8852019-01-22 22:55:31 +090026import org.onosproject.k8snode.api.K8sNode;
27import org.onosproject.k8snode.api.K8sNodeAdminService;
28import org.onosproject.k8snode.api.K8sNodeEvent;
29import org.onosproject.k8snode.api.K8sNodeHandler;
30import org.onosproject.k8snode.api.K8sNodeListener;
31import org.onosproject.k8snode.api.K8sNodeService;
32import org.onosproject.k8snode.api.K8sNodeState;
33import org.onosproject.net.Device;
34import org.onosproject.net.DeviceId;
35import org.onosproject.net.Port;
36import org.onosproject.net.behaviour.BridgeConfig;
37import org.onosproject.net.behaviour.BridgeDescription;
38import org.onosproject.net.behaviour.ControllerInfo;
39import org.onosproject.net.behaviour.DefaultBridgeDescription;
Jian Libf562c22019-04-15 18:07:14 +090040import org.onosproject.net.behaviour.DefaultPatchDescription;
Jian Lif16e8852019-01-22 22:55:31 +090041import org.onosproject.net.behaviour.DefaultTunnelDescription;
42import org.onosproject.net.behaviour.InterfaceConfig;
Jian Libf562c22019-04-15 18:07:14 +090043import org.onosproject.net.behaviour.PatchDescription;
Jian Lif16e8852019-01-22 22:55:31 +090044import org.onosproject.net.behaviour.TunnelDescription;
45import org.onosproject.net.behaviour.TunnelEndPoints;
Jian Li58b33982020-07-01 19:07:02 +090046import org.onosproject.net.behaviour.TunnelKey;
Jian Lif16e8852019-01-22 22:55:31 +090047import org.onosproject.net.device.DeviceAdminService;
48import org.onosproject.net.device.DeviceEvent;
49import org.onosproject.net.device.DeviceListener;
50import org.onosproject.net.device.DeviceService;
51import org.onosproject.ovsdb.controller.OvsdbClientService;
52import org.onosproject.ovsdb.controller.OvsdbController;
53import org.osgi.service.component.ComponentContext;
54import org.osgi.service.component.annotations.Activate;
55import org.osgi.service.component.annotations.Component;
56import org.osgi.service.component.annotations.Deactivate;
57import org.osgi.service.component.annotations.Modified;
58import org.osgi.service.component.annotations.Reference;
59import org.osgi.service.component.annotations.ReferenceCardinality;
60import org.slf4j.Logger;
61
62import java.util.Dictionary;
63import java.util.List;
64import java.util.Objects;
65import java.util.concurrent.ExecutorService;
66import java.util.stream.Collectors;
67
Jian Lie560f672020-01-03 18:42:30 +090068import static java.lang.Thread.sleep;
Jian Lif16e8852019-01-22 22:55:31 +090069import static java.util.concurrent.Executors.newSingleThreadExecutor;
70import static org.onlab.packet.TpPort.tpPort;
71import static org.onlab.util.Tools.groupedThreads;
72import static org.onosproject.k8snode.api.Constants.GENEVE;
Jian Lif16e8852019-01-22 22:55:31 +090073import static org.onosproject.k8snode.api.Constants.GRE;
Jian Lif16e8852019-01-22 22:55:31 +090074import static org.onosproject.k8snode.api.Constants.VXLAN;
Jian Li58b33982020-07-01 19:07:02 +090075import static org.onosproject.k8snode.api.K8sApiConfig.Mode.NORMAL;
Jian Lif16e8852019-01-22 22:55:31 +090076import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
77import static org.onosproject.k8snode.api.K8sNodeState.COMPLETE;
78import static org.onosproject.k8snode.api.K8sNodeState.DEVICE_CREATED;
79import static org.onosproject.k8snode.api.K8sNodeState.INCOMPLETE;
80import static org.onosproject.k8snode.impl.OsgiPropertyConstants.AUTO_RECOVERY;
81import static org.onosproject.k8snode.impl.OsgiPropertyConstants.AUTO_RECOVERY_DEFAULT;
82import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT;
83import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT_NUM_DEFAULT;
84import static org.onosproject.k8snode.util.K8sNodeUtil.getBooleanProperty;
85import static org.onosproject.k8snode.util.K8sNodeUtil.getOvsdbClient;
86import static org.onosproject.k8snode.util.K8sNodeUtil.isOvsdbConnected;
87import static org.onosproject.net.AnnotationKeys.PORT_NAME;
88import static org.slf4j.LoggerFactory.getLogger;
89
Jian Li8fa74cfa2020-11-25 16:49:34 +090090
Jian Lif16e8852019-01-22 22:55:31 +090091/**
92 * Service bootstraps kubernetes node based on its type.
93 */
94@Component(immediate = true,
95 property = {
96 OVSDB_PORT + ":Integer=" + OVSDB_PORT_NUM_DEFAULT,
97 AUTO_RECOVERY + ":Boolean=" + AUTO_RECOVERY_DEFAULT
98 }
99)
100public class DefaultK8sNodeHandler implements K8sNodeHandler {
101
102 private final Logger log = getLogger(getClass());
103
104 private static final String DEFAULT_OF_PROTO = "tcp";
105 private static final int DEFAULT_OFPORT = 6653;
106 private static final int DPID_BEGIN = 3;
Jian Lie560f672020-01-03 18:42:30 +0900107 private static final long SLEEP_MS = 3000; // we wait 3s
Jian Lif16e8852019-01-22 22:55:31 +0900108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY)
110 protected CoreService coreService;
111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY)
113 protected LeadershipService leadershipService;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY)
116 protected ClusterService clusterService;
117
118 @Reference(cardinality = ReferenceCardinality.MANDATORY)
119 protected DeviceService deviceService;
120
121 @Reference(cardinality = ReferenceCardinality.MANDATORY)
122 protected DeviceAdminService deviceAdminService;
123
124 @Reference(cardinality = ReferenceCardinality.MANDATORY)
125 protected OvsdbController ovsdbController;
126
127 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liacf88ae2021-08-26 22:04:08 +0900128 protected K8sApiConfigService k8sApiConfigService;
129
130 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lif16e8852019-01-22 22:55:31 +0900131 protected K8sNodeService k8sNodeService;
132
133 @Reference(cardinality = ReferenceCardinality.MANDATORY)
134 protected K8sNodeAdminService k8sNodeAdminService;
135
136 @Reference(cardinality = ReferenceCardinality.MANDATORY)
137 protected ComponentConfigService componentConfigService;
138
139 /** OVSDB server listen port. */
140 private int ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
141
142 /** Indicates whether auto-recover kubernetes node status on switch re-conn event. */
143 private boolean autoRecovery = AUTO_RECOVERY_DEFAULT;
144
145 private final ExecutorService eventExecutor = newSingleThreadExecutor(
146 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
147
148 private final DeviceListener ovsdbListener = new InternalOvsdbListener();
149 private final DeviceListener bridgeListener = new InternalBridgeListener();
150 private final K8sNodeListener k8sNodeListener = new InternalK8sNodeListener();
151
152 private ApplicationId appId;
153 private NodeId localNode;
154
155 @Activate
156 protected void activate() {
157 appId = coreService.getAppId(APP_ID);
158 localNode = clusterService.getLocalNode().id();
159
160 componentConfigService.registerProperties(getClass());
161 leadershipService.runForLeadership(appId.name());
162 deviceService.addListener(ovsdbListener);
163 deviceService.addListener(bridgeListener);
164 k8sNodeService.addListener(k8sNodeListener);
165
166 log.info("Started");
167 }
168
169 @Deactivate
170 protected void deactivate() {
171 k8sNodeService.removeListener(k8sNodeListener);
172 deviceService.removeListener(bridgeListener);
173 deviceService.removeListener(ovsdbListener);
174 componentConfigService.unregisterProperties(getClass(), false);
175 leadershipService.withdraw(appId.name());
176 eventExecutor.shutdown();
177
178 log.info("Stopped");
179 }
180
181 @Modified
182 protected void modified(ComponentContext context) {
183 readComponentConfiguration(context);
184
185 log.info("Modified");
186 }
187
188 @Override
189 public void processInitState(K8sNode k8sNode) {
190 if (!isOvsdbConnected(k8sNode, ovsdbPortNum, ovsdbController, deviceService)) {
191 ovsdbController.connect(k8sNode.managementIp(), tpPort(ovsdbPortNum));
192 return;
193 }
194 if (!deviceService.isAvailable(k8sNode.intgBridge())) {
Jian Li58b33982020-07-01 19:07:02 +0900195 createBridge(k8sNode, k8sNode.intgBridgeName(), k8sNode.intgBridge());
Jian Lif16e8852019-01-22 22:55:31 +0900196 }
Jian Libf562c22019-04-15 18:07:14 +0900197 if (!deviceService.isAvailable(k8sNode.extBridge())) {
Jian Li58b33982020-07-01 19:07:02 +0900198 createBridge(k8sNode, k8sNode.extBridgeName(), k8sNode.extBridge());
Jian Libf562c22019-04-15 18:07:14 +0900199 }
Jian Li121ddfe2019-08-27 02:07:05 +0900200 if (!deviceService.isAvailable(k8sNode.localBridge())) {
Jian Li58b33982020-07-01 19:07:02 +0900201 createBridge(k8sNode, k8sNode.localBridgeName(), k8sNode.localBridge());
202 }
203
204 if (k8sNode.mode() == NORMAL) {
205 if (!deviceService.isAvailable(k8sNode.tunBridge())) {
206 createBridge(k8sNode, k8sNode.tunBridgeName(), k8sNode.tunBridge());
207 }
Jian Li121ddfe2019-08-27 02:07:05 +0900208 }
Jian Lif16e8852019-01-22 22:55:31 +0900209 }
210
211 @Override
212 public void processDeviceCreatedState(K8sNode k8sNode) {
213 try {
214 if (!isOvsdbConnected(k8sNode, ovsdbPortNum, ovsdbController, deviceService)) {
215 ovsdbController.connect(k8sNode.managementIp(), tpPort(ovsdbPortNum));
216 return;
217 }
218
Jian Libf562c22019-04-15 18:07:14 +0900219 // create patch ports between integration and external bridges
220 createPatchInterfaces(k8sNode);
221
Jian Li58b33982020-07-01 19:07:02 +0900222 if (k8sNode.mode() == NORMAL) {
223 if (k8sNode.dataIp() != null &&
224 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
225 createVxlanTunnelInterface(k8sNode);
226 }
Jian Lif16e8852019-01-22 22:55:31 +0900227
Jian Li58b33982020-07-01 19:07:02 +0900228 if (k8sNode.dataIp() != null &&
229 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
230 createGreTunnelInterface(k8sNode);
231 }
Jian Lif16e8852019-01-22 22:55:31 +0900232
Jian Li58b33982020-07-01 19:07:02 +0900233 if (k8sNode.dataIp() != null &&
234 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
235 createGeneveTunnelInterface(k8sNode);
236 }
Jian Lif16e8852019-01-22 22:55:31 +0900237 }
238 } catch (Exception e) {
239 log.error("Exception occurred because of {}", e);
240 }
241 }
242
243 @Override
244 public void processCompleteState(K8sNode k8sNode) {
245 // do something if needed
246 }
247
248 @Override
249 public void processIncompleteState(K8sNode k8sNode) {
250 // do something if needed
251 }
252
Jian Li77af8f32019-12-18 11:35:05 +0900253 @Override
254 public void processPreOnBoardState(K8sNode k8sNode) {
255 processInitState(k8sNode);
256 processDeviceCreatedState(k8sNode);
257 }
258
259 @Override
260 public void processOnBoardedState(K8sNode k8sNode) {
261 // do something if needed
262 }
263
Jian Lid376e062020-01-02 23:57:13 +0900264 @Override
265 public void processPostOnBoardState(K8sNode k8sNode) {
266 // do something if needed
267 }
268
Jian Lif16e8852019-01-22 22:55:31 +0900269 /**
270 * Extracts properties from the component configuration context.
271 *
272 * @param context the component context
273 */
274 private void readComponentConfiguration(ComponentContext context) {
275 Dictionary<?, ?> properties = context.getProperties();
276
277 Integer ovsdbPortConfigured = Tools.getIntegerProperty(properties, OVSDB_PORT);
278 if (ovsdbPortConfigured == null) {
279 ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
280 log.info("OVSDB port is NOT configured, default value is {}", ovsdbPortNum);
281 } else {
282 ovsdbPortNum = ovsdbPortConfigured;
283 log.info("Configured. OVSDB port is {}", ovsdbPortNum);
284 }
285
286 Boolean autoRecoveryConfigured =
287 getBooleanProperty(properties, AUTO_RECOVERY);
288 if (autoRecoveryConfigured == null) {
289 autoRecovery = AUTO_RECOVERY_DEFAULT;
290 log.info("Auto recovery flag is NOT " +
291 "configured, default value is {}", autoRecovery);
292 } else {
293 autoRecovery = autoRecoveryConfigured;
294 log.info("Configured. Auto recovery flag is {}", autoRecovery);
295 }
296 }
297
298 /**
299 * Creates a bridge with a given name on a given kubernetes node.
300 *
301 * @param k8sNode kubernetes node
302 * @param bridgeName bridge name
303 * @param devId device identifier
304 */
305 private void createBridge(K8sNode k8sNode, String bridgeName, DeviceId devId) {
306 Device device = deviceService.getDevice(k8sNode.ovsdb());
307
Jian Liacf88ae2021-08-26 22:04:08 +0900308 List<ControllerInfo> controllers = k8sApiConfigService.apiConfigs().stream()
309 .map(c -> new ControllerInfo(c.ipAddress(), DEFAULT_OFPORT, DEFAULT_OF_PROTO))
Jian Lif16e8852019-01-22 22:55:31 +0900310 .collect(Collectors.toList());
311
312 String dpid = devId.toString().substring(DPID_BEGIN);
313
314 BridgeDescription.Builder builder = DefaultBridgeDescription.builder()
315 .name(bridgeName)
316 .failMode(BridgeDescription.FailMode.SECURE)
317 .datapathId(dpid)
318 .disableInBand()
319 .controllers(controllers);
320
321 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
322 bridgeConfig.addBridge(builder.build());
323 }
324
325 /**
326 * Creates a VXLAN tunnel interface in a given kubernetes node.
327 *
328 * @param k8sNode kubernetes node
329 */
330 private void createVxlanTunnelInterface(K8sNode k8sNode) {
Jian Li58b33982020-07-01 19:07:02 +0900331 createTunnelInterface(k8sNode, VXLAN, k8sNode.vxlanPortName());
Jian Lif16e8852019-01-22 22:55:31 +0900332 }
333
334 /**
335 * Creates a GRE tunnel interface in a given kubernetes node.
336 *
337 * @param k8sNode kubernetes node
338 */
339 private void createGreTunnelInterface(K8sNode k8sNode) {
Jian Li58b33982020-07-01 19:07:02 +0900340 createTunnelInterface(k8sNode, GRE, k8sNode.grePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900341 }
342
343 /**
344 * Creates a GENEVE tunnel interface in a given kubernetes node.
345 *
346 * @param k8sNode kubernetes node
347 */
348 private void createGeneveTunnelInterface(K8sNode k8sNode) {
Jian Li58b33982020-07-01 19:07:02 +0900349 createTunnelInterface(k8sNode, GENEVE, k8sNode.genevePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900350 }
351
Jian Libf562c22019-04-15 18:07:14 +0900352 private void createPatchInterfaces(K8sNode k8sNode) {
353 Device device = deviceService.getDevice(k8sNode.ovsdb());
354 if (device == null || !device.is(InterfaceConfig.class)) {
355 log.error("Failed to create patch interface on {}", k8sNode.ovsdb());
356 return;
357 }
358
Jian Li121ddfe2019-08-27 02:07:05 +0900359 // integration bridge -> external bridge
360 PatchDescription brIntExtPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900361 DefaultPatchDescription.builder()
Jian Li3cb86e32020-09-07 17:01:11 +0900362 .deviceId(k8sNode.intgBridgeName())
363 .ifaceName(k8sNode.intgToExtPatchPortName())
364 .peer(k8sNode.extToIntgPatchPortName())
365 .build();
Jian Li58b33982020-07-01 19:07:02 +0900366
367 // integration bridge -> tunnel bridge
368 PatchDescription brIntTunPatchDesc =
369 DefaultPatchDescription.builder()
Jian Li3cb86e32020-09-07 17:01:11 +0900370 .deviceId(k8sNode.intgBridgeName())
371 .ifaceName(k8sNode.intgToTunPatchPortName())
372 .peer(k8sNode.tunToIntgPatchPortName())
373 .build();
Jian Libf562c22019-04-15 18:07:14 +0900374
Jian Li121ddfe2019-08-27 02:07:05 +0900375 // external bridge -> integration bridge
376 PatchDescription brExtIntPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900377 DefaultPatchDescription.builder()
Jian Li3cb86e32020-09-07 17:01:11 +0900378 .deviceId(k8sNode.extBridgeName())
379 .ifaceName(k8sNode.extToIntgPatchPortName())
380 .peer(k8sNode.intgToExtPatchPortName())
381 .build();
Jian Libf562c22019-04-15 18:07:14 +0900382
Jian Li121ddfe2019-08-27 02:07:05 +0900383 // integration bridge -> local bridge
384 PatchDescription brIntLocalPatchDesc =
385 DefaultPatchDescription.builder()
Jian Li3cb86e32020-09-07 17:01:11 +0900386 .deviceId(k8sNode.intgBridgeName())
387 .ifaceName(k8sNode.intgToLocalPatchPortName())
388 .peer(k8sNode.localToIntgPatchPortName())
389 .build();
Jian Li121ddfe2019-08-27 02:07:05 +0900390
391 // local bridge -> integration bridge
392 PatchDescription brLocalIntPatchDesc =
393 DefaultPatchDescription.builder()
Jian Li3cb86e32020-09-07 17:01:11 +0900394 .deviceId(k8sNode.localBridgeName())
395 .ifaceName(k8sNode.localToIntgPatchPortName())
396 .peer(k8sNode.intgToLocalPatchPortName())
397 .build();
Jian Li121ddfe2019-08-27 02:07:05 +0900398
Jian Libf562c22019-04-15 18:07:14 +0900399 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
Jian Li58b33982020-07-01 19:07:02 +0900400 ifaceConfig.addPatchMode(k8sNode.intgToExtPatchPortName(), brIntExtPatchDesc);
401 ifaceConfig.addPatchMode(k8sNode.extToIntgPatchPortName(), brExtIntPatchDesc);
402 ifaceConfig.addPatchMode(k8sNode.intgToLocalPatchPortName(), brIntLocalPatchDesc);
403 ifaceConfig.addPatchMode(k8sNode.localToIntgPatchPortName(), brLocalIntPatchDesc);
404 ifaceConfig.addPatchMode(k8sNode.intgToTunPatchPortName(), brIntTunPatchDesc);
405
406 if (k8sNode.mode() == NORMAL) {
407 // tunnel bridge -> integration bridge
408 PatchDescription brTunIntPatchDesc =
409 DefaultPatchDescription.builder()
Jian Li3cb86e32020-09-07 17:01:11 +0900410 .deviceId(k8sNode.tunBridgeName())
411 .ifaceName(k8sNode.tunToIntgPatchPortName())
412 .peer(k8sNode.intgToTunPatchPortName())
413 .build();
Jian Li58b33982020-07-01 19:07:02 +0900414
415 ifaceConfig.addPatchMode(k8sNode.tunToIntgPatchPortName(), brTunIntPatchDesc);
Jian Li4860e372020-09-09 10:23:21 +0900416 } else {
417 // k8s integration bridge -> openstack integration bridge
418 PatchDescription k8sIntOsIntPatchDesc =
419 DefaultPatchDescription.builder()
420 .deviceId(k8sNode.intgBridgeName())
Jian Li25210732020-09-10 23:16:50 +0900421 .ifaceName(k8sNode.k8sIntgToOsPatchPortName())
Jian Li4860e372020-09-09 10:23:21 +0900422 .peer(k8sNode.osToK8sIntgPatchPortName())
423 .build();
Jian Li25210732020-09-10 23:16:50 +0900424 ifaceConfig.addPatchMode(k8sNode.k8sIntgToOsPatchPortName(), k8sIntOsIntPatchDesc);
425
426 // k8s external bridge -> openstack integration bridge
427 PatchDescription k8sExtOsIntPatchDesc =
428 DefaultPatchDescription.builder()
429 .deviceId(k8sNode.extBridgeName())
430 .ifaceName(k8sNode.k8sExtToOsPatchPortName())
431 .peer(k8sNode.osToK8sExtPatchPortName())
432 .build();
433 ifaceConfig.addPatchMode(k8sNode.k8sExtToOsPatchPortName(), k8sExtOsIntPatchDesc);
Jian Li4860e372020-09-09 10:23:21 +0900434
435 // external bridge -> router bridge
436 PatchDescription extRouterPatchDesc =
437 DefaultPatchDescription.builder()
438 .deviceId(k8sNode.extBridgeName())
439 .ifaceName(k8sNode.extToRouterPatchPortName())
440 .peer(k8sNode.routerToExtPatchPortName())
441 .build();
442 ifaceConfig.addPatchMode(k8sNode.extToRouterPatchPortName(), extRouterPatchDesc);
Jian Li58b33982020-07-01 19:07:02 +0900443 }
Jian Libf562c22019-04-15 18:07:14 +0900444 }
445
Jian Lif16e8852019-01-22 22:55:31 +0900446 /**
447 * Creates a tunnel interface in a given kubernetes node.
448 *
449 * @param k8sNode kubernetes node
450 */
451 private void createTunnelInterface(K8sNode k8sNode,
452 String type, String intfName) {
453 if (isIntfEnabled(k8sNode, intfName)) {
454 return;
455 }
456
457 Device device = deviceService.getDevice(k8sNode.ovsdb());
458 if (device == null || !device.is(InterfaceConfig.class)) {
459 log.error("Failed to create tunnel interface on {}", k8sNode.ovsdb());
460 return;
461 }
462
Jian Li58b33982020-07-01 19:07:02 +0900463 TunnelDescription tunnelDesc = buildTunnelDesc(k8sNode, type, intfName);
Jian Lif16e8852019-01-22 22:55:31 +0900464
465 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
466 ifaceConfig.addTunnelMode(intfName, tunnelDesc);
467 }
468
469 /**
470 * Builds tunnel description according to the network type.
471 *
472 * @param type network type
473 * @return tunnel description
474 */
Jian Li58b33982020-07-01 19:07:02 +0900475 private TunnelDescription buildTunnelDesc(K8sNode k8sNode,
476 String type, String intfName) {
477 TunnelKey<String> key = new TunnelKey<>(k8sNode.tunnelKey());
Jian Lif16e8852019-01-22 22:55:31 +0900478 if (VXLAN.equals(type) || GRE.equals(type) || GENEVE.equals(type)) {
479 TunnelDescription.Builder tdBuilder =
480 DefaultTunnelDescription.builder()
Jian Li58b33982020-07-01 19:07:02 +0900481 .deviceId(k8sNode.tunBridgeName())
Jian Lif16e8852019-01-22 22:55:31 +0900482 .ifaceName(intfName)
483 .remote(TunnelEndPoints.flowTunnelEndpoint())
Jian Li58b33982020-07-01 19:07:02 +0900484 .key(key);
Jian Lif16e8852019-01-22 22:55:31 +0900485
486 switch (type) {
487 case VXLAN:
488 tdBuilder.type(TunnelDescription.Type.VXLAN);
489 break;
490 case GRE:
491 tdBuilder.type(TunnelDescription.Type.GRE);
492 break;
493 case GENEVE:
494 tdBuilder.type(TunnelDescription.Type.GENEVE);
495 break;
496 default:
497 return null;
498 }
499
500 return tdBuilder.build();
501 }
502 return null;
503 }
504
505 /**
506 * Checks whether a given network interface in a given kubernetes node
507 * is enabled or not.
508 *
509 * @param k8sNode kubernetes node
510 * @param intf network interface name
511 * @return true if the given interface is enabled, false otherwise
512 */
513 private boolean isIntfEnabled(K8sNode k8sNode, String intf) {
Jian Li58b33982020-07-01 19:07:02 +0900514 return deviceService.isAvailable(k8sNode.tunBridge()) &&
515 deviceService.getPorts(k8sNode.tunBridge()).stream()
Jian Lif16e8852019-01-22 22:55:31 +0900516 .anyMatch(port -> Objects.equals(
517 port.annotations().value(PORT_NAME), intf) &&
518 port.isEnabled());
519 }
520
521 /**
522 * Checks whether all requirements for this state are fulfilled or not.
523 *
524 * @param k8sNode kubernetes node
525 * @return true if all requirements are fulfilled, false otherwise
526 */
527 private boolean isCurrentStateDone(K8sNode k8sNode) {
528 switch (k8sNode.state()) {
529 case INIT:
Jian Li77af8f32019-12-18 11:35:05 +0900530 return isInitStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900531 case DEVICE_CREATED:
Jian Li77af8f32019-12-18 11:35:05 +0900532 return isDeviceCreatedStateDone(k8sNode);
533 case PRE_ON_BOARD:
534 return isInitStateDone(k8sNode) && isDeviceCreatedStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900535 case COMPLETE:
536 case INCOMPLETE:
Jian Li77af8f32019-12-18 11:35:05 +0900537 case ON_BOARDED:
Jian Lie560f672020-01-03 18:42:30 +0900538 case POST_ON_BOARD:
Jian Lif16e8852019-01-22 22:55:31 +0900539 // always return false
540 // run init CLI to re-trigger node bootstrap
541 return false;
542 default:
543 return true;
544 }
545 }
546
Jian Li77af8f32019-12-18 11:35:05 +0900547 private boolean isInitStateDone(K8sNode k8sNode) {
548 if (!isOvsdbConnected(k8sNode, ovsdbPortNum,
549 ovsdbController, deviceService)) {
550 return false;
551 }
552
Jian Lie560f672020-01-03 18:42:30 +0900553 try {
554 // we need to wait a while, in case interface and bridge
555 // creation requires some time
556 sleep(SLEEP_MS);
557 } catch (InterruptedException e) {
558 log.error("Exception caused during init state checking...");
559 }
560
Jian Li58b33982020-07-01 19:07:02 +0900561 boolean result = k8sNode.intgBridge() != null && k8sNode.extBridge() != null &&
Jian Li77af8f32019-12-18 11:35:05 +0900562 deviceService.isAvailable(k8sNode.intgBridge()) &&
563 deviceService.isAvailable(k8sNode.extBridge()) &&
564 deviceService.isAvailable(k8sNode.localBridge());
Jian Li58b33982020-07-01 19:07:02 +0900565
566 if (k8sNode.mode() == NORMAL) {
567 return result && deviceService.isAvailable(k8sNode.tunBridge());
568 } else {
569 return result;
570 }
Jian Li77af8f32019-12-18 11:35:05 +0900571 }
572
573 private boolean isDeviceCreatedStateDone(K8sNode k8sNode) {
Jian Lie560f672020-01-03 18:42:30 +0900574
575 try {
576 // we need to wait a while, in case interface and bridge
577 // creation requires some time
578 sleep(SLEEP_MS);
579 } catch (InterruptedException e) {
580 log.error("Exception caused during init state checking...");
581 }
582
Jian Li58b33982020-07-01 19:07:02 +0900583 if (k8sNode.mode() == NORMAL) {
584 if (k8sNode.dataIp() != null &&
585 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
586 return false;
587 }
588 if (k8sNode.dataIp() != null &&
589 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
590 return false;
591 }
592 if (k8sNode.dataIp() != null &&
593 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
594 return false;
595 }
Jian Li77af8f32019-12-18 11:35:05 +0900596 }
597
598 return true;
599 }
600
Jian Lif16e8852019-01-22 22:55:31 +0900601 /**
602 * Configures the kubernetes node with new state.
603 *
604 * @param k8sNode kubernetes node
605 * @param newState a new state
606 */
607 private void setState(K8sNode k8sNode, K8sNodeState newState) {
608 if (k8sNode.state() == newState) {
609 return;
610 }
611 K8sNode updated = k8sNode.updateState(newState);
612 k8sNodeAdminService.updateNode(updated);
613 log.info("Changed {} state: {}", k8sNode.hostname(), newState);
614 }
615
616 /**
617 * Bootstraps a new kubernetes node.
618 *
619 * @param k8sNode kubernetes node
620 */
621 private void bootstrapNode(K8sNode k8sNode) {
622 if (isCurrentStateDone(k8sNode)) {
623 setState(k8sNode, k8sNode.state().nextState());
624 } else {
625 log.trace("Processing {} state for {}", k8sNode.state(),
626 k8sNode.hostname());
627 k8sNode.state().process(this, k8sNode);
628 }
629 }
630
631 private void processK8sNodeRemoved(K8sNode k8sNode) {
632 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
633 if (client == null) {
634 log.info("Failed to get ovsdb client");
635 return;
636 }
637
Jian Li8fa74cfa2020-11-25 16:49:34 +0900638 if (k8sNode.mode() == NORMAL) {
639 // delete tunnel bridge from the node
640 client.dropBridge(k8sNode.tunBridgeName());
641 } else {
642 // remove the patch ports direct to the integration bridge from tunnel bridge
643 removeTunnelPatchPort(k8sNode);
644 // remove the patch ports direct to the external bridge from the router bridge
645 removeRouterPatchPort(k8sNode);
646 // remove the patch ports directs to the openstack's br-int bridge from the int and ext bridges
647 removeOpenstackPatchPorts(k8sNode);
648 }
649
Jian Lif16e8852019-01-22 22:55:31 +0900650 // delete integration bridge from the node
Jian Li58b33982020-07-01 19:07:02 +0900651 client.dropBridge(k8sNode.intgBridgeName());
Jian Lif16e8852019-01-22 22:55:31 +0900652
Jian Libf562c22019-04-15 18:07:14 +0900653 // delete external bridge from the node
Jian Li58b33982020-07-01 19:07:02 +0900654 client.dropBridge(k8sNode.extBridgeName());
Jian Libf562c22019-04-15 18:07:14 +0900655
Jian Li121ddfe2019-08-27 02:07:05 +0900656 // delete local bridge from the node
Jian Li58b33982020-07-01 19:07:02 +0900657 client.dropBridge(k8sNode.localBridgeName());
658
Jian Li8fa74cfa2020-11-25 16:49:34 +0900659 // disconnect ovsdb
660 // client.disconnect();
661 }
662
663 private void removeTunnelPatchPort(K8sNode k8sNode) {
664 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
665 if (client == null) {
666 log.info("Failed to get ovsdb client");
667 return;
Jian Li58b33982020-07-01 19:07:02 +0900668 }
Jian Li121ddfe2019-08-27 02:07:05 +0900669
Jian Li8fa74cfa2020-11-25 16:49:34 +0900670 client.dropInterface(k8sNode.tunToIntgPatchPortName());
671 }
672
673 private void removeRouterPatchPort(K8sNode k8sNode) {
674 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
675 if (client == null) {
676 log.info("Failed to get ovsdb client");
677 return;
678 }
679
680 client.dropInterface(k8sNode.routerToExtPatchPortName());
681 }
682
683 private void removeOpenstackPatchPorts(K8sNode k8sNode) {
684 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
685 if (client == null) {
686 log.info("Failed to get ovsdb client");
687 return;
688 }
689
690 // remove patch port attached at br-int peers with the k8s integration bridge
691 client.dropInterface(k8sNode.osToK8sIntgPatchPortName());
692
693 // remove patch port attached at br-int peers with the k8s external bridge
694 client.dropInterface(k8sNode.osToK8sExtPatchPortName());
Jian Lif16e8852019-01-22 22:55:31 +0900695 }
696
697 /**
698 * An internal OVSDB listener. This listener is used for listening the
699 * network facing events from OVSDB device. If a new OVSDB device is detected,
700 * ONOS tries to bootstrap the kubernetes node.
701 */
702 private class InternalOvsdbListener implements DeviceListener {
703
704 @Override
705 public boolean isRelevant(DeviceEvent event) {
706 return event.subject().type() == Device.Type.CONTROLLER;
707 }
708
709 private boolean isRelevantHelper() {
710 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
711 }
712
713 @Override
714 public void event(DeviceEvent event) {
715 Device device = event.subject();
716
717 switch (event.type()) {
718 case DEVICE_AVAILABILITY_CHANGED:
719 case DEVICE_ADDED:
720 eventExecutor.execute(() -> {
721
722 if (!isRelevantHelper()) {
723 return;
724 }
725
726 K8sNode k8sNode = k8sNodeService.node(device.id());
727
728 if (k8sNode == null) {
729 return;
730 }
731
732 if (deviceService.isAvailable(device.id())) {
733 log.debug("OVSDB {} detected", device.id());
734 bootstrapNode(k8sNode);
735 }
736 });
737 break;
738 case PORT_ADDED:
739 case PORT_REMOVED:
740 case DEVICE_REMOVED:
741 default:
742 // do nothing
743 break;
744 }
745 }
746 }
747
748 /**
749 * An internal integration bridge listener. This listener is used for
750 * listening the events from integration bridge. To listen the events from
751 * other types of bridge such as provider bridge or tunnel bridge, we need
752 * to augment K8sNodeService.node() method.
753 */
754 private class InternalBridgeListener implements DeviceListener {
755
756 @Override
757 public boolean isRelevant(DeviceEvent event) {
758 return event.subject().type() == Device.Type.SWITCH;
759 }
760
761 private boolean isRelevantHelper() {
762 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
763 }
764
765 @Override
766 public void event(DeviceEvent event) {
767 Device device = event.subject();
768
769 switch (event.type()) {
770 case DEVICE_AVAILABILITY_CHANGED:
771 case DEVICE_ADDED:
772 eventExecutor.execute(() -> {
773
774 if (!isRelevantHelper()) {
775 return;
776 }
777
778 K8sNode k8sNode = k8sNodeService.node(device.id());
779
780 if (k8sNode == null) {
781 return;
782 }
783
Jian Libf562c22019-04-15 18:07:14 +0900784 // TODO: also need to check the external bridge's availability
Jian Li121ddfe2019-08-27 02:07:05 +0900785 // TODO: also need to check the local bridge's availability
Jian Lif16e8852019-01-22 22:55:31 +0900786 if (deviceService.isAvailable(device.id())) {
Jian Libf562c22019-04-15 18:07:14 +0900787 log.debug("Integration bridge created on {}",
788 k8sNode.hostname());
Jian Lif16e8852019-01-22 22:55:31 +0900789 bootstrapNode(k8sNode);
790 } else if (k8sNode.state() == COMPLETE) {
791 log.info("Device {} disconnected", device.id());
792 setState(k8sNode, INCOMPLETE);
793 }
794
795 if (autoRecovery) {
796 if (k8sNode.state() == INCOMPLETE ||
797 k8sNode.state() == DEVICE_CREATED) {
798 log.info("Device {} is reconnected", device.id());
799 k8sNodeAdminService.updateNode(
800 k8sNode.updateState(K8sNodeState.INIT));
801 }
802 }
803 });
804 break;
805 case PORT_UPDATED:
806 case PORT_ADDED:
807 eventExecutor.execute(() -> {
808
809 if (!isRelevantHelper()) {
810 return;
811 }
812
813 K8sNode k8sNode = k8sNodeService.node(device.id());
814
815 if (k8sNode == null) {
816 return;
817 }
818
819 Port port = event.port();
820 String portName = port.annotations().value(PORT_NAME);
821 if (k8sNode.state() == DEVICE_CREATED && (
Jian Li58b33982020-07-01 19:07:02 +0900822 Objects.equals(portName, k8sNode.vxlanPortName()) ||
823 Objects.equals(portName, k8sNode.grePortName()) ||
824 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900825 log.info("Interface {} added or updated to {}",
826 portName, device.id());
827 bootstrapNode(k8sNode);
828 }
829 });
830 break;
831 case PORT_REMOVED:
832 eventExecutor.execute(() -> {
833
834 if (!isRelevantHelper()) {
835 return;
836 }
837
838 K8sNode k8sNode = k8sNodeService.node(device.id());
839
840 if (k8sNode == null) {
841 return;
842 }
843
844 Port port = event.port();
845 String portName = port.annotations().value(PORT_NAME);
846 if (k8sNode.state() == COMPLETE && (
Jian Li58b33982020-07-01 19:07:02 +0900847 Objects.equals(portName, k8sNode.vxlanPortName()) ||
848 Objects.equals(portName, k8sNode.grePortName()) ||
849 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900850 log.warn("Interface {} removed from {}",
851 portName, event.subject().id());
852 setState(k8sNode, INCOMPLETE);
853 }
854 });
855 break;
856 case DEVICE_REMOVED:
857 default:
858 // do nothing
859 break;
860 }
861 }
862 }
863
864 /**
865 * An internal kubernetes node listener.
866 * The notification is triggered by KubernetesNodeStore.
867 */
868 private class InternalK8sNodeListener implements K8sNodeListener {
869
870 private boolean isRelevantHelper() {
871 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
872 }
873
874 @Override
875 public void event(K8sNodeEvent event) {
876 switch (event.type()) {
877 case K8S_NODE_CREATED:
878 case K8S_NODE_UPDATED:
879 eventExecutor.execute(() -> {
Jian Lif16e8852019-01-22 22:55:31 +0900880 if (!isRelevantHelper()) {
881 return;
882 }
Jian Lif16e8852019-01-22 22:55:31 +0900883 bootstrapNode(event.subject());
884 });
885 break;
886 case K8S_NODE_REMOVED:
887 eventExecutor.execute(() -> {
Jian Lif16e8852019-01-22 22:55:31 +0900888 if (!isRelevantHelper()) {
889 return;
890 }
Jian Lif16e8852019-01-22 22:55:31 +0900891 processK8sNodeRemoved(event.subject());
892 });
893 break;
894 case K8S_NODE_INCOMPLETE:
895 default:
896 break;
897 }
898 }
899 }
900}