diff --git a/docker/api/swarm.py b/docker/api/swarm.py index bab91ee453..0bd6d12816 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -84,7 +84,8 @@ def get_unlock_key(self): @utils.minimum_version('1.24') def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', force_new_cluster=False, swarm_spec=None, - default_addr_pool=None, subnet_size=None): + default_addr_pool=None, subnet_size=None, + data_path_addr=None): """ Initialize a new Swarm using the current connected engine as the first node. @@ -115,6 +116,8 @@ def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', Default: None subnet_size (int): SubnetSize specifies the subnet size of the networks created from the default subnet pool. Default: None + data_path_addr (string): Address or interface to use for data path + traffic. For example, 192.168.1.1, or an interface, like eth0. Returns: ``True`` if successful. @@ -154,6 +157,15 @@ def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', 'ForceNewCluster': force_new_cluster, 'Spec': swarm_spec, } + + if data_path_addr is not None: + if utils.version_lt(self._version, '1.30'): + raise errors.InvalidVersion( + 'Data address path is only available for ' + 'API version >= 1.30' + ) + data['DataPathAddr'] = data_path_addr + response = self._post_json(url, data=data) self._raise_for_status(response) return True @@ -194,7 +206,7 @@ def inspect_node(self, node_id): @utils.minimum_version('1.24') def join_swarm(self, remote_addrs, join_token, listen_addr='0.0.0.0:2377', - advertise_addr=None): + advertise_addr=None, data_path_addr=None): """ Make this Engine join a swarm that has already been created. @@ -213,6 +225,8 @@ def join_swarm(self, remote_addrs, join_token, listen_addr='0.0.0.0:2377', the port number from the listen address is used. If AdvertiseAddr is not specified, it will be automatically detected when possible. Default: ``None`` + data_path_addr (string): Address or interface to use for data path + traffic. For example, 192.168.1.1, or an interface, like eth0. Returns: ``True`` if the request went through. @@ -222,11 +236,20 @@ def join_swarm(self, remote_addrs, join_token, listen_addr='0.0.0.0:2377', If the server returns an error. """ data = { - "RemoteAddrs": remote_addrs, - "ListenAddr": listen_addr, - "JoinToken": join_token, - "AdvertiseAddr": advertise_addr, + 'RemoteAddrs': remote_addrs, + 'ListenAddr': listen_addr, + 'JoinToken': join_token, + 'AdvertiseAddr': advertise_addr, } + + if data_path_addr is not None: + if utils.version_lt(self._version, '1.30'): + raise errors.InvalidVersion( + 'Data address path is only available for ' + 'API version >= 1.30' + ) + data['DataPathAddr'] = data_path_addr + url = self._url('/swarm/join') response = self._post_json(url, data=data) self._raise_for_status(response) diff --git a/docker/models/swarm.py b/docker/models/swarm.py index cb27467d32..386d23d3fe 100644 --- a/docker/models/swarm.py +++ b/docker/models/swarm.py @@ -35,7 +35,7 @@ def get_unlock_key(self): def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', force_new_cluster=False, default_addr_pool=None, - subnet_size=None, **kwargs): + subnet_size=None, data_path_addr=None, **kwargs): """ Initialize a new swarm on this Engine. @@ -63,6 +63,8 @@ def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', Default: None subnet_size (int): SubnetSize specifies the subnet size of the networks created from the default subnet pool. Default: None + data_path_addr (string): Address or interface to use for data path + traffic. For example, 192.168.1.1, or an interface, like eth0. task_history_retention_limit (int): Maximum number of tasks history stored. snapshot_interval (int): Number of logs entries between snapshot. @@ -117,7 +119,8 @@ def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', 'listen_addr': listen_addr, 'force_new_cluster': force_new_cluster, 'default_addr_pool': default_addr_pool, - 'subnet_size': subnet_size + 'subnet_size': subnet_size, + 'data_path_addr': data_path_addr, } init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**kwargs) self.client.api.init_swarm(**init_kwargs) diff --git a/tests/integration/api_swarm_test.py b/tests/integration/api_swarm_test.py index 37f5fa7959..5e9aea1e1f 100644 --- a/tests/integration/api_swarm_test.py +++ b/tests/integration/api_swarm_test.py @@ -233,3 +233,7 @@ def test_remove_main_node(self): self.client.remove_node(node_id, True) assert e.value.response.status_code >= 400 + + @requires_api_version('1.30') + def test_init_swarm_data_path_addr(self): + assert self.init_swarm(data_path_addr='eth0') diff --git a/tests/integration/models_swarm_test.py b/tests/integration/models_swarm_test.py index f39f0d34cf..6c1836dc60 100644 --- a/tests/integration/models_swarm_test.py +++ b/tests/integration/models_swarm_test.py @@ -31,3 +31,15 @@ def test_init_update_leave(self): cm.value.response.status_code == 406 or cm.value.response.status_code == 503 ) + + def test_join_on_already_joined_swarm(self): + client = docker.from_env(version=TEST_API_VERSION) + client.swarm.init() + join_token = client.swarm.attrs['JoinTokens']['Manager'] + with pytest.raises(docker.errors.APIError) as cm: + client.swarm.join( + remote_addrs=['127.0.0.1'], + join_token=join_token, + ) + assert cm.value.response.status_code == 503 + assert 'This node is already part of a swarm.' in cm.value.explanation