blob: 04b6dbad6e80d5a59ffb1304195cb99f3697f23e [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 Lid5e8ea82021-01-18 00:19:31 +090020import io.fabric8.kubernetes.api.model.Pod;
Jian Li16eed162021-01-15 16:58:48 +090021import io.fabric8.kubernetes.client.ConfigBuilder;
22import io.fabric8.kubernetes.client.DefaultKubernetesClient;
23import io.fabric8.kubernetes.client.KubernetesClient;
Jian Li9871cd52021-01-09 00:19:02 +090024import org.apache.commons.lang.StringUtils;
Jian Li7bca1272021-01-14 11:30:36 +090025import org.apache.commons.net.util.SubnetUtils;
Jian Lid5e8ea82021-01-18 00:19:31 +090026import org.json.JSONArray;
27import org.json.JSONException;
28import org.json.JSONObject;
Jian Li543fe852021-02-04 17:25:01 +090029import org.onlab.osgi.DefaultServiceDirectory;
Jian Li7bca1272021-01-14 11:30:36 +090030import org.onlab.packet.IpAddress;
Jian Lid5e8ea82021-01-18 00:19:31 +090031import org.onlab.packet.MacAddress;
Jian Li9871cd52021-01-09 00:19:02 +090032import org.onosproject.cfg.ConfigProperty;
Jian Lid5e8ea82021-01-18 00:19:31 +090033import org.onosproject.kubevirtnetworking.api.DefaultKubevirtPort;
34import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
35import org.onosproject.kubevirtnetworking.api.KubevirtPort;
Jian Li16eed162021-01-15 16:58:48 +090036import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
37import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
Jian Li543fe852021-02-04 17:25:01 +090038import org.onosproject.kubevirtnode.api.KubevirtNode;
39import org.onosproject.net.DeviceId;
40import org.onosproject.net.Port;
41import org.onosproject.net.PortNumber;
42import org.onosproject.net.device.DeviceService;
Jian Li9871cd52021-01-09 00:19:02 +090043import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
45
Jian Li260231e2021-01-13 18:05:00 +090046import java.io.IOException;
Jian Li7bca1272021-01-14 11:30:36 +090047import java.util.Arrays;
48import java.util.HashSet;
Jian Li260231e2021-01-13 18:05:00 +090049import java.util.List;
Jian Lid5e8ea82021-01-18 00:19:31 +090050import java.util.Map;
Jian Li543fe852021-02-04 17:25:01 +090051import java.util.Objects;
Jian Li9871cd52021-01-09 00:19:02 +090052import java.util.Optional;
53import java.util.Set;
Jian Li7bca1272021-01-14 11:30:36 +090054import java.util.stream.Collectors;
Jian Li9871cd52021-01-09 00:19:02 +090055
Jian Li543fe852021-02-04 17:25:01 +090056import static org.onosproject.kubevirtnetworking.api.Constants.TUNNEL_TO_TENANT_PREFIX;
57import static org.onosproject.net.AnnotationKeys.PORT_NAME;
58
Jian Li9871cd52021-01-09 00:19:02 +090059/**
60 * An utility that used in KubeVirt networking app.
61 */
62public final class KubevirtNetworkingUtil {
63
64 private static final Logger log = LoggerFactory.getLogger(KubevirtNetworkingUtil.class);
65
66 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li16eed162021-01-15 16:58:48 +090067 private static final String COLON_SLASH = "://";
68 private static final String COLON = ":";
Jian Lib5ab63c2021-02-03 17:54:28 +090069 private static final String OF_PREFIX = "of:";
Jian Li9871cd52021-01-09 00:19:02 +090070
Jian Lid5e8ea82021-01-18 00:19:31 +090071 private static final String NETWORK_STATUS_KEY = "k8s.v1.cni.cncf.io/network-status";
72 private static final String NAME = "name";
73 private static final String NETWORK_PREFIX = "default/";
74 private static final String MAC = "mac";
75 private static final String IPS = "ips";
76
Jian Li9871cd52021-01-09 00:19:02 +090077 /**
78 * Prevents object installation from external.
79 */
80 private KubevirtNetworkingUtil() {
81 }
82
83 /**
84 * Obtains the boolean property value with specified property key name.
85 *
86 * @param properties a collection of properties
87 * @param name key name
88 * @return mapping value
89 */
90 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties,
91 String name) {
92 Optional<ConfigProperty> property =
93 properties.stream().filter(p -> p.name().equals(name)).findFirst();
94
95 return property.map(ConfigProperty::asBoolean).orElse(false);
96 }
97
98 /**
99 * Re-structures the OVS port name.
100 * The length of OVS port name should be not large than 15.
101 *
102 * @param portName original port name
103 * @return re-structured OVS port name
104 */
105 public static String structurePortName(String portName) {
106
107 // The size of OVS port name should not be larger than 15
108 if (portName.length() > PORT_NAME_MAX_LENGTH) {
109 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
110 }
111
112 return portName;
113 }
Jian Li260231e2021-01-13 18:05:00 +0900114
115 /**
116 * Generates string format based on the given string length list.
117 *
118 * @param stringLengths a list of string lengths
119 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
120 */
121 public static String genFormatString(List<Integer> stringLengths) {
122 StringBuilder fsb = new StringBuilder();
123 stringLengths.forEach(length -> {
124 fsb.append("%-");
125 fsb.append(length);
126 fsb.append("s");
127 });
128 return fsb.toString();
129 }
130
131 /**
Jian Lib5ab63c2021-02-03 17:54:28 +0900132 * Auto generates DPID from the given name.
133 *
134 * @param name name
135 * @return auto generated DPID
136 */
137 public static String genDpidFromName(String name) {
138 if (name != null) {
139 String hexString = Integer.toHexString(name.hashCode());
140 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
141 }
142
143 return null;
144 }
145
146 /**
Jian Li260231e2021-01-13 18:05:00 +0900147 * Prints out the JSON string in pretty format.
148 *
149 * @param mapper Object mapper
150 * @param jsonString JSON string
151 * @return pretty formatted JSON string
152 */
153 public static String prettyJson(ObjectMapper mapper, String jsonString) {
154 try {
155 Object jsonObject = mapper.readValue(jsonString, Object.class);
156 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
157 } catch (IOException e) {
158 log.debug("Json string parsing exception caused by {}", e);
159 }
160 return null;
161 }
Jian Li7bca1272021-01-14 11:30:36 +0900162
163 /**
164 * Obtains valid IP addresses of the given subnet.
165 *
166 * @param cidr CIDR
167 * @return set of IP addresses
168 */
169 public static Set<IpAddress> getSubnetIps(String cidr) {
170 SubnetUtils utils = new SubnetUtils(cidr);
171 utils.setInclusiveHostCount(false);
172 SubnetUtils.SubnetInfo info = utils.getInfo();
173 Set<String> allAddresses =
174 new HashSet<>(Arrays.asList(info.getAllAddresses()));
175
176 if (allAddresses.size() > 2) {
177 allAddresses.remove(info.getLowAddress());
178 allAddresses.remove(info.getHighAddress());
179 }
180
181 return allAddresses.stream()
182 .map(IpAddress::valueOf).collect(Collectors.toSet());
183 }
184
185 /**
186 * Calculate the broadcast address from given IP address and subnet prefix length.
187 *
188 * @param ipAddr IP address
189 * @param prefixLength subnet prefix length
190 * @return broadcast address
191 */
192 public static String getBroadcastAddr(String ipAddr, int prefixLength) {
193 String subnet = ipAddr + "/" + prefixLength;
194 SubnetUtils utils = new SubnetUtils(subnet);
195 return utils.getInfo().getBroadcastAddress();
196 }
Jian Li16eed162021-01-15 16:58:48 +0900197 /**
198 * Generates endpoint URL by referring to scheme, ipAddress and port.
199 *
200 * @param scheme scheme
201 * @param ipAddress IP address
202 * @param port port number
203 * @return generated endpoint URL
204 */
205 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
206 StringBuilder endpoint = new StringBuilder();
207 String protocol = org.apache.commons.lang3.StringUtils.lowerCase(scheme.name());
208
209 endpoint.append(protocol);
210 endpoint.append(COLON_SLASH);
211 endpoint.append(ipAddress.toString());
212 endpoint.append(COLON);
213 endpoint.append(port);
214
215 return endpoint.toString();
216 }
217
218 /**
219 * Generates endpoint URL by referring to scheme, ipAddress and port.
220 *
221 * @param apiConfig kubernetes API config
222 * @return generated endpoint URL
223 */
224 public static String endpoint(KubevirtApiConfig apiConfig) {
225 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
226 }
227
228 /**
229 * Obtains workable kubernetes client.
230 *
231 * @param config kubernetes API config
232 * @return kubernetes client
233 */
234 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
235 if (config == null) {
236 log.warn("Kubernetes API server config is empty.");
237 return null;
238 }
239
240 String endpoint = endpoint(config);
241
242 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
243
244 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
245 configBuilder.withTrustCerts(true)
Jian Li16eed162021-01-15 16:58:48 +0900246 .withCaCertData(config.caCertData())
247 .withClientCertData(config.clientCertData())
248 .withClientKeyData(config.clientKeyData());
249 }
250
251 return new DefaultKubernetesClient(configBuilder.build());
252 }
253
254 /**
255 * Obtains workable kubernetes client.
256 *
257 * @param service kubernetes API service
258 * @return kubernetes client
259 */
260 public static KubernetesClient k8sClient(KubevirtApiConfigService service) {
261 KubevirtApiConfig config = service.apiConfig();
262 if (config == null) {
263 log.error("Failed to find valid kubernetes API configuration.");
264 return null;
265 }
266
267 KubernetesClient client = k8sClient(config);
268
269 if (client == null) {
270 log.error("Failed to connect to kubernetes API server.");
271 return null;
272 }
273
274 return client;
275 }
Jian Lid5e8ea82021-01-18 00:19:31 +0900276
277 /**
Jian Lib5ab63c2021-02-03 17:54:28 +0900278 * Obtains the hex string of the given segment ID with fixed padding.
279 *
280 * @param segIdStr segment identifier string
281 * @return hex string with padding
282 */
283 public static String segmentIdHex(String segIdStr) {
284 int segId = Integer.parseInt(segIdStr);
285 return String.format("%06x", segId).toLowerCase();
286 }
287
288 /**
Jian Li543fe852021-02-04 17:25:01 +0900289 * Obtains the tunnel port number with the given network and node.
290 *
291 * @param network kubevirt network
292 * @param node kubevirt node
293 * @return tunnel port number
294 */
295 public static PortNumber tunnelPort(KubevirtNetwork network, KubevirtNode node) {
296 switch (network.type()) {
297 case VXLAN:
298 return node.vxlanPort();
299 case GRE:
300 return node.grePort();
301 case GENEVE:
302 return node.genevePort();
303 default:
304 break;
305 }
306 return null;
307 }
308
309 /**
Jian Lid5e8ea82021-01-18 00:19:31 +0900310 * Obtains the kubevirt port from kubevirt POD.
311 *
312 * @param networks set of existing kubevirt networks
313 * @param pod kubevirt POD
314 * @return kubevirt port
315 */
316 public static KubevirtPort getPort(Set<KubevirtNetwork> networks, Pod pod) {
317 try {
318 Map<String, String> annots = pod.getMetadata().getAnnotations();
Jian Li840156c2021-01-21 20:30:52 +0900319 if (annots == null) {
320 return null;
321 }
322
Jian Li2417ab72021-02-02 17:35:12 +0900323 if (!annots.containsKey(NETWORK_STATUS_KEY)) {
324 return null;
325 }
326
Jian Lid5e8ea82021-01-18 00:19:31 +0900327 String networkStatusStr = annots.get(NETWORK_STATUS_KEY);
328
329 if (networkStatusStr == null) {
330 return null;
331 }
332
333 JSONArray networkStatus = new JSONArray(networkStatusStr);
334
335 for (int i = 0; i < networkStatus.length(); i++) {
336 JSONObject object = networkStatus.getJSONObject(i);
337 String name = object.getString(NAME);
338 KubevirtNetwork network = networks.stream()
339 .filter(n -> (NETWORK_PREFIX + n.name()).equals(name))
340 .findAny().orElse(null);
341 if (network != null) {
342 String mac = object.getString(MAC);
343
344 KubevirtPort.Builder builder = DefaultKubevirtPort.builder()
345 .macAddress(MacAddress.valueOf(mac))
346 .networkId(network.networkId());
347
348 if (object.has(IPS)) {
349 JSONArray ips = object.getJSONArray(IPS);
350 String ip = (String) ips.get(0);
351 builder.ipAddress(IpAddress.valueOf(ip));
352 }
353
354 return builder.build();
355 }
356 }
357
358 } catch (JSONException e) {
359 log.error("Failed to parse network status object", e);
360 }
361
362 return null;
363 }
Jian Li543fe852021-02-04 17:25:01 +0900364
365 /**
366 * Obtains the tunnel bridge to tenant bridge patch port number.
367 *
368 * @param node kubevirt node
369 * @param network kubevirt network
370 * @return patch port number
371 */
372 public static PortNumber tunnelToTenantPort(KubevirtNode node, KubevirtNetwork network) {
373 if (network.segmentId() == null) {
374 return null;
375 }
376
377 if (node.tunBridge() == null) {
378 return null;
379 }
380
381 String tunToTenantPortName = TUNNEL_TO_TENANT_PREFIX + segmentIdHex(network.segmentId());
382 return portNumber(node.tunBridge(), tunToTenantPortName);
383 }
384
385 /**
386 * Obtains the tunnel port number of the given node.
387 *
388 * @param node kubevirt node
389 * @param network kubevirt network
390 * @return tunnel port number
391 */
392 public static PortNumber tunnelPort(KubevirtNode node, KubevirtNetwork network) {
393 if (network.segmentId() == null) {
394 return null;
395 }
396
397 if (node.tunBridge() == null) {
398 return null;
399 }
400
401 switch (network.type()) {
402 case VXLAN:
403 return node.vxlanPort();
404 case GRE:
405 return node.grePort();
406 case GENEVE:
407 return node.genevePort();
408 case FLAT:
Jian Li81b1aab2021-02-17 20:42:15 +0900409 case VLAN:
Jian Li543fe852021-02-04 17:25:01 +0900410 default:
411 // do nothing
412 return null;
413 }
414 }
415
416 private static PortNumber portNumber(DeviceId deviceId, String portName) {
417 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
418 Port port = deviceService.getPorts(deviceId).stream()
419 .filter(p -> p.isEnabled() &&
420 Objects.equals(p.annotations().value(PORT_NAME), portName))
421 .findAny().orElse(null);
422 return port != null ? port.number() : null;
423 }
424
Jian Li9871cd52021-01-09 00:19:02 +0900425}