blob: de71f29d51b1127df633db73ecd124dfd49f190b [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 Li6a53ae62022-10-18 22:43:15 +090018import com.eclipsesource.json.JsonArray;
Andrea Campanella36a57022022-06-09 08:11:25 -070019import com.eclipsesource.json.JsonObject;
Jian Li6a53ae62022-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 Li6a53ae62022-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 Li6a53ae62022-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 Li6a53ae62022-10-18 22:43:15 +090042import org.onosproject.kubevirtnetworking.api.DefaultKubevirtNetwork;
Jian Lica20b712021-01-18 00:19:31 +090043import org.onosproject.kubevirtnetworking.api.DefaultKubevirtPort;
Jian Li6a53ae62022-10-18 22:43:15 +090044import org.onosproject.kubevirtnetworking.api.KubevirtHostRoute;
45import org.onosproject.kubevirtnetworking.api.KubevirtIpPool;
Daniel Park05a94582021-05-12 10:57:02 +090046import org.onosproject.kubevirtnetworking.api.KubevirtLoadBalancer;
47import org.onosproject.kubevirtnetworking.api.KubevirtLoadBalancerService;
Jian Lica20b712021-01-18 00:19:31 +090048import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
Daniel Parkf3136042021-03-10 07:49:11 +090049import org.onosproject.kubevirtnetworking.api.KubevirtNetworkService;
Jian Lica20b712021-01-18 00:19:31 +090050import org.onosproject.kubevirtnetworking.api.KubevirtPort;
Daniel Park2884b232021-03-04 18:58:47 +090051import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
52import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
Jian Li6a53ae62022-10-18 22:43:15 +090053import org.onosproject.kubevirtnode.api.DefaultKubevirtNode;
54import org.onosproject.kubevirtnode.api.DefaultKubevirtPhyInterface;
Jian Li034820d2021-01-15 16:58:48 +090055import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
56import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
Jian Li858ccd72021-02-04 17:25:01 +090057import org.onosproject.kubevirtnode.api.KubevirtNode;
Daniel Park2884b232021-03-04 18:58:47 +090058import org.onosproject.kubevirtnode.api.KubevirtNodeService;
Jian Li6a53ae62022-10-18 22:43:15 +090059import org.onosproject.kubevirtnode.api.KubevirtNodeState;
60import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
Jian Li858ccd72021-02-04 17:25:01 +090061import org.onosproject.net.DeviceId;
62import org.onosproject.net.Port;
63import org.onosproject.net.PortNumber;
64import org.onosproject.net.device.DeviceService;
Daniel Park05a94582021-05-12 10:57:02 +090065import org.onosproject.net.group.DefaultGroupKey;
66import org.onosproject.net.group.GroupKey;
Jian Li43244382021-01-09 00:19:02 +090067import org.slf4j.Logger;
68import org.slf4j.LoggerFactory;
Jian Li94b6d162021-04-15 17:09:11 +090069import org.xbill.DNS.Address;
Jian Li43244382021-01-09 00:19:02 +090070
Jian Lif97a07e2021-01-13 18:05:00 +090071import java.io.IOException;
Jian Li94b6d162021-04-15 17:09:11 +090072import java.net.InetAddress;
73import java.net.UnknownHostException;
Jian Li3ba5c582021-01-14 11:30:36 +090074import java.util.Arrays;
75import java.util.HashSet;
Jian Lif97a07e2021-01-13 18:05:00 +090076import java.util.List;
Jian Li6a53ae62022-10-18 22:43:15 +090077import java.util.Locale;
78import java.util.Map;
Jian Li858ccd72021-02-04 17:25:01 +090079import java.util.Objects;
Jian Li43244382021-01-09 00:19:02 +090080import java.util.Optional;
81import java.util.Set;
Jian Li3ba5c582021-01-14 11:30:36 +090082import java.util.stream.Collectors;
Jian Li43244382021-01-09 00:19:02 +090083
Jian Li858ccd72021-02-04 17:25:01 +090084import static org.onosproject.kubevirtnetworking.api.Constants.TUNNEL_TO_TENANT_PREFIX;
Jian Li6a53ae62022-10-18 22:43:15 +090085import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.FLAT;
86import static org.onosproject.kubevirtnode.api.Constants.SONA_PROJECT_DOMAIN;
Daniel Park2884b232021-03-04 18:58:47 +090087import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
Jian Li6a53ae62022-10-18 22:43:15 +090088import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.MASTER;
89import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.OTHER;
90import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
Jian Li858ccd72021-02-04 17:25:01 +090091import static org.onosproject.net.AnnotationKeys.PORT_NAME;
92
Jian Li43244382021-01-09 00:19:02 +090093/**
94 * An utility that used in KubeVirt networking app.
95 */
96public final class KubevirtNetworkingUtil {
97
98 private static final Logger log = LoggerFactory.getLogger(KubevirtNetworkingUtil.class);
99
100 private static final int PORT_NAME_MAX_LENGTH = 15;
Jian Li034820d2021-01-15 16:58:48 +0900101 private static final String COLON_SLASH = "://";
102 private static final String COLON = ":";
Jian Li556709c2021-02-03 17:54:28 +0900103 private static final String OF_PREFIX = "of:";
Jian Li43244382021-01-09 00:19:02 +0900104
Jian Lica20b712021-01-18 00:19:31 +0900105 private static final String NETWORK_STATUS_KEY = "k8s.v1.cni.cncf.io/network-status";
106 private static final String NAME = "name";
107 private static final String NETWORK_PREFIX = "default/";
108 private static final String MAC = "mac";
109 private static final String IPS = "ips";
Daniel Parkbabde9c2021-03-09 13:37:42 +0900110 private static final String BR_INT = "br-int";
Jian Li9557e902021-06-08 10:12:52 +0900111 private static final String METADATA = "metadata";
Jian Li0c656f02021-06-07 13:32:39 +0900112 private static final String STATUS = "status";
113 private static final String INTERFACES = "interfaces";
Jian Li0c656f02021-06-07 13:32:39 +0900114 private static final String NODE_NAME = "nodeName";
Jian Lica20b712021-01-18 00:19:31 +0900115
Jian Li6a53ae62022-10-18 22:43:15 +0900116 private static final String NETWORK_CONFIG = "network-config";
117 private static final String TYPE = "type";
118 private static final String MTU = "mtu";
119 private static final String SEGMENT_ID = "segmentId";
120 private static final String GATEWAY_IP = "gatewayIp";
121 private static final String DEFAULT_ROUTE = "defaultRoute";
122 private static final String CIDR = "cidr";
123 private static final String HOST_ROUTES = "hostRoutes";
124 private static final String DESTINATION = "destination";
125 private static final String NEXTHOP = "nexthop";
126 private static final String IP_POOL = "ipPool";
127 private static final String START = "start";
128 private static final String END = "end";
129 private static final String DNSES = "dnses";
130
131 private static final String INTERNAL_IP = "InternalIP";
132 private static final String K8S_ROLE = "node-role.kubernetes.io";
133 private static final String PHYSNET_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/physnet-config";
134 private static final String DATA_IP_KEY = SONA_PROJECT_DOMAIN + "/data-ip";
135 private static final String GATEWAY_CONFIG_KEY = SONA_PROJECT_DOMAIN + "/gateway-config";
136 private static final String GATEWAY_BRIDGE_NAME = "gatewayBridgeName";
137 private static final String NETWORK_KEY = "network";
138 private static final String INTERFACE_KEY = "interface";
139 private static final String PHYS_BRIDGE_ID = "physBridgeId";
140
141 private static final String NO_SCHEDULE_EFFECT = "NoSchedule";
142 private static final String KUBEVIRT_IO_KEY = "kubevirt.io/drain";
143 private static final String DRAINING_VALUE = "draining";
144
Jian Li43244382021-01-09 00:19:02 +0900145 /**
146 * Prevents object installation from external.
147 */
148 private KubevirtNetworkingUtil() {
149 }
150
151 /**
152 * Obtains the boolean property value with specified property key name.
153 *
Daniel Park2884b232021-03-04 18:58:47 +0900154 * @param properties a collection of properties
155 * @param name key name
Jian Li43244382021-01-09 00:19:02 +0900156 * @return mapping value
157 */
158 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties,
159 String name) {
160 Optional<ConfigProperty> property =
161 properties.stream().filter(p -> p.name().equals(name)).findFirst();
162
163 return property.map(ConfigProperty::asBoolean).orElse(false);
164 }
165
166 /**
167 * Re-structures the OVS port name.
168 * The length of OVS port name should be not large than 15.
169 *
Daniel Park2884b232021-03-04 18:58:47 +0900170 * @param portName original port name
Jian Li43244382021-01-09 00:19:02 +0900171 * @return re-structured OVS port name
172 */
173 public static String structurePortName(String portName) {
174
175 // The size of OVS port name should not be larger than 15
176 if (portName.length() > PORT_NAME_MAX_LENGTH) {
177 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
178 }
179
180 return portName;
181 }
Jian Lif97a07e2021-01-13 18:05:00 +0900182
183 /**
184 * Generates string format based on the given string length list.
185 *
186 * @param stringLengths a list of string lengths
187 * @return string format (e.g., %-28s%-15s%-24s%-20s%-15s)
188 */
189 public static String genFormatString(List<Integer> stringLengths) {
190 StringBuilder fsb = new StringBuilder();
191 stringLengths.forEach(length -> {
192 fsb.append("%-");
193 fsb.append(length);
194 fsb.append("s");
195 });
196 return fsb.toString();
197 }
198
199 /**
Jian Li556709c2021-02-03 17:54:28 +0900200 * Auto generates DPID from the given name.
201 *
202 * @param name name
203 * @return auto generated DPID
204 */
205 public static String genDpidFromName(String name) {
206 if (name != null) {
207 String hexString = Integer.toHexString(name.hashCode());
208 return OF_PREFIX + Strings.padStart(hexString, 16, '0');
209 }
210
211 return null;
212 }
213
214 /**
Jian Lif97a07e2021-01-13 18:05:00 +0900215 * Prints out the JSON string in pretty format.
216 *
Daniel Park2884b232021-03-04 18:58:47 +0900217 * @param mapper Object mapper
218 * @param jsonString JSON string
Jian Lif97a07e2021-01-13 18:05:00 +0900219 * @return pretty formatted JSON string
220 */
221 public static String prettyJson(ObjectMapper mapper, String jsonString) {
222 try {
223 Object jsonObject = mapper.readValue(jsonString, Object.class);
224 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
225 } catch (IOException e) {
226 log.debug("Json string parsing exception caused by {}", e);
227 }
228 return null;
229 }
Jian Li3ba5c582021-01-14 11:30:36 +0900230
231 /**
232 * Obtains valid IP addresses of the given subnet.
233 *
234 * @param cidr CIDR
235 * @return set of IP addresses
236 */
237 public static Set<IpAddress> getSubnetIps(String cidr) {
238 SubnetUtils utils = new SubnetUtils(cidr);
239 utils.setInclusiveHostCount(false);
240 SubnetUtils.SubnetInfo info = utils.getInfo();
241 Set<String> allAddresses =
242 new HashSet<>(Arrays.asList(info.getAllAddresses()));
243
244 if (allAddresses.size() > 2) {
245 allAddresses.remove(info.getLowAddress());
246 allAddresses.remove(info.getHighAddress());
247 }
248
249 return allAddresses.stream()
250 .map(IpAddress::valueOf).collect(Collectors.toSet());
251 }
252
253 /**
254 * Calculate the broadcast address from given IP address and subnet prefix length.
255 *
Daniel Park2884b232021-03-04 18:58:47 +0900256 * @param ipAddr IP address
257 * @param prefixLength subnet prefix length
Jian Li3ba5c582021-01-14 11:30:36 +0900258 * @return broadcast address
259 */
260 public static String getBroadcastAddr(String ipAddr, int prefixLength) {
261 String subnet = ipAddr + "/" + prefixLength;
262 SubnetUtils utils = new SubnetUtils(subnet);
263 return utils.getInfo().getBroadcastAddress();
264 }
Daniel Park2884b232021-03-04 18:58:47 +0900265
Jian Li034820d2021-01-15 16:58:48 +0900266 /**
267 * Generates endpoint URL by referring to scheme, ipAddress and port.
268 *
Daniel Park2884b232021-03-04 18:58:47 +0900269 * @param scheme scheme
270 * @param ipAddress IP address
271 * @param port port number
Jian Li034820d2021-01-15 16:58:48 +0900272 * @return generated endpoint URL
273 */
274 public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
275 StringBuilder endpoint = new StringBuilder();
276 String protocol = org.apache.commons.lang3.StringUtils.lowerCase(scheme.name());
277
278 endpoint.append(protocol);
279 endpoint.append(COLON_SLASH);
280 endpoint.append(ipAddress.toString());
281 endpoint.append(COLON);
282 endpoint.append(port);
283
284 return endpoint.toString();
285 }
286
287 /**
288 * Generates endpoint URL by referring to scheme, ipAddress and port.
289 *
Daniel Park2884b232021-03-04 18:58:47 +0900290 * @param apiConfig kubernetes API config
Jian Li034820d2021-01-15 16:58:48 +0900291 * @return generated endpoint URL
292 */
293 public static String endpoint(KubevirtApiConfig apiConfig) {
294 return endpoint(apiConfig.scheme(), apiConfig.ipAddress(), apiConfig.port());
295 }
296
297 /**
298 * Obtains workable kubernetes client.
299 *
300 * @param config kubernetes API config
301 * @return kubernetes client
302 */
303 public static KubernetesClient k8sClient(KubevirtApiConfig config) {
304 if (config == null) {
305 log.warn("Kubernetes API server config is empty.");
306 return null;
307 }
308
309 String endpoint = endpoint(config);
310
311 ConfigBuilder configBuilder = new ConfigBuilder().withMasterUrl(endpoint);
312
313 if (config.scheme() == KubevirtApiConfig.Scheme.HTTPS) {
314 configBuilder.withTrustCerts(true)
Jian Li034820d2021-01-15 16:58:48 +0900315 .withCaCertData(config.caCertData())
316 .withClientCertData(config.clientCertData())
317 .withClientKeyData(config.clientKeyData());
318 }
319
320 return new DefaultKubernetesClient(configBuilder.build());
321 }
322
323 /**
324 * Obtains workable kubernetes client.
325 *
326 * @param service kubernetes API service
327 * @return kubernetes client
328 */
329 public static KubernetesClient k8sClient(KubevirtApiConfigService service) {
330 KubevirtApiConfig config = service.apiConfig();
331 if (config == null) {
332 log.error("Failed to find valid kubernetes API configuration.");
333 return null;
334 }
335
336 KubernetesClient client = k8sClient(config);
337
338 if (client == null) {
339 log.error("Failed to connect to kubernetes API server.");
340 return null;
341 }
342
343 return client;
344 }
Jian Lica20b712021-01-18 00:19:31 +0900345
346 /**
Jian Li556709c2021-02-03 17:54:28 +0900347 * Obtains the hex string of the given segment ID with fixed padding.
348 *
349 * @param segIdStr segment identifier string
350 * @return hex string with padding
351 */
352 public static String segmentIdHex(String segIdStr) {
353 int segId = Integer.parseInt(segIdStr);
354 return String.format("%06x", segId).toLowerCase();
355 }
356
357 /**
Jian Li858ccd72021-02-04 17:25:01 +0900358 * Obtains the tunnel port number with the given network and node.
359 *
360 * @param network kubevirt network
Daniel Park2884b232021-03-04 18:58:47 +0900361 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900362 * @return tunnel port number
363 */
364 public static PortNumber tunnelPort(KubevirtNetwork network, KubevirtNode node) {
365 switch (network.type()) {
366 case VXLAN:
367 return node.vxlanPort();
368 case GRE:
369 return node.grePort();
370 case GENEVE:
371 return node.genevePort();
Jian Li78e82f92022-03-23 13:07:19 +0900372 case STT:
373 return node.sttPort();
Jian Li858ccd72021-02-04 17:25:01 +0900374 default:
375 break;
376 }
377 return null;
378 }
379
380 /**
Jian Li0c656f02021-06-07 13:32:39 +0900381 * Obtains the kubevirt port from kubevirt VMI.
Jian Lica20b712021-01-18 00:19:31 +0900382 *
Daniel Parkf3136042021-03-10 07:49:11 +0900383 * @param nodeService kubevirt node service
Jian Lica20b712021-01-18 00:19:31 +0900384 * @param networks set of existing kubevirt networks
Jian Li0c656f02021-06-07 13:32:39 +0900385 * @param resource VMI definition
386 * @return kubevirt ports attached to the VMI
Jian Lica20b712021-01-18 00:19:31 +0900387 */
Jian Lib6dc08f2021-03-24 15:24:18 +0900388 public static Set<KubevirtPort> getPorts(KubevirtNodeService nodeService,
Jian Li0c656f02021-06-07 13:32:39 +0900389 Set<KubevirtNetwork> networks,
390 String resource) {
Jian Lica20b712021-01-18 00:19:31 +0900391 try {
Jian Li0c656f02021-06-07 13:32:39 +0900392 ObjectMapper mapper = new ObjectMapper();
393 JsonNode json = mapper.readTree(resource);
394 JsonNode statusJson = json.get(STATUS);
395 ArrayNode interfacesJson = (ArrayNode) statusJson.get(INTERFACES);
Jian Li9557e902021-06-08 10:12:52 +0900396 String vmName = parseResourceName(resource);
Jian Lib6dc08f2021-03-24 15:24:18 +0900397
Daniel Parkf3136042021-03-10 07:49:11 +0900398 KubevirtPort.Builder builder = DefaultKubevirtPort.builder();
Jian Li0c656f02021-06-07 13:32:39 +0900399 String nodeName = parseVmiNodeName(resource);
400 if (nodeName != null && nodeService.node(nodeName) != null) {
401 builder.deviceId(nodeService.node(nodeName).intgBridge());
Daniel Parkf3136042021-03-10 07:49:11 +0900402 }
Jian Lica20b712021-01-18 00:19:31 +0900403
Jian Li0c656f02021-06-07 13:32:39 +0900404 if (interfacesJson == null) {
405 return ImmutableSet.of();
406 }
Jian Lica20b712021-01-18 00:19:31 +0900407
Jian Li0c656f02021-06-07 13:32:39 +0900408 Set<KubevirtPort> ports = new HashSet<>();
409 for (JsonNode interfaceJson : interfacesJson) {
Jian Li56f241b2021-06-30 20:59:43 +0900410 JsonNode jsonName = interfaceJson.get(NAME);
411
412 // in some cases, name attribute may not be available from the
413 // interface, we skip inspect this interface
414 if (jsonName == null) {
415 continue;
416 }
417
418 String name = jsonName.asText();
Jian Lica20b712021-01-18 00:19:31 +0900419 KubevirtNetwork network = networks.stream()
Jian Li0c656f02021-06-07 13:32:39 +0900420 .filter(n -> (NETWORK_PREFIX + n.name()).equals(name) ||
421 (n.name() + "-net").equals(name))
Jian Lica20b712021-01-18 00:19:31 +0900422 .findAny().orElse(null);
423 if (network != null) {
Jian Li0c656f02021-06-07 13:32:39 +0900424 // FIXME: we do not update IP address, as learning IP address
425 // requires much more time due to the lag from VM agent
426 String mac = interfaceJson.get(MAC).asText();
Jian Li9557e902021-06-08 10:12:52 +0900427 builder.vmName(vmName)
428 .macAddress(MacAddress.valueOf(mac))
Jian Lica20b712021-01-18 00:19:31 +0900429 .networkId(network.networkId());
Jian Lid4296d02021-03-12 18:03:58 +0900430 ports.add(builder.build());
Jian Lica20b712021-01-18 00:19:31 +0900431 }
432 }
Jian Lid4296d02021-03-12 18:03:58 +0900433 return ports;
Jian Li0c656f02021-06-07 13:32:39 +0900434 } catch (IOException e) {
435 log.error("Failed to parse port info from VMI object", e);
436 }
437 return ImmutableSet.of();
438 }
Jian Lid4296d02021-03-12 18:03:58 +0900439
Jian Li0c656f02021-06-07 13:32:39 +0900440 public static String parseVmiNodeName(String resource) {
441 String nodeName = null;
442 try {
443 ObjectMapper mapper = new ObjectMapper();
444 JsonNode json = mapper.readTree(resource);
445 JsonNode statusJson = json.get(STATUS);
446 JsonNode nodeNameJson = statusJson.get(NODE_NAME);
447 nodeName = nodeNameJson != null ? nodeNameJson.asText() : null;
448 } catch (IOException e) {
449 log.error("Failed to parse kubevirt VMI nodename");
Jian Lica20b712021-01-18 00:19:31 +0900450 }
451
Jian Li0c656f02021-06-07 13:32:39 +0900452 return nodeName;
Jian Lica20b712021-01-18 00:19:31 +0900453 }
Jian Li858ccd72021-02-04 17:25:01 +0900454
455 /**
456 * Obtains the tunnel bridge to tenant bridge patch port number.
457 *
Jian Li34fff802021-07-01 10:04:04 +0900458 * @param deviceService device service
Daniel Park2884b232021-03-04 18:58:47 +0900459 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900460 * @param network kubevirt network
461 * @return patch port number
462 */
Jian Li34fff802021-07-01 10:04:04 +0900463 public static PortNumber tunnelToTenantPort(DeviceService deviceService,
464 KubevirtNode node, KubevirtNetwork network) {
Jian Li858ccd72021-02-04 17:25:01 +0900465 if (network.segmentId() == null) {
466 return null;
467 }
468
469 if (node.tunBridge() == null) {
470 return null;
471 }
472
473 String tunToTenantPortName = TUNNEL_TO_TENANT_PREFIX + segmentIdHex(network.segmentId());
Jian Li34fff802021-07-01 10:04:04 +0900474 return portNumber(deviceService, node.tunBridge(), tunToTenantPortName);
Jian Li858ccd72021-02-04 17:25:01 +0900475 }
476
477 /**
478 * Obtains the tunnel port number of the given node.
479 *
Daniel Park2884b232021-03-04 18:58:47 +0900480 * @param node kubevirt node
Jian Li858ccd72021-02-04 17:25:01 +0900481 * @param network kubevirt network
482 * @return tunnel port number
483 */
484 public static PortNumber tunnelPort(KubevirtNode node, KubevirtNetwork network) {
485 if (network.segmentId() == null) {
486 return null;
487 }
488
489 if (node.tunBridge() == null) {
490 return null;
491 }
492
493 switch (network.type()) {
494 case VXLAN:
495 return node.vxlanPort();
496 case GRE:
497 return node.grePort();
498 case GENEVE:
499 return node.genevePort();
Jian Li78e82f92022-03-23 13:07:19 +0900500 case STT:
501 return node.sttPort();
Jian Li858ccd72021-02-04 17:25:01 +0900502 case FLAT:
Jian Li2ce718e2021-02-17 20:42:15 +0900503 case VLAN:
Jian Li858ccd72021-02-04 17:25:01 +0900504 default:
505 // do nothing
506 return null;
507 }
508 }
509
Jian Li810f58c2021-02-27 01:10:50 +0900510 public static String parseResourceName(String resource) {
Andrea Campanella36a57022022-06-09 08:11:25 -0700511 JsonObject json = JsonObject.readFrom(resource);
512 JsonObject metadata = json.get("metadata").asObject();
513 return metadata != null ? metadata.get("name").asString() : "";
Jian Li810f58c2021-02-27 01:10:50 +0900514 }
515
Jian Li34fff802021-07-01 10:04:04 +0900516 public static PortNumber portNumber(DeviceService deviceService, DeviceId deviceId, String portName) {
Jian Li858ccd72021-02-04 17:25:01 +0900517 Port port = deviceService.getPorts(deviceId).stream()
518 .filter(p -> p.isEnabled() &&
519 Objects.equals(p.annotations().value(PORT_NAME), portName))
520 .findAny().orElse(null);
521 return port != null ? port.number() : null;
522 }
523
Daniel Park2884b232021-03-04 18:58:47 +0900524 /**
525 * Returns the gateway node for the specified kubevirt router.
526 * Among gateways, only one gateway would act as a gateway per perter.
527 * Currently gateway node is selected based on modulo operation with router hashcode.
528 *
529 * @param nodeService kubevirt node service
530 * @param router kubevirt router
531 * @return elected gateway node
532 */
533 public static KubevirtNode gatewayNodeForSpecifiedRouter(KubevirtNodeService nodeService,
534 KubevirtRouter router) {
535 //TODO: enhance election logic for a better load balancing
536
537 int numOfGateways = nodeService.completeNodes(GATEWAY).size();
538 if (numOfGateways == 0) {
539 return null;
540 }
541 return (KubevirtNode) nodeService.completeNodes(GATEWAY).toArray()[router.hashCode() % numOfGateways];
542 }
543
544 /**
Daniel Parkf3136042021-03-10 07:49:11 +0900545 * Returns the mac address of the router.
546 *
547 * @param router kubevirt router
548 * @return macc address of the router
549 */
550 public static MacAddress getRouterMacAddress(KubevirtRouter router) {
551 if (router.mac() == null) {
552 log.warn("Failed to get mac address of router {}", router.name());
553 }
554
555 return router.mac();
Daniel Park2884b232021-03-04 18:58:47 +0900556 }
557
558 /**
559 * Returns the snat ip address with specified router.
560 *
561 * @param routerService router service
562 * @param internalNetworkId internal network id which is associated with the router
563 * @return snat ip address if exist, null otherwise
564 */
565 public static IpAddress getRouterSnatIpAddress(KubevirtRouterService routerService,
566 String internalNetworkId) {
567 KubevirtRouter router = routerService.routers().stream()
568 .filter(r -> r.internal().contains(internalNetworkId))
569 .findAny().orElse(null);
570
571 if (router == null) {
572 return null;
573 }
574
575 String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
576
577 if (routerSnatIp == null) {
578 return null;
579 }
580
581 return Ip4Address.valueOf(routerSnatIp);
582 }
Daniel Parkbabde9c2021-03-09 13:37:42 +0900583
584 /**
585 * Returns the kubevirt router with specified kubevirt port.
586 *
587 * @param routerService kubevirt router service
588 * @param kubevirtPort kubevirt port
589 * @return kubevirt router
590 */
591 public static KubevirtRouter getRouterForKubevirtPort(KubevirtRouterService routerService,
592 KubevirtPort kubevirtPort) {
593 if (kubevirtPort.ipAddress() != null) {
594 return routerService.routers().stream()
595 .filter(r -> r.internal().contains(kubevirtPort.networkId()))
596 .findAny().orElse(null);
597 }
598 return null;
599 }
600
601 /**
602 * Returns the kubevirt router with specified kubevirt network.
603 *
604 * @param routerService kubevirt router service
605 * @param kubevirtNetwork kubevirt network
606 * @return kubevirt router
607 */
608 public static KubevirtRouter getRouterForKubevirtNetwork(KubevirtRouterService routerService,
609 KubevirtNetwork kubevirtNetwork) {
610 return routerService.routers().stream()
611 .filter(router -> router.internal().contains(kubevirtNetwork.networkId()))
612 .findAny().orElse(null);
613 }
Daniel Parkf3136042021-03-10 07:49:11 +0900614
615 /**
616 * Returns the external patch port number with specified gateway.
617 *
618 * @param deviceService device service
Jian Li9793ec42021-03-19 15:03:32 +0900619 * @param gatewayNode gateway node
Daniel Parkf3136042021-03-10 07:49:11 +0900620 * @return external patch port number
621 */
622 public static PortNumber externalPatchPortNum(DeviceService deviceService, KubevirtNode gatewayNode) {
Jian Li63f191f2021-03-25 17:14:40 +0900623 String gatewayBridgeName = gatewayNode.gatewayBridgeName();
624 if (gatewayBridgeName == null) {
Jian Li9793ec42021-03-19 15:03:32 +0900625 log.warn("No external interface is attached to gateway {}", gatewayNode.hostname());
626 return null;
627 }
628
Jian Li63f191f2021-03-25 17:14:40 +0900629 String patchPortName = "int-to-" + gatewayBridgeName;
Daniel Parkf3136042021-03-10 07:49:11 +0900630 Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
631 .filter(p -> p.isEnabled() &&
Jian Li9793ec42021-03-19 15:03:32 +0900632 Objects.equals(p.annotations().value(PORT_NAME), patchPortName))
Daniel Parkf3136042021-03-10 07:49:11 +0900633 .findAny().orElse(null);
634
635 return port != null ? port.number() : null;
636 }
637
Daniel Park157947f2021-04-09 17:50:53 +0900638 /**
639 * Returns the kubevirt external network with specified router.
640 *
641 * @param networkService kubevirt network service
642 * @param router kubevirt router
643 * @return external network
644 */
Daniel Parkf3136042021-03-10 07:49:11 +0900645 public static KubevirtNetwork getExternalNetworkByRouter(KubevirtNetworkService networkService,
646 KubevirtRouter router) {
647 String networkId = router.external().values().stream().findAny().orElse(null);
648 if (networkId == null) {
649 return null;
650 }
651
652 return networkService.network(networkId);
653 }
Daniel Park157947f2021-04-09 17:50:53 +0900654
Jian Li94b6d162021-04-15 17:09:11 +0900655 /**
656 * Resolve a DNS with the given DNS server and hostname.
657 *
658 * @param hostname hostname to be resolved
659 * @return resolved IP address
660 */
661 public static IpAddress resolveHostname(String hostname) {
662 try {
663 InetAddress addr = Address.getByName(hostname);
664 return IpAddress.valueOf(IpAddress.Version.INET, addr.getAddress());
665 } catch (UnknownHostException e) {
666 log.warn("Failed to resolve IP address of host {}", hostname);
667 }
668 return null;
669 }
670
671 /**
672 * Builds a GARP packet using the given source MAC and source IP address.
673 *
674 * @param srcMac source MAC address
675 * @param srcIp source IP address
676 * @return GARP packet
677 */
Daniel Park157947f2021-04-09 17:50:53 +0900678 public static Ethernet buildGarpPacket(MacAddress srcMac, IpAddress srcIp) {
679 if (srcMac == null || srcIp == null) {
680 return null;
681 }
682
683 Ethernet ethernet = new Ethernet();
684 ethernet.setDestinationMACAddress(MacAddress.BROADCAST);
685 ethernet.setSourceMACAddress(srcMac);
686 ethernet.setEtherType(Ethernet.TYPE_ARP);
687
688 ARP arp = new ARP();
689 arp.setOpCode(ARP.OP_REPLY);
690 arp.setProtocolType(ARP.PROTO_TYPE_IP);
691 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
692
693 arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
694 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
695
696 arp.setSenderHardwareAddress(srcMac.toBytes());
697 arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
698
699 arp.setSenderProtocolAddress(srcIp.toOctets());
700 arp.setTargetProtocolAddress(srcIp.toOctets());
701
702 ethernet.setPayload(arp);
703
704 return ethernet;
705 }
Daniel Park05a94582021-05-12 10:57:02 +0900706
707 /**
708 * Obtains flow group key from the given id.
709 *
710 * @param groupId flow group identifier
711 * @return flow group key
712 */
713 public static GroupKey getGroupKey(int groupId) {
714 return new DefaultGroupKey((Integer.toString(groupId)).getBytes());
715 }
716
717 /**
718 * Obtains load balancer set from the given router.
719 *
720 * @param router kubevirt router
721 * @param lbService kubevirt loadbalancer service
722 * @return loadbalancer set
723 */
724 public static Set<KubevirtLoadBalancer> getLoadBalancerSetForRouter(KubevirtRouter router,
725 KubevirtLoadBalancerService lbService) {
726
727 return lbService.loadBalancers().stream()
728 .filter(lb -> router.internal().contains(lb.networkId()))
729 .collect(Collectors.toSet());
730 }
Jian Li0c656f02021-06-07 13:32:39 +0900731
732 /**
733 * Waits for the given length of time.
734 *
735 * @param timeSecond the amount of time for wait in second unit
736 */
737 public static void waitFor(int timeSecond) {
738 try {
739 Thread.sleep(timeSecond * 1000L);
740 } catch (Exception e) {
741 log.error(e.toString());
742 }
743 }
Jian Li6a53ae62022-10-18 22:43:15 +0900744
745 /**
746 * Returns the kubevirt node from the node.
747 *
748 * @param node a raw node object returned from a k8s client
749 * @return kubevirt node
750 */
751 public static KubevirtNode buildKubevirtNode(Node node) {
752 String hostname = node.getMetadata().getName();
753 IpAddress managementIp = null;
754 IpAddress dataIp = null;
755
756 for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
757 if (nodeAddress.getType().equals(INTERNAL_IP)) {
758 managementIp = IpAddress.valueOf(nodeAddress.getAddress());
759 dataIp = IpAddress.valueOf(nodeAddress.getAddress());
760 }
761 }
762
763 Set<String> rolesFull = node.getMetadata().getLabels().keySet().stream()
764 .filter(l -> l.contains(K8S_ROLE))
765 .collect(Collectors.toSet());
766
767 KubevirtNode.Type nodeType = WORKER;
768
769 for (String roleStr : rolesFull) {
770 String role = roleStr.split("/")[1];
771 if (MASTER.name().equalsIgnoreCase(role)) {
772 nodeType = MASTER;
773 break;
774 }
775 }
776
777 // start to parse kubernetes annotation
778 Map<String, String> annots = node.getMetadata().getAnnotations();
779 String physnetConfig = annots.get(PHYSNET_CONFIG_KEY);
780 String gatewayConfig = annots.get(GATEWAY_CONFIG_KEY);
781 String dataIpStr = annots.get(DATA_IP_KEY);
782 Set<KubevirtPhyInterface> phys = new HashSet<>();
783 String gatewayBridgeName = null;
784 try {
785 if (physnetConfig != null) {
786 JsonArray configJson = JsonArray.readFrom(physnetConfig);
787
788 for (int i = 0; i < configJson.size(); i++) {
789 JsonObject object = configJson.get(i).asObject();
790 String network = object.get(NETWORK_KEY).asString();
791 String intf = object.get(INTERFACE_KEY).asString();
792
793 if (network != null && intf != null) {
794 String physBridgeId;
795 if (object.get(PHYS_BRIDGE_ID) != null) {
796 physBridgeId = object.get(PHYS_BRIDGE_ID).asString();
797 } else {
798 physBridgeId = genDpidFromName(network + intf + hostname);
799 log.trace("host {} physnet dpid for network {} intf {} is null so generate dpid {}",
800 hostname, network, intf, physBridgeId);
801 }
802
803 phys.add(DefaultKubevirtPhyInterface.builder()
804 .network(network)
805 .intf(intf)
806 .physBridge(DeviceId.deviceId(physBridgeId))
807 .build());
808 }
809 }
810 }
811
812 if (dataIpStr != null) {
813 dataIp = IpAddress.valueOf(dataIpStr);
814 }
815
816 if (gatewayConfig != null) {
817 JsonNode jsonNode = new ObjectMapper().readTree(gatewayConfig);
818
819 nodeType = GATEWAY;
820 gatewayBridgeName = jsonNode.get(GATEWAY_BRIDGE_NAME).asText();
821 }
822 } catch (JsonProcessingException e) {
823 log.error("Failed to parse physnet config or gateway config object", e);
824 }
825
826 // if the node is taint with kubevirt.io key configured,
827 // we mark this node as OTHER type, and do not add it into the cluster
828 NodeSpec spec = node.getSpec();
829 if (spec.getTaints() != null) {
830 for (Taint taint : spec.getTaints()) {
831 String effect = taint.getEffect();
832 String key = taint.getKey();
833 String value = taint.getValue();
834
835 if (StringUtils.equals(effect, NO_SCHEDULE_EFFECT) &&
836 StringUtils.equals(key, KUBEVIRT_IO_KEY) &&
837 StringUtils.equals(value, DRAINING_VALUE)) {
838 nodeType = OTHER;
839 }
840 }
841 }
842
843 return DefaultKubevirtNode.builder()
844 .hostname(hostname)
845 .managementIp(managementIp)
846 .dataIp(dataIp)
847 .type(nodeType)
848 .state(KubevirtNodeState.ON_BOARDED)
849 .phyIntfs(phys)
850 .gatewayBridgeName(gatewayBridgeName)
851 .build();
852 }
853
854 /**
855 * Parses kubevirt network resource.
856 *
857 * @param resource kubevirt network resource string
858 * @return kubevirt network object
859 */
860 public static KubevirtNetwork parseKubevirtNetwork(String resource) {
861 JsonObject json = JsonObject.readFrom(resource);
862 String name = parseResourceName(resource);
863 JsonObject annots = json.get("metadata").asObject().get("annotations").asObject();
864 if (annots.get(NETWORK_CONFIG) == null) {
865 // SR-IOV network does not contain network-config field
866 return null;
867 }
868 String networkConfig = annots.get(NETWORK_CONFIG).asString();
869 if (networkConfig != null) {
870 KubevirtNetwork.Builder builder = DefaultKubevirtNetwork.builder();
871
872 JsonObject configJson = JsonObject.readFrom(networkConfig);
873 String type = configJson.get(TYPE).asString().toUpperCase(Locale.ROOT);
874 Integer mtu = configJson.get(MTU).asInt();
875 String gatewayIp = configJson.getString(GATEWAY_IP, "");
876 boolean defaultRoute = configJson.getBoolean(DEFAULT_ROUTE, false);
877
878 if (!type.equalsIgnoreCase(FLAT.name())) {
879 builder.segmentId(configJson.getString(SEGMENT_ID, ""));
880 }
881
882 String cidr = configJson.getString(CIDR, "");
883
884 JsonObject poolJson = configJson.get(IP_POOL).asObject();
885 if (poolJson != null) {
886 String start = poolJson.getString(START, "");
887 String end = poolJson.getString(END, "");
888 builder.ipPool(new KubevirtIpPool(
889 IpAddress.valueOf(start), IpAddress.valueOf(end)));
890 }
891
892 if (configJson.get(HOST_ROUTES) != null) {
893 JsonArray routesJson = configJson.get(HOST_ROUTES).asArray();
894 Set<KubevirtHostRoute> hostRoutes = new HashSet<>();
895 if (routesJson != null) {
896 for (int i = 0; i < routesJson.size(); i++) {
897 JsonObject route = routesJson.get(i).asObject();
898 String destinationStr = route.getString(DESTINATION, "");
899 String nexthopStr = route.getString(NEXTHOP, "");
900
901 if (StringUtils.isNotEmpty(destinationStr) &&
902 StringUtils.isNotEmpty(nexthopStr)) {
903 hostRoutes.add(new KubevirtHostRoute(
904 IpPrefix.valueOf(destinationStr),
905 IpAddress.valueOf(nexthopStr)));
906 }
907 }
908 }
909 builder.hostRoutes(hostRoutes);
910 }
911
912 if (configJson.get(DNSES) != null) {
913 JsonArray dnsesJson = configJson.get(DNSES).asArray();
914 Set<IpAddress> dnses = new HashSet<>();
915 if (dnsesJson != null) {
916 for (int i = 0; i < dnsesJson.size(); i++) {
917 String dns = dnsesJson.get(i).asString();
918 if (StringUtils.isNotEmpty(dns)) {
919 dnses.add(IpAddress.valueOf(dns));
920 }
921 }
922 }
923 builder.dnses(dnses);
924 }
925
926 builder.networkId(name).name(name).type(KubevirtNetwork.Type.valueOf(type))
927 .mtu(mtu).gatewayIp(IpAddress.valueOf(gatewayIp))
928 .defaultRoute(defaultRoute).cidr(cidr);
929
930 return builder.build();
931 }
932 return null;
933 }
Jian Li43244382021-01-09 00:19:02 +0900934}