blob: f40d42cd8b01377867e4008fd554cbbe36cbcf9d [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;
Jian Li858ccd72021-02-04 17:25:01 +090062import static org.onosproject.net.AnnotationKeys.PORT_NAME;
63
Jian Li43244382021-01-09 00:19:02 +090064/**
65 * An utility that used in KubeVirt networking app.
66 */
67public final class KubevirtNetworkingUtil {
68
69 private static final Logger log = LoggerFactory.getLogger(KubevirtNetworkingUtil.class);
70
71 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li034820d2021-01-15 16:58:48 +090072 private static final String COLON_SLASH = "://";
73 private static final String COLON = ":";
Jian Li556709c2021-02-03 17:54:28 +090074 private static final String OF_PREFIX = "of:";
Jian Li43244382021-01-09 00:19:02 +090075
Jian Lica20b712021-01-18 00:19:31 +090076 private static final String NETWORK_STATUS_KEY = "k8s.v1.cni.cncf.io/network-status";
77 private static final String NAME = "name";
78 private static final String NETWORK_PREFIX = "default/";
79 private static final String MAC = "mac";
80 private static final String IPS = "ips";
81
Jian Li43244382021-01-09 00:19:02 +090082 /**
83 * Prevents object installation from external.
84 */
85 private KubevirtNetworkingUtil() {
86 }
87
88 /**
89 * Obtains the boolean property value with specified property key name.
90 *
Daniel Park2884b232021-03-04 18:58:47 +090091 * @param properties a collection of properties
92 * @param name key name
Jian Li43244382021-01-09 00:19:02 +090093 * @return mapping value
94 */
95 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties,
96 String name) {
97 Optional<ConfigProperty> property =
98 properties.stream().filter(p -> p.name().equals(name)).findFirst();
99
100 return property.map(ConfigProperty::asBoolean).orElse(false);
101 }
102
103 /**
104 * Re-structures the OVS port name.
105 * The length of OVS port name should be not large than 15.
106 *
Daniel Park2884b232021-03-04 18:58:47 +0900107 * @param portName original port name
Jian Li43244382021-01-09 00:19:02 +0900108 * @return re-structured OVS port name
109 */
110 public static String structurePortName(String portName) {
111
112 // The size of OVS port name should not be larger than 15
113 if (portName.length() > PORT_NAME_MAX_LENGTH) {
114 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
115 }
116
117 return portName;
118 }
Jian Lif97a07e2021-01-13 18:05:00 +0900119
120 /**
121 * Generates string format based on the given string length list.
122 *
123 * @param stringLengths a list of string lengths
124 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
125 */
126 public static String genFormatString(List<Integer> stringLengths) {
127 StringBuilder fsb = new StringBuilder();
128 stringLengths.forEach(length -> {
129 fsb.append("%-");
130 fsb.append(length);
131 fsb.append("s");
132 });
133 return fsb.toString();
134 }
135
136 /**
Jian Li556709c2021-02-03 17:54:28 +0900137 * Auto generates DPID from the given name.
138 *
139 * @param name name
140 * @return auto generated DPID
141 */
142 public static String genDpidFromName(String name) {
143 if (name != null) {
144 String hexString = Integer.toHexString(name.hashCode());
145 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
146 }
147
148 return null;
149 }
150
151 /**
Jian Lif97a07e2021-01-13 18:05:00 +0900152 * Prints out the JSON string in pretty format.
153 *
Daniel Park2884b232021-03-04 18:58:47 +0900154 * @param mapper Object mapper
155 * @param jsonString JSON string
Jian Lif97a07e2021-01-13 18:05:00 +0900156 * @return pretty formatted JSON string
157 */
158 public static String prettyJson(ObjectMapper mapper, String jsonString) {
159 try {
160 Object jsonObject = mapper.readValue(jsonString, Object.class);
161 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
162 } catch (IOException e) {
163 log.debug("Json string parsing exception caused by {}", e);
164 }
165 return null;
166 }
Jian Li3ba5c582021-01-14 11:30:36 +0900167
168 /**
169 * Obtains valid IP addresses of the given subnet.
170 *
171 * @param cidr CIDR
172 * @return set of IP addresses
173 */
174 public static Set<IpAddress> getSubnetIps(String cidr) {
175 SubnetUtils utils = new SubnetUtils(cidr);
176 utils.setInclusiveHostCount(false);
177 SubnetUtils.SubnetInfo info = utils.getInfo();
178 Set<String> allAddresses =
179 new HashSet<>(Arrays.asList(info.getAllAddresses()));
180
181 if (allAddresses.size() > 2) {
182 allAddresses.remove(info.getLowAddress());
183 allAddresses.remove(info.getHighAddress());
184 }
185
186 return allAddresses.stream()
187 .map(IpAddress::valueOf).collect(Collectors.toSet());
188 }
189
190 /**
191 * Calculate the broadcast address from given IP address and subnet prefix length.
192 *
Daniel Park2884b232021-03-04 18:58:47 +0900193 * @param ipAddr IP address
194 * @param prefixLength subnet prefix length
Jian Li3ba5c582021-01-14 11:30:36 +0900195 * @return broadcast address
196 */
197 public static String getBroadcastAddr(String ipAddr, int prefixLength) {
198 String subnet = ipAddr + "/" + prefixLength;
199 SubnetUtils utils = new SubnetUtils(subnet);
200 return utils.getInfo().getBroadcastAddress();
201 }
Daniel Park2884b232021-03-04 18:58:47 +0900202
Jian Li034820d2021-01-15 16:58:48 +0900203 /**
204 * Generates endpoint URL by referring to scheme, ipAddress and port.
205 *
Daniel Park2884b232021-03-04 18:58:47 +0900206 * @param scheme scheme
207 * @param ipAddress IP address
208 * @param port port number
Jian Li034820d2021-01-15 16:58:48 +0900209 * @return generated endpoint URL
210 */
211 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
212 StringBuilder endpoint = new StringBuilder();
213 String protocol = org.apache.commons.lang3.StringUtils.lowerCase(scheme.name());
214
215 endpoint.append(protocol);
216 endpoint.append(COLON_SLASH);
217 endpoint.append(ipAddress.toString());
218 endpoint.append(COLON);
219 endpoint.append(port);
220
221 return endpoint.toString();
222 }
223
224 /**
225 * Generates endpoint URL by referring to scheme, ipAddress and port.
226 *
Daniel Park2884b232021-03-04 18:58:47 +0900227 * @param apiConfig kubernetes API config
Jian Li034820d2021-01-15 16:58:48 +0900228 * @return generated endpoint URL
229 */
230 public static String endpoint(KubevirtApiConfig apiConfig) {
231 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
232 }
233
234 /**
235 * Obtains workable kubernetes client.
236 *
237 * @param config kubernetes API config
238 * @return kubernetes client
239 */
240 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
241 if (config == null) {
242 log.warn("Kubernetes API server config is empty.");
243 return null;
244 }
245
246 String endpoint = endpoint(config);
247
248 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
249
250 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
251 configBuilder.withTrustCerts(true)
Jian Li034820d2021-01-15 16:58:48 +0900252 .withCaCertData(config.caCertData())
253 .withClientCertData(config.clientCertData())
254 .withClientKeyData(config.clientKeyData());
255 }
256
257 return new DefaultKubernetesClient(configBuilder.build());
258 }
259
260 /**
261 * Obtains workable kubernetes client.
262 *
263 * @param service kubernetes API service
264 * @return kubernetes client
265 */
266 public static KubernetesClient k8sClient(KubevirtApiConfigService service) {
267 KubevirtApiConfig config = service.apiConfig();
268 if (config == null) {
269 log.error("Failed to find valid kubernetes API configuration.");
270 return null;
271 }
272
273 KubernetesClient client = k8sClient(config);
274
275 if (client == null) {
276 log.error("Failed to connect to kubernetes API server.");
277 return null;
278 }
279
280 return client;
281 }
Jian Lica20b712021-01-18 00:19:31 +0900282
283 /**
Jian Li556709c2021-02-03 17:54:28 +0900284 * Obtains the hex string of the given segment ID with fixed padding.
285 *
286 * @param segIdStr segment identifier string
287 * @return hex string with padding
288 */
289 public static String segmentIdHex(String segIdStr) {
290 int segId = Integer.parseInt(segIdStr);
291 return String.format("%06x", segId).toLowerCase();
292 }
293
294 /**
Jian Li858ccd72021-02-04 17:25:01 +0900295 * Obtains the tunnel port number with the given network and node.
296 *
297 * @param network kubevirt network
Daniel Park2884b232021-03-04 18:58:47 +0900298 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900299 * @return tunnel port number
300 */
301 public static PortNumber tunnelPort(KubevirtNetwork network, KubevirtNode node) {
302 switch (network.type()) {
303 case VXLAN:
304 return node.vxlanPort();
305 case GRE:
306 return node.grePort();
307 case GENEVE:
308 return node.genevePort();
309 default:
310 break;
311 }
312 return null;
313 }
314
315 /**
Jian Lica20b712021-01-18 00:19:31 +0900316 * Obtains the kubevirt port from kubevirt POD.
317 *
318 * @param networks set of existing kubevirt networks
Daniel Park2884b232021-03-04 18:58:47 +0900319 * @param pod kubevirt POD
Jian Lica20b712021-01-18 00:19:31 +0900320 * @return kubevirt port
321 */
322 public static KubevirtPort getPort(Set<KubevirtNetwork> networks, Pod pod) {
323 try {
324 Map<String, String> annots = pod.getMetadata().getAnnotations();
Jian Li6e66a302021-01-21 20:30:52 +0900325 if (annots == null) {
326 return null;
327 }
328
Jian Li7a581b12021-02-18 14:24:32 +0900329 if (!annots.containsKey(NETWORK_STATUS_KEY)) {
330 return null;
331 }
332
Jian Lica20b712021-01-18 00:19:31 +0900333 String networkStatusStr = annots.get(NETWORK_STATUS_KEY);
334
335 if (networkStatusStr == null) {
336 return null;
337 }
338
339 JSONArray networkStatus = new JSONArray(networkStatusStr);
340
341 for (int i = 0; i < networkStatus.length(); i++) {
342 JSONObject object = networkStatus.getJSONObject(i);
343 String name = object.getString(NAME);
344 KubevirtNetwork network = networks.stream()
345 .filter(n -> (NETWORK_PREFIX + n.name()).equals(name))
346 .findAny().orElse(null);
347 if (network != null) {
348 String mac = object.getString(MAC);
349
350 KubevirtPort.Builder builder = DefaultKubevirtPort.builder()
351 .macAddress(MacAddress.valueOf(mac))
352 .networkId(network.networkId());
353
354 if (object.has(IPS)) {
355 JSONArray ips = object.getJSONArray(IPS);
356 String ip = (String) ips.get(0);
357 builder.ipAddress(IpAddress.valueOf(ip));
358 }
359
360 return builder.build();
361 }
362 }
363
364 } catch (JSONException e) {
365 log.error("Failed to parse network status object", e);
366 }
367
368 return null;
369 }
Jian Li858ccd72021-02-04 17:25:01 +0900370
371 /**
372 * Obtains the tunnel bridge to tenant bridge patch port number.
373 *
Daniel Park2884b232021-03-04 18:58:47 +0900374 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900375 * @param network kubevirt network
376 * @return patch port number
377 */
378 public static PortNumber tunnelToTenantPort(KubevirtNode node, KubevirtNetwork network) {
379 if (network.segmentId() == null) {
380 return null;
381 }
382
383 if (node.tunBridge() == null) {
384 return null;
385 }
386
387 String tunToTenantPortName = TUNNEL_TO_TENANT_PREFIX + segmentIdHex(network.segmentId());
388 return portNumber(node.tunBridge(), tunToTenantPortName);
389 }
390
391 /**
392 * Obtains the tunnel port number of the given node.
393 *
Daniel Park2884b232021-03-04 18:58:47 +0900394 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900395 * @param network kubevirt network
396 * @return tunnel port number
397 */
398 public static PortNumber tunnelPort(KubevirtNode node, KubevirtNetwork network) {
399 if (network.segmentId() == null) {
400 return null;
401 }
402
403 if (node.tunBridge() == null) {
404 return null;
405 }
406
407 switch (network.type()) {
408 case VXLAN:
409 return node.vxlanPort();
410 case GRE:
411 return node.grePort();
412 case GENEVE:
413 return node.genevePort();
414 case FLAT:
Jian Li2ce718e2021-02-17 20:42:15 +0900415 case VLAN:
Jian Li858ccd72021-02-04 17:25:01 +0900416 default:
417 // do nothing
418 return null;
419 }
420 }
421
Jian Li810f58c2021-02-27 01:10:50 +0900422 public static String parseResourceName(String resource) {
423 try {
424 JSONObject json = new JSONObject(resource);
425 return json.getJSONObject("metadata").getString("name");
426 } catch (JSONException e) {
427 log.error("");
428 }
429 return "";
430 }
431
Jian Li858ccd72021-02-04 17:25:01 +0900432 private static PortNumber portNumber(DeviceId deviceId, String portName) {
433 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
434 Port port = deviceService.getPorts(deviceId).stream()
435 .filter(p -> p.isEnabled() &&
436 Objects.equals(p.annotations().value(PORT_NAME), portName))
437 .findAny().orElse(null);
438 return port != null ? port.number() : null;
439 }
440
Daniel Park2884b232021-03-04 18:58:47 +0900441 /**
442 * Returns the gateway node for the specified kubevirt router.
443 * Among gateways, only one gateway would act as a gateway per perter.
444 * Currently gateway node is selected based on modulo operation with router hashcode.
445 *
446 * @param nodeService kubevirt node service
447 * @param router kubevirt router
448 * @return elected gateway node
449 */
450 public static KubevirtNode gatewayNodeForSpecifiedRouter(KubevirtNodeService nodeService,
451 KubevirtRouter router) {
452 //TODO: enhance election logic for a better load balancing
453
454 int numOfGateways = nodeService.completeNodes(GATEWAY).size();
455 if (numOfGateways == 0) {
456 return null;
457 }
458 return (KubevirtNode) nodeService.completeNodes(GATEWAY).toArray()[router.hashCode() % numOfGateways];
459 }
460
461 /**
462 * Returns the mac address of the br-int port of specified device.
463 *
464 * @param deviceService device service
465 * @param deviceId device Id
466 * @return mac address of the br-int port
467 */
468 public static MacAddress getbrIntMacAddress(DeviceService deviceService,
469 DeviceId deviceId) {
470 return MacAddress.valueOf(deviceService.getPorts(deviceId).stream()
471 .filter(port -> Objects.equals(port.annotations().value(PORT_NAME), "br-int"))
472 .map(port -> port.annotations().value("portMac"))
473 .findAny().orElse(null));
474 }
475
476 /**
477 * Returns the snat ip address with specified router.
478 *
479 * @param routerService router service
480 * @param internalNetworkId internal network id which is associated with the router
481 * @return snat ip address if exist, null otherwise
482 */
483 public static IpAddress getRouterSnatIpAddress(KubevirtRouterService routerService,
484 String internalNetworkId) {
485 KubevirtRouter router = routerService.routers().stream()
486 .filter(r -> r.internal().contains(internalNetworkId))
487 .findAny().orElse(null);
488
489 if (router == null) {
490 return null;
491 }
492
493 String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
494
495 if (routerSnatIp == null) {
496 return null;
497 }
498
499 return Ip4Address.valueOf(routerSnatIp);
500 }
Jian Li43244382021-01-09 00:19:02 +0900501}