blob: 342787c69d12dd5128454d70f143c74e60d304d1 [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 Li077b07e2020-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 Li0c632722019-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 Li077b07e2020-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 Lie2a04ce2020-07-01 19:07:02 +090057import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
Jian Lic2242bd2020-09-03 13:12:14 +090058import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
Jian Lie2a04ce2020-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 Li0ee8d0e2019-12-18 11:35:05 +090064import static org.onosproject.k8snode.api.K8sNodeState.PRE_ON_BOARD;
Jian Li1cee9882019-02-13 11:25:25 +090065import static org.onosproject.k8snode.util.K8sNodeUtil.k8sClient;
66import static org.slf4j.LoggerFactory.getLogger;
67
68/**
69 * Handles the state of kubernetes API server configuration.
70 */
71@Component(immediate = true)
72public class DefaultK8sApiConfigHandler {
73
74 private final Logger log = getLogger(getClass());
75
76 private static final String INTERNAL_IP = "InternalIP";
77 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li0c632722019-05-08 15:58:04 +090078 private static final String EXT_BRIDGE_IP = "external.bridge.ip";
79 private static final String EXT_GATEWAY_IP = "external.gateway.ip";
80 private static final String EXT_INTF_NAME = "external.interface.name";
Jian Li1cee9882019-02-13 11:25:25 +090081
Jian Lie2a04ce2020-07-01 19:07:02 +090082 private static final String DEFAULT_GATEWAY_IP = "127.0.0.1";
83 private static final String DEFAULT_BRIDGE_IP = "127.0.0.1";
84
Jian Li077b07e2020-09-01 16:55:25 +090085 private static final long SLEEP_MS = 3000; // we wait 3s
86
Jian Li1cee9882019-02-13 11:25:25 +090087 @Reference(cardinality = ReferenceCardinality.MANDATORY)
88 protected CoreService coreService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY)
91 protected LeadershipService leadershipService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY)
94 protected ClusterService clusterService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY)
97 protected K8sApiConfigAdminService k8sApiConfigAdminService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY)
100 protected K8sNodeAdminService k8sNodeAdminService;
101
Jian Lie2a04ce2020-07-01 19:07:02 +0900102 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li077b07e2020-09-01 16:55:25 +0900103 protected K8sHostAdminService k8sHostAdminService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lie2a04ce2020-07-01 19:07:02 +0900106 protected ExternalNetworkService extNetworkService;
107
Jian Li1cee9882019-02-13 11:25:25 +0900108 private final ExecutorService eventExecutor = newSingleThreadExecutor(
109 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
110
111 private final K8sApiConfigListener k8sApiConfigListener = new InternalK8sApiConfigListener();
112
113 private ApplicationId appId;
114 private NodeId localNode;
115
116 @Activate
117 protected void activate() {
118 appId = coreService.getAppId(APP_ID);
119 localNode = clusterService.getLocalNode().id();
120 leadershipService.runForLeadership(appId.name());
121 k8sApiConfigAdminService.addListener(k8sApiConfigListener);
122
123 log.info("Started");
124 }
125
126 @Deactivate
127 protected void deactivate() {
128 k8sApiConfigAdminService.removeListener(k8sApiConfigListener);
129 leadershipService.withdraw(appId.name());
130 eventExecutor.shutdown();
131
132 log.info("Stopped");
133 }
134
135 /**
136 * Checks the validity of the given kubernetes API server configuration.
137 *
138 * @param config kubernetes API server configuration
139 * @return validity result
140 */
141 private boolean checkApiServerConfig(K8sApiConfig config) {
142 KubernetesClient k8sClient = k8sClient(config);
143 return k8sClient != null && k8sClient.getApiVersion() != null;
144 }
145
146 private void bootstrapK8sNodes(K8sApiConfig config) {
147 KubernetesClient k8sClient = k8sClient(config);
148
149 if (k8sClient == null) {
150 log.warn("Failed to connect to kubernetes API server");
151 return;
152 }
153
154 k8sClient.nodes().list().getItems().forEach(n ->
Jian Lie2a04ce2020-07-01 19:07:02 +0900155 k8sNodeAdminService.createNode(buildK8sNode(n, config))
Jian Li1cee9882019-02-13 11:25:25 +0900156 );
157 }
158
Jian Li077b07e2020-09-01 16:55:25 +0900159 private void bootstrapK8sHosts(K8sApiConfig config) {
160 KubernetesClient k8sClient = k8sClient(config);
161
162 if (k8sClient == null) {
163 log.warn("Failed to connect to kubernetes API server");
164 return;
165 }
166
167 config.infos().forEach(h -> {
168 k8sHostAdminService.createHost(buildK8sHost(h, config));
169 });
170
171 }
172
173 private K8sHost buildK8sHost(HostNodesInfo hostNodesInfo, K8sApiConfig config) {
174 int segmentId = config.segmentId();
175 K8sTunnelBridge bridge = new K8sTunnelBridge(segmentId);
176
177 return DefaultK8sHost.builder()
178 .hostIp(hostNodesInfo.hostIp())
179 .state(K8sHostState.INIT)
180 .tunBridges(ImmutableSet.of(bridge))
181 .nodeNames(hostNodesInfo.nodes())
182 .build();
183 }
184
Jian Lie2a04ce2020-07-01 19:07:02 +0900185 private K8sNode buildK8sNode(Node node, K8sApiConfig config) {
Jian Li1cee9882019-02-13 11:25:25 +0900186 String hostname = node.getMetadata().getName();
187 IpAddress managementIp = null;
188 IpAddress dataIp = null;
189
Jian Lie2a04ce2020-07-01 19:07:02 +0900190 // pass-through mode: we use host IP as the management and data IP
191 // normal mode: we use K8S node's internal IP as the management and data IP
192 if (config.mode() == PASSTHROUGH) {
193 HostNodesInfo info = config.infos().stream().filter(h -> h.nodes()
194 .contains(hostname)).findAny().orElse(null);
195 if (info == null) {
196 log.error("None of the nodes were found in the host nodes info mapping list");
197 } else {
198 managementIp = info.hostIp();
199 dataIp = info.hostIp();
200 }
201 } else {
202 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
203 if (nodeAddress.getType().equals(INTERNAL_IP)) {
204 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
205 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
206 }
Jian Li1cee9882019-02-13 11:25:25 +0900207 }
208 }
209
210 String roleStr = node.getMetadata().getLabels().keySet().stream()
211 .filter(l -> l.contains(K8S_ROLE))
212 .findFirst().orElse(null);
213
Jian Li8a988042019-05-03 23:41:19 +0900214 K8sNode.Type nodeType = MINION;
Jian Li1cee9882019-02-13 11:25:25 +0900215
216 if (roleStr != null) {
217 String role = roleStr.split("/")[1];
218 if (MASTER.name().equalsIgnoreCase(role)) {
219 nodeType = MASTER;
220 } else {
221 nodeType = MINION;
222 }
223 }
224
Jian Li0c632722019-05-08 15:58:04 +0900225 Map<String, String> annots = node.getMetadata().getAnnotations();
226
Jian Lie2a04ce2020-07-01 19:07:02 +0900227 String extIntf = "";
228 String extGatewayIpStr = DEFAULT_GATEWAY_IP;
229 String extBridgeIpStr = DEFAULT_BRIDGE_IP;
230
231 if (config.mode() == PASSTHROUGH) {
232 extNetworkService.registerNetwork(config.extNetworkCidr());
233 extIntf = EXTERNAL_TO_ROUTER + "-" + config.clusterShortName();
234 IpAddress gatewayIp = extNetworkService.getGatewayIp(config.extNetworkCidr());
235 IpAddress bridgeIp = extNetworkService.allocateIp(config.extNetworkCidr());
236 if (gatewayIp != null) {
237 extGatewayIpStr = gatewayIp.toString();
238 }
239 if (bridgeIp != null) {
240 extBridgeIpStr = bridgeIp.toString();
241 }
242 } else {
243 extIntf = annots.get(EXT_INTF_NAME);
244 extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
245 extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
246 }
Jian Li0c632722019-05-08 15:58:04 +0900247
Jian Lic2242bd2020-09-03 13:12:14 +0900248 K8sNode.Builder builder = DefaultK8sNode.builder()
Jian Lie2a04ce2020-07-01 19:07:02 +0900249 .clusterName(DEFAULT_CLUSTER_NAME)
Jian Li1cee9882019-02-13 11:25:25 +0900250 .hostname(hostname)
251 .managementIp(managementIp)
252 .dataIp(dataIp)
Jian Li0c632722019-05-08 15:58:04 +0900253 .extIntf(extIntf)
254 .type(nodeType)
Jian Lie2a04ce2020-07-01 19:07:02 +0900255 .segmentId(config.segmentId())
Jian Li0ee8d0e2019-12-18 11:35:05 +0900256 .state(PRE_ON_BOARD)
Jian Lie2a04ce2020-07-01 19:07:02 +0900257 .mode(config.mode())
Jian Li0c632722019-05-08 15:58:04 +0900258 .extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
259 .extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
Jian Lic2242bd2020-09-03 13:12:14 +0900260 .podCidr(node.getSpec().getPodCIDR());
261
262 if (config.dvr()) {
263 builder.extGatewayMac(MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC));
264 }
265
266 return builder.build();
Jian Li1cee9882019-02-13 11:25:25 +0900267 }
268
269 /**
270 * An internal kubernetes API server config listener.
271 * The notification is triggered by K8sApiConfigStore.
272 */
273 private class InternalK8sApiConfigListener implements K8sApiConfigListener {
274
275 private boolean isRelevantHelper() {
276 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
277 }
278
279 @Override
280 public void event(K8sApiConfigEvent event) {
281
282 switch (event.type()) {
283 case K8S_API_CONFIG_CREATED:
284 eventExecutor.execute(() -> processConfigCreation(event.subject()));
285 break;
286 default:
287 break;
288 }
289 }
290
291 private void processConfigCreation(K8sApiConfig config) {
292 if (!isRelevantHelper()) {
293 return;
294 }
295
296 if (checkApiServerConfig(config)) {
297 K8sApiConfig newConfig = config.updateState(K8sApiConfig.State.CONNECTED);
298 k8sApiConfigAdminService.updateApiConfig(newConfig);
Jian Lie2a04ce2020-07-01 19:07:02 +0900299
Jian Li1cee9882019-02-13 11:25:25 +0900300 bootstrapK8sNodes(config);
Jian Li077b07e2020-09-01 16:55:25 +0900301
302 try {
303 sleep(SLEEP_MS);
304 } catch (InterruptedException e) {
305 log.error("Exception caused during init state checking...");
306 }
307
308 bootstrapK8sHosts(config);
Jian Li1cee9882019-02-13 11:25:25 +0900309 }
310 }
311 }
312}