mirror of
https://github.com/containers/podman-compose.git
synced 2025-05-12 18:24:56 +02:00
Merge pull request #867 from baszoetekouw/fix-networks
Fix multiple networks with separately specified ip and mac
This commit is contained in:
commit
e893d06313
2
.gitignore
vendored
2
.gitignore
vendored
@ -47,6 +47,8 @@ coverage.xml
|
|||||||
*.cover
|
*.cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
test-compose.yaml
|
||||||
|
test-compose-?.yaml
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
67
docs/Extensions.md
Normal file
67
docs/Extensions.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Podman specific extensions to the docker-compose format
|
||||||
|
|
||||||
|
Podman-compose supports the following extension to the docker-compose format.
|
||||||
|
|
||||||
|
## Per-network MAC-addresses
|
||||||
|
|
||||||
|
Generic docker-compose files support specification of the MAC address on the container level. If the
|
||||||
|
container has multiple network interfaces, the specified MAC address is applied to the first
|
||||||
|
specified network.
|
||||||
|
|
||||||
|
Podman-compose in addition supports the specification of MAC addresses on a per-network basis. This
|
||||||
|
is done by adding a `podman.mac_address` key to the network configuration in the container. The
|
||||||
|
value of the `podman.mac_address` key is the MAC address to be used for the network interface.
|
||||||
|
|
||||||
|
Specifying a MAC address for the container and for individual networks at the same time is not
|
||||||
|
supported.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
net0:
|
||||||
|
driver: "bridge"
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: "192.168.0.0/24"
|
||||||
|
net1:
|
||||||
|
driver: "bridge"
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: "192.168.1.0/24"
|
||||||
|
|
||||||
|
services:
|
||||||
|
webserver
|
||||||
|
image: "busybox"
|
||||||
|
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc", "-p", "8001"]
|
||||||
|
networks:
|
||||||
|
net0:
|
||||||
|
ipv4_address: "192.168.0.10"
|
||||||
|
podman.mac_address: "02:aa:aa:aa:aa:aa"
|
||||||
|
net1:
|
||||||
|
ipv4_address: "192.168.1.10"
|
||||||
|
podman.mac_address: "02:bb:bb:bb:bb:bb"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Podman-specific network modes
|
||||||
|
|
||||||
|
Generic docker-compose supports the following values for `network-mode` for a container:
|
||||||
|
|
||||||
|
- `bridge`
|
||||||
|
- `host`
|
||||||
|
- `none`
|
||||||
|
- `service`
|
||||||
|
- `container`
|
||||||
|
|
||||||
|
In addition, podman-compose supports the following podman-specific values for `network-mode`:
|
||||||
|
|
||||||
|
- `slirp4netns[:<options>,...]`
|
||||||
|
- `ns:<options>`
|
||||||
|
- `pasta[:<options>,...]`
|
||||||
|
- `private`
|
||||||
|
|
||||||
|
The options to the network modes are passed to the `--network` option of the `podman create` command
|
||||||
|
as-is.
|
@ -780,27 +780,29 @@ async def assert_cnt_nets(compose, cnt):
|
|||||||
def get_net_args(compose, cnt):
|
def get_net_args(compose, cnt):
|
||||||
service_name = cnt["service_name"]
|
service_name = cnt["service_name"]
|
||||||
net_args = []
|
net_args = []
|
||||||
mac_address = cnt.get("mac_address", None)
|
|
||||||
if mac_address:
|
|
||||||
net_args.extend(["--mac-address", mac_address])
|
|
||||||
is_bridge = False
|
is_bridge = False
|
||||||
|
mac_address = cnt.get("mac_address", None)
|
||||||
net = cnt.get("network_mode", None)
|
net = cnt.get("network_mode", None)
|
||||||
if net:
|
if net:
|
||||||
if net == "none":
|
if net == "none":
|
||||||
is_bridge = False
|
is_bridge = False
|
||||||
elif net == "host":
|
elif net == "host":
|
||||||
net_args.extend(["--network", net])
|
net_args.append(f"--network={net}")
|
||||||
elif net.startswith("slirp4netns:"):
|
elif net.startswith("slirp4netns"): # Note: podman-specific network mode
|
||||||
net_args.extend(["--network", net])
|
net_args.append(f"--network={net}")
|
||||||
elif net.startswith("ns:"):
|
elif net == "private": # Note: podman-specific network mode
|
||||||
net_args.extend(["--network", net])
|
net_args.append("--network=private")
|
||||||
|
elif net.startswith("pasta"): # Note: podman-specific network mode
|
||||||
|
net_args.append(f"--network={net}")
|
||||||
|
elif net.startswith("ns:"): # Note: podman-specific network mode
|
||||||
|
net_args.append(f"--network={net}")
|
||||||
elif net.startswith("service:"):
|
elif net.startswith("service:"):
|
||||||
other_srv = net.split(":", 1)[1].strip()
|
other_srv = net.split(":", 1)[1].strip()
|
||||||
other_cnt = compose.container_names_by_service[other_srv][0]
|
other_cnt = compose.container_names_by_service[other_srv][0]
|
||||||
net_args.extend(["--network", f"container:{other_cnt}"])
|
net_args.append(f"--network=container:{other_cnt}")
|
||||||
elif net.startswith("container:"):
|
elif net.startswith("container:"):
|
||||||
other_cnt = net.split(":", 1)[1].strip()
|
other_cnt = net.split(":", 1)[1].strip()
|
||||||
net_args.extend(["--network", f"container:{other_cnt}"])
|
net_args.append(f"--network=container:{other_cnt}")
|
||||||
elif net.startswith("bridge"):
|
elif net.startswith("bridge"):
|
||||||
is_bridge = True
|
is_bridge = True
|
||||||
else:
|
else:
|
||||||
@ -812,6 +814,7 @@ def get_net_args(compose, cnt):
|
|||||||
default_net = compose.default_net
|
default_net = compose.default_net
|
||||||
nets = compose.networks
|
nets = compose.networks
|
||||||
cnt_nets = cnt.get("networks", None)
|
cnt_nets = cnt.get("networks", None)
|
||||||
|
|
||||||
aliases = [service_name]
|
aliases = [service_name]
|
||||||
# NOTE: from podman manpage:
|
# NOTE: from podman manpage:
|
||||||
# NOTE: A container will only have access to aliases on the first network
|
# NOTE: A container will only have access to aliases on the first network
|
||||||
@ -856,32 +859,82 @@ def get_net_args(compose, cnt):
|
|||||||
net_names.append(net_name)
|
net_names.append(net_name)
|
||||||
net_names_str = ",".join(net_names)
|
net_names_str = ",".join(net_names)
|
||||||
|
|
||||||
if ip_assignments > 1:
|
# TODO: add support for per-interface aliases
|
||||||
multiple_nets = cnt.get("networks", None)
|
# See https://docs.docker.com/compose/compose-file/compose-file-v3/#aliases
|
||||||
multiple_net_names = multiple_nets.keys()
|
# Even though podman accepts network-specific aliases (e.g., --network=bridge:alias=foo,
|
||||||
|
# podman currently ignores this if a per-container network-alias is set; as pdoman-compose
|
||||||
|
# always sets a network-alias to the container name, is currently doesn't make sense to
|
||||||
|
# implement this.
|
||||||
|
multiple_nets = cnt.get("networks", None)
|
||||||
|
if multiple_nets and len(multiple_nets) > 1:
|
||||||
|
# networks can be specified as a dict with config per network or as a plain list without
|
||||||
|
# config. Support both cases by converting the plain list to a dict with empty config.
|
||||||
|
if is_list(multiple_nets):
|
||||||
|
multiple_nets = {net: {} for net in multiple_nets}
|
||||||
|
else:
|
||||||
|
multiple_nets = {net: net_config or {} for net, net_config in multiple_nets.items()}
|
||||||
|
|
||||||
for net_ in multiple_net_names:
|
# if a mac_address was specified on the container level, we need to check that it is not
|
||||||
|
# specified on the network level as well
|
||||||
|
if mac_address is not None:
|
||||||
|
for net_config_ in multiple_nets.values():
|
||||||
|
network_mac = net_config_.get("podman.mac_address", None)
|
||||||
|
if network_mac is not None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"conflicting mac addresses {mac_address} and {network_mac}:"
|
||||||
|
"specifying mac_address on both container and network level "
|
||||||
|
"is not supported"
|
||||||
|
)
|
||||||
|
|
||||||
|
for net_, net_config_ in multiple_nets.items():
|
||||||
net_desc = nets[net_] or {}
|
net_desc = nets[net_] or {}
|
||||||
is_ext = net_desc.get("external", None)
|
is_ext = net_desc.get("external", None)
|
||||||
ext_desc = is_ext if is_dict(is_ext) else {}
|
ext_desc = is_ext if is_dict(is_ext) else {}
|
||||||
default_net_name = net_ if is_ext else f"{proj_name}_{net_}"
|
default_net_name = net_ if is_ext else f"{proj_name}_{net_}"
|
||||||
net_name = ext_desc.get("name", None) or net_desc.get("name", None) or default_net_name
|
net_name = ext_desc.get("name", None) or net_desc.get("name", None) or default_net_name
|
||||||
|
|
||||||
ipv4 = multiple_nets[net_].get("ipv4_address", None)
|
ipv4 = net_config_.get("ipv4_address", None)
|
||||||
ipv6 = multiple_nets[net_].get("ipv6_address", None)
|
ipv6 = net_config_.get("ipv6_address", None)
|
||||||
if ipv4 is not None and ipv6 is not None:
|
# custom extension; not supported by docker-compose v3
|
||||||
net_args.extend(["--network", f"{net_name}:ip={ipv4},ip={ipv6}"])
|
mac = net_config_.get("podman.mac_address", None)
|
||||||
elif ipv4 is None and ipv6 is not None:
|
|
||||||
net_args.extend(["--network", f"{net_name}:ip={ipv6}"])
|
# if a mac_address was specified on the container level, apply it to the first network
|
||||||
elif ipv6 is None and ipv4 is not None:
|
# This works for Python > 3.6, because dict insert ordering is preserved, so we are
|
||||||
net_args.extend(["--network", f"{net_name}:ip={ipv4}"])
|
# sure that the first network we encounter here is also the first one specified by
|
||||||
|
# the user
|
||||||
|
if mac is None and mac_address is not None:
|
||||||
|
mac = mac_address
|
||||||
|
mac_address = None
|
||||||
|
|
||||||
|
net_options = []
|
||||||
|
if ipv4:
|
||||||
|
net_options.append(f"ip={ipv4}")
|
||||||
|
if ipv6:
|
||||||
|
net_options.append(f"ip={ipv6}")
|
||||||
|
if mac:
|
||||||
|
net_options.append(f"mac={mac}")
|
||||||
|
|
||||||
|
if net_options:
|
||||||
|
net_args.append(f"--network={net_name}:" + ",".join(net_options))
|
||||||
|
else:
|
||||||
|
net_args.append(f"--network={net_name}")
|
||||||
else:
|
else:
|
||||||
if is_bridge:
|
if is_bridge:
|
||||||
net_args.extend(["--net", net_names_str, "--network-alias", ",".join(aliases)])
|
if net_names_str:
|
||||||
|
net_args.append(f"--network={net_names_str}")
|
||||||
|
else:
|
||||||
|
net_args.append("--network=bridge")
|
||||||
if ip:
|
if ip:
|
||||||
net_args.append(f"--ip={ip}")
|
net_args.append(f"--ip={ip}")
|
||||||
if ip6:
|
if ip6:
|
||||||
net_args.append(f"--ip6={ip6}")
|
net_args.append(f"--ip6={ip6}")
|
||||||
|
if mac_address:
|
||||||
|
net_args.append(f"--mac-address={mac_address}")
|
||||||
|
|
||||||
|
if is_bridge:
|
||||||
|
for alias in aliases:
|
||||||
|
net_args.extend([f"--network-alias={alias}"])
|
||||||
|
|
||||||
return net_args
|
return net_args
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ from unittest import mock
|
|||||||
from podman_compose import container_to_args
|
from podman_compose import container_to_args
|
||||||
|
|
||||||
|
|
||||||
def create_compose_mock():
|
def create_compose_mock(project_name="test_project_name"):
|
||||||
compose = mock.Mock()
|
compose = mock.Mock()
|
||||||
compose.project_name = "test_project_name"
|
compose.project_name = project_name
|
||||||
compose.dirname = "test_dirname"
|
compose.dirname = "test_dirname"
|
||||||
compose.container_names_by_service.get = mock.Mock(return_value=None)
|
compose.container_names_by_service.get = mock.Mock(return_value=None)
|
||||||
compose.prefer_volume_over_mount = False
|
compose.prefer_volume_over_mount = False
|
||||||
@ -37,10 +37,8 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
[
|
[
|
||||||
"--name=project_name_service_name1",
|
"--name=project_name_service_name1",
|
||||||
"-d",
|
"-d",
|
||||||
"--net",
|
"--network=bridge",
|
||||||
"",
|
"--network-alias=service_name",
|
||||||
"--network-alias",
|
|
||||||
"service_name",
|
|
||||||
"busybox",
|
"busybox",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -57,10 +55,8 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
[
|
[
|
||||||
"--name=project_name_service_name1",
|
"--name=project_name_service_name1",
|
||||||
"-d",
|
"-d",
|
||||||
"--net",
|
"--network=bridge",
|
||||||
"",
|
"--network-alias=service_name",
|
||||||
"--network-alias",
|
|
||||||
"service_name",
|
|
||||||
"--runtime",
|
"--runtime",
|
||||||
"runsc",
|
"runsc",
|
||||||
"busybox",
|
"busybox",
|
||||||
@ -82,10 +78,8 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
[
|
[
|
||||||
"--name=project_name_service_name1",
|
"--name=project_name_service_name1",
|
||||||
"-d",
|
"-d",
|
||||||
"--net",
|
"--network=bridge",
|
||||||
"",
|
"--network-alias=service_name",
|
||||||
"--network-alias",
|
|
||||||
"service_name",
|
|
||||||
"--sysctl",
|
"--sysctl",
|
||||||
"net.core.somaxconn=1024",
|
"net.core.somaxconn=1024",
|
||||||
"--sysctl",
|
"--sysctl",
|
||||||
@ -109,10 +103,8 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
[
|
[
|
||||||
"--name=project_name_service_name1",
|
"--name=project_name_service_name1",
|
||||||
"-d",
|
"-d",
|
||||||
"--net",
|
"--network=bridge",
|
||||||
"",
|
"--network-alias=service_name",
|
||||||
"--network-alias",
|
|
||||||
"service_name",
|
|
||||||
"--sysctl",
|
"--sysctl",
|
||||||
"net.core.somaxconn=1024",
|
"net.core.somaxconn=1024",
|
||||||
"--sysctl",
|
"--sysctl",
|
||||||
@ -143,10 +135,8 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
[
|
[
|
||||||
"--name=project_name_service_name1",
|
"--name=project_name_service_name1",
|
||||||
"-d",
|
"-d",
|
||||||
"--net",
|
"--network=bridge",
|
||||||
"",
|
"--network-alias=service_name",
|
||||||
"--network-alias",
|
|
||||||
"service_name",
|
|
||||||
"--pid",
|
"--pid",
|
||||||
"host",
|
"host",
|
||||||
"busybox",
|
"busybox",
|
||||||
@ -166,10 +156,8 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
"--name=project_name_service_name1",
|
"--name=project_name_service_name1",
|
||||||
"-d",
|
"-d",
|
||||||
"--http-proxy=false",
|
"--http-proxy=false",
|
||||||
"--net",
|
"--network=bridge",
|
||||||
"",
|
"--network-alias=service_name",
|
||||||
"--network-alias",
|
|
||||||
"service_name",
|
|
||||||
"busybox",
|
"busybox",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
298
pytests/test_get_net_args.py
Normal file
298
pytests/test_get_net_args.py
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from parameterized import parameterized
|
||||||
|
|
||||||
|
from podman_compose import get_net_args
|
||||||
|
|
||||||
|
from .test_container_to_args import create_compose_mock
|
||||||
|
|
||||||
|
PROJECT_NAME = "test_project_name"
|
||||||
|
SERVICE_NAME = "service_name"
|
||||||
|
CONTAINER_NAME = f"{PROJECT_NAME}_{SERVICE_NAME}_1"
|
||||||
|
|
||||||
|
|
||||||
|
def get_networked_compose(num_networks=1):
|
||||||
|
compose = create_compose_mock(PROJECT_NAME)
|
||||||
|
for network in range(num_networks):
|
||||||
|
compose.networks[f"net{network}"] = {
|
||||||
|
"driver": "bridge",
|
||||||
|
"ipam": {
|
||||||
|
"config": [
|
||||||
|
{"subnet": f"192.168.{network}.0/24"},
|
||||||
|
{"subnet": f"fd00:{network}::/64"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enable_ipv6": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
return compose
|
||||||
|
|
||||||
|
|
||||||
|
def get_minimal_container():
|
||||||
|
return {
|
||||||
|
"name": CONTAINER_NAME,
|
||||||
|
"service_name": SERVICE_NAME,
|
||||||
|
"image": "busybox",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetNetArgs(unittest.TestCase):
|
||||||
|
def test_minimal(self):
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
"--network=bridge",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_one_net(self):
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {}}
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_alias(self):
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {}}
|
||||||
|
container["_aliases"] = ["alias1", "alias2"]
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
"--network-alias=alias1",
|
||||||
|
"--network-alias=alias2",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_one_ipv4(self):
|
||||||
|
ip = "192.168.0.42"
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {"ipv4_address": ip}}
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0",
|
||||||
|
f"--ip={ip}",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_one_ipv6(self):
|
||||||
|
ipv6_address = "fd00:0::42"
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {"ipv6_address": ipv6_address}}
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0",
|
||||||
|
f"--ip6={ipv6_address}",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_one_mac(self):
|
||||||
|
mac = "00:11:22:33:44:55"
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {}}
|
||||||
|
container["mac_address"] = mac
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0",
|
||||||
|
f"--mac-address={mac}",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_one_mac_two_nets(self):
|
||||||
|
mac = "00:11:22:33:44:55"
|
||||||
|
compose = get_networked_compose(num_networks=6)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {}, "net1": {}}
|
||||||
|
container["mac_address"] = mac
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0:mac={mac}",
|
||||||
|
f"--network={PROJECT_NAME}_net1",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_two_nets_as_dict(self):
|
||||||
|
compose = get_networked_compose(num_networks=2)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {}, "net1": {}}
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0",
|
||||||
|
f"--network={PROJECT_NAME}_net1",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_two_nets_as_list(self):
|
||||||
|
compose = get_networked_compose(num_networks=2)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = ["net0", "net1"]
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0",
|
||||||
|
f"--network={PROJECT_NAME}_net1",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_two_ipv4(self):
|
||||||
|
ip0 = "192.168.0.42"
|
||||||
|
ip1 = "192.168.1.42"
|
||||||
|
compose = get_networked_compose(num_networks=2)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {"ipv4_address": ip0}, "net1": {"ipv4_address": ip1}}
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0:ip={ip0}",
|
||||||
|
f"--network={PROJECT_NAME}_net1:ip={ip1}",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_two_ipv6(self):
|
||||||
|
ip0 = "fd00:0::42"
|
||||||
|
ip1 = "fd00:1::42"
|
||||||
|
compose = get_networked_compose(num_networks=2)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {"net0": {"ipv6_address": ip0}, "net1": {"ipv6_address": ip1}}
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0:ip={ip0}",
|
||||||
|
f"--network={PROJECT_NAME}_net1:ip={ip1}",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
# custom extension; not supported by docker-compose
|
||||||
|
def test_two_mac(self):
|
||||||
|
mac0 = "00:00:00:00:00:01"
|
||||||
|
mac1 = "00:00:00:00:00:02"
|
||||||
|
compose = get_networked_compose(num_networks=2)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {
|
||||||
|
"net0": {"podman.mac_address": mac0},
|
||||||
|
"net1": {"podman.mac_address": mac1},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0:mac={mac0}",
|
||||||
|
f"--network={PROJECT_NAME}_net1:mac={mac1}",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_mixed_mac(self):
|
||||||
|
ip4_0 = "192.168.0.42"
|
||||||
|
ip4_1 = "192.168.1.42"
|
||||||
|
ip4_2 = "192.168.2.42"
|
||||||
|
mac_0 = "00:00:00:00:00:01"
|
||||||
|
mac_1 = "00:00:00:00:00:02"
|
||||||
|
|
||||||
|
compose = get_networked_compose(num_networks=3)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {
|
||||||
|
"net0": {"ipv4_address": ip4_0},
|
||||||
|
"net1": {"ipv4_address": ip4_1, "podman.mac_address": mac_0},
|
||||||
|
"net2": {"ipv4_address": ip4_2},
|
||||||
|
}
|
||||||
|
container["mac_address"] = mac_1
|
||||||
|
|
||||||
|
expected_exception = (
|
||||||
|
r"specifying mac_address on both container and network level " r"is not supported"
|
||||||
|
)
|
||||||
|
self.assertRaisesRegex(RuntimeError, expected_exception, get_net_args, compose, container)
|
||||||
|
|
||||||
|
def test_mixed_config(self):
|
||||||
|
ip4_0 = "192.168.0.42"
|
||||||
|
ip4_1 = "192.168.1.42"
|
||||||
|
ip6_0 = "fd00:0::42"
|
||||||
|
ip6_2 = "fd00:2::42"
|
||||||
|
mac = "00:11:22:33:44:55"
|
||||||
|
compose = get_networked_compose(num_networks=4)
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["networks"] = {
|
||||||
|
"net0": {"ipv4_address": ip4_0, "ipv6_address": ip6_0},
|
||||||
|
"net1": {"ipv4_address": ip4_1},
|
||||||
|
"net2": {"ipv6_address": ip6_2},
|
||||||
|
"net3": {},
|
||||||
|
}
|
||||||
|
container["mac_address"] = mac
|
||||||
|
|
||||||
|
expected_args = [
|
||||||
|
f"--network={PROJECT_NAME}_net0:ip={ip4_0},ip={ip6_0},mac={mac}",
|
||||||
|
f"--network={PROJECT_NAME}_net1:ip={ip4_1}",
|
||||||
|
f"--network={PROJECT_NAME}_net2:ip={ip6_2}",
|
||||||
|
f"--network={PROJECT_NAME}_net3",
|
||||||
|
f"--network-alias={SERVICE_NAME}",
|
||||||
|
]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
("bridge", ["--network=bridge", f"--network-alias={SERVICE_NAME}"]),
|
||||||
|
("host", ["--network=host"]),
|
||||||
|
("none", []),
|
||||||
|
("slirp4netns", ["--network=slirp4netns"]),
|
||||||
|
("slirp4netns:cidr=10.42.0.0/24", ["--network=slirp4netns:cidr=10.42.0.0/24"]),
|
||||||
|
("private", ["--network=private"]),
|
||||||
|
("pasta", ["--network=pasta"]),
|
||||||
|
("pasta:--ipv4-only,-a,10.0.2.0", ["--network=pasta:--ipv4-only,-a,10.0.2.0"]),
|
||||||
|
("ns:my_namespace", ["--network=ns:my_namespace"]),
|
||||||
|
("container:my_container", ["--network=container:my_container"]),
|
||||||
|
])
|
||||||
|
def test_network_modes(self, network_mode, expected_args):
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["network_mode"] = network_mode
|
||||||
|
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
||||||
|
|
||||||
|
def test_network_mode_invalid(self):
|
||||||
|
compose = get_networked_compose()
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["network_mode"] = "invalid_mode"
|
||||||
|
|
||||||
|
with self.assertRaises(SystemExit):
|
||||||
|
get_net_args(compose, container)
|
||||||
|
|
||||||
|
def test_network__mode_service(self):
|
||||||
|
compose = get_networked_compose()
|
||||||
|
compose.container_names_by_service = {
|
||||||
|
"service_1": ["container_1"],
|
||||||
|
"service_2": ["container_2"],
|
||||||
|
}
|
||||||
|
|
||||||
|
container = get_minimal_container()
|
||||||
|
container["network_mode"] = "service:service_2"
|
||||||
|
|
||||||
|
expected_args = ["--network=container:container_2"]
|
||||||
|
args = get_net_args(compose, container)
|
||||||
|
self.assertListEqual(expected_args, args)
|
61
tests/nets_test_ip/docker-compose.yml
Normal file
61
tests/nets_test_ip/docker-compose.yml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
version: "3"
|
||||||
|
networks:
|
||||||
|
shared-network:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: "172.19.1.0/24"
|
||||||
|
internal-network:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: "172.19.2.0/24"
|
||||||
|
|
||||||
|
services:
|
||||||
|
web1:
|
||||||
|
image: busybox
|
||||||
|
hostname: web1
|
||||||
|
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||||
|
working_dir: /var/www/html
|
||||||
|
networks:
|
||||||
|
shared-network:
|
||||||
|
ipv4_address: "172.19.1.10"
|
||||||
|
podman.mac_address: "02:01:01:00:01:01"
|
||||||
|
internal-network:
|
||||||
|
ipv4_address: "172.19.2.10"
|
||||||
|
podman.mac_address: "02:01:01:00:02:01"
|
||||||
|
volumes:
|
||||||
|
- ./test1.txt:/var/www/html/index.txt:ro,z
|
||||||
|
web2:
|
||||||
|
image: busybox
|
||||||
|
hostname: web2
|
||||||
|
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||||
|
working_dir: /var/www/html
|
||||||
|
mac_address: "02:01:01:00:02:02"
|
||||||
|
networks:
|
||||||
|
internal-network:
|
||||||
|
ipv4_address: "172.19.2.11"
|
||||||
|
volumes:
|
||||||
|
- ./test2.txt:/var/www/html/index.txt:ro,z
|
||||||
|
|
||||||
|
web3:
|
||||||
|
image: busybox
|
||||||
|
hostname: web2
|
||||||
|
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||||
|
working_dir: /var/www/html
|
||||||
|
networks:
|
||||||
|
internal-network:
|
||||||
|
volumes:
|
||||||
|
- ./test3.txt:/var/www/html/index.txt:ro,z
|
||||||
|
|
||||||
|
web4:
|
||||||
|
image: busybox
|
||||||
|
hostname: web2
|
||||||
|
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||||
|
working_dir: /var/www/html
|
||||||
|
networks:
|
||||||
|
internal-network:
|
||||||
|
shared-network:
|
||||||
|
ipv4_address: "172.19.1.13"
|
||||||
|
volumes:
|
||||||
|
- ./test4.txt:/var/www/html/index.txt:ro,z
|
1
tests/nets_test_ip/test1.txt
Normal file
1
tests/nets_test_ip/test1.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test1
|
1
tests/nets_test_ip/test2.txt
Normal file
1
tests/nets_test_ip/test2.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test2
|
1
tests/nets_test_ip/test3.txt
Normal file
1
tests/nets_test_ip/test3.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test3
|
1
tests/nets_test_ip/test4.txt
Normal file
1
tests/nets_test_ip/test4.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test4
|
116
tests/test_podman_compose_networks.py
Normal file
116
tests/test_podman_compose_networks.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
test_podman_compose_networks.py
|
||||||
|
|
||||||
|
Tests the podman networking parameters
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=redefined-outer-name
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from .test_podman_compose import podman_compose_path
|
||||||
|
from .test_podman_compose import test_path
|
||||||
|
from .test_utils import RunSubprocessMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TestPodmanComposeNetwork(RunSubprocessMixin, unittest.TestCase):
|
||||||
|
@staticmethod
|
||||||
|
def compose_file():
|
||||||
|
"""Returns the path to the compose file used for this test module"""
|
||||||
|
return os.path.join(test_path(), "nets_test_ip", "docker-compose.yml")
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
"""
|
||||||
|
Ensures that the services within the "profile compose file" are removed between
|
||||||
|
each test case.
|
||||||
|
"""
|
||||||
|
# run the test case
|
||||||
|
yield
|
||||||
|
|
||||||
|
down_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path(),
|
||||||
|
"-f",
|
||||||
|
self.compose_file(),
|
||||||
|
"kill",
|
||||||
|
"-a",
|
||||||
|
]
|
||||||
|
self.run_subprocess(down_cmd)
|
||||||
|
|
||||||
|
def test_networks(self):
|
||||||
|
up_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path(),
|
||||||
|
"-f",
|
||||||
|
self.compose_file(),
|
||||||
|
"up",
|
||||||
|
"-d",
|
||||||
|
"--force-recreate",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.run_subprocess_assert_returncode(up_cmd)
|
||||||
|
|
||||||
|
check_cmd = [
|
||||||
|
podman_compose_path(),
|
||||||
|
"-f",
|
||||||
|
self.compose_file(),
|
||||||
|
"ps",
|
||||||
|
"--format",
|
||||||
|
'"{{.Names}}"',
|
||||||
|
]
|
||||||
|
out, _ = self.run_subprocess_assert_returncode(check_cmd)
|
||||||
|
self.assertIn(b"nets_test_ip_web1_1", out)
|
||||||
|
self.assertIn(b"nets_test_ip_web2_1", out)
|
||||||
|
|
||||||
|
expected_wget = {
|
||||||
|
"172.19.1.10": "test1",
|
||||||
|
"172.19.2.10": "test1",
|
||||||
|
"172.19.2.11": "test2",
|
||||||
|
"web3": "test3",
|
||||||
|
"172.19.1.13": "test4",
|
||||||
|
}
|
||||||
|
|
||||||
|
for service in ("web1", "web2"):
|
||||||
|
for ip, expect in expected_wget.items():
|
||||||
|
wget_cmd = [
|
||||||
|
podman_compose_path(),
|
||||||
|
"-f",
|
||||||
|
self.compose_file(),
|
||||||
|
"exec",
|
||||||
|
service,
|
||||||
|
"wget",
|
||||||
|
"-q",
|
||||||
|
"-O-",
|
||||||
|
f"http://{ip}:8001/index.txt",
|
||||||
|
]
|
||||||
|
out, _ = self.run_subprocess_assert_returncode(wget_cmd)
|
||||||
|
self.assertEqual(f"{expect}\r\n", out.decode('utf-8'))
|
||||||
|
|
||||||
|
expected_macip = {
|
||||||
|
"web1": {
|
||||||
|
"eth0": ["172.19.1.10", "02:01:01:00:01:01"],
|
||||||
|
"eth1": ["172.19.2.10", "02:01:01:00:02:01"],
|
||||||
|
},
|
||||||
|
"web2": {"eth0": ["172.19.2.11", "02:01:01:00:02:02"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
for service, interfaces in expected_macip.items():
|
||||||
|
ip_cmd = [
|
||||||
|
podman_compose_path(),
|
||||||
|
"-f",
|
||||||
|
self.compose_file(),
|
||||||
|
"exec",
|
||||||
|
service,
|
||||||
|
"ip",
|
||||||
|
"addr",
|
||||||
|
"show",
|
||||||
|
]
|
||||||
|
out, _ = self.run_subprocess_assert_returncode(ip_cmd)
|
||||||
|
for interface, values in interfaces.items():
|
||||||
|
ip, mac = values
|
||||||
|
self.assertIn(f"ether {mac}", out.decode('utf-8'))
|
||||||
|
self.assertIn(f"inet {ip}/", out.decode('utf-8'))
|
Loading…
Reference in New Issue
Block a user