blob: 34960082e2c07b92ed39831133d344057423a39e [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 Li27996912022-10-18 22:43:15 +090018import com.eclipsesource.json.JsonArray;
Andrea Campanella775e5f82022-06-09 08:11:25 -070019import com.eclipsesource.json.JsonObject;
Jian Li27996912022-10-18 22:43:15 +090020import com.fasterxml.jackson.core.JsonProcessingException;
Jian Li0c656f02021-06-07 13:32:39 +090021import com.fasterxml.jackson.databind.JsonNode;
Jian Lif97a07e2021-01-13 18:05:00 +090022import com.fasterxml.jackson.databind.ObjectMapper;
Jian Li0c656f02021-06-07 13:32:39 +090023import com.fasterxml.jackson.databind.node.ArrayNode;
Jian Li556709c2021-02-03 17:54:28 +090024import com.google.common.base.Strings;
Jian Lid4296d02021-03-12 18:03:58 +090025import com.google.common.collect.ImmutableSet;
Jian Li27996912022-10-18 22:43:15 +090026import io.fabric8.kubernetes.api.model.Node;
27import io.fabric8.kubernetes.api.model.NodeAddress;
28import io.fabric8.kubernetes.api.model.NodeSpec;
29import io.fabric8.kubernetes.api.model.Taint;
Jian Li034820d2021-01-15 16:58:48 +090030import io.fabric8.kubernetes.client.ConfigBuilder;
31import io.fabric8.kubernetes.client.DefaultKubernetesClient;
32import io.fabric8.kubernetes.client.KubernetesClient;
Jian Li43244382021-01-09 00:19:02 +090033import org.apache.commons.lang.StringUtils;
Jian Li3ba5c582021-01-14 11:30:36 +090034import org.apache.commons.net.util.SubnetUtils;
Daniel Park157947f2021-04-09 17:50:53 +090035import org.onlab.packet.ARP;
36import org.onlab.packet.Ethernet;
Daniel Park2884b232021-03-04 18:58:47 +090037import org.onlab.packet.Ip4Address;
Jian Li3ba5c582021-01-14 11:30:36 +090038import org.onlab.packet.IpAddress;
Jian Li27996912022-10-18 22:43:15 +090039import org.onlab.packet.IpPrefix;
Jian Lica20b712021-01-18 00:19:31 +090040import org.onlab.packet.MacAddress;
Jian Li43244382021-01-09 00:19:02 +090041import org.onosproject.cfg.ConfigProperty;
Jian Li27996912022-10-18 22:43:15 +090042import org.onosproject.kubevirtnetworking.api.DefaultKubevirtNetwork;
Jian Lica20b712021-01-18 00:19:31 +090043import org.onosproject.kubevirtnetworking.api.DefaultKubevirtPort;
Daniel Park734b5532022-09-26 15:13:59 +090044import org.onosproject.kubevirtnetworking.api.KubernetesExternalLb;
Jian Li27996912022-10-18 22:43:15 +090045import org.onosproject.kubevirtnetworking.api.KubevirtHostRoute;
46import org.onosproject.kubevirtnetworking.api.KubevirtIpPool;
Daniel Park05a94582021-05-12 10:57:02 +090047import org.onosproject.kubevirtnetworking.api.KubevirtLoadBalancer;
48import org.onosproject.kubevirtnetworking.api.KubevirtLoadBalancerService;
Jian Lica20b712021-01-18 00:19:31 +090049import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
Daniel Parkf3136042021-03-10 07:49:11 +090050import org.onosproject.kubevirtnetworking.api.KubevirtNetworkService;
Jian Lica20b712021-01-18 00:19:31 +090051import org.onosproject.kubevirtnetworking.api.KubevirtPort;
Daniel Park2884b232021-03-04 18:58:47 +090052import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
53import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
Jian Li27996912022-10-18 22:43:15 +090054import org.onosproject.kubevirtnode.api.DefaultKubevirtNode;
55import org.onosproject.kubevirtnode.api.DefaultKubevirtPhyInterface;
Jian Li034820d2021-01-15 16:58:48 +090056import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
57import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
Jian Li858ccd72021-02-04 17:25:01 +090058import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Park2884b232021-03-04 18:58:47 +090059import org.onosproject.kubevirtnode.api.KubevirtNodeService;
Jian Li27996912022-10-18 22:43:15 +090060import org.onosproject.kubevirtnode.api.KubevirtNodeState;
61import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
Jian Li858ccd72021-02-04 17:25:01 +090062import org.onosproject.net.DeviceId;
63import org.onosproject.net.Port;
64import org.onosproject.net.PortNumber;
65import org.onosproject.net.device.DeviceService;
Daniel Park05a94582021-05-12 10:57:02 +090066import org.onosproject.net.group.DefaultGroupKey;
67import org.onosproject.net.group.GroupKey;
Jian Li43244382021-01-09 00:19:02 +090068import org.slf4j.Logger;
69import org.slf4j.LoggerFactory;
Jian Li94b6d162021-04-15 17:09:11 +090070import org.xbill.DNS.Address;
Jian Li43244382021-01-09 00:19:02 +090071
Jian Lif97a07e2021-01-13 18:05:00 +090072import java.io.IOException;
Jian Li94b6d162021-04-15 17:09:11 +090073import java.net.InetAddress;
74import java.net.UnknownHostException;
Jian Li3ba5c582021-01-14 11:30:36 +090075import java.util.Arrays;
76import java.util.HashSet;
Jian Lif97a07e2021-01-13 18:05:00 +090077import java.util.List;
Jian Li27996912022-10-18 22:43:15 +090078import java.util.Locale;
79import java.util.Map;
Jian Li858ccd72021-02-04 17:25:01 +090080import java.util.Objects;
Jian Li43244382021-01-09 00:19:02 +090081import java.util.Optional;
82import java.util.Set;
Jian Li3ba5c582021-01-14 11:30:36 +090083import java.util.stream.Collectors;
Jian Li43244382021-01-09 00:19:02 +090084
Jian Li858ccd72021-02-04 17:25:01 +090085import static org.onosproject.kubevirtnetworking.api.Constants.TUNNEL_TO_TENANT_PREFIX;
Jian Li27996912022-10-18 22:43:15 +090086import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.FLAT;
87import static org.onosproject.kubevirtnode.api.Constants.SONA_PROJECT_DOMAIN;
Daniel Park2884b232021-03-04 18:58:47 +090088import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Jian Li27996912022-10-18 22:43:15 +090089import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.MASTER;
90import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.OTHER;
91import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
Jian Li858ccd72021-02-04 17:25:01 +090092import static org.onosproject.net.AnnotationKeys.PORT_NAME;
93
Jian Li43244382021-01-09 00:19:02 +090094/**
95 * An utility that used in KubeVirt networking app.
96 */
97public final class KubevirtNetworkingUtil {
98
99 private static final Logger log = LoggerFactory.getLogger(KubevirtNetworkingUtil.class);
100
101 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li034820d2021-01-15 16:58:48 +0900102 private static final String COLON_SLASH = "://";
103 private static final String COLON = ":";
Jian Li556709c2021-02-03 17:54:28 +0900104 private static final String OF_PREFIX = "of:";
Jian Li43244382021-01-09 00:19:02 +0900105
Jian Lica20b712021-01-18 00:19:31 +0900106 private static final String NETWORK_STATUS_KEY = "k8s.v1.cni.cncf.io/network-status";
107 private static final String NAME = "name";
108 private static final String NETWORK_PREFIX = "default/";
109 private static final String MAC = "mac";
110 private static final String IPS = "ips";
Daniel Parkbabde9c2021-03-09 13:37:42 +0900111 private static final String BR_INT = "br-int";
Jian Li9557e902021-06-08 10:12:52 +0900112 private static final String METADATA = "metadata";
Jian Li0c656f02021-06-07 13:32:39 +0900113 private static final String STATUS = "status";
114 private static final String INTERFACES = "interfaces";
Jian Li0c656f02021-06-07 13:32:39 +0900115 private static final String NODE_NAME = "nodeName";
Jian Lica20b712021-01-18 00:19:31 +0900116
Jian Li27996912022-10-18 22:43:15 +0900117 private static final String NETWORK_CONFIG = "network-config";
118 private static final String TYPE = "type";
119 private static final String MTU = "mtu";
120 private static final String SEGMENT_ID = "segmentId";
121 private static final String GATEWAY_IP = "gatewayIp";
122 private static final String DEFAULT_ROUTE = "defaultRoute";
123 private static final String CIDR = "cidr";
124 private static final String HOST_ROUTES = "hostRoutes";
125 private static final String DESTINATION = "destination";
126 private static final String NEXTHOP = "nexthop";
127 private static final String IP_POOL = "ipPool";
128 private static final String START = "start";
129 private static final String END = "end";
130 private static final String DNSES = "dnses";
131
132 private static final String INTERNAL_IP = "InternalIP";
133 private static final String K8S_ROLE = "node-role.kubernetes.io";
134 private static final String PHYSNET_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/physnet-config";
135 private static final String DATA_IP_KEY = SONA_PROJECT_DOMAIN + "/data-ip";
136 private static final String GATEWAY_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/gateway-config";
137 private static final String GATEWAY_BRIDGE_NAME = "gatewayBridgeName";
138 private static final String NETWORK_KEY = "network";
139 private static final String INTERFACE_KEY = "interface";
140 private static final String PHYS_BRIDGE_ID = "physBridgeId";
141
142 private static final String NO_SCHEDULE_EFFECT = "NoSchedule";
143 private static final String KUBEVIRT_IO_KEY = "kubevirt.io/drain";
144 private static final String DRAINING_VALUE = "draining";
145
Jian Li43244382021-01-09 00:19:02 +0900146 /**
147 * Prevents object installation from external.
148 */
149 private KubevirtNetworkingUtil() {
150 }
151
152 /**
153 * Obtains the boolean property value with specified property key name.
154 *
Daniel Park2884b232021-03-04 18:58:47 +0900155 * @param properties a collection of properties
156 * @param name key name
Jian Li43244382021-01-09 00:19:02 +0900157 * @return mapping value
158 */
159 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties,
160 String name) {
161 Optional<ConfigProperty> property =
162 properties.stream().filter(p -> p.name().equals(name)).findFirst();
163
164 return property.map(ConfigProperty::asBoolean).orElse(false);
165 }
166
167 /**
168 * Re-structures the OVS port name.
169 * The length of OVS port name should be not large than 15.
170 *
Daniel Park2884b232021-03-04 18:58:47 +0900171 * @param portName original port name
Jian Li43244382021-01-09 00:19:02 +0900172 * @return re-structured OVS port name
173 */
174 public static String structurePortName(String portName) {
175
176 // The size of OVS port name should not be larger than 15
177 if (portName.length() > PORT_NAME_MAX_LENGTH) {
178 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
179 }
180
181 return portName;
182 }
Jian Lif97a07e2021-01-13 18:05:00 +0900183
184 /**
185 * Generates string format based on the given string length list.
186 *
187 * @param stringLengths a list of string lengths
188 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
189 */
190 public static String genFormatString(List<Integer> stringLengths) {
191 StringBuilder fsb = new StringBuilder();
192 stringLengths.forEach(length -> {
193 fsb.append("%-");
194 fsb.append(length);
195 fsb.append("s");
196 });
197 return fsb.toString();
198 }
199
200 /**
Jian Li556709c2021-02-03 17:54:28 +0900201 * Auto generates DPID from the given name.
202 *
203 * @param name name
204 * @return auto generated DPID
205 */
206 public static String genDpidFromName(String name) {
207 if (name != null) {
208 String hexString = Integer.toHexString(name.hashCode());
209 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
210 }
211
212 return null;
213 }
214
215 /**
Jian Lif97a07e2021-01-13 18:05:00 +0900216 * Prints out the JSON string in pretty format.
217 *
Daniel Park2884b232021-03-04 18:58:47 +0900218 * @param mapper Object mapper
219 * @param jsonString JSON string
Jian Lif97a07e2021-01-13 18:05:00 +0900220 * @return pretty formatted JSON string
221 */
222 public static String prettyJson(ObjectMapper mapper, String jsonString) {
223 try {
224 Object jsonObject = mapper.readValue(jsonString, Object.class);
225 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
226 } catch (IOException e) {
227 log.debug("Json string parsing exception caused by {}", e);
228 }
229 return null;
230 }
Jian Li3ba5c582021-01-14 11:30:36 +0900231
232 /**
233 * Obtains valid IP addresses of the given subnet.
234 *
235 * @param cidr CIDR
236 * @return set of IP addresses
237 */
238 public static Set<IpAddress> getSubnetIps(String cidr) {
239 SubnetUtils utils = new SubnetUtils(cidr);
240 utils.setInclusiveHostCount(false);
241 SubnetUtils.SubnetInfo info = utils.getInfo();
242 Set<String> allAddresses =
243 new HashSet<>(Arrays.asList(info.getAllAddresses()));
244
245 if (allAddresses.size() > 2) {
246 allAddresses.remove(info.getLowAddress());
247 allAddresses.remove(info.getHighAddress());
248 }
249
250 return allAddresses.stream()
251 .map(IpAddress::valueOf).collect(Collectors.toSet());
252 }
253
254 /**
255 * Calculate the broadcast address from given IP address and subnet prefix length.
256 *
Daniel Park2884b232021-03-04 18:58:47 +0900257 * @param ipAddr IP address
258 * @param prefixLength subnet prefix length
Jian Li3ba5c582021-01-14 11:30:36 +0900259 * @return broadcast address
260 */
261 public static String getBroadcastAddr(String ipAddr, int prefixLength) {
262 String subnet = ipAddr + "/" + prefixLength;
263 SubnetUtils utils = new SubnetUtils(subnet);
264 return utils.getInfo().getBroadcastAddress();
265 }
Daniel Park2884b232021-03-04 18:58:47 +0900266
Jian Li034820d2021-01-15 16:58:48 +0900267 /**
268 * Generates endpoint URL by referring to scheme, ipAddress and port.
269 *
Daniel Park2884b232021-03-04 18:58:47 +0900270 * @param scheme scheme
271 * @param ipAddress IP address
272 * @param port port number
Jian Li034820d2021-01-15 16:58:48 +0900273 * @return generated endpoint URL
274 */
275 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
276 StringBuilder endpoint = new StringBuilder();
277 String protocol = org.apache.commons.lang3.StringUtils.lowerCase(scheme.name());
278
279 endpoint.append(protocol);
280 endpoint.append(COLON_SLASH);
281 endpoint.append(ipAddress.toString());
282 endpoint.append(COLON);
283 endpoint.append(port);
284
285 return endpoint.toString();
286 }
287
288 /**
289 * Generates endpoint URL by referring to scheme, ipAddress and port.
290 *
Daniel Park2884b232021-03-04 18:58:47 +0900291 * @param apiConfig kubernetes API config
Jian Li034820d2021-01-15 16:58:48 +0900292 * @return generated endpoint URL
293 */
294 public static String endpoint(KubevirtApiConfig apiConfig) {
295 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
296 }
297
298 /**
299 * Obtains workable kubernetes client.
300 *
301 * @param config kubernetes API config
302 * @return kubernetes client
303 */
304 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
305 if (config == null) {
306 log.warn("Kubernetes API server config is empty.");
307 return null;
308 }
309
310 String endpoint = endpoint(config);
311
312 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
313
314 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
315 configBuilder.withTrustCerts(true)
Jian Li034820d2021-01-15 16:58:48 +0900316 .withCaCertData(config.caCertData())
317 .withClientCertData(config.clientCertData())
318 .withClientKeyData(config.clientKeyData());
319 }
320
321 return new DefaultKubernetesClient(configBuilder.build());
322 }
323
324 /**
325 * Obtains workable kubernetes client.
326 *
327 * @param service kubernetes API service
328 * @return kubernetes client
329 */
330 public static KubernetesClient k8sClient(KubevirtApiConfigService service) {
331 KubevirtApiConfig config = service.apiConfig();
332 if (config == null) {
333 log.error("Failed to find valid kubernetes API configuration.");
334 return null;
335 }
336
337 KubernetesClient client = k8sClient(config);
338
339 if (client == null) {
340 log.error("Failed to connect to kubernetes API server.");
341 return null;
342 }
343
344 return client;
345 }
Jian Lica20b712021-01-18 00:19:31 +0900346
347 /**
Jian Li556709c2021-02-03 17:54:28 +0900348 * Obtains the hex string of the given segment ID with fixed padding.
349 *
350 * @param segIdStr segment identifier string
351 * @return hex string with padding
352 */
353 public static String segmentIdHex(String segIdStr) {
354 int segId = Integer.parseInt(segIdStr);
355 return String.format("%06x", segId).toLowerCase();
356 }
357
358 /**
Jian Li858ccd72021-02-04 17:25:01 +0900359 * Obtains the tunnel port number with the given network and node.
360 *
361 * @param network kubevirt network
Daniel Park2884b232021-03-04 18:58:47 +0900362 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900363 * @return tunnel port number
364 */
365 public static PortNumber tunnelPort(KubevirtNetwork network, KubevirtNode node) {
366 switch (network.type()) {
367 case VXLAN:
368 return node.vxlanPort();
369 case GRE:
370 return node.grePort();
371 case GENEVE:
372 return node.genevePort();
Jian Li4b3436a2022-03-23 13:07:19 +0900373 case STT:
374 return node.sttPort();
Jian Li858ccd72021-02-04 17:25:01 +0900375 default:
376 break;
377 }
378 return null;
379 }
380
381 /**
Jian Li0c656f02021-06-07 13:32:39 +0900382 * Obtains the kubevirt port from kubevirt VMI.
Jian Lica20b712021-01-18 00:19:31 +0900383 *
Daniel Parkf3136042021-03-10 07:49:11 +0900384 * @param nodeService kubevirt node service
Jian Lica20b712021-01-18 00:19:31 +0900385 * @param networks set of existing kubevirt networks
Jian Li0c656f02021-06-07 13:32:39 +0900386 * @param resource VMI definition
387 * @return kubevirt ports attached to the VMI
Jian Lica20b712021-01-18 00:19:31 +0900388 */
Jian Lib6dc08f2021-03-24 15:24:18 +0900389 public static Set<KubevirtPort> getPorts(KubevirtNodeService nodeService,
Jian Li0c656f02021-06-07 13:32:39 +0900390 Set<KubevirtNetwork> networks,
391 String resource) {
Jian Lica20b712021-01-18 00:19:31 +0900392 try {
Jian Li0c656f02021-06-07 13:32:39 +0900393 ObjectMapper mapper = new ObjectMapper();
394 JsonNode json = mapper.readTree(resource);
395 JsonNode statusJson = json.get(STATUS);
396 ArrayNode interfacesJson = (ArrayNode) statusJson.get(INTERFACES);
Jian Li9557e902021-06-08 10:12:52 +0900397 String vmName = parseResourceName(resource);
Jian Lib6dc08f2021-03-24 15:24:18 +0900398
Daniel Parkf3136042021-03-10 07:49:11 +0900399 KubevirtPort.Builder builder = DefaultKubevirtPort.builder();
Jian Li0c656f02021-06-07 13:32:39 +0900400 String nodeName = parseVmiNodeName(resource);
401 if (nodeName != null && nodeService.node(nodeName) != null) {
402 builder.deviceId(nodeService.node(nodeName).intgBridge());
Daniel Parkf3136042021-03-10 07:49:11 +0900403 }
Jian Lica20b712021-01-18 00:19:31 +0900404
Jian Li0c656f02021-06-07 13:32:39 +0900405 if (interfacesJson == null) {
406 return ImmutableSet.of();
407 }
Jian Lica20b712021-01-18 00:19:31 +0900408
Jian Li0c656f02021-06-07 13:32:39 +0900409 Set<KubevirtPort> ports = new HashSet<>();
410 for (JsonNode interfaceJson : interfacesJson) {
Jian Li56f241b2021-06-30 20:59:43 +0900411 JsonNode jsonName = interfaceJson.get(NAME);
412
413 // in some cases, name attribute may not be available from the
414 // interface, we skip inspect this interface
415 if (jsonName == null) {
416 continue;
417 }
418
419 String name = jsonName.asText();
Jian Lica20b712021-01-18 00:19:31 +0900420 KubevirtNetwork network = networks.stream()
Jian Li0c656f02021-06-07 13:32:39 +0900421 .filter(n -> (NETWORK_PREFIX + n.name()).equals(name) ||
422 (n.name() + "-net").equals(name))
Jian Lica20b712021-01-18 00:19:31 +0900423 .findAny().orElse(null);
424 if (network != null) {
Jian Li0c656f02021-06-07 13:32:39 +0900425 // FIXME: we do not update IP address, as learning IP address
426 // requires much more time due to the lag from VM agent
427 String mac = interfaceJson.get(MAC).asText();
Jian Li9557e902021-06-08 10:12:52 +0900428 builder.vmName(vmName)
429 .macAddress(MacAddress.valueOf(mac))
Jian Lica20b712021-01-18 00:19:31 +0900430 .networkId(network.networkId());
Jian Lid4296d02021-03-12 18:03:58 +0900431 ports.add(builder.build());
Jian Lica20b712021-01-18 00:19:31 +0900432 }
433 }
Jian Lid4296d02021-03-12 18:03:58 +0900434 return ports;
Jian Li0c656f02021-06-07 13:32:39 +0900435 } catch (IOException e) {
436 log.error("Failed to parse port info from VMI object", e);
437 }
438 return ImmutableSet.of();
439 }
Jian Lid4296d02021-03-12 18:03:58 +0900440
Jian Li0c656f02021-06-07 13:32:39 +0900441 public static String parseVmiNodeName(String resource) {
442 String nodeName = null;
443 try {
444 ObjectMapper mapper = new ObjectMapper();
445 JsonNode json = mapper.readTree(resource);
446 JsonNode statusJson = json.get(STATUS);
447 JsonNode nodeNameJson = statusJson.get(NODE_NAME);
448 nodeName = nodeNameJson != null ? nodeNameJson.asText() : null;
449 } catch (IOException e) {
450 log.error("Failed to parse kubevirt VMI nodename");
Jian Lica20b712021-01-18 00:19:31 +0900451 }
452
Jian Li0c656f02021-06-07 13:32:39 +0900453 return nodeName;
Jian Lica20b712021-01-18 00:19:31 +0900454 }
Jian Li858ccd72021-02-04 17:25:01 +0900455
456 /**
457 * Obtains the tunnel bridge to tenant bridge patch port number.
458 *
Jian Li34fff802021-07-01 10:04:04 +0900459 * @param deviceService device service
Daniel Park2884b232021-03-04 18:58:47 +0900460 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900461 * @param network kubevirt network
462 * @return patch port number
463 */
Jian Li34fff802021-07-01 10:04:04 +0900464 public static PortNumber tunnelToTenantPort(DeviceService deviceService,
465 KubevirtNode node, KubevirtNetwork network) {
Jian Li858ccd72021-02-04 17:25:01 +0900466 if (network.segmentId() == null) {
467 return null;
468 }
469
470 if (node.tunBridge() == null) {
471 return null;
472 }
473
474 String tunToTenantPortName = TUNNEL_TO_TENANT_PREFIX + segmentIdHex(network.segmentId());
Jian Li34fff802021-07-01 10:04:04 +0900475 return portNumber(deviceService, node.tunBridge(), tunToTenantPortName);
Jian Li858ccd72021-02-04 17:25:01 +0900476 }
477
478 /**
479 * Obtains the tunnel port number of the given node.
480 *
Daniel Park2884b232021-03-04 18:58:47 +0900481 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900482 * @param network kubevirt network
483 * @return tunnel port number
484 */
485 public static PortNumber tunnelPort(KubevirtNode node, KubevirtNetwork network) {
486 if (network.segmentId() == null) {
487 return null;
488 }
489
490 if (node.tunBridge() == null) {
491 return null;
492 }
493
494 switch (network.type()) {
495 case VXLAN:
496 return node.vxlanPort();
497 case GRE:
498 return node.grePort();
499 case GENEVE:
500 return node.genevePort();
Jian Li4b3436a2022-03-23 13:07:19 +0900501 case STT:
502 return node.sttPort();
Jian Li858ccd72021-02-04 17:25:01 +0900503 case FLAT:
Jian Li2ce718e2021-02-17 20:42:15 +0900504 case VLAN:
Jian Li858ccd72021-02-04 17:25:01 +0900505 default:
506 // do nothing
507 return null;
508 }
509 }
510
Jian Li810f58c2021-02-27 01:10:50 +0900511 public static String parseResourceName(String resource) {
Andrea Campanella775e5f82022-06-09 08:11:25 -0700512 JsonObject json = JsonObject.readFrom(resource);
513 JsonObject metadata = json.get("metadata").asObject();
514 return metadata != null ? metadata.get("name").asString() : "";
Jian Li810f58c2021-02-27 01:10:50 +0900515 }
516
Jian Li34fff802021-07-01 10:04:04 +0900517 public static PortNumber portNumber(DeviceService deviceService, DeviceId deviceId, String portName) {
Jian Li858ccd72021-02-04 17:25:01 +0900518 Port port = deviceService.getPorts(deviceId).stream()
519 .filter(p -> p.isEnabled() &&
520 Objects.equals(p.annotations().value(PORT_NAME), portName))
521 .findAny().orElse(null);
522 return port != null ? port.number() : null;
523 }
524
Daniel Park2884b232021-03-04 18:58:47 +0900525 /**
526 * Returns the gateway node for the specified kubevirt router.
527 * Among gateways, only one gateway would act as a gateway per perter.
528 * Currently gateway node is selected based on modulo operation with router hashcode.
529 *
530 * @param nodeService kubevirt node service
531 * @param router kubevirt router
532 * @return elected gateway node
533 */
534 public static KubevirtNode gatewayNodeForSpecifiedRouter(KubevirtNodeService nodeService,
535 KubevirtRouter router) {
536 //TODO: enhance election logic for a better load balancing
537
538 int numOfGateways = nodeService.completeNodes(GATEWAY).size();
539 if (numOfGateways == 0) {
540 return null;
541 }
542 return (KubevirtNode) nodeService.completeNodes(GATEWAY).toArray()[router.hashCode() % numOfGateways];
543 }
544
545 /**
Daniel Park734b5532022-09-26 15:13:59 +0900546 * Returns the gateway node for the specified kubernetes external lb.
547 * Among gateways, only one gateway would act as a gateway per external lb.
548 * Currently gateway node is selected based on modulo operation with router hashcode.
549 *
550 * @param nodeService kubevirt node service
551 * @param externalLb kubernetes external lb
552 * @return elected gateway node
553 */
554 public static KubevirtNode gatewayNodeForSpecifiedService(KubevirtNodeService nodeService,
555 KubernetesExternalLb externalLb) {
556 //TODO: enhance election logic for a better load balancing
557
558 int numOfGateways = nodeService.completeNodes(GATEWAY).size();
559 if (numOfGateways == 0) {
560 return null;
561 }
562
563 return (KubevirtNode) nodeService.completeNodes(GATEWAY)
564 .toArray()[externalLb.hashCode() % numOfGateways];
565 }
566
567 /**
Daniel Parkf3136042021-03-10 07:49:11 +0900568 * Returns the mac address of the router.
569 *
570 * @param router kubevirt router
571 * @return macc address of the router
572 */
573 public static MacAddress getRouterMacAddress(KubevirtRouter router) {
574 if (router.mac() == null) {
575 log.warn("Failed to get mac address of router {}", router.name());
576 }
577
578 return router.mac();
Daniel Park2884b232021-03-04 18:58:47 +0900579 }
580
581 /**
582 * Returns the snat ip address with specified router.
583 *
584 * @param routerService router service
585 * @param internalNetworkId internal network id which is associated with the router
586 * @return snat ip address if exist, null otherwise
587 */
588 public static IpAddress getRouterSnatIpAddress(KubevirtRouterService routerService,
589 String internalNetworkId) {
590 KubevirtRouter router = routerService.routers().stream()
591 .filter(r -> r.internal().contains(internalNetworkId))
592 .findAny().orElse(null);
593
594 if (router == null) {
595 return null;
596 }
597
598 String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
599
600 if (routerSnatIp == null) {
601 return null;
602 }
603
604 return Ip4Address.valueOf(routerSnatIp);
605 }
Daniel Parkbabde9c2021-03-09 13:37:42 +0900606
607 /**
608 * Returns the kubevirt router with specified kubevirt port.
609 *
610 * @param routerService kubevirt router service
611 * @param kubevirtPort kubevirt port
612 * @return kubevirt router
613 */
614 public static KubevirtRouter getRouterForKubevirtPort(KubevirtRouterService routerService,
615 KubevirtPort kubevirtPort) {
616 if (kubevirtPort.ipAddress() != null) {
617 return routerService.routers().stream()
618 .filter(r -> r.internal().contains(kubevirtPort.networkId()))
619 .findAny().orElse(null);
620 }
621 return null;
622 }
623
624 /**
625 * Returns the kubevirt router with specified kubevirt network.
626 *
627 * @param routerService kubevirt router service
628 * @param kubevirtNetwork kubevirt network
629 * @return kubevirt router
630 */
631 public static KubevirtRouter getRouterForKubevirtNetwork(KubevirtRouterService routerService,
632 KubevirtNetwork kubevirtNetwork) {
633 return routerService.routers().stream()
634 .filter(router -> router.internal().contains(kubevirtNetwork.networkId()))
635 .findAny().orElse(null);
636 }
Daniel Parkf3136042021-03-10 07:49:11 +0900637
638 /**
639 * Returns the external patch port number with specified gateway.
640 *
641 * @param deviceService device service
Jian Li9793ec42021-03-19 15:03:32 +0900642 * @param gatewayNode gateway node
Daniel Parkf3136042021-03-10 07:49:11 +0900643 * @return external patch port number
644 */
645 public static PortNumber externalPatchPortNum(DeviceService deviceService, KubevirtNode gatewayNode) {
Jian Li63f191f2021-03-25 17:14:40 +0900646 String gatewayBridgeName = gatewayNode.gatewayBridgeName();
647 if (gatewayBridgeName == null) {
Jian Li9793ec42021-03-19 15:03:32 +0900648 log.warn("No external interface is attached to gateway {}", gatewayNode.hostname());
649 return null;
650 }
651
Jian Li63f191f2021-03-25 17:14:40 +0900652 String patchPortName = "int-to-" + gatewayBridgeName;
Daniel Parkf3136042021-03-10 07:49:11 +0900653 Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
654 .filter(p -> p.isEnabled() &&
Jian Li9793ec42021-03-19 15:03:32 +0900655 Objects.equals(p.annotations().value(PORT_NAME), patchPortName))
Daniel Parkf3136042021-03-10 07:49:11 +0900656 .findAny().orElse(null);
657
658 return port != null ? port.number() : null;
659 }
660
Daniel Park157947f2021-04-09 17:50:53 +0900661 /**
662 * Returns the kubevirt external network with specified router.
663 *
664 * @param networkService kubevirt network service
665 * @param router kubevirt router
666 * @return external network
667 */
Daniel Parkf3136042021-03-10 07:49:11 +0900668 public static KubevirtNetwork getExternalNetworkByRouter(KubevirtNetworkService networkService,
669 KubevirtRouter router) {
670 String networkId = router.external().values().stream().findAny().orElse(null);
671 if (networkId == null) {
672 return null;
673 }
674
675 return networkService.network(networkId);
676 }
Daniel Park157947f2021-04-09 17:50:53 +0900677
Jian Li94b6d162021-04-15 17:09:11 +0900678 /**
679 * Resolve a DNS with the given DNS server and hostname.
680 *
681 * @param hostname hostname to be resolved
682 * @return resolved IP address
683 */
684 public static IpAddress resolveHostname(String hostname) {
685 try {
686 InetAddress addr = Address.getByName(hostname);
687 return IpAddress.valueOf(IpAddress.Version.INET, addr.getAddress());
688 } catch (UnknownHostException e) {
689 log.warn("Failed to resolve IP address of host {}", hostname);
690 }
691 return null;
692 }
693
694 /**
695 * Builds a GARP packet using the given source MAC and source IP address.
696 *
697 * @param srcMac source MAC address
698 * @param srcIp source IP address
699 * @return GARP packet
700 */
Daniel Park157947f2021-04-09 17:50:53 +0900701 public static Ethernet buildGarpPacket(MacAddress srcMac, IpAddress srcIp) {
702 if (srcMac == null || srcIp == null) {
703 return null;
704 }
705
706 Ethernet ethernet = new Ethernet();
707 ethernet.setDestinationMACAddress(MacAddress.BROADCAST);
708 ethernet.setSourceMACAddress(srcMac);
709 ethernet.setEtherType(Ethernet.TYPE_ARP);
710
711 ARP arp = new ARP();
712 arp.setOpCode(ARP.OP_REPLY);
713 arp.setProtocolType(ARP.PROTO_TYPE_IP);
714 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
715
716 arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
717 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
718
719 arp.setSenderHardwareAddress(srcMac.toBytes());
720 arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
721
722 arp.setSenderProtocolAddress(srcIp.toOctets());
723 arp.setTargetProtocolAddress(srcIp.toOctets());
724
725 ethernet.setPayload(arp);
726
727 return ethernet;
728 }
Daniel Park05a94582021-05-12 10:57:02 +0900729
730 /**
731 * Obtains flow group key from the given id.
732 *
733 * @param groupId flow group identifier
734 * @return flow group key
735 */
736 public static GroupKey getGroupKey(int groupId) {
737 return new DefaultGroupKey((Integer.toString(groupId)).getBytes());
738 }
739
740 /**
741 * Obtains load balancer set from the given router.
742 *
743 * @param router kubevirt router
744 * @param lbService kubevirt loadbalancer service
745 * @return loadbalancer set
746 */
747 public static Set<KubevirtLoadBalancer> getLoadBalancerSetForRouter(KubevirtRouter router,
748 KubevirtLoadBalancerService lbService) {
749
750 return lbService.loadBalancers().stream()
751 .filter(lb -> router.internal().contains(lb.networkId()))
752 .collect(Collectors.toSet());
753 }
Jian Li0c656f02021-06-07 13:32:39 +0900754
755 /**
756 * Waits for the given length of time.
757 *
758 * @param timeSecond the amount of time for wait in second unit
759 */
760 public static void waitFor(int timeSecond) {
761 try {
762 Thread.sleep(timeSecond * 1000L);
763 } catch (Exception e) {
764 log.error(e.toString());
765 }
766 }
Jian Li27996912022-10-18 22:43:15 +0900767
768 /**
769 * Returns the kubevirt node from the node.
770 *
771 * @param node a raw node object returned from a k8s client
772 * @return kubevirt node
773 */
774 public static KubevirtNode buildKubevirtNode(Node node) {
775 String hostname = node.getMetadata().getName();
776 IpAddress managementIp = null;
777 IpAddress dataIp = null;
778
779 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
780 if (nodeAddress.getType().equals(INTERNAL_IP)) {
781 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
782 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
783 }
784 }
785
786 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
787 .filter(l -> l.contains(K8S_ROLE))
788 .collect(Collectors.toSet());
789
790 KubevirtNode.Type nodeType = WORKER;
791
792 for (String roleStr : rolesFull) {
793 String role = roleStr.split("/")[1];
794 if (MASTER.name().equalsIgnoreCase(role)) {
795 nodeType = MASTER;
796 break;
797 }
798 }
799
800 // start to parse kubernetes annotation
801 Map<String, String> annots = node.getMetadata().getAnnotations();
802 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
803 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
804 String dataIpStr = annots.get(DATA_IP_KEY);
805 Set<KubevirtPhyInterface> phys = new HashSet<>();
806 String gatewayBridgeName = null;
807 try {
808 if (physnetConfig != null) {
809 JsonArray configJson = JsonArray.readFrom(physnetConfig);
810
811 for (int i = 0; i < configJson.size(); i++) {
812 JsonObject object = configJson.get(i).asObject();
813 String network = object.get(NETWORK_KEY).asString();
814 String intf = object.get(INTERFACE_KEY).asString();
815
816 if (network != null && intf != null) {
817 String physBridgeId;
818 if (object.get(PHYS_BRIDGE_ID) != null) {
819 physBridgeId = object.get(PHYS_BRIDGE_ID).asString();
820 } else {
821 physBridgeId = genDpidFromName(network + intf + hostname);
822 log.trace("host {} physnet dpid for network {} intf {} is null so generate dpid {}",
823 hostname, network, intf, physBridgeId);
824 }
825
826 phys.add(DefaultKubevirtPhyInterface.builder()
827 .network(network)
828 .intf(intf)
829 .physBridge(DeviceId.deviceId(physBridgeId))
830 .build());
831 }
832 }
833 }
834
835 if (dataIpStr != null) {
836 dataIp = IpAddress.valueOf(dataIpStr);
837 }
838
839 if (gatewayConfig != null) {
840 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
841
842 nodeType = GATEWAY;
843 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
844 }
845 } catch (JsonProcessingException e) {
846 log.error("Failed to parse physnet config or gateway config object", e);
847 }
848
849 // if the node is taint with kubevirt.io key configured,
850 // we mark this node as OTHER type, and do not add it into the cluster
851 NodeSpec spec = node.getSpec();
852 if (spec.getTaints() != null) {
853 for (Taint taint : spec.getTaints()) {
854 String effect = taint.getEffect();
855 String key = taint.getKey();
856 String value = taint.getValue();
857
858 if (StringUtils.equals(effect, NO_SCHEDULE_EFFECT) &&
859 StringUtils.equals(key, KUBEVIRT_IO_KEY) &&
860 StringUtils.equals(value, DRAINING_VALUE)) {
861 nodeType = OTHER;
862 }
863 }
864 }
865
866 return DefaultKubevirtNode.builder()
867 .hostname(hostname)
868 .managementIp(managementIp)
869 .dataIp(dataIp)
870 .type(nodeType)
871 .state(KubevirtNodeState.ON_BOARDED)
872 .phyIntfs(phys)
873 .gatewayBridgeName(gatewayBridgeName)
874 .build();
875 }
876
877 /**
878 * Parses kubevirt network resource.
879 *
880 * @param resource kubevirt network resource string
881 * @return kubevirt network object
882 */
883 public static KubevirtNetwork parseKubevirtNetwork(String resource) {
884 JsonObject json = JsonObject.readFrom(resource);
885 String name = parseResourceName(resource);
886 JsonObject annots = json.get("metadata").asObject().get("annotations").asObject();
887 if (annots.get(NETWORK_CONFIG) == null) {
888 // SR-IOV network does not contain network-config field
889 return null;
890 }
891 String networkConfig = annots.get(NETWORK_CONFIG).asString();
892 if (networkConfig != null) {
893 KubevirtNetwork.Builder builder = DefaultKubevirtNetwork.builder();
894
895 JsonObject configJson = JsonObject.readFrom(networkConfig);
896 String type = configJson.get(TYPE).asString().toUpperCase(Locale.ROOT);
897 Integer mtu = configJson.get(MTU).asInt();
898 String gatewayIp = configJson.getString(GATEWAY_IP, "");
899 boolean defaultRoute = configJson.getBoolean(DEFAULT_ROUTE, false);
900
901 if (!type.equalsIgnoreCase(FLAT.name())) {
902 builder.segmentId(configJson.getString(SEGMENT_ID, ""));
903 }
904
905 String cidr = configJson.getString(CIDR, "");
906
907 JsonObject poolJson = configJson.get(IP_POOL).asObject();
908 if (poolJson != null) {
909 String start = poolJson.getString(START, "");
910 String end = poolJson.getString(END, "");
911 builder.ipPool(new KubevirtIpPool(
912 IpAddress.valueOf(start), IpAddress.valueOf(end)));
913 }
914
915 if (configJson.get(HOST_ROUTES) != null) {
916 JsonArray routesJson = configJson.get(HOST_ROUTES).asArray();
917 Set<KubevirtHostRoute> hostRoutes = new HashSet<>();
918 if (routesJson != null) {
919 for (int i = 0; i < routesJson.size(); i++) {
920 JsonObject route = routesJson.get(i).asObject();
921 String destinationStr = route.getString(DESTINATION, "");
922 String nexthopStr = route.getString(NEXTHOP, "");
923
924 if (StringUtils.isNotEmpty(destinationStr) &&
925 StringUtils.isNotEmpty(nexthopStr)) {
926 hostRoutes.add(new KubevirtHostRoute(
927 IpPrefix.valueOf(destinationStr),
928 IpAddress.valueOf(nexthopStr)));
929 }
930 }
931 }
932 builder.hostRoutes(hostRoutes);
933 }
934
935 if (configJson.get(DNSES) != null) {
936 JsonArray dnsesJson = configJson.get(DNSES).asArray();
937 Set<IpAddress> dnses = new HashSet<>();
938 if (dnsesJson != null) {
939 for (int i = 0; i < dnsesJson.size(); i++) {
940 String dns = dnsesJson.get(i).asString();
941 if (StringUtils.isNotEmpty(dns)) {
942 dnses.add(IpAddress.valueOf(dns));
943 }
944 }
945 }
946 builder.dnses(dnses);
947 }
948
949 builder.networkId(name).name(name).type(KubevirtNetwork.Type.valueOf(type))
950 .mtu(mtu).gatewayIp(IpAddress.valueOf(gatewayIp))
951 .defaultRoute(defaultRoute).cidr(cidr);
952
953 return builder.build();
954 }
955 return null;
956 }
Jian Li43244382021-01-09 00:19:02 +0900957}