blob: c6887409a8ff5de9f8739d8ad90441859581884d [file] [log] [blame]
Jian Li1cee9882019-02-13 11:25:25 +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
Jian Li077b07e2020-09-01 16:55:25 +090018import com.google.common.collect.ImmutableSet;
Jian Li1cee9882019-02-13 11:25:25 +090019import io.fabric8.kubernetes.api.model.Node;
20import io.fabric8.kubernetes.api.model.NodeAddress;
21import io.fabric8.kubernetes.client.KubernetesClient;
22import org.onlab.packet.IpAddress;
Jian Lic2242bd2020-09-03 13:12:14 +090023import org.onlab.packet.MacAddress;
Jian Li1cee9882019-02-13 11:25:25 +090024import org.onosproject.cluster.ClusterService;
25import org.onosproject.cluster.LeadershipService;
26import org.onosproject.cluster.NodeId;
27import org.onosproject.core.ApplicationId;
28import org.onosproject.core.CoreService;
Jian Li077b07e2020-09-01 16:55:25 +090029import org.onosproject.k8snode.api.DefaultK8sHost;
Jian Li1cee9882019-02-13 11:25:25 +090030import org.onosproject.k8snode.api.DefaultK8sNode;
Jian Lie2a04ce2020-07-01 19:07:02 +090031import org.onosproject.k8snode.api.ExternalNetworkService;
32import org.onosproject.k8snode.api.HostNodesInfo;
Jian Li1cee9882019-02-13 11:25:25 +090033import org.onosproject.k8snode.api.K8sApiConfig;
34import org.onosproject.k8snode.api.K8sApiConfigAdminService;
35import org.onosproject.k8snode.api.K8sApiConfigEvent;
36import org.onosproject.k8snode.api.K8sApiConfigListener;
Jian Li077b07e2020-09-01 16:55:25 +090037import org.onosproject.k8snode.api.K8sHost;
38import org.onosproject.k8snode.api.K8sHostAdminService;
39import org.onosproject.k8snode.api.K8sHostState;
Jian Li1cee9882019-02-13 11:25:25 +090040import org.onosproject.k8snode.api.K8sNode;
41import org.onosproject.k8snode.api.K8sNodeAdminService;
Jian Li6d2ffbf2020-11-04 15:58:18 +090042import org.onosproject.k8snode.api.K8sNodeInfo;
Jian Li019ce6a2020-09-09 10:23:21 +090043import org.onosproject.k8snode.api.K8sRouterBridge;
Jian Li077b07e2020-09-01 16:55:25 +090044import org.onosproject.k8snode.api.K8sTunnelBridge;
Jian Li1cee9882019-02-13 11:25:25 +090045import org.osgi.service.component.annotations.Activate;
46import org.osgi.service.component.annotations.Component;
47import org.osgi.service.component.annotations.Deactivate;
48import org.osgi.service.component.annotations.Reference;
49import org.osgi.service.component.annotations.ReferenceCardinality;
50import org.slf4j.Logger;
51
Jian Li0c632722019-05-08 15:58:04 +090052import java.util.Map;
Jian Li1cee9882019-02-13 11:25:25 +090053import java.util.Objects;
54import java.util.concurrent.ExecutorService;
55
Jian Li077b07e2020-09-01 16:55:25 +090056import static java.lang.Thread.sleep;
Jian Li1cee9882019-02-13 11:25:25 +090057import static java.util.concurrent.Executors.newSingleThreadExecutor;
58import static org.onlab.util.Tools.groupedThreads;
Jian Lie2a04ce2020-07-01 19:07:02 +090059import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
Jian Lic2242bd2020-09-03 13:12:14 +090060import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
Jian Lie2a04ce2020-07-01 19:07:02 +090061import static org.onosproject.k8snode.api.Constants.EXTERNAL_TO_ROUTER;
62import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
Jian Li1cee9882019-02-13 11:25:25 +090063import static org.onosproject.k8snode.api.K8sNode.Type.MASTER;
64import static org.onosproject.k8snode.api.K8sNode.Type.MINION;
65import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
Jian Li732c3422020-09-07 17:01:11 +090066import static org.onosproject.k8snode.api.K8sNodeState.ON_BOARDED;
Jian Li0ee8d0e2019-12-18 11:35:05 +090067import static org.onosproject.k8snode.api.K8sNodeState.PRE_ON_BOARD;
Jian Li1cee9882019-02-13 11:25:25 +090068import static org.onosproject.k8snode.util.K8sNodeUtil.k8sClient;
69import static org.slf4j.LoggerFactory.getLogger;
70
71/**
72 * Handles the state of kubernetes API server configuration.
73 */
74@Component(immediate = true)
75public class DefaultK8sApiConfigHandler {
76
77 private final Logger log = getLogger(getClass());
78
79 private static final String INTERNAL_IP = "InternalIP";
80 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li0c632722019-05-08 15:58:04 +090081 private static final String EXT_BRIDGE_IP = "external.bridge.ip";
82 private static final String EXT_GATEWAY_IP = "external.gateway.ip";
83 private static final String EXT_INTF_NAME = "external.interface.name";
Jian Li1cee9882019-02-13 11:25:25 +090084
Jian Lie2a04ce2020-07-01 19:07:02 +090085 private static final String DEFAULT_GATEWAY_IP = "127.0.0.1";
86 private static final String DEFAULT_BRIDGE_IP = "127.0.0.1";
87
Jian Li732c3422020-09-07 17:01:11 +090088 private static final long SLEEP_MS = 10000; // we wait 10s
Jian Li077b07e2020-09-01 16:55:25 +090089
Jian Li1cee9882019-02-13 11:25:25 +090090 @Reference(cardinality = ReferenceCardinality.MANDATORY)
91 protected CoreService coreService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY)
94 protected LeadershipService leadershipService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY)
97 protected ClusterService clusterService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY)
100 protected K8sApiConfigAdminService k8sApiConfigAdminService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY)
103 protected K8sNodeAdminService k8sNodeAdminService;
104
Jian Lie2a04ce2020-07-01 19:07:02 +0900105 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li077b07e2020-09-01 16:55:25 +0900106 protected K8sHostAdminService k8sHostAdminService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lie2a04ce2020-07-01 19:07:02 +0900109 protected ExternalNetworkService extNetworkService;
110
Jian Li1cee9882019-02-13 11:25:25 +0900111 private final ExecutorService eventExecutor = newSingleThreadExecutor(
112 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
113
114 private final K8sApiConfigListener k8sApiConfigListener = new InternalK8sApiConfigListener();
115
116 private ApplicationId appId;
117 private NodeId localNode;
118
119 @Activate
120 protected void activate() {
121 appId = coreService.getAppId(APP_ID);
122 localNode = clusterService.getLocalNode().id();
123 leadershipService.runForLeadership(appId.name());
124 k8sApiConfigAdminService.addListener(k8sApiConfigListener);
125
126 log.info("Started");
127 }
128
129 @Deactivate
130 protected void deactivate() {
131 k8sApiConfigAdminService.removeListener(k8sApiConfigListener);
132 leadershipService.withdraw(appId.name());
133 eventExecutor.shutdown();
134
135 log.info("Stopped");
136 }
137
138 /**
139 * Checks the validity of the given kubernetes API server configuration.
140 *
141 * @param config kubernetes API server configuration
142 * @return validity result
143 */
144 private boolean checkApiServerConfig(K8sApiConfig config) {
145 KubernetesClient k8sClient = k8sClient(config);
146 return k8sClient != null && k8sClient.getApiVersion() != null;
147 }
148
149 private void bootstrapK8sNodes(K8sApiConfig config) {
150 KubernetesClient k8sClient = k8sClient(config);
151
152 if (k8sClient == null) {
153 log.warn("Failed to connect to kubernetes API server");
154 return;
155 }
156
Jian Li732c3422020-09-07 17:01:11 +0900157 for (Node node : k8sClient.nodes().list().getItems()) {
158 K8sNode k8sNode = buildK8sNode(node, config);
159 k8sNodeAdminService.createNode(k8sNode);
160
161 while (k8sNodeAdminService.node(k8sNode.hostname()).state() != ON_BOARDED) {
162 try {
163 sleep(SLEEP_MS);
164 } catch (InterruptedException e) {
165 log.error("Exception caused during on-boarding state checking...");
166 }
167
168 if (k8sNodeAdminService.node(k8sNode.hostname()).state() == ON_BOARDED) {
169 break;
170 }
171 }
172 }
Jian Li1cee9882019-02-13 11:25:25 +0900173 }
174
Jian Li077b07e2020-09-01 16:55:25 +0900175 private void bootstrapK8sHosts(K8sApiConfig config) {
176 KubernetesClient k8sClient = k8sClient(config);
177
178 if (k8sClient == null) {
179 log.warn("Failed to connect to kubernetes API server");
180 return;
181 }
182
183 config.infos().forEach(h -> {
184 k8sHostAdminService.createHost(buildK8sHost(h, config));
185 });
186
187 }
188
189 private K8sHost buildK8sHost(HostNodesInfo hostNodesInfo, K8sApiConfig config) {
190 int segmentId = config.segmentId();
Jian Li019ce6a2020-09-09 10:23:21 +0900191 K8sTunnelBridge tBridge = new K8sTunnelBridge(segmentId);
192 K8sRouterBridge rBridge = new K8sRouterBridge(segmentId);
Jian Li077b07e2020-09-01 16:55:25 +0900193
194 return DefaultK8sHost.builder()
195 .hostIp(hostNodesInfo.hostIp())
196 .state(K8sHostState.INIT)
Jian Li019ce6a2020-09-09 10:23:21 +0900197 .tunBridges(ImmutableSet.of(tBridge))
198 .routerBridges(ImmutableSet.of(rBridge))
Jian Li077b07e2020-09-01 16:55:25 +0900199 .nodeNames(hostNodesInfo.nodes())
200 .build();
201 }
202
Jian Lie2a04ce2020-07-01 19:07:02 +0900203 private K8sNode buildK8sNode(Node node, K8sApiConfig config) {
Jian Li1cee9882019-02-13 11:25:25 +0900204 String hostname = node.getMetadata().getName();
205 IpAddress managementIp = null;
206 IpAddress dataIp = null;
Jian Li4294af72020-10-07 02:12:33 +0900207 IpAddress nodeIp = null;
Jian Li1cee9882019-02-13 11:25:25 +0900208
Jian Lie2a04ce2020-07-01 19:07:02 +0900209 // pass-through mode: we use host IP as the management and data IP
210 // normal mode: we use K8S node's internal IP as the management and data IP
211 if (config.mode() == PASSTHROUGH) {
212 HostNodesInfo info = config.infos().stream().filter(h -> h.nodes()
213 .contains(hostname)).findAny().orElse(null);
214 if (info == null) {
215 log.error("None of the nodes were found in the host nodes info mapping list");
216 } else {
217 managementIp = info.hostIp();
218 dataIp = info.hostIp();
219 }
Jian Li4294af72020-10-07 02:12:33 +0900220 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
221 if (nodeAddress.getType().equals(INTERNAL_IP)) {
222 nodeIp = IpAddress.valueOf(nodeAddress.getAddress());
223 }
224 }
Jian Lie2a04ce2020-07-01 19:07:02 +0900225 } else {
226 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
227 if (nodeAddress.getType().equals(INTERNAL_IP)) {
228 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
229 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
Jian Li4294af72020-10-07 02:12:33 +0900230 nodeIp = IpAddress.valueOf(nodeAddress.getAddress());
Jian Lie2a04ce2020-07-01 19:07:02 +0900231 }
Jian Li1cee9882019-02-13 11:25:25 +0900232 }
233 }
234
235 String roleStr = node.getMetadata().getLabels().keySet().stream()
236 .filter(l -> l.contains(K8S_ROLE))
237 .findFirst().orElse(null);
238
Jian Li8a988042019-05-03 23:41:19 +0900239 K8sNode.Type nodeType = MINION;
Jian Li1cee9882019-02-13 11:25:25 +0900240
241 if (roleStr != null) {
242 String role = roleStr.split("/")[1];
243 if (MASTER.name().equalsIgnoreCase(role)) {
244 nodeType = MASTER;
245 } else {
246 nodeType = MINION;
247 }
248 }
249
Jian Li0c632722019-05-08 15:58:04 +0900250 Map<String, String> annots = node.getMetadata().getAnnotations();
251
Jian Lie2a04ce2020-07-01 19:07:02 +0900252 String extIntf = "";
253 String extGatewayIpStr = DEFAULT_GATEWAY_IP;
254 String extBridgeIpStr = DEFAULT_BRIDGE_IP;
255
256 if (config.mode() == PASSTHROUGH) {
257 extNetworkService.registerNetwork(config.extNetworkCidr());
258 extIntf = EXTERNAL_TO_ROUTER + "-" + config.clusterShortName();
259 IpAddress gatewayIp = extNetworkService.getGatewayIp(config.extNetworkCidr());
260 IpAddress bridgeIp = extNetworkService.allocateIp(config.extNetworkCidr());
261 if (gatewayIp != null) {
262 extGatewayIpStr = gatewayIp.toString();
263 }
264 if (bridgeIp != null) {
265 extBridgeIpStr = bridgeIp.toString();
266 }
267 } else {
268 extIntf = annots.get(EXT_INTF_NAME);
269 extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
270 extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
271 }
Jian Li0c632722019-05-08 15:58:04 +0900272
Jian Lic2242bd2020-09-03 13:12:14 +0900273 K8sNode.Builder builder = DefaultK8sNode.builder()
Jian Lie2a04ce2020-07-01 19:07:02 +0900274 .clusterName(DEFAULT_CLUSTER_NAME)
Jian Li1cee9882019-02-13 11:25:25 +0900275 .hostname(hostname)
276 .managementIp(managementIp)
277 .dataIp(dataIp)
Jian Li6d2ffbf2020-11-04 15:58:18 +0900278 .nodeInfo(new K8sNodeInfo(nodeIp, null))
Jian Li0c632722019-05-08 15:58:04 +0900279 .extIntf(extIntf)
280 .type(nodeType)
Jian Lie2a04ce2020-07-01 19:07:02 +0900281 .segmentId(config.segmentId())
Jian Li0ee8d0e2019-12-18 11:35:05 +0900282 .state(PRE_ON_BOARD)
Jian Lie2a04ce2020-07-01 19:07:02 +0900283 .mode(config.mode())
Jian Li0c632722019-05-08 15:58:04 +0900284 .extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
285 .extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
Jian Lic2242bd2020-09-03 13:12:14 +0900286 .podCidr(node.getSpec().getPodCIDR());
287
288 if (config.dvr()) {
289 builder.extGatewayMac(MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC));
290 }
291
292 return builder.build();
Jian Li1cee9882019-02-13 11:25:25 +0900293 }
294
295 /**
296 * An internal kubernetes API server config listener.
297 * The notification is triggered by K8sApiConfigStore.
298 */
299 private class InternalK8sApiConfigListener implements K8sApiConfigListener {
300
301 private boolean isRelevantHelper() {
302 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
303 }
304
305 @Override
306 public void event(K8sApiConfigEvent event) {
307
308 switch (event.type()) {
309 case K8S_API_CONFIG_CREATED:
310 eventExecutor.execute(() -> processConfigCreation(event.subject()));
311 break;
312 default:
313 break;
314 }
315 }
316
317 private void processConfigCreation(K8sApiConfig config) {
318 if (!isRelevantHelper()) {
319 return;
320 }
321
322 if (checkApiServerConfig(config)) {
323 K8sApiConfig newConfig = config.updateState(K8sApiConfig.State.CONNECTED);
324 k8sApiConfigAdminService.updateApiConfig(newConfig);
Jian Lie2a04ce2020-07-01 19:07:02 +0900325
Jian Li1cee9882019-02-13 11:25:25 +0900326 bootstrapK8sNodes(config);
Jian Li077b07e2020-09-01 16:55:25 +0900327
Jian Li732c3422020-09-07 17:01:11 +0900328 if (config.infos().size() > 0) {
329 bootstrapK8sHosts(config);
Jian Li077b07e2020-09-01 16:55:25 +0900330 }
Jian Li1cee9882019-02-13 11:25:25 +0900331 }
332 }
333 }
334}