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()
class MachineWidget(WidgetWrap): """A widget displaying a service and associated actions. machine - the machine to display controller - a PlacementController instance actions - a list of ('label', function) pairs that wil be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_hardware - display hardware details about this machine show_assignments - display info about which charms are assigned and what assignment type (LXC, KVM, etc) they have. """ def __init__(self, machine, controller, actions=None, show_hardware=False, show_assignments=True): self.machine = machine self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_hardware = show_hardware self.show_assignments = show_assignments w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def hardware_info_markup(self): m = self.machine return [('label', 'arch'), ' {} '.format(m.arch), ('label', 'cores'), ' {} '.format(m.cpu_cores), ('label', 'mem'), ' {} '.format(m.mem), ('label', 'storage'), ' {}'.format(m.storage)] def build_widgets(self): self.machine_info_widget = Text("") self.assignments_widget = Text("") self.hardware_widget = Text("") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [Divider(' '), self.machine_info_widget] if self.show_hardware: pl.append(self.hardware_widget) if self.show_assignments: pl += [Divider(' '), self.assignments_widget] pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update_machine(self): """Refresh with potentially updated machine info from controller. Assumes that machine exists - machines going away is handled in machineslist.update(). """ self.machine = next((m for m in self.controller.machines() if m.instance_id == self.machine.instance_id), None) def update(self): self.update_machine() if self.machine == self.controller.sub_placeholder: self.machine_info_widget.set_text("\N{BULLET} Subordinate Charms") self.hardware_widget.set_text("") elif self.machine == self.controller.def_placeholder: self.machine_info_widget.set_text("\N{BULLET} Juju Default " "Placement") self.hardware_widget.set_text("") else: info_markup = [ "\N{TAPE DRIVE} {}".format(self.machine.hostname), ('label', " ({})".format(self.machine.status)) ] self.machine_info_widget.set_text(info_markup) self.hardware_widget.set_text([" "] + self.hardware_info_markup()) ad = self.controller.assignments_for_machine(self.machine) astr = [('label', " Services: ")] for atype, al in ad.items(): n = len(al) if n == 1: pl_s = "" else: pl_s = "s" if atype == AssignmentType.BareMetal: astr.append(('label', "\n {} service{}" " on Bare Metal: ".format(n, pl_s))) else: astr.append(('label', "\n {} " "{}{}: ".format(n, atype.name, pl_s))) if n == 0: astr.append("\N{EMPTY SET}") else: astr.append(", ".join( ["\N{GEAR} {}".format(c.display_name) for c in al])) if self.machine == self.controller.sub_placeholder: assignments_text = '' for _, al in ad.items(): charm_txts = ["\N{GEAR} {}".format(c.display_name) for c in al] assignments_text += ", ".join(charm_txts) else: assignments_text = astr self.assignments_widget.set_text(assignments_text) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.machine): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap( Button(label, on_press=func, user_data=self.machine), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
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 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(("body", "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()
class ServiceWidget(WidgetWrap): """A widget displaying a service and associated actions. charm_class - the class describing the service to display controller - a PlacementController instance actions - a list of ('label', function) pairs that will be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_constraints - display the charm's constraints show_placements - display the machine(s) currently assigned to host this service, both planned deployments (aka 'assignments', and already-deployed, called 'deployments'). """ def __init__(self, charm_class, controller, actions=None, show_constraints=False, show_placements=False): self.charm_class = charm_class self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_constraints = show_constraints self.show_placements = show_placements w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] if self.charm_class.summary != "": self.title_markup.append("\n {}\n".format(self.charm_class.summary)) self.charm_info_widget = Text(self.title_markup) self.placements_widget = Text("") if self.charm_class.subordinate: c_str = [('label', " (subordinate charm)")] elif len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [format_constraint(k, v) for k, v in self.charm_class.constraints.items()] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_placements: pl.append(self.placements_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update(self): mstr = [""] state, cons, deps = self.controller.get_charm_state(self.charm_class) if state == CharmState.REQUIRED: p = self.controller.get_assignments(self.charm_class) d = self.controller.get_deployments(self.charm_class) nr = self.charm_class.required_num_units() info_str = " ({} of {} placed".format(len(p), nr) if len(d) > 0: info_str += ", {} deployed)".format(len(d)) else: info_str += ")" # Add hint to explain why a dep showed up in required if len(p) == 0 and len(deps) > 0: dep_str = ", ".join([c.display_name for c in deps]) info_str += " - required by {}".format(dep_str) self.title_markup[1] = ('info', info_str) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.CONFLICTED: con_str = ", ".join([c.display_name for c in cons]) self.title_markup[1] = ('error_icon', ' - Conflicts with {}'.format(con_str)) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.OPTIONAL: self.title_markup[1] = "" self.charm_info_widget.set_text(self.title_markup) def string_for_placement_dict(d): s = [] for atype, ml in d.items(): n = len(ml) s.append(('label', " {} ({}): ".format(atype.name, n))) if len(ml) == 0: s.append("\N{DOTTED CIRCLE}") else: s.append(", ".join(["\N{TAPE DRIVE} {}".format(m.hostname) for m in ml])) if len(s) == 0: return [('label', "None")] return s mstr += [" ", ('label', "Assignments: ")] ad = self.controller.get_assignments(self.charm_class) dd = self.controller.get_deployments(self.charm_class) mstr += string_for_placement_dict(ad) mstr += ["\n ", ('label', "Deployments: ")] mstr += string_for_placement_dict(dd) self.placements_widget.set_text(mstr) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.charm_class): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap(Button(label, on_press=func, user_data=self.charm_class), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
class MachineWidget(WidgetWrap): """A widget displaying a service and associated actions. machine - the machine to display controller - a PlacementController instance actions - a list of ('label', function) pairs that wil be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_hardware - display hardware details about this machine show_assignments - display info about which charms are assigned and what assignment type (LXC, KVM, etc) they have. """ def __init__(self, machine, controller, actions=None, show_hardware=False, show_assignments=True): self.machine = machine self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_hardware = show_hardware self.show_assignments = show_assignments w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def hardware_info_markup(self): m = self.machine return [('label', 'arch'), ' {} '.format(m.arch), ('label', 'cores'), ' {} '.format(m.cpu_cores), ('label', 'mem'), ' {} '.format(m.mem), ('label', 'storage'), ' {}'.format(m.storage)] def build_widgets(self): self.machine_info_widget = Text("") self.assignments_widget = Text("") self.hardware_widget = Text("") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [Divider(' '), self.machine_info_widget] if self.show_hardware: pl.append(self.hardware_widget) if self.show_assignments: pl += [Divider(' '), self.assignments_widget] pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update_machine(self): """Refresh with potentially updated machine info from controller. Assumes that machine exists - machines going away is handled in machineslist.update(). """ self.machine = next((m for m in self.controller.machines() if m.instance_id == self.machine.instance_id), None) def update(self): self.update_machine() if self.machine == self.controller.sub_placeholder: self.machine_info_widget.set_text("\N{BULLET} Subordinate Charms") self.hardware_widget.set_text("") elif self.machine == self.controller.def_placeholder: self.machine_info_widget.set_text("\N{BULLET} Juju Default " "Placement") self.hardware_widget.set_text("") else: info_markup = ["\N{TAPE DRIVE} {}".format(self.machine.hostname), ('label', " ({})".format(self.machine.status))] self.machine_info_widget.set_text(info_markup) self.hardware_widget.set_text([" "] + self.hardware_info_markup()) ad = self.controller.assignments_for_machine(self.machine) astr = [('label', " Services: ")] for atype, al in ad.items(): n = len(al) if n == 1: pl_s = "" else: pl_s = "s" if atype == AssignmentType.BareMetal: astr.append(('label', "\n {} service{}" " on Bare Metal: ".format(n, pl_s))) else: astr.append(('label', "\n {} " "{}{}: ".format(n, atype.name, pl_s))) if n == 0: astr.append("\N{EMPTY SET}") else: astr.append(", ".join(["\N{GEAR} {}".format(c.display_name) for c in al])) if self.machine == self.controller.sub_placeholder: assignments_text = '' for _, al in ad.items(): charm_txts = ["\N{GEAR} {}".format(c.display_name) for c in al] assignments_text += ", ".join(charm_txts) else: assignments_text = astr self.assignments_widget.set_text(assignments_text) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.machine): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap(Button(label, on_press=func, user_data=self.machine), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
class ServiceWidget(WidgetWrap): """A widget displaying a service and associated actions. charm_class - the class describing the service to display controller - a PlacementController instance actions - a list of ('label', function) pairs that will be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_constraints - display the charm's constraints show_placements - display the machine(s) currently assigned to host this service, both planned deployments (aka 'assignments', and already-deployed, called 'deployments'). """ def __init__(self, charm_class, controller, actions=None, show_constraints=False, show_placements=False): self.charm_class = charm_class self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_constraints = show_constraints self.show_placements = show_placements w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] self.charm_info_widget = Text(self.title_markup) self.placements_widget = Text("") if self.charm_class.subordinate: c_str = [('label', " (subordinate charm)")] elif len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [ format_constraint(k, v) for k, v in self.charm_class.constraints.items() ] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_placements: pl.append(self.placements_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update(self): mstr = [""] state, cons, deps = self.controller.get_charm_state(self.charm_class) if state == CharmState.REQUIRED: p = self.controller.get_assignments(self.charm_class) d = self.controller.get_deployments(self.charm_class) nr = self.charm_class.required_num_units() info_str = " ({} of {} placed".format(len(p), nr) if len(d) > 0: info_str += ", {} deployed)".format(len(d)) else: info_str += ")" # Add hint to explain why a dep showed up in required if len(p) == 0 and len(deps) > 0: dep_str = ", ".join([c.display_name for c in deps]) info_str += " - required by {}".format(dep_str) self.title_markup[1] = ('info', info_str) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.CONFLICTED: con_str = ", ".join([c.display_name for c in cons]) self.title_markup[1] = ('error_icon', ' - Conflicts with {}'.format(con_str)) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.OPTIONAL: self.title_markup[1] = "" self.charm_info_widget.set_text(self.title_markup) def string_for_placement_dict(d): s = [] for atype, ml in d.items(): n = len(ml) s.append(('label', " {} ({}): ".format(atype.name, n))) if len(ml) == 0: s.append("\N{DOTTED CIRCLE}") else: s.append(", ".join( ["\N{TAPE DRIVE} {}".format(m.hostname) for m in ml])) if len(s) == 0: return [('label', "None")] return s mstr += [" ", ('label', "Assignments: ")] ad = self.controller.get_assignments(self.charm_class) dd = self.controller.get_deployments(self.charm_class) mstr += string_for_placement_dict(ad) mstr += ["\n ", ('label', "Deployments: ")] mstr += string_for_placement_dict(dd) self.placements_widget.set_text(mstr) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.charm_class): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap( Button(label, on_press=func, user_data=self.charm_class), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
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) actions = [("Choose Machine", self.placement_view.do_show_machine_chooser)] self.required_services_list = ServicesList(self.placement_controller, actions, unplaced_only=True, show_type='required', show_constraints=True, title="Required Services") self.additional_services_list = ServicesList(self.placement_controller, 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 update(self): self.deploy_view.update() self.required_services_list.update() self.additional_services_list.update() top_buttons = [] if len(self.placement_controller.unplaced_services) == 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())
class ServiceWidget(WidgetWrap): """A widget displaying a service and associated actions. charm_class - the class describing the service to display controller - a PlacementController instance actions - a list of ('label', function) pairs that will be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_constraints - display the charm's constraints show_assignments - display the machine(s) currently assigned to host this service """ def __init__(self, charm_class, controller, actions=None, show_constraints=False, show_assignments=False): self.charm_class = charm_class self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_constraints = show_constraints self.show_assignments = show_assignments w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] self.charm_info_widget = Text(self.title_markup) self.assignments_widget = Text("") if len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [format_constraint(k, v) for k, v in self.charm_class.constraints.items()] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_assignments: pl.append(self.assignments_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update(self): md = self.controller.machines_for_charm(self.charm_class) mstr = [""] if self.controller.service_is_required(self.charm_class): np = self.controller.machine_count_for_charm(self.charm_class) nr = self.charm_class.required_num_units() self.title_markup[1] = ('info', " ({} of {} placed)".format(np, nr)) self.charm_info_widget.set_text(self.title_markup) for atype, ml in md.items(): n = len(ml) mstr.append(('label', " {} ({}): ".format(atype.name, n))) if len(ml) == 0: mstr.append("\N{DOTTED CIRCLE}") else: mstr.append(", ".join(["\N{TAPE DRIVE} {}".format(m.hostname) for m in ml])) mstr.append("\n") self.assignments_widget.set_text(mstr) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: predicate = lambda x: True label, func = at else: predicate, label, func = at if not predicate(self.charm_class): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap(Button(label, on_press=func, user_data=self.charm_class), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons