From 5d69a0a62e9294bdcedfa9cc6d6d4d1f7c4fe961 Mon Sep 17 00:00:00 2001 From: Barry Shapira Date: Tue, 11 Dec 2018 22:06:59 -0800 Subject: [PATCH 1/4] Added arguments to creeate a swarm with a custom address pool and subnet size. Signed-off-by: Barry Shapira --- docker/api/swarm.py | 9 +++++++++ docker/models/swarm.py | 10 ++++++++++ tests/integration/api_swarm_test.py | 23 +++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/docker/api/swarm.py b/docker/api/swarm.py index 04595da1..bec3efdf 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -82,6 +82,7 @@ class SwarmApiMixin(object): @utils.minimum_version('1.24') def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', + default_addr_pool=[], subnet_size=24, force_new_cluster=False, swarm_spec=None): """ Initialize a new Swarm using the current connected engine as the first @@ -102,6 +103,12 @@ class SwarmApiMixin(object): or an interface followed by a port number, like ``eth0:4567``. If the port number is omitted, the default swarm listening port is used. Default: '0.0.0.0:2377' + default_addr_pool (list of strings): Default Address Pool specifies + default subnet pools for global scope networks. Each pool + should be specified as a CIDR block, like '10.0.0.0/16'. + Default: [] + subnet_size (int): SubnetSize specifies the subnet size of the + networks created from the default subnet pool. Default: 24 force_new_cluster (bool): Force creating a new Swarm, even if already part of one. Default: False swarm_spec (dict): Configuration settings of the new Swarm. Use @@ -122,6 +129,8 @@ class SwarmApiMixin(object): data = { 'AdvertiseAddr': advertise_addr, 'ListenAddr': listen_addr, + 'DefaultAddrPool': default_addr_pool, + 'SubnetSize': subnet_size, 'ForceNewCluster': force_new_cluster, 'Spec': swarm_spec, } diff --git a/docker/models/swarm.py b/docker/models/swarm.py index 3a02ae37..e39e6f35 100644 --- a/docker/models/swarm.py +++ b/docker/models/swarm.py @@ -34,6 +34,7 @@ class Swarm(Model): get_unlock_key.__doc__ = APIClient.get_unlock_key.__doc__ def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', + default_addr_pool=[], subnet_size=24, force_new_cluster=False, **kwargs): """ Initialize a new swarm on this Engine. @@ -54,6 +55,12 @@ class Swarm(Model): or an interface followed by a port number, like ``eth0:4567``. If the port number is omitted, the default swarm listening port is used. Default: ``0.0.0.0:2377`` + default_addr_pool (list of str): Default Address Pool specifies + default subnet pools for global scope networks. Each pool + should be specified as a CIDR block, like '10.0.0.0/16'. + Default: [] + subnet_size (int): SubnetSize specifies the subnet size of the + networks created from the default subnet pool. Default: 24 force_new_cluster (bool): Force creating a new Swarm, even if already part of one. Default: False task_history_retention_limit (int): Maximum number of tasks @@ -99,6 +106,7 @@ class Swarm(Model): >>> client.swarm.init( advertise_addr='eth0', listen_addr='0.0.0.0:5000', + default_addr_pool=['10.20.0.0/16], subnet_size=24, force_new_cluster=False, snapshot_interval=5000, log_entries_for_slow_followers=1200 ) @@ -107,6 +115,8 @@ class Swarm(Model): init_kwargs = { 'advertise_addr': advertise_addr, 'listen_addr': listen_addr, + 'default_addr_pool': default_addr_pool, + 'subnet_size': subnet_size, 'force_new_cluster': force_new_cluster } init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**kwargs) diff --git a/tests/integration/api_swarm_test.py b/tests/integration/api_swarm_test.py index b58dabc6..5ef651d2 100644 --- a/tests/integration/api_swarm_test.py +++ b/tests/integration/api_swarm_test.py @@ -35,6 +35,29 @@ class SwarmTest(BaseAPIIntegrationTest): version_2 = self.client.inspect_swarm()['Version']['Index'] assert version_2 != version_1 + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool(self): + assert self.init_swarm() + results_1 = self.client.inspect_swarm() + assert results_1['DefaultAddrPool'] is None + assert results_1['SubnetSize'] == 24 + + assert self.init_swarm(default_addr_pool=['2.0.0.0/16'], + force_new_cluster=True) + results_2 = self.client.inspect_swarm() + assert set(results_2['DefaultAddrPool']) == ( + {'2.0.0.0/16'} + ) + assert results_2['SubnetSize'] == 24 + + assert self.init_swarm(default_addr_pool=['2.0.0.0/16', '3.0.0.0/16'], + subnet_size=28, force_new_cluster=True) + results_3 = self.client.inspect_swarm() + assert set(results_3['DefaultAddrPool']) == ( + {'2.0.0.0/16', '3.0.0.0/16'} + ) + assert results_3['SubnetSize'] == 28 + @requires_api_version('1.24') def test_init_already_in_cluster(self): assert self.init_swarm() From 781dc30ad425286ede981d639647cae6afd1a2e9 Mon Sep 17 00:00:00 2001 From: Barry Shapira Date: Fri, 14 Dec 2018 07:30:55 +0000 Subject: [PATCH 2/4] Check API version before setting swarm addr pool. Also corrected a documentation error: the default API version from constants is currently 1.35, not 1.30 as was sometimes listed. Signed-off-by: Barry Shapira Removed accidental whitespace. Signed-off-by: Barry Shapira --- docker/api/swarm.py | 27 ++++++++++++++++++++++++--- docker/constants.py | 3 +++ docker/models/swarm.py | 6 +++--- tests/integration/api_swarm_test.py | 23 ++++++++++++++--------- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/docker/api/swarm.py b/docker/api/swarm.py index bec3efdf..4a39782a 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -1,5 +1,6 @@ import logging from six.moves import http_client +from ..constants import DEFAULT_SWARM_ADDR_POOL, DEFAULT_SWARM_SUBNET_SIZE from .. import errors from .. import types from .. import utils @@ -82,7 +83,7 @@ class SwarmApiMixin(object): @utils.minimum_version('1.24') def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', - default_addr_pool=[], subnet_size=24, + default_addr_pool=None, subnet_size=None, force_new_cluster=False, swarm_spec=None): """ Initialize a new Swarm using the current connected engine as the first @@ -106,9 +107,9 @@ class SwarmApiMixin(object): default_addr_pool (list of strings): Default Address Pool specifies default subnet pools for global scope networks. Each pool should be specified as a CIDR block, like '10.0.0.0/16'. - Default: [] + Default: None subnet_size (int): SubnetSize specifies the subnet size of the - networks created from the default subnet pool. Default: 24 + networks created from the default subnet pool. Default: None force_new_cluster (bool): Force creating a new Swarm, even if already part of one. Default: False swarm_spec (dict): Configuration settings of the new Swarm. Use @@ -124,8 +125,28 @@ class SwarmApiMixin(object): """ url = self._url('/swarm/init') + if swarm_spec is not None and not isinstance(swarm_spec, dict): raise TypeError('swarm_spec must be a dictionary') + + if default_addr_pool is not None: + if utils.version_lt(self._version, '1.39'): + raise errors.InvalidVersion( + 'Address pool is only available for API version >= 1.39' + ) + # subnet_size becomes 0 if not set with default_addr_pool + if subnet_size is None: + subnet_size = DEFAULT_SWARM_SUBNET_SIZE + + if subnet_size is not None: + if utils.version_lt(self._version, '1.39'): + raise errors.InvalidVersion( + 'Subnet size is only available for API version >= 1.39' + ) + # subnet_size is ignored if set without default_addr_pool + if default_addr_pool is None: + default_addr_pool = DEFAULT_SWARM_ADDR_POOL + data = { 'AdvertiseAddr': advertise_addr, 'ListenAddr': listen_addr, diff --git a/docker/constants.py b/docker/constants.py index dcba0de2..4b96e1ce 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -25,3 +25,6 @@ DEFAULT_NUM_POOLS = 25 DEFAULT_NUM_POOLS_SSH = 9 DEFAULT_DATA_CHUNK_SIZE = 1024 * 2048 + +DEFAULT_SWARM_ADDR_POOL = ['10.0.0.0/8'] +DEFAULT_SWARM_SUBNET_SIZE = 24 diff --git a/docker/models/swarm.py b/docker/models/swarm.py index e39e6f35..1106ce26 100644 --- a/docker/models/swarm.py +++ b/docker/models/swarm.py @@ -34,7 +34,7 @@ class Swarm(Model): get_unlock_key.__doc__ = APIClient.get_unlock_key.__doc__ def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', - default_addr_pool=[], subnet_size=24, + default_addr_pool=None, subnet_size=None, force_new_cluster=False, **kwargs): """ Initialize a new swarm on this Engine. @@ -58,9 +58,9 @@ class Swarm(Model): default_addr_pool (list of str): Default Address Pool specifies default subnet pools for global scope networks. Each pool should be specified as a CIDR block, like '10.0.0.0/16'. - Default: [] + Default: None subnet_size (int): SubnetSize specifies the subnet size of the - networks created from the default subnet pool. Default: 24 + networks created from the default subnet pool. Default: None force_new_cluster (bool): Force creating a new Swarm, even if already part of one. Default: False task_history_retention_limit (int): Maximum number of tasks diff --git a/tests/integration/api_swarm_test.py b/tests/integration/api_swarm_test.py index 5ef651d2..41fae578 100644 --- a/tests/integration/api_swarm_test.py +++ b/tests/integration/api_swarm_test.py @@ -37,26 +37,31 @@ class SwarmTest(BaseAPIIntegrationTest): @requires_api_version('1.39') def test_init_swarm_custom_addr_pool(self): + # test defaults assert self.init_swarm() results_1 = self.client.inspect_swarm() - assert results_1['DefaultAddrPool'] is None + assert set(results_1['DefaultAddrPool']) == {'10.0.0.0/8'} assert results_1['SubnetSize'] == 24 - + # test addr pool alone assert self.init_swarm(default_addr_pool=['2.0.0.0/16'], force_new_cluster=True) results_2 = self.client.inspect_swarm() - assert set(results_2['DefaultAddrPool']) == ( - {'2.0.0.0/16'} - ) + assert set(results_2['DefaultAddrPool']) == {'2.0.0.0/16'} assert results_2['SubnetSize'] == 24 - + # test subnet size alone + assert self.init_swarm(subnet_size=26, + force_new_cluster=True) + results_3 = self.client.inspect_swarm() + assert set(results_3['DefaultAddrPool']) == {'10.0.0.0/8'} + assert results_3['SubnetSize'] == 26 + # test both arguments together assert self.init_swarm(default_addr_pool=['2.0.0.0/16', '3.0.0.0/16'], subnet_size=28, force_new_cluster=True) - results_3 = self.client.inspect_swarm() - assert set(results_3['DefaultAddrPool']) == ( + results_4 = self.client.inspect_swarm() + assert set(results_4['DefaultAddrPool']) == ( {'2.0.0.0/16', '3.0.0.0/16'} ) - assert results_3['SubnetSize'] == 28 + assert results_4['SubnetSize'] == 28 @requires_api_version('1.24') def test_init_already_in_cluster(self): From d6cc972cd9955b1aadd373391673314f79e82679 Mon Sep 17 00:00:00 2001 From: Barry Shapira Date: Thu, 3 Jan 2019 17:31:06 -0800 Subject: [PATCH 3/4] Split monolithic integration tests into individual tests. The integration tests require restarting the swarm once for each test. I had done so manually with self.init_swarm(force_new_cluster=True) but that wasn't resetting the swarm state correctly. The usual test teardown procedure cleans up correctly. Signed-off-by: Barry Shapira --- tests/integration/api_swarm_test.py | 49 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tests/integration/api_swarm_test.py b/tests/integration/api_swarm_test.py index 41fae578..37f5fa79 100644 --- a/tests/integration/api_swarm_test.py +++ b/tests/integration/api_swarm_test.py @@ -36,32 +36,33 @@ class SwarmTest(BaseAPIIntegrationTest): assert version_2 != version_1 @requires_api_version('1.39') - def test_init_swarm_custom_addr_pool(self): - # test defaults + def test_init_swarm_custom_addr_pool_defaults(self): assert self.init_swarm() - results_1 = self.client.inspect_swarm() - assert set(results_1['DefaultAddrPool']) == {'10.0.0.0/8'} - assert results_1['SubnetSize'] == 24 - # test addr pool alone - assert self.init_swarm(default_addr_pool=['2.0.0.0/16'], - force_new_cluster=True) - results_2 = self.client.inspect_swarm() - assert set(results_2['DefaultAddrPool']) == {'2.0.0.0/16'} - assert results_2['SubnetSize'] == 24 - # test subnet size alone - assert self.init_swarm(subnet_size=26, - force_new_cluster=True) - results_3 = self.client.inspect_swarm() - assert set(results_3['DefaultAddrPool']) == {'10.0.0.0/8'} - assert results_3['SubnetSize'] == 26 - # test both arguments together + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'10.0.0.0/8'} + assert results['SubnetSize'] == 24 + + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool_only_pool(self): + assert self.init_swarm(default_addr_pool=['2.0.0.0/16']) + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'2.0.0.0/16'} + assert results['SubnetSize'] == 24 + + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool_only_subnet_size(self): + assert self.init_swarm(subnet_size=26) + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'10.0.0.0/8'} + assert results['SubnetSize'] == 26 + + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool_both_args(self): assert self.init_swarm(default_addr_pool=['2.0.0.0/16', '3.0.0.0/16'], - subnet_size=28, force_new_cluster=True) - results_4 = self.client.inspect_swarm() - assert set(results_4['DefaultAddrPool']) == ( - {'2.0.0.0/16', '3.0.0.0/16'} - ) - assert results_4['SubnetSize'] == 28 + subnet_size=28) + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'2.0.0.0/16', '3.0.0.0/16'} + assert results['SubnetSize'] == 28 @requires_api_version('1.24') def test_init_already_in_cluster(self): From 68a271cef4e2afe881d5c4dfe18a97496dc3adb0 Mon Sep 17 00:00:00 2001 From: Hannes Ljungberg Date: Fri, 22 Mar 2019 16:55:10 +0100 Subject: [PATCH 4/4] Fix documentation and order of arguments Following https://github.com/docker/docker-py/pull/2201#pullrequestreview-192571911 Signed-off-by: Hannes Ljungberg Co-authored-by: Hannes Ljungberg Co-authored-by: bluikko <14869000+bluikko@users.noreply.github.com> --- docker/api/swarm.py | 17 ++++++++--------- docker/models/swarm.py | 18 +++++++++--------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docker/api/swarm.py b/docker/api/swarm.py index 4a39782a..e7db5e29 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -83,8 +83,8 @@ class SwarmApiMixin(object): @utils.minimum_version('1.24') def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', - default_addr_pool=None, subnet_size=None, - force_new_cluster=False, swarm_spec=None): + force_new_cluster=False, swarm_spec=None, + default_addr_pool=None, subnet_size=None): """ Initialize a new Swarm using the current connected engine as the first node. @@ -104,17 +104,17 @@ class SwarmApiMixin(object): or an interface followed by a port number, like ``eth0:4567``. If the port number is omitted, the default swarm listening port is used. Default: '0.0.0.0:2377' - default_addr_pool (list of strings): Default Address Pool specifies - default subnet pools for global scope networks. Each pool - should be specified as a CIDR block, like '10.0.0.0/16'. - Default: None - subnet_size (int): SubnetSize specifies the subnet size of the - networks created from the default subnet pool. Default: None force_new_cluster (bool): Force creating a new Swarm, even if already part of one. Default: False swarm_spec (dict): Configuration settings of the new Swarm. Use ``APIClient.create_swarm_spec`` to generate a valid configuration. Default: None + default_addr_pool (list of strings): Default Address Pool specifies + default subnet pools for global scope networks. Each pool + should be specified as a CIDR block, like '10.0.0.0/8'. + Default: None + subnet_size (int): SubnetSize specifies the subnet size of the + networks created from the default subnet pool. Default: None Returns: ``True`` if successful. @@ -125,7 +125,6 @@ class SwarmApiMixin(object): """ url = self._url('/swarm/init') - if swarm_spec is not None and not isinstance(swarm_spec, dict): raise TypeError('swarm_spec must be a dictionary') diff --git a/docker/models/swarm.py b/docker/models/swarm.py index 1106ce26..cb27467d 100644 --- a/docker/models/swarm.py +++ b/docker/models/swarm.py @@ -34,8 +34,8 @@ class Swarm(Model): get_unlock_key.__doc__ = APIClient.get_unlock_key.__doc__ def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', - default_addr_pool=None, subnet_size=None, - force_new_cluster=False, **kwargs): + force_new_cluster=False, default_addr_pool=None, + subnet_size=None, **kwargs): """ Initialize a new swarm on this Engine. @@ -55,14 +55,14 @@ class Swarm(Model): or an interface followed by a port number, like ``eth0:4567``. If the port number is omitted, the default swarm listening port is used. Default: ``0.0.0.0:2377`` + force_new_cluster (bool): Force creating a new Swarm, even if + already part of one. Default: False default_addr_pool (list of str): Default Address Pool specifies default subnet pools for global scope networks. Each pool - should be specified as a CIDR block, like '10.0.0.0/16'. + should be specified as a CIDR block, like '10.0.0.0/8'. Default: None subnet_size (int): SubnetSize specifies the subnet size of the networks created from the default subnet pool. Default: None - force_new_cluster (bool): Force creating a new Swarm, even if - already part of one. Default: False task_history_retention_limit (int): Maximum number of tasks history stored. snapshot_interval (int): Number of logs entries between snapshot. @@ -106,8 +106,8 @@ class Swarm(Model): >>> client.swarm.init( advertise_addr='eth0', listen_addr='0.0.0.0:5000', - default_addr_pool=['10.20.0.0/16], subnet_size=24, - force_new_cluster=False, snapshot_interval=5000, + force_new_cluster=False, default_addr_pool=['10.20.0.0/16], + subnet_size=24, snapshot_interval=5000, log_entries_for_slow_followers=1200 ) @@ -115,9 +115,9 @@ class Swarm(Model): init_kwargs = { 'advertise_addr': advertise_addr, 'listen_addr': listen_addr, + 'force_new_cluster': force_new_cluster, 'default_addr_pool': default_addr_pool, - 'subnet_size': subnet_size, - 'force_new_cluster': force_new_cluster + 'subnet_size': subnet_size } init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**kwargs) self.client.api.init_swarm(**init_kwargs)