blob: 66b1af87ada0875a8e83d116c174c92b669bdb0b [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;
41import 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";
Jian Lib230e07c2020-12-21 11:25:12 +090089
Jian Li4fe40e52021-01-06 03:29:58 +090090 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li1e0f3e82021-06-17 11:06:36 +090091
92 private static final String NO_SCHEDULE_EFFECT = "NoSchedule";
93 private static final String KUBEVIRT_IO_KEY = "kubevirt.io/drain";
94 private static final String DRAINING_VALUE = "draining";
Jian Li4fe40e52021-01-06 03:29:58 +090095
Jian Lib230e07c2020-12-21 11:25:12 +090096 /**
97 * Prevents object installation from external.
98 */
99 private KubevirtNodeUtil() {
100 }
101
102 /**
Jian Lif2483072020-12-25 02:24:16 +0900103 * Generates endpoint URL by referring to scheme, ipAddress and port.
104 *
105 * @param apiConfig kubernetes API config
106 * @return generated endpoint URL
107 */
108 public static String endpoint(KubevirtApiConfig apiConfig) {
109 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
110 }
111
112 /**
113 * Generates endpoint URL by referring to scheme, ipAddress and port.
114 *
115 * @param scheme scheme
116 * @param ipAddress IP address
117 * @param port port number
118 * @return generated endpoint URL
119 */
120 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
121 StringBuilder endpoint = new StringBuilder();
122 String protocol = StringUtils.lowerCase(scheme.name());
123
124 endpoint.append(protocol);
125 endpoint.append(COLON_SLASH);
126 endpoint.append(ipAddress.toString());
127 endpoint.append(COLON);
128 endpoint.append(port);
129
130 return endpoint.toString();
131 }
132
133 /**
Jian Lib230e07c2020-12-21 11:25:12 +0900134 * Generates a DPID (of:0000000000000001) from an index value.
135 *
136 * @param index index value
137 * @return generated DPID
138 */
139 public static String genDpid(long index) {
140 if (index < 0) {
141 return null;
142 }
143
144 String hexStr = Long.toHexString(index);
145
146 StringBuilder zeroPadding = new StringBuilder();
147 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
148 zeroPadding.append(ZERO);
149 }
150
151 return OF_PREFIX + zeroPadding.toString() + hexStr;
152 }
153
154 /**
155 * Generates string format based on the given string length list.
156 *
157 * @param stringLengths a list of string lengths
158 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
159 */
160 public static String genFormatString(List<Integer> stringLengths) {
161 StringBuilder fsb = new StringBuilder();
162 stringLengths.forEach(length -> {
163 fsb.append("%-");
164 fsb.append(length);
165 fsb.append("s");
166 });
167 return fsb.toString();
168 }
169
170 /**
171 * Prints out the JSON string in pretty format.
172 *
173 * @param mapper Object mapper
174 * @param jsonString JSON string
175 * @return pretty formatted JSON string
176 */
177 public static String prettyJson(ObjectMapper mapper, String jsonString) {
178 try {
179 Object jsonObject = mapper.readValue(jsonString, Object.class);
180 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
181 } catch (IOException e) {
182 log.debug("Json string parsing exception caused by {}", e);
183 }
184 return null;
185 }
Jian Liaaf44b52020-12-27 23:22:46 +0900186
187 /**
188 * Obtains workable kubernetes client.
189 *
190 * @param config kubernetes API config
191 * @return kubernetes client
192 */
193 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
194 if (config == null) {
195 log.warn("Kubernetes API server config is empty.");
196 return null;
197 }
198
199 String endpoint = endpoint(config);
200
201 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
202
203 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
204 configBuilder.withTrustCerts(true)
205 .withCaCertData(config.caCertData())
206 .withClientCertData(config.clientCertData())
207 .withClientKeyData(config.clientKeyData());
208
209 if (StringUtils.isNotEmpty(config.token())) {
210 configBuilder.withOauthToken(config.token());
211 }
212 }
213
214 return new DefaultKubernetesClient(configBuilder.build());
215 }
Jian Li4fe40e52021-01-06 03:29:58 +0900216
217 /**
218 * Gets the ovsdb client with supplied openstack node.
219 *
220 * @param node kubernetes node
221 * @param ovsdbPort ovsdb port
222 * @param ovsdbController ovsdb controller
223 * @return ovsdb client
224 */
225 public static OvsdbClientService getOvsdbClient(KubevirtNode node,
226 int ovsdbPort,
227 OvsdbController ovsdbController) {
228 OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
229 return ovsdbController.getOvsdbClient(ovsdb);
230 }
231
232 /**
233 * Checks whether the controller has a connection with an OVSDB that resides
234 * inside the given kubernetes node.
235 *
236 * @param node kubernetes node
237 * @param ovsdbPort OVSDB port
238 * @param ovsdbController OVSDB controller
239 * @param deviceService device service
240 * @return true if the controller is connected to the OVSDB, false otherwise
241 */
242 public static boolean isOvsdbConnected(KubevirtNode node,
243 int ovsdbPort,
244 OvsdbController ovsdbController,
245 DeviceService deviceService) {
246 OvsdbClientService client = getOvsdbClient(node, ovsdbPort, ovsdbController);
247 return deviceService.isAvailable(node.ovsdb()) &&
248 client != null &&
249 client.isConnected();
250 }
251
252 /**
253 * Adds or removes a network interface (aka port) into a given bridge of kubernetes node.
254 *
255 * @param k8sNode kubernetes node
256 * @param bridgeName bridge name
257 * @param intfName interface name
258 * @param deviceService device service
259 * @param addOrRemove add port is true, remove it otherwise
260 */
261 public static synchronized void addOrRemoveSystemInterface(KubevirtNode k8sNode,
262 String bridgeName,
263 String intfName,
264 DeviceService deviceService,
265 boolean addOrRemove) {
266
267
268 Device device = deviceService.getDevice(k8sNode.ovsdb());
269 if (device == null || !device.is(BridgeConfig.class)) {
270 log.info("device is null or this device if not ovsdb device");
271 return;
272 }
273 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
274
275 if (addOrRemove) {
276 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
277 } else {
278 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
279 }
280 }
281
282 /**
283 * Re-structures the OVS port name.
284 * The length of OVS port name should be not large than 15.
285 *
286 * @param portName original port name
287 * @return re-structured OVS port name
288 */
289 public static String structurePortName(String portName) {
290
291 // The size of OVS port name should not be larger than 15
292 if (portName.length() > PORT_NAME_MAX_LENGTH) {
293 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
294 }
295
296 return portName;
297 }
298
299 /**
300 * Gets Boolean property from the propertyName
301 * Return null if propertyName is not found.
302 *
303 * @param properties properties to be looked up
304 * @param propertyName the name of the property to look up
305 * @return value when the propertyName is defined or return null
306 */
307 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
308 String propertyName) {
309 Boolean value;
310 try {
311 String s = get(properties, propertyName);
312 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
313 } catch (ClassCastException e) {
314 value = null;
315 }
316 return value;
317 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900318
319 /**
320 * Returns the kubevirt node from the node.
321 *
322 * @param node a raw node object returned from a k8s client
323 * @return kubevirt node
324 */
325 public static KubevirtNode buildKubevirtNode(Node node) {
326 String hostname = node.getMetadata().getName();
327 IpAddress managementIp = null;
328 IpAddress dataIp = null;
329
330 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
331 if (nodeAddress.getType().equals(INTERNAL_IP)) {
332 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
333 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
334 }
335 }
336
337 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
338 .filter(l -> l.contains(K8S_ROLE))
339 .collect(Collectors.toSet());
340
341 KubevirtNode.Type nodeType = WORKER;
342
343 for (String roleStr : rolesFull) {
344 String role = roleStr.split("/")[1];
345 if (MASTER.name().equalsIgnoreCase(role)) {
346 nodeType = MASTER;
347 break;
348 }
349 }
350
351 // start to parse kubernetes annotation
352 Map<String, String> annots = node.getMetadata().getAnnotations();
353 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
Daniel Park515f5f32021-02-22 17:12:20 +0900354 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
Jian Li4b249702021-02-19 18:13:10 +0900355 String dataIpStr = annots.get(DATA_IP_KEY);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900356 Set<KubevirtPhyInterface> phys = new HashSet<>();
Daniel Park515f5f32021-02-22 17:12:20 +0900357 String gatewayBridgeName = null;
Daniel Park80c6f1b2021-02-18 20:38:46 +0900358 try {
359 if (physnetConfig != null) {
360 JSONArray configJson = new JSONArray(physnetConfig);
361
362 for (int i = 0; i < configJson.length(); i++) {
363 JSONObject object = configJson.getJSONObject(i);
364 String network = object.getString(NETWORK_KEY);
365 String intf = object.getString(INTERFACE_KEY);
366
367 if (network != null && intf != null) {
368 phys.add(DefaultKubevirtPhyInterface.builder()
369 .network(network).intf(intf).build());
370 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900371 }
372 }
Jian Li4b249702021-02-19 18:13:10 +0900373
374 if (dataIpStr != null) {
375 dataIp = IpAddress.valueOf(dataIpStr);
376 }
Daniel Park515f5f32021-02-22 17:12:20 +0900377
378 if (gatewayConfig != null) {
379 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
380
381 nodeType = GATEWAY;
382 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
383 }
Jian Li9793ec42021-03-19 15:03:32 +0900384 } catch (JSONException | JsonProcessingException e) {
385 log.error("Failed to parse physnet config or gateway config object", e);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900386 }
387
Jian Li1e0f3e82021-06-17 11:06:36 +0900388 // if the node is taint with kubevirt.io key configured,
389 // we mark this node as OTHER type, and do not add it into the cluster
390 NodeSpec spec = node.getSpec();
391 if (spec.getTaints() != null) {
392 for (Taint taint : spec.getTaints()) {
393 String effect = taint.getEffect();
394 String key = taint.getKey();
395 String value = taint.getValue();
396
397 if (StringUtils.equals(effect, NO_SCHEDULE_EFFECT) &&
398 StringUtils.equals(key, KUBEVIRT_IO_KEY) &&
399 StringUtils.equals(value, DRAINING_VALUE)) {
400 nodeType = OTHER;
401 }
402 }
403 }
404
Daniel Park80c6f1b2021-02-18 20:38:46 +0900405 return DefaultKubevirtNode.builder()
406 .hostname(hostname)
407 .managementIp(managementIp)
408 .dataIp(dataIp)
409 .type(nodeType)
410 .state(KubevirtNodeState.ON_BOARDED)
411 .phyIntfs(phys)
Daniel Park515f5f32021-02-22 17:12:20 +0900412 .gatewayBridgeName(gatewayBridgeName)
Daniel Park80c6f1b2021-02-18 20:38:46 +0900413 .build();
414 }
Jian Li94b6d162021-04-15 17:09:11 +0900415
416 /**
417 * Resolve a DNS with the given DNS server and hostname.
418 *
419 * @param hostname hostname to be resolved
420 * @return resolved IP address
421 */
422 public static IpAddress resolveHostname(String hostname) {
423 try {
424 InetAddress addr = Address.getByName(hostname);
425 return IpAddress.valueOf(IpAddress.Version.INET, addr.getAddress());
426 } catch (UnknownHostException e) {
427 log.warn("Failed to resolve IP address of host {}", hostname);
428 }
429 return null;
430 }
Jian Li528335e2021-07-08 20:52:50 +0900431
432 /**
433 * Waits for the given length of time.
434 *
435 * @param timeSecond the amount of time for wait in second unit
436 */
437 public static void waitFor(int timeSecond) {
438 try {
439 Thread.sleep(timeSecond * 1000L);
440 } catch (Exception e) {
441 log.error(e.toString());
442 }
443 }
Jian Lib230e07c2020-12-21 11:25:12 +0900444}