def test_ephemeral_ipv4_network_with_prefix(self, m_subp): """EphemeralIPv4Network takes a valid prefix to setup the network.""" params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'prefix_or_mask': '24', 'broadcast': '192.168.2.255' } for prefix_val in ['24', 16]: # prefix can be int or string params['prefix_or_mask'] = prefix_val with net.EphemeralIPv4Network(**params): pass m_subp.assert_has_calls([ mock.call([ 'ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24', 'broadcast', '192.168.2.255', 'dev', 'eth0' ], capture=True, update_env={'LANG': 'C'}) ]) m_subp.assert_has_calls([ mock.call([ 'ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/16', 'broadcast', '192.168.2.255', 'dev', 'eth0' ], capture=True, update_env={'LANG': 'C'}) ])
def get_data(self): 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) self.userdata_raw = ud self.metadata_full = md """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.vendordata_raw = md.get("vendor_data", None) return True
def test_ephemeral_ipv4_network_noop_when_configured(self, m_subp): """EphemeralIPv4Network handles exception when address is setup. It performs no cleanup as the interface was already setup. """ params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255' } m_subp.side_effect = ProcessExecutionError( '', 'RTNETLINK answers: File exists', 2) expected_calls = [ mock.call([ 'ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24', 'broadcast', '192.168.2.255', 'dev', 'eth0' ], capture=True, update_env={'LANG': 'C'}) ] with net.EphemeralIPv4Network(**params): pass self.assertEqual(expected_calls, m_subp.call_args_list) self.assertIn('Skip ephemeral network setup, eth0 already has address', self.logs.getvalue())
def test_ephemeral_ipv4_network_performs_teardown(self, m_subp): """EphemeralIPv4Network performs teardown on the device if setup.""" expected_setup_calls = [ mock.call([ 'ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24', 'broadcast', '192.168.2.255', 'dev', 'eth0' ], capture=True, update_env={'LANG': 'C'}), mock.call( ['ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'up'], capture=True) ] expected_teardown_calls = [ mock.call([ 'ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'down' ], capture=True), mock.call([ 'ip', '-family', 'inet', 'addr', 'del', '192.168.2.2/24', 'dev', 'eth0' ], capture=True) ] params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255' } with net.EphemeralIPv4Network(**params): self.assertEqual(expected_setup_calls, m_subp.call_args_list) m_subp.assert_has_calls(expected_teardown_calls)
def get_data(self): 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) # 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 # user can provide base64 encoded (possibly gzipped) data as user-data. # # The fallout is that in the event of b64 encoded user-data, # /var/lib/cloud-init/cloud-config.txt will not be identical to the # user-data provided. It will be decoded. self.userdata_raw = hc_helper.maybe_b64decode(ud) self.metadata_full = md # 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.vendordata_raw = md.get("vendor_data", None) return True
def test_ephemeral_ipv4_network_with_new_default_route(self, m_subp): """Add the route when router is set and no default route exists.""" params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255', 'router': '192.168.2.1' } m_subp.return_value = '', '' # Empty response from ip route gw check expected_setup_calls = [ mock.call([ 'ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24', 'broadcast', '192.168.2.255', 'dev', 'eth0' ], capture=True, update_env={'LANG': 'C'}), mock.call( ['ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'up'], capture=True), mock.call(['ip', 'route', 'show', '0.0.0.0/0'], capture=True), mock.call([ 'ip', '-4', 'route', 'add', 'default', 'via', '192.168.2.1', 'dev', 'eth0' ], capture=True) ] expected_teardown_calls = [ mock.call(['ip', '-4', 'route', 'del', 'default', 'dev', 'eth0'], capture=True) ] with net.EphemeralIPv4Network(**params): self.assertEqual(expected_setup_calls, m_subp.call_args_list) m_subp.assert_has_calls(expected_teardown_calls)
def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255', 'static_routes': [('169.254.169.254/32', '192.168.2.1'), ('0.0.0.0/0', '192.168.2.1')], 'router': '192.168.2.1' } expected_setup_calls = [ mock.call([ 'ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24', 'broadcast', '192.168.2.255', 'dev', 'eth0' ], capture=True, update_env={'LANG': 'C'}), mock.call( ['ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'up'], capture=True), mock.call([ 'ip', '-4', 'route', 'add', '169.254.169.254/32', 'via', '192.168.2.1', 'dev', 'eth0' ], capture=True), mock.call([ 'ip', '-4', 'route', 'add', '0.0.0.0/0', 'via', '192.168.2.1', 'dev', 'eth0' ], capture=True) ] expected_teardown_calls = [ mock.call([ 'ip', '-4', 'route', 'del', '0.0.0.0/0', 'via', '192.168.2.1', 'dev', 'eth0' ], capture=True), mock.call([ 'ip', '-4', 'route', 'del', '169.254.169.254/32', 'via', '192.168.2.1', 'dev', 'eth0' ], capture=True), mock.call([ 'ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'down' ], capture=True), mock.call([ 'ip', '-family', 'inet', 'addr', 'del', '192.168.2.2/24', 'dev', 'eth0' ], capture=True) ] with net.EphemeralIPv4Network(**params): self.assertEqual(expected_setup_calls, m_subp.call_args_list) m_subp.assert_has_calls(expected_setup_calls + expected_teardown_calls)
def test_ephemeral_ipv4_network_errors_on_missing_params(self, m_subp): """No required params for EphemeralIPv4Network can be None.""" required_params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255'} for key in required_params.keys(): params = copy.deepcopy(required_params) params[key] = None with self.assertRaises(ValueError) as context_manager: net.EphemeralIPv4Network(**params) error = context_manager.exception self.assertIn('Cannot init network on', str(error)) self.assertEqual(0, m_subp.call_count)
def test_ephemeral_ipv4_network_errors_invalid_mask_prefix(self, m_subp): """Raise an error when prefix_or_mask is not a netmask or prefix.""" params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'broadcast': '192.168.2.255'} invalid_masks = ('invalid', 'invalid.', '123.123.123') for error_val in invalid_masks: params['prefix_or_mask'] = error_val with self.assertRaises(ValueError) as context_manager: with net.EphemeralIPv4Network(**params): pass error = context_manager.exception self.assertIn('Cannot setup network: netmask', str(error)) self.assertEqual(0, m_subp.call_count)
def _get_data(self): (on_hetzner, serial) = get_hcloud_data() 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, ) # 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 # user can provide base64 encoded (possibly gzipped) data as user-data. # # The fallout is that in the event of b64 encoded user-data, # /var/lib/cloud-init/cloud-config.txt will not be identical to the # user-data provided. It will be decoded. self.userdata_raw = hc_helper.maybe_b64decode(ud) self.metadata_full = md # 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.vendordata_raw = md.get("vendor_data", None) # instance-id and serial from SMBIOS should be identical if self.get_instance_id() != serial: raise RuntimeError( "SMBIOS serial does not match instance ID from metadata") return True
def test_ephemeral_ipv4_no_network_if_url_connectivity( self, m_readurl, m_subp): """No network setup is performed if we can successfully connect to connectivity_url.""" params = { 'interface': 'eth0', 'ip': '192.168.2.2', 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255', 'connectivity_url': 'http://example.org/index.html' } with net.EphemeralIPv4Network(**params): self.assertEqual( [mock.call('http://example.org/index.html', timeout=5)], m_readurl.call_args_list) # Ensure that no teardown happens: m_subp.assert_has_calls([])
def _get_data(self): seed_ret = {} if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")): self.userdata_raw = seed_ret['user-data'] self.metadata = seed_ret['meta-data'] LOG.debug("Using seeded ec2 data from %s", self.seed_dir) self._cloud_platform = Platforms.SEEDED return True strict_mode, _sleep = read_strict_mode( util.get_cfg_by_path(self.sys_cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT), ("warn", None)) LOG.debug("strict_mode: %s, cloud_platform=%s", strict_mode, self.cloud_platform) if strict_mode == "true" and self.cloud_platform == Platforms.UNKNOWN: return False elif self.cloud_platform == Platforms.NO_EC2_METADATA: return False if self.get_network_metadata: # Setup networking in init-local stage. if util.is_FreeBSD(): LOG.debug("FreeBSD doesn't support running dhclient with -sf") return False dhcp_leases = dhcp.maybe_perform_dhcp_discovery( self.fallback_interface) if not dhcp_leases: # DataSourceEc2Local failed in init-local stage. DataSourceEc2 # will still run in init-network stage. return False dhcp_opts = dhcp_leases[-1] net_params = { 'interface': dhcp_opts.get('interface'), 'ip': dhcp_opts.get('fixed-address'), 'prefix_or_mask': dhcp_opts.get('subnet-mask'), 'broadcast': dhcp_opts.get('broadcast-address'), 'router': dhcp_opts.get('routers') } with net.EphemeralIPv4Network(**net_params): return util.log_time(logfunc=LOG.debug, msg='Crawl of metadata service', func=self._crawl_metadata) else: return self._crawl_metadata()