blob: 754efdf599f624ed3c749dede4c98cb1be325bbc [file] [log] [blame]
Jian Libde20bf2019-01-25 17:34:43 +09001/*
2 * Copyright 2019-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.k8snetworking.util;
17
18import com.fasterxml.jackson.core.JsonParseException;
19import com.fasterxml.jackson.core.JsonProcessingException;
20import com.fasterxml.jackson.databind.JsonMappingException;
21import com.fasterxml.jackson.databind.ObjectMapper;
Jian Li2cc2b632019-02-18 00:56:40 +090022import com.google.common.collect.Maps;
Jian Lie1a5b8f2019-07-23 17:13:19 +090023import io.fabric8.kubernetes.api.model.Container;
24import io.fabric8.kubernetes.api.model.ContainerPort;
25import io.fabric8.kubernetes.api.model.Namespace;
26import io.fabric8.kubernetes.api.model.Pod;
Jian Li85387732019-02-19 23:56:18 +090027import io.fabric8.kubernetes.client.ConfigBuilder;
28import io.fabric8.kubernetes.client.DefaultKubernetesClient;
29import io.fabric8.kubernetes.client.KubernetesClient;
30import org.apache.commons.lang3.StringUtils;
Jian Li9b199162019-02-10 18:00:35 +090031import org.apache.commons.net.util.SubnetUtils;
32import org.onlab.packet.IpAddress;
Jian Li140d8a22019-04-24 23:41:44 +090033import org.onlab.packet.TpPort;
Jian Li4aa17642019-01-30 00:01:11 +090034import org.onosproject.cfg.ConfigProperty;
Jian Lie1a5b8f2019-07-23 17:13:19 +090035import org.onosproject.k8snetworking.api.K8sNamespaceService;
Jian Li4aa17642019-01-30 00:01:11 +090036import org.onosproject.k8snetworking.api.K8sNetwork;
37import org.onosproject.k8snetworking.api.K8sNetworkService;
Jian Lie1a5b8f2019-07-23 17:13:19 +090038import org.onosproject.k8snetworking.api.K8sPodService;
39import org.onosproject.k8snetworking.api.K8sServiceService;
Jian Li85387732019-02-19 23:56:18 +090040import org.onosproject.k8snode.api.K8sApiConfig;
Jian Li3d1111e2019-02-22 02:02:13 +090041import org.onosproject.k8snode.api.K8sApiConfigService;
Jian Li4aa17642019-01-30 00:01:11 +090042import org.onosproject.k8snode.api.K8sNode;
Jian Li2cc2b632019-02-18 00:56:40 +090043import org.onosproject.k8snode.api.K8sNodeService;
Jian Li4aa17642019-01-30 00:01:11 +090044import org.onosproject.net.PortNumber;
Jian Li2cc2b632019-02-18 00:56:40 +090045import org.onosproject.net.group.DefaultGroupKey;
46import org.onosproject.net.group.GroupKey;
Jian Libde20bf2019-01-25 17:34:43 +090047import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
49
50import java.io.IOException;
Jian Li9b199162019-02-10 18:00:35 +090051import java.util.Arrays;
52import java.util.HashSet;
Jian Li2cc2b632019-02-18 00:56:40 +090053import java.util.Map;
Jian Li140d8a22019-04-24 23:41:44 +090054import java.util.Objects;
Jian Li4aa17642019-01-30 00:01:11 +090055import java.util.Optional;
56import java.util.Set;
Jian Li9b199162019-02-10 18:00:35 +090057import java.util.stream.Collectors;
Jian Libde20bf2019-01-25 17:34:43 +090058
Jian Lie1a5b8f2019-07-23 17:13:19 +090059import static org.onosproject.k8snetworking.api.Constants.DEFAULT_NAMESPACE_HASH;
Jian Libde20bf2019-01-25 17:34:43 +090060import static org.onosproject.k8snetworking.api.Constants.PORT_NAME_PREFIX_CONTAINER;
61
62/**
63 * An utility that used in kubernetes networking app.
64 */
65public final class K8sNetworkingUtil {
66
67 private static final Logger log = LoggerFactory.getLogger(K8sNetworkingUtil.class);
68
Jian Li85387732019-02-19 23:56:18 +090069 private static final String COLON_SLASH = "://";
70 private static final String COLON = ":";
71
Jian Li140d8a22019-04-24 23:41:44 +090072 private static final String STR_ZERO = "0";
73 private static final String STR_ONE = "1";
74 private static final String STR_PADDING = "0000000000000000";
75 private static final int MASK_BEGIN_IDX = 0;
76 private static final int MASK_MAX_IDX = 16;
77 private static final int MASK_RADIX = 2;
78 private static final int PORT_RADIX = 16;
79
Jian Libde20bf2019-01-25 17:34:43 +090080 private K8sNetworkingUtil() {
81 }
82
83 /**
84 * Checks that whether the port is associated with container interface.
85 *
86 * @param portName port name
87 * @return true if the port is associated with container; false otherwise
88 */
89 public static boolean isContainer(String portName) {
Jian Li4aa17642019-01-30 00:01:11 +090090 return portName != null && portName.contains(PORT_NAME_PREFIX_CONTAINER);
91 }
92
93 /**
94 * Returns the tunnel port number with specified net ID and kubernetes node.
95 *
96 * @param netId network ID
97 * @param netService network service
98 * @param node kubernetes node
99 * @return tunnel port number
100 */
101 public static PortNumber tunnelPortNumByNetId(String netId,
102 K8sNetworkService netService,
103 K8sNode node) {
104 K8sNetwork.Type netType = netService.network(netId).type();
105
106 if (netType == null) {
107 return null;
108 }
109
110 return tunnelPortNumByNetType(netType, node);
111 }
112
113 /**
114 * Returns the tunnel port number with specified net type and kubernetes node.
115 *
116 * @param netType network type
117 * @param node kubernetes node
118 * @return tunnel port number
119 */
120 public static PortNumber tunnelPortNumByNetType(K8sNetwork.Type netType,
121 K8sNode node) {
122 switch (netType) {
123 case VXLAN:
124 return node.vxlanPortNum();
125 case GRE:
126 return node.grePortNum();
127 case GENEVE:
128 return node.genevePortNum();
129 default:
130 return null;
131 }
132 }
133
134 /**
135 * Obtains the property value with specified property key name.
136 *
137 * @param properties a collection of properties
138 * @param name key name
139 * @return mapping value
140 */
141 public static String getPropertyValue(Set<ConfigProperty> properties,
142 String name) {
143 Optional<ConfigProperty> property =
144 properties.stream().filter(p -> p.name().equals(name)).findFirst();
145 return property.map(ConfigProperty::value).orElse(null);
Jian Libde20bf2019-01-25 17:34:43 +0900146 }
147
148 /**
149 * Prints out the JSON string in pretty format.
150 *
151 * @param mapper Object mapper
152 * @param jsonString JSON string
153 * @return pretty formatted JSON string
154 */
155 public static String prettyJson(ObjectMapper mapper, String jsonString) {
156 try {
157 Object jsonObject = mapper.readValue(jsonString, Object.class);
158 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
159 } catch (JsonParseException e) {
160 log.debug("JsonParseException caused by {}", e);
161 } catch (JsonMappingException e) {
162 log.debug("JsonMappingException caused by {}", e);
163 } catch (JsonProcessingException e) {
164 log.debug("JsonProcessingException caused by {}", e);
165 } catch (IOException e) {
166 log.debug("IOException caused by {}", e);
167 }
168 return null;
169 }
Jian Li9b199162019-02-10 18:00:35 +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);
Jian Li7970b712019-05-03 20:58:21 +0900179 utils.setInclusiveHostCount(false);
Jian Li9b199162019-02-10 18:00:35 +0900180 SubnetUtils.SubnetInfo info = utils.getInfo();
181 Set<String> allAddresses =
182 new HashSet<>(Arrays.asList(info.getAllAddresses()));
183
184 if (allAddresses.size() > 2) {
Jian Li7970b712019-05-03 20:58:21 +0900185 allAddresses.remove(info.getLowAddress());
186 allAddresses.remove(info.getHighAddress());
Jian Li9b199162019-02-10 18:00:35 +0900187 }
188
189 return allAddresses.stream()
190 .map(IpAddress::valueOf).collect(Collectors.toSet());
191 }
Jian Li85387732019-02-19 23:56:18 +0900192
193 /**
Jian Li2cc2b632019-02-18 00:56:40 +0900194 * Obtains flow group key from the given id.
195 *
196 * @param groupId flow group identifier
197 * @return flow group key
198 */
199 public static GroupKey getGroupKey(int groupId) {
200 return new DefaultGroupKey((Integer.toString(groupId)).getBytes());
201 }
202
203 /**
Jian Li85387732019-02-19 23:56:18 +0900204 * Generates endpoint URL by referring to scheme, ipAddress and port.
205 *
206 * @param scheme scheme
207 * @param ipAddress IP address
208 * @param port port number
209 * @return generated endpoint URL
210 */
211 public static String endpoint(K8sApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
212 StringBuilder endpoint = new StringBuilder();
213 String protocol = 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 *
227 * @param apiConfig kubernetes API config
228 * @return generated endpoint URL
229 */
230 public static String endpoint(K8sApiConfig 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(K8sApiConfig 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() == K8sApiConfig.Scheme.HTTPS) {
251 configBuilder.withTrustCerts(true)
252 .withOauthToken(config.token())
253 .withCaCertData(config.caCertData())
254 .withClientCertData(config.clientCertData())
255 .withClientKeyData(config.clientKeyData());
256 }
257
258 return new DefaultKubernetesClient(configBuilder.build());
259 }
Jian Li3d1111e2019-02-22 02:02:13 +0900260
261 /**
262 * Obtains workable kubernetes client.
263 *
264 * @param service kubernetes API service
265 * @return kubernetes client
266 */
267 public static KubernetesClient k8sClient(K8sApiConfigService service) {
268 K8sApiConfig config =
269 service.apiConfigs().stream().findAny().orElse(null);
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 Li2cc2b632019-02-18 00:56:40 +0900284
285 /**
286 * Obtains the kubernetes node IP and kubernetes network gateway IP map.
287 *
288 * @param nodeService kubernetes node service
289 * @param networkService kubernetes network service
290 * @return kubernetes node IP and kubernetes network gateway IP map
291 */
292 public static Map<String, String> nodeIpGatewayIpMap(K8sNodeService nodeService,
293 K8sNetworkService networkService) {
294 Map<String, String> ipMap = Maps.newConcurrentMap();
295
296 nodeService.completeNodes().forEach(n -> {
297 K8sNetwork network = networkService.network(n.hostname());
298 if (network != null) {
299 ipMap.put(n.dataIp().toString(), network.gatewayIp().toString());
300 }
301 });
302
303 return ipMap;
304 }
Jian Li004526d2019-02-25 16:26:27 +0900305
306 /**
Jian Li73d3b6a2019-07-08 18:07:53 +0900307 * Returns a shifted IP address.
308 *
309 * @param ipAddress IP address to be shifted
310 * @param shiftPrefix A IP prefix used in shifted IP address
311 * @return shifted Ip address
312 */
313 public static String shiftIpDomain(String ipAddress, String shiftPrefix) {
314 String origIpPrefix = ipAddress.split("\\.")[0] + "." + ipAddress.split("\\.")[1];
315 return StringUtils.replace(ipAddress, origIpPrefix, shiftPrefix);
316 }
317
318 /**
Jian Li140d8a22019-04-24 23:41:44 +0900319 * Returns an unshifted IP address.
Jian Li004526d2019-02-25 16:26:27 +0900320 *
Jian Li140d8a22019-04-24 23:41:44 +0900321 * @param ipAddress IP address to be unshifted
322 * @param ipPrefix IP prefix which to be used for unshifting
323 * @param cidr a POD network CIDR
324 * @return unshifted IP address
Jian Li004526d2019-02-25 16:26:27 +0900325 */
Jian Li140d8a22019-04-24 23:41:44 +0900326 public static String unshiftIpDomain(String ipAddress,
327 String ipPrefix,
328 String cidr) {
Jian Li004526d2019-02-25 16:26:27 +0900329
Jian Li140d8a22019-04-24 23:41:44 +0900330 String origIpPrefix = cidr.split("\\.")[0] + "." + cidr.split("\\.")[1];
331 return StringUtils.replace(ipAddress, ipPrefix, origIpPrefix);
332 }
Jian Li004526d2019-02-25 16:26:27 +0900333
Jian Li140d8a22019-04-24 23:41:44 +0900334 /**
335 * Returns the B class IP prefix of the given CIDR.
336 *
337 * @param cidr CIDR
338 * @return IP prefix
339 */
340 public static String getBclassIpPrefixFromCidr(String cidr) {
341 if (cidr == null) {
342 return null;
343 }
344 return cidr.split("\\.")[0] + "." + cidr.split("\\.")[1];
345 }
Jian Li004526d2019-02-25 16:26:27 +0900346
Jian Li140d8a22019-04-24 23:41:44 +0900347 /**
348 * Returns the A class IP prefix of the given CIDR.
349 *
350 * @param cidr CIDR
351 * @return IP prefix
352 */
353 public static String getAclassIpPrefixFromCidr(String cidr) {
354 if (cidr == null) {
355 return null;
356 }
357 return cidr.split("\\.")[0];
358 }
359
360 /**
361 * Returns the map of port range.
362 *
363 * @param portMin minimum port number
364 * @param portMax maximum port number
365 * @return map of port range
366 */
367 public static Map<TpPort, TpPort> buildPortRangeMatches(int portMin, int portMax) {
368
369 boolean processing = true;
370 int start = portMin;
371 Map<TpPort, TpPort> portMaskMap = Maps.newHashMap();
372 while (processing) {
373 String minStr = Integer.toBinaryString(start);
374 String binStrMinPadded = STR_PADDING.substring(minStr.length()) + minStr;
375
376 int mask = testMasks(binStrMinPadded, start, portMax);
377 int maskStart = binLower(binStrMinPadded, mask);
378 int maskEnd = binHigher(binStrMinPadded, mask);
379
380 log.debug("start : {} port/mask = {} / {} ", start, getMask(mask), maskStart);
381 portMaskMap.put(TpPort.tpPort(maskStart), TpPort.tpPort(
382 Integer.parseInt(Objects.requireNonNull(getMask(mask)), PORT_RADIX)));
383
384 start = maskEnd + 1;
385 if (start > portMax) {
386 processing = false;
387 }
388 }
389
390 return portMaskMap;
391 }
392
Jian Lie1a5b8f2019-07-23 17:13:19 +0900393 /**
394 * Returns the namespace hash value by given POD IP.
395 *
396 * @param k8sPodService kubernetes POD service
397 * @param k8sNamespaceService kubernetes namespace service
398 * @param podIp POD IP address
399 * @return namespace hash value
400 */
401 public static Integer namespaceHashByPodIp(K8sPodService k8sPodService,
402 K8sNamespaceService k8sNamespaceService,
403 String podIp) {
404 String ns = k8sPodService.pods().stream()
405 .filter(pod -> pod.getStatus().getPodIP() != null)
406 .filter(pod -> pod.getStatus().getPodIP().equals(podIp))
407 .map(pod -> pod.getMetadata().getNamespace())
408 .findAny().orElse(null);
409
410 if (ns != null) {
411 return k8sNamespaceService.namespaces().stream()
412 .filter(n -> n.getMetadata().getName().equals(ns))
413 .map(Namespace::hashCode).findAny().orElse(null);
414 } else {
415 return null;
416 }
417 }
418
419 /**
420 * Returns the namespace hash value by given service IP.
421 *
422 * @param k8sServiceService kubernetes service service
423 * @param k8sNamespaceService kubernetes namespace service
424 * @param serviceIp service IP address
425 * @return namespace hash value
426 */
427 public static int namespaceHashByServiceIp(K8sServiceService k8sServiceService,
428 K8sNamespaceService k8sNamespaceService,
429 String serviceIp) {
430 String ns = k8sServiceService.services().stream()
431 .filter(service -> service.getSpec().getClusterIP() != null)
432 .filter(service -> service.getSpec().getClusterIP().equalsIgnoreCase(serviceIp))
433 .map(service -> service.getMetadata().getNamespace())
434 .findAny().orElse(null);
435
436 if (ns != null) {
437 return namespaceHashByNamespace(k8sNamespaceService, ns);
438 } else {
439 return DEFAULT_NAMESPACE_HASH;
440 }
441 }
442
443 /**
444 * Returns the namespace hash value by given namespace name.
445 *
446 * @param k8sNamespaceService kubernetes namespace service
447 * @param ns namespace name
448 * @return namespace hash value
449 */
450 public static int namespaceHashByNamespace(K8sNamespaceService k8sNamespaceService,
451 String ns) {
452
453 return k8sNamespaceService.namespaces().stream()
454 .filter(n -> n.getMetadata().getName() != null)
455 .filter(n -> n.getMetadata().getName().equalsIgnoreCase(ns))
456 .map(Namespace::hashCode).findAny().orElse(DEFAULT_NAMESPACE_HASH);
457 }
458
459 /**
460 * Returns POD instance by POD IP address.
461 *
462 * @param podService kubernetes POD service
463 * @param podIp POD IP address
464 * @return POD instance
465 */
466 public static Pod podByIp(K8sPodService podService, String podIp) {
467 return podService.pods().stream()
468 .filter(pod -> pod.getStatus().getPodIP() != null)
469 .filter(pod -> pod.getStatus().getPodIP().equals(podIp))
470 .findAny().orElse(null);
471 }
472
473 /**
474 * Returns the container port number by given container port name.
475 *
476 * @param pod kubernetes POD
477 * @param portName port name
478 * @return container port number,
479 * return 0 if there is no port number mapped with the given port name
480 */
481 public static int portNumberByName(Pod pod, String portName) {
482 for (Container container : pod.getSpec().getContainers()) {
483 for (ContainerPort cp : container.getPorts()) {
484 if (cp.getName() != null && cp.getName().equals(portName)) {
485 return cp.getContainerPort();
486 }
487 }
488 }
489
490 return 0;
491 }
492
Jian Li140d8a22019-04-24 23:41:44 +0900493 private static int binLower(String binStr, int bits) {
494 StringBuilder outBin = new StringBuilder(
495 binStr.substring(MASK_BEGIN_IDX, MASK_MAX_IDX - bits));
496 for (int i = 0; i < bits; i++) {
497 outBin.append(STR_ZERO);
498 }
499
500 return Integer.parseInt(outBin.toString(), MASK_RADIX);
501 }
502
503 private static int binHigher(String binStr, int bits) {
504 StringBuilder outBin = new StringBuilder(
505 binStr.substring(MASK_BEGIN_IDX, MASK_MAX_IDX - bits));
506 for (int i = 0; i < bits; i++) {
507 outBin.append(STR_ONE);
508 }
509
510 return Integer.parseInt(outBin.toString(), MASK_RADIX);
511 }
512
513 private static int testMasks(String binStr, int start, int end) {
514 int mask = MASK_BEGIN_IDX;
515 for (; mask <= MASK_MAX_IDX; mask++) {
516 int maskStart = binLower(binStr, mask);
517 int maskEnd = binHigher(binStr, mask);
518 if (maskStart < start || maskEnd > end) {
519 return mask - 1;
520 }
521 }
522
523 return mask;
524 }
525
526 private static String getMask(int bits) {
527 switch (bits) {
528 case 0: return "ffff";
529 case 1: return "fffe";
530 case 2: return "fffc";
531 case 3: return "fff8";
532 case 4: return "fff0";
533 case 5: return "ffe0";
534 case 6: return "ffc0";
535 case 7: return "ff80";
536 case 8: return "ff00";
537 case 9: return "fe00";
538 case 10: return "fc00";
539 case 11: return "f800";
540 case 12: return "f000";
541 case 13: return "e000";
542 case 14: return "c000";
543 case 15: return "8000";
544 case 16: return "0000";
545 default: return null;
546 }
Jian Li004526d2019-02-25 16:26:27 +0900547 }
Jian Libde20bf2019-01-25 17:34:43 +0900548}