blob: 30606f07b1aa8fe06d625545591e0a67b07086bf [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 Li8685dd32020-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 Lib1218442020-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 Li8685dd32020-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 Li58b33982020-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 Li8685dd32020-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 Li4860e372020-09-09 10:23:21 +090042import org.onosproject.k8snode.api.K8sRouterBridge;
Jian Li8685dd32020-09-01 16:55:25 +090043import org.onosproject.k8snode.api.K8sTunnelBridge;
Jian Li1cee9882019-02-13 11:25:25 +090044import org.osgi.service.component.annotations.Activate;
45import org.osgi.service.component.annotations.Component;
46import org.osgi.service.component.annotations.Deactivate;
47import org.osgi.service.component.annotations.Reference;
48import org.osgi.service.component.annotations.ReferenceCardinality;
49import org.slf4j.Logger;
50
Jian Li7709eb42019-05-08 15:58:04 +090051import java.util.Map;
Jian Li1cee9882019-02-13 11:25:25 +090052import java.util.Objects;
53import java.util.concurrent.ExecutorService;
54
Jian Li8685dd32020-09-01 16:55:25 +090055import static java.lang.Thread.sleep;
Jian Li1cee9882019-02-13 11:25:25 +090056import static java.util.concurrent.Executors.newSingleThreadExecutor;
57import static org.onlab.util.Tools.groupedThreads;
Jian Li58b33982020-07-01 19:07:02 +090058import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
Jian Lib1218442020-09-03 13:12:14 +090059import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
Jian Li58b33982020-07-01 19:07:02 +090060import static org.onosproject.k8snode.api.Constants.EXTERNAL_TO_ROUTER;
61import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
Jian Li1cee9882019-02-13 11:25:25 +090062import static org.onosproject.k8snode.api.K8sNode.Type.MASTER;
63import static org.onosproject.k8snode.api.K8sNode.Type.MINION;
64import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
Jian Li3cb86e32020-09-07 17:01:11 +090065import static org.onosproject.k8snode.api.K8sNodeState.ON_BOARDED;
Jian Li77af8f32019-12-18 11:35:05 +090066import static org.onosproject.k8snode.api.K8sNodeState.PRE_ON_BOARD;
Jian Li1cee9882019-02-13 11:25:25 +090067import static org.onosproject.k8snode.util.K8sNodeUtil.k8sClient;
68import static org.slf4j.LoggerFactory.getLogger;
69
70/**
71 * Handles the state of kubernetes API server configuration.
72 */
73@Component(immediate = true)
74public class DefaultK8sApiConfigHandler {
75
76 private final Logger log = getLogger(getClass());
77
78 private static final String INTERNAL_IP = "InternalIP";
79 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li7709eb42019-05-08 15:58:04 +090080 private static final String EXT_BRIDGE_IP = "external.bridge.ip";
81 private static final String EXT_GATEWAY_IP = "external.gateway.ip";
82 private static final String EXT_INTF_NAME = "external.interface.name";
Jian Li1cee9882019-02-13 11:25:25 +090083
Jian Li58b33982020-07-01 19:07:02 +090084 private static final String DEFAULT_GATEWAY_IP = "127.0.0.1";
85 private static final String DEFAULT_BRIDGE_IP = "127.0.0.1";
86
Jian Li3cb86e32020-09-07 17:01:11 +090087 private static final long SLEEP_MS = 10000; // we wait 10s
Jian Li8685dd32020-09-01 16:55:25 +090088
Jian Li1cee9882019-02-13 11:25:25 +090089 @Reference(cardinality = ReferenceCardinality.MANDATORY)
90 protected CoreService coreService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY)
93 protected LeadershipService leadershipService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY)
96 protected ClusterService clusterService;
97
98 @Reference(cardinality = ReferenceCardinality.MANDATORY)
99 protected K8sApiConfigAdminService k8sApiConfigAdminService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY)
102 protected K8sNodeAdminService k8sNodeAdminService;
103
Jian Li58b33982020-07-01 19:07:02 +0900104 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li8685dd32020-09-01 16:55:25 +0900105 protected K8sHostAdminService k8sHostAdminService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li58b33982020-07-01 19:07:02 +0900108 protected ExternalNetworkService extNetworkService;
109
Jian Li1cee9882019-02-13 11:25:25 +0900110 private final ExecutorService eventExecutor = newSingleThreadExecutor(
111 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
112
113 private final K8sApiConfigListener k8sApiConfigListener = new InternalK8sApiConfigListener();
114
115 private ApplicationId appId;
116 private NodeId localNode;
117
118 @Activate
119 protected void activate() {
120 appId = coreService.getAppId(APP_ID);
121 localNode = clusterService.getLocalNode().id();
122 leadershipService.runForLeadership(appId.name());
123 k8sApiConfigAdminService.addListener(k8sApiConfigListener);
124
125 log.info("Started");
126 }
127
128 @Deactivate
129 protected void deactivate() {
130 k8sApiConfigAdminService.removeListener(k8sApiConfigListener);
131 leadershipService.withdraw(appId.name());
132 eventExecutor.shutdown();
133
134 log.info("Stopped");
135 }
136
137 /**
138 * Checks the validity of the given kubernetes API server configuration.
139 *
140 * @param config kubernetes API server configuration
141 * @return validity result
142 */
143 private boolean checkApiServerConfig(K8sApiConfig config) {
144 KubernetesClient k8sClient = k8sClient(config);
145 return k8sClient != null && k8sClient.getApiVersion() != null;
146 }
147
148 private void bootstrapK8sNodes(K8sApiConfig config) {
149 KubernetesClient k8sClient = k8sClient(config);
150
151 if (k8sClient == null) {
152 log.warn("Failed to connect to kubernetes API server");
153 return;
154 }
155
Jian Li3cb86e32020-09-07 17:01:11 +0900156 for (Node node : k8sClient.nodes().list().getItems()) {
157 K8sNode k8sNode = buildK8sNode(node, config);
158 k8sNodeAdminService.createNode(k8sNode);
159
160 while (k8sNodeAdminService.node(k8sNode.hostname()).state() != ON_BOARDED) {
161 try {
162 sleep(SLEEP_MS);
163 } catch (InterruptedException e) {
164 log.error("Exception caused during on-boarding state checking...");
165 }
166
167 if (k8sNodeAdminService.node(k8sNode.hostname()).state() == ON_BOARDED) {
168 break;
169 }
170 }
171 }
Jian Li1cee9882019-02-13 11:25:25 +0900172 }
173
Jian Li8685dd32020-09-01 16:55:25 +0900174 private void bootstrapK8sHosts(K8sApiConfig config) {
175 KubernetesClient k8sClient = k8sClient(config);
176
177 if (k8sClient == null) {
178 log.warn("Failed to connect to kubernetes API server");
179 return;
180 }
181
182 config.infos().forEach(h -> {
183 k8sHostAdminService.createHost(buildK8sHost(h, config));
184 });
185
186 }
187
188 private K8sHost buildK8sHost(HostNodesInfo hostNodesInfo, K8sApiConfig config) {
189 int segmentId = config.segmentId();
Jian Li4860e372020-09-09 10:23:21 +0900190 K8sTunnelBridge tBridge = new K8sTunnelBridge(segmentId);
191 K8sRouterBridge rBridge = new K8sRouterBridge(segmentId);
Jian Li8685dd32020-09-01 16:55:25 +0900192
193 return DefaultK8sHost.builder()
194 .hostIp(hostNodesInfo.hostIp())
195 .state(K8sHostState.INIT)
Jian Li4860e372020-09-09 10:23:21 +0900196 .tunBridges(ImmutableSet.of(tBridge))
197 .routerBridges(ImmutableSet.of(rBridge))
Jian Li8685dd32020-09-01 16:55:25 +0900198 .nodeNames(hostNodesInfo.nodes())
199 .build();
200 }
201
Jian Li58b33982020-07-01 19:07:02 +0900202 private K8sNode buildK8sNode(Node node, K8sApiConfig config) {
Jian Li1cee9882019-02-13 11:25:25 +0900203 String hostname = node.getMetadata().getName();
204 IpAddress managementIp = null;
205 IpAddress dataIp = null;
Jian Li9bc67772020-10-07 02:12:33 +0900206 IpAddress nodeIp = null;
Jian Li1cee9882019-02-13 11:25:25 +0900207
Jian Li58b33982020-07-01 19:07:02 +0900208 // pass-through mode: we use host IP as the management and data IP
209 // normal mode: we use K8S node's internal IP as the management and data IP
210 if (config.mode() == PASSTHROUGH) {
211 HostNodesInfo info = config.infos().stream().filter(h -> h.nodes()
212 .contains(hostname)).findAny().orElse(null);
213 if (info == null) {
214 log.error("None of the nodes were found in the host nodes info mapping list");
215 } else {
216 managementIp = info.hostIp();
217 dataIp = info.hostIp();
218 }
Jian Li9bc67772020-10-07 02:12:33 +0900219 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
220 if (nodeAddress.getType().equals(INTERNAL_IP)) {
221 nodeIp = IpAddress.valueOf(nodeAddress.getAddress());
222 }
223 }
Jian Li58b33982020-07-01 19:07:02 +0900224 } else {
225 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
226 if (nodeAddress.getType().equals(INTERNAL_IP)) {
227 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
228 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
Jian Li9bc67772020-10-07 02:12:33 +0900229 nodeIp = IpAddress.valueOf(nodeAddress.getAddress());
Jian Li58b33982020-07-01 19:07:02 +0900230 }
Jian Li1cee9882019-02-13 11:25:25 +0900231 }
232 }
233
234 String roleStr = node.getMetadata().getLabels().keySet().stream()
235 .filter(l -> l.contains(K8S_ROLE))
236 .findFirst().orElse(null);
237
Jian Li8a988042019-05-03 23:41:19 +0900238 K8sNode.Type nodeType = MINION;
Jian Li1cee9882019-02-13 11:25:25 +0900239
240 if (roleStr != null) {
241 String role = roleStr.split("/")[1];
242 if (MASTER.name().equalsIgnoreCase(role)) {
243 nodeType = MASTER;
244 } else {
245 nodeType = MINION;
246 }
247 }
248
Jian Li7709eb42019-05-08 15:58:04 +0900249 Map<String, String> annots = node.getMetadata().getAnnotations();
250
Jian Li58b33982020-07-01 19:07:02 +0900251 String extIntf = "";
252 String extGatewayIpStr = DEFAULT_GATEWAY_IP;
253 String extBridgeIpStr = DEFAULT_BRIDGE_IP;
254
255 if (config.mode() == PASSTHROUGH) {
256 extNetworkService.registerNetwork(config.extNetworkCidr());
257 extIntf = EXTERNAL_TO_ROUTER + "-" + config.clusterShortName();
258 IpAddress gatewayIp = extNetworkService.getGatewayIp(config.extNetworkCidr());
259 IpAddress bridgeIp = extNetworkService.allocateIp(config.extNetworkCidr());
260 if (gatewayIp != null) {
261 extGatewayIpStr = gatewayIp.toString();
262 }
263 if (bridgeIp != null) {
264 extBridgeIpStr = bridgeIp.toString();
265 }
266 } else {
267 extIntf = annots.get(EXT_INTF_NAME);
268 extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
269 extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
270 }
Jian Li7709eb42019-05-08 15:58:04 +0900271
Jian Lib1218442020-09-03 13:12:14 +0900272 K8sNode.Builder builder = DefaultK8sNode.builder()
Jian Li58b33982020-07-01 19:07:02 +0900273 .clusterName(DEFAULT_CLUSTER_NAME)
Jian Li1cee9882019-02-13 11:25:25 +0900274 .hostname(hostname)
275 .managementIp(managementIp)
276 .dataIp(dataIp)
Jian Li9bc67772020-10-07 02:12:33 +0900277 .nodeIp(nodeIp)
Jian Li7709eb42019-05-08 15:58:04 +0900278 .extIntf(extIntf)
279 .type(nodeType)
Jian Li58b33982020-07-01 19:07:02 +0900280 .segmentId(config.segmentId())
Jian Li77af8f32019-12-18 11:35:05 +0900281 .state(PRE_ON_BOARD)
Jian Li58b33982020-07-01 19:07:02 +0900282 .mode(config.mode())
Jian Li7709eb42019-05-08 15:58:04 +0900283 .extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
284 .extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
Jian Lib1218442020-09-03 13:12:14 +0900285 .podCidr(node.getSpec().getPodCIDR());
286
287 if (config.dvr()) {
288 builder.extGatewayMac(MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC));
289 }
290
291 return builder.build();
Jian Li1cee9882019-02-13 11:25:25 +0900292 }
293
294 /**
295 * An internal kubernetes API server config listener.
296 * The notification is triggered by K8sApiConfigStore.
297 */
298 private class InternalK8sApiConfigListener implements K8sApiConfigListener {
299
300 private boolean isRelevantHelper() {
301 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
302 }
303
304 @Override
305 public void event(K8sApiConfigEvent event) {
306
307 switch (event.type()) {
308 case K8S_API_CONFIG_CREATED:
309 eventExecutor.execute(() -> processConfigCreation(event.subject()));
310 break;
311 default:
312 break;
313 }
314 }
315
316 private void processConfigCreation(K8sApiConfig config) {
317 if (!isRelevantHelper()) {
318 return;
319 }
320
321 if (checkApiServerConfig(config)) {
322 K8sApiConfig newConfig = config.updateState(K8sApiConfig.State.CONNECTED);
323 k8sApiConfigAdminService.updateApiConfig(newConfig);
Jian Li58b33982020-07-01 19:07:02 +0900324
Jian Li1cee9882019-02-13 11:25:25 +0900325 bootstrapK8sNodes(config);
Jian Li8685dd32020-09-01 16:55:25 +0900326
Jian Li3cb86e32020-09-07 17:01:11 +0900327 if (config.infos().size() > 0) {
328 bootstrapK8sHosts(config);
Jian Li8685dd32020-09-01 16:55:25 +0900329 }
Jian Li1cee9882019-02-13 11:25:25 +0900330 }
331 }
332 }
333}