blob: a4b0abb1cbd01dd3629181ba50ae29f3dabc494d [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;
47
48import java.io.IOException;
Jian Li4fe40e52021-01-06 03:29:58 +090049import java.util.Dictionary;
Daniel Park80c6f1b2021-02-18 20:38:46 +090050import java.util.HashSet;
Jian Lib230e07c2020-12-21 11:25:12 +090051import java.util.List;
Daniel Park80c6f1b2021-02-18 20:38:46 +090052import java.util.Map;
53import java.util.Set;
54import java.util.stream.Collectors;
Jian Lib230e07c2020-12-21 11:25:12 +090055
Jian Li4fe40e52021-01-06 03:29:58 +090056import static org.onlab.util.Tools.get;
Jian Li4b249702021-02-19 18:13:10 +090057import static org.onosproject.kubevirtnode.api.Constants.SONA_PROJECT_DOMAIN;
Daniel Park515f5f32021-02-22 17:12:20 +090058import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Daniel Park80c6f1b2021-02-18 20:38:46 +090059import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.MASTER;
60import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
Jian Li4fe40e52021-01-06 03:29:58 +090061
Jian Lib230e07c2020-12-21 11:25:12 +090062/**
63 * An utility that used in KubeVirt node app.
64 */
65public final class KubevirtNodeUtil {
66
67 private static final Logger log = LoggerFactory.getLogger(KubevirtNodeUtil.class);
68
69 private static final String COLON_SLASH = "://";
70 private static final String COLON = ":";
71
72 private static final int HEX_LENGTH = 16;
73 private static final String OF_PREFIX = "of:";
74 private static final String ZERO = "0";
Daniel Park80c6f1b2021-02-18 20:38:46 +090075 private static final String INTERNAL_IP = "InternalIP";
76 private static final String K8S_ROLE = "node-role.kubernetes.io";
Jian Li4b249702021-02-19 18:13:10 +090077 private static final String PHYSNET_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/physnet-config";
78 private static final String DATA_IP_KEY = SONA_PROJECT_DOMAIN + "/data-ip";
Daniel Park515f5f32021-02-22 17:12:20 +090079 private static final String GATEWAY_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/gateway-config";
80 private static final String GATEWAY_BRIDGE_NAME = "gatewayBridgeName";
Daniel Park80c6f1b2021-02-18 20:38:46 +090081 private static final String NETWORK_KEY = "network";
82 private static final String INTERFACE_KEY = "interface";
Jian Lib230e07c2020-12-21 11:25:12 +090083
Jian Li4fe40e52021-01-06 03:29:58 +090084 private static final int PORT_NAME_MAX_LENGTH = 15;
85
Jian Lib230e07c2020-12-21 11:25:12 +090086 /**
87 * Prevents object installation from external.
88 */
89 private KubevirtNodeUtil() {
90 }
91
92 /**
Jian Lif2483072020-12-25 02:24:16 +090093 * Generates endpoint URL by referring to scheme, ipAddress and port.
94 *
95 * @param apiConfig kubernetes API config
96 * @return generated endpoint URL
97 */
98 public static String endpoint(KubevirtApiConfig apiConfig) {
99 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
100 }
101
102 /**
103 * Generates endpoint URL by referring to scheme, ipAddress and port.
104 *
105 * @param scheme scheme
106 * @param ipAddress IP address
107 * @param port port number
108 * @return generated endpoint URL
109 */
110 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
111 StringBuilder endpoint = new StringBuilder();
112 String protocol = StringUtils.lowerCase(scheme.name());
113
114 endpoint.append(protocol);
115 endpoint.append(COLON_SLASH);
116 endpoint.append(ipAddress.toString());
117 endpoint.append(COLON);
118 endpoint.append(port);
119
120 return endpoint.toString();
121 }
122
123 /**
Jian Lib230e07c2020-12-21 11:25:12 +0900124 * Generates a DPID (of:0000000000000001) from an index value.
125 *
126 * @param index index value
127 * @return generated DPID
128 */
129 public static String genDpid(long index) {
130 if (index < 0) {
131 return null;
132 }
133
134 String hexStr = Long.toHexString(index);
135
136 StringBuilder zeroPadding = new StringBuilder();
137 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
138 zeroPadding.append(ZERO);
139 }
140
141 return OF_PREFIX + zeroPadding.toString() + hexStr;
142 }
143
144 /**
145 * Generates string format based on the given string length list.
146 *
147 * @param stringLengths a list of string lengths
148 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
149 */
150 public static String genFormatString(List<Integer> stringLengths) {
151 StringBuilder fsb = new StringBuilder();
152 stringLengths.forEach(length -> {
153 fsb.append("%-");
154 fsb.append(length);
155 fsb.append("s");
156 });
157 return fsb.toString();
158 }
159
160 /**
161 * Prints out the JSON string in pretty format.
162 *
163 * @param mapper Object mapper
164 * @param jsonString JSON string
165 * @return pretty formatted JSON string
166 */
167 public static String prettyJson(ObjectMapper mapper, String jsonString) {
168 try {
169 Object jsonObject = mapper.readValue(jsonString, Object.class);
170 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
171 } catch (IOException e) {
172 log.debug("Json string parsing exception caused by {}", e);
173 }
174 return null;
175 }
Jian Liaaf44b52020-12-27 23:22:46 +0900176
177 /**
178 * Obtains workable kubernetes client.
179 *
180 * @param config kubernetes API config
181 * @return kubernetes client
182 */
183 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
184 if (config == null) {
185 log.warn("Kubernetes API server config is empty.");
186 return null;
187 }
188
189 String endpoint = endpoint(config);
190
191 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
192
193 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
194 configBuilder.withTrustCerts(true)
195 .withCaCertData(config.caCertData())
196 .withClientCertData(config.clientCertData())
197 .withClientKeyData(config.clientKeyData());
198
199 if (StringUtils.isNotEmpty(config.token())) {
200 configBuilder.withOauthToken(config.token());
201 }
202 }
203
204 return new DefaultKubernetesClient(configBuilder.build());
205 }
Jian Li4fe40e52021-01-06 03:29:58 +0900206
207 /**
208 * Gets the ovsdb client with supplied openstack node.
209 *
210 * @param node kubernetes node
211 * @param ovsdbPort ovsdb port
212 * @param ovsdbController ovsdb controller
213 * @return ovsdb client
214 */
215 public static OvsdbClientService getOvsdbClient(KubevirtNode node,
216 int ovsdbPort,
217 OvsdbController ovsdbController) {
218 OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
219 return ovsdbController.getOvsdbClient(ovsdb);
220 }
221
222 /**
223 * Checks whether the controller has a connection with an OVSDB that resides
224 * inside the given kubernetes node.
225 *
226 * @param node kubernetes node
227 * @param ovsdbPort OVSDB port
228 * @param ovsdbController OVSDB controller
229 * @param deviceService device service
230 * @return true if the controller is connected to the OVSDB, false otherwise
231 */
232 public static boolean isOvsdbConnected(KubevirtNode node,
233 int ovsdbPort,
234 OvsdbController ovsdbController,
235 DeviceService deviceService) {
236 OvsdbClientService client = getOvsdbClient(node, ovsdbPort, ovsdbController);
237 return deviceService.isAvailable(node.ovsdb()) &&
238 client != null &&
239 client.isConnected();
240 }
241
242 /**
243 * Adds or removes a network interface (aka port) into a given bridge of kubernetes node.
244 *
245 * @param k8sNode kubernetes node
246 * @param bridgeName bridge name
247 * @param intfName interface name
248 * @param deviceService device service
249 * @param addOrRemove add port is true, remove it otherwise
250 */
251 public static synchronized void addOrRemoveSystemInterface(KubevirtNode k8sNode,
252 String bridgeName,
253 String intfName,
254 DeviceService deviceService,
255 boolean addOrRemove) {
256
257
258 Device device = deviceService.getDevice(k8sNode.ovsdb());
259 if (device == null || !device.is(BridgeConfig.class)) {
260 log.info("device is null or this device if not ovsdb device");
261 return;
262 }
263 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
264
265 if (addOrRemove) {
266 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
267 } else {
268 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
269 }
270 }
271
272 /**
273 * Re-structures the OVS port name.
274 * The length of OVS port name should be not large than 15.
275 *
276 * @param portName original port name
277 * @return re-structured OVS port name
278 */
279 public static String structurePortName(String portName) {
280
281 // The size of OVS port name should not be larger than 15
282 if (portName.length() > PORT_NAME_MAX_LENGTH) {
283 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
284 }
285
286 return portName;
287 }
288
289 /**
290 * Gets Boolean property from the propertyName
291 * Return null if propertyName is not found.
292 *
293 * @param properties properties to be looked up
294 * @param propertyName the name of the property to look up
295 * @return value when the propertyName is defined or return null
296 */
297 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
298 String propertyName) {
299 Boolean value;
300 try {
301 String s = get(properties, propertyName);
302 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
303 } catch (ClassCastException e) {
304 value = null;
305 }
306 return value;
307 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900308
309 /**
310 * Returns the kubevirt node from the node.
311 *
312 * @param node a raw node object returned from a k8s client
313 * @return kubevirt node
314 */
315 public static KubevirtNode buildKubevirtNode(Node node) {
316 String hostname = node.getMetadata().getName();
317 IpAddress managementIp = null;
318 IpAddress dataIp = null;
319
320 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
321 if (nodeAddress.getType().equals(INTERNAL_IP)) {
322 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
323 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
324 }
325 }
326
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 // start to parse kubernetes annotation
342 Map<String, String> annots = node.getMetadata().getAnnotations();
343 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
Daniel Park515f5f32021-02-22 17:12:20 +0900344 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
Jian Li4b249702021-02-19 18:13:10 +0900345 String dataIpStr = annots.get(DATA_IP_KEY);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900346 Set<KubevirtPhyInterface> phys = new HashSet<>();
Daniel Park515f5f32021-02-22 17:12:20 +0900347 String gatewayBridgeName = null;
Daniel Park80c6f1b2021-02-18 20:38:46 +0900348 try {
349 if (physnetConfig != null) {
350 JSONArray configJson = new JSONArray(physnetConfig);
351
352 for (int i = 0; i < configJson.length(); i++) {
353 JSONObject object = configJson.getJSONObject(i);
354 String network = object.getString(NETWORK_KEY);
355 String intf = object.getString(INTERFACE_KEY);
356
357 if (network != null && intf != null) {
358 phys.add(DefaultKubevirtPhyInterface.builder()
359 .network(network).intf(intf).build());
360 }
Daniel Park80c6f1b2021-02-18 20:38:46 +0900361 }
362 }
Jian Li4b249702021-02-19 18:13:10 +0900363
364 if (dataIpStr != null) {
365 dataIp = IpAddress.valueOf(dataIpStr);
366 }
Daniel Park515f5f32021-02-22 17:12:20 +0900367
368 if (gatewayConfig != null) {
369 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
370
371 nodeType = GATEWAY;
372 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
373 }
Jian Li9793ec42021-03-19 15:03:32 +0900374 } catch (JSONException | JsonProcessingException e) {
375 log.error("Failed to parse physnet config or gateway config object", e);
Daniel Park80c6f1b2021-02-18 20:38:46 +0900376 }
377
378 return DefaultKubevirtNode.builder()
379 .hostname(hostname)
380 .managementIp(managementIp)
381 .dataIp(dataIp)
382 .type(nodeType)
383 .state(KubevirtNodeState.ON_BOARDED)
384 .phyIntfs(phys)
Daniel Park515f5f32021-02-22 17:12:20 +0900385 .gatewayBridgeName(gatewayBridgeName)
Daniel Park80c6f1b2021-02-18 20:38:46 +0900386 .build();
387 }
Jian Lib230e07c2020-12-21 11:25:12 +0900388}