diff --git a/dev/docker/tools/docker-connector.conf b/dev/docker/tools/docker-connector.conf index 378e0699478..d8e859b95cf 100644 --- a/dev/docker/tools/docker-connector.conf +++ b/dev/docker/tools/docker-connector.conf @@ -5,4 +5,4 @@ # Fixed docker container network subnet in Gravitino integration testing module # Generate command: `docker network ls --filter driver=bridge --format "{{.ID}}" | xargs docker network inspect --format "route {{range .IPAM.Config}}{{.Subnet}}{{end}}" > ${bin}/docker-connector.conf` -route 10.0.0.0/22 +route 10.20.30.0/28 diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index e4284fad01f..94e99b49056 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -271,6 +271,15 @@ tasks.test { environment("PROJECT_VERSION", version) environment("TRINO_CONF_DIR", buildDir.path + "/trino-conf") + val dockerRunning = project.extra["dockerRunning"] as? Boolean ?: false + val macDockerConnector = project.extra["macDockerConnector"] as? Boolean ?: false + if (OperatingSystem.current().isMacOsX() && + dockerRunning && + macDockerConnector + ) { + environment("NEED_CREATE_DOCKER_NETWORK", "true") + } + // Gravitino CI Docker image environment("GRAVITINO_CI_HIVE_DOCKER_IMAGE", "datastrato/gravitino-ci-hive:0.1.6") environment("GRAVITINO_CI_TRINO_DOCKER_IMAGE", "datastrato/gravitino-ci-trino:0.1.2") diff --git a/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/BaseContainer.java b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/BaseContainer.java index d70d3721f61..331efae498e 100644 --- a/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/BaseContainer.java +++ b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/BaseContainer.java @@ -189,7 +189,7 @@ public SELF withEnvVars(Map envVars) { } public SELF withNetwork(Network network) { - this.network = Optional.of(network); + this.network = Optional.ofNullable(network); return self; } diff --git a/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/ContainerSuite.java b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/ContainerSuite.java index d69d146f563..43d2ef76586 100644 --- a/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/ContainerSuite.java +++ b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/ContainerSuite.java @@ -6,12 +6,17 @@ import com.datastrato.gravitino.integration.test.util.CloseableGroup; import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.RemoveNetworkCmd; import com.github.dockerjava.api.model.Info; +import com.github.dockerjava.api.model.Network.Ipam.Config; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.Closeable; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.DockerClientFactory; @@ -22,9 +27,11 @@ public class ContainerSuite implements Closeable { private static volatile ContainerSuite instance = null; // The subnet must match the configuration in `dev/docker/tools/mac-docker-connector.conf` - private static final String CONTAINER_NETWORK_SUBNET = "10.0.0.0/22"; - private static final String CONTAINER_NETWORK_GATEWAY = "10.0.0.1"; - private static final String CONTAINER_NETWORK_IPRANGE = "10.0.0.100/22"; + public static final String CONTAINER_NETWORK_SUBNET = "10.20.30.0/28"; + private static final String CONTAINER_NETWORK_GATEWAY = "10.20.30.1"; + private static final String CONTAINER_NETWORK_IPRANGE = "10.20.30.14/28"; + private static final String NETWORK_NAME = "gravitino-ci-network"; + private static Network network = null; private static HiveContainer hiveContainer; private static TrinoContainer trinoContainer; @@ -38,7 +45,9 @@ private ContainerSuite() { Info info = dockerClient.infoCmd().exec(); LOG.info("Docker info: {}", info); - network = createDockerNetwork(); + if ("true".equalsIgnoreCase(System.getenv("NEED_CREATE_DOCKER_NETWORK"))) { + network = createDockerNetwork(); + } } catch (Exception e) { throw new RuntimeException("Failed to initialize ContainerSuite", e); } @@ -124,12 +133,53 @@ public HiveContainer getHiveContainer() { // Let containers assign addresses in a fixed subnet to avoid `mac-docker-connector` needing to // refresh the configuration private static Network createDockerNetwork() { + DockerClient dockerClient = DockerClientFactory.instance().client(); + + // Remove the `gravitino-ci-network` if it exists + boolean networkExists = + dockerClient.listNetworksCmd().withNameFilter(NETWORK_NAME).exec().stream() + .anyMatch(network -> network.getName().equals(NETWORK_NAME)); + if (networkExists) { + RemoveNetworkCmd removeNetworkCmd = dockerClient.removeNetworkCmd(NETWORK_NAME); + removeNetworkCmd.exec(); + } + + // Check if the subnet of the network conflicts with `gravitino-ci-network` + List networks = dockerClient.listNetworksCmd().exec(); + + for (com.github.dockerjava.api.model.Network network : networks) { + List ipamConfigs = network.getIpam().getConfig(); + for (Config ipamConfig : ipamConfigs) { + try { + if (ipRangesOverlap(ipamConfig.getSubnet(), CONTAINER_NETWORK_SUBNET)) { + LOG.error( + "The Docker of the network {} subnet {} conflicts with the `gravitino-ci-network` {}, " + + "You can either remove {} network from Docker, or modify the `ContainerSuite.CONTAINER_NETWORK_SUBNET` variable", + network.getName(), + ipamConfig.getSubnet(), + CONTAINER_NETWORK_SUBNET, + network.getName()); + throw new RuntimeException( + "The Docker of the network " + + network.getName() + + " subnet " + + ipamConfig.getSubnet() + + " conflicts with the `gravitino-ci-network` " + + CONTAINER_NETWORK_SUBNET); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + com.github.dockerjava.api.model.Network.Ipam.Config ipamConfig = new com.github.dockerjava.api.model.Network.Ipam.Config(); ipamConfig .withSubnet(CONTAINER_NETWORK_SUBNET) .withGateway(CONTAINER_NETWORK_GATEWAY) - .withIpRange(CONTAINER_NETWORK_IPRANGE); + .withIpRange(CONTAINER_NETWORK_IPRANGE) + .setNetworkID("gravitino-ci-network"); return closer.register( Network.builder() @@ -140,6 +190,61 @@ private static Network createDockerNetwork() { .build()); } + public static boolean ipRangesOverlap(String cidr1, String cidr2) throws Exception { + long[] net1 = cidrToRange(cidr1); + long[] net2 = cidrToRange(cidr2); + + long startIp1 = net1[0]; + long endIp1 = net1[1]; + long startIp2 = net2[0]; + long endIp2 = net2[1]; + + LOG.info("Subnet1: {} allocate IP ranger [{} ~ {}]", cidr1, long2Ip(startIp1), long2Ip(endIp1)); + LOG.info("Subnet2: {} allocate IP ranger [{} ~ {}]", cidr2, long2Ip(startIp2), long2Ip(endIp2)); + + if (startIp1 > endIp2 || endIp1 < startIp2) { + return false; + } else { + return true; + } + } + + public static String long2Ip(final long ip) { + final StringBuilder result = new StringBuilder(15); + result.append(ip >> 24 & 0xff).append("."); + result.append(ip >> 16 & 0xff).append("."); + result.append(ip >> 8 & 0xff).append("."); + result.append(ip & 0xff); + + return result.toString(); + } + + // Classless Inter-Domain Routing (CIDR) + // https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing + private static long[] cidrToRange(String cidr) throws Exception { + String[] parts = cidr.split("/"); + InetAddress inetAddress = InetAddress.getByName(parts[0]); + int prefixLength = Integer.parseInt(parts[1]); + + ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress()); + long ip = + (inetAddress.getAddress().length == 4) ? buffer.getInt() & 0xFFFFFFFFL : buffer.getLong(); + long mask = -(1L << (32 - prefixLength)); + + long startIp = ip & mask; + + long endIp; + if (inetAddress.getAddress().length == 4) { + // IPv4 + endIp = startIp + ((1L << (32 - prefixLength)) - 1); + } else { + // IPv6 + endIp = startIp + ((1L << (128 - prefixLength)) - 1); + } + + return new long[] {startIp, endIp}; + } + @Override public void close() throws IOException { try { diff --git a/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/NetworksConflictTest.java b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/NetworksConflictTest.java new file mode 100644 index 00000000000..ad69ccd6529 --- /dev/null +++ b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/container/NetworksConflictTest.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 Datastrato Pvt Ltd. + * This software is licensed under the Apache License version 2. + */ +package com.datastrato.gravitino.integration.test.container; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class NetworksConflictTest { + @Test + public void networksConflictTest1() throws Exception { + final String subnet1 = "10.20.30.0/28"; // allocate IP ranger 10.20.30.1 ~ 10.20.30.14 + final String subnet2 = "10.20.30.0/26"; // allocate IP ranger is 10.20.30.1 ~ 10.20.30.62 + Assertions.assertTrue(ContainerSuite.ipRangesOverlap(subnet1, subnet2)); + } + + @Test + public void networksConflictTest2() throws Exception { + final String subnet1 = "10.20.30.0/28"; // allocate IP ranger is 10.20.30.1 ~ 10.20.30.14 + final String subnet2 = "10.20.31.0/28"; // allocate IP ranger is 10.20.31.1 ~ 10.20.31.14 + Assertions.assertFalse(ContainerSuite.ipRangesOverlap(subnet1, subnet2)); + } +}