network.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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. class Network(object):
  10. def __init__(self, client, project, name, driver=None, driver_opts=None,
  11. ipam=None, external_name=None, aliases=None):
  12. self.client = client
  13. self.project = project
  14. self.name = name
  15. self.driver = driver
  16. self.driver_opts = driver_opts
  17. self.ipam = create_ipam_config_from_dict(ipam)
  18. self.external_name = external_name
  19. self.aliases = aliases or []
  20. def ensure(self):
  21. if self.external_name:
  22. try:
  23. self.inspect()
  24. log.debug(
  25. 'Network {0} declared as external. No new '
  26. 'network will be created.'.format(self.name)
  27. )
  28. except NotFound:
  29. raise ConfigurationError(
  30. 'Network {name} declared as external, but could'
  31. ' not be found. Please create the network manually'
  32. ' using `{command} {name}` and try again.'.format(
  33. name=self.external_name,
  34. command='docker network create'
  35. )
  36. )
  37. return
  38. try:
  39. data = self.inspect()
  40. if self.driver and data['Driver'] != self.driver:
  41. raise ConfigurationError(
  42. 'Network "{}" needs to be recreated - driver has changed'
  43. .format(self.full_name))
  44. if data['Options'] != (self.driver_opts or {}):
  45. raise ConfigurationError(
  46. 'Network "{}" needs to be recreated - options have changed'
  47. .format(self.full_name))
  48. except NotFound:
  49. driver_name = 'the default driver'
  50. if self.driver:
  51. driver_name = 'driver "{}"'.format(self.driver)
  52. log.info(
  53. 'Creating network "{}" with {}'
  54. .format(self.full_name, driver_name)
  55. )
  56. self.client.create_network(
  57. name=self.full_name,
  58. driver=self.driver,
  59. options=self.driver_opts,
  60. ipam=self.ipam,
  61. )
  62. def remove(self):
  63. if self.external_name:
  64. log.info("Network %s is external, skipping", self.full_name)
  65. return
  66. log.info("Removing network {}".format(self.full_name))
  67. self.client.remove_network(self.full_name)
  68. def inspect(self):
  69. return self.client.inspect_network(self.full_name)
  70. @property
  71. def full_name(self):
  72. if self.external_name:
  73. return self.external_name
  74. return '{0}_{1}'.format(self.project, self.name)
  75. def create_ipam_config_from_dict(ipam_dict):
  76. if not ipam_dict:
  77. return None
  78. return create_ipam_config(
  79. driver=ipam_dict.get('driver'),
  80. pool_configs=[
  81. create_ipam_pool(
  82. subnet=config.get('subnet'),
  83. iprange=config.get('ip_range'),
  84. gateway=config.get('gateway'),
  85. aux_addresses=config.get('aux_addresses'),
  86. )
  87. for config in ipam_dict.get('config', [])
  88. ],
  89. )
  90. def build_networks(name, config_data, client):
  91. network_config = config_data.networks or {}
  92. networks = {
  93. network_name: Network(
  94. client=client, project=name, name=network_name,
  95. driver=data.get('driver'),
  96. driver_opts=data.get('driver_opts'),
  97. ipam=data.get('ipam'),
  98. external_name=data.get('external_name'),
  99. )
  100. for network_name, data in network_config.items()
  101. }
  102. if 'default' not in networks:
  103. networks['default'] = Network(client, name, 'default')
  104. return networks
  105. class ProjectNetworks(object):
  106. def __init__(self, networks, use_networking):
  107. self.networks = networks or {}
  108. self.use_networking = use_networking
  109. @classmethod
  110. def from_services(cls, services, networks, use_networking):
  111. service_networks = {
  112. network: networks.get(network)
  113. for service in services
  114. for network in get_network_names_for_service(service)
  115. }
  116. unused = set(networks) - set(service_networks) - {'default'}
  117. if unused:
  118. log.warn(
  119. "Some networks were defined but are not used by any service: "
  120. "{}".format(", ".join(unused)))
  121. return cls(service_networks, use_networking)
  122. def remove(self):
  123. if not self.use_networking:
  124. return
  125. for network in self.networks.values():
  126. network.remove()
  127. def initialize(self):
  128. if not self.use_networking:
  129. return
  130. for network in self.networks.values():
  131. network.ensure()
  132. def get_network_names_for_service(service_dict):
  133. if 'network_mode' in service_dict:
  134. return []
  135. return service_dict.get('networks', ['default'])
  136. def get_networks(service_dict, network_definitions):
  137. networks = {}
  138. aliases = service_dict.get('network_aliases', {})
  139. for name in get_network_names_for_service(service_dict):
  140. log.debug(name)
  141. network = network_definitions.get(name)
  142. if network:
  143. log.debug(aliases)
  144. networks[network.full_name] = aliases.get(name, [])
  145. else:
  146. raise ConfigurationError(
  147. 'Service "{}" uses an undefined network "{}"'
  148. .format(service_dict['name'], name))
  149. log.debug(networks)
  150. return networks