def test_gen_defaults_uses_only_ready(self): """gen_defaults should only use ready machines""" mock_maas_state = MagicMock() mock_maas_state.machines.return_value = [] c = Config() c.setopt('storage_backend', 'none') pc = PlacementController(config=c, maas_state=mock_maas_state) # reset the mock to avoid looking at calls from # PlacementController.__init__(). mock_maas_state.reset_mock() pc.gen_defaults() # we simply check the first call because we know that # follow-on calls are from calls to get_assignments and do # not affect machines used for defaults self.assertEqual(mock_maas_state.machines.mock_calls[0], call(MaasMachineStatus.READY))
class WaitForDeployedServicesReadyCoreTestCase(unittest.TestCase): """ Tests core.wait_for_deployed_services_ready to make sure waiting for services to start are handled properly. """ def setUp(self): self.conf = Config({}) self.mock_ui = MagicMock(name='ui') self.mock_log = MagicMock(name='log') self.mock_loop = MagicMock(name='loop') self.conf.setopt('headless', False) self.dc = Controller( ui=self.mock_ui, config=self.conf, loop=self.mock_loop) self.dc.initialize = MagicMock() self.dc.juju_state = JujuState(juju=MagicMock()) self.dc.juju_state.all_agents_started = MagicMock() def test_validate_services_ready(self): """ Verifies wait_for_deployed_services_ready time.sleep should not be called here as all services are in a started state. """ self.dc.juju_state.all_agents_started.return_value = True with patch('cloudinstall.core.time.sleep') as mock_sleep: self.dc.wait_for_deployed_services_ready() self.assertEqual(len(mock_sleep.mock_calls), 0) def test_validate_services_some_ready(self): """ Verifies wait_for_deployed_services_ready against some of the services in started state Here we test if time.sleep was called twice due to some services being in an installing and allocating state. """ self.dc.juju_state.all_agents_started.side_effect = [ False, False, True, True] with patch('cloudinstall.core.time.sleep') as mock_sleep: self.dc.wait_for_deployed_services_ready() print(mock_sleep.mock_calls) self.assertEqual(len(mock_sleep.mock_calls), 2)
def test_gen_single_backends(self): "gen_single has no storage backend by default" def find_charm(cn, defs): allcharms = [] for mname, ad in defs.items(): for atype, charmclasses in ad.items(): allcharms += charmclasses return cn in allcharms c = Config() pc = PlacementController(config=c) # default storage_backend is 'none' c.setopt('storage_backend', 'none') defaults = pc.gen_single() self.assertFalse(find_charm(CharmSwiftProxy, defaults)) self.assertFalse(find_charm(CharmSwift, defaults)) self.assertFalse(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults)) c.setopt('storage_backend', 'swift') defaults = pc.gen_single() self.assertTrue(find_charm(CharmSwiftProxy, defaults)) self.assertTrue(find_charm(CharmSwift, defaults)) self.assertFalse(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults)) c.setopt('storage_backend', 'ceph') defaults = pc.gen_single() self.assertFalse(find_charm(CharmSwiftProxy, defaults)) self.assertFalse(find_charm(CharmSwift, defaults)) self.assertTrue(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults))
class ControllerStateTestCase(unittest.TestCase): def setUp(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config({}, tempf.name, save_backups=False) self.bad_states_int = [5, 6, 7] self.good_states_int = [0, 1, 2] def test_set_controller_state(self): """ Validate config controller state """ for i in self.bad_states_int: self.conf.setopt('current_state', i) with self.assertRaises(ValueError): s = self.conf.getopt('current_state') ControllerState(s) for i in self.good_states_int: self.conf.setopt('current_state', i) s = self.conf.getopt('current_state') self.assertEqual(ControllerState(s), i)
class MultiInstallNewMaasTestCase(unittest.TestCase): def setUp(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config({}, tempf.name) self.conf.setopt('openstack_password', 'ampersand&') def make_installer(self, loop=None, dc=None): if dc is None: dc = MagicMock(name="display_controller") if loop is None: loop = MagicMock(name="loop") self.installer = MultiInstallNewMaas( loop, dc, self.conf) def _create_superuser(self, raises): expected = ("maas-region-admin createadmin --username root " "--password 'ampersand&' " "--email [email protected]") self.make_installer() with patch('cloudinstall.multi_install.utils') as mock_utils: if raises: mock_utils.get_command_output.return_value = {'status': -1} self.assertRaises(MaasInstallError, self.installer.create_superuser) else: mock_utils.get_command_output.return_value = {'status': 0} self.installer.create_superuser() mock_utils.get_command_output.assert_called_with(expected) def test_create_superuser_raises(self): self._create_superuser(True) def test_create_superuser_ok(self): self._create_superuser(False)
class InstallStateTestCase(unittest.TestCase): def setUp(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config({}, tempf.name) self.bad_states_int = [5, 6, 7] self.good_states_int = [0, 1] def test_install_state(self): """ Validate config install state """ for i in self.bad_states_int: self.conf.setopt('current_state', i) with self.assertRaises(ValueError): s = self.conf.getopt('current_state') InstallState(s) for i in self.good_states_int: self.conf.setopt('current_state', i) s = self.conf.getopt('current_state') self.assertEqual(InstallState(s), i)
class MultiInstallNewMaasTestCase(unittest.TestCase): def setUp(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config({}, tempf.name) self.conf.setopt('openstack_password', 'ampersand&') def make_installer(self, loop=None, dc=None): if dc is None: dc = MagicMock(name="display_controller") if loop is None: loop = MagicMock(name="loop") self.installer = MultiInstallNewMaas(loop, dc, self.conf) def _create_superuser(self, raises): expected = ("maas-region-admin createadmin --username root " "--password 'ampersand&' " "--email [email protected]") self.make_installer() with patch('cloudinstall.multi_install.utils') as mock_utils: if raises: mock_utils.get_command_output.return_value = {'status': -1} self.assertRaises(MaasInstallError, self.installer.create_superuser) else: mock_utils.get_command_output.return_value = {'status': 0} self.installer.create_superuser() mock_utils.get_command_output.assert_called_with(expected) def test_create_superuser_raises(self): self._create_superuser(True) def test_create_superuser_ok(self): self._create_superuser(False)
class TestRenderCharmConfig(unittest.TestCase): def setUp(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.config = Config({}, tempf.name) type(self.config).cfg_path = PropertyMock(return_value='fake_cfg_path') self.config.setopt('openstack_password', 'fake_pw') self.ltp = patch('cloudinstall.utils.load_template') self.mock_load_template = self.ltp.start() self.mock_load_template.side_effect = source_tree_template_loader def tearDown(self): self.ltp.stop() def _do_test_osrel(self, optsvalue, expected, mockspew): "check that opts.openstack_release is rendered correctly" self.config.setopt('openstack_release', optsvalue) render_charm_config(self.config) (fake_path, generated_yaml), kwargs = mockspew.call_args d = yaml.load(generated_yaml) print(d) for oscharmname in ['nova-cloud-controller', 'glance', 'openstack-dashboard', 'keystone', 'swift-proxy']: if expected is None: self.assertTrue(oscharmname not in d or 'openstack-origin' not in d[oscharmname]) else: self.assertEqual(d[oscharmname]['openstack-origin'], expected) def test_render_openstack_release_given(self, mockspew): self._do_test_osrel('klaxon', 'cloud:trusty-klaxon', mockspew) def _do_test_multiplier(self, is_single, mockspew, expected=None): if is_single: self.config.setopt('install_type', 'Single') else: self.config.setopt('install_type', 'Multi') self.config.setopt('openstack_release', 'klaxon') render_charm_config(self.config) (fake_path, generated_yaml), kwargs = mockspew.call_args d = yaml.load(generated_yaml) wmul = d['nova-cloud-controller'].get('worker-multiplier', None) self.assertEqual(wmul, expected) wmul = d['glance'].get('worker-multiplier', None) self.assertEqual(wmul, expected) wmul = d['keystone'].get('worker-multiplier', None) self.assertEqual(wmul, expected) def test_render_worker_multiplier_multi(self, mockspew): self._do_test_multiplier(False, mockspew) def test_render_worker_multiplier_single(self, mockspew): self._do_test_multiplier(True, mockspew, expected=1) def test_charmconfig_custom_merge(self, mockspew): """ Verify rightmost custom charm config dictionary does not overwrite untouched items in rendered charmconfig """ charm_custom = {'swift-proxy': {'replicas': 15}, 'mysql': {'dataset-size': '2048M'}} charm_conf = yaml.load(slurp(os.path.join(DATA_DIR, 'charmconf.yaml'))) merged_dicts = merge_dicts(charm_conf, charm_custom) self.assertEqual(merged_dicts['mysql']['max-connections'], 25000) self.assertEqual(merged_dicts['swift-proxy']['zone-assignment'], 'auto')
class TestRenderCharmConfig(unittest.TestCase): def setUp(self): with NamedTemporaryFile(mode="w+", encoding="utf-8") as tempf: # Override config file to save to self.config = Config({}, tempf.name) type(self.config).cfg_path = PropertyMock(return_value="fake_cfg_path") self.config.setopt("openstack_password", "fake_pw") self.ltp = patch("cloudinstall.utils.load_template") self.mock_load_template = self.ltp.start() self.mock_load_template.side_effect = source_tree_template_loader def tearDown(self): self.ltp.stop() def _do_test_osrel(self, series, optsvalue, expected, mockspew): "check that opts.openstack_release is rendered correctly" self.config.setopt("openstack_release", optsvalue) self.config.setopt("ubuntu_series", series) render_charm_config(self.config) (fake_path, generated_yaml), kwargs = mockspew.call_args d = yaml.load(generated_yaml) print(d) for oscharmname in ["nova-cloud-controller", "glance", "openstack-dashboard", "keystone", "swift-proxy"]: if expected is None: self.assertTrue(oscharmname not in d or "openstack-origin" not in d[oscharmname]) else: self.assertEqual(d[oscharmname]["openstack-origin"], expected) def test_render_openstack_release_given(self, mockspew): self._do_test_osrel("trusty", "klaxon", "cloud:trusty-klaxon", mockspew) def _do_test_multiplier(self, is_single, mockspew, expected=None): if is_single: self.config.setopt("install_type", "Single") else: self.config.setopt("install_type", "Multi") self.config.setopt("openstack_release", "klaxon") render_charm_config(self.config) (fake_path, generated_yaml), kwargs = mockspew.call_args d = yaml.load(generated_yaml) wmul = d["nova-cloud-controller"].get("worker-multiplier", None) self.assertEqual(wmul, expected) wmul = d["glance"].get("worker-multiplier", None) self.assertEqual(wmul, expected) wmul = d["keystone"].get("worker-multiplier", None) self.assertEqual(wmul, expected) def test_render_worker_multiplier_multi(self, mockspew): self._do_test_multiplier(False, mockspew) def test_render_worker_multiplier_single(self, mockspew): self._do_test_multiplier(True, mockspew, expected=1) def test_charmconfig_custom_merge(self, mockspew): """ Verify rightmost custom charm config dictionary does not overwrite untouched items in rendered charmconfig """ charm_custom = {"swift-proxy": {"replicas": 15}, "mysql": {"dataset-size": "2048M"}} charm_conf = yaml.load(slurp(os.path.join(DATA_DIR, "charmconf.yaml"))) merged_dicts = merge_dicts(charm_conf, charm_custom) self.assertEqual(merged_dicts["mysql"]["max-connections"], 25000) self.assertEqual(merged_dicts["swift-proxy"]["zone-assignment"], "auto")
class LandscapeInstallFinalTestCase(unittest.TestCase): def setUp(self): self.mock_multi_installer = MagicMock() self.mock_display_controller = MagicMock() self.loop = MagicMock() with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config({}, tempf.name) def make_installer_with_config(self, landscape_creds=None, maas_creds=None): if landscape_creds is None: landscape_creds = dict(admin_name="fakeadminname", admin_email="*****@*****.**", system_email="*****@*****.**", maas_server='fake.host', maas_apikey='fake:keyz:yo') self.conf.setopt('maascreds', dict(api_host='fake.host', api_key='fake:keyz:yo')) self.conf.setopt('landscapecreds', landscape_creds) pm_binpath = PropertyMock(return_value='mockbinpath') type(self.conf).bin_path = pm_binpath pm_cfgpath = PropertyMock(return_value='mockcfgpath') type(self.conf).cfg_path = pm_cfgpath lif = LandscapeInstallFinal(self.mock_multi_installer, self.mock_display_controller, self.conf, self.loop) self.installer = lif def test_run_configure_not_sudo_user(self, mock_utils): """Do not sudo -u $SUDO_USER when running landscape-configure, it will be 'root'. """ self.make_installer_with_config() mock_utils.get_command_output.return_value = {'status': '', 'output': ''} self.installer.run_configure_script() mock_utils.get_command_output.assert_called_with(ANY, timeout=None) def test_run_configure_quotes_config_values(self, mock_utils): cd = dict(admin_name="fake admin name with spaces", admin_email="[email protected]", system_email="[email protected]") self.make_installer_with_config(landscape_creds=cd) mock_utils.get_command_output.return_value = {'status': '', 'output': ''} self.installer.run_configure_script() expectedcmdstr = ("mockbinpath/configure-landscape " "--admin-email '{}' " "--admin-name '{}' " "--system-email '{}' " "--maas-host fake.host".format(cd['admin_email'], cd['admin_name'], cd['system_email'])) mock_utils.get_command_output.assert_called_with(expectedcmdstr, timeout=None) def test_run_configure_raises_on_error(self, mock_utils): self.make_installer_with_config() mock_utils.get_command_output.return_value = {'status': 1, 'output': 'failure'} self.assertRaises(Exception, self.installer.run_configure_script) def test_run_deployer_raises_on_error(self, mock_utils): self.make_installer_with_config() mock_utils.get_command_output.return_value = {'status': 1, 'output': 'failure'} self.assertRaises(Exception, self.installer.run_deployer) def test_run_deployer_has_no_timeout(self, mock_utils): self.make_installer_with_config() mock_utils.get_command_output.return_value = {'status': '', 'output': 'failure'} self.installer.run_deployer() mock_utils.get_command_output.assert_called_with(ANY, timeout=None, user_sudo=ANY)
class ServicesListTestCase(unittest.TestCase): def setUp(self): self.mock_maas_state = MagicMock() with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: utils.spew(tempf.name, yaml.dump(dict())) self.conf = Config({}, tempf.name) self.conf.setopt('storage_backend', 'none') self.pc = PlacementController(self.mock_maas_state, self.conf) self.mock_machine = make_fake_machine('machine1', {'cpu_count': 3}) self.mock_machine2 = make_fake_machine('machine2') self.mock_machine3 = make_fake_machine('machine3') self.mock_machines = [self.mock_machine] self.mock_maas_state.machines.return_value = self.mock_machines self.actions = [] self.sub_actions = [] def test_widgets_config(self, mock_servicewidgetclass): for show_constraints in [False, True]: sl = ServicesList(self.pc, self.actions, self.sub_actions, show_constraints=show_constraints) mock_servicewidgetclass.assert_any_call( CharmNovaCompute, self.pc, self.actions, show_constraints, show_placements=sl.show_placements) mock_servicewidgetclass.reset_mock() def test_no_machine_no_constraints(self, mock_servicewidgetclass): with patch.object(self.pc, 'charm_classes') as mock_classesfunc: fc = MagicMock(name='fakeclass1') fc.required_num_units.return_value = 1 fc.constraints = {'cpu_count': 1000} mock_classesfunc.return_value = [fc] sl = ServicesList(self.pc, self.actions, self.sub_actions) self.assertEqual(len(sl.service_widgets), 1) def test_machine_checks_constraints(self, mock_servicewidgetclass): mock_machine = make_fake_machine('fm', {'cpu_count': 0, 'storage': 0, 'memory': 0}) sl = ServicesList(self.pc, self.actions, self.sub_actions, machine=mock_machine) self.assertEqual(len(sl.service_widgets), 0) def test_do_not_show_assigned(self, mock_servicewidgetclass): mock_machine = make_fake_machine('fm', {'cpu_count': 0, 'storage': 0, 'memory': 0}) self.pc.assign(mock_machine, CharmNovaCompute, AssignmentType.LXC) sl = ServicesList(self.pc, self.actions, self.sub_actions, machine=mock_machine) classes = [sw.charm_class for sw in sl.service_widgets] self.assertTrue(CharmNovaCompute not in classes) def test_show_type(self, mock_servicewidgetclass): """Test combinations of show_type values. This tests three values of show_type with three return values for is_required(): all required, no required, and 1/3 required. It's all lumped in one test to consolidate setup. """ mock_sw1 = MagicMock(name='sw1') mock_sw1.charm_class.charm_name = 'cc1' mock_sw2 = MagicMock(name='sw2') mock_sw2.charm_class.charm_name = 'cc2' mock_sw3 = MagicMock(name='sw3') mock_sw3.charm_class.charm_name = 'cc3' mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] with patch.object(self.pc, 'get_charm_state') as mock_get_state: with patch.object(self.pc, 'charm_classes') as mock_classesfunc: mock_classesfunc.return_value = [MagicMock(name='fake-class-1', charm_name='cc1'), MagicMock(name='fake-class-2', charm_name='cc2'), MagicMock(name='fake-class-3', charm_name='cc3')] # First, test when all charms are required mock_get_state.return_value = (CharmState.REQUIRED, [], []) # rsl shows required charms rsl = ServicesList(self.pc, self.actions, self.sub_actions, machine=None, show_type='required') self.assertEqual(len(mock_get_state.mock_calls), 3) # should show all 3 self.assertEqual(len(rsl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] # usl shows ONLY un-required charms usl = ServicesList(self.pc, self.actions, self.sub_actions, machine=None, show_type='non-required') self.assertEqual(len(mock_get_state.mock_calls), 3) # should show 0 self.assertEqual(len(usl.service_widgets), 0) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] # asl has default show_type='all', showing all charms asl = ServicesList(self.pc, self.actions, self.sub_actions) self.assertEqual(len(mock_get_state.mock_calls), 3) # should show all 3 self.assertEqual(len(asl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] # next, test where no charms are required mock_get_state.return_value = (CharmState.OPTIONAL, [], []) rsl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show 0 charms self.assertEqual(len(rsl.service_widgets), 0) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] usl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show all 3 self.assertEqual(len(usl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] asl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should still show all 3 self.assertEqual(len(asl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] # next test two un-required and one required charm: mock_get_state.side_effect = [(CharmState.OPTIONAL, [], []), (CharmState.REQUIRED, [], []), (CharmState.OPTIONAL, [], [])] rsl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show 1: self.assertEqual(len(rsl.service_widgets), 1) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] mock_get_state.side_effect = [(CharmState.OPTIONAL, [], []), (CharmState.REQUIRED, [], []), (CharmState.OPTIONAL, [], [])] usl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show two self.assertEqual(len(usl.service_widgets), 2) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] mock_get_state.side_effect = [(CharmState.OPTIONAL, [], []), (CharmState.REQUIRED, [], []), (CharmState.OPTIONAL, [], [])] asl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should still show all three self.assertEqual(len(asl.service_widgets), 3)
class ServiceWidgetTestCase(unittest.TestCase): def setUp(self): self.mock_maas_state = MagicMock() with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: utils.spew(tempf.name, yaml.dump(dict())) self.conf = Config({}, tempf.name) self.conf.setopt('storage_backend', 'none') self.pc = PlacementController(self.mock_maas_state, self.conf) self.mock_machine = make_fake_machine('machine1') self.mock_machine_2 = make_fake_machine('machine2') self.mock_machines = [self.mock_machine, self.mock_machine_2] self.mock_maas_state.machines.return_value = self.mock_machines def test_required_label_shown(self): """Widget showing a required charm should have a label showing how many units are required""" w = ServiceWidget(CharmKeystone, self.pc) self.assertTrue(search_in_widget("0 of 1 placed", w)) def test_required_label_not_shown(self): """Widget showing a non-required charm should NOT have a label showing how many units are required. """ w = ServiceWidget(CharmJujuGui, self.pc) self.assertFalse(search_in_widget(".* of .* placed", w)) def test_show_placements(self): """Widget with show_placements set should show placements""" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) w = ServiceWidget(CharmNovaCompute, self.pc, show_placements=True) self.assertTrue(search_in_widget("LXC.*machine1-hostname", w)) def test_dont_show_placements(self): """Widget with show_placements set to FALSE should NOT show placements""" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) w = ServiceWidget(CharmNovaCompute, self.pc, show_placements=False) self.assertFalse(search_in_widget("LXC.*machine1-hostname", w)) def test_show_constraints(self): """Widget with show_constraints set should show constraints""" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) w = ServiceWidget(CharmNovaCompute, self.pc, show_constraints=True) conpat = ("constraints.*" + ".*".join(CharmNovaCompute.constraints.keys())) self.assertTrue(search_in_widget(conpat, w)) def test_dont_show_constraints(self): """Widget with show_constraints set to FALSE should NOT show constraints""" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) w = ServiceWidget(CharmNovaCompute, self.pc, show_constraints=False) self.assertFalse(search_in_widget("constraints", w)) def test_show_actions(self): """Actions should be shown as buttons""" fake_action_func = MagicMock() actions = [("fake-action", fake_action_func)] w = ServiceWidget(CharmNovaCompute, self.pc, actions=actions) self.assertTrue(search_in_widget("fake-action", w)) def test_actions_use_pred(self): """Action predicates control whether a button appears (disabled)""" # NOTE: this test assumes that disabled buttons are just the # button label with parentheses. fake_action_func = MagicMock() fake_pred = MagicMock() fake_pred.return_value = False actions = [(fake_pred, "fake-action", fake_action_func)] w = ServiceWidget(CharmNovaCompute, self.pc, actions=actions) self.assertTrue(search_in_widget("\(.*fake-action.*\)", w)) fake_pred.assert_called_with(CharmNovaCompute) fake_pred.return_value = True fake_pred.reset_mock() w.update() self.assertTrue(search_in_widget("<.*fake-action.*>", w)) fake_pred.assert_called_with(CharmNovaCompute)
class PlacementControllerTestCase(unittest.TestCase): def setUp(self): self.mock_maas_state = MagicMock() with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: utils.spew(tempf.name, yaml.dump(dict())) self.conf = Config({}, tempf.name) self.conf.setopt('storage_backend', 'none') self.pc = PlacementController(self.mock_maas_state, self.conf) self.mock_machine = MagicMock(name='machine1') pmid = PropertyMock(return_value='fake-instance-id-1') type(self.mock_machine).instance_id = pmid self.mock_machine_2 = MagicMock(name='machine2') pmid2 = PropertyMock(return_value='fake-instance-id-2') type(self.mock_machine_2).instance_id = pmid2 self.mock_machines = [self.mock_machine, self.mock_machine_2] self.mock_maas_state.machines.return_value = self.mock_machines def test_get_assignments_atype(self): self.assertEqual(0, len(self.pc.get_assignments(CharmNovaCompute))) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) md = self.pc.get_assignments(CharmNovaCompute) self.assertEqual(1, len(md)) self.assertEqual(2, len(md[AssignmentType.LXC])) def _do_test_simple_assign_type(self, assignment_type): self.pc.assign(self.mock_machine, CharmNovaCompute, assignment_type) print("assignments is {}".format(self.pc.assignments)) machines = self.pc.get_assignments(CharmNovaCompute) print('machines for charm is {}'.format(machines)) self.assertEqual(machines, {assignment_type: [self.mock_machine]}) ma = self.pc.assignments_for_machine(self.mock_machine) self.assertEqual(ma[assignment_type], [CharmNovaCompute]) def test_simple_assign_bare(self): self._do_test_simple_assign_type(AssignmentType.BareMetal) def test_simple_assign_lxc(self): self._do_test_simple_assign_type(AssignmentType.LXC) def test_simple_assign_kvm(self): self._do_test_simple_assign_type(AssignmentType.KVM) def test_assign_nonmulti(self): self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.LXC) self.assertEqual(self.pc.get_assignments(CharmKeystone), {AssignmentType.LXC: [self.mock_machine]}) self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertEqual(self.pc.get_assignments(CharmKeystone), {AssignmentType.KVM: [self.mock_machine]}) am = self.pc.assignments_for_machine(self.mock_machine) self.assertEqual(am[AssignmentType.KVM], [CharmKeystone]) self.assertEqual(am[AssignmentType.LXC], []) def test_assign_multi(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.assertEqual(self.pc.get_assignments(CharmNovaCompute), {AssignmentType.LXC: [self.mock_machine]}) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.assertEqual( self.pc.get_assignments(CharmNovaCompute), { AssignmentType.LXC: [self.mock_machine], AssignmentType.KVM: [self.mock_machine] }) ma = self.pc.assignments_for_machine(self.mock_machine) self.assertEqual(ma[AssignmentType.LXC], [CharmNovaCompute]) self.assertEqual(ma[AssignmentType.KVM], [CharmNovaCompute]) def test_remove_assignment_multi(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmNovaCompute, AssignmentType.LXC) mfc = self.pc.get_assignments(CharmNovaCompute) mfc_lxc = set(mfc[AssignmentType.LXC]) self.assertEqual(mfc_lxc, set(self.mock_machines)) self.pc.clear_assignments(self.mock_machine) self.assertEqual(self.pc.get_assignments(CharmNovaCompute), {AssignmentType.LXC: [self.mock_machine_2]}) def test_gen_defaults(self): satisfies_importstring = 'cloudinstall.placement.controller.satisfies' with patch(satisfies_importstring) as mock_satisfies: mock_satisfies.return_value = (True, ) defs = self.pc.gen_defaults( charm_classes=[CharmNovaCompute, CharmKeystone], maas_machines=[self.mock_machine, self.mock_machine_2]) m1_as = defs[self.mock_machine.instance_id] m2_as = defs[self.mock_machine_2.instance_id] self.assertEqual(m1_as[AssignmentType.BareMetal], [CharmNovaCompute]) self.assertEqual(m1_as[AssignmentType.LXC], []) self.assertEqual(m1_as[AssignmentType.KVM], []) self.assertEqual(m2_as[AssignmentType.BareMetal], []) self.assertEqual(m2_as[AssignmentType.LXC], [CharmKeystone]) self.assertEqual(m2_as[AssignmentType.KVM], []) def test_remove_one_assignment_sametype(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] self.assertEqual(lxcs, [CharmNovaCompute]) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] self.assertEqual(lxcs, []) def test_remove_one_assignment_othertype(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] kvms = md[AssignmentType.KVM] self.assertEqual(1, len(lxcs) + len(kvms)) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] kvms = md[AssignmentType.KVM] self.assertEqual(0, len(lxcs) + len(kvms)) def test_clear_all(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmNovaCompute, AssignmentType.KVM) self.pc.clear_all_assignments() # check that it's empty: self.assertEqual(self.pc.assignments, {}) # and that it's still a defaultdict(lambda: defaultdict(list)) mid = self.mock_machine.machine_id lxcs = self.pc.assignments[mid][AssignmentType.LXC] self.assertEqual(lxcs, []) def test_unassigned_starts_full(self): self.assertEqual(len(self.pc.unassigned_undeployed_services()), len(self.pc.charm_classes())) def test_assigned_charm_classes_starts_empty(self): self.assertEqual(0, len(self.pc.assigned_charm_classes())) def test_reset_unassigned_undeployed_none(self): """Assign all charms, ensure that unassigned is empty""" for cc in self.pc.charm_classes(): self.pc.assign(self.mock_machine, cc, AssignmentType.LXC) self.pc.reset_assigned_deployed() self.assertEqual(0, len(self.pc.unassigned_undeployed_services())) def test_reset_unassigned_undeployed_two(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmKeystone, AssignmentType.KVM) self.pc.reset_assigned_deployed() self.assertEqual( len(self.pc.charm_classes()) - 2, len(self.pc.unassigned_undeployed_services())) def test_reset_excepting_compute(self): for cc in self.pc.charm_classes(): if cc.charm_name == 'nova-compute': continue self.pc.assign(self.mock_machine, cc, AssignmentType.LXC) self.pc.reset_assigned_deployed() self.assertEqual(len(self.pc.unassigned_undeployed_services()), 1) def test_unassigned_undeployed(self): all_charms = set(self.pc.charm_classes()) self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.pc.mark_deployed(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertTrue( CharmKeystone not in self.pc.unassigned_undeployed_services()) self.assertTrue( CharmNovaCompute not in self.pc.unassigned_undeployed_services()) self.assertTrue(self.pc.is_deployed(CharmKeystone)) self.assertTrue(self.pc.is_assigned(CharmNovaCompute)) self.assertTrue( len(all_charms) - 2, len(self.pc.unassigned_undeployed_services())) n_k_as = self.pc.assignment_machine_count_for_charm(CharmKeystone) self.assertEqual(n_k_as, 0) n_k_dl = self.pc.deployment_machine_count_for_charm(CharmKeystone) self.assertEqual(n_k_dl, 1) n_nc_as = self.pc.assignment_machine_count_for_charm(CharmNovaCompute) self.assertEqual(n_nc_as, 1) n_nc_dl = self.pc.deployment_machine_count_for_charm(CharmNovaCompute) self.assertEqual(n_nc_dl, 0) def test_deployed_charms_starts_empty(self): "Initially there are no deployed charms" self.assertEqual(0, len(self.pc.deployed_charm_classes())) def test_mark_deployed_unsets_assignment(self): "Setting a placement to deployed removes it from assignment dict" self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertEqual([CharmKeystone], self.pc.assigned_charm_classes()) self.pc.mark_deployed(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertEqual([CharmKeystone], self.pc.deployed_charm_classes()) self.assertEqual([], self.pc.assigned_charm_classes()) def test_set_deployed_unsets_assignment_only_once(self): "Setting a placement to deployed removes it from assignment dict" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.pc.assign(self.mock_machine_2, CharmNovaCompute, AssignmentType.KVM) self.assertEqual([CharmNovaCompute], self.pc.assigned_charm_classes()) ad = self.pc.get_assignments(CharmNovaCompute) dd = self.pc.get_deployments(CharmNovaCompute) from pprint import pformat print("Assignments is {}".format(pformat(ad))) print("Deployments is {}".format(pformat(dd))) self.assertEqual(set([self.mock_machine, self.mock_machine_2]), set(ad[AssignmentType.KVM])) self.assertEqual(len(dd.items()), 0) self.pc.mark_deployed(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.assertEqual([CharmNovaCompute], self.pc.deployed_charm_classes()) self.assertEqual([CharmNovaCompute], self.pc.assigned_charm_classes()) ad = self.pc.get_assignments(CharmNovaCompute) dd = self.pc.get_deployments(CharmNovaCompute) self.assertEqual([self.mock_machine_2], ad[AssignmentType.KVM]) self.assertEqual([self.mock_machine], dd[AssignmentType.KVM]) def test_get_charm_state(self): "Test a sampling of required services and special handling for compute" self.assertEqual( self.pc.get_charm_state(CharmKeystone)[0], CharmState.REQUIRED) self.assertEqual( self.pc.get_charm_state(CharmNovaCompute)[0], CharmState.REQUIRED) def test_one_compute_required(self): """after being assigned at least once, novacompute is no longer considered 'required' (aka required)""" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.assertNotEqual( self.pc.get_charm_state(CharmNovaCompute)[0], CharmState.REQUIRED) def test_swift_unrequired_then_required_default(self): "Swift and swift-proxy are both optional until you add swift" self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwift, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_swift_unrequired_then_required_swift_backend(self): "Swift and swift-proxy are not optional with swift as the backend." self.conf.setopt('storage_backend', 'swift') self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwift, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_swift_proxy_unrequired_then_required_default(self): "Swift and swift-proxy are both optional until you add swift-proxy" self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) # Only one swift-proxy is required, so now that we've added # it, it is still not required: self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_swift_proxy_unrequired_then_required_swift_backend(self): "Swift and swift-proxy are not optional with swift as the backend" self.conf.setopt('storage_backend', 'swift') self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) # Only one swift-proxy is required, so now that we've added # it, it is still not required: self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_storage_backends_in_is_required(self): # default is 'none' self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCeph)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCephOSD)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.conf.setopt('storage_backend', 'swift') self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCeph)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCephOSD)[0]) self.assertEqual(CharmState.CONFLICTED, self.pc.get_charm_state(CharmCephRadosGw)[0]) st = self.pc.get_charm_state(CharmSwift) swift_state, swift_cons, swift_deps = st self.assertEqual(CharmState.REQUIRED, swift_state) st = self.pc.get_charm_state(CharmSwiftProxy) swp_state, swp_cons, swp_deps = st self.assertEqual(CharmState.REQUIRED, swp_state) self.assertEqual([], swp_cons) ceph_state, ceph_cons, ceph_deps = self.pc.get_charm_state(CharmCeph) self.assertEqual(CharmState.OPTIONAL, ceph_state) st = self.pc.get_charm_state(CharmCephRadosGw) ceph_rg_state, ceph_rg_cons, ceph_rg_deps = st self.assertEqual(CharmState.CONFLICTED, ceph_rg_state) self.conf.setopt('storage_backend', 'ceph') ceph_state, ceph_cons, ceph_deps = self.pc.get_charm_state(CharmCeph) self.assertEqual(CharmState.REQUIRED, ceph_state) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCephOSD)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_ceph_num_required(self): "3 units of ceph should be required after having been assigned" state, cons, deps = self.pc.get_charm_state(CharmCeph) self.assertEqual(state, CharmState.OPTIONAL) self.pc.assign(self.mock_machine, CharmCeph, AssignmentType.KVM) self.assertEqual( self.pc.get_charm_state(CharmCeph)[0], CharmState.REQUIRED) self.pc.assign(self.mock_machine, CharmCeph, AssignmentType.KVM) self.pc.assign(self.mock_machine, CharmCeph, AssignmentType.KVM) self.assertEqual( self.pc.get_charm_state(CharmCeph)[0], CharmState.OPTIONAL) def test_persistence(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmKeystone, AssignmentType.KVM) cons1 = PropertyMock(return_value={}) type(self.mock_machine).constraints = cons1 cons2 = PropertyMock(return_value={'cpu': 8}) type(self.mock_machine_2).constraints = cons2 with TemporaryFile(mode='w+', encoding='utf-8') as tempf: self.pc.save(tempf) tempf.seek(0) print(tempf.read()) tempf.seek(0) newpc = PlacementController(self.mock_maas_state, self.conf) newpc.load(tempf) self.assertEqual(self.pc.assignments, newpc.assignments) self.assertEqual(self.pc.machines_pending(), newpc.machines_pending()) self.assertEqual(self.pc.assigned_charm_classes(), newpc.assigned_charm_classes()) m2 = next((m for m in newpc.machines_pending() if m.instance_id == 'fake-instance-id-2')) self.assertEqual(m2.constraints, {'cpu': 8}) def test_load_machines_single(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: utils.spew(tempf.name, yaml.dump(dict())) conf = Config({}, tempf.name) fake_assignments = { 'fake_iid': { 'constraints': {}, 'assignments': { 'KVM': ['nova-compute'] } }, 'fake_iid_2': { 'constraints': { 'cpu': 8 }, 'assignments': { 'BareMetal': ['nova-compute'] } } } singlepc = PlacementController(None, conf) with TemporaryFile(mode='w+', encoding='utf-8') as tempf: yaml.dump(fake_assignments, tempf) tempf.seek(0) singlepc.load(tempf) self.assertEqual( set([m.instance_id for m in singlepc.machines_pending()]), set(['fake_iid', 'fake_iid_2'])) m2 = next((m for m in singlepc.machines_pending() if m.instance_id == 'fake_iid_2')) self.assertEqual(m2.constraints, {'cpu': 8}) def test_load_error_mismatch_charm_name(self): """Should safely ignore (and log) a charm name in a placement file that can't be matched to a loaded charm class.""" singlepc = PlacementController(None, self.conf) fake_assignments = { 'fake_iid': { 'constraints': {}, 'assignments': { 'KVM': ['non-existent'] } }, 'fake_iid_2': { 'constraints': { 'cpu': 8 }, 'assignments': { 'BareMetal': ['nova-compute'] } } } with TemporaryFile(mode='w+', encoding='utf-8') as tempf: yaml.dump(fake_assignments, tempf) tempf.seek(0) singlepc.load(tempf) self.assertEqual( set([m.instance_id for m in singlepc.machines_pending()]), set(['fake_iid_2'])) m2 = next((m for m in singlepc.machines_pending() if m.instance_id == 'fake_iid_2')) self.assertEqual(m2.constraints, {'cpu': 8}) def test_is_assigned_to_is_deployed_to(self): self.assertFalse( self.pc.is_assigned_to(CharmSwiftProxy, self.mock_machine)) self.assertFalse( self.pc.is_deployed_to(CharmSwiftProxy, self.mock_machine)) self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertFalse( self.pc.is_deployed_to(CharmSwiftProxy, self.mock_machine)) self.assertTrue( self.pc.is_assigned_to(CharmSwiftProxy, self.mock_machine)) self.pc.mark_deployed(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertTrue( self.pc.is_deployed_to(CharmSwiftProxy, self.mock_machine)) self.assertFalse( self.pc.is_assigned_to(CharmSwiftProxy, self.mock_machine)) def test_double_clear_ok(self): """clearing assignments for a machine that isn't assigned (anymore) is OK and should do nothing """ self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.pc.clear_assignments(self.mock_machine) self.pc.clear_assignments(self.mock_machine) self.pc.clear_assignments(self.mock_machine_2) def test_gen_defaults_raises_with_no_maas_state(self): pc = PlacementController(None, self.conf) self.assertRaises(PlacementError, pc.gen_defaults) def test_gen_defaults_uses_only_ready(self): """gen_defaults should only use ready machines""" mock_maas_state = MagicMock() mock_maas_state.machines.return_value = [] c = Config() c.setopt('storage_backend', 'none') pc = PlacementController(config=c, maas_state=mock_maas_state) # reset the mock to avoid looking at calls from # PlacementController.__init__(). mock_maas_state.reset_mock() pc.gen_defaults() # we simply check the first call because we know that # follow-on calls are from calls to get_assignments and do # not affect machines used for defaults self.assertEqual(mock_maas_state.machines.mock_calls[0], call(MaasMachineStatus.READY)) def test_gen_single_backends(self): "gen_single has no storage backend by default" def find_charm(cn, defs): allcharms = [] for mname, ad in defs.items(): for atype, charmclasses in ad.items(): allcharms += charmclasses return cn in allcharms c = Config() pc = PlacementController(config=c) # default storage_backend is 'none' c.setopt('storage_backend', 'none') defaults = pc.gen_single() self.assertFalse(find_charm(CharmSwiftProxy, defaults)) self.assertFalse(find_charm(CharmSwift, defaults)) self.assertFalse(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults)) c.setopt('storage_backend', 'swift') defaults = pc.gen_single() self.assertTrue(find_charm(CharmSwiftProxy, defaults)) self.assertTrue(find_charm(CharmSwift, defaults)) self.assertFalse(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults)) c.setopt('storage_backend', 'ceph') defaults = pc.gen_single() self.assertFalse(find_charm(CharmSwiftProxy, defaults)) self.assertFalse(find_charm(CharmSwift, defaults)) self.assertTrue(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults))
class LandscapeInstallFinalTestCase(unittest.TestCase): def setUp(self): self.mock_multi_installer = MagicMock() self.mock_display_controller = MagicMock() self.loop = MagicMock() with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config({}, tempf.name) def make_installer_with_config(self, landscape_creds=None, maas_creds=None): if landscape_creds is None: landscape_creds = dict(admin_name="fakeadminname", admin_email="*****@*****.**", system_email="*****@*****.**", maas_server='fake.host', maas_apikey='fake:keyz:yo') self.conf.setopt('maascreds', dict(api_host='fake.host', api_key='fake:keyz:yo')) self.conf.setopt('landscapecreds', landscape_creds) pm_binpath = PropertyMock(return_value='mockbinpath') type(self.conf).bin_path = pm_binpath pm_cfgpath = PropertyMock(return_value='mockcfgpath') type(self.conf).cfg_path = pm_cfgpath lif = LandscapeInstallFinal(self.mock_multi_installer, self.mock_display_controller, self.conf, self.loop) self.installer = lif def test_run_configure_not_sudo_user(self, mock_utils): """Do not sudo -u $SUDO_USER when running landscape-configure, it will be 'root'. """ self.make_installer_with_config() mock_utils.get_command_output.return_value = { 'status': '', 'output': '' } self.installer.run_configure_script() mock_utils.get_command_output.assert_called_with(ANY, timeout=None) def test_run_configure_quotes_config_values(self, mock_utils): cd = dict(admin_name="fake admin name with spaces", admin_email="[email protected]", system_email="[email protected]") self.make_installer_with_config(landscape_creds=cd) mock_utils.get_command_output.return_value = { 'status': '', 'output': '' } self.installer.run_configure_script() expectedcmdstr = ("mockbinpath/configure-landscape " "--admin-email '{}' " "--admin-name '{}' " "--system-email '{}' " "--maas-host fake.host".format( cd['admin_email'], cd['admin_name'], cd['system_email'])) mock_utils.get_command_output.assert_called_with(expectedcmdstr, timeout=None) def test_run_configure_raises_on_error(self, mock_utils): self.make_installer_with_config() mock_utils.get_command_output.return_value = { 'status': 1, 'output': 'failure' } self.assertRaises(Exception, self.installer.run_configure_script) def test_run_deployer_raises_on_error(self, mock_utils): self.make_installer_with_config() mock_utils.get_command_output.return_value = { 'status': 1, 'output': 'failure' } self.assertRaises(Exception, self.installer.run_deployer) def test_run_deployer_has_no_timeout(self, mock_utils): self.make_installer_with_config() mock_utils.get_command_output.return_value = { 'status': '', 'output': 'failure' } self.installer.run_deployer() mock_utils.get_command_output.assert_called_with(ANY, timeout=None, user_sudo=ANY)
class EventLoopCoreTestCase(unittest.TestCase): def setUp(self): self.conf = Config({}) self.mock_ui = MagicMock(name='ui') self.mock_log = MagicMock(name='log') self.mock_loop = MagicMock(name='loop') def make_ev(self, headless=False): self.conf.setopt('headless', headless) return EventLoop(self.mock_ui, self.conf, self.mock_log) def test_validate_loop(self): """ Validate eventloop runs """ self.conf.setopt('headless', False) dc = Controller( ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() dc.start() self.mock_loop.run.assert_called_once_with() def test_validate_redraw_screen_commit_placement(self): """ Validate redraw_screen on commit_placement """ self.conf.setopt('headless', False) dc = Controller( ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() dc.commit_placement() self.mock_loop.redraw_screen.assert_called_once_with() def test_validate_redraw_screen_enqueue(self): """ Validate redraw_screen on enqueue_deployed_charms """ self.conf.setopt('headless', False) dc = Controller( ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() dc.enqueue_deployed_charms() self.mock_loop.redraw_screen.assert_called_once_with() def test_validate_set_alarm_in(self): """ Validate set_alarm_in called with eventloop """ dc = Controller( ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() self.conf.node_install_wait_interval = 1 dc.update(self.conf.node_install_wait_interval, ANY) self.mock_loop.set_alarm_in.assert_called_once_with(1, ANY) def test_validate_exit(self): """ Validate error code set with eventloop """ ev = self.make_ev() dc = Controller( ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() with self.assertRaises(urwid.ExitMainLoop): dc.loop.exit(1) self.assertEqual(ev.error_code, 1) def test_hotkey_exit(self): ev = self.make_ev() dc = Controller( ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() with self.assertRaises(urwid.ExitMainLoop): dc.loop.header_hotkeys('q') self.assertEqual(ev.error_code, 0) def test_repr_ev(self): """ Prints appropriate class string for eventloop """ ev = self.make_ev() dc = Controller( ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() self.assertEqual(str(ev), '<eventloop urwid based on select()>') def test_repr_no_ev(self): """ Prints appropriate class string for no eventloop """ ev = self.make_ev(True) dc = Controller( ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() self.assertEqual(str(ev), '<eventloop disabled>') def test_validate_exit_no_ev(self): """ Validate SystemExit with no eventloop """ ev = self.make_ev(True) dc = Controller( ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() with self.assertRaises(SystemExit) as cm: dc.loop.exit(1) exc = cm.exception self.assertEqual(ev.error_code, exc.code, "Found loop")
class EventLoopCoreTestCase(unittest.TestCase): def setUp(self): self.conf = Config({}, save_backups=False) self.mock_ui = MagicMock(name='ui') self.mock_log = MagicMock(name='log') self.mock_loop = MagicMock(name='loop') def make_ev(self, headless=False): self.conf.setopt('headless', headless) return EventLoop(self.mock_ui, self.conf, self.mock_log) def test_validate_loop(self): """ Validate eventloop runs """ self.conf.setopt('headless', False) self.conf.setopt('openstack_release', 'kilo') dc = Controller(ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() dc.start() self.mock_loop.run.assert_called_once_with() def test_validate_redraw_screen_commit_placement(self): """ Validate redraw_screen on commit_placement """ self.conf.setopt('headless', False) dc = Controller(ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() dc.commit_placement() self.mock_loop.redraw_screen.assert_called_once_with() def test_validate_redraw_screen_enqueue(self): """ Validate redraw_screen on enqueue_deployed_charms """ self.conf.setopt('headless', False) dc = Controller(ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() dc.enqueue_deployed_charms() self.mock_loop.redraw_screen.assert_called_once_with() def test_validate_set_alarm_in(self): """ Validate set_alarm_in called with eventloop """ dc = Controller(ui=self.mock_ui, config=self.conf, loop=self.mock_loop) dc.initialize = MagicMock() self.conf.node_install_wait_interval = 1 dc.update(self.conf.node_install_wait_interval, ANY) self.mock_loop.set_alarm_in.assert_called_once_with(1, ANY) def test_validate_exit(self): """ Validate error code set with eventloop """ ev = self.make_ev() dc = Controller(ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() with self.assertRaises(urwid.ExitMainLoop): dc.loop.exit(1) self.assertEqual(ev.error_code, 1) def test_hotkey_exit(self): ev = self.make_ev() dc = Controller(ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() with self.assertRaises(urwid.ExitMainLoop): dc.loop.header_hotkeys('q') self.assertEqual(ev.error_code, 0) def test_repr_ev(self): """ Prints appropriate class string for eventloop """ ev = self.make_ev() dc = Controller(ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() self.assertEqual(str(ev), '<eventloop urwid based on tornado()>') def test_repr_no_ev(self): """ Prints appropriate class string for no eventloop """ ev = self.make_ev(True) dc = Controller(ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() self.assertEqual(str(ev), '<eventloop disabled>') def test_validate_exit_no_ev(self): """ Validate SystemExit with no eventloop """ ev = self.make_ev(True) dc = Controller(ui=self.mock_ui, config=self.conf, loop=ev) dc.initialize = MagicMock() with self.assertRaises(SystemExit) as cm: dc.loop.exit(1) exc = cm.exception self.assertEqual(ev.error_code, exc.code, "Found loop")
class PlacementControllerTestCase(unittest.TestCase): def setUp(self): self.mock_maas_state = MagicMock() with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: utils.spew(tempf.name, yaml.dump(dict())) self.conf = Config({}, tempf.name) self.conf.setopt('storage_backend', 'none') self.pc = PlacementController(self.mock_maas_state, self.conf) self.mock_machine = MagicMock(name='machine1') pmid = PropertyMock(return_value='fake-instance-id-1') type(self.mock_machine).instance_id = pmid self.mock_machine_2 = MagicMock(name='machine2') pmid2 = PropertyMock(return_value='fake-instance-id-2') type(self.mock_machine_2).instance_id = pmid2 self.mock_machines = [self.mock_machine, self.mock_machine_2] self.mock_maas_state.machines.return_value = self.mock_machines def test_get_assignments_atype(self): self.assertEqual(0, len(self.pc.get_assignments(CharmNovaCompute))) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) md = self.pc.get_assignments(CharmNovaCompute) self.assertEqual(1, len(md)) self.assertEqual(2, len(md[AssignmentType.LXC])) def _do_test_simple_assign_type(self, assignment_type): self.pc.assign(self.mock_machine, CharmNovaCompute, assignment_type) print("assignments is {}".format(self.pc.assignments)) machines = self.pc.get_assignments(CharmNovaCompute) print('machines for charm is {}'.format(machines)) self.assertEqual(machines, {assignment_type: [self.mock_machine]}) ma = self.pc.assignments_for_machine(self.mock_machine) self.assertEqual(ma[assignment_type], [CharmNovaCompute]) def test_simple_assign_bare(self): self._do_test_simple_assign_type(AssignmentType.BareMetal) def test_simple_assign_lxc(self): self._do_test_simple_assign_type(AssignmentType.LXC) def test_simple_assign_kvm(self): self._do_test_simple_assign_type(AssignmentType.KVM) def test_assign_nonmulti(self): self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.LXC) self.assertEqual(self.pc.get_assignments(CharmKeystone), {AssignmentType.LXC: [self.mock_machine]}) self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertEqual(self.pc.get_assignments(CharmKeystone), {AssignmentType.KVM: [self.mock_machine]}) am = self.pc.assignments_for_machine(self.mock_machine) self.assertEqual(am[AssignmentType.KVM], [CharmKeystone]) self.assertEqual(am[AssignmentType.LXC], []) def test_assign_multi(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.assertEqual(self.pc.get_assignments(CharmNovaCompute), {AssignmentType.LXC: [self.mock_machine]}) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.assertEqual(self.pc.get_assignments(CharmNovaCompute), {AssignmentType.LXC: [self.mock_machine], AssignmentType.KVM: [self.mock_machine]}) ma = self.pc.assignments_for_machine(self.mock_machine) self.assertEqual(ma[AssignmentType.LXC], [CharmNovaCompute]) self.assertEqual(ma[AssignmentType.KVM], [CharmNovaCompute]) def test_remove_assignment_multi(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmNovaCompute, AssignmentType.LXC) mfc = self.pc.get_assignments(CharmNovaCompute) mfc_lxc = set(mfc[AssignmentType.LXC]) self.assertEqual(mfc_lxc, set(self.mock_machines)) self.pc.clear_assignments(self.mock_machine) self.assertEqual(self.pc.get_assignments(CharmNovaCompute), {AssignmentType.LXC: [self.mock_machine_2]}) def test_gen_defaults(self): satisfies_importstring = 'cloudinstall.placement.controller.satisfies' with patch(satisfies_importstring) as mock_satisfies: mock_satisfies.return_value = (True, ) defs = self.pc.gen_defaults(charm_classes=[CharmNovaCompute, CharmKeystone], maas_machines=[self.mock_machine, self.mock_machine_2]) m1_as = defs[self.mock_machine.instance_id] m2_as = defs[self.mock_machine_2.instance_id] self.assertEqual(m1_as[AssignmentType.BareMetal], [CharmNovaCompute]) self.assertEqual(m1_as[AssignmentType.LXC], []) self.assertEqual(m1_as[AssignmentType.KVM], []) self.assertEqual(m2_as[AssignmentType.BareMetal], []) self.assertEqual(m2_as[AssignmentType.LXC], [CharmKeystone]) self.assertEqual(m2_as[AssignmentType.KVM], []) def test_remove_one_assignment_sametype(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] self.assertEqual(lxcs, [CharmNovaCompute]) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] self.assertEqual(lxcs, []) def test_remove_one_assignment_othertype(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] kvms = md[AssignmentType.KVM] self.assertEqual(1, len(lxcs) + len(kvms)) self.pc.remove_one_assignment(self.mock_machine, CharmNovaCompute) md = self.pc.assignments[self.mock_machine.instance_id] lxcs = md[AssignmentType.LXC] kvms = md[AssignmentType.KVM] self.assertEqual(0, len(lxcs) + len(kvms)) def test_clear_all(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmNovaCompute, AssignmentType.KVM) self.pc.clear_all_assignments() # check that it's empty: self.assertEqual(self.pc.assignments, {}) # and that it's still a defaultdict(lambda: defaultdict(list)) mid = self.mock_machine.machine_id lxcs = self.pc.assignments[mid][AssignmentType.LXC] self.assertEqual(lxcs, []) def test_unassigned_starts_full(self): self.assertEqual(len(self.pc.unassigned_undeployed_services()), len(self.pc.charm_classes())) def test_assigned_charm_classes_starts_empty(self): self.assertEqual(0, len(self.pc.assigned_charm_classes())) def test_reset_unassigned_undeployed_none(self): """Assign all charms, ensure that unassigned is empty""" for cc in self.pc.charm_classes(): self.pc.assign(self.mock_machine, cc, AssignmentType.LXC) self.pc.reset_assigned_deployed() self.assertEqual(0, len(self.pc.unassigned_undeployed_services())) def test_reset_unassigned_undeployed_two(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmKeystone, AssignmentType.KVM) self.pc.reset_assigned_deployed() self.assertEqual(len(self.pc.charm_classes()) - 2, len(self.pc.unassigned_undeployed_services())) def test_reset_excepting_compute(self): for cc in self.pc.charm_classes(): if cc.charm_name == 'nova-compute': continue self.pc.assign(self.mock_machine, cc, AssignmentType.LXC) self.pc.reset_assigned_deployed() self.assertEqual(len(self.pc.unassigned_undeployed_services()), 1) def test_unassigned_undeployed(self): all_charms = set(self.pc.charm_classes()) self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.pc.mark_deployed(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertTrue(CharmKeystone not in self.pc.unassigned_undeployed_services()) self.assertTrue(CharmNovaCompute not in self.pc.unassigned_undeployed_services()) self.assertTrue(self.pc.is_deployed(CharmKeystone)) self.assertTrue(self.pc.is_assigned(CharmNovaCompute)) self.assertTrue(len(all_charms) - 2, len(self.pc.unassigned_undeployed_services())) n_k_as = self.pc.assignment_machine_count_for_charm(CharmKeystone) self.assertEqual(n_k_as, 0) n_k_dl = self.pc.deployment_machine_count_for_charm(CharmKeystone) self.assertEqual(n_k_dl, 1) n_nc_as = self.pc.assignment_machine_count_for_charm(CharmNovaCompute) self.assertEqual(n_nc_as, 1) n_nc_dl = self.pc.deployment_machine_count_for_charm(CharmNovaCompute) self.assertEqual(n_nc_dl, 0) def test_deployed_charms_starts_empty(self): "Initially there are no deployed charms" self.assertEqual(0, len(self.pc.deployed_charm_classes())) def test_mark_deployed_unsets_assignment(self): "Setting a placement to deployed removes it from assignment dict" self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertEqual([CharmKeystone], self.pc.assigned_charm_classes()) self.pc.mark_deployed(self.mock_machine, CharmKeystone, AssignmentType.KVM) self.assertEqual([CharmKeystone], self.pc.deployed_charm_classes()) self.assertEqual([], self.pc.assigned_charm_classes()) def test_set_deployed_unsets_assignment_only_once(self): "Setting a placement to deployed removes it from assignment dict" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.pc.assign(self.mock_machine_2, CharmNovaCompute, AssignmentType.KVM) self.assertEqual([CharmNovaCompute], self.pc.assigned_charm_classes()) ad = self.pc.get_assignments(CharmNovaCompute) dd = self.pc.get_deployments(CharmNovaCompute) from pprint import pformat print("Assignments is {}".format(pformat(ad))) print("Deployments is {}".format(pformat(dd))) self.assertEqual(set([self.mock_machine, self.mock_machine_2]), set(ad[AssignmentType.KVM])) self.assertEqual(len(dd.items()), 0) self.pc.mark_deployed(self.mock_machine, CharmNovaCompute, AssignmentType.KVM) self.assertEqual([CharmNovaCompute], self.pc.deployed_charm_classes()) self.assertEqual([CharmNovaCompute], self.pc.assigned_charm_classes()) ad = self.pc.get_assignments(CharmNovaCompute) dd = self.pc.get_deployments(CharmNovaCompute) self.assertEqual([self.mock_machine_2], ad[AssignmentType.KVM]) self.assertEqual([self.mock_machine], dd[AssignmentType.KVM]) def test_get_charm_state(self): "Test a sampling of required services and special handling for compute" self.assertEqual(self.pc.get_charm_state(CharmKeystone)[0], CharmState.REQUIRED) self.assertEqual(self.pc.get_charm_state(CharmNovaCompute)[0], CharmState.REQUIRED) def test_one_compute_required(self): """after being assigned at least once, novacompute is no longer considered 'required' (aka required)""" self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.assertNotEqual(self.pc.get_charm_state(CharmNovaCompute)[0], CharmState.REQUIRED) def test_swift_unrequired_then_required_default(self): "Swift and swift-proxy are both optional until you add swift" self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwift, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_swift_unrequired_then_required_swift_backend(self): "Swift and swift-proxy are not optional with swift as the backend." self.conf.setopt('storage_backend', 'swift') self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwift, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_swift_proxy_unrequired_then_required_default(self): "Swift and swift-proxy are both optional until you add swift-proxy" self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) # Only one swift-proxy is required, so now that we've added # it, it is still not required: self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_swift_proxy_unrequired_then_required_swift_backend(self): "Swift and swift-proxy are not optional with swift as the backend" self.conf.setopt('storage_backend', 'swift') self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertEqual(CharmState.REQUIRED, self.pc.get_charm_state(CharmSwift)[0]) # Only one swift-proxy is required, so now that we've added # it, it is still not required: self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_storage_backends_in_is_required(self): # default is 'none' self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCeph)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCephOSD)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) self.conf.setopt('storage_backend', 'swift') self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCeph)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCephOSD)[0]) self.assertEqual(CharmState.CONFLICTED, self.pc.get_charm_state(CharmCephRadosGw)[0]) st = self.pc.get_charm_state(CharmSwift) swift_state, swift_cons, swift_deps = st self.assertEqual(CharmState.REQUIRED, swift_state) st = self.pc.get_charm_state(CharmSwiftProxy) swp_state, swp_cons, swp_deps = st self.assertEqual(CharmState.REQUIRED, swp_state) self.assertEqual([], swp_cons) ceph_state, ceph_cons, ceph_deps = self.pc.get_charm_state(CharmCeph) self.assertEqual(CharmState.OPTIONAL, ceph_state) st = self.pc.get_charm_state(CharmCephRadosGw) ceph_rg_state, ceph_rg_cons, ceph_rg_deps = st self.assertEqual(CharmState.CONFLICTED, ceph_rg_state) self.conf.setopt('storage_backend', 'ceph') ceph_state, ceph_cons, ceph_deps = self.pc.get_charm_state(CharmCeph) self.assertEqual(CharmState.REQUIRED, ceph_state) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmCephOSD)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwift)[0]) self.assertEqual(CharmState.OPTIONAL, self.pc.get_charm_state(CharmSwiftProxy)[0]) def test_ceph_num_required(self): "3 units of ceph should be required after having been assigned" state, cons, deps = self.pc.get_charm_state(CharmCeph) self.assertEqual(state, CharmState.OPTIONAL) self.pc.assign(self.mock_machine, CharmCeph, AssignmentType.KVM) self.assertEqual(self.pc.get_charm_state(CharmCeph)[0], CharmState.REQUIRED) self.pc.assign(self.mock_machine, CharmCeph, AssignmentType.KVM) self.pc.assign(self.mock_machine, CharmCeph, AssignmentType.KVM) self.assertEqual(self.pc.get_charm_state(CharmCeph)[0], CharmState.OPTIONAL) def test_persistence(self): self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC) self.pc.assign(self.mock_machine_2, CharmKeystone, AssignmentType.KVM) cons1 = PropertyMock(return_value={}) type(self.mock_machine).constraints = cons1 cons2 = PropertyMock(return_value={'cpu': 8}) type(self.mock_machine_2).constraints = cons2 with TemporaryFile(mode='w+', encoding='utf-8') as tempf: self.pc.save(tempf) tempf.seek(0) print(tempf.read()) tempf.seek(0) newpc = PlacementController( self.mock_maas_state, self.conf) newpc.load(tempf) self.assertEqual(self.pc.assignments, newpc.assignments) self.assertEqual(self.pc.machines_pending(), newpc.machines_pending()) self.assertEqual(self.pc.assigned_charm_classes(), newpc.assigned_charm_classes()) m2 = next((m for m in newpc.machines_pending() if m.instance_id == 'fake-instance-id-2')) self.assertEqual(m2.constraints, {'cpu': 8}) def test_load_machines_single(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: utils.spew(tempf.name, yaml.dump(dict())) conf = Config({}, tempf.name) fake_assignments = { 'fake_iid': {'constraints': {}, 'assignments': {'KVM': ['nova-compute']}}, 'fake_iid_2': {'constraints': {'cpu': 8}, 'assignments': {'BareMetal': ['nova-compute']}}} singlepc = PlacementController( None, conf) with TemporaryFile(mode='w+', encoding='utf-8') as tempf: yaml.dump(fake_assignments, tempf) tempf.seek(0) singlepc.load(tempf) self.assertEqual(set([m.instance_id for m in singlepc.machines_pending()]), set(['fake_iid', 'fake_iid_2'])) m2 = next((m for m in singlepc.machines_pending() if m.instance_id == 'fake_iid_2')) self.assertEqual(m2.constraints, {'cpu': 8}) def test_load_error_mismatch_charm_name(self): """Should safely ignore (and log) a charm name in a placement file that can't be matched to a loaded charm class.""" singlepc = PlacementController(None, self.conf) fake_assignments = { 'fake_iid': { 'constraints': {}, 'assignments': {'KVM': ['non-existent']}}, 'fake_iid_2': { 'constraints': {'cpu': 8}, 'assignments': {'BareMetal': ['nova-compute']}}} with TemporaryFile(mode='w+', encoding='utf-8') as tempf: yaml.dump(fake_assignments, tempf) tempf.seek(0) singlepc.load(tempf) self.assertEqual(set([m.instance_id for m in singlepc.machines_pending()]), set(['fake_iid_2'])) m2 = next((m for m in singlepc.machines_pending() if m.instance_id == 'fake_iid_2')) self.assertEqual(m2.constraints, {'cpu': 8}) def test_is_assigned_to_is_deployed_to(self): self.assertFalse(self.pc.is_assigned_to(CharmSwiftProxy, self.mock_machine)) self.assertFalse(self.pc.is_deployed_to(CharmSwiftProxy, self.mock_machine)) self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertFalse(self.pc.is_deployed_to(CharmSwiftProxy, self.mock_machine)) self.assertTrue(self.pc.is_assigned_to(CharmSwiftProxy, self.mock_machine)) self.pc.mark_deployed(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.assertTrue(self.pc.is_deployed_to(CharmSwiftProxy, self.mock_machine)) self.assertFalse(self.pc.is_assigned_to(CharmSwiftProxy, self.mock_machine)) def test_double_clear_ok(self): """clearing assignments for a machine that isn't assigned (anymore) is OK and should do nothing """ self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC) self.pc.clear_assignments(self.mock_machine) self.pc.clear_assignments(self.mock_machine) self.pc.clear_assignments(self.mock_machine_2) def test_gen_defaults_raises_with_no_maas_state(self): pc = PlacementController(None, self.conf) self.assertRaises(PlacementError, pc.gen_defaults) def test_gen_defaults_uses_only_ready(self): """gen_defaults should only use ready machines""" mock_maas_state = MagicMock() mock_maas_state.machines.return_value = [] c = Config() c.setopt('storage_backend', 'none') pc = PlacementController(config=c, maas_state=mock_maas_state) # reset the mock to avoid looking at calls from # PlacementController.__init__(). mock_maas_state.reset_mock() pc.gen_defaults() # we simply check the first call because we know that # follow-on calls are from calls to get_assignments and do # not affect machines used for defaults self.assertEqual(mock_maas_state.machines.mock_calls[0], call(MaasMachineStatus.READY)) def test_gen_single_backends(self): "gen_single has no storage backend by default" def find_charm(cn, defs): allcharms = [] for mname, ad in defs.items(): for atype, charmclasses in ad.items(): allcharms += charmclasses return cn in allcharms c = Config() pc = PlacementController(config=c) # default storage_backend is 'none' c.setopt('storage_backend', 'none') defaults = pc.gen_single() self.assertFalse(find_charm(CharmSwiftProxy, defaults)) self.assertFalse(find_charm(CharmSwift, defaults)) self.assertFalse(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults)) c.setopt('storage_backend', 'swift') defaults = pc.gen_single() self.assertTrue(find_charm(CharmSwiftProxy, defaults)) self.assertTrue(find_charm(CharmSwift, defaults)) self.assertFalse(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults)) c.setopt('storage_backend', 'ceph') defaults = pc.gen_single() self.assertFalse(find_charm(CharmSwiftProxy, defaults)) self.assertFalse(find_charm(CharmSwift, defaults)) self.assertTrue(find_charm(CharmCeph, defaults)) self.assertFalse(find_charm(CharmCephOSD, defaults))
class TestGoodConfig(unittest.TestCase): def setUp(self): self._temp_conf = Config(GOOD_CONFIG) with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config(self._temp_conf._config, tempf.name) def test_save_openstack_password(self): """ Save openstack password to config """ self.conf.setopt('openstack_password', 'pass') self.conf.save() self.assertEqual('pass', self.conf.getopt('openstack_password')) def test_save_maas_creds(self): """ Save maas credentials """ self.conf.setopt('maascreds', dict(api_host='127.0.0.1', api_key='1234567')) self.conf.save() self.assertEqual( '127.0.0.1', self.conf.getopt('maascreds')['api_host']) def test_save_landscape_creds(self): """ Save landscape credentials """ self.conf.setopt('landscapecreds', dict(admin_name='foo', admin_email='*****@*****.**', system_email='*****@*****.**', maas_server='127.0.0.1', maas_apikey='123457')) self.conf.save() self.assertEqual( '*****@*****.**', self.conf.getopt('landscapecreds')['admin_email']) def test_save_installer_type(self): """ Save installer type """ self.conf.setopt("install_type", 'multi') self.conf.save() self.assertEqual('multi', self.conf.getopt('install_type')) @unittest.skip def test_cfg_path(self): """ Validate current users config path """ self.assertEqual( self.conf.cfg_path, path.join(USER_DIR, '.cloud-install')) def test_bin_path(self): """ Validate additional tools bin path """ self.assertEqual(self.conf.bin_path, '/usr/share/openstack/bin') @unittest.skip def test_juju_environments_path(self): """ Validate juju environments path in user dir """ self.assertEqual( self.conf.juju_environments_path, path.join( USER_DIR, '.cloud-install/juju/environments.yaml')) def test_clear_empty_args(self): """ Empty cli options are not populated in generated config """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config(parse_opts(['--config', cfg_file])) self.assertEqual(True, 'http-proxy' not in cfg) def test_config_file_persists(self): """ CLI options override options in config file """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config( parse_opts(['--config', cfg_file, '--headless'])) self.assertEqual(True, cfg['headless']) def test_config_file_persists_new_cli_opts(self): """ Generated config object appends new options passed via cli """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config( parse_opts(['--config', cfg_file, '--install-only', '--killcloud-noprompt'])) self.assertEqual(True, cfg['install_only']) self.assertEqual(True, cfg['killcloud_noprompt']) def test_config_overrides_from_cli(self): """ Config object item is not overridden by unset cli option """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config( parse_opts(['--http-proxy', 'http://localhost:2222', '--killcloud-noprompt', '--config', cfg_file])) self.assertEqual(cfg['https_proxy'], GOOD_CONFIG['https_proxy']) def test_default_opts_not_override_config(self): """ Verify that default cli opts that are False do not override their config_option whose option is True. """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg_opts_raw = parse_opts(['--config', cfg_file]) cfg_opts_raw = vars(cfg_opts_raw) self.assertEqual(True, 'headless' not in cfg_opts_raw) cfg = utils.populate_config(parse_opts(['--config', cfg_file])) self.assertEqual(True, cfg['headless']) def test_default_opts_no_config(self): """ Verify that default cli opts are sanitized and that no options set to False or None exist in the config object """ cfg = utils.populate_config(parse_opts([])) print(cfg) self.assertEqual(True, 'headless' not in cfg)
class TestGoodConfig(unittest.TestCase): def setUp(self): self._temp_conf = Config(GOOD_CONFIG) with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.conf = Config(self._temp_conf._config, tempf.name) def test_save_openstack_password(self): """ Save openstack password to config """ self.conf.setopt('openstack_password', 'pass') self.conf.save() self.assertEqual('pass', self.conf.getopt('openstack_password')) def test_save_maas_creds(self): """ Save maas credentials """ self.conf.setopt('maascreds', dict(api_host='127.0.0.1', api_key='1234567')) self.conf.save() self.assertEqual('127.0.0.1', self.conf.getopt('maascreds')['api_host']) def test_save_landscape_creds(self): """ Save landscape credentials """ self.conf.setopt( 'landscapecreds', dict(admin_name='foo', admin_email='*****@*****.**', system_email='*****@*****.**', maas_server='127.0.0.1', maas_apikey='123457')) self.conf.save() self.assertEqual('*****@*****.**', self.conf.getopt('landscapecreds')['admin_email']) def test_save_installer_type(self): """ Save installer type """ self.conf.setopt("install_type", 'multi') self.conf.save() self.assertEqual('multi', self.conf.getopt('install_type')) def test_cfg_path(self): """ Validate current users config path """ self.assertEqual(self.conf.cfg_path, path.join(USER_DIR, '.cloud-install')) def test_bin_path(self): """ Validate additional tools bin path """ self.assertEqual(self.conf.bin_path, '/usr/share/openstack/bin') def test_juju_environments_path(self): """ Validate juju environments path in user dir """ self.assertEqual( self.conf.juju_environments_path, path.join(USER_DIR, '.cloud-install/juju/environments.yaml')) def test_clear_empty_args(self): """ Empty cli options are not populated in generated config """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config(parse_opts(['--config', cfg_file])) self.assertEqual(True, 'http-proxy' not in cfg) def test_config_file_persists(self): """ CLI options override options in config file """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config( parse_opts(['--config', cfg_file, '--headless'])) self.assertEqual(True, cfg['headless']) def test_config_file_persists_new_cli_opts(self): """ Generated config object appends new options passed via cli """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config( parse_opts([ '--config', cfg_file, '--install-only', '--killcloud-noprompt' ])) self.assertEqual(True, cfg['install_only']) self.assertEqual(True, cfg['killcloud_noprompt']) def test_config_overrides_from_cli(self): """ Config object item is not overridden by unset cli option """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg = utils.populate_config( parse_opts([ '--http-proxy', 'http://localhost:2222', '--killcloud-noprompt', '--config', cfg_file ])) self.assertEqual(cfg['https_proxy'], GOOD_CONFIG['https_proxy']) def test_default_opts_not_override_config(self): """ Verify that default cli opts that are False do not override their config_option whose option is True. """ cfg_file = path.join(DATA_DIR, 'good_config.yaml') cfg_opts_raw = parse_opts(['--config', cfg_file]) cfg_opts_raw = vars(cfg_opts_raw) self.assertEqual(True, 'headless' not in cfg_opts_raw) cfg = utils.populate_config(parse_opts(['--config', cfg_file])) self.assertEqual(True, cfg['headless']) def test_default_opts_no_config(self): """ Verify that default cli opts are sanitized and that no options set to False or None exist in the config object """ cfg = utils.populate_config(parse_opts([])) print(cfg) self.assertEqual(True, 'headless' not in cfg)
class TestRenderCharmConfig(unittest.TestCase): def setUp(self): with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: # Override config file to save to self.config = Config({}, tempf.name) type(self.config).cfg_path = PropertyMock(return_value='fake_cfg_path') self.config.setopt('openstack_password', 'fake_pw') self.ltp = patch('cloudinstall.utils.load_template') self.mock_load_template = self.ltp.start() self.mock_load_template.side_effect = source_tree_template_loader def tearDown(self): self.ltp.stop() def _do_test_osrel(self, optsvalue, expected, mockspew): "check that opts.openstack_release is rendered correctly" self.config.setopt('openstack_release', optsvalue) render_charm_config(self.config) (fake_path, generated_yaml), kwargs = mockspew.call_args d = yaml.load(generated_yaml) print(d) for oscharmname in [ 'nova-cloud-controller', 'glance', 'openstack-dashboard', 'keystone', 'swift-proxy' ]: if expected is None: self.assertTrue(oscharmname not in d or 'openstack-origin' not in d[oscharmname]) else: self.assertEqual(d[oscharmname]['openstack-origin'], expected) def test_render_openstack_release_given(self, mockspew): self._do_test_osrel('klaxon', 'cloud:trusty-klaxon', mockspew) def _do_test_multiplier(self, is_single, mockspew, expected=None): if is_single: self.config.setopt('install_type', 'Single') else: self.config.setopt('install_type', 'Multi') self.config.setopt('openstack_release', 'klaxon') render_charm_config(self.config) (fake_path, generated_yaml), kwargs = mockspew.call_args d = yaml.load(generated_yaml) wmul = d['nova-cloud-controller'].get('worker-multiplier', None) self.assertEqual(wmul, expected) wmul = d['glance'].get('worker-multiplier', None) self.assertEqual(wmul, expected) wmul = d['keystone'].get('worker-multiplier', None) self.assertEqual(wmul, expected) def test_render_worker_multiplier_multi(self, mockspew): self._do_test_multiplier(False, mockspew) def test_render_worker_multiplier_single(self, mockspew): self._do_test_multiplier(True, mockspew, expected=1) def test_charmconfig_custom_merge(self, mockspew): """ Verify rightmost custom charm config dictionary does not overwrite untouched items in rendered charmconfig """ charm_custom = { 'swift-proxy': { 'replicas': 15 }, 'mysql': { 'dataset-size': '2048M' } } charm_conf = yaml.load(slurp(os.path.join(DATA_DIR, 'charmconf.yaml'))) merged_dicts = merge_dicts(charm_conf, charm_custom) self.assertEqual(merged_dicts['mysql']['max-connections'], 25000) self.assertEqual(merged_dicts['swift-proxy']['zone-assignment'], 'auto')
class ServicesListTestCase(unittest.TestCase): def setUp(self): self.mock_maas_state = MagicMock() with NamedTemporaryFile(mode='w+', encoding='utf-8') as tempf: utils.spew(tempf.name, yaml.dump(dict())) self.conf = Config({}, tempf.name) self.conf.setopt('storage_backend', 'none') self.pc = PlacementController(self.mock_maas_state, self.conf) self.mock_machine = make_fake_machine('machine1', {'cpu_count': 3}) self.mock_machine2 = make_fake_machine('machine2') self.mock_machine3 = make_fake_machine('machine3') self.mock_machines = [self.mock_machine] self.mock_maas_state.machines.return_value = self.mock_machines self.actions = [] self.sub_actions = [] def test_widgets_config(self, mock_servicewidgetclass): for show_constraints in [False, True]: sl = ServicesList(self.pc, self.actions, self.sub_actions, show_constraints=show_constraints) mock_servicewidgetclass.assert_any_call( CharmNovaCompute, self.pc, self.actions, show_constraints, show_placements=sl.show_placements) mock_servicewidgetclass.reset_mock() def test_no_machine_no_constraints(self, mock_servicewidgetclass): with patch.object(self.pc, 'charm_classes') as mock_classesfunc: fc = MagicMock(name='fakeclass1') fc.required_num_units.return_value = 1 fc.constraints = {'cpu_count': 1000} mock_classesfunc.return_value = [fc] sl = ServicesList(self.pc, self.actions, self.sub_actions) self.assertEqual(len(sl.service_widgets), 1) def test_machine_checks_constraints(self, mock_servicewidgetclass): mock_machine = make_fake_machine('fm', { 'cpu_count': 0, 'storage': 0, 'memory': 0 }) sl = ServicesList(self.pc, self.actions, self.sub_actions, machine=mock_machine) self.assertEqual(len(sl.service_widgets), 0) def test_do_not_show_assigned(self, mock_servicewidgetclass): mock_machine = make_fake_machine('fm', { 'cpu_count': 0, 'storage': 0, 'memory': 0 }) self.pc.assign(mock_machine, CharmNovaCompute, AssignmentType.LXC) sl = ServicesList(self.pc, self.actions, self.sub_actions, machine=mock_machine) classes = [sw.charm_class for sw in sl.service_widgets] self.assertTrue(CharmNovaCompute not in classes) def test_show_type(self, mock_servicewidgetclass): """Test combinations of show_type values. This tests three values of show_type with three return values for is_required(): all required, no required, and 1/3 required. It's all lumped in one test to consolidate setup. """ mock_sw1 = MagicMock(name='sw1') mock_sw1.charm_class.charm_name = 'cc1' mock_sw2 = MagicMock(name='sw2') mock_sw2.charm_class.charm_name = 'cc2' mock_sw3 = MagicMock(name='sw3') mock_sw3.charm_class.charm_name = 'cc3' mock_servicewidgetclass.side_effect = [mock_sw1, mock_sw2, mock_sw3] with patch.object(self.pc, 'get_charm_state') as mock_get_state: with patch.object(self.pc, 'charm_classes') as mock_classesfunc: mock_classesfunc.return_value = [ MagicMock(name='fake-class-1', charm_name='cc1'), MagicMock(name='fake-class-2', charm_name='cc2'), MagicMock(name='fake-class-3', charm_name='cc3') ] # First, test when all charms are required mock_get_state.return_value = (CharmState.REQUIRED, [], []) # rsl shows required charms rsl = ServicesList(self.pc, self.actions, self.sub_actions, machine=None, show_type='required') self.assertEqual(len(mock_get_state.mock_calls), 3) # should show all 3 self.assertEqual(len(rsl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] # usl shows ONLY un-required charms usl = ServicesList(self.pc, self.actions, self.sub_actions, machine=None, show_type='non-required') self.assertEqual(len(mock_get_state.mock_calls), 3) # should show 0 self.assertEqual(len(usl.service_widgets), 0) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] # asl has default show_type='all', showing all charms asl = ServicesList(self.pc, self.actions, self.sub_actions) self.assertEqual(len(mock_get_state.mock_calls), 3) # should show all 3 self.assertEqual(len(asl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] # next, test where no charms are required mock_get_state.return_value = (CharmState.OPTIONAL, [], []) rsl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show 0 charms self.assertEqual(len(rsl.service_widgets), 0) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] usl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show all 3 self.assertEqual(len(usl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] asl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should still show all 3 self.assertEqual(len(asl.service_widgets), 3) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] # next test two un-required and one required charm: mock_get_state.side_effect = [(CharmState.OPTIONAL, [], []), (CharmState.REQUIRED, [], []), (CharmState.OPTIONAL, [], [])] rsl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show 1: self.assertEqual(len(rsl.service_widgets), 1) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] mock_get_state.side_effect = [(CharmState.OPTIONAL, [], []), (CharmState.REQUIRED, [], []), (CharmState.OPTIONAL, [], [])] usl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should show two self.assertEqual(len(usl.service_widgets), 2) mock_get_state.reset_mock() mock_servicewidgetclass.reset_mock() mock_servicewidgetclass.side_effect = [ mock_sw1, mock_sw2, mock_sw3 ] mock_get_state.side_effect = [(CharmState.OPTIONAL, [], []), (CharmState.REQUIRED, [], []), (CharmState.OPTIONAL, [], [])] asl.update() self.assertEqual(len(mock_get_state.mock_calls), 3) # should still show all three self.assertEqual(len(asl.service_widgets), 3)