blob: 7107285b27ccbf8b5e9fcf1bcbe99795f16e34db [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;
Daniel Park80c6f1b2021-02-18 20:38:46 +090030import org.json.JSONArray;
31import org.json.JSONException;
32import org.json.JSONObject;
Jian Lif2483072020-12-25 02:24:16 +090033import org.onlab.packet.IpAddress;
Daniel Park80c6f1b2021-02-18 20:38:46 +090034import org.onosproject.kubevirtnode.api.DefaultKubevirtNode;
35import org.onosproject.kubevirtnode.api.DefaultKubevirtPhyInterface;
Jian Lif2483072020-12-25 02:24:16 +090036import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
Jian Li4fe40e52021-01-06 03:29:58 +090037import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Park80c6f1b2021-02-18 20:38:46 +090038import org.onosproject.kubevirtnode.api.KubevirtNodeState;
39import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
Jian Li4fe40e52021-01-06 03:29:58 +090040import org.onosproject.net.Device;
Daniel Park17fe7982022-04-04 17:48:01 +090041import org.onosproject.net.DeviceId;
Jian Li4fe40e52021-01-06 03:29:58 +090042import org.onosproject.net.behaviour.BridgeConfig;
43import org.onosproject.net.behaviour.BridgeName;
44import org.onosproject.net.device.DeviceService;
45import org.onosproject.ovsdb.controller.OvsdbClientService;
46import org.onosproject.ovsdb.controller.OvsdbController;
47import org.onosproject.ovsdb.controller.OvsdbNodeId;
Jian Lib230e07c2020-12-21 11:25:12 +090048import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
Jian Li94b6d162021-04-15 17:09:11 +090050import org.xbill.DNS.Address;
Jian Lib230e07c2020-12-21 11:25:12 +090051
52import java.io.IOException;
Jian Li94b6d162021-04-15 17:09:11 +090053import java.net.InetAddress;
54import java.net.UnknownHostException;
Jian Li4fe40e52021-01-06 03:29:58 +090055import java.util.Dictionary;
Daniel Park80c6f1b2021-02-18 20:38:46 +090056import java.util.HashSet;
Jian Lib230e07c2020-12-21 11:25:12 +090057import java.util.List;
Daniel Park80c6f1b2021-02-18 20:38:46 +090058import java.util.Map;
59import java.util.Set;
60import java.util.stream.Collectors;
Jian Lib230e07c2020-12-21 11:25:12 +090061
Jian Li4fe40e52021-01-06 03:29:58 +090062import static org.onlab.util.Tools.get;
Jian Li4b249702021-02-19 18:13:10 +090063import static org.onosproject.kubevirtnode.api.Constants.SONA_PROJECT_DOMAIN;
Daniel Park515f5f32021-02-22 17:12:20 +090064import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Daniel Park80c6f1b2021-02-18 20:38:46 +090065import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.MASTER;
Jian Li1e0f3e82021-06-17 11:06:36 +090066import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.OTHER;
Daniel Park80c6f1b2021-02-18 20:38:46 +090067import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
Jian Li4fe40e52021-01-06 03:29:58 +090068
Jian Lib230e07c2020-12-21 11:25:12 +090069/**
70 * An utility that used in KubeVirt node app.
71 */
72public final class KubevirtNodeUtil {
73
74 private static final Logger log = LoggerFactory.getLogger(KubevirtNodeUtil.class);
75
76 private static final String COLON_SLASH = "://";
77 private static final String COLON = ":";
78
79 private static final int HEX_LENGTH = 16;
80 private static final String OF_PREFIX = "of:";
81 private static final String ZERO = "0";
Daniel Park80c6f1b2021-02-18 20:38:46 +090082 private static final String INTERNAL_IP = "InternalIP";
83 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li4b249702021-02-19 18:13:10 +090084 private static final String PHYSNET_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/physnet-config";
85 private static final String DATA_IP_KEY = SONA_PROJECT_DOMAIN + "/data-ip";
Daniel Park515f5f32021-02-22 17:12:20 +090086 private static final String GATEWAY_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/gateway-config";
87 private static final String GATEWAY_BRIDGE_NAME = "gatewayBridgeName";
Daniel Park80c6f1b2021-02-18 20:38:46 +090088 private static final String NETWORK_KEY = "network";
89 private static final String INTERFACE_KEY = "interface";
Daniel Park17fe7982022-04-04 17:48:01 +090090 private static final String PHYS_BRIDGE_ID = "physBridgeId";
Jian Lib230e07c2020-12-21 11:25:12 +090091
Jian Li4fe40e52021-01-06 03:29:58 +090092 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li1e0f3e82021-06-17 11:06:36 +090093
94 private static final String NO_SCHEDULE_EFFECT = "NoSchedule";
95 private static final String KUBEVIRT_IO_KEY = "kubevirt.io/drain";
96 private static final String DRAINING_VALUE = "draining";
Jian Li4fe40e52021-01-06 03:29:58 +090097
Jian Lib230e07c2020-12-21 11:25:12 +090098 /**
99 * Prevents object installation from external.
100 */
101 private KubevirtNodeUtil() {
102 }
103
104 /**
Jian Lif2483072020-12-25 02:24:16 +0900105 * Generates endpoint URL by referring to scheme, ipAddress and port.
106 *
107 * @param apiConfig kubernetes API config
108 * @return generated endpoint URL
109 */
110 public static String endpoint(KubevirtApiConfig apiConfig) {
111 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
112 }
113
114 /**
115 * Generates endpoint URL by referring to scheme, ipAddress and port.
116 *
117 * @param scheme scheme
118 * @param ipAddress IP address
119 * @param port port number
120 * @return generated endpoint URL
121 */
122 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
123 StringBuilder endpoint = new StringBuilder();
124 String protocol = StringUtils.lowerCase(scheme.name());
125
126 endpoint.append(protocol);
127 endpoint.append(COLON_SLASH);
128 endpoint.append(ipAddress.toString());
129 endpoint.append(COLON);
130 endpoint.append(port);
131
132 return endpoint.toString();
133 }
134
135 /**
Jian Lib230e07c2020-12-21 11:25:12 +0900136 * Generates a DPID (of:0000000000000001) from an index value.
137 *
138 * @param index index value
139 * @return generated DPID
140 */
141 public static String genDpid(long index) {
142 if (index < 0) {
143 return null;
144 }
145
146 String hexStr = Long.toHexString(index);
147
148 StringBuilder zeroPadding = new StringBuilder();
149 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
150 zeroPadding.append(ZERO);
151 }
152
153 return OF_PREFIX + zeroPadding.toString() + hexStr;
154 }
155
156 /**
157 * Generates string format based on the given string length list.
158 *
159 * @param stringLengths a list of string lengths
160 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
161 */
162 public static String genFormatString(List<Integer> stringLengths) {
163 StringBuilder fsb = new StringBuilder();
164 stringLengths.forEach(length -> {
165 fsb.append("%-");
166 fsb.append(length);
167 fsb.append("s");
168 });
169 return fsb.toString();
170 }
171
172 /**
173 * Prints out the JSON string in pretty format.
174 *
175 * @param mapper Object mapper
176 * @param jsonString JSON string
177 * @return pretty formatted JSON string
178 */
179 public static String prettyJson(ObjectMapper mapper, String jsonString) {
180 try {
181 Object jsonObject = mapper.readValue(jsonString, Object.class);
182 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
183 } catch (IOException e) {
184 log.debug("Json string parsing exception caused by {}", e);
185 }
186 return null;
187 }
Jian Liaaf44b52020-12-27 23:22:46 +0900188
189 /**
190 * Obtains workable kubernetes client.
191 *
192 * @param config kubernetes API config
193 * @return kubernetes client
194 */
195 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
196 if (config == null) {
197 log.warn("Kubernetes API server config is empty.");
198 return null;
199 }
200
201 String endpoint = endpoint(config);
202
203 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
204
205 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
206 configBuilder.withTrustCerts(true)
207 .withCaCertData(config.caCertData())
208 .withClientCertData(config.clientCertData())
209 .withClientKeyData(config.clientKeyData());
210
211 if (StringUtils.isNotEmpty(config.token())) {
212 configBuilder.withOauthToken(config.token());
213 }
214 }
215
216 return new DefaultKubernetesClient(configBuilder.build());
217 }
Jian Li4fe40e52021-01-06 03:29:58 +0900218
219 /**
220 * Gets the ovsdb client with supplied openstack node.
221 *
222 * @param node kubernetes node
223 * @param ovsdbPort ovsdb port
224 * @param ovsdbController ovsdb controller
225 * @return ovsdb client
226 */
227 public static OvsdbClientService getOvsdbClient(KubevirtNode node,
228 int ovsdbPort,
229 OvsdbController ovsdbController) {
230 OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
231 return ovsdbController.getOvsdbClient(ovsdb);
232 }
233
234 /**
235 * Checks whether the controller has a connection with an OVSDB that resides
236 * inside the given kubernetes node.
237 *
238 * @param node kubernetes node
239 * @param ovsdbPort OVSDB port
240 * @param ovsdbController OVSDB controller
241 * @param deviceService device service
242 * @return true if the controller is connected to the OVSDB, false otherwise
243 */
244 public static boolean isOvsdbConnected(KubevirtNode node,
245 int ovsdbPort,
246 OvsdbController ovsdbController,
247 DeviceService deviceService) {
248 OvsdbClientService client = getOvsdbClient(node, ovsdbPort, ovsdbController);
249 return deviceService.isAvailable(node.ovsdb()) &&
250 client != null &&
251 client.isConnected();
252 }
253
254 /**
255 * Adds or removes a network interface (aka port) into a given bridge of kubernetes node.
256 *
257 * @param k8sNode kubernetes node
258 * @param bridgeName bridge name
259 * @param intfName interface name
260 * @param deviceService device service
261 * @param addOrRemove add port is true, remove it otherwise
262 */
263 public static synchronized void addOrRemoveSystemInterface(KubevirtNode k8sNode,
264 String bridgeName,
265 String intfName,
266 DeviceService deviceService,
267 boolean addOrRemove) {
268
269
270 Device device = deviceService.getDevice(k8sNode.ovsdb());
271 if (device == null || !device.is(BridgeConfig.class)) {
272 log.info("device is null or this device if not ovsdb device");
273 return;
274 }
275 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
276
277 if (addOrRemove) {
278 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
279 } else {
280 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
281 }
282 }
283
284 /**
285 * Re-structures the OVS port name.
286 * The length of OVS port name should be not large than 15.
287 *
288 * @param portName original port name
289 * @return re-structured OVS port name
290 */
291 public static String structurePortName(String portName) {
292
293 // The size of OVS port name should not be larger than 15
294 if (portName.length() > PORT_NAME_MAX_LENGTH) {
295 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
296 }
297
298 return portName;
299 }
300
301 /**
302 * Gets Boolean property from the propertyName
303 * Return null if propertyName is not found.
304 *
305 * @param properties properties to be looked up
306 * @param propertyName the name of the property to look up
307 * @return value when the propertyName is defined or return null
308 */
309 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
310 String propertyName) {
311 Boolean value;
312 try {
313 String s = get(properties, propertyName);
314 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
315 } catch (ClassCastException e) {
316 value = null;
317 }
318 return value;
319 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900320
321 /**
322 * Returns the kubevirt node from the node.
323 *
324 * @param node a raw node object returned from a k8s client
325 * @return kubevirt node
326 */
327 public static KubevirtNode buildKubevirtNode(Node node) {
328 String hostname = node.getMetadata().getName();
329 IpAddress managementIp = null;
330 IpAddress dataIp = null;
331
332 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
333 if (nodeAddress.getType().equals(INTERNAL_IP)) {
334 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
335 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
336 }
337 }
338
339 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
340 .filter(l -> l.contains(K8S_ROLE))
341 .collect(Collectors.toSet());
342
343 KubevirtNode.Type nodeType = WORKER;
344
345 for (String roleStr : rolesFull) {
346 String role = roleStr.split("/")[1];
347 if (MASTER.name().equalsIgnoreCase(role)) {
348 nodeType = MASTER;
349 break;
350 }
351 }
352
353 // start to parse kubernetes annotation
354 Map<String, String> annots = node.getMetadata().getAnnotations();
355 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
Daniel Park515f5f32021-02-22 17:12:20 +0900356 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
Jian Li4b249702021-02-19 18:13:10 +0900357 String dataIpStr = annots.get(DATA_IP_KEY);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900358 Set<KubevirtPhyInterface> phys = new HashSet<>();
Daniel Park515f5f32021-02-22 17:12:20 +0900359 String gatewayBridgeName = null;
Daniel Park80c6f1b2021-02-18 20:38:46 +0900360 try {
361 if (physnetConfig != null) {
362 JSONArray configJson = new JSONArray(physnetConfig);
363
364 for (int i = 0; i < configJson.length(); i++) {
365 JSONObject object = configJson.getJSONObject(i);
366 String network = object.getString(NETWORK_KEY);
367 String intf = object.getString(INTERFACE_KEY);
368
369 if (network != null && intf != null) {
Daniel Park17fe7982022-04-04 17:48:01 +0900370 String physBridgeId;
371 if (object.has(PHYS_BRIDGE_ID)) {
372 physBridgeId = object.getString(PHYS_BRIDGE_ID);
373 } else {
374 physBridgeId = genDpidFromName(network + intf + hostname);
375 log.trace("host {} physnet dpid for network {} intf {} is null so generate dpid {}",
376 hostname, network, intf, physBridgeId);
377 }
378
Daniel Park80c6f1b2021-02-18 20:38:46 +0900379 phys.add(DefaultKubevirtPhyInterface.builder()
Daniel Park17fe7982022-04-04 17:48:01 +0900380 .network(network)
381 .intf(intf)
382 .physBridge(DeviceId.deviceId(physBridgeId))
383 .build());
Daniel Park80c6f1b2021-02-18 20:38:46 +0900384 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900385 }
386 }
Jian Li4b249702021-02-19 18:13:10 +0900387
388 if (dataIpStr != null) {
389 dataIp = IpAddress.valueOf(dataIpStr);
390 }
Daniel Park515f5f32021-02-22 17:12:20 +0900391
392 if (gatewayConfig != null) {
393 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
394
395 nodeType = GATEWAY;
396 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
397 }
Jian Li9793ec42021-03-19 15:03:32 +0900398 } catch (JSONException | JsonProcessingException e) {
399 log.error("Failed to parse physnet config or gateway config object", e);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900400 }
401
Jian Li1e0f3e82021-06-17 11:06:36 +0900402 // if the node is taint with kubevirt.io key configured,
403 // we mark this node as OTHER type, and do not add it into the cluster
404 NodeSpec spec = node.getSpec();
405 if (spec.getTaints() != null) {
406 for (Taint taint : spec.getTaints()) {
407 String effect = taint.getEffect();
408 String key = taint.getKey();
409 String value = taint.getValue();
410
411 if (StringUtils.equals(effect, NO_SCHEDULE_EFFECT) &&
412 StringUtils.equals(key, KUBEVIRT_IO_KEY) &&
413 StringUtils.equals(value, DRAINING_VALUE)) {
414 nodeType = OTHER;
415 }
416 }
417 }
418
Daniel Park80c6f1b2021-02-18 20:38:46 +0900419 return DefaultKubevirtNode.builder()
420 .hostname(hostname)
421 .managementIp(managementIp)
422 .dataIp(dataIp)
423 .type(nodeType)
424 .state(KubevirtNodeState.ON_BOARDED)
425 .phyIntfs(phys)
Daniel Park515f5f32021-02-22 17:12:20 +0900426 .gatewayBridgeName(gatewayBridgeName)
Daniel Park80c6f1b2021-02-18 20:38:46 +0900427 .build();
428 }
Jian Li94b6d162021-04-15 17:09:11 +0900429
430 /**
Daniel Park17fe7982022-04-04 17:48:01 +0900431 * Generates a unique dpid from given name.
432 *
433 * @param name name
434 * @return device id in string
435 */
436 public static String genDpidFromName(String name) {
437 if (name != null) {
438 String hexString = Integer.toHexString(name.hashCode());
439 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
440 }
441 return null;
442 }
443
444 /**
Jian Li94b6d162021-04-15 17:09:11 +0900445 * Resolve a DNS with the given DNS server and hostname.
446 *
447 * @param hostname hostname to be resolved
448 * @return resolved IP address
449 */
450 public static IpAddress resolveHostname(String hostname) {
451 try {
452 InetAddress addr = Address.getByName(hostname);
453 return IpAddress.valueOf(IpAddress.Version.INET, addr.getAddress());
454 } catch (UnknownHostException e) {
455 log.warn("Failed to resolve IP address of host {}", hostname);
456 }
457 return null;
458 }
Jian Li528335e2021-07-08 20:52:50 +0900459
460 /**
461 * Waits for the given length of time.
462 *
463 * @param timeSecond the amount of time for wait in second unit
464 */
465 public static void waitFor(int timeSecond) {
466 try {
467 Thread.sleep(timeSecond * 1000L);
468 } catch (Exception e) {
469 log.error(e.toString());
470 }
471 }
Jian Lib230e07c2020-12-21 11:25:12 +0900472}