blob: e1a26d7f30e884b9cc6b2050af7337b6e3cc819d [file] [log] [blame]
Jian Li9871cd52021-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 Li260231e2021-01-13 18:05:00 +090018import com.fasterxml.jackson.databind.ObjectMapper;
Jian Lib5ab63c2021-02-03 17:54:28 +090019import com.google.common.base.Strings;
Jian Li3831f0c2021-03-12 18:03:58 +090020import com.google.common.collect.ImmutableSet;
Jian Lid5e8ea82021-01-18 00:19:31 +090021import io.fabric8.kubernetes.api.model.Pod;
Jian Li16eed162021-01-15 16:58:48 +090022import io.fabric8.kubernetes.client.ConfigBuilder;
23import io.fabric8.kubernetes.client.DefaultKubernetesClient;
24import io.fabric8.kubernetes.client.KubernetesClient;
Jian Li9871cd52021-01-09 00:19:02 +090025import org.apache.commons.lang.StringUtils;
Jian Li7bca1272021-01-14 11:30:36 +090026import org.apache.commons.net.util.SubnetUtils;
Jian Lid5e8ea82021-01-18 00:19:31 +090027import org.json.JSONArray;
28import org.json.JSONException;
29import org.json.JSONObject;
Jian Li543fe852021-02-04 17:25:01 +090030import org.onlab.osgi.DefaultServiceDirectory;
Daniel Parkb9a22022021-03-04 18:58:47 +090031import org.onlab.packet.Ip4Address;
Jian Li7bca1272021-01-14 11:30:36 +090032import org.onlab.packet.IpAddress;
Jian Lid5e8ea82021-01-18 00:19:31 +090033import org.onlab.packet.MacAddress;
Jian Li9871cd52021-01-09 00:19:02 +090034import org.onosproject.cfg.ConfigProperty;
Jian Lid5e8ea82021-01-18 00:19:31 +090035import org.onosproject.kubevirtnetworking.api.DefaultKubevirtPort;
36import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
37import org.onosproject.kubevirtnetworking.api.KubevirtPort;
Daniel Parkb9a22022021-03-04 18:58:47 +090038import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
39import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
Jian Li16eed162021-01-15 16:58:48 +090040import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
41import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
Jian Li543fe852021-02-04 17:25:01 +090042import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Parkb9a22022021-03-04 18:58:47 +090043import org.onosproject.kubevirtnode.api.KubevirtNodeService;
Jian Li543fe852021-02-04 17:25:01 +090044import org.onosproject.net.DeviceId;
45import org.onosproject.net.Port;
46import org.onosproject.net.PortNumber;
47import org.onosproject.net.device.DeviceService;
Jian Li9871cd52021-01-09 00:19:02 +090048import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
50
Jian Li260231e2021-01-13 18:05:00 +090051import java.io.IOException;
Jian Li7bca1272021-01-14 11:30:36 +090052import java.util.Arrays;
53import java.util.HashSet;
Jian Li260231e2021-01-13 18:05:00 +090054import java.util.List;
Jian Lid5e8ea82021-01-18 00:19:31 +090055import java.util.Map;
Jian Li543fe852021-02-04 17:25:01 +090056import java.util.Objects;
Jian Li9871cd52021-01-09 00:19:02 +090057import java.util.Optional;
58import java.util.Set;
Jian Li7bca1272021-01-14 11:30:36 +090059import java.util.stream.Collectors;
Jian Li9871cd52021-01-09 00:19:02 +090060
Jian Li543fe852021-02-04 17:25:01 +090061import static org.onosproject.kubevirtnetworking.api.Constants.TUNNEL_TO_TENANT_PREFIX;
Daniel Parkb9a22022021-03-04 18:58:47 +090062import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Daniel Parkcc8e7462021-03-09 13:37:42 +090063import static org.onosproject.net.AnnotationKeys.PORT_MAC;
Jian Li543fe852021-02-04 17:25:01 +090064import static org.onosproject.net.AnnotationKeys.PORT_NAME;
65
Jian Li9871cd52021-01-09 00:19:02 +090066/**
67 * An utility that used in KubeVirt networking app.
68 */
69public final class KubevirtNetworkingUtil {
70
71 private static final Logger log = LoggerFactory.getLogger(KubevirtNetworkingUtil.class);
72
73 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li16eed162021-01-15 16:58:48 +090074 private static final String COLON_SLASH = "://";
75 private static final String COLON = ":";
Jian Lib5ab63c2021-02-03 17:54:28 +090076 private static final String OF_PREFIX = "of:";
Jian Li9871cd52021-01-09 00:19:02 +090077
Jian Lid5e8ea82021-01-18 00:19:31 +090078 private static final String NETWORK_STATUS_KEY = "k8s.v1.cni.cncf.io/network-status";
79 private static final String NAME = "name";
80 private static final String NETWORK_PREFIX = "default/";
81 private static final String MAC = "mac";
82 private static final String IPS = "ips";
Daniel Parkcc8e7462021-03-09 13:37:42 +090083 private static final String BR_INT = "br-int";
Jian Lid5e8ea82021-01-18 00:19:31 +090084
Jian Li9871cd52021-01-09 00:19:02 +090085 /**
86 * Prevents object installation from external.
87 */
88 private KubevirtNetworkingUtil() {
89 }
90
91 /**
92 * Obtains the boolean property value with specified property key name.
93 *
Daniel Parkb9a22022021-03-04 18:58:47 +090094 * @param properties a collection of properties
95 * @param name key name
Jian Li9871cd52021-01-09 00:19:02 +090096 * @return mapping value
97 */
98 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties,
99 String name) {
100 Optional<ConfigProperty> property =
101 properties.stream().filter(p -> p.name().equals(name)).findFirst();
102
103 return property.map(ConfigProperty::asBoolean).orElse(false);
104 }
105
106 /**
107 * Re-structures the OVS port name.
108 * The length of OVS port name should be not large than 15.
109 *
Daniel Parkb9a22022021-03-04 18:58:47 +0900110 * @param portName original port name
Jian Li9871cd52021-01-09 00:19:02 +0900111 * @return re-structured OVS port name
112 */
113 public static String structurePortName(String portName) {
114
115 // The size of OVS port name should not be larger than 15
116 if (portName.length() > PORT_NAME_MAX_LENGTH) {
117 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
118 }
119
120 return portName;
121 }
Jian Li260231e2021-01-13 18:05:00 +0900122
123 /**
124 * Generates string format based on the given string length list.
125 *
126 * @param stringLengths a list of string lengths
127 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
128 */
129 public static String genFormatString(List<Integer> stringLengths) {
130 StringBuilder fsb = new StringBuilder();
131 stringLengths.forEach(length -> {
132 fsb.append("%-");
133 fsb.append(length);
134 fsb.append("s");
135 });
136 return fsb.toString();
137 }
138
139 /**
Jian Lib5ab63c2021-02-03 17:54:28 +0900140 * Auto generates DPID from the given name.
141 *
142 * @param name name
143 * @return auto generated DPID
144 */
145 public static String genDpidFromName(String name) {
146 if (name != null) {
147 String hexString = Integer.toHexString(name.hashCode());
148 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
149 }
150
151 return null;
152 }
153
154 /**
Jian Li260231e2021-01-13 18:05:00 +0900155 * Prints out the JSON string in pretty format.
156 *
Daniel Parkb9a22022021-03-04 18:58:47 +0900157 * @param mapper Object mapper
158 * @param jsonString JSON string
Jian Li260231e2021-01-13 18:05:00 +0900159 * @return pretty formatted JSON string
160 */
161 public static String prettyJson(ObjectMapper mapper, String jsonString) {
162 try {
163 Object jsonObject = mapper.readValue(jsonString, Object.class);
164 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
165 } catch (IOException e) {
166 log.debug("Json string parsing exception caused by {}", e);
167 }
168 return null;
169 }
Jian Li7bca1272021-01-14 11:30:36 +0900170
171 /**
172 * Obtains valid IP addresses of the given subnet.
173 *
174 * @param cidr CIDR
175 * @return set of IP addresses
176 */
177 public static Set<IpAddress> getSubnetIps(String cidr) {
178 SubnetUtils utils = new SubnetUtils(cidr);
179 utils.setInclusiveHostCount(false);
180 SubnetUtils.SubnetInfo info = utils.getInfo();
181 Set<String> allAddresses =
182 new HashSet<>(Arrays.asList(info.getAllAddresses()));
183
184 if (allAddresses.size() > 2) {
185 allAddresses.remove(info.getLowAddress());
186 allAddresses.remove(info.getHighAddress());
187 }
188
189 return allAddresses.stream()
190 .map(IpAddress::valueOf).collect(Collectors.toSet());
191 }
192
193 /**
194 * Calculate the broadcast address from given IP address and subnet prefix length.
195 *
Daniel Parkb9a22022021-03-04 18:58:47 +0900196 * @param ipAddr IP address
197 * @param prefixLength subnet prefix length
Jian Li7bca1272021-01-14 11:30:36 +0900198 * @return broadcast address
199 */
200 public static String getBroadcastAddr(String ipAddr, int prefixLength) {
201 String subnet = ipAddr + "/" + prefixLength;
202 SubnetUtils utils = new SubnetUtils(subnet);
203 return utils.getInfo().getBroadcastAddress();
204 }
Daniel Parkb9a22022021-03-04 18:58:47 +0900205
Jian Li16eed162021-01-15 16:58:48 +0900206 /**
207 * Generates endpoint URL by referring to scheme, ipAddress and port.
208 *
Daniel Parkb9a22022021-03-04 18:58:47 +0900209 * @param scheme scheme
210 * @param ipAddress IP address
211 * @param port port number
Jian Li16eed162021-01-15 16:58:48 +0900212 * @return generated endpoint URL
213 */
214 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
215 StringBuilder endpoint = new StringBuilder();
216 String protocol = org.apache.commons.lang3.StringUtils.lowerCase(scheme.name());
217
218 endpoint.append(protocol);
219 endpoint.append(COLON_SLASH);
220 endpoint.append(ipAddress.toString());
221 endpoint.append(COLON);
222 endpoint.append(port);
223
224 return endpoint.toString();
225 }
226
227 /**
228 * Generates endpoint URL by referring to scheme, ipAddress and port.
229 *
Daniel Parkb9a22022021-03-04 18:58:47 +0900230 * @param apiConfig kubernetes API config
Jian Li16eed162021-01-15 16:58:48 +0900231 * @return generated endpoint URL
232 */
233 public static String endpoint(KubevirtApiConfig apiConfig) {
234 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
235 }
236
237 /**
238 * Obtains workable kubernetes client.
239 *
240 * @param config kubernetes API config
241 * @return kubernetes client
242 */
243 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
244 if (config == null) {
245 log.warn("Kubernetes API server config is empty.");
246 return null;
247 }
248
249 String endpoint = endpoint(config);
250
251 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
252
253 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
254 configBuilder.withTrustCerts(true)
Jian Li16eed162021-01-15 16:58:48 +0900255 .withCaCertData(config.caCertData())
256 .withClientCertData(config.clientCertData())
257 .withClientKeyData(config.clientKeyData());
258 }
259
260 return new DefaultKubernetesClient(configBuilder.build());
261 }
262
263 /**
264 * Obtains workable kubernetes client.
265 *
266 * @param service kubernetes API service
267 * @return kubernetes client
268 */
269 public static KubernetesClient k8sClient(KubevirtApiConfigService service) {
270 KubevirtApiConfig config = service.apiConfig();
271 if (config == null) {
272 log.error("Failed to find valid kubernetes API configuration.");
273 return null;
274 }
275
276 KubernetesClient client = k8sClient(config);
277
278 if (client == null) {
279 log.error("Failed to connect to kubernetes API server.");
280 return null;
281 }
282
283 return client;
284 }
Jian Lid5e8ea82021-01-18 00:19:31 +0900285
286 /**
Jian Lib5ab63c2021-02-03 17:54:28 +0900287 * Obtains the hex string of the given segment ID with fixed padding.
288 *
289 * @param segIdStr segment identifier string
290 * @return hex string with padding
291 */
292 public static String segmentIdHex(String segIdStr) {
293 int segId = Integer.parseInt(segIdStr);
294 return String.format("%06x", segId).toLowerCase();
295 }
296
297 /**
Jian Li543fe852021-02-04 17:25:01 +0900298 * Obtains the tunnel port number with the given network and node.
299 *
300 * @param network kubevirt network
Daniel Parkb9a22022021-03-04 18:58:47 +0900301 * @param node kubevirt node
Jian Li543fe852021-02-04 17:25:01 +0900302 * @return tunnel port number
303 */
304 public static PortNumber tunnelPort(KubevirtNetwork network, KubevirtNode node) {
305 switch (network.type()) {
306 case VXLAN:
307 return node.vxlanPort();
308 case GRE:
309 return node.grePort();
310 case GENEVE:
311 return node.genevePort();
312 default:
313 break;
314 }
315 return null;
316 }
317
318 /**
Jian Lid5e8ea82021-01-18 00:19:31 +0900319 * Obtains the kubevirt port from kubevirt POD.
320 *
321 * @param networks set of existing kubevirt networks
Daniel Parkb9a22022021-03-04 18:58:47 +0900322 * @param pod kubevirt POD
Jian Li3831f0c2021-03-12 18:03:58 +0900323 * @return kubevirt ports attached to the POD
Jian Lid5e8ea82021-01-18 00:19:31 +0900324 */
Jian Li3831f0c2021-03-12 18:03:58 +0900325 public static Set<KubevirtPort> getPorts(Set<KubevirtNetwork> networks, Pod pod) {
Jian Lid5e8ea82021-01-18 00:19:31 +0900326 try {
327 Map<String, String> annots = pod.getMetadata().getAnnotations();
Jian Li840156c2021-01-21 20:30:52 +0900328 if (annots == null) {
Jian Li3831f0c2021-03-12 18:03:58 +0900329 return ImmutableSet.of();
Jian Li840156c2021-01-21 20:30:52 +0900330 }
331
Jian Li2417ab72021-02-02 17:35:12 +0900332 if (!annots.containsKey(NETWORK_STATUS_KEY)) {
Jian Li3831f0c2021-03-12 18:03:58 +0900333 return ImmutableSet.of();
Jian Li2417ab72021-02-02 17:35:12 +0900334 }
335
Jian Lid5e8ea82021-01-18 00:19:31 +0900336 String networkStatusStr = annots.get(NETWORK_STATUS_KEY);
337
338 if (networkStatusStr == null) {
Jian Li3831f0c2021-03-12 18:03:58 +0900339 return ImmutableSet.of();
Jian Lid5e8ea82021-01-18 00:19:31 +0900340 }
341
342 JSONArray networkStatus = new JSONArray(networkStatusStr);
Jian Li3831f0c2021-03-12 18:03:58 +0900343 Set<KubevirtPort> ports = new HashSet<>();
Jian Lid5e8ea82021-01-18 00:19:31 +0900344
345 for (int i = 0; i < networkStatus.length(); i++) {
346 JSONObject object = networkStatus.getJSONObject(i);
347 String name = object.getString(NAME);
348 KubevirtNetwork network = networks.stream()
349 .filter(n -> (NETWORK_PREFIX + n.name()).equals(name))
350 .findAny().orElse(null);
351 if (network != null) {
352 String mac = object.getString(MAC);
353
354 KubevirtPort.Builder builder = DefaultKubevirtPort.builder()
355 .macAddress(MacAddress.valueOf(mac))
356 .networkId(network.networkId());
357
358 if (object.has(IPS)) {
359 JSONArray ips = object.getJSONArray(IPS);
360 String ip = (String) ips.get(0);
361 builder.ipAddress(IpAddress.valueOf(ip));
362 }
363
Jian Li3831f0c2021-03-12 18:03:58 +0900364 ports.add(builder.build());
Jian Lid5e8ea82021-01-18 00:19:31 +0900365 }
366 }
367
Jian Li3831f0c2021-03-12 18:03:58 +0900368 return ports;
369
Jian Lid5e8ea82021-01-18 00:19:31 +0900370 } catch (JSONException e) {
371 log.error("Failed to parse network status object", e);
372 }
373
Jian Li3831f0c2021-03-12 18:03:58 +0900374 return ImmutableSet.of();
Jian Lid5e8ea82021-01-18 00:19:31 +0900375 }
Jian Li543fe852021-02-04 17:25:01 +0900376
377 /**
378 * Obtains the tunnel bridge to tenant bridge patch port number.
379 *
Daniel Parkb9a22022021-03-04 18:58:47 +0900380 * @param node kubevirt node
Jian Li543fe852021-02-04 17:25:01 +0900381 * @param network kubevirt network
382 * @return patch port number
383 */
384 public static PortNumber tunnelToTenantPort(KubevirtNode node, KubevirtNetwork network) {
385 if (network.segmentId() == null) {
386 return null;
387 }
388
389 if (node.tunBridge() == null) {
390 return null;
391 }
392
393 String tunToTenantPortName = TUNNEL_TO_TENANT_PREFIX + segmentIdHex(network.segmentId());
394 return portNumber(node.tunBridge(), tunToTenantPortName);
395 }
396
397 /**
398 * Obtains the tunnel port number of the given node.
399 *
Daniel Parkb9a22022021-03-04 18:58:47 +0900400 * @param node kubevirt node
Jian Li543fe852021-02-04 17:25:01 +0900401 * @param network kubevirt network
402 * @return tunnel port number
403 */
404 public static PortNumber tunnelPort(KubevirtNode node, KubevirtNetwork network) {
405 if (network.segmentId() == null) {
406 return null;
407 }
408
409 if (node.tunBridge() == null) {
410 return null;
411 }
412
413 switch (network.type()) {
414 case VXLAN:
415 return node.vxlanPort();
416 case GRE:
417 return node.grePort();
418 case GENEVE:
419 return node.genevePort();
420 case FLAT:
Jian Li81b1aab2021-02-17 20:42:15 +0900421 case VLAN:
Jian Li543fe852021-02-04 17:25:01 +0900422 default:
423 // do nothing
424 return null;
425 }
426 }
427
Jian Li7eb20782021-02-27 01:10:50 +0900428 public static String parseResourceName(String resource) {
429 try {
430 JSONObject json = new JSONObject(resource);
431 return json.getJSONObject("metadata").getString("name");
432 } catch (JSONException e) {
433 log.error("");
434 }
435 return "";
436 }
437
Jian Li543fe852021-02-04 17:25:01 +0900438 private static PortNumber portNumber(DeviceId deviceId, String portName) {
439 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
440 Port port = deviceService.getPorts(deviceId).stream()
441 .filter(p -> p.isEnabled() &&
442 Objects.equals(p.annotations().value(PORT_NAME), portName))
443 .findAny().orElse(null);
444 return port != null ? port.number() : null;
445 }
446
Daniel Parkb9a22022021-03-04 18:58:47 +0900447 /**
448 * Returns the gateway node for the specified kubevirt router.
449 * Among gateways, only one gateway would act as a gateway per perter.
450 * Currently gateway node is selected based on modulo operation with router hashcode.
451 *
452 * @param nodeService kubevirt node service
453 * @param router kubevirt router
454 * @return elected gateway node
455 */
456 public static KubevirtNode gatewayNodeForSpecifiedRouter(KubevirtNodeService nodeService,
457 KubevirtRouter router) {
458 //TODO: enhance election logic for a better load balancing
459
460 int numOfGateways = nodeService.completeNodes(GATEWAY).size();
461 if (numOfGateways == 0) {
462 return null;
463 }
464 return (KubevirtNode) nodeService.completeNodes(GATEWAY).toArray()[router.hashCode() % numOfGateways];
465 }
466
467 /**
468 * Returns the mac address of the br-int port of specified device.
469 *
470 * @param deviceService device service
471 * @param deviceId device Id
472 * @return mac address of the br-int port
473 */
474 public static MacAddress getbrIntMacAddress(DeviceService deviceService,
475 DeviceId deviceId) {
476 return MacAddress.valueOf(deviceService.getPorts(deviceId).stream()
Daniel Parkcc8e7462021-03-09 13:37:42 +0900477 .filter(port -> Objects.equals(port.annotations().value(PORT_NAME), BR_INT))
478 .map(port -> port.annotations().value(PORT_MAC))
Daniel Parkb9a22022021-03-04 18:58:47 +0900479 .findAny().orElse(null));
480 }
481
482 /**
483 * Returns the snat ip address with specified router.
484 *
485 * @param routerService router service
486 * @param internalNetworkId internal network id which is associated with the router
487 * @return snat ip address if exist, null otherwise
488 */
489 public static IpAddress getRouterSnatIpAddress(KubevirtRouterService routerService,
490 String internalNetworkId) {
491 KubevirtRouter router = routerService.routers().stream()
492 .filter(r -> r.internal().contains(internalNetworkId))
493 .findAny().orElse(null);
494
495 if (router == null) {
496 return null;
497 }
498
499 String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
500
501 if (routerSnatIp == null) {
502 return null;
503 }
504
505 return Ip4Address.valueOf(routerSnatIp);
506 }
Daniel Parkcc8e7462021-03-09 13:37:42 +0900507
508 /**
509 * Returns the kubevirt router with specified kubevirt port.
510 *
511 * @param routerService kubevirt router service
512 * @param kubevirtPort kubevirt port
513 * @return kubevirt router
514 */
515 public static KubevirtRouter getRouterForKubevirtPort(KubevirtRouterService routerService,
516 KubevirtPort kubevirtPort) {
517 if (kubevirtPort.ipAddress() != null) {
518 return routerService.routers().stream()
519 .filter(r -> r.internal().contains(kubevirtPort.networkId()))
520 .findAny().orElse(null);
521 }
522 return null;
523 }
524
525 /**
526 * Returns the kubevirt router with specified kubevirt network.
527 *
528 * @param routerService kubevirt router service
529 * @param kubevirtNetwork kubevirt network
530 * @return kubevirt router
531 */
532 public static KubevirtRouter getRouterForKubevirtNetwork(KubevirtRouterService routerService,
533 KubevirtNetwork kubevirtNetwork) {
534 return routerService.routers().stream()
535 .filter(router -> router.internal().contains(kubevirtNetwork.networkId()))
536 .findAny().orElse(null);
537 }
Jian Li9871cd52021-01-09 00:19:02 +0900538}