blob: 8a9ee6c8404c832230b80c4f0c27ee2e3fd6d3c4 [file] [log] [blame]
Jian Li43244382021-01-09 00:19:02 +09001/*
2 * Copyright 2021-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.kubevirtnetworking.util;
17
Jian Lif97a07e2021-01-13 18:05:00 +090018import com.fasterxml.jackson.databind.ObjectMapper;
Jian Li556709c2021-02-03 17:54:28 +090019import com.google.common.base.Strings;
Jian Lica20b712021-01-18 00:19:31 +090020import io.fabric8.kubernetes.api.model.Pod;
Jian Li034820d2021-01-15 16:58:48 +090021import io.fabric8.kubernetes.client.ConfigBuilder;
22import io.fabric8.kubernetes.client.DefaultKubernetesClient;
23import io.fabric8.kubernetes.client.KubernetesClient;
Jian Li43244382021-01-09 00:19:02 +090024import org.apache.commons.lang.StringUtils;
Jian Li3ba5c582021-01-14 11:30:36 +090025import org.apache.commons.net.util.SubnetUtils;
Jian Lica20b712021-01-18 00:19:31 +090026import org.json.JSONArray;
27import org.json.JSONException;
28import org.json.JSONObject;
Jian Li858ccd72021-02-04 17:25:01 +090029import org.onlab.osgi.DefaultServiceDirectory;
Daniel Park2884b232021-03-04 18:58:47 +090030import org.onlab.packet.Ip4Address;
Jian Li3ba5c582021-01-14 11:30:36 +090031import org.onlab.packet.IpAddress;
Jian Lica20b712021-01-18 00:19:31 +090032import org.onlab.packet.MacAddress;
Jian Li43244382021-01-09 00:19:02 +090033import org.onosproject.cfg.ConfigProperty;
Jian Lica20b712021-01-18 00:19:31 +090034import org.onosproject.kubevirtnetworking.api.DefaultKubevirtPort;
35import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
36import org.onosproject.kubevirtnetworking.api.KubevirtPort;
Daniel Park2884b232021-03-04 18:58:47 +090037import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
38import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
Jian Li034820d2021-01-15 16:58:48 +090039import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
40import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
Jian Li858ccd72021-02-04 17:25:01 +090041import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Park2884b232021-03-04 18:58:47 +090042import org.onosproject.kubevirtnode.api.KubevirtNodeService;
Jian Li858ccd72021-02-04 17:25:01 +090043import org.onosproject.net.DeviceId;
44import org.onosproject.net.Port;
45import org.onosproject.net.PortNumber;
46import org.onosproject.net.device.DeviceService;
Jian Li43244382021-01-09 00:19:02 +090047import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
49
Jian Lif97a07e2021-01-13 18:05:00 +090050import java.io.IOException;
Jian Li3ba5c582021-01-14 11:30:36 +090051import java.util.Arrays;
52import java.util.HashSet;
Jian Lif97a07e2021-01-13 18:05:00 +090053import java.util.List;
Jian Lica20b712021-01-18 00:19:31 +090054import java.util.Map;
Jian Li858ccd72021-02-04 17:25:01 +090055import java.util.Objects;
Jian Li43244382021-01-09 00:19:02 +090056import java.util.Optional;
57import java.util.Set;
Jian Li3ba5c582021-01-14 11:30:36 +090058import java.util.stream.Collectors;
Jian Li43244382021-01-09 00:19:02 +090059
Jian Li858ccd72021-02-04 17:25:01 +090060import static org.onosproject.kubevirtnetworking.api.Constants.TUNNEL_TO_TENANT_PREFIX;
Daniel Park2884b232021-03-04 18:58:47 +090061import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Daniel Parkbabde9c2021-03-09 13:37:42 +090062import static org.onosproject.net.AnnotationKeys.PORT_MAC;
Jian Li858ccd72021-02-04 17:25:01 +090063import static org.onosproject.net.AnnotationKeys.PORT_NAME;
64
Jian Li43244382021-01-09 00:19:02 +090065/**
66 * An utility that used in KubeVirt networking app.
67 */
68public final class KubevirtNetworkingUtil {
69
70 private static final Logger log = LoggerFactory.getLogger(KubevirtNetworkingUtil.class);
71
72 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li034820d2021-01-15 16:58:48 +090073 private static final String COLON_SLASH = "://";
74 private static final String COLON = ":";
Jian Li556709c2021-02-03 17:54:28 +090075 private static final String OF_PREFIX = "of:";
Jian Li43244382021-01-09 00:19:02 +090076
Jian Lica20b712021-01-18 00:19:31 +090077 private static final String NETWORK_STATUS_KEY = "k8s.v1.cni.cncf.io/network-status";
78 private static final String NAME = "name";
79 private static final String NETWORK_PREFIX = "default/";
80 private static final String MAC = "mac";
81 private static final String IPS = "ips";
Daniel Parkbabde9c2021-03-09 13:37:42 +090082 private static final String BR_INT = "br-int";
Jian Lica20b712021-01-18 00:19:31 +090083
Jian Li43244382021-01-09 00:19:02 +090084 /**
85 * Prevents object installation from external.
86 */
87 private KubevirtNetworkingUtil() {
88 }
89
90 /**
91 * Obtains the boolean property value with specified property key name.
92 *
Daniel Park2884b232021-03-04 18:58:47 +090093 * @param properties a collection of properties
94 * @param name key name
Jian Li43244382021-01-09 00:19:02 +090095 * @return mapping value
96 */
97 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties,
98 String name) {
99 Optional<ConfigProperty> property =
100 properties.stream().filter(p -> p.name().equals(name)).findFirst();
101
102 return property.map(ConfigProperty::asBoolean).orElse(false);
103 }
104
105 /**
106 * Re-structures the OVS port name.
107 * The length of OVS port name should be not large than 15.
108 *
Daniel Park2884b232021-03-04 18:58:47 +0900109 * @param portName original port name
Jian Li43244382021-01-09 00:19:02 +0900110 * @return re-structured OVS port name
111 */
112 public static String structurePortName(String portName) {
113
114 // The size of OVS port name should not be larger than 15
115 if (portName.length() > PORT_NAME_MAX_LENGTH) {
116 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
117 }
118
119 return portName;
120 }
Jian Lif97a07e2021-01-13 18:05:00 +0900121
122 /**
123 * Generates string format based on the given string length list.
124 *
125 * @param stringLengths a list of string lengths
126 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
127 */
128 public static String genFormatString(List<Integer> stringLengths) {
129 StringBuilder fsb = new StringBuilder();
130 stringLengths.forEach(length -> {
131 fsb.append("%-");
132 fsb.append(length);
133 fsb.append("s");
134 });
135 return fsb.toString();
136 }
137
138 /**
Jian Li556709c2021-02-03 17:54:28 +0900139 * Auto generates DPID from the given name.
140 *
141 * @param name name
142 * @return auto generated DPID
143 */
144 public static String genDpidFromName(String name) {
145 if (name != null) {
146 String hexString = Integer.toHexString(name.hashCode());
147 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
148 }
149
150 return null;
151 }
152
153 /**
Jian Lif97a07e2021-01-13 18:05:00 +0900154 * Prints out the JSON string in pretty format.
155 *
Daniel Park2884b232021-03-04 18:58:47 +0900156 * @param mapper Object mapper
157 * @param jsonString JSON string
Jian Lif97a07e2021-01-13 18:05:00 +0900158 * @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 Li3ba5c582021-01-14 11:30:36 +0900169
170 /**
171 * Obtains valid IP addresses of the given subnet.
172 *
173 * @param cidr CIDR
174 * @return set of IP addresses
175 */
176 public static Set<IpAddress> getSubnetIps(String cidr) {
177 SubnetUtils utils = new SubnetUtils(cidr);
178 utils.setInclusiveHostCount(false);
179 SubnetUtils.SubnetInfo info = utils.getInfo();
180 Set<String> allAddresses =
181 new HashSet<>(Arrays.asList(info.getAllAddresses()));
182
183 if (allAddresses.size() > 2) {
184 allAddresses.remove(info.getLowAddress());
185 allAddresses.remove(info.getHighAddress());
186 }
187
188 return allAddresses.stream()
189 .map(IpAddress::valueOf).collect(Collectors.toSet());
190 }
191
192 /**
193 * Calculate the broadcast address from given IP address and subnet prefix length.
194 *
Daniel Park2884b232021-03-04 18:58:47 +0900195 * @param ipAddr IP address
196 * @param prefixLength subnet prefix length
Jian Li3ba5c582021-01-14 11:30:36 +0900197 * @return broadcast address
198 */
199 public static String getBroadcastAddr(String ipAddr, int prefixLength) {
200 String subnet = ipAddr + "/" + prefixLength;
201 SubnetUtils utils = new SubnetUtils(subnet);
202 return utils.getInfo().getBroadcastAddress();
203 }
Daniel Park2884b232021-03-04 18:58:47 +0900204
Jian Li034820d2021-01-15 16:58:48 +0900205 /**
206 * Generates endpoint URL by referring to scheme, ipAddress and port.
207 *
Daniel Park2884b232021-03-04 18:58:47 +0900208 * @param scheme scheme
209 * @param ipAddress IP address
210 * @param port port number
Jian Li034820d2021-01-15 16:58:48 +0900211 * @return generated endpoint URL
212 */
213 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
214 StringBuilder endpoint = new StringBuilder();
215 String protocol = org.apache.commons.lang3.StringUtils.lowerCase(scheme.name());
216
217 endpoint.append(protocol);
218 endpoint.append(COLON_SLASH);
219 endpoint.append(ipAddress.toString());
220 endpoint.append(COLON);
221 endpoint.append(port);
222
223 return endpoint.toString();
224 }
225
226 /**
227 * Generates endpoint URL by referring to scheme, ipAddress and port.
228 *
Daniel Park2884b232021-03-04 18:58:47 +0900229 * @param apiConfig kubernetes API config
Jian Li034820d2021-01-15 16:58:48 +0900230 * @return generated endpoint URL
231 */
232 public static String endpoint(KubevirtApiConfig apiConfig) {
233 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
234 }
235
236 /**
237 * Obtains workable kubernetes client.
238 *
239 * @param config kubernetes API config
240 * @return kubernetes client
241 */
242 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
243 if (config == null) {
244 log.warn("Kubernetes API server config is empty.");
245 return null;
246 }
247
248 String endpoint = endpoint(config);
249
250 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
251
252 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
253 configBuilder.withTrustCerts(true)
Jian Li034820d2021-01-15 16:58:48 +0900254 .withCaCertData(config.caCertData())
255 .withClientCertData(config.clientCertData())
256 .withClientKeyData(config.clientKeyData());
257 }
258
259 return new DefaultKubernetesClient(configBuilder.build());
260 }
261
262 /**
263 * Obtains workable kubernetes client.
264 *
265 * @param service kubernetes API service
266 * @return kubernetes client
267 */
268 public static KubernetesClient k8sClient(KubevirtApiConfigService service) {
269 KubevirtApiConfig config = service.apiConfig();
270 if (config == null) {
271 log.error("Failed to find valid kubernetes API configuration.");
272 return null;
273 }
274
275 KubernetesClient client = k8sClient(config);
276
277 if (client == null) {
278 log.error("Failed to connect to kubernetes API server.");
279 return null;
280 }
281
282 return client;
283 }
Jian Lica20b712021-01-18 00:19:31 +0900284
285 /**
Jian Li556709c2021-02-03 17:54:28 +0900286 * Obtains the hex string of the given segment ID with fixed padding.
287 *
288 * @param segIdStr segment identifier string
289 * @return hex string with padding
290 */
291 public static String segmentIdHex(String segIdStr) {
292 int segId = Integer.parseInt(segIdStr);
293 return String.format("%06x", segId).toLowerCase();
294 }
295
296 /**
Jian Li858ccd72021-02-04 17:25:01 +0900297 * Obtains the tunnel port number with the given network and node.
298 *
299 * @param network kubevirt network
Daniel Park2884b232021-03-04 18:58:47 +0900300 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900301 * @return tunnel port number
302 */
303 public static PortNumber tunnelPort(KubevirtNetwork network, KubevirtNode node) {
304 switch (network.type()) {
305 case VXLAN:
306 return node.vxlanPort();
307 case GRE:
308 return node.grePort();
309 case GENEVE:
310 return node.genevePort();
311 default:
312 break;
313 }
314 return null;
315 }
316
317 /**
Jian Lica20b712021-01-18 00:19:31 +0900318 * Obtains the kubevirt port from kubevirt POD.
319 *
320 * @param networks set of existing kubevirt networks
Daniel Park2884b232021-03-04 18:58:47 +0900321 * @param pod kubevirt POD
Jian Lica20b712021-01-18 00:19:31 +0900322 * @return kubevirt port
323 */
324 public static KubevirtPort getPort(Set<KubevirtNetwork> networks, Pod pod) {
325 try {
326 Map<String, String> annots = pod.getMetadata().getAnnotations();
Jian Li6e66a302021-01-21 20:30:52 +0900327 if (annots == null) {
328 return null;
329 }
330
Jian Li7a581b12021-02-18 14:24:32 +0900331 if (!annots.containsKey(NETWORK_STATUS_KEY)) {
332 return null;
333 }
334
Jian Lica20b712021-01-18 00:19:31 +0900335 String networkStatusStr = annots.get(NETWORK_STATUS_KEY);
336
337 if (networkStatusStr == null) {
338 return null;
339 }
340
341 JSONArray networkStatus = new JSONArray(networkStatusStr);
342
343 for (int i = 0; i < networkStatus.length(); i++) {
344 JSONObject object = networkStatus.getJSONObject(i);
345 String name = object.getString(NAME);
346 KubevirtNetwork network = networks.stream()
347 .filter(n -> (NETWORK_PREFIX + n.name()).equals(name))
348 .findAny().orElse(null);
349 if (network != null) {
350 String mac = object.getString(MAC);
351
352 KubevirtPort.Builder builder = DefaultKubevirtPort.builder()
353 .macAddress(MacAddress.valueOf(mac))
354 .networkId(network.networkId());
355
356 if (object.has(IPS)) {
357 JSONArray ips = object.getJSONArray(IPS);
358 String ip = (String) ips.get(0);
359 builder.ipAddress(IpAddress.valueOf(ip));
360 }
361
362 return builder.build();
363 }
364 }
365
366 } catch (JSONException e) {
367 log.error("Failed to parse network status object", e);
368 }
369
370 return null;
371 }
Jian Li858ccd72021-02-04 17:25:01 +0900372
373 /**
374 * Obtains the tunnel bridge to tenant bridge patch port number.
375 *
Daniel Park2884b232021-03-04 18:58:47 +0900376 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900377 * @param network kubevirt network
378 * @return patch port number
379 */
380 public static PortNumber tunnelToTenantPort(KubevirtNode node, KubevirtNetwork network) {
381 if (network.segmentId() == null) {
382 return null;
383 }
384
385 if (node.tunBridge() == null) {
386 return null;
387 }
388
389 String tunToTenantPortName = TUNNEL_TO_TENANT_PREFIX + segmentIdHex(network.segmentId());
390 return portNumber(node.tunBridge(), tunToTenantPortName);
391 }
392
393 /**
394 * Obtains the tunnel port number of the given node.
395 *
Daniel Park2884b232021-03-04 18:58:47 +0900396 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900397 * @param network kubevirt network
398 * @return tunnel port number
399 */
400 public static PortNumber tunnelPort(KubevirtNode node, KubevirtNetwork network) {
401 if (network.segmentId() == null) {
402 return null;
403 }
404
405 if (node.tunBridge() == null) {
406 return null;
407 }
408
409 switch (network.type()) {
410 case VXLAN:
411 return node.vxlanPort();
412 case GRE:
413 return node.grePort();
414 case GENEVE:
415 return node.genevePort();
416 case FLAT:
Jian Li2ce718e2021-02-17 20:42:15 +0900417 case VLAN:
Jian Li858ccd72021-02-04 17:25:01 +0900418 default:
419 // do nothing
420 return null;
421 }
422 }
423
Jian Li810f58c2021-02-27 01:10:50 +0900424 public static String parseResourceName(String resource) {
425 try {
426 JSONObject json = new JSONObject(resource);
427 return json.getJSONObject("metadata").getString("name");
428 } catch (JSONException e) {
429 log.error("");
430 }
431 return "";
432 }
433
Jian Li858ccd72021-02-04 17:25:01 +0900434 private static PortNumber portNumber(DeviceId deviceId, String portName) {
435 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
436 Port port = deviceService.getPorts(deviceId).stream()
437 .filter(p -> p.isEnabled() &&
438 Objects.equals(p.annotations().value(PORT_NAME), portName))
439 .findAny().orElse(null);
440 return port != null ? port.number() : null;
441 }
442
Daniel Park2884b232021-03-04 18:58:47 +0900443 /**
444 * Returns the gateway node for the specified kubevirt router.
445 * Among gateways, only one gateway would act as a gateway per perter.
446 * Currently gateway node is selected based on modulo operation with router hashcode.
447 *
448 * @param nodeService kubevirt node service
449 * @param router kubevirt router
450 * @return elected gateway node
451 */
452 public static KubevirtNode gatewayNodeForSpecifiedRouter(KubevirtNodeService nodeService,
453 KubevirtRouter router) {
454 //TODO: enhance election logic for a better load balancing
455
456 int numOfGateways = nodeService.completeNodes(GATEWAY).size();
457 if (numOfGateways == 0) {
458 return null;
459 }
460 return (KubevirtNode) nodeService.completeNodes(GATEWAY).toArray()[router.hashCode() % numOfGateways];
461 }
462
463 /**
464 * Returns the mac address of the br-int port of specified device.
465 *
466 * @param deviceService device service
467 * @param deviceId device Id
468 * @return mac address of the br-int port
469 */
470 public static MacAddress getbrIntMacAddress(DeviceService deviceService,
471 DeviceId deviceId) {
472 return MacAddress.valueOf(deviceService.getPorts(deviceId).stream()
Daniel Parkbabde9c2021-03-09 13:37:42 +0900473 .filter(port -> Objects.equals(port.annotations().value(PORT_NAME), BR_INT))
474 .map(port -> port.annotations().value(PORT_MAC))
Daniel Park2884b232021-03-04 18:58:47 +0900475 .findAny().orElse(null));
476 }
477
478 /**
479 * Returns the snat ip address with specified router.
480 *
481 * @param routerService router service
482 * @param internalNetworkId internal network id which is associated with the router
483 * @return snat ip address if exist, null otherwise
484 */
485 public static IpAddress getRouterSnatIpAddress(KubevirtRouterService routerService,
486 String internalNetworkId) {
487 KubevirtRouter router = routerService.routers().stream()
488 .filter(r -> r.internal().contains(internalNetworkId))
489 .findAny().orElse(null);
490
491 if (router == null) {
492 return null;
493 }
494
495 String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
496
497 if (routerSnatIp == null) {
498 return null;
499 }
500
501 return Ip4Address.valueOf(routerSnatIp);
502 }
Daniel Parkbabde9c2021-03-09 13:37:42 +0900503
504 /**
505 * Returns the kubevirt router with specified kubevirt port.
506 *
507 * @param routerService kubevirt router service
508 * @param kubevirtPort kubevirt port
509 * @return kubevirt router
510 */
511 public static KubevirtRouter getRouterForKubevirtPort(KubevirtRouterService routerService,
512 KubevirtPort kubevirtPort) {
513 if (kubevirtPort.ipAddress() != null) {
514 return routerService.routers().stream()
515 .filter(r -> r.internal().contains(kubevirtPort.networkId()))
516 .findAny().orElse(null);
517 }
518 return null;
519 }
520
521 /**
522 * Returns the kubevirt router with specified kubevirt network.
523 *
524 * @param routerService kubevirt router service
525 * @param kubevirtNetwork kubevirt network
526 * @return kubevirt router
527 */
528 public static KubevirtRouter getRouterForKubevirtNetwork(KubevirtRouterService routerService,
529 KubevirtNetwork kubevirtNetwork) {
530 return routerService.routers().stream()
531 .filter(router -> router.internal().contains(kubevirtNetwork.networkId()))
532 .findAny().orElse(null);
533 }
Jian Li43244382021-01-09 00:19:02 +0900534}