blob: bf81f85a3464200dc2e1402a809f7ea0c7226dfe [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 Li019ce6a2020-09-09 10:23:21 +090042import org.onosproject.k8snode.api.K8sRouterBridge;
Jian Li077b07e2020-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 Li0c632722019-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 Li077b07e2020-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 Lie2a04ce2020-07-01 19:07:02 +090058import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
Jian Lic2242bd2020-09-03 13:12:14 +090059import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
Jian Lie2a04ce2020-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 Li732c3422020-09-07 17:01:11 +090065import static org.onosproject.k8snode.api.K8sNodeState.ON_BOARDED;
Jian Li0ee8d0e2019-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 Li0c632722019-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 Lie2a04ce2020-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 Li732c3422020-09-07 17:01:11 +090087 private static final long SLEEP_MS = 10000; // we wait 10s
Jian Li077b07e2020-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 Lie2a04ce2020-07-01 19:07:02 +0900104 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li077b07e2020-09-01 16:55:25 +0900105 protected K8sHostAdminService k8sHostAdminService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lie2a04ce2020-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 Li732c3422020-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 Li077b07e2020-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 Li019ce6a2020-09-09 10:23:21 +0900190 K8sTunnelBridge tBridge = new K8sTunnelBridge(segmentId);
191 K8sRouterBridge rBridge = new K8sRouterBridge(segmentId);
Jian Li077b07e2020-09-01 16:55:25 +0900192
193 return DefaultK8sHost.builder()
194 .hostIp(hostNodesInfo.hostIp())
195 .state(K8sHostState.INIT)
Jian Li019ce6a2020-09-09 10:23:21 +0900196 .tunBridges(ImmutableSet.of(tBridge))
197 .routerBridges(ImmutableSet.of(rBridge))
Jian Li077b07e2020-09-01 16:55:25 +0900198 .nodeNames(hostNodesInfo.nodes())
199 .build();
200 }
201
Jian Lie2a04ce2020-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;
206
Jian Lie2a04ce2020-07-01 19:07:02 +0900207 // pass-through mode: we use host IP as the management and data IP
208 // normal mode: we use K8S node's internal IP as the management and data IP
209 if (config.mode() == PASSTHROUGH) {
210 HostNodesInfo info = config.infos().stream().filter(h -> h.nodes()
211 .contains(hostname)).findAny().orElse(null);
212 if (info == null) {
213 log.error("None of the nodes were found in the host nodes info mapping list");
214 } else {
215 managementIp = info.hostIp();
216 dataIp = info.hostIp();
217 }
218 } else {
219 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
220 if (nodeAddress.getType().equals(INTERNAL_IP)) {
221 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
222 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
223 }
Jian Li1cee9882019-02-13 11:25:25 +0900224 }
225 }
226
227 String roleStr = node.getMetadata().getLabels().keySet().stream()
228 .filter(l -> l.contains(K8S_ROLE))
229 .findFirst().orElse(null);
230
Jian Li8a988042019-05-03 23:41:19 +0900231 K8sNode.Type nodeType = MINION;
Jian Li1cee9882019-02-13 11:25:25 +0900232
233 if (roleStr != null) {
234 String role = roleStr.split("/")[1];
235 if (MASTER.name().equalsIgnoreCase(role)) {
236 nodeType = MASTER;
237 } else {
238 nodeType = MINION;
239 }
240 }
241
Jian Li0c632722019-05-08 15:58:04 +0900242 Map<String, String> annots = node.getMetadata().getAnnotations();
243
Jian Lie2a04ce2020-07-01 19:07:02 +0900244 String extIntf = "";
245 String extGatewayIpStr = DEFAULT_GATEWAY_IP;
246 String extBridgeIpStr = DEFAULT_BRIDGE_IP;
247
248 if (config.mode() == PASSTHROUGH) {
249 extNetworkService.registerNetwork(config.extNetworkCidr());
250 extIntf = EXTERNAL_TO_ROUTER + "-" + config.clusterShortName();
251 IpAddress gatewayIp = extNetworkService.getGatewayIp(config.extNetworkCidr());
252 IpAddress bridgeIp = extNetworkService.allocateIp(config.extNetworkCidr());
253 if (gatewayIp != null) {
254 extGatewayIpStr = gatewayIp.toString();
255 }
256 if (bridgeIp != null) {
257 extBridgeIpStr = bridgeIp.toString();
258 }
259 } else {
260 extIntf = annots.get(EXT_INTF_NAME);
261 extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
262 extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
263 }
Jian Li0c632722019-05-08 15:58:04 +0900264
Jian Lic2242bd2020-09-03 13:12:14 +0900265 K8sNode.Builder builder = DefaultK8sNode.builder()
Jian Lie2a04ce2020-07-01 19:07:02 +0900266 .clusterName(DEFAULT_CLUSTER_NAME)
Jian Li1cee9882019-02-13 11:25:25 +0900267 .hostname(hostname)
268 .managementIp(managementIp)
269 .dataIp(dataIp)
Jian Li0c632722019-05-08 15:58:04 +0900270 .extIntf(extIntf)
271 .type(nodeType)
Jian Lie2a04ce2020-07-01 19:07:02 +0900272 .segmentId(config.segmentId())
Jian Li0ee8d0e2019-12-18 11:35:05 +0900273 .state(PRE_ON_BOARD)
Jian Lie2a04ce2020-07-01 19:07:02 +0900274 .mode(config.mode())
Jian Li0c632722019-05-08 15:58:04 +0900275 .extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
276 .extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
Jian Lic2242bd2020-09-03 13:12:14 +0900277 .podCidr(node.getSpec().getPodCIDR());
278
279 if (config.dvr()) {
280 builder.extGatewayMac(MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC));
281 }
282
283 return builder.build();
Jian Li1cee9882019-02-13 11:25:25 +0900284 }
285
286 /**
287 * An internal kubernetes API server config listener.
288 * The notification is triggered by K8sApiConfigStore.
289 */
290 private class InternalK8sApiConfigListener implements K8sApiConfigListener {
291
292 private boolean isRelevantHelper() {
293 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
294 }
295
296 @Override
297 public void event(K8sApiConfigEvent event) {
298
299 switch (event.type()) {
300 case K8S_API_CONFIG_CREATED:
301 eventExecutor.execute(() -> processConfigCreation(event.subject()));
302 break;
303 default:
304 break;
305 }
306 }
307
308 private void processConfigCreation(K8sApiConfig config) {
309 if (!isRelevantHelper()) {
310 return;
311 }
312
313 if (checkApiServerConfig(config)) {
314 K8sApiConfig newConfig = config.updateState(K8sApiConfig.State.CONNECTED);
315 k8sApiConfigAdminService.updateApiConfig(newConfig);
Jian Lie2a04ce2020-07-01 19:07:02 +0900316
Jian Li1cee9882019-02-13 11:25:25 +0900317 bootstrapK8sNodes(config);
Jian Li077b07e2020-09-01 16:55:25 +0900318
Jian Li732c3422020-09-07 17:01:11 +0900319 if (config.infos().size() > 0) {
320 bootstrapK8sHosts(config);
Jian Li077b07e2020-09-01 16:55:25 +0900321 }
Jian Li1cee9882019-02-13 11:25:25 +0900322 }
323 }
324 }
325}