summaryrefslogtreecommitdiff
path: root/cloudinit/sources/DataSourceDigitalOcean.py
diff options
context:
space:
mode:
authorBen Howard <bh@digitalocean.com>2016-08-19 16:28:26 -0600
committerScott Moser <smoser@brickies.net>2016-09-29 09:57:13 -0400
commit9f83bb8e80806d3dd79ba426474dc3c696e19a41 (patch)
tree90f695169bec78c544c9f2f345514360089231fd /cloudinit/sources/DataSourceDigitalOcean.py
parent6d1edc3f5a18b328bdd307426056539d5b9071fd (diff)
downloadvyos-cloud-init-9f83bb8e80806d3dd79ba426474dc3c696e19a41.tar.gz
vyos-cloud-init-9f83bb8e80806d3dd79ba426474dc3c696e19a41.zip
DigitalOcean: use meta-data for network configruation
On DigitalOcean, Network information is provided via Meta-data. It changes the datasource to be a local datasource, meaning it will run before fallback networking is configured. The advantage of that is that before networking is configured it can bring up a network device with ipv4 link-local and hit the metadata service that lives at 169.254.169.254 to find its networking configuration. It then takes down the link local address and lets cloud-init configure networking. The configuring of a network device to go looking for a metadata service is gated by a check of data in the smbios. This guarantees that the code will not run on another system.
Diffstat (limited to 'cloudinit/sources/DataSourceDigitalOcean.py')
-rw-r--r--cloudinit/sources/DataSourceDigitalOcean.py101
1 files changed, 47 insertions, 54 deletions
diff --git a/cloudinit/sources/DataSourceDigitalOcean.py b/cloudinit/sources/DataSourceDigitalOcean.py
index fc596e17..c5770d5d 100644
--- a/cloudinit/sources/DataSourceDigitalOcean.py
+++ b/cloudinit/sources/DataSourceDigitalOcean.py
@@ -18,13 +18,12 @@
# DigitalOcean Droplet API:
# https://developers.digitalocean.com/documentation/metadata/
-import json
-
from cloudinit import log as logging
from cloudinit import sources
-from cloudinit import url_helper
from cloudinit import util
+import cloudinit.sources.helpers.digitalocean as do_helper
+
LOG = logging.getLogger(__name__)
BUILTIN_DS_CONFIG = {
@@ -36,11 +35,13 @@ BUILTIN_DS_CONFIG = {
MD_RETRIES = 30
MD_TIMEOUT = 2
MD_WAIT_RETRY = 2
+MD_USE_IPV4LL = True
class DataSourceDigitalOcean(sources.DataSource):
def __init__(self, sys_cfg, distro, paths):
sources.DataSource.__init__(self, sys_cfg, distro, paths)
+ self.distro = distro
self.metadata = dict()
self.ds_cfg = util.mergemanydict([
util.get_cfg_by_path(sys_cfg, ["datasource", "DigitalOcean"], {}),
@@ -48,80 +49,72 @@ class DataSourceDigitalOcean(sources.DataSource):
self.metadata_address = self.ds_cfg['metadata_url']
self.retries = self.ds_cfg.get('retries', MD_RETRIES)
self.timeout = self.ds_cfg.get('timeout', MD_TIMEOUT)
+ self.use_ip4LL = self.ds_cfg.get('use_ip4LL', MD_USE_IPV4LL)
self.wait_retry = self.ds_cfg.get('wait_retry', MD_WAIT_RETRY)
+ self._network_config = None
def _get_sysinfo(self):
- # DigitalOcean embeds vendor ID and instance/droplet_id in the
- # SMBIOS information
-
- LOG.debug("checking if instance is a DigitalOcean droplet")
-
- # Detect if we are on DigitalOcean and return the Droplet's ID
- vendor_name = util.read_dmi_data("system-manufacturer")
- if vendor_name != "DigitalOcean":
- return (False, None)
+ return do_helper.read_sysinfo()
- LOG.info("running on DigitalOcean")
-
- droplet_id = util.read_dmi_data("system-serial-number")
- if droplet_id:
- LOG.debug(("system identified via SMBIOS as DigitalOcean Droplet"
- "{}").format(droplet_id))
- else:
- LOG.critical(("system identified via SMBIOS as a DigitalOcean "
- "Droplet, but did not provide an ID. Please file a "
- "support ticket at: "
- "https://cloud.digitalocean.com/support/tickets/"
- "new"))
-
- return (True, droplet_id)
-
- def get_data(self, apply_filter=False):
+ def get_data(self):
(is_do, droplet_id) = self._get_sysinfo()
# only proceed if we know we are on DigitalOcean
if not is_do:
return False
- LOG.debug("reading metadata from {}".format(self.metadata_address))
- response = url_helper.readurl(self.metadata_address,
- timeout=self.timeout,
- sec_between=self.wait_retry,
- retries=self.retries)
+ LOG.info("Running on digital ocean. droplet_id=%s" % droplet_id)
- contents = util.decode_binary(response.contents)
- decoded = json.loads(contents)
+ ipv4LL_nic = None
+ if self.use_ip4LL:
+ ipv4LL_nic = do_helper.assign_ipv4_link_local()
- self.metadata = decoded
- self.metadata['instance-id'] = decoded.get('droplet_id', droplet_id)
- self.metadata['local-hostname'] = decoded.get('hostname', droplet_id)
- self.vendordata_raw = decoded.get("vendor_data", None)
- self.userdata_raw = decoded.get("user_data", None)
- return True
+ md = do_helper.read_metadata(
+ self.metadata_address, timeout=self.timeout,
+ sec_between=self.wait_retry, retries=self.retries)
- def get_public_ssh_keys(self):
- public_keys = self.metadata.get('public_keys', [])
- if isinstance(public_keys, list):
- return public_keys
- else:
- return [public_keys]
+ self.metadata_full = md
+ self.metadata['instance-id'] = md.get('droplet_id', droplet_id)
+ self.metadata['local-hostname'] = md.get('hostname', droplet_id)
+ self.metadata['interfaces'] = md.get('interfaces')
+ self.metadata['public-keys'] = md.get('public_keys')
+ self.metadata['availability_zone'] = md.get('region', 'default')
+ self.vendordata_raw = md.get("vendor_data", None)
+ self.userdata_raw = md.get("user_data", None)
- @property
- def availability_zone(self):
- return self.metadata.get('region', 'default')
+ if ipv4LL_nic:
+ do_helper.del_ipv4_link_local(ipv4LL_nic)
- @property
- def launch_index(self):
- return None
+ return True
def check_instance_id(self, sys_cfg):
return sources.instance_id_matches_system_uuid(
self.get_instance_id(), 'system-serial-number')
+ @property
+ def network_config(self):
+ """Configure the networking. This needs to be done each boot, since
+ the IP information may have changed due to snapshot and/or
+ migration.
+ """
+
+ if self._network_config:
+ return self._network_config
+
+ interfaces = self.metadata.get('interfaces')
+ LOG.debug(interfaces)
+ if not interfaces:
+ raise Exception("Unable to get meta-data from server....")
+
+ nameservers = self.metadata_full['dns']['nameservers']
+ self._network_config = do_helper.convert_network_configuration(
+ interfaces, nameservers)
+ return self._network_config
+
# Used to match classes to dependencies
datasources = [
- (DataSourceDigitalOcean, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
+ (DataSourceDigitalOcean, (sources.DEP_FILESYSTEM, )),
]