blob: cf78b6eeb128cde6bb458013e0c84de19aaf9e2b [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 Li8685dd32020-09-01 16:55:25 +090042import org.onosproject.k8snode.api.K8sTunnelBridge;
Jian Li1cee9882019-02-13 11:25:25 +090043import org.osgi.service.component.annotations.Activate;
44import org.osgi.service.component.annotations.Component;
45import org.osgi.service.component.annotations.Deactivate;
46import org.osgi.service.component.annotations.Reference;
47import org.osgi.service.component.annotations.ReferenceCardinality;
48import org.slf4j.Logger;
49
Jian Li7709eb42019-05-08 15:58:04 +090050import java.util.Map;
Jian Li1cee9882019-02-13 11:25:25 +090051import java.util.Objects;
52import java.util.concurrent.ExecutorService;
53
Jian Li8685dd32020-09-01 16:55:25 +090054import static java.lang.Thread.sleep;
Jian Li1cee9882019-02-13 11:25:25 +090055import static java.util.concurrent.Executors.newSingleThreadExecutor;
56import static org.onlab.util.Tools.groupedThreads;
Jian Li58b33982020-07-01 19:07:02 +090057import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
Jian Lib1218442020-09-03 13:12:14 +090058import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
Jian Li58b33982020-07-01 19:07:02 +090059import static org.onosproject.k8snode.api.Constants.EXTERNAL_TO_ROUTER;
60import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
Jian Li1cee9882019-02-13 11:25:25 +090061import static org.onosproject.k8snode.api.K8sNode.Type.MASTER;
62import static org.onosproject.k8snode.api.K8sNode.Type.MINION;
63import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
Jian Li3cb86e32020-09-07 17:01:11 +090064import static org.onosproject.k8snode.api.K8sNodeState.ON_BOARDED;
Jian Li77af8f32019-12-18 11:35:05 +090065import static org.onosproject.k8snode.api.K8sNodeState.PRE_ON_BOARD;
Jian Li1cee9882019-02-13 11:25:25 +090066import static org.onosproject.k8snode.util.K8sNodeUtil.k8sClient;
67import static org.slf4j.LoggerFactory.getLogger;
68
69/**
70 * Handles the state of kubernetes API server configuration.
71 */
72@Component(immediate = true)
73public class DefaultK8sApiConfigHandler {
74
75 private final Logger log = getLogger(getClass());
76
77 private static final String INTERNAL_IP = "InternalIP";
78 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li7709eb42019-05-08 15:58:04 +090079 private static final String EXT_BRIDGE_IP = "external.bridge.ip";
80 private static final String EXT_GATEWAY_IP = "external.gateway.ip";
81 private static final String EXT_INTF_NAME = "external.interface.name";
Jian Li1cee9882019-02-13 11:25:25 +090082
Jian Li58b33982020-07-01 19:07:02 +090083 private static final String DEFAULT_GATEWAY_IP = "127.0.0.1";
84 private static final String DEFAULT_BRIDGE_IP = "127.0.0.1";
85
Jian Li3cb86e32020-09-07 17:01:11 +090086 private static final long SLEEP_MS = 10000; // we wait 10s
Jian Li8685dd32020-09-01 16:55:25 +090087
Jian Li1cee9882019-02-13 11:25:25 +090088 @Reference(cardinality = ReferenceCardinality.MANDATORY)
89 protected CoreService coreService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY)
92 protected LeadershipService leadershipService;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY)
95 protected ClusterService clusterService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY)
98 protected K8sApiConfigAdminService k8sApiConfigAdminService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY)
101 protected K8sNodeAdminService k8sNodeAdminService;
102
Jian Li58b33982020-07-01 19:07:02 +0900103 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li8685dd32020-09-01 16:55:25 +0900104 protected K8sHostAdminService k8sHostAdminService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li58b33982020-07-01 19:07:02 +0900107 protected ExternalNetworkService extNetworkService;
108
Jian Li1cee9882019-02-13 11:25:25 +0900109 private final ExecutorService eventExecutor = newSingleThreadExecutor(
110 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
111
112 private final K8sApiConfigListener k8sApiConfigListener = new InternalK8sApiConfigListener();
113
114 private ApplicationId appId;
115 private NodeId localNode;
116
117 @Activate
118 protected void activate() {
119 appId = coreService.getAppId(APP_ID);
120 localNode = clusterService.getLocalNode().id();
121 leadershipService.runForLeadership(appId.name());
122 k8sApiConfigAdminService.addListener(k8sApiConfigListener);
123
124 log.info("Started");
125 }
126
127 @Deactivate
128 protected void deactivate() {
129 k8sApiConfigAdminService.removeListener(k8sApiConfigListener);
130 leadershipService.withdraw(appId.name());
131 eventExecutor.shutdown();
132
133 log.info("Stopped");
134 }
135
136 /**
137 * Checks the validity of the given kubernetes API server configuration.
138 *
139 * @param config kubernetes API server configuration
140 * @return validity result
141 */
142 private boolean checkApiServerConfig(K8sApiConfig config) {
143 KubernetesClient k8sClient = k8sClient(config);
144 return k8sClient != null && k8sClient.getApiVersion() != null;
145 }
146
147 private void bootstrapK8sNodes(K8sApiConfig config) {
148 KubernetesClient k8sClient = k8sClient(config);
149
150 if (k8sClient == null) {
151 log.warn("Failed to connect to kubernetes API server");
152 return;
153 }
154
Jian Li3cb86e32020-09-07 17:01:11 +0900155 for (Node node : k8sClient.nodes().list().getItems()) {
156 K8sNode k8sNode = buildK8sNode(node, config);
157 k8sNodeAdminService.createNode(k8sNode);
158
159 while (k8sNodeAdminService.node(k8sNode.hostname()).state() != ON_BOARDED) {
160 try {
161 sleep(SLEEP_MS);
162 } catch (InterruptedException e) {
163 log.error("Exception caused during on-boarding state checking...");
164 }
165
166 if (k8sNodeAdminService.node(k8sNode.hostname()).state() == ON_BOARDED) {
167 break;
168 }
169 }
170 }
Jian Li1cee9882019-02-13 11:25:25 +0900171 }
172
Jian Li8685dd32020-09-01 16:55:25 +0900173 private void bootstrapK8sHosts(K8sApiConfig config) {
174 KubernetesClient k8sClient = k8sClient(config);
175
176 if (k8sClient == null) {
177 log.warn("Failed to connect to kubernetes API server");
178 return;
179 }
180
181 config.infos().forEach(h -> {
182 k8sHostAdminService.createHost(buildK8sHost(h, config));
183 });
184
185 }
186
187 private K8sHost buildK8sHost(HostNodesInfo hostNodesInfo, K8sApiConfig config) {
188 int segmentId = config.segmentId();
189 K8sTunnelBridge bridge = new K8sTunnelBridge(segmentId);
190
191 return DefaultK8sHost.builder()
192 .hostIp(hostNodesInfo.hostIp())
193 .state(K8sHostState.INIT)
194 .tunBridges(ImmutableSet.of(bridge))
195 .nodeNames(hostNodesInfo.nodes())
196 .build();
197 }
198
Jian Li58b33982020-07-01 19:07:02 +0900199 private K8sNode buildK8sNode(Node node, K8sApiConfig config) {
Jian Li1cee9882019-02-13 11:25:25 +0900200 String hostname = node.getMetadata().getName();
201 IpAddress managementIp = null;
202 IpAddress dataIp = null;
203
Jian Li58b33982020-07-01 19:07:02 +0900204 // pass-through mode: we use host IP as the management and data IP
205 // normal mode: we use K8S node's internal IP as the management and data IP
206 if (config.mode() == PASSTHROUGH) {
207 HostNodesInfo info = config.infos().stream().filter(h -> h.nodes()
208 .contains(hostname)).findAny().orElse(null);
209 if (info == null) {
210 log.error("None of the nodes were found in the host nodes info mapping list");
211 } else {
212 managementIp = info.hostIp();
213 dataIp = info.hostIp();
214 }
215 } else {
216 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
217 if (nodeAddress.getType().equals(INTERNAL_IP)) {
218 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
219 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
220 }
Jian Li1cee9882019-02-13 11:25:25 +0900221 }
222 }
223
224 String roleStr = node.getMetadata().getLabels().keySet().stream()
225 .filter(l -> l.contains(K8S_ROLE))
226 .findFirst().orElse(null);
227
Jian Li8a988042019-05-03 23:41:19 +0900228 K8sNode.Type nodeType = MINION;
Jian Li1cee9882019-02-13 11:25:25 +0900229
230 if (roleStr != null) {
231 String role = roleStr.split("/")[1];
232 if (MASTER.name().equalsIgnoreCase(role)) {
233 nodeType = MASTER;
234 } else {
235 nodeType = MINION;
236 }
237 }
238
Jian Li7709eb42019-05-08 15:58:04 +0900239 Map<String, String> annots = node.getMetadata().getAnnotations();
240
Jian Li58b33982020-07-01 19:07:02 +0900241 String extIntf = "";
242 String extGatewayIpStr = DEFAULT_GATEWAY_IP;
243 String extBridgeIpStr = DEFAULT_BRIDGE_IP;
244
245 if (config.mode() == PASSTHROUGH) {
246 extNetworkService.registerNetwork(config.extNetworkCidr());
247 extIntf = EXTERNAL_TO_ROUTER + "-" + config.clusterShortName();
248 IpAddress gatewayIp = extNetworkService.getGatewayIp(config.extNetworkCidr());
249 IpAddress bridgeIp = extNetworkService.allocateIp(config.extNetworkCidr());
250 if (gatewayIp != null) {
251 extGatewayIpStr = gatewayIp.toString();
252 }
253 if (bridgeIp != null) {
254 extBridgeIpStr = bridgeIp.toString();
255 }
256 } else {
257 extIntf = annots.get(EXT_INTF_NAME);
258 extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
259 extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
260 }
Jian Li7709eb42019-05-08 15:58:04 +0900261
Jian Lib1218442020-09-03 13:12:14 +0900262 K8sNode.Builder builder = DefaultK8sNode.builder()
Jian Li58b33982020-07-01 19:07:02 +0900263 .clusterName(DEFAULT_CLUSTER_NAME)
Jian Li1cee9882019-02-13 11:25:25 +0900264 .hostname(hostname)
265 .managementIp(managementIp)
266 .dataIp(dataIp)
Jian Li7709eb42019-05-08 15:58:04 +0900267 .extIntf(extIntf)
268 .type(nodeType)
Jian Li58b33982020-07-01 19:07:02 +0900269 .segmentId(config.segmentId())
Jian Li77af8f32019-12-18 11:35:05 +0900270 .state(PRE_ON_BOARD)
Jian Li58b33982020-07-01 19:07:02 +0900271 .mode(config.mode())
Jian Li7709eb42019-05-08 15:58:04 +0900272 .extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
273 .extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
Jian Lib1218442020-09-03 13:12:14 +0900274 .podCidr(node.getSpec().getPodCIDR());
275
276 if (config.dvr()) {
277 builder.extGatewayMac(MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC));
278 }
279
280 return builder.build();
Jian Li1cee9882019-02-13 11:25:25 +0900281 }
282
283 /**
284 * An internal kubernetes API server config listener.
285 * The notification is triggered by K8sApiConfigStore.
286 */
287 private class InternalK8sApiConfigListener implements K8sApiConfigListener {
288
289 private boolean isRelevantHelper() {
290 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
291 }
292
293 @Override
294 public void event(K8sApiConfigEvent event) {
295
296 switch (event.type()) {
297 case K8S_API_CONFIG_CREATED:
298 eventExecutor.execute(() -> processConfigCreation(event.subject()));
299 break;
300 default:
301 break;
302 }
303 }
304
305 private void processConfigCreation(K8sApiConfig config) {
306 if (!isRelevantHelper()) {
307 return;
308 }
309
310 if (checkApiServerConfig(config)) {
311 K8sApiConfig newConfig = config.updateState(K8sApiConfig.State.CONNECTED);
312 k8sApiConfigAdminService.updateApiConfig(newConfig);
Jian Li58b33982020-07-01 19:07:02 +0900313
Jian Li1cee9882019-02-13 11:25:25 +0900314 bootstrapK8sNodes(config);
Jian Li8685dd32020-09-01 16:55:25 +0900315
Jian Li3cb86e32020-09-07 17:01:11 +0900316 if (config.infos().size() > 0) {
317 bootstrapK8sHosts(config);
Jian Li8685dd32020-09-01 16:55:25 +0900318 }
Jian Li1cee9882019-02-13 11:25:25 +0900319 }
320 }
321 }
322}