From bae9b11da9ed7dd0b16fe5adeaf4774b7cc628cf Mon Sep 17 00:00:00 2001 From: James Falcon Date: Wed, 15 Dec 2021 20:16:38 -0600 Subject: Adopt Black and isort (SC-700) (#1157) Applied Black and isort, fixed any linting issues, updated tox.ini and CI. --- cloudinit/sources/DataSourceHetzner.py | 74 +++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 32 deletions(-) (limited to 'cloudinit/sources/DataSourceHetzner.py') diff --git a/cloudinit/sources/DataSourceHetzner.py b/cloudinit/sources/DataSourceHetzner.py index c7c88dd7..50324cc4 100644 --- a/cloudinit/sources/DataSourceHetzner.py +++ b/cloudinit/sources/DataSourceHetzner.py @@ -6,21 +6,19 @@ """Hetzner Cloud API Documentation https://docs.hetzner.cloud/""" +import cloudinit.sources.helpers.hetzner as hc_helper from cloudinit import dmi from cloudinit import log as logging from cloudinit import net as cloudnet -from cloudinit import sources -from cloudinit import util - -import cloudinit.sources.helpers.hetzner as hc_helper +from cloudinit import sources, util LOG = logging.getLogger(__name__) -BASE_URL_V1 = 'http://169.254.169.254/hetzner/v1' +BASE_URL_V1 = "http://169.254.169.254/hetzner/v1" BUILTIN_DS_CONFIG = { - 'metadata_url': BASE_URL_V1 + '/metadata', - 'userdata_url': BASE_URL_V1 + '/userdata', + "metadata_url": BASE_URL_V1 + "/metadata", + "userdata_url": BASE_URL_V1 + "/userdata", } MD_RETRIES = 60 @@ -30,20 +28,23 @@ MD_WAIT_RETRY = 2 class DataSourceHetzner(sources.DataSource): - dsname = 'Hetzner' + dsname = "Hetzner" 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", "Hetzner"], {}), - BUILTIN_DS_CONFIG]) - self.metadata_address = self.ds_cfg['metadata_url'] - self.userdata_address = self.ds_cfg['userdata_url'] - self.retries = self.ds_cfg.get('retries', MD_RETRIES) - self.timeout = self.ds_cfg.get('timeout', MD_TIMEOUT) - self.wait_retry = self.ds_cfg.get('wait_retry', MD_WAIT_RETRY) + self.ds_cfg = util.mergemanydict( + [ + util.get_cfg_by_path(sys_cfg, ["datasource", "Hetzner"], {}), + BUILTIN_DS_CONFIG, + ] + ) + self.metadata_address = self.ds_cfg["metadata_url"] + self.userdata_address = self.ds_cfg["userdata_url"] + self.retries = self.ds_cfg.get("retries", MD_RETRIES) + self.timeout = self.ds_cfg.get("timeout", MD_TIMEOUT) + self.wait_retry = self.ds_cfg.get("wait_retry", MD_WAIT_RETRY) self._network_config = None self.dsmode = sources.DSMODE_NETWORK @@ -54,14 +55,21 @@ class DataSourceHetzner(sources.DataSource): return False nic = cloudnet.find_fallback_nic() - with cloudnet.EphemeralIPv4Network(nic, "169.254.0.1", 16, - "169.254.255.255"): + with cloudnet.EphemeralIPv4Network( + nic, "169.254.0.1", 16, "169.254.255.255" + ): md = hc_helper.read_metadata( - self.metadata_address, timeout=self.timeout, - sec_between=self.wait_retry, retries=self.retries) + self.metadata_address, + timeout=self.timeout, + sec_between=self.wait_retry, + retries=self.retries, + ) ud = hc_helper.read_userdata( - self.userdata_address, timeout=self.timeout, - sec_between=self.wait_retry, retries=self.retries) + self.userdata_address, + timeout=self.timeout, + sec_between=self.wait_retry, + retries=self.retries, + ) # Hetzner cloud does not support binary user-data. So here, do a # base64 decode of the data if we can. The end result being that a @@ -76,10 +84,10 @@ class DataSourceHetzner(sources.DataSource): # hostname is name provided by user at launch. The API enforces it is # a valid hostname, but it is not guaranteed to be resolvable in dns or # fully qualified. - self.metadata['instance-id'] = md['instance-id'] - self.metadata['local-hostname'] = md['hostname'] - self.metadata['network-config'] = md.get('network-config', None) - self.metadata['public-keys'] = md.get('public-keys', None) + self.metadata["instance-id"] = md["instance-id"] + self.metadata["local-hostname"] = md["hostname"] + self.metadata["network-config"] = md.get("network-config", None) + self.metadata["public-keys"] = md.get("public-keys", None) self.vendordata_raw = md.get("vendor_data", None) # instance-id and serial from SMBIOS should be identical @@ -92,19 +100,20 @@ class DataSourceHetzner(sources.DataSource): def check_instance_id(self, sys_cfg): return sources.instance_id_matches_system_uuid( - self.get_instance_id(), 'system-serial-number') + 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. + the IP information may have changed due to snapshot and/or + migration. """ if self._network_config: return self._network_config - _net_config = self.metadata['network-config'] + _net_config = self.metadata["network-config"] if not _net_config: raise Exception("Unable to get meta-data from server....") @@ -114,7 +123,7 @@ class DataSourceHetzner(sources.DataSource): def get_hcloud_data(): - vendor_name = dmi.read_dmi_data('system-manufacturer') + vendor_name = dmi.read_dmi_data("system-manufacturer") if vendor_name != "Hetzner": return (False, None) @@ -129,7 +138,7 @@ def get_hcloud_data(): # Used to match classes to dependencies datasources = [ - (DataSourceHetzner, (sources.DEP_FILESYSTEM, )), + (DataSourceHetzner, (sources.DEP_FILESYSTEM,)), ] @@ -137,4 +146,5 @@ datasources = [ def get_datasource_list(depends): return sources.list_from_depends(depends, datasources) + # vi: ts=4 expandtab -- cgit v1.2.3 From 159c12a933f1a394b701f2c15b6267fc717067f7 Mon Sep 17 00:00:00 2001 From: Markus Schade Date: Wed, 9 Feb 2022 23:16:02 +0100 Subject: sources/hetzner: Use EphemeralDHCPv4 instead of static configuration (#1251) When the datasource was originally submitted, EphemeralDHCPv4 was not yet available. Also avoid race conditions by skipping network configuration if metadata service can be reached. Signed-off-by: Markus Schade --- cloudinit/sources/DataSourceHetzner.py | 53 ++++++++++++++++++++------------- tests/unittests/sources/test_hetzner.py | 45 ++++++++++++++++++---------- 2 files changed, 62 insertions(+), 36 deletions(-) (limited to 'cloudinit/sources/DataSourceHetzner.py') diff --git a/cloudinit/sources/DataSourceHetzner.py b/cloudinit/sources/DataSourceHetzner.py index 50324cc4..91a6f9c9 100644 --- a/cloudinit/sources/DataSourceHetzner.py +++ b/cloudinit/sources/DataSourceHetzner.py @@ -9,8 +9,8 @@ import cloudinit.sources.helpers.hetzner as hc_helper from cloudinit import dmi from cloudinit import log as logging -from cloudinit import net as cloudnet -from cloudinit import sources, util +from cloudinit import net, sources, util +from cloudinit.net.dhcp import EphemeralDHCPv4, NoDHCPLeaseError LOG = logging.getLogger(__name__) @@ -45,7 +45,7 @@ class DataSourceHetzner(sources.DataSource): self.retries = self.ds_cfg.get("retries", MD_RETRIES) self.timeout = self.ds_cfg.get("timeout", MD_TIMEOUT) self.wait_retry = self.ds_cfg.get("wait_retry", MD_WAIT_RETRY) - self._network_config = None + self._network_config = sources.UNSET self.dsmode = sources.DSMODE_NETWORK def _get_data(self): @@ -54,22 +54,28 @@ class DataSourceHetzner(sources.DataSource): if not on_hetzner: return False - nic = cloudnet.find_fallback_nic() - with cloudnet.EphemeralIPv4Network( - nic, "169.254.0.1", 16, "169.254.255.255" - ): - md = hc_helper.read_metadata( - self.metadata_address, - timeout=self.timeout, - sec_between=self.wait_retry, - retries=self.retries, - ) - ud = hc_helper.read_userdata( - self.userdata_address, - timeout=self.timeout, - sec_between=self.wait_retry, - retries=self.retries, - ) + try: + with EphemeralDHCPv4( + iface=net.find_fallback_nic(), + connectivity_url_data={ + "url": BASE_URL_V1 + "/metadata/instance-id", + }, + ): + md = hc_helper.read_metadata( + self.metadata_address, + timeout=self.timeout, + sec_between=self.wait_retry, + retries=self.retries, + ) + ud = hc_helper.read_userdata( + self.userdata_address, + timeout=self.timeout, + sec_between=self.wait_retry, + retries=self.retries, + ) + except (NoDHCPLeaseError) as e: + LOG.error("Bailing, DHCP Exception: %s", e) + raise # Hetzner cloud does not support binary user-data. So here, do a # base64 decode of the data if we can. The end result being that a @@ -110,7 +116,14 @@ class DataSourceHetzner(sources.DataSource): migration. """ - if self._network_config: + if self._network_config is None: + LOG.warning( + "Found None as cached _network_config. Resetting to %s", + sources.UNSET, + ) + self._network_config = sources.UNSET + + if self._network_config != sources.UNSET: return self._network_config _net_config = self.metadata["network-config"] diff --git a/tests/unittests/sources/test_hetzner.py b/tests/unittests/sources/test_hetzner.py index 9e70de34..f80ed45f 100644 --- a/tests/unittests/sources/test_hetzner.py +++ b/tests/unittests/sources/test_hetzner.py @@ -24,30 +24,24 @@ network-config: name: eth0 subnets: - dns_nameservers: - - 213.133.99.99 - - 213.133.100.100 - - 213.133.98.98 + - 185.12.64.1 + - 185.12.64.2 ipv4: true type: dhcp - type: physical - - name: eth0:0 - subnets: - address: 2a01:4f8:beef:beef::1/64 + dns_nameservers: + - 2a01:4ff:ff00::add:2 + - 2a01:4ff:ff00::add:1 gateway: fe80::1 ipv6: true - routes: - - gateway: fe80::1%eth0 - netmask: 0 - network: '::' - type: static type: physical version: 1 network-sysconfig: "DEVICE='eth0'\nTYPE=Ethernet\nBOOTPROTO=dhcp\n\ ONBOOT='yes'\nHWADDR=96:00:00:08:19:da\n\ IPV6INIT=yes\nIPV6ADDR=2a01:4f8:beef:beef::1/64\n\ IPV6_DEFAULTGW=fe80::1%eth0\nIPV6_AUTOCONF=no\n\ - DNS1=213.133.99.99\nDNS2=213.133.100.100\n" -public-ipv4: 192.168.0.1 + DNS1=185.12.64.1\nDNS2=185.12.64.2\n" +public-ipv4: 192.168.0.2 public-keys: - ssh-ed25519 \ AAAAC3Nzac1lZdI1NTE5AaaAIaFrcac0yVITsmRrmueq6MD0qYNKlEvW8O1Ib4nkhmWh \ @@ -77,13 +71,20 @@ class TestDataSourceHetzner(CiTestCase): ) return ds - @mock.patch("cloudinit.net.EphemeralIPv4Network") + @mock.patch("cloudinit.net.dhcp.maybe_perform_dhcp_discovery") + @mock.patch("cloudinit.sources.DataSourceHetzner.EphemeralDHCPv4") @mock.patch("cloudinit.net.find_fallback_nic") @mock.patch("cloudinit.sources.helpers.hetzner.read_metadata") @mock.patch("cloudinit.sources.helpers.hetzner.read_userdata") @mock.patch("cloudinit.sources.DataSourceHetzner.get_hcloud_data") def test_read_data( - self, m_get_hcloud_data, m_usermd, m_readmd, m_fallback_nic, m_net + self, + m_get_hcloud_data, + m_usermd, + m_readmd, + m_fallback_nic, + m_net, + m_dhcp, ): m_get_hcloud_data.return_value = ( True, @@ -92,13 +93,25 @@ class TestDataSourceHetzner(CiTestCase): m_readmd.return_value = METADATA.copy() m_usermd.return_value = USERDATA m_fallback_nic.return_value = "eth0" + m_dhcp.return_value = [ + { + "interface": "eth0", + "fixed-address": "192.168.0.2", + "routers": "192.168.0.1", + "subnet-mask": "255.255.255.0", + "broadcast-address": "192.168.0.255", + } + ] ds = self.get_ds() ret = ds.get_data() self.assertTrue(ret) m_net.assert_called_once_with( - "eth0", "169.254.0.1", 16, "169.254.255.255" + iface="eth0", + connectivity_url_data={ + "url": "http://169.254.169.254/hetzner/v1/metadata/instance-id" + }, ) self.assertTrue(m_readmd.called) -- cgit v1.2.3