network.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import logging
  4. from docker.errors import NotFound
  5. from docker.utils import create_ipam_config
  6. from docker.utils import create_ipam_pool
  7. from .config import ConfigurationError
  8. log = logging.getLogger(__name__)
  9. OPTS_EXCEPTIONS = [
  10. 'com.docker.network.driver.overlay.vxlanid_list',
  11. ]
  12. class Network(object):
  13. def __init__(self, client, project, name, driver=None, driver_opts=None,
  14. ipam=None, external_name=None, internal=False, enable_ipv6=False,
  15. labels=None):
  16. self.client = client
  17. self.project = project
  18. self.name = name
  19. self.driver = driver
  20. self.driver_opts = driver_opts
  21. self.ipam = create_ipam_config_from_dict(ipam)
  22. self.external_name = external_name
  23. self.internal = internal
  24. self.enable_ipv6 = enable_ipv6
  25. self.labels = labels
  26. def ensure(self):
  27. if self.external_name:
  28. try:
  29. self.inspect()
  30. log.debug(
  31. 'Network {0} declared as external. No new '
  32. 'network will be created.'.format(self.name)
  33. )
  34. except NotFound:
  35. raise ConfigurationError(
  36. 'Network {name} declared as external, but could'
  37. ' not be found. Please create the network manually'
  38. ' using `{command} {name}` and try again.'.format(
  39. name=self.external_name,
  40. command='docker network create'
  41. )
  42. )
  43. return
  44. try:
  45. data = self.inspect()
  46. if self.driver and data['Driver'] != self.driver:
  47. raise ConfigurationError(
  48. 'Network "{}" needs to be recreated - driver has changed'
  49. .format(self.full_name))
  50. if data['Options'] != (self.driver_opts or {}):
  51. raise ConfigurationError(
  52. 'Network "{}" needs to be recreated - options have changed'
  53. .format(self.full_name))
  54. except NotFound:
  55. driver_name = 'the default driver'
  56. if self.driver:
  57. driver_name = 'driver "{}"'.format(self.driver)
  58. log.info(
  59. 'Creating network "{}" with {}'
  60. .format(self.full_name, driver_name)
  61. )
  62. self.client.create_network(
  63. name=self.full_name,
  64. driver=self.driver,
  65. options=self.driver_opts,
  66. ipam=self.ipam,
  67. internal=self.internal,
  68. enable_ipv6=self.enable_ipv6,
  69. labels=self.labels,
  70. )
  71. def remove(self):
  72. if self.external_name:
  73. log.info("Network %s is external, skipping", self.full_name)
  74. return
  75. log.info("Removing network {}".format(self.full_name))
  76. self.client.remove_network(self.full_name)
  77. def inspect(self):
  78. return self.client.inspect_network(self.full_name)
  79. @property
  80. def full_name(self):
  81. if self.external_name:
  82. return self.external_name
  83. return '{0}_{1}'.format(self.project, self.name)
  84. def create_ipam_config_from_dict(ipam_dict):
  85. if not ipam_dict:
  86. return None
  87. return create_ipam_config(
  88. driver=ipam_dict.get('driver'),
  89. pool_configs=[
  90. create_ipam_pool(
  91. subnet=config.get('subnet'),
  92. iprange=config.get('ip_range'),
  93. gateway=config.get('gateway'),
  94. aux_addresses=config.get('aux_addresses'),
  95. )
  96. for config in ipam_dict.get('config', [])
  97. ],
  98. )
  99. def check_remote_network_config(remote, local):
  100. if local.driver and remote['Driver'] != local.driver:
  101. raise ConfigurationError(
  102. 'Network "{}" needs to be recreated - driver has changed'
  103. .format(local.full_name)
  104. )
  105. local_opts = local.driver_opts or {}
  106. for k in set.union(set(remote['Options'].keys()), set(local_opts.keys())):
  107. if k in OPTS_EXCEPTIONS:
  108. continue
  109. if remote['Options'].get(k) != local_opts.get(k):
  110. raise ConfigurationError(
  111. 'Network "{}" needs to be recreated - options have changed'
  112. .format(local.full_name)
  113. )
  114. def build_networks(name, config_data, client):
  115. network_config = config_data.networks or {}
  116. networks = {
  117. network_name: Network(
  118. client=client, project=name, name=network_name,
  119. driver=data.get('driver'),
  120. driver_opts=data.get('driver_opts'),
  121. ipam=data.get('ipam'),
  122. external_name=data.get('external_name'),
  123. internal=data.get('internal'),
  124. enable_ipv6=data.get('enable_ipv6'),
  125. labels=data.get('labels'),
  126. )
  127. for network_name, data in network_config.items()
  128. }
  129. if 'default' not in networks:
  130. networks['default'] = Network(client, name, 'default')
  131. return networks
  132. class ProjectNetworks(object):
  133. def __init__(self, networks, use_networking):
  134. self.networks = networks or {}
  135. self.use_networking = use_networking
  136. @classmethod
  137. def from_services(cls, services, networks, use_networking):
  138. service_networks = {
  139. network: networks.get(network)
  140. for service in services
  141. for network in get_network_names_for_service(service)
  142. }
  143. unused = set(networks) - set(service_networks) - {'default'}
  144. if unused:
  145. log.warn(
  146. "Some networks were defined but are not used by any service: "
  147. "{}".format(", ".join(unused)))
  148. return cls(service_networks, use_networking)
  149. def remove(self):
  150. if not self.use_networking:
  151. return
  152. for network in self.networks.values():
  153. try:
  154. network.remove()
  155. except NotFound:
  156. log.warn("Network %s not found.", network.full_name)
  157. def initialize(self):
  158. if not self.use_networking:
  159. return
  160. for network in self.networks.values():
  161. network.ensure()
  162. def get_network_defs_for_service(service_dict):
  163. if 'network_mode' in service_dict:
  164. return {}
  165. networks = service_dict.get('networks', {'default': None})
  166. return dict(
  167. (net, (config or {}))
  168. for net, config in networks.items()
  169. )
  170. def get_network_names_for_service(service_dict):
  171. return get_network_defs_for_service(service_dict).keys()
  172. def get_networks(service_dict, network_definitions):
  173. networks = {}
  174. for name, netdef in get_network_defs_for_service(service_dict).items():
  175. network = network_definitions.get(name)
  176. if network:
  177. networks[network.full_name] = netdef
  178. else:
  179. raise ConfigurationError(
  180. 'Service "{}" uses an undefined network "{}"'
  181. .format(service_dict['name'], name))
  182. return networks