Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

swarm.py 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. import logging
  2. import http.client as http_client
  3. from ..constants import DEFAULT_SWARM_ADDR_POOL, DEFAULT_SWARM_SUBNET_SIZE
  4. from .. import errors
  5. from .. import types
  6. from .. import utils
  7. log = logging.getLogger(__name__)
  8. class SwarmApiMixin:
  9. def create_swarm_spec(self, *args, **kwargs):
  10. """
  11. Create a :py:class:`docker.types.SwarmSpec` instance that can be used
  12. as the ``swarm_spec`` argument in
  13. :py:meth:`~docker.api.swarm.SwarmApiMixin.init_swarm`.
  14. Args:
  15. task_history_retention_limit (int): Maximum number of tasks
  16. history stored.
  17. snapshot_interval (int): Number of logs entries between snapshot.
  18. keep_old_snapshots (int): Number of snapshots to keep beyond the
  19. current snapshot.
  20. log_entries_for_slow_followers (int): Number of log entries to
  21. keep around to sync up slow followers after a snapshot is
  22. created.
  23. heartbeat_tick (int): Amount of ticks (in seconds) between each
  24. heartbeat.
  25. election_tick (int): Amount of ticks (in seconds) needed without a
  26. leader to trigger a new election.
  27. dispatcher_heartbeat_period (int): The delay for an agent to send
  28. a heartbeat to the dispatcher.
  29. node_cert_expiry (int): Automatic expiry for nodes certificates.
  30. external_cas (:py:class:`list`): Configuration for forwarding
  31. signing requests to an external certificate authority. Use
  32. a list of :py:class:`docker.types.SwarmExternalCA`.
  33. name (string): Swarm's name
  34. labels (dict): User-defined key/value metadata.
  35. signing_ca_cert (str): The desired signing CA certificate for all
  36. swarm node TLS leaf certificates, in PEM format.
  37. signing_ca_key (str): The desired signing CA key for all swarm
  38. node TLS leaf certificates, in PEM format.
  39. ca_force_rotate (int): An integer whose purpose is to force swarm
  40. to generate a new signing CA certificate and key, if none have
  41. been specified.
  42. autolock_managers (boolean): If set, generate a key and use it to
  43. lock data stored on the managers.
  44. log_driver (DriverConfig): The default log driver to use for tasks
  45. created in the orchestrator.
  46. Returns:
  47. :py:class:`docker.types.SwarmSpec`
  48. Raises:
  49. :py:class:`docker.errors.APIError`
  50. If the server returns an error.
  51. Example:
  52. >>> spec = client.api.create_swarm_spec(
  53. snapshot_interval=5000, log_entries_for_slow_followers=1200
  54. )
  55. >>> client.api.init_swarm(
  56. advertise_addr='eth0', listen_addr='0.0.0.0:5000',
  57. force_new_cluster=False, swarm_spec=spec
  58. )
  59. """
  60. ext_ca = kwargs.pop('external_ca', None)
  61. if ext_ca:
  62. kwargs['external_cas'] = [ext_ca]
  63. return types.SwarmSpec(self._version, *args, **kwargs)
  64. @utils.minimum_version('1.24')
  65. def get_unlock_key(self):
  66. """
  67. Get the unlock key for this Swarm manager.
  68. Returns:
  69. A ``dict`` containing an ``UnlockKey`` member
  70. """
  71. return self._result(self._get(self._url('/swarm/unlockkey')), True)
  72. @utils.minimum_version('1.24')
  73. def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377',
  74. force_new_cluster=False, swarm_spec=None,
  75. default_addr_pool=None, subnet_size=None,
  76. data_path_addr=None, data_path_port=None):
  77. """
  78. Initialize a new Swarm using the current connected engine as the first
  79. node.
  80. Args:
  81. advertise_addr (string): Externally reachable address advertised
  82. to other nodes. This can either be an address/port combination
  83. in the form ``192.168.1.1:4567``, or an interface followed by a
  84. port number, like ``eth0:4567``. If the port number is omitted,
  85. the port number from the listen address is used. If
  86. ``advertise_addr`` is not specified, it will be automatically
  87. detected when possible. Default: None
  88. listen_addr (string): Listen address used for inter-manager
  89. communication, as well as determining the networking interface
  90. used for the VXLAN Tunnel Endpoint (VTEP). This can either be
  91. an address/port combination in the form ``192.168.1.1:4567``,
  92. or an interface followed by a port number, like ``eth0:4567``.
  93. If the port number is omitted, the default swarm listening port
  94. is used. Default: '0.0.0.0:2377'
  95. force_new_cluster (bool): Force creating a new Swarm, even if
  96. already part of one. Default: False
  97. swarm_spec (dict): Configuration settings of the new Swarm. Use
  98. ``APIClient.create_swarm_spec`` to generate a valid
  99. configuration. Default: None
  100. default_addr_pool (list of strings): Default Address Pool specifies
  101. default subnet pools for global scope networks. Each pool
  102. should be specified as a CIDR block, like '10.0.0.0/8'.
  103. Default: None
  104. subnet_size (int): SubnetSize specifies the subnet size of the
  105. networks created from the default subnet pool. Default: None
  106. data_path_addr (string): Address or interface to use for data path
  107. traffic. For example, 192.168.1.1, or an interface, like eth0.
  108. data_path_port (int): Port number to use for data path traffic.
  109. Acceptable port range is 1024 to 49151. If set to ``None`` or
  110. 0, the default port 4789 will be used. Default: None
  111. Returns:
  112. (str): The ID of the created node.
  113. Raises:
  114. :py:class:`docker.errors.APIError`
  115. If the server returns an error.
  116. """
  117. url = self._url('/swarm/init')
  118. if swarm_spec is not None and not isinstance(swarm_spec, dict):
  119. raise TypeError('swarm_spec must be a dictionary')
  120. if default_addr_pool is not None:
  121. if utils.version_lt(self._version, '1.39'):
  122. raise errors.InvalidVersion(
  123. 'Address pool is only available for API version >= 1.39'
  124. )
  125. # subnet_size becomes 0 if not set with default_addr_pool
  126. if subnet_size is None:
  127. subnet_size = DEFAULT_SWARM_SUBNET_SIZE
  128. if subnet_size is not None:
  129. if utils.version_lt(self._version, '1.39'):
  130. raise errors.InvalidVersion(
  131. 'Subnet size is only available for API version >= 1.39'
  132. )
  133. # subnet_size is ignored if set without default_addr_pool
  134. if default_addr_pool is None:
  135. default_addr_pool = DEFAULT_SWARM_ADDR_POOL
  136. data = {
  137. 'AdvertiseAddr': advertise_addr,
  138. 'ListenAddr': listen_addr,
  139. 'DefaultAddrPool': default_addr_pool,
  140. 'SubnetSize': subnet_size,
  141. 'ForceNewCluster': force_new_cluster,
  142. 'Spec': swarm_spec,
  143. }
  144. if data_path_addr is not None:
  145. if utils.version_lt(self._version, '1.30'):
  146. raise errors.InvalidVersion(
  147. 'Data address path is only available for '
  148. 'API version >= 1.30'
  149. )
  150. data['DataPathAddr'] = data_path_addr
  151. if data_path_port is not None:
  152. if utils.version_lt(self._version, '1.40'):
  153. raise errors.InvalidVersion(
  154. 'Data path port is only available for '
  155. 'API version >= 1.40'
  156. )
  157. data['DataPathPort'] = data_path_port
  158. response = self._post_json(url, data=data)
  159. return self._result(response, json=True)
  160. @utils.minimum_version('1.24')
  161. def inspect_swarm(self):
  162. """
  163. Retrieve low-level information about the current swarm.
  164. Returns:
  165. A dictionary containing data about the swarm.
  166. Raises:
  167. :py:class:`docker.errors.APIError`
  168. If the server returns an error.
  169. """
  170. url = self._url('/swarm')
  171. return self._result(self._get(url), True)
  172. @utils.check_resource('node_id')
  173. @utils.minimum_version('1.24')
  174. def inspect_node(self, node_id):
  175. """
  176. Retrieve low-level information about a swarm node
  177. Args:
  178. node_id (string): ID of the node to be inspected.
  179. Returns:
  180. A dictionary containing data about this node.
  181. Raises:
  182. :py:class:`docker.errors.APIError`
  183. If the server returns an error.
  184. """
  185. url = self._url('/nodes/{0}', node_id)
  186. return self._result(self._get(url), True)
  187. @utils.minimum_version('1.24')
  188. def join_swarm(self, remote_addrs, join_token, listen_addr='0.0.0.0:2377',
  189. advertise_addr=None, data_path_addr=None):
  190. """
  191. Make this Engine join a swarm that has already been created.
  192. Args:
  193. remote_addrs (:py:class:`list`): Addresses of one or more manager
  194. nodes already participating in the Swarm to join.
  195. join_token (string): Secret token for joining this Swarm.
  196. listen_addr (string): Listen address used for inter-manager
  197. communication if the node gets promoted to manager, as well as
  198. determining the networking interface used for the VXLAN Tunnel
  199. Endpoint (VTEP). Default: ``'0.0.0.0:2377``
  200. advertise_addr (string): Externally reachable address advertised
  201. to other nodes. This can either be an address/port combination
  202. in the form ``192.168.1.1:4567``, or an interface followed by a
  203. port number, like ``eth0:4567``. If the port number is omitted,
  204. the port number from the listen address is used. If
  205. AdvertiseAddr is not specified, it will be automatically
  206. detected when possible. Default: ``None``
  207. data_path_addr (string): Address or interface to use for data path
  208. traffic. For example, 192.168.1.1, or an interface, like eth0.
  209. Returns:
  210. ``True`` if the request went through.
  211. Raises:
  212. :py:class:`docker.errors.APIError`
  213. If the server returns an error.
  214. """
  215. data = {
  216. 'RemoteAddrs': remote_addrs,
  217. 'ListenAddr': listen_addr,
  218. 'JoinToken': join_token,
  219. 'AdvertiseAddr': advertise_addr,
  220. }
  221. if data_path_addr is not None:
  222. if utils.version_lt(self._version, '1.30'):
  223. raise errors.InvalidVersion(
  224. 'Data address path is only available for '
  225. 'API version >= 1.30'
  226. )
  227. data['DataPathAddr'] = data_path_addr
  228. url = self._url('/swarm/join')
  229. response = self._post_json(url, data=data)
  230. self._raise_for_status(response)
  231. return True
  232. @utils.minimum_version('1.24')
  233. def leave_swarm(self, force=False):
  234. """
  235. Leave a swarm.
  236. Args:
  237. force (bool): Leave the swarm even if this node is a manager.
  238. Default: ``False``
  239. Returns:
  240. ``True`` if the request went through.
  241. Raises:
  242. :py:class:`docker.errors.APIError`
  243. If the server returns an error.
  244. """
  245. url = self._url('/swarm/leave')
  246. response = self._post(url, params={'force': force})
  247. # Ignore "this node is not part of a swarm" error
  248. if force and response.status_code == http_client.NOT_ACCEPTABLE:
  249. return True
  250. # FIXME: Temporary workaround for 1.13.0-rc bug
  251. # https://github.com/docker/docker/issues/29192
  252. if force and response.status_code == http_client.SERVICE_UNAVAILABLE:
  253. return True
  254. self._raise_for_status(response)
  255. return True
  256. @utils.minimum_version('1.24')
  257. def nodes(self, filters=None):
  258. """
  259. List swarm nodes.
  260. Args:
  261. filters (dict): Filters to process on the nodes list. Valid
  262. filters: ``id``, ``name``, ``membership`` and ``role``.
  263. Default: ``None``
  264. Returns:
  265. A list of dictionaries containing data about each swarm node.
  266. Raises:
  267. :py:class:`docker.errors.APIError`
  268. If the server returns an error.
  269. """
  270. url = self._url('/nodes')
  271. params = {}
  272. if filters:
  273. params['filters'] = utils.convert_filters(filters)
  274. return self._result(self._get(url, params=params), True)
  275. @utils.check_resource('node_id')
  276. @utils.minimum_version('1.24')
  277. def remove_node(self, node_id, force=False):
  278. """
  279. Remove a node from the swarm.
  280. Args:
  281. node_id (string): ID of the node to be removed.
  282. force (bool): Force remove an active node. Default: `False`
  283. Raises:
  284. :py:class:`docker.errors.NotFound`
  285. If the node referenced doesn't exist in the swarm.
  286. :py:class:`docker.errors.APIError`
  287. If the server returns an error.
  288. Returns:
  289. `True` if the request was successful.
  290. """
  291. url = self._url('/nodes/{0}', node_id)
  292. params = {
  293. 'force': force
  294. }
  295. res = self._delete(url, params=params)
  296. self._raise_for_status(res)
  297. return True
  298. @utils.minimum_version('1.24')
  299. def unlock_swarm(self, key):
  300. """
  301. Unlock a locked swarm.
  302. Args:
  303. key (string): The unlock key as provided by
  304. :py:meth:`get_unlock_key`
  305. Raises:
  306. :py:class:`docker.errors.InvalidArgument`
  307. If the key argument is in an incompatible format
  308. :py:class:`docker.errors.APIError`
  309. If the server returns an error.
  310. Returns:
  311. `True` if the request was successful.
  312. Example:
  313. >>> key = client.api.get_unlock_key()
  314. >>> client.unlock_swarm(key)
  315. """
  316. if isinstance(key, dict):
  317. if 'UnlockKey' not in key:
  318. raise errors.InvalidArgument('Invalid unlock key format')
  319. else:
  320. key = {'UnlockKey': key}
  321. url = self._url('/swarm/unlock')
  322. res = self._post_json(url, data=key)
  323. self._raise_for_status(res)
  324. return True
  325. @utils.minimum_version('1.24')
  326. def update_node(self, node_id, version, node_spec=None):
  327. """
  328. Update the node's configuration
  329. Args:
  330. node_id (string): ID of the node to be updated.
  331. version (int): The version number of the node object being
  332. updated. This is required to avoid conflicting writes.
  333. node_spec (dict): Configuration settings to update. Any values
  334. not provided will be removed. Default: ``None``
  335. Returns:
  336. `True` if the request went through.
  337. Raises:
  338. :py:class:`docker.errors.APIError`
  339. If the server returns an error.
  340. Example:
  341. >>> node_spec = {'Availability': 'active',
  342. 'Name': 'node-name',
  343. 'Role': 'manager',
  344. 'Labels': {'foo': 'bar'}
  345. }
  346. >>> client.api.update_node(node_id='24ifsmvkjbyhk', version=8,
  347. node_spec=node_spec)
  348. """
  349. url = self._url('/nodes/{0}/update?version={1}', node_id, str(version))
  350. res = self._post_json(url, data=node_spec)
  351. self._raise_for_status(res)
  352. return True
  353. @utils.minimum_version('1.24')
  354. def update_swarm(self, version, swarm_spec=None,
  355. rotate_worker_token=False,
  356. rotate_manager_token=False,
  357. rotate_manager_unlock_key=False):
  358. """
  359. Update the Swarm's configuration
  360. Args:
  361. version (int): The version number of the swarm object being
  362. updated. This is required to avoid conflicting writes.
  363. swarm_spec (dict): Configuration settings to update. Use
  364. :py:meth:`~docker.api.swarm.SwarmApiMixin.create_swarm_spec` to
  365. generate a valid configuration. Default: ``None``.
  366. rotate_worker_token (bool): Rotate the worker join token. Default:
  367. ``False``.
  368. rotate_manager_token (bool): Rotate the manager join token.
  369. Default: ``False``.
  370. rotate_manager_unlock_key (bool): Rotate the manager unlock key.
  371. Default: ``False``.
  372. Returns:
  373. ``True`` if the request went through.
  374. Raises:
  375. :py:class:`docker.errors.APIError`
  376. If the server returns an error.
  377. """
  378. url = self._url('/swarm/update')
  379. params = {
  380. 'rotateWorkerToken': rotate_worker_token,
  381. 'rotateManagerToken': rotate_manager_token,
  382. 'version': version
  383. }
  384. if rotate_manager_unlock_key:
  385. if utils.version_lt(self._version, '1.25'):
  386. raise errors.InvalidVersion(
  387. 'Rotate manager unlock key '
  388. 'is only available for API version >= 1.25'
  389. )
  390. params['rotateManagerUnlockKey'] = rotate_manager_unlock_key
  391. response = self._post_json(url, data=swarm_spec, params=params)
  392. self._raise_for_status(response)
  393. return True