blob: 839695f91208e5e62fa4139a5408ae5232ea4e81 [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 Liaaf44b52020-12-27 23:22:46 +090024import io.fabric8.kubernetes.client.ConfigBuilder;
25import io.fabric8.kubernetes.client.DefaultKubernetesClient;
26import io.fabric8.kubernetes.client.KubernetesClient;
Jian Lif2483072020-12-25 02:24:16 +090027import org.apache.commons.lang.StringUtils;
Daniel Park80c6f1b2021-02-18 20:38:46 +090028import org.json.JSONArray;
29import org.json.JSONException;
30import org.json.JSONObject;
Jian Lif2483072020-12-25 02:24:16 +090031import org.onlab.packet.IpAddress;
Daniel Park80c6f1b2021-02-18 20:38:46 +090032import org.onosproject.kubevirtnode.api.DefaultKubevirtNode;
33import org.onosproject.kubevirtnode.api.DefaultKubevirtPhyInterface;
Jian Lif2483072020-12-25 02:24:16 +090034import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
Jian Li4fe40e52021-01-06 03:29:58 +090035import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Park80c6f1b2021-02-18 20:38:46 +090036import org.onosproject.kubevirtnode.api.KubevirtNodeState;
37import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
Jian Li4fe40e52021-01-06 03:29:58 +090038import org.onosproject.net.Device;
39import org.onosproject.net.behaviour.BridgeConfig;
40import org.onosproject.net.behaviour.BridgeName;
41import org.onosproject.net.device.DeviceService;
42import org.onosproject.ovsdb.controller.OvsdbClientService;
43import org.onosproject.ovsdb.controller.OvsdbController;
44import org.onosproject.ovsdb.controller.OvsdbNodeId;
Jian Lib230e07c2020-12-21 11:25:12 +090045import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
Jian Li94b6d162021-04-15 17:09:11 +090047import org.xbill.DNS.Address;
Jian Lib230e07c2020-12-21 11:25:12 +090048
49import java.io.IOException;
Jian Li94b6d162021-04-15 17:09:11 +090050import java.net.InetAddress;
51import java.net.UnknownHostException;
Jian Li4fe40e52021-01-06 03:29:58 +090052import java.util.Dictionary;
Daniel Park80c6f1b2021-02-18 20:38:46 +090053import java.util.HashSet;
Jian Lib230e07c2020-12-21 11:25:12 +090054import java.util.List;
Daniel Park80c6f1b2021-02-18 20:38:46 +090055import java.util.Map;
56import java.util.Set;
57import java.util.stream.Collectors;
Jian Lib230e07c2020-12-21 11:25:12 +090058
Jian Li4fe40e52021-01-06 03:29:58 +090059import static org.onlab.util.Tools.get;
Jian Li4b249702021-02-19 18:13:10 +090060import static org.onosproject.kubevirtnode.api.Constants.SONA_PROJECT_DOMAIN;
Daniel Park515f5f32021-02-22 17:12:20 +090061import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Daniel Park80c6f1b2021-02-18 20:38:46 +090062import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.MASTER;
63import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
Jian Li4fe40e52021-01-06 03:29:58 +090064
Jian Lib230e07c2020-12-21 11:25:12 +090065/**
66 * An utility that used in KubeVirt node app.
67 */
68public final class KubevirtNodeUtil {
69
70 private static final Logger log = LoggerFactory.getLogger(KubevirtNodeUtil.class);
71
72 private static final String COLON_SLASH = "://";
73 private static final String COLON = ":";
74
75 private static final int HEX_LENGTH = 16;
76 private static final String OF_PREFIX = "of:";
77 private static final String ZERO = "0";
Daniel Park80c6f1b2021-02-18 20:38:46 +090078 private static final String INTERNAL_IP = "InternalIP";
79 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li4b249702021-02-19 18:13:10 +090080 private static final String PHYSNET_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/physnet-config";
81 private static final String DATA_IP_KEY = SONA_PROJECT_DOMAIN + "/data-ip";
Daniel Park515f5f32021-02-22 17:12:20 +090082 private static final String GATEWAY_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/gateway-config";
83 private static final String GATEWAY_BRIDGE_NAME = "gatewayBridgeName";
Daniel Park80c6f1b2021-02-18 20:38:46 +090084 private static final String NETWORK_KEY = "network";
85 private static final String INTERFACE_KEY = "interface";
Jian Lib230e07c2020-12-21 11:25:12 +090086
Jian Li4fe40e52021-01-06 03:29:58 +090087 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li94b6d162021-04-15 17:09:11 +090088 private static final int DNS_DEFAULT_PORT = 53;
Jian Li4fe40e52021-01-06 03:29:58 +090089
Jian Lib230e07c2020-12-21 11:25:12 +090090 /**
91 * Prevents object installation from external.
92 */
93 private KubevirtNodeUtil() {
94 }
95
96 /**
Jian Lif2483072020-12-25 02:24:16 +090097 * Generates endpoint URL by referring to scheme, ipAddress and port.
98 *
99 * @param apiConfig kubernetes API config
100 * @return generated endpoint URL
101 */
102 public static String endpoint(KubevirtApiConfig apiConfig) {
103 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
104 }
105
106 /**
107 * Generates endpoint URL by referring to scheme, ipAddress and port.
108 *
109 * @param scheme scheme
110 * @param ipAddress IP address
111 * @param port port number
112 * @return generated endpoint URL
113 */
114 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
115 StringBuilder endpoint = new StringBuilder();
116 String protocol = StringUtils.lowerCase(scheme.name());
117
118 endpoint.append(protocol);
119 endpoint.append(COLON_SLASH);
120 endpoint.append(ipAddress.toString());
121 endpoint.append(COLON);
122 endpoint.append(port);
123
124 return endpoint.toString();
125 }
126
127 /**
Jian Lib230e07c2020-12-21 11:25:12 +0900128 * Generates a DPID (of:0000000000000001) from an index value.
129 *
130 * @param index index value
131 * @return generated DPID
132 */
133 public static String genDpid(long index) {
134 if (index < 0) {
135 return null;
136 }
137
138 String hexStr = Long.toHexString(index);
139
140 StringBuilder zeroPadding = new StringBuilder();
141 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
142 zeroPadding.append(ZERO);
143 }
144
145 return OF_PREFIX + zeroPadding.toString() + hexStr;
146 }
147
148 /**
149 * Generates string format based on the given string length list.
150 *
151 * @param stringLengths a list of string lengths
152 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
153 */
154 public static String genFormatString(List<Integer> stringLengths) {
155 StringBuilder fsb = new StringBuilder();
156 stringLengths.forEach(length -> {
157 fsb.append("%-");
158 fsb.append(length);
159 fsb.append("s");
160 });
161 return fsb.toString();
162 }
163
164 /**
165 * Prints out the JSON string in pretty format.
166 *
167 * @param mapper Object mapper
168 * @param jsonString JSON string
169 * @return pretty formatted JSON string
170 */
171 public static String prettyJson(ObjectMapper mapper, String jsonString) {
172 try {
173 Object jsonObject = mapper.readValue(jsonString, Object.class);
174 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
175 } catch (IOException e) {
176 log.debug("Json string parsing exception caused by {}", e);
177 }
178 return null;
179 }
Jian Liaaf44b52020-12-27 23:22:46 +0900180
181 /**
182 * Obtains workable kubernetes client.
183 *
184 * @param config kubernetes API config
185 * @return kubernetes client
186 */
187 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
188 if (config == null) {
189 log.warn("Kubernetes API server config is empty.");
190 return null;
191 }
192
193 String endpoint = endpoint(config);
194
195 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
196
197 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
198 configBuilder.withTrustCerts(true)
199 .withCaCertData(config.caCertData())
200 .withClientCertData(config.clientCertData())
201 .withClientKeyData(config.clientKeyData());
202
203 if (StringUtils.isNotEmpty(config.token())) {
204 configBuilder.withOauthToken(config.token());
205 }
206 }
207
208 return new DefaultKubernetesClient(configBuilder.build());
209 }
Jian Li4fe40e52021-01-06 03:29:58 +0900210
211 /**
212 * Gets the ovsdb client with supplied openstack node.
213 *
214 * @param node kubernetes node
215 * @param ovsdbPort ovsdb port
216 * @param ovsdbController ovsdb controller
217 * @return ovsdb client
218 */
219 public static OvsdbClientService getOvsdbClient(KubevirtNode node,
220 int ovsdbPort,
221 OvsdbController ovsdbController) {
222 OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
223 return ovsdbController.getOvsdbClient(ovsdb);
224 }
225
226 /**
227 * Checks whether the controller has a connection with an OVSDB that resides
228 * inside the given kubernetes node.
229 *
230 * @param node kubernetes node
231 * @param ovsdbPort OVSDB port
232 * @param ovsdbController OVSDB controller
233 * @param deviceService device service
234 * @return true if the controller is connected to the OVSDB, false otherwise
235 */
236 public static boolean isOvsdbConnected(KubevirtNode node,
237 int ovsdbPort,
238 OvsdbController ovsdbController,
239 DeviceService deviceService) {
240 OvsdbClientService client = getOvsdbClient(node, ovsdbPort, ovsdbController);
241 return deviceService.isAvailable(node.ovsdb()) &&
242 client != null &&
243 client.isConnected();
244 }
245
246 /**
247 * Adds or removes a network interface (aka port) into a given bridge of kubernetes node.
248 *
249 * @param k8sNode kubernetes node
250 * @param bridgeName bridge name
251 * @param intfName interface name
252 * @param deviceService device service
253 * @param addOrRemove add port is true, remove it otherwise
254 */
255 public static synchronized void addOrRemoveSystemInterface(KubevirtNode k8sNode,
256 String bridgeName,
257 String intfName,
258 DeviceService deviceService,
259 boolean addOrRemove) {
260
261
262 Device device = deviceService.getDevice(k8sNode.ovsdb());
263 if (device == null || !device.is(BridgeConfig.class)) {
264 log.info("device is null or this device if not ovsdb device");
265 return;
266 }
267 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
268
269 if (addOrRemove) {
270 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
271 } else {
272 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
273 }
274 }
275
276 /**
277 * Re-structures the OVS port name.
278 * The length of OVS port name should be not large than 15.
279 *
280 * @param portName original port name
281 * @return re-structured OVS port name
282 */
283 public static String structurePortName(String portName) {
284
285 // The size of OVS port name should not be larger than 15
286 if (portName.length() > PORT_NAME_MAX_LENGTH) {
287 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
288 }
289
290 return portName;
291 }
292
293 /**
294 * Gets Boolean property from the propertyName
295 * Return null if propertyName is not found.
296 *
297 * @param properties properties to be looked up
298 * @param propertyName the name of the property to look up
299 * @return value when the propertyName is defined or return null
300 */
301 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
302 String propertyName) {
303 Boolean value;
304 try {
305 String s = get(properties, propertyName);
306 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
307 } catch (ClassCastException e) {
308 value = null;
309 }
310 return value;
311 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900312
313 /**
314 * Returns the kubevirt node from the node.
315 *
316 * @param node a raw node object returned from a k8s client
317 * @return kubevirt node
318 */
319 public static KubevirtNode buildKubevirtNode(Node node) {
320 String hostname = node.getMetadata().getName();
321 IpAddress managementIp = null;
322 IpAddress dataIp = null;
323
324 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
325 if (nodeAddress.getType().equals(INTERNAL_IP)) {
326 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
327 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
328 }
329 }
330
331 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
332 .filter(l -> l.contains(K8S_ROLE))
333 .collect(Collectors.toSet());
334
335 KubevirtNode.Type nodeType = WORKER;
336
337 for (String roleStr : rolesFull) {
338 String role = roleStr.split("/")[1];
339 if (MASTER.name().equalsIgnoreCase(role)) {
340 nodeType = MASTER;
341 break;
342 }
343 }
344
345 // start to parse kubernetes annotation
346 Map<String, String> annots = node.getMetadata().getAnnotations();
347 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
Daniel Park515f5f32021-02-22 17:12:20 +0900348 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
Jian Li4b249702021-02-19 18:13:10 +0900349 String dataIpStr = annots.get(DATA_IP_KEY);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900350 Set<KubevirtPhyInterface> phys = new HashSet<>();
Daniel Park515f5f32021-02-22 17:12:20 +0900351 String gatewayBridgeName = null;
Daniel Park80c6f1b2021-02-18 20:38:46 +0900352 try {
353 if (physnetConfig != null) {
354 JSONArray configJson = new JSONArray(physnetConfig);
355
356 for (int i = 0; i < configJson.length(); i++) {
357 JSONObject object = configJson.getJSONObject(i);
358 String network = object.getString(NETWORK_KEY);
359 String intf = object.getString(INTERFACE_KEY);
360
361 if (network != null && intf != null) {
362 phys.add(DefaultKubevirtPhyInterface.builder()
363 .network(network).intf(intf).build());
364 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900365 }
366 }
Jian Li4b249702021-02-19 18:13:10 +0900367
368 if (dataIpStr != null) {
369 dataIp = IpAddress.valueOf(dataIpStr);
370 }
Daniel Park515f5f32021-02-22 17:12:20 +0900371
372 if (gatewayConfig != null) {
373 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
374
375 nodeType = GATEWAY;
376 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
377 }
Jian Li9793ec42021-03-19 15:03:32 +0900378 } catch (JSONException | JsonProcessingException e) {
379 log.error("Failed to parse physnet config or gateway config object", e);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900380 }
381
382 return DefaultKubevirtNode.builder()
383 .hostname(hostname)
384 .managementIp(managementIp)
385 .dataIp(dataIp)
386 .type(nodeType)
387 .state(KubevirtNodeState.ON_BOARDED)
388 .phyIntfs(phys)
Daniel Park515f5f32021-02-22 17:12:20 +0900389 .gatewayBridgeName(gatewayBridgeName)
Daniel Park80c6f1b2021-02-18 20:38:46 +0900390 .build();
391 }
Jian Li94b6d162021-04-15 17:09:11 +0900392
393 /**
394 * Resolve a DNS with the given DNS server and hostname.
395 *
396 * @param hostname hostname to be resolved
397 * @return resolved IP address
398 */
399 public static IpAddress resolveHostname(String hostname) {
400 try {
401 InetAddress addr = Address.getByName(hostname);
402 return IpAddress.valueOf(IpAddress.Version.INET, addr.getAddress());
403 } catch (UnknownHostException e) {
404 log.warn("Failed to resolve IP address of host {}", hostname);
405 }
406 return null;
407 }
Jian Lib230e07c2020-12-21 11:25:12 +0900408}