Example #1
0
    def build_widgets(self):

        instructions = Text("Remove services from {}".format(
            self.machine.hostname))

        self.machine_widget = MachineWidget(self.machine,
                                            self.controller,
                                            show_hardware=True)

        def show_remove_p(cc):
            md = self.controller.get_assignments(cc)
            for atype, ms in md.items():
                hostnames = [m.hostname for m in ms]
                if self.machine.hostname in hostnames:
                    return True
            return False

        actions = [(show_remove_p, 'Remove', self.do_remove)]

        self.services_list = ServicesList(self.controller,
                                          actions,
                                          actions,
                                          machine=self.machine)

        close_button = AttrMap(Button('X', on_press=self.close_pressed),
                               'button_secondary', 'button_secondary focus')
        p = Pile([
            GridFlow([close_button], 5, 1, 0, 'right'), instructions,
            Divider(), self.machine_widget,
            Divider(), self.services_list
        ])

        return LineBox(p, title="Remove Services")
Example #2
0
    def build_widgets(self):
        self.deploy_view = DeployView(self.display_controller,
                                      self.placement_controller,
                                      self.placement_view)

        def not_conflicted_p(cc):
            state, _, _ = self.placement_controller.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(not_conflicted_p, "Choose Machine",
                    self.placement_view.do_show_machine_chooser)]
        subordinate_actions = [(not_conflicted_p, "Add",
                                self.do_place_subordinate)]
        self.required_services_list = ServicesList(self.placement_controller,
                                                   actions,
                                                   subordinate_actions,
                                                   ignore_assigned=True,
                                                   ignore_deployed=True,
                                                   show_type='required',
                                                   show_constraints=True,
                                                   title="Required Services")
        self.additional_services_list = ServicesList(self.placement_controller,
                                                     actions,
                                                     subordinate_actions,
                                                     show_type='non-required',
                                                     show_constraints=True,
                                                     title="Additional "
                                                     "Services")

        autoplace_func = self.placement_view.do_autoplace
        self.autoplace_button = AttrMap(
            Button("Auto-place Remaining Services", on_press=autoplace_func),
            'button_secondary', 'button_secondary focus')

        clear_all_func = self.placement_view.do_clear_all
        self.clear_all_button = AttrMap(
            Button("Clear all Placements", on_press=clear_all_func),
            'button_secondary', 'button_secondary focus')

        self.required_services_pile = Pile(
            [self.required_services_list,
             Divider()])
        self.additional_services_pile = Pile(
            [self.additional_services_list,
             Divider()])

        self.top_buttons = []
        self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center')

        pl = [
            Text(('subheading', "Services"), align='center'),
            Divider(), self.top_button_grid,
            Divider(), self.deploy_view,
            Divider(), self.required_services_pile,
            Divider(), self.additional_services_pile
        ]

        self.main_pile = Pile(pl)

        return self.main_pile
    def build_widget(self, **kwargs):

        def remove_p(charm_class):
            n = self.pc.assignment_machine_count_for_charm(charm_class)
            return n > 0

        def not_conflicted_p(cc):
            state, _, _ = self.pc.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(remove_p, 'Remove', self.do_remove),
                   (not_conflicted_p, 'Add', self.do_add)]
        self.unrequired_undeployed_sl = ServicesList(self.pc,
                                                     actions, actions,
                                                     ignore_deployed=True,
                                                     title="Un-Deployed")
        self.deployed_sl = ServicesList(self.pc,
                                        actions, actions,
                                        deployed_only=True,
                                        show_placements=True,
                                        title="Deployed Services")

        self.assigned_sl = ServicesList(self.pc,
                                        actions, actions,
                                        assigned_only=True,
                                        show_placements=True,
                                        title="Services to be Deployed")

        self.buttons = []
        self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center')
        self.pile1 = Pile([self.button_grid, self.assigned_sl,
                           self.unrequired_undeployed_sl])
        self.pile2 = Pile([self.deployed_sl])
        return LineBox(Columns([self.pile1, self.pile2]),
                       title="Add Services")
Example #4
0
    def build_widgets(self):
        self.deploy_view = DeployView(self.display_controller,
                                      self.placement_controller,
                                      self.placement_view)

        def not_conflicted_p(cc):
            state, _, _ = self.placement_controller.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(not_conflicted_p, "Choose Machine",
                    self.placement_view.do_show_machine_chooser)]
        subordinate_actions = [(not_conflicted_p, "Add",
                                self.do_place_subordinate)]
        self.required_services_list = ServicesList(self.placement_controller,
                                                   actions,
                                                   subordinate_actions,
                                                   ignore_assigned=True,
                                                   ignore_deployed=True,
                                                   show_type='required',
                                                   show_constraints=True,
                                                   title="Required Services")
        self.additional_services_list = ServicesList(self.placement_controller,
                                                     actions,
                                                     subordinate_actions,
                                                     show_type='non-required',
                                                     show_constraints=True,
                                                     title="Additional "
                                                     "Services")

        autoplace_func = self.placement_view.do_autoplace
        self.autoplace_button = AttrMap(Button("Auto-place Remaining Services",
                                               on_press=autoplace_func),
                                        'button_secondary',
                                        'button_secondary focus')

        clear_all_func = self.placement_view.do_clear_all
        self.clear_all_button = AttrMap(Button("Clear all Placements",
                                               on_press=clear_all_func),
                                        'button_secondary',
                                        'button_secondary focus')

        self.required_services_pile = Pile([self.required_services_list,
                                            Divider()])
        self.additional_services_pile = Pile([self.additional_services_list,
                                              Divider()])

        self.top_buttons = []
        self.top_button_grid = GridFlow(self.top_buttons,
                                        36, 1, 0, 'center')

        pl = [Text(('subheading', "Services"), align='center'),
              Divider(),
              self.top_button_grid, Divider(),
              self.deploy_view, Divider(),
              self.required_services_pile, Divider(),
              self.additional_services_pile]

        self.main_pile = Pile(pl)

        return self.main_pile
    def build_widgets(self):

        instructions = Text("Remove services from {}".format(
            self.machine.hostname))

        self.machine_widget = MachineWidget(self.machine,
                                            self.controller,
                                            show_hardware=True)

        def show_remove_p(cc):
            md = self.controller.get_assignments(cc)
            for atype, ms in md.items():
                hostnames = [m.hostname for m in ms]
                if self.machine.hostname in hostnames:
                    return True
            return False

        actions = [(show_remove_p, 'Remove', self.do_remove)]

        self.services_list = ServicesList(self.controller,
                                          actions, actions,
                                          machine=self.machine)

        close_button = AttrMap(Button('X',
                                      on_press=self.close_pressed),
                               'button_secondary', 'button_secondary focus')
        p = Pile([GridFlow([close_button], 5, 1, 0, 'right'),
                  instructions, Divider(), self.machine_widget,
                  Divider(), self.services_list])

        return LineBox(p, title="Remove Services")
Example #6
0
 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)
Example #7
0
 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)
Example #8
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)
Example #9
0
 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 build_widget(self, **kwargs):

        def remove_p(charm_class):
            n = self.pc.assignment_machine_count_for_charm(charm_class)
            return n > 0

        def not_conflicted_p(cc):
            state, _, _ = self.pc.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(remove_p, 'Remove', self.do_remove),
                   (not_conflicted_p, 'Add', self.do_add)]
        self.unrequired_undeployed_sl = ServicesList(self.pc,
                                                     actions, actions,
                                                     ignore_deployed=True,
                                                     title="Un-Deployed")
        self.deployed_sl = ServicesList(self.pc,
                                        actions, actions,
                                        deployed_only=True,
                                        show_placements=True,
                                        title="Deployed Services")

        self.assigned_sl = ServicesList(self.pc,
                                        actions, actions,
                                        assigned_only=True,
                                        show_placements=True,
                                        title="Services to be Deployed")

        self.buttons = []
        self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center')
        self.pile1 = Pile([self.button_grid, self.assigned_sl,
                           self.unrequired_undeployed_sl])
        self.pile2 = Pile([self.deployed_sl])
        return LineBox(Columns([self.pile1, self.pile2]),
                       title="Add Services")
Example #11
0
class ServicesColumn(WidgetWrap):
    """Displays dynamic list of unplaced services and associated controls
    """
    def __init__(self, display_controller, placement_controller,
                 placement_view):
        self.display_controller = display_controller
        self.placement_controller = placement_controller
        self.placement_view = placement_view
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):
        self.deploy_view = DeployView(self.display_controller,
                                      self.placement_controller,
                                      self.placement_view)

        def not_conflicted_p(cc):
            state, _, _ = self.placement_controller.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(not_conflicted_p, "Choose Machine",
                    self.placement_view.do_show_machine_chooser)]
        subordinate_actions = [(not_conflicted_p, "Add",
                                self.do_place_subordinate)]
        self.required_services_list = ServicesList(self.placement_controller,
                                                   actions,
                                                   subordinate_actions,
                                                   ignore_assigned=True,
                                                   ignore_deployed=True,
                                                   show_type='required',
                                                   show_constraints=True,
                                                   title="Required Services")
        self.additional_services_list = ServicesList(self.placement_controller,
                                                     actions,
                                                     subordinate_actions,
                                                     ignore_assigned=True,
                                                     show_type='non-required',
                                                     show_constraints=True,
                                                     title="Additional "
                                                     "Services")

        autoplace_func = self.placement_view.do_autoplace
        self.autoplace_button = AttrMap(
            Button("Auto-place Remaining Services", on_press=autoplace_func),
            'button_secondary', 'button_secondary focus')

        clear_all_func = self.placement_view.do_clear_all
        self.clear_all_button = AttrMap(
            Button("Clear all Placements", on_press=clear_all_func),
            'button_secondary', 'button_secondary focus')

        self.required_services_pile = Pile(
            [self.required_services_list,
             Divider()])
        self.additional_services_pile = Pile(
            [self.additional_services_list,
             Divider()])

        self.top_buttons = []
        self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center')

        pl = [
            Text(('subheading', "Services"), align='center'),
            Divider(), self.top_button_grid,
            Divider(), self.deploy_view,
            Divider(), self.required_services_pile,
            Divider(), self.additional_services_pile
        ]

        self.main_pile = Pile(pl)

        return self.main_pile

    def update(self):
        self.deploy_view.update()
        self.required_services_list.update()
        self.additional_services_list.update()

        top_buttons = []
        unplaced = self.placement_controller.unassigned_undeployed_services()
        if len(unplaced) == 0:
            icon = SelectableIcon(" (Auto-place Remaining Services) ")
            top_buttons.append((AttrMap(icon, 'disabled_button',
                                        'disabled_button_focus'),
                                self.top_button_grid.options()))

        else:
            top_buttons.append(
                (self.autoplace_button, self.top_button_grid.options()))

        top_buttons.append(
            (self.clear_all_button, self.top_button_grid.options()))

        self.top_button_grid.contents = top_buttons

    def do_reset_to_defaults(self, sender):
        self.placement_controller.set_all_assignments(
            self.placement_controller.gen_defaults())

    def do_place_subordinate(self, sender, charm_class):
        sub_placeholder = self.placement_controller.sub_placeholder
        self.placement_controller.assign(sub_placeholder, charm_class,
                                         AssignmentType.BareMetal)
class AddServicesDialog(WidgetWrap):
    """ Dialog to add services. Does not specify placement.

    :param cb: callback routine to process submit/cancel actions
    """

    def __init__(self, install_controller, deploy_cb, cancel_cb):
        self.install_controller = install_controller
        self.orig_pc = install_controller.placement_controller
        self.pc = self.orig_pc.get_temp_copy()
        self.charms = []
        self.deploy_cb = deploy_cb
        self.cancel_cb = cancel_cb
        self.boxes = []
        w = self.build_widget()
        self.update()

        super().__init__(w)

    def build_widget(self, **kwargs):

        def remove_p(charm_class):
            n = self.pc.assignment_machine_count_for_charm(charm_class)
            return n > 0

        def not_conflicted_p(cc):
            state, _, _ = self.pc.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(remove_p, 'Remove', self.do_remove),
                   (not_conflicted_p, 'Add', self.do_add)]
        self.unrequired_undeployed_sl = ServicesList(self.pc,
                                                     actions, actions,
                                                     ignore_deployed=True,
                                                     title="Un-Deployed")
        self.deployed_sl = ServicesList(self.pc,
                                        actions, actions,
                                        deployed_only=True,
                                        show_placements=True,
                                        title="Deployed Services")

        self.assigned_sl = ServicesList(self.pc,
                                        actions, actions,
                                        assigned_only=True,
                                        show_placements=True,
                                        title="Services to be Deployed")

        self.buttons = []
        self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center')
        self.pile1 = Pile([self.button_grid, self.assigned_sl,
                           self.unrequired_undeployed_sl])
        self.pile2 = Pile([self.deployed_sl])
        return LineBox(Columns([self.pile1, self.pile2]),
                       title="Add Services")

    def update(self):
        self.unrequired_undeployed_sl.update()
        self.deployed_sl.update()
        self.assigned_sl.update()
        self.update_buttons()

    def update_buttons(self):
        buttons = [(AttrMap(Button("Cancel", self.handle_cancel),
                            'button_primary', 'button_primary focus'),
                    self.button_grid.options())]
        n_assigned = len(self.pc.assigned_services)
        if n_assigned > 0 and self.pc.can_deploy():
            b = AttrMap(Button("Deploy", self.handle_deploy),
                        'button_primary', 'button_primary focus')
        else:
            b = AttrMap(SelectableIcon("(Deploy)"),
                        'disabled_button',
                        'disabled_button_focus')
        buttons.append((b, self.button_grid.options()))
        self.button_grid.contents = buttons

    def do_add(self, sender, charm_class):
        """Add the selected charm using default juju location.
        Equivalent to a simple 'juju deploy foo'

        Attempt to also add any charms that are required as a result
        of the newly added charm.
        """

        def num_to_auto_add(charm_class):
            return (charm_class.required_num_units() -
                    self.pc.assignment_machine_count_for_charm(charm_class))

        for i in range(max(1, num_to_auto_add(charm_class))):
            self.pc.assign(self.pc.def_placeholder, charm_class,
                           AssignmentType.DEFAULT)

        def get_unassigned_requireds():
            return [cc for cc in
                    self.pc.unassigned_undeployed_services()
                    if self.pc.get_charm_state(cc)[0] ==
                    CharmState.REQUIRED]

        while len(get_unassigned_requireds()) > 0:
            for u_r_cc in get_unassigned_requireds():
                for i in range(num_to_auto_add(u_r_cc)):
                    self.pc.assign(self.pc.def_placeholder, u_r_cc,
                                   AssignmentType.DEFAULT)
        self.update()

    def do_remove(self, sender, charm_class):
        "Undo an assignment"
        self.pc.remove_one_assignment(self.pc.def_placeholder,
                                      charm_class)
        self.update()

    def handle_deploy(self, button):
        """Commits changes to the main placement controller, and calls the
        deploy callback to do the rest.
        """
        self.orig_pc.update_from_controller(self.pc)
        self.deploy_cb()

    def handle_cancel(self, button):
        self.cancel_cb()
Example #13
0
    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 ServiceChooser(WidgetWrap):

    """Presents list of services to put on a machine.

    Supports multiple selection, implying separate containers using
    --to.

    """

    def __init__(self, controller, machine, parent_widget):
        """
        controller is a PlacementController
        machine is the machine to show Services for
        parent_widget should support 'remove_overlay()'
        """
        self.controller = controller
        self.machine = machine
        self.parent_widget = parent_widget
        w = self.build_widgets()
        super().__init__(w)

    def build_widgets(self):

        instructions = Text("Remove services from {}".format(
            self.machine.hostname))

        self.machine_widget = MachineWidget(self.machine,
                                            self.controller,
                                            show_hardware=True)

        def show_remove_p(cc):
            md = self.controller.get_assignments(cc)
            for atype, ms in md.items():
                hostnames = [m.hostname for m in ms]
                if self.machine.hostname in hostnames:
                    return True
            return False

        actions = [(show_remove_p, 'Remove', self.do_remove)]

        self.services_list = ServicesList(self.controller,
                                          actions, actions,
                                          machine=self.machine)

        close_button = AttrMap(Button('X',
                                      on_press=self.close_pressed),
                               'button_secondary', 'button_secondary focus')
        p = Pile([GridFlow([close_button], 5, 1, 0, 'right'),
                  instructions, Divider(), self.machine_widget,
                  Divider(), self.services_list])

        return LineBox(p, title="Remove Services")

    def update(self):
        self.machine_widget.update()
        self.services_list.update()

    def do_add(self, sender, charm_class, atype):
        self.controller.assign(self.machine, charm_class, atype)
        self.update()

    def do_remove(self, sender, charm_class):
        self.controller.remove_one_assignment(self.machine,
                                              charm_class)
        self.update()

    def close_pressed(self, sender):
        self.parent_widget.remove_overlay(self)
    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 AddServicesDialog(WidgetWrap):
    """ Dialog to add services. Does not specify placement.

    :param cb: callback routine to process submit/cancel actions
    """

    def __init__(self, install_controller, deploy_cb, cancel_cb):
        self.install_controller = install_controller
        self.orig_pc = install_controller.placement_controller
        self.pc = self.orig_pc.get_temp_copy()
        self.charms = []
        self.deploy_cb = deploy_cb
        self.cancel_cb = cancel_cb
        self.boxes = []
        w = self.build_widget()
        self.update()

        super().__init__(w)

    def build_widget(self, **kwargs):

        def remove_p(charm_class):
            n = self.pc.assignment_machine_count_for_charm(charm_class)
            return n > 0

        def not_conflicted_p(cc):
            state, _, _ = self.pc.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(remove_p, 'Remove', self.do_remove),
                   (not_conflicted_p, 'Add', self.do_add)]
        self.unrequired_undeployed_sl = ServicesList(self.pc,
                                                     actions, actions,
                                                     ignore_deployed=True,
                                                     title="Un-Deployed")
        self.deployed_sl = ServicesList(self.pc,
                                        actions, actions,
                                        deployed_only=True,
                                        show_placements=True,
                                        title="Deployed Services")

        self.assigned_sl = ServicesList(self.pc,
                                        actions, actions,
                                        assigned_only=True,
                                        show_placements=True,
                                        title="Services to be Deployed")

        self.buttons = []
        self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center')
        self.pile1 = Pile([self.button_grid, self.assigned_sl,
                           self.unrequired_undeployed_sl])
        self.pile2 = Pile([self.deployed_sl])
        return LineBox(Columns([self.pile1, self.pile2]),
                       title="Add Services")

    def update(self):
        self.unrequired_undeployed_sl.update()
        self.deployed_sl.update()
        self.assigned_sl.update()
        self.update_buttons()

    def update_buttons(self):
        buttons = [(AttrMap(Button("Cancel", self.handle_cancel),
                            'button_primary', 'button_primary focus'),
                    self.button_grid.options())]
        n_assigned = len(self.pc.assigned_services)
        if n_assigned > 0 and self.pc.can_deploy():
            b = AttrMap(Button("Deploy", self.handle_deploy),
                        'button_primary', 'button_primary focus')
        else:
            b = AttrMap(SelectableIcon("(Deploy)"),
                        'disabled_button',
                        'disabled_button_focus')
        buttons.append((b, self.button_grid.options()))
        self.button_grid.contents = buttons

    def do_add(self, sender, charm_class):
        """Add the selected charm using default juju location.
        Equivalent to a simple 'juju deploy foo'

        Attempt to also add any charms that are required as a result
        of the newly added charm.
        """

        def num_to_auto_add(charm_class):
            return (charm_class.required_num_units() -
                    self.pc.assignment_machine_count_for_charm(charm_class))

        for i in range(num_to_auto_add(charm_class)):
            self.pc.assign(self.pc.def_placeholder, charm_class,
                           AssignmentType.DEFAULT)

        def get_unassigned_requireds():
            return [cc for cc in
                    self.pc.unassigned_undeployed_services()
                    if self.pc.get_charm_state(cc)[0] ==
                    CharmState.REQUIRED]

        while len(get_unassigned_requireds()) > 0:
            for u_r_cc in get_unassigned_requireds():
                for i in range(num_to_auto_add(u_r_cc)):
                    self.pc.assign(self.pc.def_placeholder, u_r_cc,
                                   AssignmentType.DEFAULT)
        self.update()

    def do_remove(self, sender, charm_class):
        "Undo an assignment"
        self.pc.remove_one_assignment(self.pc.def_placeholder,
                                      charm_class)
        self.update()

    def handle_deploy(self, button):
        """Commits changes to the main placement controller, and calls the
        deploy callback to do the rest.
        """
        self.orig_pc.update_from_controller(self.pc)
        self.deploy_cb()

    def handle_cancel(self, button):
        self.cancel_cb()
Example #17
0
class ServicesColumn(WidgetWrap):

    """Displays dynamic list of unplaced services and associated controls
    """

    def __init__(self, display_controller, placement_controller,
                 placement_view):
        self.display_controller = display_controller
        self.placement_controller = placement_controller
        self.placement_view = placement_view
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):
        self.deploy_view = DeployView(self.display_controller,
                                      self.placement_controller,
                                      self.placement_view)

        def not_conflicted_p(cc):
            state, _, _ = self.placement_controller.get_charm_state(cc)
            return state != CharmState.CONFLICTED

        actions = [(not_conflicted_p, "Choose Machine",
                    self.placement_view.do_show_machine_chooser)]
        subordinate_actions = [(not_conflicted_p, "Add",
                                self.do_place_subordinate)]
        self.required_services_list = ServicesList(self.placement_controller,
                                                   actions,
                                                   subordinate_actions,
                                                   ignore_assigned=True,
                                                   ignore_deployed=True,
                                                   show_type='required',
                                                   show_constraints=True,
                                                   title="Required Services")
        self.additional_services_list = ServicesList(self.placement_controller,
                                                     actions,
                                                     subordinate_actions,
                                                     ignore_assigned=True,
                                                     show_type='non-required',
                                                     show_constraints=True,
                                                     title="Additional "
                                                     "Services")

        autoplace_func = self.placement_view.do_autoplace
        self.autoplace_button = AttrMap(Button("Auto-place Remaining Services",
                                               on_press=autoplace_func),
                                        'button_secondary',
                                        'button_secondary focus')

        clear_all_func = self.placement_view.do_clear_all
        self.clear_all_button = AttrMap(Button("Clear all Placements",
                                               on_press=clear_all_func),
                                        'button_secondary',
                                        'button_secondary focus')

        self.required_services_pile = Pile([self.required_services_list,
                                            Divider()])
        self.additional_services_pile = Pile([self.additional_services_list,
                                              Divider()])

        self.top_buttons = []
        self.top_button_grid = GridFlow(self.top_buttons,
                                        36, 1, 0, 'center')

        pl = [Text(('subheading', "Services"), align='center'),
              Divider(),
              self.top_button_grid, Divider(),
              self.deploy_view, Divider(),
              self.required_services_pile, Divider(),
              self.additional_services_pile]

        self.main_pile = Pile(pl)

        return self.main_pile

    def update(self):
        self.deploy_view.update()
        self.required_services_list.update()
        self.additional_services_list.update()

        top_buttons = []
        unplaced = self.placement_controller.unassigned_undeployed_services()
        if len(unplaced) == 0:
            icon = SelectableIcon(" (Auto-place Remaining Services) ")
            top_buttons.append((AttrMap(icon,
                                        'disabled_button',
                                        'disabled_button_focus'),
                                self.top_button_grid.options()))

        else:
            top_buttons.append((self.autoplace_button,
                                self.top_button_grid.options()))

        top_buttons.append((self.clear_all_button,
                            self.top_button_grid.options()))

        self.top_button_grid.contents = top_buttons

    def do_reset_to_defaults(self, sender):
        self.placement_controller.set_all_assignments(
            self.placement_controller.gen_defaults())

    def do_place_subordinate(self, sender, charm_class):
        sub_placeholder = self.placement_controller.sub_placeholder
        self.placement_controller.assign(sub_placeholder,
                                         charm_class,
                                         AssignmentType.BareMetal)
Example #18
0
class ServiceChooser(WidgetWrap):
    """Presents list of services to put on a machine.

    Supports multiple selection, implying separate containers using
    --to.

    """
    def __init__(self, controller, machine, parent_widget):
        """
        controller is a PlacementController
        machine is the machine to show Services for
        parent_widget should support 'remove_overlay()'
        """
        self.controller = controller
        self.machine = machine
        self.parent_widget = parent_widget
        w = self.build_widgets()
        super().__init__(w)

    def build_widgets(self):

        instructions = Text("Remove services from {}".format(
            self.machine.hostname))

        self.machine_widget = MachineWidget(self.machine,
                                            self.controller,
                                            show_hardware=True)

        def show_remove_p(cc):
            md = self.controller.get_assignments(cc)
            for atype, ms in md.items():
                hostnames = [m.hostname for m in ms]
                if self.machine.hostname in hostnames:
                    return True
            return False

        actions = [(show_remove_p, 'Remove', self.do_remove)]

        self.services_list = ServicesList(self.controller,
                                          actions,
                                          actions,
                                          machine=self.machine)

        close_button = AttrMap(Button('X', on_press=self.close_pressed),
                               'button_secondary', 'button_secondary focus')
        p = Pile([
            GridFlow([close_button], 5, 1, 0, 'right'), instructions,
            Divider(), self.machine_widget,
            Divider(), self.services_list
        ])

        return LineBox(p, title="Remove Services")

    def update(self):
        self.machine_widget.update()
        self.services_list.update()

    def do_add(self, sender, charm_class, atype):
        self.controller.assign(self.machine, charm_class, atype)
        self.update()

    def do_remove(self, sender, charm_class):
        self.controller.remove_one_assignment(self.machine, charm_class)
        self.update()

    def close_pressed(self, sender):
        self.parent_widget.remove_overlay(self)