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