diff --git a/docker/api/network.py b/docker/api/network.py index 4dec3f52..a35f0a40 100644 --- a/docker/api/network.py +++ b/docker/api/network.py @@ -1,7 +1,7 @@ import json from ..errors import InvalidVersion -from ..utils import check_resource, minimum_version, normalize_links +from ..utils import check_resource, minimum_version from ..utils import version_lt @@ -63,26 +63,12 @@ class NetworkApiMixin(object): aliases=None, links=None): data = { "Container": container, - "EndpointConfig": { - "Aliases": aliases, - "Links": normalize_links(links) if links else None, - }, + "EndpointConfig": self.create_endpoint_config( + aliases=aliases, links=links, ipv4_address=ipv4_address, + ipv6_address=ipv6_address + ), } - # IPv4 or IPv6 or neither: - if ipv4_address or ipv6_address: - if version_lt(self._version, '1.22'): - raise InvalidVersion('IP address assignment is not ' - 'supported in API version < 1.22') - - data['EndpointConfig']['IPAMConfig'] = dict() - if ipv4_address: - data['EndpointConfig']['IPAMConfig']['IPv4Address'] = \ - ipv4_address - if ipv6_address: - data['EndpointConfig']['IPAMConfig']['IPv6Address'] = \ - ipv6_address - url = self._url("/networks/{0}/connect", net_id) res = self._post_json(url, data=data) self._raise_for_status(res) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index caa98314..ee48bbac 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -813,19 +813,30 @@ def create_networking_config(endpoints_config=None): return networking_config -def create_endpoint_config(version, aliases=None, links=None): +def create_endpoint_config(version, aliases=None, links=None, + ipv4_address=None, ipv6_address=None): + if version_lt(version, '1.22'): + raise errors.InvalidVersion( + 'Endpoint config is not supported for API version < 1.22' + ) endpoint_config = {} if aliases: - if version_lt(version, '1.22'): - raise host_config_version_error('endpoint_config.aliases', '1.22') endpoint_config["Aliases"] = aliases if links: - if version_lt(version, '1.22'): - raise host_config_version_error('endpoint_config.links', '1.22') endpoint_config["Links"] = normalize_links(links) + ipam_config = {} + if ipv4_address: + ipam_config['IPv4Address'] = ipv4_address + + if ipv6_address: + ipam_config['IPv6Address'] = ipv6_address + + if ipam_config: + endpoint_config['IPAMConfig'] = ipam_config + return endpoint_config diff --git a/docs/api.md b/docs/api.md index e7ab80f7..51b6e271 100644 --- a/docs/api.md +++ b/docs/api.md @@ -242,6 +242,7 @@ from. Optionally a single string joining container id's with commas * labels (dict or list): A dictionary of name-value labels (e.g. `{"label1": "value1", "label2": "value2"}`) or a list of names of labels to set with empty values (e.g. `["label1", "label2"]`) * volume_driver (str): The name of a volume driver/plugin. * stop_signal (str): The stop signal to use to stop the container (e.g. `SIGINT`). +* networking_config (dict): A [NetworkingConfig](networks.md) dictionary **Returns** (dict): A dictionary with an image 'Id' key and a 'Warnings' key. diff --git a/docs/networks.md b/docs/networks.md index 5a14d38d..ec45e1c5 100644 --- a/docs/networks.md +++ b/docs/networks.md @@ -1,27 +1,176 @@ # Using Networks +## Network creation + With the release of Docker 1.9 you can now manage custom networks. -Here you can see how to create a network named ```network1``` using the ```bridge``` driver +Here you can see how to create a network named `network1` using +the `bridge` driver ```python docker_client.create_network("network1", driver="bridge") ``` -You can also create more advanced networks with custom IPAM configurations. For example, -setting the subnet to ```192.168.52.0/24``` and gateway to ```192.168.52.254``` +You can also create more advanced networks with custom IPAM configurations. +For example, setting the subnet to `192.168.52.0/24` and gateway address +to `192.168.52.254` ```python - -ipam_config = docker.utils.create_ipam_config(subnet='192.168.52.0/24', gateway='192.168.52.254') +ipam_pool = docker.utils.create_ipam_pool( + subnet='192.168.52.0/24', + gateway='192.168.52.254' +) +ipam_config = docker.utils.create_ipam_config( + pool_configs=[ipam_pool] +) docker_client.create_network("network1", driver="bridge", ipam=ipam_config) ``` -With Docker 1.10 you can now also create internal networks +By default, when you connect a container to an overlay network, Docker also +connects a bridge network to it to provide external connectivity. If you want +to create an externally isolated overlay network, with Docker 1.10 you can +create an internal network. ```python docker_client.create_network("network1", driver="bridge", internal=True) ``` + +## Container network configuration + +In order to specify which network a container will be connected to, and +additional configuration, use the `networking_config` parameter in +`Client.create_container`. Note that at the time of creation, you can +only connect a container to a single network. Later on, you may create more +connections using `Client.connect_container_to_network`. + + +```python +networking_config = docker_client.create_networking_config({ + 'network1': docker_client.create_endpoint_config( + ipv4_address='172.28.0.124', + aliases=['foo', 'bar'], + links=['container2'] + ) +}) + +ctnr = docker_client.create_container( + img, command, networking_config=networking_config +) + +``` + +## Network API documentation + +### Client.create_networking_config + +Create a networking config dictionary to be used as the `networking_config` +parameter in `Client.create_container_config` + +**Params**: + +* endpoints_config (dict): A dictionary of `network_name -> endpoint_config` + relationships. Values should be endpoint config dictionaries created by + `Client.create_endpoint_config`. Defaults to `None` (default config). + +**Returns** A networking config dictionary. + +```python + +docker_client.create_network('network1') + +networking_config = docker_client.create_networking_config({ + 'network1': docker_client.create_endpoint_config() +}) + +container = docker_client.create_container( + img, command, networking_config=networking_config +) +``` + + +### Client.create_endpoint_config + +Create an endpoint config dictionary to be used with +`Client.create_networking_config`. + +**Params**: + +* aliases (list): A list of aliases for this endpoint. Names in that list can + be used within the network to reach the container. Defaults to `None`. +* links (list): A list of links for this endpoint. Containers declared in this + list will be [linked](https://docs.docker.com/engine/userguide/networking/work-with-networks/#linking-containers-in-user-defined-networks) + to this container. Defaults to `None`. +* ipv4_address (str): The IP address of this container on the network, + using the IPv4 protocol. Defaults to `None`. +* ipv6_address (str): The IP address of this container on the network, + using the IPv6 protocol. Defaults to `None`. + +**Returns** An endpoint config dictionary. + +```python +endpoint_config = docker_client.create_endpoint_config( + aliases=['web', 'app'], + links=['app_db'], + ipv4_address='132.65.0.123' +) + +docker_client.create_network('network1') +networking_config = docker_client.create_networking_config({ + 'network1': endpoint_config +}) +container = docker_client.create_container( + img, command, networking_config=networking_config +) +``` +### docker.utils.create_ipam_config + +Create an IPAM (IP Address Management) config dictionary to be used with +`Client.create_network`. + + +**Params**: + +* driver (str): The IPAM driver to use. Defaults to `'default'`. +* pool_configs (list): A list of pool configuration dictionaries as created + by `docker.utils.create_ipam_pool`. Defaults to empty list. + +**Returns** An IPAM config dictionary + +```python +ipam_config = docker.utils.create_ipam_config(driver='default') +network = docker_client.create_network('network1', ipam=ipam_config) +``` + +### docker.utils.create_ipam_pool + +Create an IPAM pool config dictionary to be added to the `pool_configs` param +in `docker.utils.create_ipam_config`. + +**Params**: + +* subnet (str): Custom subnet for this IPAM pool using the CIDR notation. + Defaults to `None`. +* iprange (str): Custom IP range for endpoints in this IPAM pool using the + CIDR notation. Defaults to `None`. +* gateway (str): Custom IP address for the pool's gateway. +* aux_addresses (dict): A dictionary of `key -> ip_address` relationships + specifying auxiliary addresses that need to be allocated by the + IPAM driver. + +**Returns** An IPAM pool config dictionary + +```python +ipam_pool = docker.utils.create_ipam_pool( + subnet='124.42.0.0/16', + iprange='124.42.0.0/24', + gateway='124.42.0.254', + aux_addresses={ + 'reserved1': '124.42.1.1' + } +) +ipam_config = docker.utils.create_ipam_config(pool_configs=[ipam_pool]) +network = docker_client.create_network('network1', ipam=ipam_config) +``` diff --git a/tests/integration/network_test.py b/tests/integration/network_test.py index 179ae88e..26d27a5a 100644 --- a/tests/integration/network_test.py +++ b/tests/integration/network_test.py @@ -185,7 +185,66 @@ class TestNetworks(helpers.BaseTestCase): container_data = self.client.inspect_container(container) self.assertEqual( container_data['NetworkSettings']['Networks'][net_name]['Aliases'], - ['foo', 'bar']) + ['foo', 'bar'] + ) + + @requires_api_version('1.22') + def test_create_with_ipv4_address(self): + net_name, net_id = self.create_network( + ipam=create_ipam_config( + driver='default', + pool_configs=[create_ipam_pool(subnet="132.124.0.0/16")], + ), + ) + container = self.client.create_container( + image='busybox', command='top', + host_config=self.client.create_host_config(network_mode=net_name), + networking_config=self.client.create_networking_config({ + net_name: self.client.create_endpoint_config( + ipv4_address='132.124.0.23' + ) + }) + ) + self.tmp_containers.append(container) + self.client.start(container) + + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data[ + 'NetworkSettings']['Networks'][net_name]['IPAMConfig'][ + 'IPv4Address' + ], + '132.124.0.23' + ) + + @requires_api_version('1.22') + def test_create_with_ipv6_address(self): + net_name, net_id = self.create_network( + ipam=create_ipam_config( + driver='default', + pool_configs=[create_ipam_pool(subnet="2001:389::1/64")], + ), + ) + container = self.client.create_container( + image='busybox', command='top', + host_config=self.client.create_host_config(network_mode=net_name), + networking_config=self.client.create_networking_config({ + net_name: self.client.create_endpoint_config( + ipv6_address='2001:389::f00d' + ) + }) + ) + self.tmp_containers.append(container) + self.client.start(container) + + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data[ + 'NetworkSettings']['Networks'][net_name]['IPAMConfig'][ + 'IPv6Address' + ], + '2001:389::f00d' + ) @requires_api_version('1.22') def test_create_with_links(self):