diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ef47c06e3..cdb87a82b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,6 @@ +## 5.3.0 +* Docker Solver: Specify Preferred Networks to use when solving container IP #662 + ## 5.2.0 Docker Solver * Fixing DPS Docker Network IP Range diff --git a/docs/content/2-features/specify-from-which-network-solve-container/_index.en.md b/docs/content/2-features/specify-from-which-network-solve-container/_index.en.md index 1484f58f2..ca6335fe1 100644 --- a/docs/content/2-features/specify-from-which-network-solve-container/_index.en.md +++ b/docs/content/2-features/specify-from-which-network-solve-container/_index.en.md @@ -32,3 +32,10 @@ Non-authoritative answer: Name: server1.acme.com Address: 192.168.16.2 ``` + +You can also specify default preferred networks + +| Name | Description | Default Value | +|----------------------------------------------------|-----------------------------------------------------------------------------------------|-----------------| +| `solver.docker.networks.preferred.names` | Which networks DPS must prioritize when discovering container IP | | +| `solver.docker.networks.preferred.overrideDefault` | If will disable DPS and BRIDGE default networks when solving | false | diff --git a/docs/content/3-configuration/_index.en.md b/docs/content/3-configuration/_index.en.md index a46072cb7..657de5a4d 100644 --- a/docs/content/3-configuration/_index.en.md +++ b/docs/content/3-configuration/_index.en.md @@ -48,18 +48,23 @@ Common DNS resolution mechanisms used by DPS. Solvers are evaluated according to ### Docker Solver -| Name | Description | Default Value | -|----------------------------------------------|-----------------------------------------------------------------------------------------|-----------------| -| `solver.docker.registerContainerNames` | Whether container or service names should be registered as DNS hostnames. | `false` | -| `solver.docker.domain` | Domain suffix used when registering Docker containers and services. | `docker` | -| `solver.docker.hostMachineFallback` | Whether the host machine IP should be returned when a container is found but has no IP. | `true` | -| `solver.docker.dockerDaemonUri` | Docker daemon URI used to connect to Docker. | OS dependent | -| `solver.docker.dpsNetwork.autoCreate` | Whether DPS should automatically create a Docker bridge network. | `false` | -| `solver.docker.dpsNetwork.autoConnect` | Whether all containers should be auto-connected to the DPS network. | `false` | -| `solver.docker.dpsNetwork.configs` | Docker network IP configuration | | -| `solver.docker.dpsNetwork.configs[].subNet` | Subnet | `172.20.0.0/16` | -| `solver.docker.dpsNetwork.configs[].ipRange` | Ip Range | `172.20.5.0/24` | -| `solver.docker.dpsNetwork.configs[].gateway` | Gateway | `172.20.5.1` | +| Name | Description | Default Value | +|----------------------------------------------------|-----------------------------------------------------------------------------------------|-----------------| +| `solver.docker.registerContainerNames` | Whether container or service names should be registered as DNS hostnames. | `false` | +| `solver.docker.domain` | Domain suffix used when registering Docker containers and services. | `docker` | +| `solver.docker.hostMachineFallback` | Whether the host machine IP should be returned when a container is found but has no IP. | `true` | +| `solver.docker.dockerDaemonUri` | Docker daemon URI used to connect to Docker. | OS dependent | + +#### DPS Network +| Name | Description | Default Value | +|----------------------------------------------------|-----------------------------------------------------------------------------------------|-----------------| +| `solver.docker.dpsNetwork.autoCreate` | Whether DPS should automatically create a Docker bridge network. | `false` | +| `solver.docker.dpsNetwork.autoConnect` | Whether all containers should be auto-connected to the DPS network. | `false` | +| `solver.docker.dpsNetwork.configs` | Docker network IP configuration | | +| `solver.docker.dpsNetwork.configs[].subNet` | Subnet | `172.20.0.0/16` | +| `solver.docker.dpsNetwork.configs[].ipRange` | Ip Range | `172.20.5.0/24` | +| `solver.docker.dpsNetwork.configs[].gateway` | Gateway | `172.20.5.1` | + Default DPS network settings @@ -72,7 +77,14 @@ Default DPS network settings gateway: fc00:5c6f:db50::1 ``` ---- +#### Network Priority when Solving Container IP +| Name | Description | Default Value | +|----------------------------------------------------|-----------------------------------------------------------------------------------------|-----------------| +| `solver.docker.networks.preferred.names` | Which networks DPS must prioritize when discovering container IP | | +| `solver.docker.networks.preferred.overrideDefault` | If will disable DPS and BRIDGE default networks when solving | false | + +See more on [specify from which network solve container][6]. + ### System Solver @@ -154,6 +166,10 @@ solver: - subNet: fc00:5c6f:db50::/64 gateway: fc00:5c6f:db50::1 dockerDaemonUri: + networks: + preferred: + names: + - my-awesome-network system: hostMachineHostname: host.docker local: @@ -186,3 +202,4 @@ log: [3]: {{%relref "2-features/remote-solver-circuitbreaker/_index.en.md" %}} [4]: {{%relref "3-configuration/legacy.en.md" %}} [5]: {{%relref "3-configuration/format.en.md" %}} +[6]: {{%relref "2-features/specify-from-which-network-solve-container/_index.en.md" %}} diff --git a/gradle.properties b/gradle.properties index c8a701e62..d3573c222 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=5.2.0-snapshot +version=5.3.0-snapshot diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java index 54690d49f..9f6cf07b1 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java +++ b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java @@ -220,6 +220,13 @@ public String getActiveEnv() { return this.solverLocal.getActiveEnv(); } + public SolverDocker.Networks getDockerSolverNetworks() { + if (this.solverDocker == null) { + return null; + } + return this.solverDocker.networks; + } + @Value @Builder(toBuilder = true) public static class DefaultDns { @@ -483,6 +490,8 @@ public static class SolverDocker { DpsNetwork dpsNetwork; Boolean hostMachineFallback; + Networks networks; + public boolean shouldUseHostMachineFallback() { return BooleanUtils.toBoolean(hostMachineFallback); } @@ -501,6 +510,23 @@ public boolean shouldAutoConnect() { return this.dpsNetwork.shouldAutoConnect(); } + @Value + @Builder + public static class Networks { + + Preferred preferred; + + @Value + @Builder + public static class Preferred { + + boolean overrideDefault; + + List names; + + } + } + @Value @Builder public static class DpsNetwork { diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/ConfigV3.java b/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/ConfigV3.java index c624a2823..a6028b46f 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/ConfigV3.java +++ b/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/ConfigV3.java @@ -85,7 +85,7 @@ static public class Docker { String domain; Boolean hostMachineFallback; DpsNetwork dpsNetwork; - // Networks networks; + Networks networks; String dockerDaemonUri; } @@ -148,13 +148,27 @@ static public class Log { @Accessors(chain = true) @FieldDefaults(level = AccessLevel.PRIVATE) static public class Networks { - List preferredNetworkNames; + + Preferred preferred; + + @Data + @Accessors(chain = true) + @FieldDefaults(level = AccessLevel.PRIVATE) + public static class Preferred { + + List names; + + Boolean overrideDefault; + + + } } @Data @Accessors(chain = true) @FieldDefaults(level = AccessLevel.PRIVATE) static public class Remote { + Boolean active; List dnsServers; diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigMapper.java b/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigMapper.java index faedf7bc8..5b62c930a 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigMapper.java +++ b/src/main/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigMapper.java @@ -14,6 +14,7 @@ import com.mageddo.dnsproxyserver.config.NonResilientCircuitBreakerStrategyConfig; import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategyConfig; import com.mageddo.dnsproxyserver.config.dataformat.v3.ConfigV3; +import com.mageddo.dnsproxyserver.utils.Booleans; import com.mageddo.dnsserver.SimpleServer; import com.mageddo.net.IP; import com.mageddo.net.IpAddr; @@ -192,6 +193,21 @@ private static Config.SolverDocker mapSolverDocker(final ConfigV3.Solver s) { : null ) .dpsNetwork(mapDomainDpsNetwork(s)) + .networks(mapNetworks(docker.getNetworks())) + .build(); + } + + private static Config.SolverDocker.Networks mapNetworks(ConfigV3.Networks networks) { + if (networks == null) { + return null; + } + final var preferred = networks.getPreferred(); + return Config.SolverDocker.Networks.builder() + .preferred(Config.SolverDocker.Networks.Preferred.builder() + .names(preferred.getNames()) + .overrideDefault(Booleans.getOrDefault(preferred.getOverrideDefault(), false)) + .build() + ) .build(); } @@ -289,7 +305,8 @@ private static ConfigV3.Solver mapSolverV3(final Config config) { ); } - if (config.getSolverDocker() != null) { + final var solverDocker = config.getSolverDocker(); + if (solverDocker != null) { final var dpsNetwork = config.getDockerSolverDpsNetwork(); solver.setDocker(new ConfigV3.Docker() .setDomain(config.getDockerDomain()) @@ -307,6 +324,7 @@ private static ConfigV3.Solver mapSolverV3(final Config config) { Collections.map(dpsNetwork.getConfigs(), ConfigMapper::mapDpsNetworkConfigV3) ) ) + .setNetworks(mapNetworksDf(solverDocker.getNetworks())) ); } @@ -348,6 +366,18 @@ private static ConfigV3.Solver mapSolverV3(final Config config) { return solver; } + private static ConfigV3.Networks mapNetworksDf(Config.SolverDocker.Networks networks) { + if (networks == null) { + return null; + } + final var preferred = networks.getPreferred(); + return new ConfigV3.Networks() + .setPreferred(new ConfigV3.Networks.Preferred() + .setNames(preferred.getNames()) + .setOverrideDefault(preferred.isOverrideDefault()) + ); + } + private static ConfigV3.Hostname mapEntryV3(Entry entry) { return new ConfigV3.Hostname() .setHostname(entry.getHostname()) diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/mapper/ConfigMapper.java b/src/main/java/com/mageddo/dnsproxyserver/config/mapper/ConfigMapper.java index 2f7d55e0a..907dfdfc1 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/config/mapper/ConfigMapper.java +++ b/src/main/java/com/mageddo/dnsproxyserver/config/mapper/ConfigMapper.java @@ -204,28 +204,36 @@ private Config mapFrom0(List configs) { )) .dpsNetwork( SolverDocker.DpsNetwork.builder() - .name(ValueResolver.findFirst( + .name(ValueResolver.findFirstOrThrow( configs, Config::getDockerSolverDpsNetwork, SolverDocker.DpsNetwork::getName )) - .autoCreate(ValueResolver.findFirst( + .autoCreate(ValueResolver.findFirstOrThrow( configs, Config::getDockerSolverDpsNetwork, SolverDocker.DpsNetwork::getAutoCreate )) - .autoConnect(ValueResolver.findFirst( + .autoConnect(ValueResolver.findFirstOrThrow( configs, Config::getDockerSolverDpsNetwork, SolverDocker.DpsNetwork::getAutoConnect )) - .configs(ValueResolver.findFirst( + .configs(ValueResolver.findFirstOrThrow( configs, Config::getDockerSolverDpsNetwork, SolverDocker.DpsNetwork::getConfigs )) .build() ) + .networks(SolverDocker.Networks.builder() + .preferred(ValueResolver.findFirstOrThrow( + configs, + Config::getDockerSolverNetworks, + SolverDocker.Networks::getPreferred + )) + .build() + ) .build() ) .solverSystem(Config.SolverSystem @@ -295,6 +303,13 @@ static Config buildDefault() { )) .build() ) + .networks(SolverDocker.Networks.builder() + .preferred(SolverDocker.Networks.Preferred.builder() + .overrideDefault(false) + .build() + ) + .build() + ) .build() ) .solverLocal(Config.SolverLocal.builder() diff --git a/src/main/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapper.java b/src/main/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapper.java index cff202f6a..5e2c91f57 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapper.java +++ b/src/main/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapper.java @@ -1,5 +1,6 @@ package com.mageddo.dnsproxyserver.solver.docker.dataprovider.mapper; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -10,6 +11,8 @@ import com.github.dockerjava.api.command.InspectContainerResponse; import com.github.dockerjava.api.model.ContainerNetwork; +import com.mageddo.dnsproxyserver.config.Config.SolverDocker; +import com.mageddo.dnsproxyserver.config.application.Configs; import com.mageddo.dnsproxyserver.docker.application.Labels; import com.mageddo.dnsproxyserver.solver.docker.Container; import com.mageddo.dnsproxyserver.solver.docker.Network; @@ -23,8 +26,14 @@ public class ContainerMapper { public static final String DEFAULT_NETWORK_LABEL = "dps.network"; public static Container of(InspectContainerResponse inspect) { + return of(inspect, findPreferred()); + } + + public static Container of( + InspectContainerResponse inspect, SolverDocker.Networks.Preferred preferred + ) { final var foundNetworks = buildNetworks(inspect); - final var possibleNetworksNames = buildNetworkNames(inspect); + final var possibleNetworksNames = buildNetworkNames(inspect, preferred); return Container .builder() .id(inspect.getId()) @@ -66,9 +75,48 @@ static Container.Network toNetwork(ContainerNetwork n) { ; } - static Set buildNetworkNames(InspectContainerResponse c) { + static Set buildNetworkNames( + InspectContainerResponse c, SolverDocker.Networks.Preferred preferred + ) { + if (preferred.isOverrideDefault() && preferred.getNames() != null) { + return mapPrincipalNetworkWith(c, preferred.getNames()); + } + if (preferred.getNames() == null) { + return buildDefaultWithPrincipal(c); + } + return mapPrincipalNetworkWith(c, preferred.getNames(), buildDefault()); + } + + @SafeVarargs + private static Set mapPrincipalNetworkWith( + InspectContainerResponse c, Collection... namesCollections + ) { + final var set = new LinkedHashSet(); + final var principal = mapPrincipalNetworkName(c); + if (StringUtils.isNotBlank(principal)) { + set.add(principal); + } + for (var names : namesCollections) { + set.addAll(names); + } + return set; + } + + private static LinkedHashSet buildDefault() { + return buildDefault(null); + } + + private static LinkedHashSet buildDefaultWithPrincipal(InspectContainerResponse c) { + return buildDefault(mapPrincipalNetworkName(c)); + } + + private static String mapPrincipalNetworkName(InspectContainerResponse c) { + return Labels.findLabelValue(c.getConfig(), DEFAULT_NETWORK_LABEL); + } + + private static LinkedHashSet buildDefault(String principalNetworkName) { return Stream.of( - Labels.findLabelValue(c.getConfig(), DEFAULT_NETWORK_LABEL), + principalNetworkName, Network.Name.DPS.lowerCaseName(), Network.Name.BRIDGE.lowerCaseName() ) @@ -76,6 +124,13 @@ static Set buildNetworkNames(InspectContainerResponse c) { .collect(Collectors.toCollection(LinkedHashSet::new)); } + private static SolverDocker.Networks.Preferred findPreferred() { + return Configs.getInstance() + .getSolverDocker() + .getNetworks() + .getPreferred(); + } + static IP buildDefaultIp(InspectContainerResponse c, IP.Version version) { final var settings = c.getNetworkSettings(); if (settings == null) { diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigV3JsonMapperTest.java b/src/test/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigV3JsonMapperTest.java index 37f83e7f2..cc4eb376f 100644 --- a/src/test/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigV3JsonMapperTest.java +++ b/src/test/java/com/mageddo/dnsproxyserver/config/dataformat/v3/mapper/ConfigV3JsonMapperTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; class ConfigV3JsonMapperTest { @@ -14,11 +14,11 @@ void mustFindAndSerializeWithTheExactSameContent() { final var json = ConfigV3Templates.buildJson(); final var parsed = ConfigV3JsonMapper.of(json); - final var marshalled = ConfigV3JsonMapper.toJson(parsed); - final var marshalledParsed = ConfigV3JsonMapper.of(json); + final var marshalledParsed = ConfigV3JsonMapper.of(ConfigV3JsonMapper.toJson(parsed)); - assertEquals(json, marshalled); - assertEquals(parsed, marshalledParsed); + assertThat(parsed) + .usingRecursiveComparison() + .isEqualTo(marshalledParsed); } } diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/templates/DockerSolverPreferredNetworksTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/config/templates/DockerSolverPreferredNetworksTemplates.java new file mode 100644 index 000000000..e9ef0711c --- /dev/null +++ b/src/test/java/com/mageddo/dnsproxyserver/config/templates/DockerSolverPreferredNetworksTemplates.java @@ -0,0 +1,21 @@ +package com.mageddo.dnsproxyserver.config.templates; + +import java.util.List; + +import com.mageddo.dnsproxyserver.config.Config.SolverDocker.Networks.Preferred; + +public class DockerSolverPreferredNetworksTemplates { + public static Preferred batataPreferredNetworkOverride() { + return Preferred.builder() + .names(List.of("batata")) + .overrideDefault(true) + .build(); + } + + public static Preferred batataPreferredNetwork() { + return Preferred.builder() + .names(List.of("batata")) + .overrideDefault(false) + .build(); + } +} diff --git a/src/test/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapperTest.java b/src/test/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapperTest.java index 3a3c87741..7e6dd69de 100644 --- a/src/test/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapperTest.java +++ b/src/test/java/com/mageddo/dnsproxyserver/solver/docker/dataprovider/mapper/ContainerMapperTest.java @@ -1,11 +1,13 @@ package com.mageddo.dnsproxyserver.solver.docker.dataprovider.mapper; +import com.mageddo.dnsproxyserver.config.templates.DockerSolverPreferredNetworksTemplates; import com.mageddo.net.IP; import org.junit.jupiter.api.Test; import testing.templates.docker.InspectContainerResponseTemplates; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static testing.templates.docker.InspectContainerResponseTemplates.ngixWithDefaultBridgeNetworkOnly; @@ -43,6 +45,74 @@ void mustMapBridgeNetwork() { } + @Test + void mustPreferSpecifiedNetworks() { + + // arrange + final var inspect = ngixWithDefaultBridgeNetworkOnly(); + final var preferred = DockerSolverPreferredNetworksTemplates.batataPreferredNetwork(); + + // act + final var container = ContainerMapper.of(inspect, preferred); + + // assert + assertNotNull(container); + assertThat(container.getPreferredNetworkNames()).containsExactly( + "shibata", "batata", "dps", "bridge" + ); + + } + + @Test + void mustReplaceBySpecifiedNetworksRespectingPrincipal() { + // arrange + final var inspect = ngixWithDefaultBridgeNetworkOnly(); + final var preferred = DockerSolverPreferredNetworksTemplates.batataPreferredNetworkOverride(); + + // act + final var container = ContainerMapper.of(inspect, preferred); + + // assert + assertNotNull(container); + assertThat(container.getPreferredNetworkNames()) + .containsExactly("shibata", "batata"); + + } + + @Test + void mustReplaceSpecifiedNetworkNames() { + // arrange + final var inspect = ngixWithIpv6DefaultBridgeNetworkOnly(); + final var preferred = DockerSolverPreferredNetworksTemplates.batataPreferredNetworkOverride(); + + // act + final var container = ContainerMapper.of(inspect, preferred); + + // assert + assertNotNull(container); + + assertThat(container.getPreferredNetworkNames()) + .containsExactly("batata"); + + } + + @Test + void mustPrependSpecifiedNetworkNames() { + // arrange + final var inspect = ngixWithIpv6DefaultBridgeNetworkOnly(); + final var preferred = DockerSolverPreferredNetworksTemplates.batataPreferredNetwork(); + + // act + final var container = ContainerMapper.of(inspect, preferred); + + // assert + assertNotNull(container); + + assertThat(container.getPreferredNetworkNames()) + .containsExactly("batata", "dps", "bridge"); + + } + @Test void mustMapOverlayNetwork() { // arrange