blob: c1888717cb3a603b8dc77b88eddedc08457b7266 [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;
23import org.onosproject.cluster.ClusterService;
24import org.onosproject.cluster.LeadershipService;
25import org.onosproject.cluster.NodeId;
26import org.onosproject.core.ApplicationId;
27import org.onosproject.core.CoreService;
Jian Li8685dd32020-09-01 16:55:25 +090028import org.onosproject.k8snode.api.DefaultK8sHost;
Jian Li1cee9882019-02-13 11:25:25 +090029import org.onosproject.k8snode.api.DefaultK8sNode;
Jian Li58b33982020-07-01 19:07:02 +090030import org.onosproject.k8snode.api.ExternalNetworkService;
31import org.onosproject.k8snode.api.HostNodesInfo;
Jian Li1cee9882019-02-13 11:25:25 +090032import org.onosproject.k8snode.api.K8sApiConfig;
33import org.onosproject.k8snode.api.K8sApiConfigAdminService;
34import org.onosproject.k8snode.api.K8sApiConfigEvent;
35import org.onosproject.k8snode.api.K8sApiConfigListener;
Jian Li8685dd32020-09-01 16:55:25 +090036import org.onosproject.k8snode.api.K8sHost;
37import org.onosproject.k8snode.api.K8sHostAdminService;
38import org.onosproject.k8snode.api.K8sHostState;
Jian Li1cee9882019-02-13 11:25:25 +090039import org.onosproject.k8snode.api.K8sNode;
40import org.onosproject.k8snode.api.K8sNodeAdminService;
Jian Li8685dd32020-09-01 16:55:25 +090041import org.onosproject.k8snode.api.K8sTunnelBridge;
Jian Li1cee9882019-02-13 11:25:25 +090042import org.osgi.service.component.annotations.Activate;
43import org.osgi.service.component.annotations.Component;
44import org.osgi.service.component.annotations.Deactivate;
45import org.osgi.service.component.annotations.Reference;
46import org.osgi.service.component.annotations.ReferenceCardinality;
47import org.slf4j.Logger;
48
Jian Li7709eb42019-05-08 15:58:04 +090049import java.util.Map;
Jian Li1cee9882019-02-13 11:25:25 +090050import java.util.Objects;
51import java.util.concurrent.ExecutorService;
52
Jian Li8685dd32020-09-01 16:55:25 +090053import static java.lang.Thread.sleep;
Jian Li1cee9882019-02-13 11:25:25 +090054import static java.util.concurrent.Executors.newSingleThreadExecutor;
55import static org.onlab.util.Tools.groupedThreads;
Jian Li58b33982020-07-01 19:07:02 +090056import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
57import static org.onosproject.k8snode.api.Constants.EXTERNAL_TO_ROUTER;
58import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
Jian Li1cee9882019-02-13 11:25:25 +090059import static org.onosproject.k8snode.api.K8sNode.Type.MASTER;
60import static org.onosproject.k8snode.api.K8sNode.Type.MINION;
61import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
Jian Li77af8f32019-12-18 11:35:05 +090062import static org.onosproject.k8snode.api.K8sNodeState.PRE_ON_BOARD;
Jian Li1cee9882019-02-13 11:25:25 +090063import static org.onosproject.k8snode.util.K8sNodeUtil.k8sClient;
64import static org.slf4j.LoggerFactory.getLogger;
65
66/**
67 * Handles the state of kubernetes API server configuration.
68 */
69@Component(immediate = true)
70public class DefaultK8sApiConfigHandler {
71
72 private final Logger log = getLogger(getClass());
73
74 private static final String INTERNAL_IP = "InternalIP";
75 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li7709eb42019-05-08 15:58:04 +090076 private static final String EXT_BRIDGE_IP = "external.bridge.ip";
77 private static final String EXT_GATEWAY_IP = "external.gateway.ip";
78 private static final String EXT_INTF_NAME = "external.interface.name";
Jian Li1cee9882019-02-13 11:25:25 +090079
Jian Li58b33982020-07-01 19:07:02 +090080 private static final String DEFAULT_GATEWAY_IP = "127.0.0.1";
81 private static final String DEFAULT_BRIDGE_IP = "127.0.0.1";
82
Jian Li8685dd32020-09-01 16:55:25 +090083 private static final long SLEEP_MS = 3000; // we wait 3s
84
Jian Li1cee9882019-02-13 11:25:25 +090085 @Reference(cardinality = ReferenceCardinality.MANDATORY)
86 protected CoreService coreService;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY)
89 protected LeadershipService leadershipService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY)
92 protected ClusterService clusterService;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY)
95 protected K8sApiConfigAdminService k8sApiConfigAdminService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY)
98 protected K8sNodeAdminService k8sNodeAdminService;
99
Jian Li58b33982020-07-01 19:07:02 +0900100 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li8685dd32020-09-01 16:55:25 +0900101 protected K8sHostAdminService k8sHostAdminService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li58b33982020-07-01 19:07:02 +0900104 protected ExternalNetworkService extNetworkService;
105
Jian Li1cee9882019-02-13 11:25:25 +0900106 private final ExecutorService eventExecutor = newSingleThreadExecutor(
107 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
108
109 private final K8sApiConfigListener k8sApiConfigListener = new InternalK8sApiConfigListener();
110
111 private ApplicationId appId;
112 private NodeId localNode;
113
114 @Activate
115 protected void activate() {
116 appId = coreService.getAppId(APP_ID);
117 localNode = clusterService.getLocalNode().id();
118 leadershipService.runForLeadership(appId.name());
119 k8sApiConfigAdminService.addListener(k8sApiConfigListener);
120
121 log.info("Started");
122 }
123
124 @Deactivate
125 protected void deactivate() {
126 k8sApiConfigAdminService.removeListener(k8sApiConfigListener);
127 leadershipService.withdraw(appId.name());
128 eventExecutor.shutdown();
129
130 log.info("Stopped");
131 }
132
133 /**
134 * Checks the validity of the given kubernetes API server configuration.
135 *
136 * @param config kubernetes API server configuration
137 * @return validity result
138 */
139 private boolean checkApiServerConfig(K8sApiConfig config) {
140 KubernetesClient k8sClient = k8sClient(config);
141 return k8sClient != null && k8sClient.getApiVersion() != null;
142 }
143
144 private void bootstrapK8sNodes(K8sApiConfig config) {
145 KubernetesClient k8sClient = k8sClient(config);
146
147 if (k8sClient == null) {
148 log.warn("Failed to connect to kubernetes API server");
149 return;
150 }
151
152 k8sClient.nodes().list().getItems().forEach(n ->
Jian Li58b33982020-07-01 19:07:02 +0900153 k8sNodeAdminService.createNode(buildK8sNode(n, config))
Jian Li1cee9882019-02-13 11:25:25 +0900154 );
155 }
156
Jian Li8685dd32020-09-01 16:55:25 +0900157 private void bootstrapK8sHosts(K8sApiConfig config) {
158 KubernetesClient k8sClient = k8sClient(config);
159
160 if (k8sClient == null) {
161 log.warn("Failed to connect to kubernetes API server");
162 return;
163 }
164
165 config.infos().forEach(h -> {
166 k8sHostAdminService.createHost(buildK8sHost(h, config));
167 });
168
169 }
170
171 private K8sHost buildK8sHost(HostNodesInfo hostNodesInfo, K8sApiConfig config) {
172 int segmentId = config.segmentId();
173 K8sTunnelBridge bridge = new K8sTunnelBridge(segmentId);
174
175 return DefaultK8sHost.builder()
176 .hostIp(hostNodesInfo.hostIp())
177 .state(K8sHostState.INIT)
178 .tunBridges(ImmutableSet.of(bridge))
179 .nodeNames(hostNodesInfo.nodes())
180 .build();
181 }
182
Jian Li58b33982020-07-01 19:07:02 +0900183 private K8sNode buildK8sNode(Node node, K8sApiConfig config) {
Jian Li1cee9882019-02-13 11:25:25 +0900184 String hostname = node.getMetadata().getName();
185 IpAddress managementIp = null;
186 IpAddress dataIp = null;
187
Jian Li58b33982020-07-01 19:07:02 +0900188 // pass-through mode: we use host IP as the management and data IP
189 // normal mode: we use K8S node's internal IP as the management and data IP
190 if (config.mode() == PASSTHROUGH) {
191 HostNodesInfo info = config.infos().stream().filter(h -> h.nodes()
192 .contains(hostname)).findAny().orElse(null);
193 if (info == null) {
194 log.error("None of the nodes were found in the host nodes info mapping list");
195 } else {
196 managementIp = info.hostIp();
197 dataIp = info.hostIp();
198 }
199 } else {
200 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
201 if (nodeAddress.getType().equals(INTERNAL_IP)) {
202 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
203 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
204 }
Jian Li1cee9882019-02-13 11:25:25 +0900205 }
206 }
207
208 String roleStr = node.getMetadata().getLabels().keySet().stream()
209 .filter(l -> l.contains(K8S_ROLE))
210 .findFirst().orElse(null);
211
Jian Li8a988042019-05-03 23:41:19 +0900212 K8sNode.Type nodeType = MINION;
Jian Li1cee9882019-02-13 11:25:25 +0900213
214 if (roleStr != null) {
215 String role = roleStr.split("/")[1];
216 if (MASTER.name().equalsIgnoreCase(role)) {
217 nodeType = MASTER;
218 } else {
219 nodeType = MINION;
220 }
221 }
222
Jian Li7709eb42019-05-08 15:58:04 +0900223 Map<String, String> annots = node.getMetadata().getAnnotations();
224
Jian Li58b33982020-07-01 19:07:02 +0900225 String extIntf = "";
226 String extGatewayIpStr = DEFAULT_GATEWAY_IP;
227 String extBridgeIpStr = DEFAULT_BRIDGE_IP;
228
229 if (config.mode() == PASSTHROUGH) {
230 extNetworkService.registerNetwork(config.extNetworkCidr());
231 extIntf = EXTERNAL_TO_ROUTER + "-" + config.clusterShortName();
232 IpAddress gatewayIp = extNetworkService.getGatewayIp(config.extNetworkCidr());
233 IpAddress bridgeIp = extNetworkService.allocateIp(config.extNetworkCidr());
234 if (gatewayIp != null) {
235 extGatewayIpStr = gatewayIp.toString();
236 }
237 if (bridgeIp != null) {
238 extBridgeIpStr = bridgeIp.toString();
239 }
240 } else {
241 extIntf = annots.get(EXT_INTF_NAME);
242 extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
243 extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
244 }
Jian Li7709eb42019-05-08 15:58:04 +0900245
Jian Li1cee9882019-02-13 11:25:25 +0900246 return DefaultK8sNode.builder()
Jian Li58b33982020-07-01 19:07:02 +0900247 .clusterName(DEFAULT_CLUSTER_NAME)
Jian Li1cee9882019-02-13 11:25:25 +0900248 .hostname(hostname)
249 .managementIp(managementIp)
250 .dataIp(dataIp)
Jian Li7709eb42019-05-08 15:58:04 +0900251 .extIntf(extIntf)
252 .type(nodeType)
Jian Li58b33982020-07-01 19:07:02 +0900253 .segmentId(config.segmentId())
Jian Li77af8f32019-12-18 11:35:05 +0900254 .state(PRE_ON_BOARD)
Jian Li58b33982020-07-01 19:07:02 +0900255 .mode(config.mode())
Jian Li7709eb42019-05-08 15:58:04 +0900256 .extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
257 .extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
258 .podCidr(node.getSpec().getPodCIDR())
Jian Li1cee9882019-02-13 11:25:25 +0900259 .build();
260 }
261
262 /**
263 * An internal kubernetes API server config listener.
264 * The notification is triggered by K8sApiConfigStore.
265 */
266 private class InternalK8sApiConfigListener implements K8sApiConfigListener {
267
268 private boolean isRelevantHelper() {
269 return Objects.equals(localNode, leadershipService.getLeader(appId.name()));
270 }
271
272 @Override
273 public void event(K8sApiConfigEvent event) {
274
275 switch (event.type()) {
276 case K8S_API_CONFIG_CREATED:
277 eventExecutor.execute(() -> processConfigCreation(event.subject()));
278 break;
279 default:
280 break;
281 }
282 }
283
284 private void processConfigCreation(K8sApiConfig config) {
285 if (!isRelevantHelper()) {
286 return;
287 }
288
289 if (checkApiServerConfig(config)) {
290 K8sApiConfig newConfig = config.updateState(K8sApiConfig.State.CONNECTED);
291 k8sApiConfigAdminService.updateApiConfig(newConfig);
Jian Li58b33982020-07-01 19:07:02 +0900292
Jian Li1cee9882019-02-13 11:25:25 +0900293 bootstrapK8sNodes(config);
Jian Li8685dd32020-09-01 16:55:25 +0900294
295 try {
296 sleep(SLEEP_MS);
297 } catch (InterruptedException e) {
298 log.error("Exception caused during init state checking...");
299 }
300
301 bootstrapK8sHosts(config);
Jian Li1cee9882019-02-13 11:25:25 +0900302 }
303 }
304 }
305}