class PlacementControllerTestCase(unittest.TestCase):

    def setUp(self):
        self.mock_maas_state = MagicMock()
        self.mock_opts = MagicMock()
        swopt = PropertyMock(return_value=False)
        type(self.mock_opts).enable_swift = swopt

        self.pc = PlacementController(self.mock_maas_state,
                                      self.mock_opts)
        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_machines_for_charm_atype(self):
        self.assertEqual(0, len(self.pc.machines_for_charm(CharmNovaCompute)))
        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC)
        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC)
        md = self.pc.machines_for_charm(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.machines_for_charm(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.machines_for_charm(CharmKeystone),
                         {AssignmentType.LXC: [self.mock_machine]})

        self.pc.assign(self.mock_machine, CharmKeystone, AssignmentType.KVM)
        self.assertEqual(self.pc.machines_for_charm(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.machines_for_charm(CharmNovaCompute),
                         {AssignmentType.LXC: [self.mock_machine]})

        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.KVM)
        self.assertEqual(self.pc.machines_for_charm(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.machines_for_charm(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.machines_for_charm(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_reset_unplaced_none(self):
        """Assign all charms, ensure that unplaced is empty"""
        for cc in self.pc.charm_classes():
            self.pc.assign(self.mock_machine, cc, AssignmentType.LXC)

        self.pc.reset_unplaced()

        self.assertEqual(0, len(self.pc.unplaced_services))

    def test_reset_unplaced_two(self):
        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC)
        self.pc.assign(self.mock_machine_2, CharmKeystone, AssignmentType.KVM)
        self.pc.reset_unplaced()
        self.assertEqual(len(self.pc.charm_classes()) - 2,
                         len(self.pc.unplaced_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_unplaced()
        self.assertEqual(len(self.pc.unplaced_services), 1)

    def test_service_is_required(self):
        "Test a sampling of required services and special handling for compute"
        self.assertTrue(self.pc.service_is_required(CharmKeystone))
        self.assertTrue(self.pc.service_is_required(CharmNovaCompute))
        self.assertFalse(self.pc.service_is_required(CharmJujuGui))

    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.assertFalse(self.pc.service_is_required(CharmNovaCompute))

    def test_swift_unrequired_then_required(self):
        "Swift and swift-proxy are both optional until you add swift"

        self.assertFalse(self.pc.service_is_required(CharmSwift))
        self.assertFalse(self.pc.service_is_required(CharmSwiftProxy))
        self.pc.assign(self.mock_machine, CharmSwift, AssignmentType.LXC)
        self.assertTrue(self.pc.service_is_required(CharmSwift))
        self.assertTrue(self.pc.service_is_required(CharmSwiftProxy))

    def test_swift_proxy_unrequired_then_required(self):
        "Swift and swift-proxy are both optional until you add swift-proxy"
        self.assertFalse(self.pc.service_is_required(CharmSwift))
        self.assertFalse(self.pc.service_is_required(CharmSwiftProxy))
        self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC)
        self.assertTrue(self.pc.service_is_required(CharmSwift))
        # Only one swift-proxy is required, so now that we've added
        # it, it is still not required:
        self.assertFalse(self.pc.service_is_required(CharmSwiftProxy))

    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)
            newpc = PlacementController(self.mock_maas_state, self.mock_opts)
            newpc.load(tempf)
        self.assertEqual(self.pc.assignments, newpc.assignments)
        self.assertEqual(self.pc.machines_used(), newpc.machines_used())
        self.assertEqual(self.pc.placed_charm_classes(),
                         newpc.placed_charm_classes())

        m2 = next((m for m in newpc.machines_used()
                   if m.instance_id == 'fake-instance-id-2'))
        self.assertEqual(m2.constraints, {'cpu': 8})

    def test_load_machines_single(self):
        singlepc = PlacementController(None, self.mock_opts)
        fake_assignments = {'fake_iid': {'constraints': {},
                                         'assignments': {'KVM':
                                                         ['nova-compute']}},
                            '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_used()]),
                         set(['fake_iid', 'fake_iid_2']))

        m2 = next((m for m in singlepc.machines_used()
                   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.mock_opts)
        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_used()]),
                         set(['fake_iid_2']))

        m2 = next((m for m in singlepc.machines_used()
                   if m.instance_id == 'fake_iid_2'))
        self.assertEqual(m2.constraints, {'cpu': 8})

    def test_is_assigned(self):
        self.assertFalse(self.pc.is_assigned(CharmSwiftProxy,
                                             self.mock_machine))
        self.pc.assign(self.mock_machine, CharmSwiftProxy, AssignmentType.LXC)
        self.assertTrue(self.pc.is_assigned(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)
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.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_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_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)

    # FIXME: Not sure whats going on with this test, assume
    # its from the maas-tag addition. Also not sure why
    # it fails now and not in previous commit?
    @unittest.skip
    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()
        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)

        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))
示例#3
0
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, save_backups=False)

        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)
示例#4
0
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.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_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_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)

    # FIXME: Not sure whats going on with this test, assume
    # its from the maas-tag addition. Also not sure why
    # it fails now and not in previous commit?
    @unittest.skip
    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()
        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)

        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))
示例#5
0
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, save_backups=False)

        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)
示例#6
0
class MachineWidgetTestCase(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, save_backups=False)

        self.pc = PlacementController(self.mock_maas_state,
                                      self.conf)
        self.mock_machine = make_fake_machine('machine1')

        self.mock_machines = [self.mock_machine]

        self.mock_maas_state.machines.return_value = self.mock_machines

    def test_hardware_shown(self):
        """show_hardware=True should show hardware details"""
        w = MachineWidget(self.mock_machine, self.pc, show_hardware=True)
        self.assertTrue(search_in_widget("arch", w))
        self.assertTrue(search_in_widget("cores", w))
        self.assertTrue(search_in_widget("mem", w))
        self.assertTrue(search_in_widget("storage", w))

    def test_hardware_not_shown(self):
        """show_hardware=False should NOT show hardware details"""
        w = MachineWidget(self.mock_machine, self.pc, show_hardware=False)
        self.assertFalse(search_in_widget("arch", w))
        self.assertFalse(search_in_widget("cores", w))
        self.assertFalse(search_in_widget("mem", w))
        self.assertFalse(search_in_widget("storage", w))

    def test_show_assignments(self):
        """Widget with show_assignments set should show assignments"""
        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC)
        w = MachineWidget(self.mock_machine, self.pc, show_assignments=True)

        self.assertTrue(search_in_widget("LXC.*Compute", w))

    def test_dont_show_assignments(self):
        """Widget with show_assignments set to FALSE should NOT show
        assignments"""
        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC)
        w = MachineWidget(self.mock_machine, self.pc, show_assignments=False)

        self.assertFalse(search_in_widget("LXC.*Compute", w))

    def test_show_actions(self):
        """Actions passed as 2-tuples should always be shown as buttons"""
        fake_action_func = MagicMock()
        actions = [("fake-action", fake_action_func)]
        w = MachineWidget(self.mock_machine, 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 = MachineWidget(self.mock_machine, self.pc, actions=actions)

        self.assertTrue(search_in_widget("\(.*fake-action.*\)", w))
        fake_pred.assert_called_with(self.mock_machine)

        fake_pred.return_value = True
        fake_pred.reset_mock()

        w.update()
        self.assertTrue(search_in_widget("<.*fake-action.*>", w))
        fake_pred.assert_called_with(self.mock_machine)
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.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 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.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 MachineWidgetTestCase(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.pc = PlacementController(self.mock_maas_state,
                                      self.conf)
        self.mock_machine = make_fake_machine('machine1')

        self.mock_machines = [self.mock_machine]

        self.mock_maas_state.machines.return_value = self.mock_machines

    def test_hardware_shown(self):
        """show_hardware=True should show hardware details"""
        w = MachineWidget(self.mock_machine, self.pc, show_hardware=True)
        self.assertTrue(search_in_widget("arch", w))
        self.assertTrue(search_in_widget("cores", w))
        self.assertTrue(search_in_widget("mem", w))
        self.assertTrue(search_in_widget("storage", w))

    def test_hardware_not_shown(self):
        """show_hardware=False should NOT show hardware details"""
        w = MachineWidget(self.mock_machine, self.pc, show_hardware=False)
        self.assertFalse(search_in_widget("arch", w))
        self.assertFalse(search_in_widget("cores", w))
        self.assertFalse(search_in_widget("mem", w))
        self.assertFalse(search_in_widget("storage", w))

    def test_show_assignments(self):
        """Widget with show_assignments set should show assignments"""
        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC)
        w = MachineWidget(self.mock_machine, self.pc, show_assignments=True)

        self.assertTrue(search_in_widget("LXC.*Compute", w))

    def test_dont_show_assignments(self):
        """Widget with show_assignments set to FALSE should NOT show
        assignments"""
        self.pc.assign(self.mock_machine, CharmNovaCompute, AssignmentType.LXC)
        w = MachineWidget(self.mock_machine, self.pc, show_assignments=False)

        self.assertFalse(search_in_widget("LXC.*Compute", w))

    def test_show_actions(self):
        """Actions passed as 2-tuples should always be shown as buttons"""
        fake_action_func = MagicMock()
        actions = [("fake-action", fake_action_func)]
        w = MachineWidget(self.mock_machine, 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 = MachineWidget(self.mock_machine, self.pc, actions=actions)

        self.assertTrue(search_in_widget("\(.*fake-action.*\)", w))
        fake_pred.assert_called_with(self.mock_machine)

        fake_pred.return_value = True
        fake_pred.reset_mock()

        w.update()
        self.assertTrue(search_in_widget("<.*fake-action.*>", w))
        fake_pred.assert_called_with(self.mock_machine)