blob: f11547fc42da75e4d76b3c6fabbea0539a91d6f3 [file] [log] [blame]
Jian Li7c4a8822020-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 Park54d8baf2021-02-22 17:12:20 +090018import com.fasterxml.jackson.core.JsonProcessingException;
19import com.fasterxml.jackson.databind.JsonMappingException;
20import com.fasterxml.jackson.databind.JsonNode;
Jian Li7c4a8822020-12-21 11:25:12 +090021import com.fasterxml.jackson.databind.ObjectMapper;
Jian Li138f51f2021-01-06 03:29:58 +090022import com.google.common.base.Strings;
Daniel Park81bd46b2021-02-18 20:38:46 +090023import io.fabric8.kubernetes.api.model.Node;
24import io.fabric8.kubernetes.api.model.NodeAddress;
Jian Li304dca42020-12-27 23:22:46 +090025import io.fabric8.kubernetes.client.ConfigBuilder;
26import io.fabric8.kubernetes.client.DefaultKubernetesClient;
27import io.fabric8.kubernetes.client.KubernetesClient;
Jian Lic4604302020-12-25 02:24:16 +090028import org.apache.commons.lang.StringUtils;
Daniel Park81bd46b2021-02-18 20:38:46 +090029import org.json.JSONArray;
30import org.json.JSONException;
31import org.json.JSONObject;
Jian Lic4604302020-12-25 02:24:16 +090032import org.onlab.packet.IpAddress;
Daniel Park81bd46b2021-02-18 20:38:46 +090033import org.onosproject.kubevirtnode.api.DefaultKubevirtNode;
34import org.onosproject.kubevirtnode.api.DefaultKubevirtPhyInterface;
Jian Lic4604302020-12-25 02:24:16 +090035import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
Jian Li138f51f2021-01-06 03:29:58 +090036import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Park81bd46b2021-02-18 20:38:46 +090037import org.onosproject.kubevirtnode.api.KubevirtNodeState;
38import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
Jian Li138f51f2021-01-06 03:29:58 +090039import org.onosproject.net.Device;
40import org.onosproject.net.behaviour.BridgeConfig;
41import org.onosproject.net.behaviour.BridgeName;
42import org.onosproject.net.device.DeviceService;
43import org.onosproject.ovsdb.controller.OvsdbClientService;
44import org.onosproject.ovsdb.controller.OvsdbController;
45import org.onosproject.ovsdb.controller.OvsdbNodeId;
Jian Li7c4a8822020-12-21 11:25:12 +090046import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
48
49import java.io.IOException;
Jian Li138f51f2021-01-06 03:29:58 +090050import java.util.Dictionary;
Daniel Park81bd46b2021-02-18 20:38:46 +090051import java.util.HashSet;
Jian Li7c4a8822020-12-21 11:25:12 +090052import java.util.List;
Daniel Park81bd46b2021-02-18 20:38:46 +090053import java.util.Map;
54import java.util.Set;
55import java.util.stream.Collectors;
Jian Li7c4a8822020-12-21 11:25:12 +090056
Jian Li138f51f2021-01-06 03:29:58 +090057import static org.onlab.util.Tools.get;
Jian Lib9eb11d2021-02-19 18:13:10 +090058import static org.onosproject.kubevirtnode.api.Constants.SONA_PROJECT_DOMAIN;
Daniel Park54d8baf2021-02-22 17:12:20 +090059import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Daniel Park81bd46b2021-02-18 20:38:46 +090060import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.MASTER;
61import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
Jian Li138f51f2021-01-06 03:29:58 +090062
Jian Li7c4a8822020-12-21 11:25:12 +090063/**
64 * An utility that used in KubeVirt node app.
65 */
66public final class KubevirtNodeUtil {
67
68 private static final Logger log = LoggerFactory.getLogger(KubevirtNodeUtil.class);
69
70 private static final String COLON_SLASH = "://";
71 private static final String COLON = ":";
72
73 private static final int HEX_LENGTH = 16;
74 private static final String OF_PREFIX = "of:";
75 private static final String ZERO = "0";
Daniel Park81bd46b2021-02-18 20:38:46 +090076 private static final String INTERNAL_IP = "InternalIP";
77 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Lib9eb11d2021-02-19 18:13:10 +090078 private static final String PHYSNET_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/physnet-config";
79 private static final String DATA_IP_KEY = SONA_PROJECT_DOMAIN + "/data-ip";
Daniel Park54d8baf2021-02-22 17:12:20 +090080 private static final String GATEWAY_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/gateway-config";
81 private static final String GATEWAY_BRIDGE_NAME = "gatewayBridgeName";
Daniel Park81bd46b2021-02-18 20:38:46 +090082 private static final String NETWORK_KEY = "network";
83 private static final String INTERFACE_KEY = "interface";
Jian Li7c4a8822020-12-21 11:25:12 +090084
Jian Li138f51f2021-01-06 03:29:58 +090085 private static final int PORT_NAME_MAX_LENGTH = 15;
86
Jian Li7c4a8822020-12-21 11:25:12 +090087 /**
88 * Prevents object installation from external.
89 */
90 private KubevirtNodeUtil() {
91 }
92
93 /**
Jian Lic4604302020-12-25 02:24:16 +090094 * Generates endpoint URL by referring to scheme, ipAddress and port.
95 *
96 * @param apiConfig kubernetes API config
97 * @return generated endpoint URL
98 */
99 public static String endpoint(KubevirtApiConfig apiConfig) {
100 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
101 }
102
103 /**
104 * Generates endpoint URL by referring to scheme, ipAddress and port.
105 *
106 * @param scheme scheme
107 * @param ipAddress IP address
108 * @param port port number
109 * @return generated endpoint URL
110 */
111 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
112 StringBuilder endpoint = new StringBuilder();
113 String protocol = StringUtils.lowerCase(scheme.name());
114
115 endpoint.append(protocol);
116 endpoint.append(COLON_SLASH);
117 endpoint.append(ipAddress.toString());
118 endpoint.append(COLON);
119 endpoint.append(port);
120
121 return endpoint.toString();
122 }
123
124 /**
Jian Li7c4a8822020-12-21 11:25:12 +0900125 * Generates a DPID (of:0000000000000001) from an index value.
126 *
127 * @param index index value
128 * @return generated DPID
129 */
130 public static String genDpid(long index) {
131 if (index < 0) {
132 return null;
133 }
134
135 String hexStr = Long.toHexString(index);
136
137 StringBuilder zeroPadding = new StringBuilder();
138 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
139 zeroPadding.append(ZERO);
140 }
141
142 return OF_PREFIX + zeroPadding.toString() + hexStr;
143 }
144
145 /**
146 * Generates string format based on the given string length list.
147 *
148 * @param stringLengths a list of string lengths
149 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
150 */
151 public static String genFormatString(List<Integer> stringLengths) {
152 StringBuilder fsb = new StringBuilder();
153 stringLengths.forEach(length -> {
154 fsb.append("%-");
155 fsb.append(length);
156 fsb.append("s");
157 });
158 return fsb.toString();
159 }
160
161 /**
162 * Prints out the JSON string in pretty format.
163 *
164 * @param mapper Object mapper
165 * @param jsonString JSON string
166 * @return pretty formatted JSON string
167 */
168 public static String prettyJson(ObjectMapper mapper, String jsonString) {
169 try {
170 Object jsonObject = mapper.readValue(jsonString, Object.class);
171 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
172 } catch (IOException e) {
173 log.debug("Json string parsing exception caused by {}", e);
174 }
175 return null;
176 }
Jian Li304dca42020-12-27 23:22:46 +0900177
178 /**
179 * Obtains workable kubernetes client.
180 *
181 * @param config kubernetes API config
182 * @return kubernetes client
183 */
184 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
185 if (config == null) {
186 log.warn("Kubernetes API server config is empty.");
187 return null;
188 }
189
190 String endpoint = endpoint(config);
191
192 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
193
194 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
195 configBuilder.withTrustCerts(true)
196 .withCaCertData(config.caCertData())
197 .withClientCertData(config.clientCertData())
198 .withClientKeyData(config.clientKeyData());
199
200 if (StringUtils.isNotEmpty(config.token())) {
201 configBuilder.withOauthToken(config.token());
202 }
203 }
204
205 return new DefaultKubernetesClient(configBuilder.build());
206 }
Jian Li138f51f2021-01-06 03:29:58 +0900207
208 /**
209 * Gets the ovsdb client with supplied openstack node.
210 *
211 * @param node kubernetes node
212 * @param ovsdbPort ovsdb port
213 * @param ovsdbController ovsdb controller
214 * @return ovsdb client
215 */
216 public static OvsdbClientService getOvsdbClient(KubevirtNode node,
217 int ovsdbPort,
218 OvsdbController ovsdbController) {
219 OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
220 return ovsdbController.getOvsdbClient(ovsdb);
221 }
222
223 /**
224 * Checks whether the controller has a connection with an OVSDB that resides
225 * inside the given kubernetes node.
226 *
227 * @param node kubernetes node
228 * @param ovsdbPort OVSDB port
229 * @param ovsdbController OVSDB controller
230 * @param deviceService device service
231 * @return true if the controller is connected to the OVSDB, false otherwise
232 */
233 public static boolean isOvsdbConnected(KubevirtNode node,
234 int ovsdbPort,
235 OvsdbController ovsdbController,
236 DeviceService deviceService) {
237 OvsdbClientService client = getOvsdbClient(node, ovsdbPort, ovsdbController);
238 return deviceService.isAvailable(node.ovsdb()) &&
239 client != null &&
240 client.isConnected();
241 }
242
243 /**
244 * Adds or removes a network interface (aka port) into a given bridge of kubernetes node.
245 *
246 * @param k8sNode kubernetes node
247 * @param bridgeName bridge name
248 * @param intfName interface name
249 * @param deviceService device service
250 * @param addOrRemove add port is true, remove it otherwise
251 */
252 public static synchronized void addOrRemoveSystemInterface(KubevirtNode k8sNode,
253 String bridgeName,
254 String intfName,
255 DeviceService deviceService,
256 boolean addOrRemove) {
257
258
259 Device device = deviceService.getDevice(k8sNode.ovsdb());
260 if (device == null || !device.is(BridgeConfig.class)) {
261 log.info("device is null or this device if not ovsdb device");
262 return;
263 }
264 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
265
266 if (addOrRemove) {
267 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
268 } else {
269 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
270 }
271 }
272
273 /**
274 * Re-structures the OVS port name.
275 * The length of OVS port name should be not large than 15.
276 *
277 * @param portName original port name
278 * @return re-structured OVS port name
279 */
280 public static String structurePortName(String portName) {
281
282 // The size of OVS port name should not be larger than 15
283 if (portName.length() > PORT_NAME_MAX_LENGTH) {
284 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
285 }
286
287 return portName;
288 }
289
290 /**
291 * Gets Boolean property from the propertyName
292 * Return null if propertyName is not found.
293 *
294 * @param properties properties to be looked up
295 * @param propertyName the name of the property to look up
296 * @return value when the propertyName is defined or return null
297 */
298 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
299 String propertyName) {
300 Boolean value;
301 try {
302 String s = get(properties, propertyName);
303 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
304 } catch (ClassCastException e) {
305 value = null;
306 }
307 return value;
308 }
Daniel Park81bd46b2021-02-18 20:38:46 +0900309
310 /**
311 * Returns the kubevirt node from the node.
312 *
313 * @param node a raw node object returned from a k8s client
314 * @return kubevirt node
315 */
316 public static KubevirtNode buildKubevirtNode(Node node) {
317 String hostname = node.getMetadata().getName();
318 IpAddress managementIp = null;
319 IpAddress dataIp = null;
320
321 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
322 if (nodeAddress.getType().equals(INTERNAL_IP)) {
323 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
324 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
325 }
326 }
327
328 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
329 .filter(l -> l.contains(K8S_ROLE))
330 .collect(Collectors.toSet());
331
332 KubevirtNode.Type nodeType = WORKER;
333
334 for (String roleStr : rolesFull) {
335 String role = roleStr.split("/")[1];
336 if (MASTER.name().equalsIgnoreCase(role)) {
337 nodeType = MASTER;
338 break;
339 }
340 }
341
342 // start to parse kubernetes annotation
343 Map<String, String> annots = node.getMetadata().getAnnotations();
344 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
Daniel Park54d8baf2021-02-22 17:12:20 +0900345 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
Jian Lib9eb11d2021-02-19 18:13:10 +0900346 String dataIpStr = annots.get(DATA_IP_KEY);
Daniel Park81bd46b2021-02-18 20:38:46 +0900347 Set<KubevirtPhyInterface> phys = new HashSet<>();
Daniel Park54d8baf2021-02-22 17:12:20 +0900348 String gatewayBridgeName = null;
Daniel Park81bd46b2021-02-18 20:38:46 +0900349 try {
350 if (physnetConfig != null) {
351 JSONArray configJson = new JSONArray(physnetConfig);
352
353 for (int i = 0; i < configJson.length(); i++) {
354 JSONObject object = configJson.getJSONObject(i);
355 String network = object.getString(NETWORK_KEY);
356 String intf = object.getString(INTERFACE_KEY);
357
358 if (network != null && intf != null) {
359 phys.add(DefaultKubevirtPhyInterface.builder()
360 .network(network).intf(intf).build());
361 }
Daniel Park81bd46b2021-02-18 20:38:46 +0900362 }
363 }
Jian Lib9eb11d2021-02-19 18:13:10 +0900364
365 if (dataIpStr != null) {
366 dataIp = IpAddress.valueOf(dataIpStr);
367 }
Daniel Park54d8baf2021-02-22 17:12:20 +0900368
369 if (gatewayConfig != null) {
370 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
371
372 nodeType = GATEWAY;
373 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
374 }
Daniel Park81bd46b2021-02-18 20:38:46 +0900375 } catch (JSONException e) {
Daniel Park54d8baf2021-02-22 17:12:20 +0900376 log.error("Failed to parse physnet config or gateway config object because of{}", e);
377 } catch (JsonMappingException e) {
378 log.error("Failed to parse physnet config or gateway config object because of{}", e);
379 } catch (JsonProcessingException e) {
380 log.error("Failed to parse physnet config or gateway config object because of{}", e);
Daniel Park81bd46b2021-02-18 20:38:46 +0900381 }
382
383 return DefaultKubevirtNode.builder()
384 .hostname(hostname)
385 .managementIp(managementIp)
386 .dataIp(dataIp)
387 .type(nodeType)
388 .state(KubevirtNodeState.ON_BOARDED)
389 .phyIntfs(phys)
Daniel Park54d8baf2021-02-22 17:12:20 +0900390 .gatewayBridgeName(gatewayBridgeName)
Daniel Park81bd46b2021-02-18 20:38:46 +0900391 .build();
392 }
Jian Li7c4a8822020-12-21 11:25:12 +0900393}