blob: e5c8b000fadb76c2cb2ec0983119be1f6d6f4a7d [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 Lidc1df642020-11-25 16:49:34 +0900265 @Override
266 public void processOffBoardedState(K8sNode k8sNode) {
267 // do something if needed
268 }
269
Jian Lif16e8852019-01-22 22:55:31 +0900270 /**
271 * Extracts properties from the component configuration context.
272 *
273 * @param context the component context
274 */
275 private void readComponentConfiguration(ComponentContext context) {
276 Dictionary<?, ?> properties = context.getProperties();
277
278 Integer ovsdbPortConfigured = Tools.getIntegerProperty(properties, OVSDB_PORT);
279 if (ovsdbPortConfigured == null) {
280 ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
281 log.info("OVSDB port is NOT configured, default value is {}", ovsdbPortNum);
282 } else {
283 ovsdbPortNum = ovsdbPortConfigured;
284 log.info("Configured. OVSDB port is {}", ovsdbPortNum);
285 }
286
287 Boolean autoRecoveryConfigured =
288 getBooleanProperty(properties, AUTO_RECOVERY);
289 if (autoRecoveryConfigured == null) {
290 autoRecovery = AUTO_RECOVERY_DEFAULT;
291 log.info("Auto recovery flag is NOT " +
292 "configured, default value is {}", autoRecovery);
293 } else {
294 autoRecovery = autoRecoveryConfigured;
295 log.info("Configured. Auto recovery flag is {}", autoRecovery);
296 }
297 }
298
299 /**
300 * Creates a bridge with a given name on a given kubernetes node.
301 *
302 * @param k8sNode kubernetes node
303 * @param bridgeName bridge name
304 * @param devId device identifier
305 */
306 private void createBridge(K8sNode k8sNode, String bridgeName, DeviceId devId) {
307 Device device = deviceService.getDevice(k8sNode.ovsdb());
308
309 List<ControllerInfo> controllers = clusterService.getNodes().stream()
310 .map(n -> new ControllerInfo(n.ip(), DEFAULT_OFPORT, DEFAULT_OF_PROTO))
311 .collect(Collectors.toList());
312
313 String dpid = devId.toString().substring(DPID_BEGIN);
314
315 BridgeDescription.Builder builder = DefaultBridgeDescription.builder()
316 .name(bridgeName)
317 .failMode(BridgeDescription.FailMode.SECURE)
318 .datapathId(dpid)
319 .disableInBand()
320 .controllers(controllers);
321
322 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
323 bridgeConfig.addBridge(builder.build());
324 }
325
326 /**
327 * Creates a VXLAN tunnel interface in a given kubernetes node.
328 *
329 * @param k8sNode kubernetes node
330 */
331 private void createVxlanTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900332 createTunnelInterface(k8sNode, VXLAN, k8sNode.vxlanPortName());
Jian Lif16e8852019-01-22 22:55:31 +0900333 }
334
335 /**
336 * Creates a GRE tunnel interface in a given kubernetes node.
337 *
338 * @param k8sNode kubernetes node
339 */
340 private void createGreTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900341 createTunnelInterface(k8sNode, GRE, k8sNode.grePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900342 }
343
344 /**
345 * Creates a GENEVE tunnel interface in a given kubernetes node.
346 *
347 * @param k8sNode kubernetes node
348 */
349 private void createGeneveTunnelInterface(K8sNode k8sNode) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900350 createTunnelInterface(k8sNode, GENEVE, k8sNode.genevePortName());
Jian Lif16e8852019-01-22 22:55:31 +0900351 }
352
Jian Libf562c22019-04-15 18:07:14 +0900353 private void createPatchInterfaces(K8sNode k8sNode) {
354 Device device = deviceService.getDevice(k8sNode.ovsdb());
355 if (device == null || !device.is(InterfaceConfig.class)) {
356 log.error("Failed to create patch interface on {}", k8sNode.ovsdb());
357 return;
358 }
359
Jian Li1a2eb5d2019-08-27 02:07:05 +0900360 // integration bridge -> external bridge
361 PatchDescription brIntExtPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900362 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900363 .deviceId(k8sNode.intgBridgeName())
364 .ifaceName(k8sNode.intgToExtPatchPortName())
365 .peer(k8sNode.extToIntgPatchPortName())
366 .build();
Jian Lie2a04ce2020-07-01 19:07:02 +0900367
368 // integration bridge -> tunnel bridge
369 PatchDescription brIntTunPatchDesc =
370 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900371 .deviceId(k8sNode.intgBridgeName())
372 .ifaceName(k8sNode.intgToTunPatchPortName())
373 .peer(k8sNode.tunToIntgPatchPortName())
374 .build();
Jian Libf562c22019-04-15 18:07:14 +0900375
Jian Li1a2eb5d2019-08-27 02:07:05 +0900376 // external bridge -> integration bridge
377 PatchDescription brExtIntPatchDesc =
Jian Libf562c22019-04-15 18:07:14 +0900378 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900379 .deviceId(k8sNode.extBridgeName())
380 .ifaceName(k8sNode.extToIntgPatchPortName())
381 .peer(k8sNode.intgToExtPatchPortName())
382 .build();
Jian Libf562c22019-04-15 18:07:14 +0900383
Jian Li1a2eb5d2019-08-27 02:07:05 +0900384 // integration bridge -> local bridge
385 PatchDescription brIntLocalPatchDesc =
386 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900387 .deviceId(k8sNode.intgBridgeName())
388 .ifaceName(k8sNode.intgToLocalPatchPortName())
389 .peer(k8sNode.localToIntgPatchPortName())
390 .build();
Jian Li1a2eb5d2019-08-27 02:07:05 +0900391
392 // local bridge -> integration bridge
393 PatchDescription brLocalIntPatchDesc =
394 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900395 .deviceId(k8sNode.localBridgeName())
396 .ifaceName(k8sNode.localToIntgPatchPortName())
397 .peer(k8sNode.intgToLocalPatchPortName())
398 .build();
Jian Li1a2eb5d2019-08-27 02:07:05 +0900399
Jian Libf562c22019-04-15 18:07:14 +0900400 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
Jian Lie2a04ce2020-07-01 19:07:02 +0900401 ifaceConfig.addPatchMode(k8sNode.intgToExtPatchPortName(), brIntExtPatchDesc);
402 ifaceConfig.addPatchMode(k8sNode.extToIntgPatchPortName(), brExtIntPatchDesc);
403 ifaceConfig.addPatchMode(k8sNode.intgToLocalPatchPortName(), brIntLocalPatchDesc);
404 ifaceConfig.addPatchMode(k8sNode.localToIntgPatchPortName(), brLocalIntPatchDesc);
405 ifaceConfig.addPatchMode(k8sNode.intgToTunPatchPortName(), brIntTunPatchDesc);
406
407 if (k8sNode.mode() == NORMAL) {
408 // tunnel bridge -> integration bridge
409 PatchDescription brTunIntPatchDesc =
410 DefaultPatchDescription.builder()
Jian Li732c3422020-09-07 17:01:11 +0900411 .deviceId(k8sNode.tunBridgeName())
412 .ifaceName(k8sNode.tunToIntgPatchPortName())
413 .peer(k8sNode.intgToTunPatchPortName())
414 .build();
Jian Lie2a04ce2020-07-01 19:07:02 +0900415
416 ifaceConfig.addPatchMode(k8sNode.tunToIntgPatchPortName(), brTunIntPatchDesc);
Jian Li019ce6a2020-09-09 10:23:21 +0900417 } else {
418 // k8s integration bridge -> openstack integration bridge
419 PatchDescription k8sIntOsIntPatchDesc =
420 DefaultPatchDescription.builder()
421 .deviceId(k8sNode.intgBridgeName())
Jian Lia4d8fba2020-09-10 23:16:50 +0900422 .ifaceName(k8sNode.k8sIntgToOsPatchPortName())
Jian Li019ce6a2020-09-09 10:23:21 +0900423 .peer(k8sNode.osToK8sIntgPatchPortName())
424 .build();
Jian Lia4d8fba2020-09-10 23:16:50 +0900425 ifaceConfig.addPatchMode(k8sNode.k8sIntgToOsPatchPortName(), k8sIntOsIntPatchDesc);
426
427 // k8s external bridge -> openstack integration bridge
428 PatchDescription k8sExtOsIntPatchDesc =
429 DefaultPatchDescription.builder()
430 .deviceId(k8sNode.extBridgeName())
431 .ifaceName(k8sNode.k8sExtToOsPatchPortName())
432 .peer(k8sNode.osToK8sExtPatchPortName())
433 .build();
434 ifaceConfig.addPatchMode(k8sNode.k8sExtToOsPatchPortName(), k8sExtOsIntPatchDesc);
Jian Li019ce6a2020-09-09 10:23:21 +0900435
436 // external bridge -> router bridge
437 PatchDescription extRouterPatchDesc =
438 DefaultPatchDescription.builder()
439 .deviceId(k8sNode.extBridgeName())
440 .ifaceName(k8sNode.extToRouterPatchPortName())
441 .peer(k8sNode.routerToExtPatchPortName())
442 .build();
443 ifaceConfig.addPatchMode(k8sNode.extToRouterPatchPortName(), extRouterPatchDesc);
Jian Lie2a04ce2020-07-01 19:07:02 +0900444 }
Jian Libf562c22019-04-15 18:07:14 +0900445 }
446
Jian Lif16e8852019-01-22 22:55:31 +0900447 /**
448 * Creates a tunnel interface in a given kubernetes node.
449 *
450 * @param k8sNode kubernetes node
451 */
452 private void createTunnelInterface(K8sNode k8sNode,
453 String type, String intfName) {
454 if (isIntfEnabled(k8sNode, intfName)) {
455 return;
456 }
457
458 Device device = deviceService.getDevice(k8sNode.ovsdb());
459 if (device == null || !device.is(InterfaceConfig.class)) {
460 log.error("Failed to create tunnel interface on {}", k8sNode.ovsdb());
461 return;
462 }
463
Jian Lie2a04ce2020-07-01 19:07:02 +0900464 TunnelDescription tunnelDesc = buildTunnelDesc(k8sNode, type, intfName);
Jian Lif16e8852019-01-22 22:55:31 +0900465
466 InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
467 ifaceConfig.addTunnelMode(intfName, tunnelDesc);
468 }
469
470 /**
471 * Builds tunnel description according to the network type.
472 *
473 * @param type network type
474 * @return tunnel description
475 */
Jian Lie2a04ce2020-07-01 19:07:02 +0900476 private TunnelDescription buildTunnelDesc(K8sNode k8sNode,
477 String type, String intfName) {
478 TunnelKey<String> key = new TunnelKey<>(k8sNode.tunnelKey());
Jian Lif16e8852019-01-22 22:55:31 +0900479 if (VXLAN.equals(type) || GRE.equals(type) || GENEVE.equals(type)) {
480 TunnelDescription.Builder tdBuilder =
481 DefaultTunnelDescription.builder()
Jian Lie2a04ce2020-07-01 19:07:02 +0900482 .deviceId(k8sNode.tunBridgeName())
Jian Lif16e8852019-01-22 22:55:31 +0900483 .ifaceName(intfName)
484 .remote(TunnelEndPoints.flowTunnelEndpoint())
Jian Lie2a04ce2020-07-01 19:07:02 +0900485 .key(key);
Jian Lif16e8852019-01-22 22:55:31 +0900486
487 switch (type) {
488 case VXLAN:
489 tdBuilder.type(TunnelDescription.Type.VXLAN);
490 break;
491 case GRE:
492 tdBuilder.type(TunnelDescription.Type.GRE);
493 break;
494 case GENEVE:
495 tdBuilder.type(TunnelDescription.Type.GENEVE);
496 break;
497 default:
498 return null;
499 }
500
501 return tdBuilder.build();
502 }
503 return null;
504 }
505
506 /**
507 * Checks whether a given network interface in a given kubernetes node
508 * is enabled or not.
509 *
510 * @param k8sNode kubernetes node
511 * @param intf network interface name
512 * @return true if the given interface is enabled, false otherwise
513 */
514 private boolean isIntfEnabled(K8sNode k8sNode, String intf) {
Jian Lie2a04ce2020-07-01 19:07:02 +0900515 return deviceService.isAvailable(k8sNode.tunBridge()) &&
516 deviceService.getPorts(k8sNode.tunBridge()).stream()
Jian Lif16e8852019-01-22 22:55:31 +0900517 .anyMatch(port -> Objects.equals(
518 port.annotations().value(PORT_NAME), intf) &&
519 port.isEnabled());
520 }
521
522 /**
523 * Checks whether all requirements for this state are fulfilled or not.
524 *
525 * @param k8sNode kubernetes node
526 * @return true if all requirements are fulfilled, false otherwise
527 */
528 private boolean isCurrentStateDone(K8sNode k8sNode) {
529 switch (k8sNode.state()) {
530 case INIT:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900531 return isInitStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900532 case DEVICE_CREATED:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900533 return isDeviceCreatedStateDone(k8sNode);
534 case PRE_ON_BOARD:
535 return isInitStateDone(k8sNode) && isDeviceCreatedStateDone(k8sNode);
Jian Lif16e8852019-01-22 22:55:31 +0900536 case COMPLETE:
537 case INCOMPLETE:
Jian Li0ee8d0e2019-12-18 11:35:05 +0900538 case ON_BOARDED:
Jian Li4604b7f2020-01-03 18:42:30 +0900539 case POST_ON_BOARD:
Jian Lif16e8852019-01-22 22:55:31 +0900540 // always return false
541 // run init CLI to re-trigger node bootstrap
542 return false;
543 default:
544 return true;
545 }
546 }
547
Jian Li0ee8d0e2019-12-18 11:35:05 +0900548 private boolean isInitStateDone(K8sNode k8sNode) {
549 if (!isOvsdbConnected(k8sNode, ovsdbPortNum,
550 ovsdbController, deviceService)) {
551 return false;
552 }
553
Jian Li4604b7f2020-01-03 18:42:30 +0900554 try {
555 // we need to wait a while, in case interface and bridge
556 // creation requires some time
557 sleep(SLEEP_MS);
558 } catch (InterruptedException e) {
559 log.error("Exception caused during init state checking...");
560 }
561
Jian Lie2a04ce2020-07-01 19:07:02 +0900562 boolean result = k8sNode.intgBridge() != null && k8sNode.extBridge() != null &&
Jian Li0ee8d0e2019-12-18 11:35:05 +0900563 deviceService.isAvailable(k8sNode.intgBridge()) &&
564 deviceService.isAvailable(k8sNode.extBridge()) &&
565 deviceService.isAvailable(k8sNode.localBridge());
Jian Lie2a04ce2020-07-01 19:07:02 +0900566
567 if (k8sNode.mode() == NORMAL) {
568 return result && deviceService.isAvailable(k8sNode.tunBridge());
569 } else {
570 return result;
571 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900572 }
573
574 private boolean isDeviceCreatedStateDone(K8sNode k8sNode) {
Jian Li4604b7f2020-01-03 18:42:30 +0900575
576 try {
577 // we need to wait a while, in case interface and bridge
578 // creation requires some time
579 sleep(SLEEP_MS);
580 } catch (InterruptedException e) {
581 log.error("Exception caused during init state checking...");
582 }
583
Jian Lie2a04ce2020-07-01 19:07:02 +0900584 if (k8sNode.mode() == NORMAL) {
585 if (k8sNode.dataIp() != null &&
586 !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
587 return false;
588 }
589 if (k8sNode.dataIp() != null &&
590 !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
591 return false;
592 }
593 if (k8sNode.dataIp() != null &&
594 !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
595 return false;
596 }
Jian Li0ee8d0e2019-12-18 11:35:05 +0900597 }
598
599 return true;
600 }
601
Jian Lif16e8852019-01-22 22:55:31 +0900602 /**
603 * Configures the kubernetes node with new state.
604 *
605 * @param k8sNode kubernetes node
606 * @param newState a new state
607 */
608 private void setState(K8sNode k8sNode, K8sNodeState newState) {
609 if (k8sNode.state() == newState) {
610 return;
611 }
612 K8sNode updated = k8sNode.updateState(newState);
613 k8sNodeAdminService.updateNode(updated);
614 log.info("Changed {} state: {}", k8sNode.hostname(), newState);
615 }
616
617 /**
618 * Bootstraps a new kubernetes node.
619 *
620 * @param k8sNode kubernetes node
621 */
622 private void bootstrapNode(K8sNode k8sNode) {
623 if (isCurrentStateDone(k8sNode)) {
624 setState(k8sNode, k8sNode.state().nextState());
625 } else {
626 log.trace("Processing {} state for {}", k8sNode.state(),
627 k8sNode.hostname());
628 k8sNode.state().process(this, k8sNode);
629 }
630 }
631
632 private void processK8sNodeRemoved(K8sNode k8sNode) {
633 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
634 if (client == null) {
635 log.info("Failed to get ovsdb client");
636 return;
637 }
638
Jian Lidc1df642020-11-25 16:49:34 +0900639 if (k8sNode.mode() == NORMAL) {
640 // delete tunnel bridge from the node
641 client.dropBridge(k8sNode.tunBridgeName());
642 } else {
643 // remove the patch ports direct to the integration bridge from tunnel bridge
644 removeTunnelPatchPort(k8sNode);
645 // remove the patch ports direct to the external bridge from the router bridge
646 removeRouterPatchPort(k8sNode);
647 // remove the patch ports directs to the openstack's br-int bridge from the int and ext bridges
648 removeOpenstackPatchPorts(k8sNode);
649 }
650
Jian Lif16e8852019-01-22 22:55:31 +0900651 // delete integration bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900652 client.dropBridge(k8sNode.intgBridgeName());
Jian Lif16e8852019-01-22 22:55:31 +0900653
Jian Libf562c22019-04-15 18:07:14 +0900654 // delete external bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900655 client.dropBridge(k8sNode.extBridgeName());
Jian Libf562c22019-04-15 18:07:14 +0900656
Jian Li1a2eb5d2019-08-27 02:07:05 +0900657 // delete local bridge from the node
Jian Lie2a04ce2020-07-01 19:07:02 +0900658 client.dropBridge(k8sNode.localBridgeName());
659
Jian Lidc1df642020-11-25 16:49:34 +0900660 // disconnect ovsdb
661 // client.disconnect();
662 }
663
664 private void removeTunnelPatchPort(K8sNode k8sNode) {
665 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
666 if (client == null) {
667 log.info("Failed to get ovsdb client");
668 return;
Jian Lie2a04ce2020-07-01 19:07:02 +0900669 }
Jian Li1a2eb5d2019-08-27 02:07:05 +0900670
Jian Lidc1df642020-11-25 16:49:34 +0900671 client.dropInterface(k8sNode.tunToIntgPatchPortName());
672 }
673
674 private void removeRouterPatchPort(K8sNode k8sNode) {
675 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
676 if (client == null) {
677 log.info("Failed to get ovsdb client");
678 return;
679 }
680
681 client.dropInterface(k8sNode.routerToExtPatchPortName());
682 }
683
684 private void removeOpenstackPatchPorts(K8sNode k8sNode) {
685 OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
686 if (client == null) {
687 log.info("Failed to get ovsdb client");
688 return;
689 }
690
691 // remove patch port attached at br-int peers with the k8s integration bridge
692 client.dropInterface(k8sNode.osToK8sIntgPatchPortName());
693
694 // remove patch port attached at br-int peers with the k8s external bridge
695 client.dropInterface(k8sNode.osToK8sExtPatchPortName());
Jian Lif16e8852019-01-22 22:55:31 +0900696 }
697
698 /**
699 * An internal OVSDB listener. This listener is used for listening the
700 * network facing events from OVSDB device. If a new OVSDB device is detected,
701 * ONOS tries to bootstrap the kubernetes node.
702 */
703 private class InternalOvsdbListener implements DeviceListener {
704
705 @Override
706 public boolean isRelevant(DeviceEvent event) {
707 return event.subject().type() == Device.Type.CONTROLLER;
708 }
709
710 private boolean isRelevantHelper() {
711 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
712 }
713
714 @Override
715 public void event(DeviceEvent event) {
716 Device device = event.subject();
717
718 switch (event.type()) {
719 case DEVICE_AVAILABILITY_CHANGED:
720 case DEVICE_ADDED:
721 eventExecutor.execute(() -> {
722
723 if (!isRelevantHelper()) {
724 return;
725 }
726
727 K8sNode k8sNode = k8sNodeService.node(device.id());
728
729 if (k8sNode == null) {
730 return;
731 }
732
733 if (deviceService.isAvailable(device.id())) {
734 log.debug("OVSDB {} detected", device.id());
735 bootstrapNode(k8sNode);
736 }
737 });
738 break;
739 case PORT_ADDED:
740 case PORT_REMOVED:
741 case DEVICE_REMOVED:
742 default:
743 // do nothing
744 break;
745 }
746 }
747 }
748
749 /**
750 * An internal integration bridge listener. This listener is used for
751 * listening the events from integration bridge. To listen the events from
752 * other types of bridge such as provider bridge or tunnel bridge, we need
753 * to augment K8sNodeService.node() method.
754 */
755 private class InternalBridgeListener implements DeviceListener {
756
757 @Override
758 public boolean isRelevant(DeviceEvent event) {
759 return event.subject().type() == Device.Type.SWITCH;
760 }
761
762 private boolean isRelevantHelper() {
763 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
764 }
765
766 @Override
767 public void event(DeviceEvent event) {
768 Device device = event.subject();
769
770 switch (event.type()) {
771 case DEVICE_AVAILABILITY_CHANGED:
772 case DEVICE_ADDED:
773 eventExecutor.execute(() -> {
774
775 if (!isRelevantHelper()) {
776 return;
777 }
778
779 K8sNode k8sNode = k8sNodeService.node(device.id());
780
781 if (k8sNode == null) {
782 return;
783 }
784
Jian Libf562c22019-04-15 18:07:14 +0900785 // TODO: also need to check the external bridge's availability
Jian Li1a2eb5d2019-08-27 02:07:05 +0900786 // TODO: also need to check the local bridge's availability
Jian Lif16e8852019-01-22 22:55:31 +0900787 if (deviceService.isAvailable(device.id())) {
Jian Libf562c22019-04-15 18:07:14 +0900788 log.debug("Integration bridge created on {}",
789 k8sNode.hostname());
Jian Lif16e8852019-01-22 22:55:31 +0900790 bootstrapNode(k8sNode);
791 } else if (k8sNode.state() == COMPLETE) {
792 log.info("Device {} disconnected", device.id());
793 setState(k8sNode, INCOMPLETE);
794 }
795
796 if (autoRecovery) {
797 if (k8sNode.state() == INCOMPLETE ||
798 k8sNode.state() == DEVICE_CREATED) {
799 log.info("Device {} is reconnected", device.id());
800 k8sNodeAdminService.updateNode(
801 k8sNode.updateState(K8sNodeState.INIT));
802 }
803 }
804 });
805 break;
806 case PORT_UPDATED:
807 case PORT_ADDED:
808 eventExecutor.execute(() -> {
809
810 if (!isRelevantHelper()) {
811 return;
812 }
813
814 K8sNode k8sNode = k8sNodeService.node(device.id());
815
816 if (k8sNode == null) {
817 return;
818 }
819
820 Port port = event.port();
821 String portName = port.annotations().value(PORT_NAME);
822 if (k8sNode.state() == DEVICE_CREATED && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900823 Objects.equals(portName, k8sNode.vxlanPortName()) ||
824 Objects.equals(portName, k8sNode.grePortName()) ||
825 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900826 log.info("Interface {} added or updated to {}",
827 portName, device.id());
828 bootstrapNode(k8sNode);
829 }
830 });
831 break;
832 case PORT_REMOVED:
833 eventExecutor.execute(() -> {
834
835 if (!isRelevantHelper()) {
836 return;
837 }
838
839 K8sNode k8sNode = k8sNodeService.node(device.id());
840
841 if (k8sNode == null) {
842 return;
843 }
844
845 Port port = event.port();
846 String portName = port.annotations().value(PORT_NAME);
847 if (k8sNode.state() == COMPLETE && (
Jian Lie2a04ce2020-07-01 19:07:02 +0900848 Objects.equals(portName, k8sNode.vxlanPortName()) ||
849 Objects.equals(portName, k8sNode.grePortName()) ||
850 Objects.equals(portName, k8sNode.genevePortName()))) {
Jian Lif16e8852019-01-22 22:55:31 +0900851 log.warn("Interface {} removed from {}",
852 portName, event.subject().id());
853 setState(k8sNode, INCOMPLETE);
854 }
855 });
856 break;
857 case DEVICE_REMOVED:
858 default:
859 // do nothing
860 break;
861 }
862 }
863 }
864
865 /**
866 * An internal kubernetes node listener.
867 * The notification is triggered by KubernetesNodeStore.
868 */
869 private class InternalK8sNodeListener implements K8sNodeListener {
870
871 private boolean isRelevantHelper() {
872 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
873 }
874
875 @Override
876 public void event(K8sNodeEvent event) {
877 switch (event.type()) {
878 case K8S_NODE_CREATED:
879 case K8S_NODE_UPDATED:
880 eventExecutor.execute(() -> {
Jian Lif16e8852019-01-22 22:55:31 +0900881 if (!isRelevantHelper()) {
882 return;
883 }
Jian Lif16e8852019-01-22 22:55:31 +0900884 bootstrapNode(event.subject());
885 });
886 break;
887 case K8S_NODE_REMOVED:
888 eventExecutor.execute(() -> {
Jian Lif16e8852019-01-22 22:55:31 +0900889 if (!isRelevantHelper()) {
890 return;
891 }
Jian Lif16e8852019-01-22 22:55:31 +0900892 processK8sNodeRemoved(event.subject());
893 });
894 break;
895 case K8S_NODE_INCOMPLETE:
896 default:
897 break;
898 }
899 }
900 }
901}