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