def test_get_data_vmware_customization_disabled(self): """When cloud-init workflow for vmware is enabled via sys_cfg log a message. """ paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource( sys_cfg={'disable_vmware_customization': False}, distro={}, paths=paths) conf_file = self.tmp_path('test-cust', self.tdir) conf_content = dedent("""\ [CUSTOM-SCRIPT] SCRIPT-NAME = test-script [MISC] MARKER-ID = 12345345 """) util.write_file(conf_file, conf_content) with self.assertRaises(CustomScriptNotFound) as context: wrap_and_call( 'cloudinit.sources.DataSourceOVF', {'util.read_dmi_data': 'vmware', 'util.del_dir': True, 'search_file': self.tdir, 'wait_for_imc_cfg_file': conf_file, 'get_nics_to_enable': ''}, ds.get_data) customscript = self.tmp_path('test-script', self.tdir) self.assertIn('Script %s not found!!' % customscript, str(context.exception))
def test_add_assertions_adds_assertions_as_dict(self, m_subp): """When provided with a dict, add_assertions adds all assertions.""" self.assertEqual( ASSERTIONS_FILE, '/var/lib/cloud/instance/snapd.assertions') assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) assertions = {'00': SYSTEM_USER_ASSERTION, '01': ACCOUNT_ASSERTION} wrap_and_call( 'cloudinit.config.cc_snap', {'ASSERTIONS_FILE': {'new': assert_file}}, add_assertions, assertions) self.assertIn( 'Importing user-provided snap assertions', self.logs.getvalue()) self.assertIn( "DEBUG: Snap acking: ['type: system-user', 'authority-id: Lqv", self.logs.getvalue()) self.assertIn( "DEBUG: Snap acking: ['type: account-key', 'authority-id: canonic", self.logs.getvalue()) self.assertEqual( [mock.call(['snap', 'ack', assert_file], capture=True)], m_subp.call_args_list) compare_file = self.tmp_path('comparison', dir=self.tmp) combined = '\n'.join(assertions.values()) util.write_file(compare_file, combined.encode('utf-8')) self.assertEqual( util.load_file(compare_file), util.load_file(assert_file))
def test_get_data_vmware_customization_disabled(self): """When cloud-init workflow for vmware is enabled via sys_cfg log a message. """ paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource(sys_cfg={'disable_vmware_customization': False}, distro={}, paths=paths) conf_file = self.tmp_path('test-cust', self.tdir) conf_content = dedent("""\ [CUSTOM-SCRIPT] SCRIPT-NAME = test-script [MISC] MARKER-ID = 12345345 """) util.write_file(conf_file, conf_content) with mock.patch(MPATH + 'get_tools_config', return_value='true'): with self.assertRaises(CustomScriptNotFound) as context: wrap_and_call( 'cloudinit.sources.DataSourceOVF', { 'dmi.read_dmi_data': 'vmware', 'util.del_dir': True, 'search_file': self.tdir, 'wait_for_imc_cfg_file': conf_file, 'get_nics_to_enable': '' }, ds.get_data) customscript = self.tmp_path('test-script', self.tdir) self.assertIn('Script %s not found!!' % customscript, str(context.exception))
def test_get_data_cloudinit_metadata_not_found(self): """Test metadata file can't be found. """ paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource(sys_cfg={'disable_vmware_customization': True}, distro={}, paths=paths) # Prepare the conf file conf_file = self.tmp_path('test-cust', self.tdir) conf_content = dedent("""\ [CLOUDINIT] METADATA = test-meta """) util.write_file(conf_file, conf_content) # Don't prepare the meta data file with mock.patch(MPATH + 'set_customization_status', return_value=('msg', b'')): with self.assertRaises(FileNotFoundError) as context: wrap_and_call( 'cloudinit.sources.DataSourceOVF', { 'dmi.read_dmi_data': 'vmware', 'util.del_dir': True, 'search_file': self.tdir, 'wait_for_imc_cfg_file': conf_file, 'get_nics_to_enable': '' }, ds.get_data) self.assertIn('is not found', str(context.exception))
def test_get_data_cust_script_disabled(self): """If custom script is disabled by VMware tools configuration, raise a RuntimeError. """ paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource(sys_cfg={'disable_vmware_customization': False}, distro={}, paths=paths) # Prepare the conf file conf_file = self.tmp_path('test-cust', self.tdir) conf_content = dedent("""\ [CUSTOM-SCRIPT] SCRIPT-NAME = test-script [MISC] MARKER-ID = 12345346 """) util.write_file(conf_file, conf_content) # Prepare the custom sript customscript = self.tmp_path('test-script', self.tdir) util.write_file(customscript, "This is the post cust script") with mock.patch(MPATH + 'get_tools_config', return_value='invalid'): with mock.patch(MPATH + 'set_customization_status', return_value=('msg', b'')): with self.assertRaises(RuntimeError) as context: wrap_and_call( 'cloudinit.sources.DataSourceOVF', { 'dmi.read_dmi_data': 'vmware', 'util.del_dir': True, 'search_file': self.tdir, 'wait_for_imc_cfg_file': conf_file, 'get_nics_to_enable': '' }, ds.get_data) self.assertIn('Custom script is disabled by VM Administrator', str(context.exception))
def test_handler_installs_client_and_creates_config_file(self): """Write landscape client.conf and install landscape-client.""" mycloud = get_cloud('ubuntu') cfg = {'landscape': {'client': {}}} expected = { 'client': { 'log_level': 'info', 'url': 'https://landscape.canonical.com/message-system', 'ping_url': 'http://landscape.canonical.com/ping', 'data_path': '/var/lib/landscape/client' } } mycloud.distro = mock.MagicMock() wrap_and_call( 'cloudinit.config.cc_landscape', { 'LSC_CLIENT_CFG_FILE': { 'new': self.conf }, 'LS_DEFAULT_FILE': { 'new': self.default_file } }, cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) self.assertEqual([mock.call('landscape-client')], mycloud.distro.install_packages.call_args) self.assertEqual(expected, dict(ConfigObj(self.conf))) self.assertIn('Wrote landscape config file to {0}'.format(self.conf), self.logs.getvalue()) default_content = util.load_file(self.default_file) self.assertEqual('RUN=1\n', default_content)
def test_status_main(self): '''status.main can be run as a standalone script.''' write_json(self.status_file, {'v1': { 'init': { 'start': 1, 'finished': None } }}) with self.assertRaises(SystemExit) as context_manager: with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: wrap_and_call( 'cloudinit.cmd.status', { 'sys.argv': { 'new': ['status'] }, 'sys.exit': { 'side_effect': self.sys_exit }, '_is_cloudinit_disabled': (False, ''), 'Init': { 'side_effect': self.init_class } }, status.main) self.assertEqual(0, context_manager.exception.code) self.assertEqual('status: running\n', m_stdout.getvalue())
def test_handle_warns_on_undiscoverable_root_path_in_commandline(self): """handle noops when the root path is not found on the commandline.""" cfg = {'resize_rootfs': True} exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists' def fake_mount_info(path, log): self.assertEqual('/', path) self.assertEqual(LOG, log) return ('/dev/root', 'ext4', '/') with mock.patch(exists_mock_path) as m_exists: m_exists.return_value = False wrap_and_call('cloudinit.config.cc_resizefs.util', { 'is_container': { 'return_value': False }, 'get_mount_info': { 'side_effect': fake_mount_info }, 'get_cmdline': { 'return_value': 'BOOT_IMAGE=/vmlinuz.efi' } }, handle, 'cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) logs = self.logs.getvalue() self.assertIn("WARNING: Unable to find device '/dev/root'", logs)
def test_collect_logs_includes_optional_userdata(self, m_getuid): """collect-logs include userdata when --include-userdata is set.""" m_getuid.return_value = 0 log1 = self.tmp_path('cloud-init.log', self.new_root) write_file(log1, 'cloud-init-log') log2 = self.tmp_path('cloud-init-output.log', self.new_root) write_file(log2, 'cloud-init-output-log') userdata = self.tmp_path('user-data.txt', self.new_root) write_file(userdata, 'user-data') ensure_dir(self.run_dir) write_file(self.tmp_path('results.json', self.run_dir), 'results') write_file(self.tmp_path(INSTANCE_JSON_SENSITIVE_FILE, self.run_dir), 'sensitive') output_tarfile = self.tmp_path('logs.tgz') date = datetime.utcnow().date().strftime('%Y-%m-%d') date_logdir = 'cloud-init-logs-{0}'.format(date) version_out = '/usr/bin/cloud-init 18.2fake\n' expected_subp = { ('dpkg-query', '--show', "-f=${Version}\n", 'cloud-init'): '0.7fake', ('cloud-init', '--version'): version_out, ('dmesg',): 'dmesg-out\n', ('journalctl', '--boot=0', '-o', 'short-precise'): 'journal-out\n', ('tar', 'czvf', output_tarfile, date_logdir): '' } def fake_subp(cmd): cmd_tuple = tuple(cmd) if cmd_tuple not in expected_subp: raise AssertionError( 'Unexpected command provided to subp: {0}'.format(cmd)) if cmd == ['tar', 'czvf', output_tarfile, date_logdir]: subp(cmd) # Pass through tar cmd so we can check output return expected_subp[cmd_tuple], '' fake_stderr = mock.MagicMock() wrap_and_call( 'cloudinit.cmd.devel.logs', {'subp': {'side_effect': fake_subp}, 'sys.stderr': {'new': fake_stderr}, 'CLOUDINIT_LOGS': {'new': [log1, log2]}, 'CLOUDINIT_RUN_DIR': {'new': self.run_dir}, 'USER_DATA_FILE': {'new': userdata}}, logs.collect_logs, output_tarfile, include_userdata=True) # unpack the tarfile and check file contents subp(['tar', 'zxvf', output_tarfile, '-C', self.new_root]) out_logdir = self.tmp_path(date_logdir, self.new_root) self.assertEqual( 'user-data', load_file(os.path.join(out_logdir, 'user-data.txt'))) self.assertEqual( 'sensitive', load_file(os.path.join(out_logdir, 'run', 'cloud-init', INSTANCE_JSON_SENSITIVE_FILE))) fake_stderr.write.assert_any_call('Wrote %s\n' % output_tarfile)
def test_handler_restarts_landscape_client(self, m_util): """handler restarts lansdscape-client after install.""" mycloud = self._get_cloud('ubuntu') cfg = {'landscape': {'client': {}}} wrap_and_call( 'cloudinit.config.cc_landscape', {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) self.assertEqual( [mock.call(['service', 'landscape-client', 'restart'])], m_util.subp.call_args_list)
def test_maybe_get_writable_device_path_raises_oserror(self): """When unexpected OSError is raises by os.stat it is reraised.""" info = 'dev=/dev/I/dont/exist mnt_point=/ path=/dev/none' with self.assertRaises(OSError) as context_manager: wrap_and_call( 'cloudinit.config.cc_resizefs', {'util.is_container': {'return_value': True}, 'os.stat': {'side_effect': OSError('Something unexpected')}}, maybe_get_writable_device_path, '/dev/I/dont/exist', info, LOG) self.assertEqual( 'Something unexpected', str(context_manager.exception))
def test_maybe_get_writable_device_path_raises_oserror(self): """When unexpected OSError is raises by os.stat it is reraised.""" info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none' with self.assertRaises(OSError) as context_manager: wrap_and_call( 'cloudinit.config.cc_resizefs', {'util.is_container': {'return_value': True}, 'os.stat': {'side_effect': OSError('Something unexpected')}}, maybe_get_writable_device_path, '/I/dont/exist', info, LOG) self.assertEqual( 'Something unexpected', str(context_manager.exception))
def test_collect_logs_includes_optional_userdata(self): """collect-logs include userdata when --include-userdata is set.""" log1 = self.tmp_path('cloud-init.log', self.new_root) write_file(log1, 'cloud-init-log') log2 = self.tmp_path('cloud-init-output.log', self.new_root) write_file(log2, 'cloud-init-output-log') userdata = self.tmp_path('user-data.txt', self.new_root) write_file(userdata, 'user-data') ensure_dir(self.run_dir) write_file(self.tmp_path('results.json', self.run_dir), 'results') output_tarfile = self.tmp_path('logs.tgz') date = datetime.utcnow().date().strftime('%Y-%m-%d') date_logdir = 'cloud-init-logs-{0}'.format(date) expected_subp = { ('dpkg-query', '--show', "-f=${Version}\n", 'cloud-init'): '0.7fake', ('dmesg', ): 'dmesg-out\n', ('journalctl', '-o', 'short-precise'): 'journal-out\n', ('tar', 'czvf', output_tarfile, date_logdir): '' } def fake_subp(cmd): cmd_tuple = tuple(cmd) if cmd_tuple not in expected_subp: raise AssertionError( 'Unexpected command provided to subp: {0}'.format(cmd)) if cmd == ['tar', 'czvf', output_tarfile, date_logdir]: subp(cmd) # Pass through tar cmd so we can check output return expected_subp[cmd_tuple], '' wrap_and_call('cloudinit.cmd.devel.logs', { 'subp': { 'side_effect': fake_subp }, 'CLOUDINIT_LOGS': { 'new': [log1, log2] }, 'CLOUDINIT_RUN_DIR': { 'new': self.run_dir }, 'USER_DATA_FILE': { 'new': userdata } }, logs.collect_logs, output_tarfile, include_userdata=True) # unpack the tarfile and check file contents subp(['tar', 'zxvf', output_tarfile, '-C', self.new_root]) out_logdir = self.tmp_path(date_logdir, self.new_root) self.assertEqual('user-data', load_file(os.path.join(out_logdir, 'user-data.txt')))
def test_handle_validates_schema(self, m_subp): """Any provided configuration is runs validate_cloudconfig_schema.""" assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) cfg = {'snap': {'invalid': ''}} # Generates schema warning wrap_and_call( 'cloudinit.config.cc_snap', {'ASSERTIONS_FILE': {'new': assert_file}}, handle, 'snap', cfg=cfg, cloud=None, log=self.logger, args=None) self.assertEqual( "WARNING: Invalid config:\nsnap: Additional properties are not" " allowed ('invalid' was unexpected)\n", self.logs.getvalue())
def test_status_main(self): '''clean.main can be run as a standalone script.''' write_file(self.log1, 'cloud-init-log') with self.assertRaises(SystemExit) as context_manager: wrap_and_call( 'cloudinit.cmd.clean', {'Init': {'side_effect': self.init_class}, 'sys.argv': {'new': ['clean', '--logs']}}, clean.main) self.assertEqual(0, context_manager.exception.code) self.assertFalse( os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))
def test_status_main(self): '''clean.main can be run as a standalone script.''' write_file(self.log1, 'cloud-init-log') with self.assertRaises(SystemExit) as context_manager: wrap_and_call( 'cloudinit.cmd.clean', {'Init': {'side_effect': self.init_class}, 'sys.exit': {'side_effect': self.sys_exit}, 'sys.argv': {'new': ['clean', '--logs']}}, clean.main) self.assertEqual(0, context_manager.exception.code) self.assertFalse( os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))
def test_handle_adds_assertions(self, m_subp): """Any configured snap assertions are provided to add_assertions.""" assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) compare_file = self.tmp_path('comparison', dir=self.tmp) cfg = { 'snap': {'assertions': [SYSTEM_USER_ASSERTION, ACCOUNT_ASSERTION]}} wrap_and_call( 'cloudinit.config.cc_snap', {'ASSERTIONS_FILE': {'new': assert_file}}, handle, 'snap', cfg=cfg, cloud=None, log=self.logger, args=None) content = '\n'.join(cfg['snap']['assertions']) util.write_file(compare_file, content.encode('utf-8')) self.assertEqual( util.load_file(compare_file), util.load_file(assert_file))
def test_get_data_no_vmware_customization_disabled(self): """When cloud-init workflow for vmware is disabled via sys_cfg and no meta data provided, log a message. """ paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource(sys_cfg={'disable_vmware_customization': True}, distro={}, paths=paths) conf_file = self.tmp_path('test-cust', self.tdir) conf_content = dedent("""\ [CUSTOM-SCRIPT] SCRIPT-NAME = test-script [MISC] MARKER-ID = 12345345 """) util.write_file(conf_file, conf_content) retcode = wrap_and_call( 'cloudinit.sources.DataSourceOVF', { 'dmi.read_dmi_data': 'vmware', 'transport_iso9660': NOT_FOUND, 'transport_vmware_guestinfo': NOT_FOUND, 'util.del_dir': True, 'search_file': self.tdir, 'wait_for_imc_cfg_file': conf_file }, ds.get_data) self.assertFalse(retcode, 'Expected False return from ds.get_data') self.assertIn('DEBUG: Customization for VMware platform is disabled.', self.logs.getvalue())
def test_status_returns_disabled_long_on_presence_of_disable_file(self): '''When cloudinit is disabled, return disabled reason.''' checked_files = [] def fakeexists(filepath): checked_files.append(filepath) status_file = os.path.join(self.paths.run_dir, 'status.json') return bool(not filepath == status_file) cmdargs = myargs(long=True, wait=False) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: retcode = wrap_and_call( 'cloudinit.cmd.status', {'os.path.exists': {'side_effect': fakeexists}, '_is_cloudinit_disabled': (True, 'disabled for some reason'), 'Init': {'side_effect': self.init_class}}, status.handle_status_args, 'ignored', cmdargs) self.assertEqual(0, retcode) self.assertEqual( [os.path.join(self.paths.run_dir, 'status.json')], checked_files) expected = dedent('''\ status: disabled detail: disabled for some reason ''') self.assertEqual(expected, m_stdout.getvalue())
def test_cmdline_overrides_confd_runtime_and_defaults(self): builtin = {'key1': 'value0', 'key3': 'other2'} conf_d = {'key1': 'value1', 'key2': 'other1'} cmdline = {'key3': 'other3', 'key2': 'other2'} runtime = {'key3': 'runtime3'} ret = helpers.wrap_and_call( 'cloudinit.stages', { 'util.read_conf_with_confd': { 'return_value': conf_d }, 'util.get_builtin_cfg': { 'return_value': builtin }, 'read_runtime_config': { 'return_value': runtime }, 'util.read_conf_from_cmdline': { 'return_value': cmdline } }, stages.fetch_base_config) self.assertEqual(ret, { 'key1': 'value1', 'key2': 'other2', 'key3': 'other3' })
def test_main_init_run_net_stops_on_file_no_net(self): """When no-net file is present, main_init does not process modules.""" stop_file = os.path.join(self.cloud_dir, 'data', 'no-net') # stop file write_file(stop_file, '') cmdargs = myargs( debug=False, files=None, force=False, local=False, reporter=None, subcommand='init') (_item1, item2) = wrap_and_call( 'cloudinit.cmd.main', {'util.close_stdin': True, 'netinfo.debug_info': 'my net debug info', 'util.fixup_output': ('outfmt', 'errfmt')}, main.main_init, 'init', cmdargs) # We should not run write_files module self.assertFalse( os.path.exists(os.path.join(self.new_root, 'etc/blah.ini')), 'Unexpected run of write_files module produced blah.ini') self.assertEqual([], item2) # Instancify is called instance_id_path = 'var/lib/cloud/data/instance-id' self.assertFalse( os.path.exists(os.path.join(self.new_root, instance_id_path)), 'Unexpected call to datasource.instancify produced instance-id') expected_logs = [ "Exiting. stop file ['{stop_file}'] existed\n".format( stop_file=stop_file), 'my net debug info' # netinfo.debug_info ] for log in expected_logs: self.assertIn(log, self.stderr.getvalue())
def test_main_init_run_net_runs_modules(self): """Modules like write_files are run in 'net' mode.""" cmdargs = myargs( debug=False, files=None, force=False, local=False, reporter=None, subcommand='init') (_item1, item2) = wrap_and_call( 'cloudinit.cmd.main', {'util.close_stdin': True, 'netinfo.debug_info': 'my net debug info', 'util.fixup_output': ('outfmt', 'errfmt')}, main.main_init, 'init', cmdargs) self.assertEqual([], item2) # Instancify is called instance_id_path = 'var/lib/cloud/data/instance-id' self.assertEqual( 'iid-datasource-none\n', os.path.join(load_file( os.path.join(self.new_root, instance_id_path)))) # modules are run (including write_files) self.assertEqual( 'blah', load_file(os.path.join(self.new_root, 'etc/blah.ini'))) expected_logs = [ 'network config is disabled by fallback', # apply_network_config 'my net debug info', # netinfo.debug_info 'no previous run detected' ] for log in expected_logs: self.assertIn(log, self.stderr.getvalue())
def test_main_init_run_net_stops_on_file_no_net(self): """When no-net file is present, main_init does not process modules.""" stop_file = os.path.join(self.cloud_dir, 'data', 'no-net') # stop file write_file(stop_file, '') cmdargs = myargs(debug=False, files=None, force=False, local=False, reporter=None, subcommand='init') (_item1, item2) = wrap_and_call( 'cloudinit.cmd.main', { 'util.close_stdin': True, 'netinfo.debug_info': 'my net debug info', 'util.fixup_output': ('outfmt', 'errfmt') }, main.main_init, 'init', cmdargs) # We should not run write_files module self.assertFalse( os.path.exists(os.path.join(self.new_root, 'etc/blah.ini')), 'Unexpected run of write_files module produced blah.ini') self.assertEqual([], item2) # Instancify is called instance_id_path = 'var/lib/cloud/data/instance-id' self.assertFalse( os.path.exists(os.path.join(self.new_root, instance_id_path)), 'Unexpected call to datasource.instancify produced instance-id') expected_logs = [ "Exiting. stop file ['{stop_file}'] existed\n".format( stop_file=stop_file), 'my net debug info' # netinfo.debug_info ] for log in expected_logs: self.assertIn(log, self.stderr.getvalue())
def test_maybe_get_writable_device_path_returns_cmdline_root(self): """When root device is UUID in kernel commandline, update devpath.""" # XXX Long-term we want to use FilesystemMocking test to avoid # touching os.stat. FakeStat = namedtuple( 'FakeStat', ['st_mode', 'st_size', 'st_mtime']) # minimal def. info = 'dev=/dev/root mnt_point=/ path=/does/not/matter' devpath = wrap_and_call( 'cloudinit.config.cc_resizefs', { 'util.get_cmdline': { 'return_value': 'asdf root=UUID=my-uuid' }, 'util.is_container': False, 'os.path.exists': False, # /dev/root doesn't exist 'os.stat': { 'return_value': FakeStat(25008, 0, 1) } # char block device }, maybe_get_writable_device_path, '/dev/root', info, LOG) self.assertEqual('/dev/disk/by-uuid/my-uuid', devpath) self.assertIn( "DEBUG: Converted /dev/root to '/dev/disk/by-uuid/my-uuid'" " per kernel cmdline", self.logs.getvalue())
def test_status_on_errors(self): '''Reports error when any stage has errors.''' write_json( self.status_file, { 'v1': { 'stage': None, 'blah': { 'errors': [], 'finished': 123.456 }, 'init': { 'errors': ['error1'], 'start': 124.567, 'finished': 125.678 }, 'init-local': { 'start': 123.45, 'finished': 123.46 } } }) cmdargs = myargs(long=False, wait=False) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: retcode = wrap_and_call( 'cloudinit.cmd.status', { '_is_cloudinit_disabled': (False, ''), 'Init': { 'side_effect': self.init_class } }, status.handle_status_args, 'ignored', cmdargs) self.assertEqual(1, retcode) self.assertEqual('status: error\n', m_stdout.getvalue())
def test_order_precedence_is_builtin_system_runtime_cmdline(self): builtin = {'key1': 'builtin0', 'key3': 'builtin3'} conf_d = {'key1': 'confd1', 'key2': 'confd2', 'keyconfd1': 'kconfd1'} runtime = {'key1': 'runtime1', 'key2': 'runtime2'} cmdline = {'key1': 'cmdline1'} ret = helpers.wrap_and_call( 'cloudinit.stages', { 'util.read_conf_with_confd': { 'return_value': conf_d }, 'util.get_builtin_cfg': { 'return_value': builtin }, 'util.read_conf_from_cmdline': { 'return_value': cmdline }, 'read_runtime_config': { 'return_value': runtime }, }, stages.fetch_base_config) self.assertEqual( ret, { 'key1': 'cmdline1', 'key2': 'runtime2', 'key3': 'builtin3', 'keyconfd1': 'kconfd1' })
def test_maybe_get_writable_device_path_warns_missing_cmdline_root(self): """When root does not exist isn't in the cmdline, log warning.""" info = 'does not matter' def fake_mount_info(path, log): self.assertEqual('/', path) self.assertEqual(LOG, log) return ('/dev/root', 'ext4', '/') exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists' with mock.patch(exists_mock_path) as m_exists: m_exists.return_value = False devpath = wrap_and_call( 'cloudinit.config.cc_resizefs.util', { 'is_container': { 'return_value': False }, 'get_mount_info': { 'side_effect': fake_mount_info }, 'get_cmdline': { 'return_value': 'BOOT_IMAGE=/vmlinuz.efi' } }, maybe_get_writable_device_path, '/dev/root', info, LOG) self.assertIsNone(devpath) logs = self.logs.getvalue() self.assertIn("WARNING: Unable to find device '/dev/root'", logs)
def test_status_returns_done_long(self): '''Long format of done status includes datasource info.''' ensure_file(self.tmp_path('result.json', self.new_root)) write_json( self.status_file, {'v1': {'stage': None, 'datasource': ( 'DataSourceNoCloud [seed=/var/.../seed/nocloud-net]' '[dsmode=net]'), 'init': {'start': 124.567, 'finished': 125.678}, 'init-local': {'start': 123.45, 'finished': 123.46}}}) cmdargs = myargs(long=True, wait=False) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: retcode = wrap_and_call( 'cloudinit.cmd.status', {'_is_cloudinit_disabled': (False, ''), 'Init': {'side_effect': self.init_class}}, status.handle_status_args, 'ignored', cmdargs) self.assertEqual(0, retcode) expected = dedent('''\ status: done time: Thu, 01 Jan 1970 00:02:05 +0000 detail: DataSourceNoCloud [seed=/var/.../seed/nocloud-net][dsmode=net] ''') self.assertEqual(expected, m_stdout.getvalue())
def test_status_returns_done(self): '''Reports done when stage is None and all stages are finished.''' write_json( self.status_file, { 'v1': { 'stage': None, 'datasource': ('DataSourceNoCloud [seed=/var/.../seed/nocloud-net]' '[dsmode=net]'), 'blah': { 'finished': 123.456 }, 'init': { 'errors': [], 'start': 124.567, 'finished': 125.678 }, 'init-local': { 'start': 123.45, 'finished': 123.46 } } }) cmdargs = myargs(long=False, wait=False) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: retcode = wrap_and_call( 'cloudinit.cmd.status', { '_is_cloudinit_disabled': (False, ''), 'Init': { 'side_effect': self.init_class } }, status.handle_status_args, 'ignored', cmdargs) self.assertEqual(0, retcode) self.assertEqual('status: done\n', m_stdout.getvalue())
def test_status_wait_blocks_until_error(self): '''Specifying wait will poll every 1/4 second until error state.''' running_json = { 'v1': {'stage': 'init', 'init': {'start': 124.456, 'finished': None}, 'init-local': {'start': 123.45, 'finished': 123.46}}} error_json = { 'v1': {'stage': None, 'init': {'errors': ['error1'], 'start': 124.456, 'finished': 125.678}, 'init-local': {'start': 123.45, 'finished': 123.46}}} self.sleep_calls = 0 def fake_sleep(interval): self.assertEqual(0.25, interval) self.sleep_calls += 1 if self.sleep_calls == 2: write_json(self.status_file, running_json) elif self.sleep_calls == 3: write_json(self.status_file, error_json) cmdargs = myargs(long=False, wait=True) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: retcode = wrap_and_call( 'cloudinit.cmd.status', {'sleep': {'side_effect': fake_sleep}, '_is_cloudinit_disabled': (False, ''), 'Init': {'side_effect': self.init_class}}, status.handle_status_args, 'ignored', cmdargs) self.assertEqual(1, retcode) self.assertEqual(4, self.sleep_calls) self.assertEqual('....\nstatus: error\n', m_stdout.getvalue())
def test_get_data_vmware_customization_sys_cfg_disabled(self): """When vmware customization is disabled via sys_cfg and no meta data is found, log a message. """ paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource(sys_cfg={ 'disable_vmware_customization': True, 'datasource': { 'OVF': { 'allow_raw_data': True } } }, distro={}, paths=paths) conf_file = self.tmp_path('test-cust', self.tdir) conf_content = dedent("""\ [MISC] MARKER-ID = 12345345 """) util.write_file(conf_file, conf_content) retcode = wrap_and_call( 'cloudinit.sources.DataSourceOVF', { 'dmi.read_dmi_data': 'vmware', 'transport_iso9660': NOT_FOUND, 'transport_vmware_guestinfo': NOT_FOUND, 'util.del_dir': True, 'search_file': self.tdir, 'wait_for_imc_cfg_file': conf_file }, ds.get_data) self.assertFalse(retcode, 'Expected False return from ds.get_data') self.assertIn('DEBUG: Customization using VMware config is disabled.', self.logs.getvalue())
def test_status_on_errors_long(self): '''Long format of error status includes all error messages.''' write_json( self.status_file, {'v1': {'stage': None, 'datasource': ( 'DataSourceNoCloud [seed=/var/.../seed/nocloud-net]' '[dsmode=net]'), 'init': {'errors': ['error1'], 'start': 124.567, 'finished': 125.678}, 'init-local': {'errors': ['error2', 'error3'], 'start': 123.45, 'finished': 123.46}}}) cmdargs = myargs(long=True, wait=False) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: retcode = wrap_and_call( 'cloudinit.cmd.status', {'_is_cloudinit_disabled': (False, ''), 'Init': {'side_effect': self.init_class}}, status.handle_status_args, 'ignored', cmdargs) self.assertEqual(1, retcode) expected = dedent('''\ status: error time: Thu, 01 Jan 1970 00:02:05 +0000 detail: error1 error2 error3 ''') self.assertEqual(expected, m_stdout.getvalue())
def test_main_init_run_net_runs_modules(self): """Modules like write_files are run in 'net' mode.""" cmdargs = myargs(debug=False, files=None, force=False, local=False, reporter=None, subcommand='init') (_item1, item2) = wrap_and_call( 'cloudinit.cmd.main', { 'util.close_stdin': True, 'netinfo.debug_info': 'my net debug info', 'util.fixup_output': ('outfmt', 'errfmt') }, main.main_init, 'init', cmdargs) self.assertEqual([], item2) # Instancify is called instance_id_path = 'var/lib/cloud/data/instance-id' self.assertEqual( 'iid-datasource-none\n', os.path.join( load_file(os.path.join(self.new_root, instance_id_path)))) # modules are run (including write_files) self.assertEqual( 'blah', load_file(os.path.join(self.new_root, 'etc/blah.ini'))) expected_logs = [ 'network config is disabled by fallback', # apply_network_config 'my net debug info', # netinfo.debug_info 'no previous run detected' ] for log in expected_logs: self.assertIn(log, self.stderr.getvalue())
def test_status_returns_running_long_format(self): '''Long format reports the stage in which we are running.''' write_json( self.status_file, { 'v1': { 'stage': 'init', 'init': { 'start': 124.456, 'finished': None }, 'init-local': { 'start': 123.45, 'finished': 123.46 } } }) cmdargs = myargs(long=True, wait=False) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: retcode = wrap_and_call( 'cloudinit.cmd.status', { '_is_cloudinit_disabled': (False, ''), 'Init': { 'side_effect': self.init_class } }, status.handle_status_args, 'ignored', cmdargs) self.assertEqual(0, retcode) expected = dedent('''\ status: running time: Thu, 01 Jan 1970 00:02:04 +0000 detail: Running in stage: init ''') self.assertEqual(expected, m_stdout.getvalue())
def test_only_builtin_gets_builtin(self): ret = helpers.wrap_and_call( 'cloudinit.stages', {'util.read_conf_with_confd': None, 'util.read_conf_from_cmdline': None, 'read_runtime_config': {'return_value': {}}}, stages.fetch_base_config) self.assertEqual(util.get_builtin_cfg(), ret)
def test_get_data_cloudinit_userdata(self): """Test user data can be loaded to cloud-init user data. """ paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource(sys_cfg={'disable_vmware_customization': False}, distro={}, paths=paths) # Prepare the conf file conf_file = self.tmp_path('test-cust', self.tdir) conf_content = dedent("""\ [CLOUDINIT] METADATA = test-meta USERDATA = test-user """) util.write_file(conf_file, conf_content) # Prepare the meta data file metadata_file = self.tmp_path('test-meta', self.tdir) metadata_content = dedent("""\ instance-id: cloud-vm local-hostname: my-host.domain.com network: version: 2 ethernets: nics: match: name: ens* dhcp4: yes """) util.write_file(metadata_file, metadata_content) # Prepare the user data file userdata_file = self.tmp_path('test-user', self.tdir) userdata_content = "This is the user data" util.write_file(userdata_file, userdata_content) with mock.patch(MPATH + 'set_customization_status', return_value=('msg', b'')): result = wrap_and_call( 'cloudinit.sources.DataSourceOVF', { 'dmi.read_dmi_data': 'vmware', 'util.del_dir': True, 'search_file': self.tdir, 'wait_for_imc_cfg_file': conf_file, 'collect_imc_file_paths': [self.tdir + '/test-meta', self.tdir + '/test-user', ''], 'get_nics_to_enable': '' }, ds._get_data) self.assertTrue(result) self.assertEqual("cloud-vm", ds.metadata['instance-id']) self.assertEqual(userdata_content, ds.userdata_raw)
def test_handler_writes_merged_client_config_file_with_defaults(self): """Merge and write options from LSC_CLIENT_CFG_FILE with defaults.""" # Write existing sparse client.conf file util.write_file(self.conf, '[client]\ncomputer_title = My PC\n') mycloud = self._get_cloud('ubuntu') cfg = {'landscape': {'client': {}}} expected = {'client': { 'log_level': 'info', 'url': 'https://landscape.canonical.com/message-system', 'ping_url': 'http://landscape.canonical.com/ping', 'data_path': '/var/lib/landscape/client', 'computer_title': 'My PC'}} wrap_and_call( 'cloudinit.config.cc_landscape', {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) self.assertEqual(expected, dict(ConfigObj(self.conf))) self.assertIn( 'Wrote landscape config file to {0}'.format(self.conf), self.logs.getvalue())
def test_maybe_get_writable_device_path_none_on_overlayroot(self): """When devpath is overlayroot (on MAAS), is_dev_writable is False.""" info = 'does not matter' devpath = wrap_and_call( 'cloudinit.config.cc_resizefs.util', {'is_container': {'return_value': False}}, maybe_get_writable_device_path, 'overlayroot', info, LOG) self.assertIsNone(devpath) self.assertIn( "Not attempting to resize devpath 'overlayroot'", self.logs.getvalue())
def test_maybe_get_writable_device_path_does_not_exist_in_container(self): """When devpath does not exist in a container, log a debug message.""" info = 'dev=/dev/I/dont/exist mnt_point=/ path=/dev/none' devpath = wrap_and_call( 'cloudinit.config.cc_resizefs.util', {'is_container': {'return_value': True}}, maybe_get_writable_device_path, '/dev/I/dont/exist', info, LOG) self.assertIsNone(devpath) self.assertIn( "DEBUG: Device '/dev/I/dont/exist' did not exist in container." ' cannot resize: %s' % info, self.logs.getvalue())
def test_maybe_get_writable_device_path_does_not_exist(self): """When devpath does not exist, a warning is logged.""" info = 'dev=/dev/I/dont/exist mnt_point=/ path=/dev/none' devpath = wrap_and_call( 'cloudinit.config.cc_resizefs.util', {'is_container': {'return_value': False}}, maybe_get_writable_device_path, '/dev/I/dont/exist', info, LOG) self.assertIsNone(devpath) self.assertIn( "WARNING: Device '/dev/I/dont/exist' did not exist." ' cannot resize: %s' % info, self.logs.getvalue())
def test_handle_warns_on_undiscoverable_root_path_in_commandline(self): """handle noops when the root path is not found on the commandline.""" cfg = {'resize_rootfs': True} exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists' def fake_mount_info(path, log): self.assertEqual('/', path) self.assertEqual(LOG, log) return ('/dev/root', 'ext4', '/') with mock.patch(exists_mock_path) as m_exists: m_exists.return_value = False wrap_and_call( 'cloudinit.config.cc_resizefs.util', {'is_container': {'return_value': False}, 'get_mount_info': {'side_effect': fake_mount_info}, 'get_cmdline': {'return_value': 'BOOT_IMAGE=/vmlinuz.efi'}}, handle, 'cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) logs = self.logs.getvalue() self.assertIn("WARNING: Unable to find device '/dev/root'", logs)
def test_remove_artifacts_preserves_logs(self): """remove_artifacts leaves logs when remove_logs is False.""" write_file(self.log1, 'cloud-init-log') write_file(self.log2, 'cloud-init-output-log') retcode = wrap_and_call( 'cloudinit.cmd.clean', {'Init': {'side_effect': self.init_class}}, clean.remove_artifacts, remove_logs=False) self.assertTrue(os.path.exists(self.log1), 'Missing expected file') self.assertTrue(os.path.exists(self.log2), 'Missing expected file') self.assertEqual(0, retcode)
def test_add_assertions_adds_assertions_as_list(self, m_subp): """When provided with a list, add_assertions adds all assertions.""" self.assertEqual( ASSERTIONS_FILE, '/var/lib/cloud/instance/snapd.assertions') assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) assertions = [SYSTEM_USER_ASSERTION, ACCOUNT_ASSERTION] wrap_and_call( 'cloudinit.config.cc_snap', {'ASSERTIONS_FILE': {'new': assert_file}}, add_assertions, assertions) self.assertIn( 'Importing user-provided snap assertions', self.logs.getvalue()) self.assertIn( 'sertions', self.logs.getvalue()) self.assertEqual( [mock.call(['snap', 'ack', assert_file], capture=True)], m_subp.call_args_list) compare_file = self.tmp_path('comparison', dir=self.tmp) util.write_file(compare_file, '\n'.join(assertions).encode('utf-8')) self.assertEqual( util.load_file(compare_file), util.load_file(assert_file))
def test_get_data_false_on_none_dmi_data(self): """When dmi for system-product-name is None, get_data returns False.""" paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource(sys_cfg={}, distro={}, paths=paths) retcode = wrap_and_call( 'cloudinit.sources.DataSourceOVF', {'util.read_dmi_data': None, 'transport_iso9660': NOT_FOUND, 'transport_vmware_guestinfo': NOT_FOUND}, ds.get_data) self.assertFalse(retcode, 'Expected False return from ds.get_data') self.assertIn( 'DEBUG: No system-product-name found', self.logs.getvalue())
def test_main_init_run_net_calls_set_hostname_when_metadata_present(self): """When local-hostname metadata is present, call cc_set_hostname.""" self.cfg['datasource'] = { 'None': {'metadata': {'local-hostname': 'md-hostname'}}} cloud_cfg = yaml_dumps(self.cfg) write_file(self.cloud_cfg_file, cloud_cfg) cmdargs = myargs( debug=False, files=None, force=False, local=False, reporter=None, subcommand='init') def set_hostname(name, cfg, cloud, log, args): self.assertEqual('set-hostname', name) updated_cfg = copy.deepcopy(self.cfg) updated_cfg.update( {'def_log_file': '/var/log/cloud-init.log', 'log_cfgs': [], 'syslog_fix_perms': [ 'syslog:adm', 'root:adm', 'root:wheel', 'root:root' ], 'vendor_data': {'enabled': True, 'prefix': []}}) updated_cfg.pop('system_info') self.assertEqual(updated_cfg, cfg) self.assertEqual(main.LOG, log) self.assertIsNone(args) (_item1, item2) = wrap_and_call( 'cloudinit.cmd.main', {'util.close_stdin': True, 'netinfo.debug_info': 'my net debug info', 'cc_set_hostname.handle': {'side_effect': set_hostname}, 'util.fixup_output': ('outfmt', 'errfmt')}, main.main_init, 'init', cmdargs) self.assertEqual([], item2) # Instancify is called instance_id_path = 'var/lib/cloud/data/instance-id' self.assertEqual( 'iid-datasource-none\n', os.path.join(load_file( os.path.join(self.new_root, instance_id_path)))) # modules are run (including write_files) self.assertEqual( 'blah', load_file(os.path.join(self.new_root, 'etc/blah.ini'))) expected_logs = [ 'network config is disabled by fallback', # apply_network_config 'my net debug info', # netinfo.debug_info 'no previous run detected' ] for log in expected_logs: self.assertIn(log, self.stderr.getvalue())
def test_cmdline_overrides_defaults(self): builtin = util.get_builtin_cfg() test_key = sorted(builtin)[0] test_value = 'test' cmdline = {test_key: test_value} ret = helpers.wrap_and_call( 'cloudinit.stages', {'util.read_conf_from_cmdline': {'return_value': cmdline}, 'util.read_conf_with_confd': None, 'read_runtime_config': None}, stages.fetch_base_config) self.assertEqual(ret.get(test_key), test_value) builtin[test_key] = test_value self.assertEqual(ret, builtin)
def test_cmdline_overrides_confd_runtime_and_defaults(self): builtin = {'key1': 'value0', 'key3': 'other2'} conf_d = {'key1': 'value1', 'key2': 'other1'} cmdline = {'key3': 'other3', 'key2': 'other2'} runtime = {'key3': 'runtime3'} ret = helpers.wrap_and_call( 'cloudinit.stages', {'util.read_conf_with_confd': {'return_value': conf_d}, 'util.get_builtin_cfg': {'return_value': builtin}, 'read_runtime_config': {'return_value': runtime}, 'util.read_conf_from_cmdline': {'return_value': cmdline}}, stages.fetch_base_config) self.assertEqual(ret, {'key1': 'value1', 'key2': 'other2', 'key3': 'other3'})
def test_maybe_get_writable_device_path_non_block_on_container(self): """When device is non-block device in container, emit debug log.""" fake_devpath = self.tmp_path('dev/readwrite') util.write_file(fake_devpath, '', mode=0o600) # read-write info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath) devpath = wrap_and_call( 'cloudinit.config.cc_resizefs.util', {'is_container': {'return_value': True}}, maybe_get_writable_device_path, fake_devpath, info, LOG) self.assertIsNone(devpath) self.assertIn( "DEBUG: device '{0}' not a block device in container." ' cannot resize'.format(fake_devpath), self.logs.getvalue())
def test_handler_installs_client_and_creates_config_file(self): """Write landscape client.conf and install landscape-client.""" mycloud = self._get_cloud('ubuntu') cfg = {'landscape': {'client': {}}} expected = {'client': { 'log_level': 'info', 'url': 'https://landscape.canonical.com/message-system', 'ping_url': 'http://landscape.canonical.com/ping', 'data_path': '/var/lib/landscape/client'}} mycloud.distro = mock.MagicMock() wrap_and_call( 'cloudinit.config.cc_landscape', {'LSC_CLIENT_CFG_FILE': {'new': self.conf}, 'LS_DEFAULT_FILE': {'new': self.default_file}}, cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) self.assertEqual( [mock.call('landscape-client')], mycloud.distro.install_packages.call_args) self.assertEqual(expected, dict(ConfigObj(self.conf))) self.assertIn( 'Wrote landscape config file to {0}'.format(self.conf), self.logs.getvalue()) default_content = util.load_file(self.default_file) self.assertEqual('RUN=1\n', default_content)
def test_remove_artifacts_returns_one_on_errors(self): """remove_artifacts returns non-zero on failure and prints an error.""" ensure_dir(self.artifact_dir) ensure_dir(os.path.join(self.artifact_dir, 'dir1')) with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: retcode = wrap_and_call( 'cloudinit.cmd.clean', {'del_dir': {'side_effect': OSError('oops')}, 'Init': {'side_effect': self.init_class}}, clean.remove_artifacts, remove_logs=False) self.assertEqual(1, retcode) self.assertEqual( 'ERROR: Could not remove %s/dir1: oops\n' % self.artifact_dir, m_stderr.getvalue())
def test_order_precedence_is_builtin_system_runtime_cmdline(self): builtin = {'key1': 'builtin0', 'key3': 'builtin3'} conf_d = {'key1': 'confd1', 'key2': 'confd2', 'keyconfd1': 'kconfd1'} runtime = {'key1': 'runtime1', 'key2': 'runtime2'} cmdline = {'key1': 'cmdline1'} ret = helpers.wrap_and_call( 'cloudinit.stages', {'util.read_conf_with_confd': {'return_value': conf_d}, 'util.get_builtin_cfg': {'return_value': builtin}, 'util.read_conf_from_cmdline': {'return_value': cmdline}, 'read_runtime_config': {'return_value': runtime}, }, stages.fetch_base_config) self.assertEqual(ret, {'key1': 'cmdline1', 'key2': 'runtime2', 'key3': 'builtin3', 'keyconfd1': 'kconfd1'})
def test_get_data_no_vmware_customization_disabled(self): """When vmware customization is disabled via sys_cfg log a message.""" paths = Paths({'cloud_dir': self.tdir}) ds = self.datasource( sys_cfg={'disable_vmware_customization': True}, distro={}, paths=paths) retcode = wrap_and_call( 'cloudinit.sources.DataSourceOVF', {'util.read_dmi_data': 'vmware', 'transport_iso9660': NOT_FOUND, 'transport_vmware_guestinfo': NOT_FOUND}, ds.get_data) self.assertFalse(retcode, 'Expected False return from ds.get_data') self.assertIn( 'DEBUG: Customization for VMware platform is disabled.', self.logs.getvalue())
def test_remove_artifacts_removes_unlinks_symlinks(self): """remove_artifacts cleans artifacts dir unlinking any symlinks.""" dir1 = os.path.join(self.artifact_dir, 'dir1') ensure_dir(dir1) symlink = os.path.join(self.artifact_dir, 'mylink') sym_link(dir1, symlink) retcode = wrap_and_call( 'cloudinit.cmd.clean', {'Init': {'side_effect': self.init_class}}, clean.remove_artifacts, remove_logs=False) self.assertEqual(0, retcode) for path in (dir1, symlink): self.assertFalse( os.path.exists(path), 'Unexpected {0} dir'.format(path))
def test_mkstemp_default_root(self): """mkstemp creates a secure tempfile in /run/cloud-init for root.""" calls = [] def fake_mkstemp(*args, **kwargs): calls.append(kwargs) return '/fake/return/path' retval = wrap_and_call( 'cloudinit.temp_utils', {'os.getuid': 0, 'tempfile.mkstemp': {'side_effect': fake_mkstemp}, '_TMPDIR': {'new': None}, 'os.path.isdir': True}, mkstemp) self.assertEqual('/fake/return/path', retval) self.assertEqual([{'dir': '/run/cloud-init/tmp'}], calls)
def test_mkdtemp_default_non_root_needs_exe(self): """mkdtemp creates a dir under /var/tmp/cloud-init when needs_exe.""" calls = [] def fake_mkdtemp(*args, **kwargs): calls.append(kwargs) return '/fake/return/path' retval = wrap_and_call( 'cloudinit.temp_utils', {'os.getuid': 1000, 'tempfile.mkdtemp': {'side_effect': fake_mkdtemp}, '_TMPDIR': {'new': None}, 'os.path.isdir': True}, mkdtemp, needs_exe=True) self.assertEqual('/fake/return/path', retval) self.assertEqual([{'dir': '/var/tmp/cloud-init'}], calls)