blob: f8573646fa67af0093fac8f7b4639acbf9f8e5e2 [file] [log] [blame]
Jian Lib230e07c2020-12-21 11:25:12 +09001/*
2 * Copyright 2020-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.kubevirtnode.util;
17
Daniel Park515f5f32021-02-22 17:12:20 +090018import com.fasterxml.jackson.core.JsonProcessingException;
Daniel Park515f5f32021-02-22 17:12:20 +090019import com.fasterxml.jackson.databind.JsonNode;
Jian Lib230e07c2020-12-21 11:25:12 +090020import com.fasterxml.jackson.databind.ObjectMapper;
Jian Li4fe40e52021-01-06 03:29:58 +090021import com.google.common.base.Strings;
Daniel Park80c6f1b2021-02-18 20:38:46 +090022import io.fabric8.kubernetes.api.model.Node;
23import io.fabric8.kubernetes.api.model.NodeAddress;
Jian Li1e0f3e82021-06-17 11:06:36 +090024import io.fabric8.kubernetes.api.model.NodeSpec;
25import io.fabric8.kubernetes.api.model.Taint;
Jian Liaaf44b52020-12-27 23:22:46 +090026import io.fabric8.kubernetes.client.ConfigBuilder;
27import io.fabric8.kubernetes.client.DefaultKubernetesClient;
28import io.fabric8.kubernetes.client.KubernetesClient;
Jian Lif2483072020-12-25 02:24:16 +090029import org.apache.commons.lang.StringUtils;
Andrea Campanella775e5f82022-06-09 08:11:25 -070030import com.eclipsesource.json.JsonArray;
31import com.eclipsesource.json.JsonObject;
Jian Lif2483072020-12-25 02:24:16 +090032import org.onlab.packet.IpAddress;
Daniel Park80c6f1b2021-02-18 20:38:46 +090033import org.onosproject.kubevirtnode.api.DefaultKubevirtNode;
34import org.onosproject.kubevirtnode.api.DefaultKubevirtPhyInterface;
Jian Lif2483072020-12-25 02:24:16 +090035import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
Jian Li4fe40e52021-01-06 03:29:58 +090036import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Park80c6f1b2021-02-18 20:38:46 +090037import org.onosproject.kubevirtnode.api.KubevirtNodeState;
38import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
Jian Li4fe40e52021-01-06 03:29:58 +090039import org.onosproject.net.Device;
Daniel Park17fe7982022-04-04 17:48:01 +090040import org.onosproject.net.DeviceId;
Jian Li4fe40e52021-01-06 03:29:58 +090041import org.onosproject.net.behaviour.BridgeConfig;
42import org.onosproject.net.behaviour.BridgeName;
43import org.onosproject.net.device.DeviceService;
44import org.onosproject.ovsdb.controller.OvsdbClientService;
45import org.onosproject.ovsdb.controller.OvsdbController;
46import org.onosproject.ovsdb.controller.OvsdbNodeId;
Jian Lib230e07c2020-12-21 11:25:12 +090047import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
Jian Li94b6d162021-04-15 17:09:11 +090049import org.xbill.DNS.Address;
Jian Lib230e07c2020-12-21 11:25:12 +090050
51import java.io.IOException;
Jian Li94b6d162021-04-15 17:09:11 +090052import java.net.InetAddress;
53import java.net.UnknownHostException;
Jian Li4fe40e52021-01-06 03:29:58 +090054import java.util.Dictionary;
Daniel Park80c6f1b2021-02-18 20:38:46 +090055import java.util.HashSet;
Jian Lib230e07c2020-12-21 11:25:12 +090056import java.util.List;
Daniel Park80c6f1b2021-02-18 20:38:46 +090057import java.util.Map;
58import java.util.Set;
59import java.util.stream.Collectors;
Jian Lib230e07c2020-12-21 11:25:12 +090060
Jian Li4fe40e52021-01-06 03:29:58 +090061import static org.onlab.util.Tools.get;
Jian Li4b249702021-02-19 18:13:10 +090062import static org.onosproject.kubevirtnode.api.Constants.SONA_PROJECT_DOMAIN;
Daniel Park515f5f32021-02-22 17:12:20 +090063import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Daniel Park80c6f1b2021-02-18 20:38:46 +090064import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.MASTER;
Jian Li1e0f3e82021-06-17 11:06:36 +090065import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.OTHER;
Daniel Park80c6f1b2021-02-18 20:38:46 +090066import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
Jian Li4fe40e52021-01-06 03:29:58 +090067
Jian Lib230e07c2020-12-21 11:25:12 +090068/**
69 * An utility that used in KubeVirt node app.
70 */
71public final class KubevirtNodeUtil {
72
73 private static final Logger log = LoggerFactory.getLogger(KubevirtNodeUtil.class);
74
75 private static final String COLON_SLASH = "://";
76 private static final String COLON = ":";
77
78 private static final int HEX_LENGTH = 16;
79 private static final String OF_PREFIX = "of:";
80 private static final String ZERO = "0";
Daniel Park80c6f1b2021-02-18 20:38:46 +090081 private static final String INTERNAL_IP = "InternalIP";
82 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li4b249702021-02-19 18:13:10 +090083 private static final String PHYSNET_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/physnet-config";
84 private static final String DATA_IP_KEY = SONA_PROJECT_DOMAIN + "/data-ip";
Daniel Park515f5f32021-02-22 17:12:20 +090085 private static final String GATEWAY_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/gateway-config";
86 private static final String GATEWAY_BRIDGE_NAME = "gatewayBridgeName";
Daniel Park80c6f1b2021-02-18 20:38:46 +090087 private static final String NETWORK_KEY = "network";
88 private static final String INTERFACE_KEY = "interface";
Daniel Park17fe7982022-04-04 17:48:01 +090089 private static final String PHYS_BRIDGE_ID = "physBridgeId";
Jian Lib230e07c2020-12-21 11:25:12 +090090
Jian Li4fe40e52021-01-06 03:29:58 +090091 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li1e0f3e82021-06-17 11:06:36 +090092
93 private static final String NO_SCHEDULE_EFFECT = "NoSchedule";
94 private static final String KUBEVIRT_IO_KEY = "kubevirt.io/drain";
95 private static final String DRAINING_VALUE = "draining";
Jian Li4fe40e52021-01-06 03:29:58 +090096
Jian Lib230e07c2020-12-21 11:25:12 +090097 /**
98 * Prevents object installation from external.
99 */
100 private KubevirtNodeUtil() {
101 }
102
103 /**
Jian Lif2483072020-12-25 02:24:16 +0900104 * Generates endpoint URL by referring to scheme, ipAddress and port.
105 *
106 * @param apiConfig kubernetes API config
107 * @return generated endpoint URL
108 */
109 public static String endpoint(KubevirtApiConfig apiConfig) {
110 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
111 }
112
113 /**
114 * Generates endpoint URL by referring to scheme, ipAddress and port.
115 *
116 * @param scheme scheme
117 * @param ipAddress IP address
118 * @param port port number
119 * @return generated endpoint URL
120 */
121 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
122 StringBuilder endpoint = new StringBuilder();
123 String protocol = StringUtils.lowerCase(scheme.name());
124
125 endpoint.append(protocol);
126 endpoint.append(COLON_SLASH);
127 endpoint.append(ipAddress.toString());
128 endpoint.append(COLON);
129 endpoint.append(port);
130
131 return endpoint.toString();
132 }
133
134 /**
Jian Lib230e07c2020-12-21 11:25:12 +0900135 * Generates a DPID (of:0000000000000001) from an index value.
136 *
137 * @param index index value
138 * @return generated DPID
139 */
140 public static String genDpid(long index) {
141 if (index < 0) {
142 return null;
143 }
144
145 String hexStr = Long.toHexString(index);
146
147 StringBuilder zeroPadding = new StringBuilder();
148 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
149 zeroPadding.append(ZERO);
150 }
151
152 return OF_PREFIX + zeroPadding.toString() + hexStr;
153 }
154
155 /**
156 * Generates string format based on the given string length list.
157 *
158 * @param stringLengths a list of string lengths
159 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
160 */
161 public static String genFormatString(List<Integer> stringLengths) {
162 StringBuilder fsb = new StringBuilder();
163 stringLengths.forEach(length -> {
164 fsb.append("%-");
165 fsb.append(length);
166 fsb.append("s");
167 });
168 return fsb.toString();
169 }
170
171 /**
172 * Prints out the JSON string in pretty format.
173 *
174 * @param mapper Object mapper
175 * @param jsonString JSON string
176 * @return pretty formatted JSON string
177 */
178 public static String prettyJson(ObjectMapper mapper, String jsonString) {
179 try {
180 Object jsonObject = mapper.readValue(jsonString, Object.class);
181 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
182 } catch (IOException e) {
183 log.debug("Json string parsing exception caused by {}", e);
184 }
185 return null;
186 }
Jian Liaaf44b52020-12-27 23:22:46 +0900187
188 /**
189 * Obtains workable kubernetes client.
190 *
191 * @param config kubernetes API config
192 * @return kubernetes client
193 */
194 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
195 if (config == null) {
196 log.warn("Kubernetes API server config is empty.");
197 return null;
198 }
199
200 String endpoint = endpoint(config);
201
202 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
203
204 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
205 configBuilder.withTrustCerts(true)
206 .withCaCertData(config.caCertData())
207 .withClientCertData(config.clientCertData())
208 .withClientKeyData(config.clientKeyData());
209
210 if (StringUtils.isNotEmpty(config.token())) {
211 configBuilder.withOauthToken(config.token());
212 }
213 }
214
215 return new DefaultKubernetesClient(configBuilder.build());
216 }
Jian Li4fe40e52021-01-06 03:29:58 +0900217
218 /**
219 * Gets the ovsdb client with supplied openstack node.
220 *
221 * @param node kubernetes node
222 * @param ovsdbPort ovsdb port
223 * @param ovsdbController ovsdb controller
224 * @return ovsdb client
225 */
226 public static OvsdbClientService getOvsdbClient(KubevirtNode node,
227 int ovsdbPort,
228 OvsdbController ovsdbController) {
229 OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
230 return ovsdbController.getOvsdbClient(ovsdb);
231 }
232
233 /**
234 * Checks whether the controller has a connection with an OVSDB that resides
235 * inside the given kubernetes node.
236 *
237 * @param node kubernetes node
238 * @param ovsdbPort OVSDB port
239 * @param ovsdbController OVSDB controller
240 * @param deviceService device service
241 * @return true if the controller is connected to the OVSDB, false otherwise
242 */
243 public static boolean isOvsdbConnected(KubevirtNode node,
244 int ovsdbPort,
245 OvsdbController ovsdbController,
246 DeviceService deviceService) {
247 OvsdbClientService client = getOvsdbClient(node, ovsdbPort, ovsdbController);
248 return deviceService.isAvailable(node.ovsdb()) &&
249 client != null &&
250 client.isConnected();
251 }
252
253 /**
254 * Adds or removes a network interface (aka port) into a given bridge of kubernetes node.
255 *
256 * @param k8sNode kubernetes node
257 * @param bridgeName bridge name
258 * @param intfName interface name
259 * @param deviceService device service
260 * @param addOrRemove add port is true, remove it otherwise
261 */
262 public static synchronized void addOrRemoveSystemInterface(KubevirtNode k8sNode,
263 String bridgeName,
264 String intfName,
265 DeviceService deviceService,
266 boolean addOrRemove) {
267
268
269 Device device = deviceService.getDevice(k8sNode.ovsdb());
270 if (device == null || !device.is(BridgeConfig.class)) {
271 log.info("device is null or this device if not ovsdb device");
272 return;
273 }
274 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
275
276 if (addOrRemove) {
277 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
278 } else {
279 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
280 }
281 }
282
283 /**
284 * Re-structures the OVS port name.
285 * The length of OVS port name should be not large than 15.
286 *
287 * @param portName original port name
288 * @return re-structured OVS port name
289 */
290 public static String structurePortName(String portName) {
291
292 // The size of OVS port name should not be larger than 15
293 if (portName.length() > PORT_NAME_MAX_LENGTH) {
294 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
295 }
296
297 return portName;
298 }
299
300 /**
301 * Gets Boolean property from the propertyName
302 * Return null if propertyName is not found.
303 *
304 * @param properties properties to be looked up
305 * @param propertyName the name of the property to look up
306 * @return value when the propertyName is defined or return null
307 */
308 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
309 String propertyName) {
310 Boolean value;
311 try {
312 String s = get(properties, propertyName);
313 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
314 } catch (ClassCastException e) {
315 value = null;
316 }
317 return value;
318 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900319
320 /**
Jian Lidd5172e2022-03-05 00:03:35 +0900321 * Returns the type of the given kubernetes node.
322 *
323 * @param node kubernetes node
324 * @return node type
325 */
326 public static KubevirtNode.Type getNodeType(Node node) {
327 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
328 .filter(l -> l.contains(K8S_ROLE))
329 .collect(Collectors.toSet());
330
331 KubevirtNode.Type nodeType = WORKER;
332
333 for (String roleStr : rolesFull) {
334 String role = roleStr.split("/")[1];
335 if (MASTER.name().equalsIgnoreCase(role)) {
336 nodeType = MASTER;
337 break;
338 }
339 }
340
341 Map<String, String> annots = node.getMetadata().getAnnotations();
342 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
343 if (gatewayConfig != null) {
344 nodeType = GATEWAY;
345 }
346
347 return nodeType;
348 }
349
350 /**
Daniel Park80c6f1b2021-02-18 20:38:46 +0900351 * Returns the kubevirt node from the node.
352 *
353 * @param node a raw node object returned from a k8s client
354 * @return kubevirt node
355 */
356 public static KubevirtNode buildKubevirtNode(Node node) {
357 String hostname = node.getMetadata().getName();
358 IpAddress managementIp = null;
359 IpAddress dataIp = null;
360
361 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
362 if (nodeAddress.getType().equals(INTERNAL_IP)) {
363 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
364 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
365 }
366 }
367
368 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
369 .filter(l -> l.contains(K8S_ROLE))
370 .collect(Collectors.toSet());
371
372 KubevirtNode.Type nodeType = WORKER;
373
374 for (String roleStr : rolesFull) {
375 String role = roleStr.split("/")[1];
376 if (MASTER.name().equalsIgnoreCase(role)) {
377 nodeType = MASTER;
378 break;
379 }
380 }
381
382 // start to parse kubernetes annotation
383 Map<String, String> annots = node.getMetadata().getAnnotations();
384 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
Daniel Park515f5f32021-02-22 17:12:20 +0900385 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
Jian Li4b249702021-02-19 18:13:10 +0900386 String dataIpStr = annots.get(DATA_IP_KEY);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900387 Set<KubevirtPhyInterface> phys = new HashSet<>();
Daniel Park515f5f32021-02-22 17:12:20 +0900388 String gatewayBridgeName = null;
Daniel Park80c6f1b2021-02-18 20:38:46 +0900389 try {
390 if (physnetConfig != null) {
Andrea Campanella775e5f82022-06-09 08:11:25 -0700391 JsonArray configJson = JsonArray.readFrom(physnetConfig);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900392
Andrea Campanella775e5f82022-06-09 08:11:25 -0700393 for (int i = 0; i < configJson.size(); i++) {
394 JsonObject object = configJson.get(i).asObject();
395 String network = object.get(NETWORK_KEY).asString();
396 String intf = object.get(INTERFACE_KEY).asString();
Daniel Park80c6f1b2021-02-18 20:38:46 +0900397
398 if (network != null && intf != null) {
Daniel Park17fe7982022-04-04 17:48:01 +0900399 String physBridgeId;
Andrea Campanella775e5f82022-06-09 08:11:25 -0700400 if (object.get(PHYS_BRIDGE_ID) != null) {
401 physBridgeId = object.get(PHYS_BRIDGE_ID).asString();
Daniel Park17fe7982022-04-04 17:48:01 +0900402 } else {
403 physBridgeId = genDpidFromName(network + intf + hostname);
404 log.trace("host {} physnet dpid for network {} intf {} is null so generate dpid {}",
405 hostname, network, intf, physBridgeId);
406 }
407
Daniel Park80c6f1b2021-02-18 20:38:46 +0900408 phys.add(DefaultKubevirtPhyInterface.builder()
Daniel Park17fe7982022-04-04 17:48:01 +0900409 .network(network)
410 .intf(intf)
411 .physBridge(DeviceId.deviceId(physBridgeId))
412 .build());
Daniel Park80c6f1b2021-02-18 20:38:46 +0900413 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900414 }
415 }
Jian Li4b249702021-02-19 18:13:10 +0900416
417 if (dataIpStr != null) {
418 dataIp = IpAddress.valueOf(dataIpStr);
419 }
Daniel Park515f5f32021-02-22 17:12:20 +0900420
421 if (gatewayConfig != null) {
422 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
423
424 nodeType = GATEWAY;
425 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
426 }
Andrea Campanella775e5f82022-06-09 08:11:25 -0700427 } catch (JsonProcessingException e) {
Jian Li9793ec42021-03-19 15:03:32 +0900428 log.error("Failed to parse physnet config or gateway config object", e);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900429 }
430
Jian Li1e0f3e82021-06-17 11:06:36 +0900431 // if the node is taint with kubevirt.io key configured,
432 // we mark this node as OTHER type, and do not add it into the cluster
433 NodeSpec spec = node.getSpec();
434 if (spec.getTaints() != null) {
435 for (Taint taint : spec.getTaints()) {
436 String effect = taint.getEffect();
437 String key = taint.getKey();
438 String value = taint.getValue();
439
440 if (StringUtils.equals(effect, NO_SCHEDULE_EFFECT) &&
441 StringUtils.equals(key, KUBEVIRT_IO_KEY) &&
442 StringUtils.equals(value, DRAINING_VALUE)) {
443 nodeType = OTHER;
444 }
445 }
446 }
447
Daniel Park80c6f1b2021-02-18 20:38:46 +0900448 return DefaultKubevirtNode.builder()
449 .hostname(hostname)
450 .managementIp(managementIp)
451 .dataIp(dataIp)
452 .type(nodeType)
453 .state(KubevirtNodeState.ON_BOARDED)
454 .phyIntfs(phys)
Daniel Park515f5f32021-02-22 17:12:20 +0900455 .gatewayBridgeName(gatewayBridgeName)
Daniel Park80c6f1b2021-02-18 20:38:46 +0900456 .build();
457 }
Jian Li94b6d162021-04-15 17:09:11 +0900458
459 /**
Daniel Park17fe7982022-04-04 17:48:01 +0900460 * Generates a unique dpid from given name.
461 *
462 * @param name name
463 * @return device id in string
464 */
465 public static String genDpidFromName(String name) {
466 if (name != null) {
467 String hexString = Integer.toHexString(name.hashCode());
468 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
469 }
470 return null;
471 }
472
473 /**
Jian Li94b6d162021-04-15 17:09:11 +0900474 * Resolve a DNS with the given DNS server and hostname.
475 *
476 * @param hostname hostname to be resolved
477 * @return resolved IP address
478 */
479 public static IpAddress resolveHostname(String hostname) {
480 try {
481 InetAddress addr = Address.getByName(hostname);
482 return IpAddress.valueOf(IpAddress.Version.INET, addr.getAddress());
483 } catch (UnknownHostException e) {
484 log.warn("Failed to resolve IP address of host {}", hostname);
485 }
486 return null;
487 }
Jian Li528335e2021-07-08 20:52:50 +0900488
489 /**
490 * Waits for the given length of time.
491 *
492 * @param timeSecond the amount of time for wait in second unit
493 */
494 public static void waitFor(int timeSecond) {
495 try {
496 Thread.sleep(timeSecond * 1000L);
497 } catch (Exception e) {
498 log.error(e.toString());
499 }
500 }
Jian Lib230e07c2020-12-21 11:25:12 +0900501}