diff --git a/Makefile b/Makefile index fb0d2eb..ee36a1f 100644 --- a/Makefile +++ b/Makefile @@ -487,12 +487,19 @@ coverage-jenkins: # # gettext, po files, etc # - po/POTFILES.in: # generate the POTFILES.in file expected by intltool. it wants one # file per line, but we're lazy. find $(SRC_DIR)/ $(RCT_SRC_DIR) $(RD_SRC_DIR) $(DAEMONS_SRC_DIR) $(YUM_PLUGINS_SRC_DIR) -name "*.py" > po/POTFILES.in - find $(SRC_DIR)/gui/data/ -name "*.glade" >> po/POTFILES.in + find $(SRC_DIR)/gui/data/glade/ -name "*.glade" >> po/POTFILES.in + # intltool-update doesn't recognize .ui as glade files, so + # build a dir of .glade symlinks to the .ui files and add to POTFILES.in + mkdir -p po/tmp_ui_links + for ui_file in ./$(SRC_DIR)/gui/data/ui/*.ui ; do \ + ui_base=$$(basename "$$ui_file") ; \ + ln -f -s "../../$$ui_file" "po/tmp_ui_links/$$ui_base.glade" ; \ + done ; + find po/tmp_ui_links/ -name "*.glade" >> po/POTFILES.in find $(BIN_DIR) -name "*-to-rhsm" >> po/POTFILES.in find $(BIN_DIR) -name "subscription-manager*" >> po/POTFILES.in find $(BIN_DIR) -name "rct" >> po/POTFILES.in diff --git a/man/rhsm.conf.5 b/man/rhsm.conf.5 index 04e9aca..29349b2 100644 --- a/man/rhsm.conf.5 +++ b/man/rhsm.conf.5 @@ -51,7 +51,7 @@ The port which the subscription service is listening on\&. .PP insecure .RS 4 -This flag enables or disables certification verification using the certificate authorities which are installed in /etc/rhsm/ca\&. +This flag enables or disables entitlement server certification verification using the certificate authorities which are installed in /etc/rhsm/ca\&. .RE .PP ssl_verify_depth diff --git a/rel-eng/packages/subscription-manager b/rel-eng/packages/subscription-manager index 6f399f3..9f6984a 100644 --- a/rel-eng/packages/subscription-manager +++ b/rel-eng/packages/subscription-manager @@ -1 +1 @@ -1.15.9-7 ./ +1.15.9-8 ./ diff --git a/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.py b/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.py index 09ca0b6..43b6947 100644 --- a/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.py +++ b/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.py @@ -22,6 +22,7 @@ import sys from pyanaconda.ui.gui.spokes import NormalSpoke from pyanaconda.ui.common import FirstbootOnlySpokeMixIn from pyanaconda.ui.categories.system import SystemCategory +from pyanaconda.ui.gui.utils import really_hide log = logging.getLogger(__name__) @@ -38,6 +39,7 @@ from subscription_manager.gui import managergui from subscription_manager.injectioninit import init_dep_injection from subscription_manager import injection as inj from subscription_manager.gui import registergui +from subscription_manager.gui import utils ga_GObject.threads_init() @@ -45,7 +47,7 @@ __all__ = ["RHSMSpoke"] class RHSMSpoke(FirstbootOnlySpokeMixIn, NormalSpoke): - buildrObjects = ["RHSMSpokeWindow", "RHSMSpokeWindow-action_area1"] + buildrObjects = ["RHSMSpokeWindow"] mainWidgetName = "RHSMSpokeWindow" @@ -67,68 +69,113 @@ class RHSMSpoke(FirstbootOnlySpokeMixIn, NormalSpoke): init_dep_injection() facts = inj.require(inj.FACTS) + backend = managergui.Backend() - self._registergui = registergui.RegisterScreen(backend, facts, - callbacks=[self.finished]) - self._action_area = self.builder.get_object("RHSMSpokeWindow-action_area1") - self._register_box = self._registergui.dialog_vbox6 - - # FIXME: close_window handling is kind of a mess. Standlone subman gui, - # the firstboot screens, and initial-setup need it to do different - # things. Potentially a 'Im done with this window now' signal, with - # each attaching different handlers. - self._registergui.close_window_callback = self._close_window_callback - - self._registergui._error_screen = registergui.CHOOSE_SERVER_PAGE - - # we have a ref to _register_box, but need to remove it from - # the regustergui.window (a GtkDialog), and add it to the main - # box in the action area of our initial-setup screen. - self._registergui.window.remove(self._register_box) - self._action_area.pack_end(self._register_box, True, True, 0) - self._action_area.show() - self._register_box.show_all() - self._registergui.initialize() - - def _close_window_callback(self): - self._registergui.goto_error_screen() - - def finished(self): - self._registergui.done() - self._registergui.cancel_button.hide() - self._registergui.register_button.hide() + self.register_widget = registergui.RegisterWidget(backend, facts, + parent_window=self.main_window) + + self.register_box = self.builder.get_object("register_box") + self.button_box = self.builder.get_object('navigation_button_box') + self.proceed_button = self.builder.get_object('proceed_button') + self.cancel_button = self.builder.get_object('cancel_button') + + self.register_box.pack_start(self.register_widget.register_widget, + True, True, 0) + + # Hook up the nav buttons in the gui + # TODO: add a 'start over'? + self.proceed_button.connect('clicked', self._on_register_button_clicked) + self.cancel_button.connect('clicked', self.cancel) + + # initial-setup will likely + self.register_widget.connect('finished', self.finished) + self.register_widget.connect('register-finished', self.register_finished) + self.register_widget.connect('register-error', self._on_register_error) + + # update the 'next/register button on page change' + self.register_widget.connect('notify::register-button-label', + self._on_register_button_label_change) + + self.register_box.show_all() + self.register_widget.initialize() + + # handler for RegisterWidgets 'finished' signal + def finished(self, obj): + self._done = True + really_hide(self.button_box) + + # If we completed registration, that's close enough to consider + # completed. + def register_finished(self, obj): self._done = True # Update gui widgets to reflect state of self.data + # This could also be used to pre populate partial answers from a ks + # or answer file def refresh(self): log.debug("data.addons.com_redhat_subscription_manager %s", self.data.addons.com_redhat_subscription_manager) - pass + # take info from the gui widgets and set into the self.data def apply(self): self.data.addons.com_redhat_subscription_manager.text = \ "System is registered to Red Hat Subscription Management." + # when the spoke is left, this can run anything that happens def execute(self): pass + def cancel(self, button): + # TODO: clear out settings and restart? + # TODO: attempt to undo the REST api calls we've made? + self.register_widget.set_initial_screen() + self.register_widget.clear_screens() + + # A property indicating the spoke is ready to be visited. This + # could depend on other modules or waiting for internal state to be setup. @property def ready(self): return True + # Indicate if all the mandatory actions are completed @property def completed(self): + # TODO: tie into register_widget.info.register-state return self._done + # indicate if the module has to be completed before initial-setup is done. @property def mandatory(self): return False + # A user facing string showing a summary of the status. This is displayed + # under the spokes name on it's hub. @property def status(self): if self._done: + # TODO: add consumer uuid, products, etc? return "System is registered to RHSM." else: return "System is not registered to RHSM." + + def _on_register_button_clicked(self, button): + # unset any error info + self.clear_info() + + self.register_widget.emit('proceed') + + def _on_register_error(self, widget, msg, exc_info): + if exc_info: + formatted_msg = utils.format_exception(exc_info, msg) + self.set_error(formatted_msg) + else: + log.error(msg) + self.set_error(msg) + + def _on_register_button_label_change(self, obj, value): + register_label = obj.get_property('register-button-label') + + if register_label: + self.proceed_button.set_label(register_label) diff --git a/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.ui b/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.ui index ea465a3..3a5f64f 100644 --- a/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.ui +++ b/src/initial-setup/com_redhat_subscription_manager/gui/spokes/rhsm_gui.ui @@ -39,10 +39,60 @@ False + start vertical 6 - + + True + False + start + end + + + Cancel + True + 0.43999999761581421 + + + True + True + 0 + + + + + Register + True + 0.56999999284744263 + + + True + True + 1 + + + + + False + True + end + 0 + + + + + True + False + + + + + + False + False + 1 + diff --git a/src/subscription_manager/ga_impls/ga_gtk2/GObject.py b/src/subscription_manager/ga_impls/ga_gtk2/GObject.py index 3783ce7..d77a280 100644 --- a/src/subscription_manager/ga_impls/ga_gtk2/GObject.py +++ b/src/subscription_manager/ga_impls/ga_gtk2/GObject.py @@ -5,21 +5,22 @@ from gobject import GObject from gobject import MainLoop # methods -from gobject import add_emission_hook, idle_add, source_remove, timeout_add +from gobject import add_emission_hook, idle_add, property +from gobject import source_remove, timeout_add from gobject import markup_escape_text # enums -from gobject import SIGNAL_RUN_LAST +from gobject import SIGNAL_RUN_FIRST, SIGNAL_RUN_LAST from gobject import TYPE_BOOLEAN, TYPE_PYOBJECT, PARAM_READWRITE class SignalFlags(object): + RUN_FIRST = SIGNAL_RUN_FIRST RUN_LAST = SIGNAL_RUN_LAST - constants = [TYPE_BOOLEAN, TYPE_PYOBJECT, PARAM_READWRITE] methods = [add_emission_hook, idle_add, markup_escape_text, - source_remove, timeout_add] + property, source_remove, timeout_add] enums = [SignalFlags] objects = [GObject, MainLoop] __all__ = objects + methods + constants + enums diff --git a/src/subscription_manager/ga_impls/ga_gtk2/Gtk.py b/src/subscription_manager/ga_impls/ga_gtk2/Gtk.py index d009019..3d04bbc 100644 --- a/src/subscription_manager/ga_impls/ga_gtk2/Gtk.py +++ b/src/subscription_manager/ga_impls/ga_gtk2/Gtk.py @@ -26,11 +26,13 @@ from gtk import SORT_ASCENDING from gtk import SELECTION_NONE from gtk import STATE_NORMAL from gtk import WINDOW_TOPLEVEL -from gtk import WIN_POS_MOUSE, WIN_POS_CENTER_ON_PARENT +from gtk import WIN_POS_MOUSE, WIN_POS_CENTER, WIN_POS_CENTER_ON_PARENT # methods from gtk import image_new_from_icon_name +from gtk import events_pending from gtk import main +from gtk import main_iteration from gtk import main_quit from gtk import check_version @@ -98,6 +100,7 @@ class WindowType(object): class WindowPosition(object): MOUSE = WIN_POS_MOUSE + CENTER = WIN_POS_CENTER CENTER_ON_PARENT = WIN_POS_CENTER_ON_PARENT @@ -129,6 +132,6 @@ widgets = [AboutDialog, Adjustment, Builder, Button, Calendar, CellRendererPixbu RadioButton, SpinButton, TextBuffer, TreeStore, TreeView, TreeViewColumn, VBox, Viewport, Window] -methods = [check_version, main, main_quit] +methods = [check_version, events_pending, main, main_iteration, main_quit] __all__ = widgets + constants + methods + enums diff --git a/src/subscription_manager/gui/autobind.py b/src/subscription_manager/gui/autobind.py index 8d55cf4..f863f0c 100644 --- a/src/subscription_manager/gui/autobind.py +++ b/src/subscription_manager/gui/autobind.py @@ -47,7 +47,6 @@ class DryRunResult(object): # The products that would be covered if we did this autobind: autobind_products = set() - log.debug("Unentitled products: %s" % required_products) for pool_quantity in self.json: pool = pool_quantity['pool'] # This is usually the MKT product and has no content, but it diff --git a/src/subscription_manager/gui/data/glade/choose_server.glade b/src/subscription_manager/gui/data/glade/choose_server.glade index 622e4fc..e67b428 100644 --- a/src/subscription_manager/gui/data/glade/choose_server.glade +++ b/src/subscription_manager/gui/data/glade/choose_server.glade @@ -114,7 +114,6 @@ True True - server_entry diff --git a/src/subscription_manager/gui/data/glade/register_dialog.glade b/src/subscription_manager/gui/data/glade/register_dialog.glade new file mode 100644 index 0000000..fe76ce1 --- /dev/null +++ b/src/subscription_manager/gui/data/glade/register_dialog.glade @@ -0,0 +1,81 @@ + + + + + + False + 5 + System Registration + subscription-manager + splashscreen + + + True + False + 2 + + + True + False + end + + + gtk-cancel + True + True + True + True + right + + + cancel_button + + + + + False + False + 0 + + + + + Register + True + True + True + True + True + right + + + register_button + register_button + + + + + False + False + 1 + + + + + False + False + end + 0 + + + + + + + + + cancel_button + register_button + + + diff --git a/src/subscription_manager/gui/data/glade/registration.glade b/src/subscription_manager/gui/data/glade/registration.glade index 02a9e61..b42f510 100644 --- a/src/subscription_manager/gui/data/glade/registration.glade +++ b/src/subscription_manager/gui/data/glade/registration.glade @@ -1,192 +1,103 @@ - + - - + + + True False - 5 - System Registration - False - True - center-on-parent - True - subscription-manager - dialog - False - - - - + 2 + + + register_widget_main_vbox + + + + True False - 2 - - - + False + False + + True False - end - + 25 + 7 - - gtk-cancel + True - True - True - True - right - - + False + 0 + <b>Registering</b> + True - - cancel_button + + progress_label False False + 8 0 - - Register + True - True - True - True - True - right - - + False + True + 0.25 - - register_button - register_button + + register_progressbar False - False + True 1 - - - registration_dialog_action_area - - - - - False - False - end - 0 - - - - - True - True - False - False - + True False - 25 - 7 - - - True - False - 0 - <b>Registering</b> - True - - - - progress_label - - - - - False - False - 8 - 0 - - - - - True - False - True - - - - register_progressbar - - - - - False - True - 1 - - - - - True - False - 0 - True - - - - register_details_label - - + 0 + True + True + + + register_details_label - - False - True - 2 - - - - - True - False - page 2 - - False + False + True + 2 - - True - True - 1 - - - - register_dialog_main_vbox + + + True + False + Progress Page + + False + - - - - register_dialog - + + True + True + 0 + diff --git a/src/subscription_manager/gui/data/glade/repositories.glade b/src/subscription_manager/gui/data/glade/repositories.glade index e45fc29..944db9a 100644 --- a/src/subscription_manager/gui/data/glade/repositories.glade +++ b/src/subscription_manager/gui/data/glade/repositories.glade @@ -19,7 +19,7 @@ manage_repositories_dialog - + True @@ -240,7 +240,7 @@ remove_all_overrides_button - + False @@ -255,7 +255,7 @@ True True True - + False @@ -275,7 +275,7 @@ close_button - + False diff --git a/src/subscription_manager/gui/data/ui/choose_server.ui b/src/subscription_manager/gui/data/ui/choose_server.ui index 5bd3e6b..8684f07 100644 --- a/src/subscription_manager/gui/data/ui/choose_server.ui +++ b/src/subscription_manager/gui/data/ui/choose_server.ui @@ -114,7 +114,6 @@ 30 False False - server_entry diff --git a/src/subscription_manager/gui/data/ui/credentials.ui b/src/subscription_manager/gui/data/ui/credentials.ui index 219d32a..283a410 100644 --- a/src/subscription_manager/gui/data/ui/credentials.ui +++ b/src/subscription_manager/gui/data/ui/credentials.ui @@ -53,9 +53,11 @@ True False + start True + center False 0 gtk-info @@ -75,11 +77,13 @@ True False - 0 - 0 + start + True + middle 1 2 True + 1 True diff --git a/src/subscription_manager/gui/data/ui/register_dialog.ui b/src/subscription_manager/gui/data/ui/register_dialog.ui new file mode 100644 index 0000000..ed83845 --- /dev/null +++ b/src/subscription_manager/gui/data/ui/register_dialog.ui @@ -0,0 +1,99 @@ + + + + + + False + center + end + System Registration + False + center-on-parent + True + subscription-manager + dialog + False + + + True + False + end + vertical + 2 + + + True + False + end + + + gtk-cancel + True + True + True + True + right + + + cancel_button + + + + + False + False + 0 + + + + + Register + True + True + True + True + True + right + + + register_button + register_button + + + + + False + False + 1 + + + + + registration_dialog_action_area + + + + + False + False + end + 0 + + + + + + + + register_dialog_main_vbox + + + + + + + register_dialog + + + + diff --git a/src/subscription_manager/gui/data/ui/registration.ui b/src/subscription_manager/gui/data/ui/registration.ui index 31baac9..3bf3d1a 100644 --- a/src/subscription_manager/gui/data/ui/registration.ui +++ b/src/subscription_manager/gui/data/ui/registration.ui @@ -2,188 +2,107 @@ - + + True False - 5 - System Registration - False - True - center-on-parent - True - subscription-manager - dialog - False - - - + start + vertical + 2 + + True False - baseline - start - vertical - 2 - - + False + False + + True False - start - end + 25 + vertical + 7 - - gtk-cancel + True - True - True - True - right - + False + <b>Registering</b> + True + 0 - - cancel_button + + progress_label False False + 8 0 - - Register + True - True - True - True - True - right - + False + 0.1 - - register_button - register_button + + register_progressbar False - False + True 1 - - - registration_dialog_action_area - - - - - False - False - 0 - - - - - True - True - start - False - False - + True False - 25 - vertical - 7 - - - True - False - <b>Registering</b> - True - 0 - - - progress_label - - - - - False - False - 8 - 0 - - - - - True - False - - - register_progressbar - - - - - False - False - 1 - - - - - True - False - True - 0 - - - register_details_label - - + True + 0 + + + register_details_label - - False - False - 2 - - False - - - - - True - False - page 2 - - - False + False + True + 2 + + + + True + False + Progress Page + - False - False - 1 + False - - register_dialog_main_vbox + + register_notebook + + False + False + 1 + - - register_dialog + + register_widget_main_vbox diff --git a/src/subscription_manager/gui/firstboot/rhsm_login.py b/src/subscription_manager/gui/firstboot/rhsm_login.py index 7c06f19..3aea9a6 100644 --- a/src/subscription_manager/gui/firstboot/rhsm_login.py +++ b/src/subscription_manager/gui/firstboot/rhsm_login.py @@ -1,14 +1,18 @@ import gettext -import socket import sys import logging _ = lambda x: gettext.ldgettext("rhsm", x) -import gtk -gtk.gdk.threads_init() +from subscription_manager import ga_loader +ga_loader.init_ga() + +from subscription_manager.ga import Gtk as ga_Gtk +from subscription_manager.ga import gtk_compat + +gtk_compat.threads_init() import rhsm @@ -26,20 +30,21 @@ running_as_firstboot() from subscription_manager.injectioninit import init_dep_injection init_dep_injection() -from subscription_manager.injection import PLUGIN_MANAGER, IDENTITY, require + +from subscription_manager import injection as inj from subscription_manager.facts import Facts from subscription_manager.hwprobe import Hardware -from subscription_manager.gui.firstboot_base import RhsmFirstbootModule from subscription_manager.gui import managergui from subscription_manager.gui import registergui -from subscription_manager.gui.utils import handle_gui_exception -from subscription_manager.gui.autobind import \ - ServiceLevelNotSupportedException, NoProductsException, \ - AllProductsCoveredException -from subscription_manager import managerlib +from subscription_manager.gui.utils import format_exception +from subscription_manager.i18n import configure_i18n + +from firstboot import module +from firstboot import constants + +configure_i18n(with_glade=True) -from rhsm.connection import RestlibException from rhsm.utils import remove_scheme sys.path.append("/usr/share/rhn") @@ -50,179 +55,36 @@ try: except ImportError: log.debug("no rhn-client-tools modules could be imported") -MANUALLY_SUBSCRIBE_PAGE = 11 - - -class SelectSLAScreen(registergui.SelectSLAScreen): - """ - override the default SelectSLAScreen to jump to the manual subscribe page. - """ - def _on_get_service_levels_cb(self, result, error=None): - if error is not None: - if isinstance(error[1], ServiceLevelNotSupportedException): - message = _("Unable to auto-attach, server does not support " - "service levels. Please run 'Subscription Manager' " - "to manually attach a subscription.") - self._parent.manual_message = message - self._parent.pre_done(MANUALLY_SUBSCRIBE_PAGE) - elif isinstance(error[1], NoProductsException): - message = _("No installed products on system. No need to " - "update subscriptions at this time.") - self._parent.manual_message = message - self._parent.pre_done(MANUALLY_SUBSCRIBE_PAGE) - elif isinstance(error[1], AllProductsCoveredException): - message = _("All installed products are fully subscribed.") - self._parent.manual_message = message - self._parent.pre_done(MANUALLY_SUBSCRIBE_PAGE) - else: - handle_gui_exception(error, _("Error subscribing"), - self._parent.window) - self._parent.finish_registration(failed=True) - return - - (current_sla, unentitled_products, sla_data_map) = result - - self._parent.current_sla = current_sla - if len(sla_data_map) == 1: - # If system already had a service level, we can hit this point - # when we cannot fix any unentitled products: - if current_sla is not None and \ - not self._can_add_more_subs(current_sla, sla_data_map): - message = _("Unable to attach any additional subscriptions at " - "current service level: %s") % current_sla - self._parent.manual_message = message - self._parent.pre_done(MANUALLY_SUBSCRIBE_PAGE) - return - - self._dry_run_result = sla_data_map.values()[0] - self._parent.pre_done(registergui.CONFIRM_SUBS_PAGE) - elif len(sla_data_map) > 1: - self._sla_data_map = sla_data_map - self.set_model(unentitled_products, sla_data_map) - self._parent.pre_done(registergui.DONT_CHANGE) - else: - message = _("No service levels will cover all installed products. " - "Please run 'Subscription Manager' to manually " - "attach subscriptions.") - self._parent.manual_message = message - self._parent.pre_done(MANUALLY_SUBSCRIBE_PAGE) - - -class PerformRegisterScreen(registergui.PerformRegisterScreen): - - def _on_registration_finished_cb(self, new_account, error=None): - if error is not None: - handle_gui_exception(error, registergui.REGISTER_ERROR, - self._parent.window) - self._parent.finish_registration(failed=True) - return - - try: - managerlib.persist_consumer_cert(new_account) - self._parent.backend.cs.force_cert_check() # Ensure there isn't much wait time - - if self._parent.activation_keys: - self._parent.pre_done(registergui.REFRESH_SUBSCRIPTIONS_PAGE) - elif self._parent.skip_auto_bind: - message = _("You have opted to skip auto-attach.") - self._parent.manual_message = message - self._parent.pre_done(MANUALLY_SUBSCRIBE_PAGE) - else: - self._parent.pre_done(registergui.SELECT_SLA_PAGE) - - # If we get errors related to consumer name on register, - # go back to the credentials screen where we set the - # consumer name. See bz#865954 - except RestlibException, e: - handle_gui_exception(e, registergui.REGISTER_ERROR, - self._parent.window) - if e.code == 404 and self._parent.activation_keys: - self._parent.pre_done(registergui.ACTIVATION_KEY_PAGE) - if e.code == 400: - self._parent.pre_done(registergui.CREDENTIALS_PAGE) - - except Exception, e: - handle_gui_exception(e, registergui.REGISTER_ERROR, - self._parent.window) - self._parent.finish_registration(failed=True) - - def pre(self): - # TODO: this looks like it needs updating now that we run - # firstboot without rhn client tools. - - # Because the RHN client tools check if certs exist and bypass our - # firstboot module if so, we know that if we reach this point and - # identity certs exist, someone must have hit the back button. - # TODO: i'd like this call to be inside the async progress stuff, - # since it does take some time - identity = require(IDENTITY) - if identity.is_valid(): - try: - managerlib.unregister(self._parent.backend.cp_provider.get_consumer_auth_cp(), - self._parent.identity.uuid) - except socket.error, e: - handle_gui_exception(e, e, self._parent.window) - self._parent._registration_finished = False - - return registergui.PerformRegisterScreen.pre(self) - -class ManuallySubscribeScreen(registergui.Screen): - widget_names = registergui.Screen.widget_names + ['title'] - gui_file = "manually_subscribe" - - def __init__(self, parent, backend): - super(ManuallySubscribeScreen, self).__init__(parent, backend) - - self.button_label = _("Finish") - - def apply(self): - return registergui.FINISH - - def pre(self): - if self._parent.manual_message: - self.title.set_label(self._parent.manual_message) - # XXX set message here. - return False - - -class moduleClass(RhsmFirstbootModule, registergui.RegisterScreen): +class moduleClass(module.Module, object): def __init__(self): """ Create a new firstboot Module for the 'register' screen. """ - RhsmFirstbootModule.__init__(self, # Firstboot module title - # Note: translated title needs to be unique across all - # firstboot modules, not just the rhsm ones. See bz #828042 - _("Subscription Management Registration"), - _("Subscription Registration"), - 200.1, 109.10) + super(moduleClass, self).__init__() - backend = managergui.Backend() - self.plugin_manager = require(PLUGIN_MANAGER) - registergui.RegisterScreen.__init__(self, backend, Facts()) + self.mode = constants.MODE_REGULAR + self.title = _("Subscription Management Registration") + self.sidebarTitle = _("Subscription Registration") + self.priority = 200.1 - #insert our new screens - screen = SelectSLAScreen(self, backend) - screen.index = self._screens[registergui.SELECT_SLA_PAGE].index - self._screens[registergui.SELECT_SLA_PAGE] = screen - self.register_notebook.remove_page(screen.index) - self.register_notebook.insert_page(screen.container, - position=screen.index) + # NOTE: all of this is copied form former firstboot_base module + # and may no longer be needed + # set this so subclasses can override behaviour if needed + self._is_compat = False - screen = PerformRegisterScreen(self, backend) - self._screens[registergui.PERFORM_REGISTER_PAGE] = screen - - screen = ManuallySubscribeScreen(self, backend) - self._screens.append(screen) - screen.index = self.register_notebook.append_page(screen.container) + reg_info = registergui.RegisterInfo() + backend = managergui.Backend() + self.plugin_manager = inj.require(inj.PLUGIN_MANAGER) + self.register_widget = registergui.RegisterWidget(backend, Facts(), reg_info) # Will be False if we are on an older RHEL version where # rhn-client-tools already does some things so we don't have to. self.standalone = True distribution = Hardware().get_distribution() log.debug("Distribution: %s" % str(distribution)) + try: dist_version = float(distribution[1]) # We run this for Fedora as well, but all we really care about here @@ -243,7 +105,141 @@ class moduleClass(RhsmFirstbootModule, registergui.RegisterScreen): self.interface = None self.proxies_were_enabled_from_gui = None - self._apply_result = self._RESULT_FAILURE + self._apply_result = constants.RESULT_FAILURE + + self.page_status = constants.RESULT_FAILURE + + def apply(self, interface, testing=False): + """ + 'Next' button has been clicked - try to register with the + provided user credentials and return the appropriate result + value. + """ + self.interface = interface + + self.register_widget.emit('proceed') + + # This is always "fail" until we get to the done screen + return self.page_status + + def createScreen(self): + """ + Create a new instance of gtk.VBox, pulling in child widgets from the + glade file. + """ + self.vbox = ga_Gtk.VBox() + # self.vbox.pack_start(self.get_widget("register_widget"), False, False, 0) + self.vbox.pack_start(self.register_widget.register_widget, False, False, 0) + + self.register_widget.connect('finished', self.on_finished) + self.register_widget.connect('register-error', self.on_register_error) + + # In firstboot, we leverage the RHN setup proxy settings already + # presented to the user, so hide the choose server screen's proxy + # text and button. But, if we are standalone, show our versions. + if not self.standalone and False: + screen = self._screens[registergui.CHOOSE_SERVER_PAGE] + screen.proxy_frame.destroy() + + def focus(self): + """ + Focus the initial UI element on the page, in this case the + login name field. + """ + # FIXME: This is currently broken + # login_text = self.glade.get_widget("account_login") + # login_text.grab_focus() + + def initializeUI(self): + log.debug("initializeUi %s", self) + # Need to make sure that each time the UI is initialized we reset back + # to the main register screen. + + # Note, even if we are standalone firstboot mode (no rhn modules), + # we may still have RHN installed, and possibly configured. + self._read_rhn_proxy_settings() + + self.register_widget.initialize() + + def needsNetwork(self): + """ + This lets firstboot know that networking is required, in order to + talk to hosted UEP. + """ + return True + + def needsReboot(self): + return False + + # TODO: verify this doesnt break anything + def not_renderModule(self, interface): + #ParentClass.renderModule(self, interface) + + # firstboot module class docs state to not override renderModule, + # so this is breaking the law. + # + # This is to set line wrapping on the title label to resize + # correctly with our long titles and their even longer translations + super(moduleClass, self).renderModule(interface) + + # FIXME: likely all of this should be behind a try/except, since it's + # likely to break, and it is just to fix cosmetic issues. + # Walk down widget tree to find the title label + label_container = self.vbox.get_children()[0] + title_label = label_container.get_children()[0] + + # Set the title to wrap and connect to size-allocate to + # properly resize the label so that it takes up the most + # space it can. + title_label.set_line_wrap(True) + title_label.connect('size-allocate', + lambda label, size: label.set_size_request(size.width - 1, -1)) + + def shouldAppear(self): + """ + Indicates to firstboot whether to show this screen. In this case + we want to skip over this screen if there is already an identity + certificate on the machine (most likely laid down in a kickstart). + """ + identity = inj.require(inj.IDENTITY) + return not identity.is_valid() + + def on_register_error(self, obj, msg, exc_list): + self.page_status = constants.RESULT_FAILURE + + # TODO: we can add the register state, error type (error or exc) + if exc_list: + self.handle_register_exception(obj, msg, exc_list) + else: + self.handle_register_error(obj, msg) + return True + + def on_finished(self, obj): + self.finished = True + self.page_status = constants.RESULT_SUCCESS + return False + + def handle_register_error(self, obj, msg): + self.error_dialog(msg) + + def handle_register_exception(self, obj, msg, exc_info): + message = format_exception(exc_info, msg) + self.error_dialog(message) + + def error_dialog(self, text): + dlg = ga_Gtk.MessageDialog(None, 0, ga_Gtk.MessageType.ERROR, + ga_Gtk.ButtonsType.OK, text) + dlg.set_markup(text) + dlg.set_skip_taskbar_hint(True) + dlg.set_skip_pager_hint(True) + dlg.set_position(ga_Gtk.WindowPosition.CENTER) + + def response_handler(obj, response_id): + obj.destroy() + + dlg.connect('response', response_handler) + dlg.set_modal(True) + dlg.show() def _get_initial_screen(self): """ @@ -310,47 +306,6 @@ class moduleClass(RhsmFirstbootModule, registergui.RegisterScreen): self.backend.cp_provider.set_connection_info() - def apply(self, interface, testing=False): - """ - 'Next' button has been clicked - try to register with the - provided user credentials and return the appropriate result - value. - """ - - # on el5 we can't just move to another page, we have to set the next - # page then do an apply. since we've already done our work async, skip - # this time through - if self._skip_apply_for_page_jump: - self._skip_apply_for_page_jump = False - # Reset back to first screen in our module in case the user hits back. - # The firstboot register screen subclass will handle unregistering - # if necessary when it runs again. - self.show() - return self._RESULT_SUCCESS - - self.interface = interface - - # bad proxy settings can cause socket.error or friends here - # see bz #810363 - try: - valid_registration = self.register() - - except socket.error, e: - handle_gui_exception(e, e, self.window) - return self._RESULT_FAILURE - - # run main_iteration till we have no events, like idle - # loop sources, aka, the thread watchers are finished. - while gtk.events_pending(): - gtk.main_iteration() - - if valid_registration: - self._cached_credentials = self._get_credentials_hash() - - # finish_registration/skip_remaining_screens should set - # __apply_result to RESULT_JUMP - return self._apply_result - def close_window(self): """ Overridden from RegisterScreen - we want to bypass the default behavior @@ -365,49 +320,6 @@ class moduleClass(RhsmFirstbootModule, registergui.RegisterScreen): """ pass - def createScreen(self): - """ - Create a new instance of gtk.VBox, pulling in child widgets from the - glade file. - """ - self.vbox = gtk.VBox(spacing=10) - self.register_dialog = self.get_widget("dialog-vbox6") - self.register_dialog.reparent(self.vbox) - - # Get rid of the 'register' and 'cancel' buttons, as we are going to - # use the 'forward' and 'back' buttons provided by the firsboot module - # to drive the same functionality - self._destroy_widget('register_button') - self._destroy_widget('cancel_button') - - # In firstboot, we leverage the RHN setup proxy settings already - # presented to the user, so hide the choose server screen's proxy - # text and button. But, if we are standalone, show our versions. - if not self.standalone: - screen = self._screens[registergui.CHOOSE_SERVER_PAGE] - screen.proxy_frame.destroy() - - def initializeUI(self): - # Need to make sure that each time the UI is initialized we reset back - # to the main register screen. - - # Note, even if we are standalone firstboot mode (no rhn modules), - # we may still have RHN installed, and possibly configured. - self._read_rhn_proxy_settings() - - # NOTE: On EL5 this does not appear to be called when the user - # presses Back, only when they go through the first time. - self.show() - - def focus(self): - """ - Focus the initial UI element on the page, in this case the - login name field. - """ - # FIXME: This is currently broken - # login_text = self.glade.get_widget("account_login") - # login_text.grab_focus() - def _destroy_widget(self, widget_name): """ Destroy a widget by name. @@ -489,6 +401,3 @@ class moduleClass(RhsmFirstbootModule, registergui.RegisterScreen): else: self._apply_result = self._RESULT_SUCCESS return - -# for el5 -childWindow = moduleClass diff --git a/src/subscription_manager/gui/firstboot_base.py b/src/subscription_manager/gui/firstboot_base.py deleted file mode 100644 index 1a7b507..0000000 --- a/src/subscription_manager/gui/firstboot_base.py +++ /dev/null @@ -1,122 +0,0 @@ -# -# Copyright (c) 2012 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# - -import sys - -sys.path.append("/usr/share/rhsm") - -# rhsm_login init the injector before we are loaded -from subscription_manager import injection as inj - -from subscription_manager.i18n import configure_i18n - -configure_i18n(with_glade=True) - -# Number of total RHSM firstboot screens, used to skip past to whatever's -# next in a couple places. -NUM_RHSM_SCREENS = 4 - -try: - _version = "el6" - from firstboot.constants import RESULT_SUCCESS, RESULT_FAILURE, RESULT_JUMP - from firstboot.module import Module -except Exception: - # we must be on el5 - _version = "el5" - from firstboot_module_window import FirstbootModuleWindow - - -if _version == "el5": - ParentClass = FirstbootModuleWindow -else: - ParentClass = Module - - -class RhsmFirstbootModule(ParentClass): - - def __init__(self, title, sidebar_title, priority, compat_priority): - ParentClass.__init__(self) - - if _version == "el6": - # set this so subclasses can override behaviour if needed - self._is_compat = False - self._RESULT_SUCCESS = RESULT_SUCCESS - self._RESULT_FAILURE = RESULT_FAILURE - self._RESULT_JUMP = RESULT_JUMP - else: - self._is_compat = True - self._RESULT_SUCCESS = True - self._RESULT_FAILURE = None - self._RESULT_JUMP = True - - # this value is relative to when you want to load the screen - # so check other modules before setting - self.priority = priority - self.sidebarTitle = sidebar_title - self.title = title - - # el5 values - self.runPriority = compat_priority - self.moduleName = self.sidebarTitle - self.windowTitle = self.moduleName - self.shortMessage = self.title - self.noSidebar = True - - # el5 value to get access to parent object for page jumping - self.needsparent = 1 - - def renderModule(self, interface): - ParentClass.renderModule(self, interface) - label_container = self.vbox.get_children()[0] - title_label = label_container.get_children()[0] - - # Set the title to wrap and connect to size-allocate to - # properly resize the label so that it takes up the most - # space it can. - title_label.set_line_wrap(True) - title_label.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1)) - - def needsNetwork(self): - """ - This lets firstboot know that networking is required, in order to - talk to hosted UEP. - """ - return True - - def shouldAppear(self): - """ - Indicates to firstboot whether to show this screen. In this case - we want to skip over this screen if there is already an identity - certificate on the machine (most likely laid down in a kickstart). - """ - identity = inj.require(inj.IDENTITY) - return not identity.is_valid() - - ############################## - # el5 compat functions follow - ############################## - - def launch(self, doDebug=None): - self.createScreen() - return self.vbox, self.icon, self.windowTitle - - def passInParent(self, parent): - self.compat_parent = parent - - self.register_button = parent.nextButton - self.cancel_button = parent.backButton - - def grabFocus(self): - self.initializeUI() diff --git a/src/subscription_manager/gui/managergui.py b/src/subscription_manager/gui/managergui.py index 2aae80e..cb2cec9 100644 --- a/src/subscription_manager/gui/managergui.py +++ b/src/subscription_manager/gui/managergui.py @@ -158,6 +158,7 @@ class MainWindow(widgets.SubmanBaseWidget): log.debug("Server Versions: %s " % get_server_versions(self.backend.cp_provider.get_consumer_auth_cp())) settings = self.main_window.get_settings() + # prevent gtk from trying to save a list of recently used files, which # as root, causes gtk warning: # "Attempting to set the permissions of `/root/.local/share/recently-used.xbel' @@ -171,8 +172,7 @@ class MainWindow(widgets.SubmanBaseWidget): self.system_facts_dialog = factsgui.SystemFactsDialog(self.facts) - self.registration_dialog = registergui.RegisterScreen(self.backend, self.facts, - self._get_window()) + self.registration_dialog = registergui.RegisterDialog(self.backend, self.facts) self.preferences_dialog = PreferencesDialog(self.backend, self._get_window()) @@ -360,7 +360,6 @@ class MainWindow(widgets.SubmanBaseWidget): self.redeem_menu_item.set_sensitive(False) def _register_item_clicked(self, widget): - self.log.debug("_register_item_clicked widget=%s", widget) self.registration_dialog.initialize() self.registration_dialog.show() @@ -427,9 +426,8 @@ class MainWindow(widgets.SubmanBaseWidget): self.import_sub_dialog.show() def _update_certificates_button_clicked(self, widget): - autobind_wizard = registergui.AutobindWizard(self.backend, - self.facts, - self._get_window()) + autobind_wizard = registergui.AutobindWizardDialog(self.backend, + self.facts) autobind_wizard.initialize() autobind_wizard.show() diff --git a/src/subscription_manager/gui/registergui.py b/src/subscription_manager/gui/registergui.py index 123c783..3ba0fff 100644 --- a/src/subscription_manager/gui/registergui.py +++ b/src/subscription_manager/gui/registergui.py @@ -23,7 +23,6 @@ import socket import sys import threading - from subscription_manager.ga import Gtk as ga_Gtk from subscription_manager.ga import GObject as ga_GObject @@ -41,21 +40,16 @@ from subscription_manager import managerlib from subscription_manager.utils import is_valid_server_info, MissingCaCertException, \ parse_server_info, restart_virt_who -from subscription_manager.gui.utils import handle_gui_exception, show_error_window +from subscription_manager.gui.utils import format_exception, show_error_window from subscription_manager.gui.autobind import DryRunResult, \ ServiceLevelNotSupportedException, AllProductsCoveredException, \ NoProductsException -from subscription_manager.gui.messageWindow import InfoDialog, OkDialog from subscription_manager.jsonwrapper import PoolWrapper _ = lambda x: gettext.ldgettext("rhsm", x) gettext.textdomain("rhsm") -#Gtk.glade.bindtextdomain("rhsm") - -#Gtk.glade.textdomain("rhsm") - log = logging.getLogger('rhsm-app.' + __name__) CFG = config.initConfig() @@ -74,6 +68,7 @@ def set_state(new_state): global state state = new_state +ERROR_SCREEN = -3 DONT_CHANGE = -2 PROGRESS_PAGE = -1 CHOOSE_SERVER_PAGE = 0 @@ -122,388 +117,545 @@ def reset_resolver(): pass -class RegistrationBox(widgets.SubmanBaseWidget): - gui_file = "registration_box" - - -class RegisterScreen(widgets.SubmanBaseWidget): - """ - Registration Widget Screen - - RegisterScreen is the parent widget of registration screens, and - also the base class of the firstboot rhsm_module. - - RegisterScreen has a list of Screen subclasses. +class RegisterInfo(ga_GObject.GObject): - Screen subclasses can be Screen, NonGuiScreen, or GuiScreen - classes. Only GuiScreen classes are user visible. NonGuiScreen - and subclasses are used for state transitions (a between screens - check for pools, for example) + username = ga_GObject.property(type=str, default='') + password = ga_GObject.property(type=str, default='') - The rhsmModule.apply() runs RegisterScreen.register(). - RegisterScreen.register runs the current screens .apply(). + # server info + hostname = ga_GObject.property(type=str, default='') + port = ga_GObject.property(type=str, default='') + prefix = ga_GObject.property(type=str, default='') - A Screen.apply() will return the index of the next screen that - should be invoked (which may be a different screen, the same screen, - or the special numbers for DONT_CHANGE and FINISH.) + # rhsm model info + environment = ga_GObject.property(type=str, default='') + consumername = ga_GObject.property(type=str, default='') + owner_key = ga_GObject.property(type=ga_GObject.TYPE_PYOBJECT, default=None) + activation_keys = ga_GObject.property(type=ga_GObject.TYPE_PYOBJECT, default=None) - In firstboot, calling the firstboot modules .apply() results in calling - rhsm_module.moduleClass.apply() which calls the first Screen.apply() - (also self._current_screen). + # split into AttachInfo or FindSlaInfo? + current_sla = ga_GObject.property(type=ga_GObject.TYPE_PYOBJECT, default=None) + dry_run_result = ga_GObject.property(type=ga_GObject.TYPE_PYOBJECT, default=None) - After the Screen.apply(), RegisterScreen.register checks it's return - for DONT_CHANGE or FINISH. + # registergui states + skip_auto_bind = ga_GObject.property(type=bool, default=False) + details_label_txt = ga_GObject.property(type=str, default='') + register_state = ga_GObject.property(type=int, default=REGISTERING) - If the apply returns a screen index, then the Screen.post() is called. - The return value is ignored. - - The RegisterScreen.register calls RegisterScreen.run_pre() on the - screen index that the current_screen .apply() returned(i.e. the - next screen). - - run_pre() checks that it's arg (the result of above apply(), what - is still currently the next screen) is not DONT_CHANGE/FINISH. + # TODO: make a gobj prop as well, with custom set/get, so we can be notified + @property + def identity(self): + id = require(IDENTITY) + return id - If not, then it calls self._set_screen() which updates - self._current_screen to point to the next screen. + def __init__(self): + ga_GObject.GObject.__init__(self) - run_pre() then calls the new current_screens's .pre() - .register() - next_screen = current_screen.apply() - current_screen.post() - RegisterScreen.run_pre(next_screen) - RegisterScreen._set_screen(next_screen) - current_screen = next_screen +class RegisterWidget(widgets.SubmanBaseWidget): + gui_file = "registration" + widget_names = ['register_widget', 'register_notebook', + 'register_details_label', 'register_progressbar', + 'progress_label'] + + __gsignals__ = {'proceed': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'register-warning': (ga_GObject.SignalFlags.RUN_FIRST, + None, (ga_GObject.TYPE_PYOBJECT,)), + 'register-error': (ga_GObject.SignalFlags.RUN_FIRST, + None, (ga_GObject.TYPE_PYOBJECT, + ga_GObject.TYPE_PYOBJECT)), + 'finished': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'attach-finished': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'register-finished': (ga_GObject.SignalFlags.RUN_FIRST, + None, [])} + + initial_screen = CHOOSE_SERVER_PAGE + + register_button_label = ga_GObject.property(type=str, default=_('Register')) + # TODO: a prop equilivent to initial-setups 'completed' and 'status' props + + def __init__(self, backend, facts, reg_info=None, parent_window=None): + super(RegisterWidget, self).__init__() - Then if current_screen is a gui screen, the visible - gui will update with the new widgets. + self.backend = backend + self.identity = require(IDENTITY) + self.facts = facts - The new current_screen has its pre() method invoked. pre() - methods may return an async representing that a request - has been called and a callback registered. If that's the case, - then RegisterScreen._set_screen() sets the current screen - to a progress screen. + self.async = AsyncBackend(self.backend) - The return value of RegisterScreen.run_pre() is ignored, and - RegisterScreen.register() returns False. + # TODO: should be able to get rid of this soon, the + # only thing that uses it is the NetworkConfigDialog in + # chooseServerScreen and we can replace that with an embedded + # widget + self.parent_window = parent_window + + self.info = reg_info or RegisterInfo() + + self.progress_timer = None + + # TODO: move these handlers into their own class + self.info.connect("notify::username", + self._on_username_password_change) + self.info.connect("notify::password", + self._on_username_password_change) + self.info.connect("notify::hostname", + self._on_connection_info_change) + self.info.connect("notify::port", + self._on_connection_info_change) + self.info.connect("notify::prefix", + self._on_connection_info_change) + self.info.connect("notify::activation-keys", + self._on_activation_keys_change) + self.info.connect('notify::details-label-txt', + self._on_details_label_txt_change) + self.info.connect('notify::register-state', + self._on_register_state_change) + + # expect this to be driving from the parent dialog + self.connect('proceed', + self._on_proceed) + + # FIXME: change glade name + self.details_label = self.register_details_label + + # To update the 'next/register' button in the parent dialog based on the new page + self.register_notebook.connect('switch-page', + self._on_switch_page) - This returns to rhsm_login.apply(), where valid_registration is - set to the return value. valid_registration=True indicates a - succesful registration + screen_classes = [ChooseServerScreen, ActivationKeyScreen, + CredentialsScreen, OrganizationScreen, + EnvironmentScreen, PerformRegisterScreen, + SelectSLAScreen, ConfirmSubscriptionsScreen, + PerformSubscribeScreen, RefreshSubscriptionsScreen, + InfoScreen, DoneScreen] + self._screens = [] - If valid_registration=True, we are basically done with registeration. - But rhsm_login can't return from apply() yet, since that could - potential lead to firstboot ending if it's the last or only module. + # TODO: current_screen as a gobject property + for idx, screen_class in enumerate(screen_classes): + self.add_screen(idx, screen_class) - gtk main loop iterations are run, mostly to let any threads finish - up and any idle loop thread watchers to dry up. + self._current_screen = None - The return value of rhsm_login.apply() at this point is actualy - the _apply_result instance variable. Register Screens() are expected - to set this by calling their finish_registration() method. For - subscription-manager-gui that means RegisterScreen.finish_registration, - usually access as a Screens() self._parent.finish_registration. + # Track screens we "show" so we can choose a reasonable error screen + self.screen_history = [] - For firstboot screens, self._parent will be rhsm_module.moduleClass - (also a subclass of RegisterScreen). + # FIXME: modify property instead + self.callbacks = [] - rhsm_module.finish_registration() will check the "failed" boolean, - and either return to a Screen() (CredentialsPage, atm). Or if - failed=True, it will also call RegisterScreen.finish_registration(), - that closes the gui window. + self.register_widget.show() - The UI flow is a result of the order of RegisterScreen._screens, - and the screen indexes returned by Screen.apply(). + def add_screen(self, idx, screen_class): + screen = screen_class(reg_info=self.info, + async_backend=self.async, + facts=self.facts, + parent_window=self.parent_window) - But, between the Screen activity call also change the flow, most - notably the results of any async calls and callbacks invoked from - the screens .pre() + # add the index of the screen in self._screens to the class itself + screen.screens_index = idx - A common case is the async callbacks error handling calling - self._parent.finish_registration(failed=True) + # connect handlers to various screen signals. The screens are + # Gobjects not gtk widgets, so they can't propagate normally. + screen.connect('move-to-screen', self._on_move_to_screen) + screen.connect('stay-on-screen', self._on_stay_on_screen) + screen.connect('register-error', self._on_screen_register_error) + screen.connect('register-finished', + self._on_screen_register_finished) + screen.connect('attach-finished', + self._on_screen_attach_finished) - The async callback can also call RegisterScreen.pre_done() to send the - UI to a different screen. RHSM api call results that indicate multiple - choices for a sub would send flow to a chooseSub GuiScreen vs a - NonGuiScreen for attaching a sub, for example. + self._screens.append(screen) - RegisterScreen.run_pre schedules async jobs, they get queued, and - wait for their callbacks. The callbacks then can use pre_done() - to finish the tasks the run_pre started. Typicaly the UI will - see the Progress screens in the meantime. + # Some screens have no gui controls, they just use the + # PROGRESS_PAGE, so the indexes to the register_notebook's pages and + # to self._screen differ + if screen.needs_gui: + # screen.index is the screens index in self.register_notebook + screen.index = self.register_notebook.append_page(screen.container, + tab_label=None) - If going to screen requires an async task, run_pre starts it by - calling the new screens pre(), setting that screen to current (_set_screen), - and then setting the GuiScreen to the progress screens. Screen - transitions that don't need async tasks just return nothing from - their pre() and go to the next screen in the order in self._screens. + def initialize(self): + self.set_initial_screen() + self.clear_screens() + # TODO: move this so it's only running when a progress bar is "active" + self.register_widget.show_all() + + def start_progress_timer(self): + if not self.progress_timer: + self.progress_timer = ga_GObject.timeout_add(100, self._timeout_callback) + + def stop_progress_timer(self): + if self.progress_timer: + ga_GObject.source_remove(self.progress_timer) + self.progress_timer = None + + def set_initial_screen(self): + self._set_screen(self.initial_screen) + self._current_screen = self.initial_screen + self.screen_history = [self.initial_screen] + + # switch-page should be after the current screen is reset + def _on_switch_page(self, notebook, page, page_num): + current_screen = self._screens[self._current_screen] + # NonGuiScreens have a None button label + if current_screen.button_label: + self.set_property('register-button-label', current_screen.button_label) + + # HMMM: If the connect/backend/async, and the auth info is composited into + # the same GObject, these could be class closure handlers + def _on_username_password_change(self, *args): + self.async.set_user_pass(self.info.username, self.info.password) + + def _on_connection_info_change(self, *args): + self.async.update() + + def _on_activation_keys_change(self, obj, param): + activation_keys = obj.get_property('activation-keys') + + # Unset backend from attempting to use basic auth + if activation_keys: + self.async.cp_provider.set_user_pass() + self.async.update() + + def _on_details_label_txt_change(self, obj, value): + """Update the label under the progress bar on progress page.""" + self.details_label.set_label("%s" % + obj.get_property('details-label-txt')) + + def _on_register_state_change(self, obj, value): + """Handler for the signal indicating we moved from registering to attaching. + + (the 'register-state' property changed), so update the + related label on progress page.""" + state = obj.get_property('register-state') + if state == REGISTERING: + self.progress_label.set_markup(_("Registering")) + elif state == SUBSCRIBING: + self.progress_label.set_markup(_("Attaching")) - Note the the flow of firstboot through multiple modules is driven - by the return value of rhsm_login.apply(). firstboot itself maintains - a list of modules and a an ordered list of them. True goes to the - next screen, False stays. Except for RHEL6, where it is the opposite. + def do_register_error(self, msg, exc_info): + """Class closure signal handler for 'register-error'. - As of RHEL7.0+, none of that matters much, since rhsm_login is the - only module in firstboot. + This should always get run first, when this widget emits a + 'register-error', then it's emitted to other handlers (set up by + any parent dialogs for example).""" + # return to the last gui screen we showed. - """ + self._set_screen(self.screen_history[-1]) - widget_names = ['register_dialog', 'register_notebook', - 'register_progressbar', 'register_details_label', - 'cancel_button', 'register_button', 'progress_label', - 'dialog_vbox6'] - gui_file = "registration" - __gtype_name__ = 'RegisterScreen' + def _on_screen_register_error(self, obj, msg, exc_info): + """Handler for 'register-error' signals emitted from the Screens. - def __init__(self, backend, facts=None, parent=None, callbacks=None): - """ - Callbacks will be executed when registration status changes. - """ - super(RegisterScreen, self).__init__() + Then emit one ourselves. Now emit a new signal for parent widget and + self.do_register_error() to handle""" - self.backend = backend - self.identity = require(IDENTITY) - self.facts = facts - self.parent = parent - self.callbacks = callbacks or [] + self.emit('register-error', msg, exc_info) - self.async = AsyncBackend(self.backend) + # do_register_error handles it for this widget, so stop emission + return False - callbacks = {"on_register_cancel_button_clicked": self.cancel, - "on_register_button_clicked": self._on_register_button_clicked, - "hide": self.cancel, - "on_register_dialog_delete_event": self._delete_event} - self.connect_signals(callbacks) + def _on_stay_on_screen(self, current_screen): + """A 'stay-on-screen' handler, for errors that need to be corrected before proceeding. - self.window = self.register_dialog - self.register_dialog.set_transient_for(self.parent) + A screen has been shown, and error handling emits this to indicate the + widget should not move to a different screen. - screen_classes = [ChooseServerScreen, ActivationKeyScreen, - CredentialsScreen, OrganizationScreen, - EnvironmentScreen, PerformRegisterScreen, - SelectSLAScreen, ConfirmSubscriptionsScreen, - PerformSubscribeScreen, RefreshSubscriptionsScreen, - InfoScreen, DoneScreen] - self._screens = [] - for screen_class in screen_classes: - screen = screen_class(self, self.backend) - self._screens.append(screen) - if screen.needs_gui: - screen.index = self.register_notebook.append_page( - screen.container, tab_label=None) - - self._current_screen = CHOOSE_SERVER_PAGE - self._error_screen = DONT_CHANGE - - # values that will be set by the screens - self.username = None - self.consumername = None - self.activation_keys = None - self.owner_key = None - self.environment = None - self.current_sla = None - self.dry_run_result = None - self.skip_auto_bind = False - - # XXX needed by firstboot - self.password = None + This also represents screens that allow the user to potentially correct + an error, so we track the history of these screens so errors can go to + a useful screen.""" + self.screen_history.append(current_screen.screens_index) + self._set_screen(self._current_screen) - # FIXME: a 'done' signal maybe? - # initial_setup needs to be able to make this empty - self.close_window_callback = self._close_window_callback + # TODO: replace most of the gui flow logic in the Screen subclasses with + # some state machine that drives them, possibly driving via signals + # indicating each state - def initialize(self): - # Ensure that we start on the first page and that - # all widgets are cleared. - self._set_initial_screen() + def _on_move_to_screen(self, current_screen, next_screen_id): + """Handler for the 'move-to-screen' signal, indicating a jump to another screen. - self._set_navigation_sensitive(True) - self._clear_registration_widgets() - self.timer = ga_GObject.timeout_add(100, self._timeout_callback) + This can be used to send the UI to any other screen, including the next screen. + For example, to skip SLA selection if there is only one SLA.""" + self.change_screen(next_screen_id) - def show(self): - # initial-setup module skips this, since it results in a - # new top level window that isn't reparented to the initial-setup - # screen. - self.register_dialog.show() + def change_screen(self, next_screen_id): + """Move to the next screen and call the next screens .pre(). - def _set_initial_screen(self): - target = self._get_initial_screen() - self._set_screen(target) + If next_screen.pre() indicates it is async (by returning True)and is spinning + off a thread and we should wait for a callback, then move the screen to the + PROGRESS_PAGE. The callback passed to AsyncBackend in pre() is then responsible + for sending the user to the right screen via 'move-to-screen' signal.""" + next_screen = self._screens[next_screen_id] - def _get_initial_screen(self): - return CHOOSE_SERVER_PAGE + self._set_screen(next_screen_id) - # for subman gui, we don't need to switch screens on error - # but for firstboot, we will go back to the info screen if - # we have it. - @property - def error_screen(self): - return self._error_screen - - def goto_error_screen(self): - self._set_navigation_sensitive(True) - self._set_screen(self.error_screen) - - # FIXME: This exists because standalone gui needs to update the nav - # buttons in it's own top level window, while firstboot needs to - # update the buttons in the main firstboot window. Firstboot version - # has additional logic for rhel5/rhel6 differences. - # FIXME: just split this into a registerWidget and a registerDialog - def _set_navigation_sensitive(self, sensitive): - # We could unsens the cancel button here, but since we use it as - # a 'do over' button that sends the dialog back to the start, just - # leave it enabled, to avoid leaving un unsens after an async error - # handler. - self.register_button.set_sensitive(sensitive) + async = next_screen.pre() + if async: + self.start_progress_timer() + next_screen.emit('move-to-screen', PROGRESS_PAGE) def _set_screen(self, screen): + """Handle both updating self._current_screen, and updating register_notebook.""" + next_notebook_page = screen + if screen > PROGRESS_PAGE: self._current_screen = screen + # FIXME: If we just add ProgressPage in the screen order, we + # shouldn't need this bookeeping if self._screens[screen].needs_gui: - self._set_register_label(screen) - self.register_notebook.set_current_page(self._screens[screen].index) + next_notebook_page = self._screens[screen].index else: - self.register_notebook.set_current_page(screen + 1) + # TODO: replace with a generator + next_notebook_page = screen + 1 + + # set_current_page changes the gui, and also results in the + # 'switch-page' attribute of the gtk notebook being emitted, + # indicating the gui has switched to that page. + self.register_notebook.set_current_page(next_notebook_page) + + # FIXME: figure out to determine we are on first screen, then this + # could just be 'move-to-screen', next screen + # Go to the next screen/state + def _on_proceed(self, obj): + self.apply_current_screen() + + def apply_current_screen(self): + """Extract any info from the widgets and call the screens apply().""" + self._screens[self._current_screen].apply() + + def _on_screen_register_finished(self, obj): + """Handler for 'register-finished' signal, indicating register is finished. + + The 'register-finished' signal indicates that we are finished with + registration (either completly, or because it's not needed, etc). + RegisterWidget then emits it's own 'register-finished' for any parent + dialogs to handle. Note: 'register-finished' only means the registration + steps are finished, and not neccasarily that the gui should be close. + It may need to auto attach, etc. The 'finished' signal indicates register + and attach are finished, while 'register-finished' is just the first part.""" + + self.emit('register-finished') + + # We are done if there is auto bind is being skipped ("Manually attach + # to subscriptions" is clicked in the gui) + if self.info.get_property('skip-auto-bind'): + self.emit('finished') + + def _on_screen_attach_finished(self, obj): + """Handler for 'attach-finished' signal from our Screens. + + One of our Screens has indicated that subscription attachment is done. + Note: This doesn't neccasarily indicate success, just that the gui has + done all the attaching it can. RegisterWidget emits it's own + 'attach-finished' for parent widgets to handle. Again, note that + attach-finished is not the same as 'finished', even though at the moment, + 'finished' does immediately follow 'attach-finished'""" + + self.emit('attach-finished') + + # If attach is finished, we are done. + # let RegisterWidget's self.do_finished() handle any self specific + # shutdown (like detaching self.timer) first. + self.emit('finished') + + def do_finished(self): + """Class closure signal handler for the 'finished' signal. + + Ran first before the any other signal handlers attach to 'finished'""" + if self.progress_timer: + ga_GObject.source_remove(self.progress_timer) + + # Switch to the 'done' screen before telling other signal handlers we + # are done. This way, parent widgets like initial-setup that don't just + # close the window have something to display. + self.done() - if get_state() == REGISTERING: - # aka, if this is firstboot - if not isinstance(self.register_dialog, ga_Gtk.VBox): - self.register_dialog.set_title(_("System Registration")) - self.progress_label.set_markup(_("Registering")) - elif get_state() == SUBSCRIBING: - if not isinstance(self.register_dialog, ga_Gtk.VBox): - self.register_dialog.set_title(_("Subscription Attachment")) - self.progress_label.set_markup(_("Attaching")) - - def _set_register_label(self, screen): - button_label = self._screens[screen].button_label - self.register_button.set_label(button_label) + def done(self): + self.change_screen(DONE_PAGE) - def _delete_event(self, event, data=None): - return self.close_window() + def clear_screens(self): + for screen in self._screens: + screen.clear() - def cancel(self, button): - self.close_window() + def _timeout_callback(self): + """Callback used to drive the progress bar 'pulse'.""" + self.register_progressbar.pulse() + # return true to keep it pulsing + return True - # callback needs the extra arg, so just a wrapper here - def _on_register_button_clicked(self, button): - self.register() - def register(self): +class RegisterDialog(widgets.SubmanBaseWidget): - result = self._screens[self._current_screen].apply() + widget_names = ['register_dialog', 'register_dialog_main_vbox', + 'register_details_label', + 'cancel_button', 'register_button', 'progress_label', + 'dialog_vbox6'] - if result == FINISH: - self.finish_registration() - return True - elif result == DONT_CHANGE: - return False + gui_file = "register_dialog" + __gtype_name__ = 'RegisterDialog' - self._screens[self._current_screen].post() + def __init__(self, backend, facts=None, callbacks=None): + """ + Callbacks will be executed when registration status changes. + """ + super(RegisterDialog, self).__init__() - self._run_pre(result) - return False + # dialog + callbacks = {"on_register_cancel_button_clicked": self.cancel, + "on_register_button_clicked": self._on_register_button_clicked, + "hide": self.cancel, + "on_register_dialog_delete_event": self.cancel} + self.connect_signals(callbacks) - def _run_pre(self, screen): - # XXX move this into the button handling somehow? - if screen == FINISH: - self.finish_registration() - return + self.reg_info = RegisterInfo() + # FIXME: Need better error handling in general, but it's kind of + # annoying to have to pass the top level widget all over the place + self.register_widget = RegisterWidget(backend, facts, + reg_info=self.reg_info, + parent_window=self.register_dialog) - self._set_screen(screen) - async = self._screens[self._current_screen].pre() - if async: - self._set_navigation_sensitive(False) - self._set_screen(PROGRESS_PAGE) - self._set_register_details_label( - self._screens[self._current_screen].pre_message) + # Ensure that we start on the first page and that + # all widgets are cleared. + self.register_widget.initialize() - def _timeout_callback(self): - self.register_progressbar.pulse() - # return true to keep it pulsing - return True + self.register_dialog_main_vbox.pack_start(self.register_widget.register_widget, + True, True, 0) - def finish_registration(self, failed=False): - # failed is used by the firstboot subclasses to decide if they should - # advance the screen or not. - # XXX it would be cool here to do some async spinning while the - # main window gui refreshes itself + self.register_button.connect('clicked', self._on_register_button_clicked) + self.cancel_button.connect('clicked', self.cancel) - if failed: - self.goto_error_screen() - return + # initial-setup will likely handle these itself + self.register_widget.connect('finished', self.cancel) + self.register_widget.connect('register-error', self.on_register_error) - # FIXME: subman-gui needs this but initial-setup doesnt - self.close_window_callback() + # update window title on register state changes + self.register_widget.info.connect('notify::register-state', + self._on_register_state_change) - self.emit_consumer_signal() + # update the 'next/register button on page change' + self.register_widget.connect('notify::register-button-label', + self._on_register_button_label_change) - ga_GObject.source_remove(self.timer) + self.window = self.register_dialog - def emit_consumer_signal(self): - for method in self.callbacks: - method() + # FIXME: needed by firstboot + self.password = None - def done(self): - self._set_screen(DONE_PAGE) + def initialize(self): + self.register_widget.clear_screens() + # self.register_widget.initialize() - def close_window(self): - if self.close_window_callback: - self.close_window_callback() + def show(self): + # initial-setup module skips this, since it results in a + # new top level window that isn't reparented to the initial-setup + # screen. + self.register_dialog.show() - def _close_window_callback(self): - set_state(REGISTERING) + def cancel(self, button): self.register_dialog.hide() return True - def _set_register_details_label(self, details): - self.register_details_label.set_label("%s" % details) + def on_register_error(self, obj, msg, exc_list): + # TODO: we can add the register state, error type (error or exc) + if exc_list: + self.handle_register_exception(obj, msg, exc_list) + else: + self.handle_register_error(obj, msg) + return True - def _clear_registration_widgets(self): - for screen in self._screens: - screen.clear() + def handle_register_error(self, obj, msg): + log.error("registration error: %s", msg) + self.error_dialog(obj, msg) - def pre_done(self, next_screen): - self._set_navigation_sensitive(True) - if next_screen == DONT_CHANGE: - self._set_screen(self._current_screen) - else: - self._screens[self._current_screen].post() - self._run_pre(next_screen) + # RegisterWidget.do_register_error() will take care of changing screens + + def handle_register_exception(self, obj, msg, exc_info): + # format_exception ends up logging the exception as well + message = format_exception(exc_info, msg) + self.error_dialog(obj, message) + def error_dialog(self, obj, msg): + show_error_window(msg) -class AutobindWizard(RegisterScreen): + def _on_register_button_clicked(self, button): + self.register_widget.emit('proceed') - def __init__(self, backend, facts, parent): - super(AutobindWizard, self).__init__(backend, facts, parent) + def _on_register_state_change(self, obj, value): + state = obj.get_property('register-state') + if state == REGISTERING: + self.register_dialog.set_title(_("System Registration")) + elif state == SUBSCRIBING: + self.register_dialog.set_title(_("Subscription Attachment")) - def show(self): - super(AutobindWizard, self).show() - self._run_pre(SELECT_SLA_PAGE) + def _on_register_button_label_change(self, obj, value): + register_label = obj.get_property('register-button-label') + # FIXME: button_label can be None for NonGuiScreens. Seems like + # + if register_label: + self.register_button.set_label(register_label) - def _get_initial_screen(self): - return SELECT_SLA_PAGE +class AutobindWizardDialog(RegisterDialog): + __gtype_name__ = "AutobindWizard" + + initial_screen = SELECT_SLA_PAGE + + def __init__(self, backend, facts): + super(AutobindWizardDialog, self).__init__(backend, facts) + + def show(self): + super(AutobindWizardDialog, self).show() + self.register_widget.change_screen(SELECT_SLA_PAGE) + +# TODO: Screen could be a container widget, that has the rest of the gui as +# a child. That way, we could add the Screen class to the +# register_notebook directly, and follow up to the parent the normal +# way. Then we could stop passing 'parent' around. And RegisterInfo +# could be on the parent register_notebook. I think the various GtkDialogs +# for error handling (handle_gui_exception, etc) would also find it by +# default. class Screen(widgets.SubmanBaseWidget): widget_names = ['container'] gui_file = None - - def __init__(self, parent, backend): + screen_enum = None + + # TODO: replace page int with class enum + __gsignals__ = {'stay-on-screen': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'register-finished': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'attach-finished': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'register-error': (ga_GObject.SignalFlags.RUN_FIRST, + None, (ga_GObject.TYPE_PYOBJECT, + ga_GObject.TYPE_PYOBJECT)), + 'move-to-screen': (ga_GObject.SignalFlags.RUN_FIRST, + None, (int,))} + + def __init__(self, reg_info, async_backend, facts, parent_window): super(Screen, self).__init__() self.pre_message = "" self.button_label = _("Register") self.needs_gui = True self.index = -1 - self._parent = parent - self._backend = backend + # REMOVE self._error_screen = self.index + + self.parent_window = parent_window + self.info = reg_info + self.async = async_backend + self.facts = facts + + def stay(self): + self.emit('stay-on-screen') def pre(self): return False + # do whatever the screen indicates, and emit any signals indicating where + # to move to next. apply() should not return anything. def apply(self): pass @@ -514,19 +666,45 @@ class Screen(widgets.SubmanBaseWidget): pass -class NoGuiScreen(object): +class NoGuiScreen(ga_GObject.GObject): + screen_enum = None + + __gsignals__ = {'identity-updated': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'move-to-screen': (ga_GObject.SignalFlags.RUN_FIRST, + None, (int,)), + 'stay-on-screen': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'register-finished': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'attach-finished': (ga_GObject.SignalFlags.RUN_FIRST, + None, []), + 'register-error': (ga_GObject.SignalFlags.RUN_FIRST, + None, (ga_GObject.TYPE_PYOBJECT, + ga_GObject.TYPE_PYOBJECT)), + 'certs-updated': (ga_GObject.SignalFlags.RUN_FIRST, + None, [])} + + def __init__(self, reg_info, async_backend, facts, parent_window): + ga_GObject.GObject.__init__(self) + + self.parent_window = parent_window + self.info = reg_info + self.async = async_backend + self.facts = facts - def __init__(self, parent, backend): - self._parent = parent - self._backend = backend self.button_label = None self.needs_gui = False + self.pre_message = "Default Pre Message" + + # FIXME: a do_register_error could be used for logging? + # Otherwise it's up to the parent dialog to do the logging. def pre(self): return True def apply(self): - return 1 + self.emit('move-to-screen') def post(self): pass @@ -536,82 +714,99 @@ class NoGuiScreen(object): class PerformRegisterScreen(NoGuiScreen): + screen_enum = PERFORM_REGISTER_PAGE - def __init__(self, parent, backend): - super(PerformRegisterScreen, self).__init__(parent, backend) - self.pre_message = _("Registering your system") + def __init__(self, reg_info, async_backend, facts, parent_window): + super(PerformRegisterScreen, self).__init__(reg_info, async_backend, facts, parent_window) def _on_registration_finished_cb(self, new_account, error=None): if error is not None: - handle_gui_exception(error, REGISTER_ERROR, self._parent.parent) - self._parent.finish_registration(failed=True) + self.emit('register-error', + REGISTER_ERROR, + error) + # TODO: register state return try: managerlib.persist_consumer_cert(new_account) - self._parent.backend.cs.force_cert_check() # Ensure there isn't much wait time - - if self._parent.activation_keys: - self._parent.pre_done(REFRESH_SUBSCRIPTIONS_PAGE) - elif self._parent.skip_auto_bind: - self._parent.pre_done(FINISH) - else: - self._parent.pre_done(SELECT_SLA_PAGE) except Exception, e: - handle_gui_exception(e, REGISTER_ERROR, self._parent.parent) - self._parent.finish_registration(failed=True) + # hint: register error, back to creds? + self.emit('register-error', REGISTER_ERROR, e) + return + + # trigger a id cert reload + self.emit('identity-updated') + + # Force all the cert dir backends to update, but mostly + # force the identity cert monitor to run, which will + # also update Backend. It also blocks until the new + # identity is reloaded, so we don't start the selectSLA + # screen before it. + self.async.backend.cs.force_cert_check() + + # Done with the registration stuff, now on to attach + self.emit('register-finished') + + if self.info.get_property('activation-keys'): + self.emit('move-to-screen', REFRESH_SUBSCRIPTIONS_PAGE) + return + elif self.info.get_property('skip-auto-bind'): + return + else: + self.emit('move-to-screen', SELECT_SLA_PAGE) + return def pre(self): log.info("Registering to owner: %s environment: %s" % - (self._parent.owner_key, self._parent.environment)) + (self.info.get_property('owner-key'), + self.info.get_property('environment'))) - self._parent.async.register_consumer(self._parent.consumername, - self._parent.facts, - self._parent.owner_key, - self._parent.environment, - self._parent.activation_keys, - self._on_registration_finished_cb) + self.async.register_consumer(self.info.get_property('consumername'), + self.facts, + self.info.get_property('owner-key'), + self.info.get_property('environment'), + self.info.get_property('activation-keys'), + self._on_registration_finished_cb) return True class PerformSubscribeScreen(NoGuiScreen): + screen_enum = PERFORM_SUBSCRIBE_PAGE - def __init__(self, parent, backend): - super(PerformSubscribeScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(PerformSubscribeScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.pre_message = _("Attaching subscriptions") def _on_subscribing_finished_cb(self, unused, error=None): if error is not None: - handle_gui_exception(error, _("Error subscribing: %s"), - self._parent.parent) - self._parent.finish_registration(failed=True) + message = _("Error subscribing: %s") + self.emit('register-error', message, error) return - self._parent.pre_done(FINISH) - self._parent.backend.cs.force_cert_check() + self.emit('certs-updated') + self.emit('attach-finished') def pre(self): - self._parent.async.subscribe(self._parent.identity.uuid, - self._parent.current_sla, - self._parent.dry_run_result, - self._on_subscribing_finished_cb) + self.info.set_property('details-label-txt', self.pre_message) + self.async.subscribe(self.info.identity.uuid, + self.info.get_property('current-sla'), + self.info.get_property('dry-run-result'), + self._on_subscribing_finished_cb) return True class ConfirmSubscriptionsScreen(Screen): """ Confirm Subscriptions GUI Window """ - + screen_enum = CONFIRM_SUBS_PAGE widget_names = Screen.widget_names + ['subs_treeview', 'back_button', 'sla_label'] gui_file = "confirmsubs" - def __init__(self, parent, backend): - - super(ConfirmSubscriptionsScreen, self).__init__(parent, - backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(ConfirmSubscriptionsScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.button_label = _("Attach") self.store = ga_Gtk.ListStore(str, bool, str) @@ -636,18 +831,22 @@ class ConfirmSubscriptionsScreen(Screen): return column def apply(self): - return PERFORM_SUBSCRIBE_PAGE + self.emit('move-to-screen', PERFORM_SUBSCRIBE_PAGE) def set_model(self): - self._dry_run_result = self._parent.dry_run_result + dry_run_result = self.info.get_property('dry-run-result') # Make sure that the store is cleared each time # the data is loaded into the screen. self.store.clear() - self.sla_label.set_markup("" + self._dry_run_result.service_level + + + if not dry_run_result: + return + + self.sla_label.set_markup("" + dry_run_result.service_level + "") - for pool_quantity in self._dry_run_result.json: + for pool_quantity in dry_run_result.json: self.store.append([pool_quantity['pool']['productName'], PoolWrapper(pool_quantity['pool']).is_virt_only(), str(pool_quantity['quantity'])]) @@ -662,19 +861,18 @@ class SelectSLAScreen(Screen): An wizard screen that displays the available SLAs that are provided by the installed products. """ + screen_enum = SELECT_SLA_PAGE widget_names = Screen.widget_names + ['product_list_label', 'sla_radio_container', 'owner_treeview'] gui_file = "selectsla" - def __init__(self, parent, backend): - super(SelectSLAScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(SelectSLAScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.pre_message = _("Finding suitable service levels") self.button_label = _("Next") - self._dry_run_result = None - def set_model(self, unentitled_prod_certs, sla_data_map): self.product_list_label.set_text( self._format_prods(unentitled_prod_certs)) @@ -684,7 +882,9 @@ class SelectSLAScreen(Screen): # of the screen. for sla in reversed(sla_data_map.keys()): radio = ga_Gtk.RadioButton(group=group, label=sla) - radio.connect("toggled", self._radio_clicked, sla) + radio.connect("toggled", + self._radio_clicked, + (sla, sla_data_map)) self.sla_radio_container.pack_start(radio, expand=False, fill=False, padding=0) radio.show() @@ -694,19 +894,19 @@ class SelectSLAScreen(Screen): group.set_active(True) def apply(self): - return CONFIRM_SUBS_PAGE - - def post(self): - self._parent.dry_run_result = self._dry_run_result + self.emit('move-to-screen', CONFIRM_SUBS_PAGE) def clear(self): child_widgets = self.sla_radio_container.get_children() for child in child_widgets: self.sla_radio_container.remove(child) - def _radio_clicked(self, button, service_level): + def _radio_clicked(self, button, data): + sla, sla_data_map = data + if button.get_active(): - self._dry_run_result = self._sla_data_map[service_level] + self.info.set_property('dry-run-result', + sla_data_map[sla]) def _format_prods(self, prod_certs): prod_str = "" @@ -718,71 +918,89 @@ class SelectSLAScreen(Screen): return prod_str # so much for service level simplifying things + # FIXME: this could be split into 'on_get_all_service_levels_cb' and + # and 'on_get_service_levels_cb' def _on_get_service_levels_cb(self, result, error=None): - # The parent for the dialogs is set to the grandparent window - # (which is MainWindow) because the parent window is closed - # by finish_registration() after displaying the dialogs. See - # BZ #855762. if error is not None: if isinstance(error[1], ServiceLevelNotSupportedException): - OkDialog(_("Unable to auto-attach, server does not support service levels."), - parent=self._parent.parent) + msg = _("Unable to auto-attach, server does not support service levels.") + self.emit('register-error', msg, None) + # HMM: if we make the ok a register-error as well, we may get + # wacky ordering if the register-error is followed immed by a + # register-finished? + self.emit('attach-finished') + return elif isinstance(error[1], NoProductsException): - InfoDialog(_("No installed products on system. No need to attach subscriptions at this time."), - parent=self._parent.parent) + msg = _("No installed products on system. No need to attach subscriptions at this time.") + self.emit('register-error', msg, None) + self.emit('attach-finished') + return elif isinstance(error[1], AllProductsCoveredException): - InfoDialog(_("All installed products are covered by valid entitlements. No need to attach subscriptions at this time."), - parent=self._parent.parent) + msg = _("All installed products are covered by valid entitlements. " + "No need to attach subscriptions at this time.") + self.emit('register-error', msg, None) + self.emit('attach-finished') + return elif isinstance(error[1], GoneException): - InfoDialog(_("Consumer has been deleted."), parent=self._parent.parent) + # FIXME: shoudl we log here about deleted consumer or + # did we do that when we created GoneException? + msg = _("Consumer has been deleted.") + self.emit('register-error', msg, None) + return + # TODO: where we should go from here? else: log.exception(error) - handle_gui_exception(error, _("Error subscribing"), - self._parent.parent) - self._parent.finish_registration(failed=True) - return + self.emit('register-error', + _("Error subscribing"), + error) + return (current_sla, unentitled_products, sla_data_map) = result - self._parent.current_sla = current_sla + self.info.set_property('current-sla', current_sla) + if len(sla_data_map) == 1: # If system already had a service level, we can hit this point # when we cannot fix any unentitled products: if current_sla is not None and \ not self._can_add_more_subs(current_sla, sla_data_map): - handle_gui_exception(None, - _("No available subscriptions at " - "the current service level: %s. " - "Please use the \"All Available " - "Subscriptions\" tab to manually " - "attach subscriptions.") % current_sla, - self._parent.parent) - self._parent.finish_registration(failed=True) + msg = _("No available subscriptions at " + "the current service level: %s. " + "Please use the \"All Available " + "Subscriptions\" tab to manually " + "attach subscriptions.") % current_sla + # TODO: add 'attach' state + self.emit('register-error', msg, None) + self.emit('attach-finished') return - self._dry_run_result = sla_data_map.values()[0] - self._parent.pre_done(CONFIRM_SUBS_PAGE) + self.info.set_property('dry-run-result', + sla_data_map.values()[0]) + self.emit('move-to-screen', CONFIRM_SUBS_PAGE) + return elif len(sla_data_map) > 1: - self._sla_data_map = sla_data_map self.set_model(unentitled_products, sla_data_map) - self._parent.pre_done(DONT_CHANGE) + self.stay() + return else: log.info("No suitable service levels found.") - handle_gui_exception(None, - _("No service level will cover all " - "installed products. Please manually " - "subscribe using multiple service levels " - "via the \"All Available Subscriptions\" " - "tab or purchase additional subscriptions."), - parent=self._parent.parent) - self._parent.finish_registration(failed=True) + msg = _("No service level will cover all " + "installed products. Please manually " + "subscribe using multiple service levels " + "via the \"All Available Subscriptions\" " + "tab or purchase additional subscriptions.") + # TODO: add 'registering/attaching' state info + self.emit('register-error', msg, None) + self.emit('attach-finished') def pre(self): - set_state(SUBSCRIBING) - self._parent.identity.reload() - self._parent.async.find_service_levels(self._parent.identity.uuid, - self._parent.facts, - self._on_get_service_levels_cb) + self.info.set_property('details-label-txt', self.pre_message) + self.info.set_property('register-state', SUBSCRIBING) + self.info.identity.reload() + + self.async.find_service_levels(self.info.identity.uuid, + self.facts, + self._on_get_service_levels_cb) return True def _can_add_more_subs(self, current_sla, sla_data_map): @@ -800,8 +1018,8 @@ class EnvironmentScreen(Screen): widget_names = Screen.widget_names + ['environment_treeview'] gui_file = "environment" - def __init__(self, parent, backend): - super(EnvironmentScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(EnvironmentScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.pre_message = _("Fetching list of possible environments") renderer = ga_Gtk.CellRendererText() @@ -812,35 +1030,39 @@ class EnvironmentScreen(Screen): def _on_get_environment_list_cb(self, result_tuple, error=None): environments = result_tuple if error is not None: - handle_gui_exception(error, REGISTER_ERROR, self._parent.parent) - self._parent.finish_registration(failed=True) + # TODO: registering state + self.emit('register-error', REGISTER_ERROR, error) return if not environments: - self._environment = None - self._parent.pre_done(PERFORM_REGISTER_PAGE) + self.set_environment(None) + self.emit('move-to-screen', PERFORM_REGISTER_PAGE) return envs = [(env['id'], env['name']) for env in environments] if len(envs) == 1: - self._environment = envs[0][0] - self._parent.pre_done(PERFORM_REGISTER_PAGE) + self.set_environement(envs[0][0]) + self.emit('move-to-screen', PERFORM_REGISTER_PAGE) + return + else: self.set_model(envs) - self._parent.pre_done(DONT_CHANGE) + self.stay() + return def pre(self): - self._parent.async.get_environment_list(self._parent.owner_key, - self._on_get_environment_list_cb) + self.info.set_property('details-label-txt', self.pre_message) + self.async.get_environment_list(self.info.get_property('owner-key'), + self._on_get_environment_list_cb) return True def apply(self): model, tree_iter = self.environment_treeview.get_selection().get_selected() - self._environment = model.get_value(tree_iter, 0) - return PERFORM_REGISTER_PAGE + self.set_environment(model.get_value(tree_iter, 0)) + self.emit('move-to-screen', PERFORM_REGISTER_PAGE) - def post(self): - self._parent.environment = self._environment + def set_environment(self, environment): + self.info.set_property('environment', environment) def set_model(self, envs): environment_model = ga_Gtk.ListStore(str, str) @@ -857,8 +1079,8 @@ class OrganizationScreen(Screen): widget_names = Screen.widget_names + ['owner_treeview'] gui_file = "organization" - def __init__(self, parent, backend): - super(OrganizationScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(OrganizationScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.pre_message = _("Fetching list of possible organizations") @@ -867,13 +1089,9 @@ class OrganizationScreen(Screen): self.owner_treeview.set_property("headers-visible", False) self.owner_treeview.append_column(column) - self._owner_key = None - def _on_get_owner_list_cb(self, owners, error=None): if error is not None: - handle_gui_exception(error, REGISTER_ERROR, - self._parent.window) - self._parent.finish_registration(failed=True) + self.emit('register-error', REGISTER_ERROR, error) return owners = [(owner['key'], owner['displayName']) for owner in owners] @@ -881,32 +1099,35 @@ class OrganizationScreen(Screen): owners = sorted(owners, key=lambda item: item[1]) if len(owners) == 0: - handle_gui_exception(None, - _("User %s is not able to register with any orgs.") % - (self._parent.username), - self._parent.parent) - self._parent.finish_registration(failed=True) + msg = _("User %s is not able to register with any orgs.") % \ + self.info.get_property('username') + self.emit('register-error', msg, None) return if len(owners) == 1: - self._owner_key = owners[0][0] - self._parent.pre_done(ENVIRONMENT_SELECT_PAGE) + owner_key = owners[0][0] + self.info.set_property('owner-key', owner_key) + # only one org, use it and skip the org selection screen + self.emit('move-to-screen', ENVIRONMENT_SELECT_PAGE) + return + else: self.set_model(owners) - self._parent.pre_done(DONT_CHANGE) + self.stay() + return def pre(self): - self._parent.async.get_owner_list(self._parent.username, - self._on_get_owner_list_cb) + self.info.set_property('details-label-txt', self.pre_message) + self.async.get_owner_list(self.info.get_property('username'), + self._on_get_owner_list_cb) return True def apply(self): + # check for selection exists model, tree_iter = self.owner_treeview.get_selection().get_selected() - self._owner_key = model.get_value(tree_iter, 0) - return ENVIRONMENT_SELECT_PAGE - - def post(self): - self._parent.owner_key = self._owner_key + owner_key = model.get_value(tree_iter, 0) + self.info.set_property('owner-key', owner_key) + self.emit('move-to-screen', ENVIRONMENT_SELECT_PAGE) def set_model(self, owners): owner_model = ga_Gtk.ListStore(str, str) @@ -927,11 +1148,10 @@ class CredentialsScreen(Screen): gui_file = "credentials" - def __init__(self, parent, backend): - super(CredentialsScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(CredentialsScreen, self).__init__(reg_info, async_backend, facts, parent_window) self._initialize_consumer_name() - self.registration_tip_label.set_label("%s" % get_branding().GUI_FORGOT_LOGIN_TIP) @@ -944,7 +1164,11 @@ class CredentialsScreen(Screen): def _validate_consumername(self, consumername): if not consumername: - show_error_window(_("You must enter a system name."), self._parent.window) + # TODO: register state to signal + self.emit('register-error', + _("You must enter a system name."), + None) + self.consumer_name.grab_focus() return False return True @@ -952,42 +1176,46 @@ class CredentialsScreen(Screen): def _validate_account(self): # validate / check user name if self.account_login.get_text().strip() == "": - show_error_window(_("You must enter a login."), self._parent.window) + self.emit('register-error', + _("You must enter a login."), + None) + self.account_login.grab_focus() return False if self.account_password.get_text().strip() == "": - show_error_window(_("You must enter a password."), self._parent.window) + self.emit('register-error', + _("You must enter a password."), + None) + self.account_password.grab_focus() return False return True def pre(self): + self.info.set_property('details-label-txt', self.pre_message) self.account_login.grab_focus() return False def apply(self): - self._username = self.account_login.get_text().strip() - self._password = self.account_password.get_text().strip() - self._consumername = self.consumer_name.get_text() - self._skip_auto_bind = self.skip_auto_bind.get_active() + self.stay() + username = self.account_login.get_text().strip() + password = self.account_password.get_text().strip() + consumername = self.consumer_name.get_text() + skip_auto_bind = self.skip_auto_bind.get_active() - if not self._validate_consumername(self._consumername): - return DONT_CHANGE + if not self._validate_consumername(consumername): + return if not self._validate_account(): - return DONT_CHANGE + return - self._backend.cp_provider.set_user_pass(self._username, self._password) + self.info.set_property('username', username) + self.info.set_property('password', password) + self.info.set_property('skip-auto-bind', skip_auto_bind) + self.info.set_property('consumername', consumername) - return OWNER_SELECT_PAGE - - def post(self): - self._parent.username = self._username - self._parent.password = self._password - self._parent.consumername = self._consumername - self._parent.skip_auto_bind = self._skip_auto_bind - self._parent.activation_keys = None + self.emit('move-to-screen', OWNER_SELECT_PAGE) def clear(self): self.account_login.set_text("") @@ -1005,8 +1233,8 @@ class ActivationKeyScreen(Screen): ] gui_file = "activation_key" - def __init__(self, parent, backend): - super(ActivationKeyScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(ActivationKeyScreen, self).__init__(reg_info, async_backend, facts, parent_window) self._initialize_consumer_name() def _initialize_consumer_name(self): @@ -1014,21 +1242,26 @@ class ActivationKeyScreen(Screen): self.consumer_entry.set_text(socket.gethostname()) def apply(self): - self._activation_keys = self._split_activation_keys( + self.stay() + activation_keys = self._split_activation_keys( self.activation_key_entry.get_text().strip()) - self._owner_key = self.organization_entry.get_text().strip() - self._consumername = self.consumer_entry.get_text().strip() + owner_key = self.organization_entry.get_text().strip() + consumername = self.consumer_entry.get_text().strip() + + if not self._validate_owner_key(owner_key): + return - if not self._validate_owner_key(self._owner_key): - return DONT_CHANGE + if not self._validate_activation_keys(activation_keys): + return - if not self._validate_activation_keys(self._activation_keys): - return DONT_CHANGE + if not self._validate_consumername(consumername): + return - if not self._validate_consumername(self._consumername): - return DONT_CHANGE + self.info.set_property('consumername', consumername) + self.info.set_property('owner-key', owner_key) + self.info.set_property('activation-keys', activation_keys) - return PERFORM_REGISTER_PAGE + self.emit('move-to-screen', PERFORM_REGISTER_PAGE) def _split_activation_keys(self, entry): keys = re.split(',\s*|\s+', entry) @@ -1036,56 +1269,59 @@ class ActivationKeyScreen(Screen): def _validate_owner_key(self, owner_key): if not owner_key: - show_error_window(_("You must enter an organization."), self._parent.window) + self.emit('register-error', + _("You must enter an organization."), + None) + self.organization_entry.grab_focus() return False return True def _validate_activation_keys(self, activation_keys): if not activation_keys: - show_error_window(_("You must enter an activation key."), self._parent.window) + self.emit('register-error', + _("You must enter an activation key."), + None) + self.activation_key_entry.grab_focus() return False return True def _validate_consumername(self, consumername): if not consumername: - show_error_window(_("You must enter a system name."), self._parent.window) + self.emit('register-error', + _("You must enter a system name."), + None) + self.consumer_entry.grab_focus() return False return True def pre(self): + self.info.set_property('details-label-txt', self.pre_message) self.organization_entry.grab_focus() return False - def post(self): - self._parent.activation_keys = self._activation_keys - self._parent.owner_key = self._owner_key - self._parent.consumername = self._consumername - # Environments aren't used with activation keys so clear any - # cached value. - self._parent.environment = None - self._backend.cp_provider.set_user_pass() - class RefreshSubscriptionsScreen(NoGuiScreen): - def __init__(self, parent, backend): - super(RefreshSubscriptionsScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(RefreshSubscriptionsScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.pre_message = _("Attaching subscriptions") def _on_refresh_cb(self, error=None): if error is not None: - handle_gui_exception(error, _("Error subscribing: %s"), - self._parent.parent) - self._parent.finish_registration(failed=True) + self.emit('register-error', + _("Error subscribing: %s"), + error) + # TODO: register state return - self._parent.pre_done(FINISH) + self.emit('attach-finished') def pre(self): - self._parent.async.refresh(self._on_refresh_cb) + self.info.set_property('details-label-txt', self.pre_message) + self.async.refresh(self._on_refresh_cb) return True @@ -1095,16 +1331,14 @@ class ChooseServerScreen(Screen): 'activation_key_checkbox'] gui_file = "choose_server" - def __init__(self, parent, backend): - - super(ChooseServerScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(ChooseServerScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.button_label = _("Next") callbacks = { "on_default_button_clicked": self._on_default_button_clicked, "on_proxy_button_clicked": self._on_proxy_button_clicked, - "on_server_entry_changed": self._on_server_entry_changed, } self.connect_signals(callbacks) @@ -1121,29 +1355,8 @@ class ChooseServerScreen(Screen): # bump the resolver as well. self.reset_resolver() - self.network_config_dialog.set_parent_window(self._parent.window) self.network_config_dialog.show() - def _on_server_entry_changed(self, widget): - """ - Disable the activation key checkbox if the user is registering - to hosted. - """ - server = self.server_entry.get_text() - try: - (hostname, port, prefix) = parse_server_info(server) - if re.search('subscription\.rhn\.(.*\.)*redhat\.com', hostname): - sensitive = False - self.activation_key_checkbox.set_active(False) - else: - sensitive = True - self.activation_key_checkbox.set_sensitive(sensitive) - except ServerUrlParseError: - # This may seem like it should be False, but we don't want - # the checkbox blinking on and off as the user types a value - # that is first unparseable and then later parseable. - self.activation_key_checkbox.set_sensitive(True) - def reset_resolver(self): try: reset_resolver() @@ -1151,7 +1364,11 @@ class ChooseServerScreen(Screen): log.warn("Error from reset_resolver: %s", e) def apply(self): + self.stay() server = self.server_entry.get_text() + + # TODO: test the values before saving, then update + # self.info and cfg if it works try: (hostname, port, prefix) = parse_server_info(server) CFG.set('server', 'hostname', hostname) @@ -1162,27 +1379,38 @@ class ChooseServerScreen(Screen): try: if not is_valid_server_info(hostname, port, prefix): - show_error_window(_("Unable to reach the server at %s:%s%s") % - (hostname, port, prefix), - self._parent.window) - return self._parent.error_screen + self.emit('register-error', + _("Unable to reach the server at %s:%s%s") % + (hostname, port, prefix), + None) + return except MissingCaCertException: - show_error_window(_("CA certificate for subscription service has not been installed."), - self._parent.window) - return self._parent.error_screen + self.emit('register-error', + _("CA certificate for subscription service has not been installed."), + None) + return except ServerUrlParseError: - show_error_window(_("Please provide a hostname with optional port and/or prefix: hostname[:port][/prefix]"), - self._parent.window) - return self._parent.error_screen + self.emit('register-error', + _("Please provide a hostname with optional port and/or prefix: " + "hostname[:port][/prefix]"), + None) + return log.debug("Writing server data to rhsm.conf") CFG.save() - self._backend.update() + + self.info.set_property('hostname', hostname) + self.info.set_property('port', port) + self.info.set_property('prefix', prefix) + if self.activation_key_checkbox.get_active(): - return ACTIVATION_KEY_PAGE + self.emit('move-to-screen', ACTIVATION_KEY_PAGE) + return + else: - return CREDENTIALS_PAGE + self.emit('move-to-screen', CREDENTIALS_PAGE) + return def clear(self): # Load the current server values from rhsm.conf: @@ -1205,6 +1433,28 @@ class AsyncBackend(object): self.plugin_manager = require(PLUGIN_MANAGER) self.queue = Queue.Queue() + def update(self): + self.backend.update() + + def set_user_pass(self, username, password): + self.backend.cp_provider.set_user_pass(username, password) + self.backend.update() + + def _watch_thread(self): + """ + glib idle method to watch for thread completion. + runs the provided callback method in the main thread. + """ + try: + (callback, retval, error) = self.queue.get(block=False) + if error: + callback(retval, error=error) + else: + callback(retval) + return False + except Queue.Empty: + return True + def _get_owner_list(self, username, callback): """ method run in the worker thread. @@ -1243,14 +1493,20 @@ class AsyncBackend(object): try: installed_mgr = require(INSTALLED_PRODUCTS_MANAGER) + # TODO: not sure why we pass in a facts.Facts, and call it's + # get_facts() three times. The two bracketing plugin calls + # are meant to be able to enhance/tweak facts self.plugin_manager.run("pre_register_consumer", name=name, - facts=facts.get_facts()) - retval = self.backend.cp_provider.get_basic_auth_cp().registerConsumer(name=name, - facts=facts.get_facts(), owner=owner, environment=env, - keys=activation_keys, - installed_products=installed_mgr.format_for_server()) + facts=facts.get_facts()) + + cp = self.backend.cp_provider.get_basic_auth_cp() + retval = cp.registerConsumer(name=name, facts=facts.get_facts(), + owner=owner, environment=env, + keys=activation_keys, + installed_products=installed_mgr.format_for_server()) + self.plugin_manager.run("post_register_consumer", consumer=retval, - facts=facts.get_facts()) + facts=facts.get_facts()) require(IDENTITY).reload() # Facts and installed products went out with the registration @@ -1288,6 +1544,7 @@ class AsyncBackend(object): try: if not current_sla: log.debug("Saving selected service level for this system.") + self.backend.cp_provider.get_consumer_auth_cp().updateConsumer(uuid, service_level=dry_run_result.service_level) @@ -1301,10 +1558,12 @@ class AsyncBackend(object): pool_id=pool_id, quantity=quantity) ents = self.backend.cp_provider.get_consumer_auth_cp().bindByEntitlementPool(uuid, pool_id, quantity) self.plugin_manager.run("post_subscribe", consumer_uuid=uuid, entitlement_data=ents) + # FIXME: this should be a different asyncBackend task managerlib.fetch_certificates(self.backend.certlib) except Exception: # Going to try to update certificates just in case we errored out # mid-way through a bunch of binds: + # FIXME: emit update-ent-certs signal try: managerlib.fetch_certificates(self.backend.certlib) except Exception, cert_update_ex: @@ -1316,6 +1575,16 @@ class AsyncBackend(object): # This guy is really ugly to run in a thread, can we run it # in the main thread with just the network stuff threaded? + + # get_consumer + # get_service_level_list + # update_consumer + # action_client + # update_installed_products + # update_facts + # update_other_action_client_stuff + # for sla in available_slas: + # get_dry_run_bind for sla def _find_suitable_service_levels(self, consumer_uuid, facts): # FIXME: @@ -1356,7 +1625,14 @@ class AsyncBackend(object): action_client.update() for sla in available_slas: + + # TODO: what kind of madness would happen if we did a couple of + # these in parallel in seperate threads? dry_run_json = self.backend.cp_provider.get_consumer_auth_cp().dryRunBind(consumer_uuid, sla) + + # FIXME: are we modifying cert_sorter (self.backend.cs) state here? + # FIXME: it's only to get the unentitled products list, can pass + # that in dry_run = DryRunResult(sla, dry_run_json, self.backend.cs) # If we have a current SLA for this system, we do not need @@ -1364,6 +1640,8 @@ class AsyncBackend(object): # this wizard: if current_sla or dry_run.covers_required_products(): suitable_slas[sla] = dry_run + + # why do we call cert_sorter stuff in the return? return (current_sla, self.backend.cs.unentitled_products.values(), suitable_slas) def _find_service_levels(self, consumer_uuid, facts, callback): @@ -1383,21 +1661,6 @@ class AsyncBackend(object): except Exception: self.queue.put((callback, None, sys.exc_info())) - def _watch_thread(self): - """ - glib idle method to watch for thread completion. - runs the provided callback method in the main thread. - """ - try: - (callback, retval, error) = self.queue.get(block=False) - if error: - callback(retval, error=error) - else: - callback(retval) - return False - except Queue.Empty: - return True - def get_owner_list(self, username, callback): ga_GObject.idle_add(self._watch_thread) threading.Thread(target=self._get_owner_list, @@ -1440,11 +1703,12 @@ class AsyncBackend(object): args=(callback,)).start() +# TODO: make this a more informative 'summary' page. class DoneScreen(Screen): gui_file = "done_box" - def __init__(self, parent, backend): - super(DoneScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(DoneScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.pre_message = "We are done." @@ -1463,32 +1727,30 @@ class InfoScreen(Screen): ] gui_file = "registration_info" - def __init__(self, parent, backend): - super(InfoScreen, self).__init__(parent, backend) + def __init__(self, reg_info, async_backend, facts, parent_window): + super(InfoScreen, self).__init__(reg_info, async_backend, facts, parent_window) self.button_label = _("Next") - callbacks = { - "on_why_register_button_clicked": - self._on_why_register_button_clicked, - "on_back_to_reg_button_clicked": - self._on_back_to_reg_button_clicked - } + callbacks = {"on_why_register_button_clicked": + self._on_why_register_button_clicked, + "on_back_to_reg_button_clicked": + self._on_back_to_reg_button_clicked + } - # FIXME: self.conntect_signals to wrap self.gui.connect_signals self.connect_signals(callbacks) def pre(self): return False def apply(self): + self.stay() if self.register_radio.get_active(): log.debug("Proceeding with registration.") - return CHOOSE_SERVER_PAGE + self.emit('move-to-screen', CHOOSE_SERVER_PAGE) + return + else: log.debug("Skipping registration.") - return FINISH - - def post(self): - pass + self.emit('move-to-screen', FINISH) def _on_why_register_button_clicked(self, button): self.why_register_dialog.show() diff --git a/src/subscription_manager/gui/utils.py b/src/subscription_manager/gui/utils.py index 6c77ff7..9b8a52e 100644 --- a/src/subscription_manager/gui/utils.py +++ b/src/subscription_manager/gui/utils.py @@ -103,6 +103,52 @@ def handle_gui_exception(e, msg, parent, format_msg=True, log_msg=None): show_error_window(msg, parent=parent) +def format_mapped_message(e, msg, mapped_message, format_msg=True): + message = None + if isinstance(e, connection.RestlibException): + # If this exception's code is in the 200 range (such as 202 ACCEPTED) + # we're going to ignore the message we were given and just display + # the message from the server as an info dialog. (not an error) + if 200 < int(e.code) < 300: + message = linkify(mapped_message) + else: + try: + if format_msg: + message = msg % linkify(mapped_message) + else: + message = linkify(mapped_message) + except Exception: + message = msg + return message + + +def format_interpolated_message(e, msg, mapped_message, format_msg=True): + message = None + #catch-all, try to interpolate and if it doesn't work out, just display the message + try: + interpolated_str = msg % e + message = interpolated_str + except Exception: + message = msg + return message + + +def format_exception(e, msg, format_msg=True, log_msg=None): + if isinstance(e, tuple): + log.error(log_msg, exc_info=e) + # Get the class instance of the exception + e = e[1] + message = None + exception_mapper = ExceptionMapper() + mapped_message = exception_mapper.get_message(e) + if mapped_message: + message = format_mapped_message(e, msg, mapped_message, format_msg=format_msg) + else: + message = format_interpolated_message(e, msg, mapped_message, format_msg=format_msg) + + return message + + def show_error_window(message, parent=None): messageWindow.ErrorDialog(messageWindow.wrap_text(message), parent) diff --git a/src/subscription_manager/gui/widgets.py b/src/subscription_manager/gui/widgets.py index 57d400d..c1615a2 100644 --- a/src/subscription_manager/gui/widgets.py +++ b/src/subscription_manager/gui/widgets.py @@ -96,7 +96,7 @@ class BuilderFileBasedWidget(FileBasedGui): builder_based_widget = cls() builder_based_widget.gui_file = builder_file - #print "ga", ga.GTK_BUILDER_FILES_DIR + builder_based_widget.builder.set_translation_domain('rhsm') builder_based_widget.gui_file_suffix = ga_gtk_compat.GTK_BUILDER_FILES_SUFFIX builder_based_widget.file_dir = ga_gtk_compat.GTK_BUILDER_FILES_DIR @@ -126,11 +126,12 @@ class BuilderFileBasedWidget(FileBasedGui): # FIXME: not actually a widget, just an object that has a widget -class SubmanBaseWidget(object): +class SubmanBaseWidget(ga_GObject.GObject): widget_names = [] gui_file = None def __init__(self): + ga_GObject.GObject.__init__(self) self.gui = self._gui_factory() self.pull_widgets(self.gui, self.widget_names) self.log = logging.getLogger('rhsm-app.' + __name__ + diff --git a/src/subscription_manager/managercli.py b/src/subscription_manager/managercli.py index 1b50b67..3933c30 100644 --- a/src/subscription_manager/managercli.py +++ b/src/subscription_manager/managercli.py @@ -305,7 +305,7 @@ class CliCommand(AbstractCLICommand): self.parser.add_option("--serverurl", dest="server_url", default=None, help=_("server URL in the form of https://hostname:port/prefix")) self.parser.add_option("--insecure", action="store_true", - default=False, help=_("do not check the server SSL certificate against available certificate authorities")) + default=False, help=_("do not check the entitlement server SSL certificate against available certificate authorities")) def _add_proxy_options(self): """ Add proxy options that apply to sub-commands that require network connections. """ @@ -534,8 +534,6 @@ class UserPassCommand(CliCommand): @property def username(self): if not self._username: - print _("Registering to: %s:%s%s") % \ - (cfg.get("server", "hostname"), cfg.get("server", "port"), cfg.get("server", "prefix")) (self._username, self._password) = self._get_username_and_password( self.options.username, self.options.password) return self._username @@ -1041,6 +1039,8 @@ class RegisterCommand(UserPassCommand): # Proceed with new registration: try: if not self.options.activation_keys: + print _("Registering to: %s:%s%s") % \ + (cfg.get("server", "hostname"), cfg.get("server", "port"), cfg.get("server", "prefix")) self.cp_provider.set_user_pass(self.username, self.password) admin_cp = self.cp_provider.get_basic_auth_cp() else: diff --git a/subscription-manager.spec b/subscription-manager.spec index 1d4b9a4..e6d4b1d 100644 --- a/subscription-manager.spec +++ b/subscription-manager.spec @@ -49,7 +49,7 @@ Name: subscription-manager Version: 1.15.9 -Release: 7%{?dist} +Release: 8%{?dist} Summary: Tools and libraries for subscription and repository management Group: System Environment/Base License: GPLv2 @@ -542,6 +542,13 @@ fi %endif %changelog +* Wed Sep 02 2015 Chris Rog 1.15.9-8 +- 884288: Better registergui for initial-setup (alikins@redhat.com) +- Fix 'make gladelint' errors in repositories.glade (alikins@redhat.com) +- 1254349: Move registering to message (vrjain@redhat.com) +- 1257460: Set text domain on Gtk.Builder widgets (alikins@redhat.com) +- 1207247: Insecure parameter needs more explanation (wpoteat@redhat.com) + * Wed Aug 19 2015 Chris Rog 1.15.9-7 - search-disabled-repos: ignore failed temporarily enabled repos (vmukhame@redhat.com) diff --git a/test/stubs.py b/test/stubs.py index 5704cca..36c8888 100644 --- a/test/stubs.py +++ b/test/stubs.py @@ -468,15 +468,9 @@ class StubBackend(object): self.overrides = None self.certlib = None - def monitor_certs(self, callback): + def on_cert_check_timer(self): pass - def monitor_identity(self, callback): - pass - - def create_admin_uep(self, username, password): - return StubUEP(username, password) - def update(self): pass diff --git a/test/test_managergui.py b/test/test_managergui.py index af78f9e..150a2f0 100644 --- a/test/test_managergui.py +++ b/test/test_managergui.py @@ -12,9 +12,6 @@ from subscription_manager.injection import provide, \ class TestManagerGuiMainWindow(SubManFixture): def test_main_window(self): - managergui.Backend = stubs.StubBackend - managergui.Facts = stubs.StubFacts() - provide(PROD_DIR, stubs.StubProductDirectory([])) provide(PRODUCT_DATE_RANGE_CALCULATOR, mock.Mock()) @@ -25,8 +22,11 @@ class TestManagerGuiMainWindow(SubManFixture): class TestRegisterScreen(unittest.TestCase): def test_register_screen(self): - registergui.RegisterScreen(stubs.StubBackend()) + registergui.RegisterDialog(stubs.StubBackend()) def test_register_screen_register(self): - rs = registergui.RegisterScreen(stubs.StubBackend()) - rs.register() + rd = registergui.RegisterDialog(stubs.StubBackend()) + #rs.initialize() + rd.show() + rd.register_dialog.hide() + #rs.cancel() diff --git a/test/test_migration.py b/test/test_migration.py index 7286b3e..019b2f9 100644 --- a/test/test_migration.py +++ b/test/test_migration.py @@ -26,8 +26,14 @@ from fixture import Capture, SubManFixture, temp_file from optparse import OptionParser from textwrap import dedent +from nose import SkipTest + from subscription_manager import injection as inj -from subscription_manager.migrate import migrate +try: + from subscription_manager.migrate import migrate +except ImportError: + raise SkipTest("Couldn't import rhn modules for migration tests") + from subscription_manager.certdirectory import ProductDirectory diff --git a/test/test_registrationgui.py b/test/test_registrationgui.py index 9610c33..9374325 100644 --- a/test/test_registrationgui.py +++ b/test/test_registrationgui.py @@ -4,14 +4,17 @@ from mock import Mock from fixture import SubManFixture from stubs import StubBackend, StubFacts -from subscription_manager.gui.registergui import RegisterScreen, \ - CredentialsScreen, ActivationKeyScreen, ChooseServerScreen, \ - CREDENTIALS_PAGE, CHOOSE_SERVER_PAGE +from subscription_manager.gui.registergui import RegisterWidget, \ + CredentialsScreen, ActivationKeyScreen, ChooseServerScreen, \ + CREDENTIALS_PAGE, CHOOSE_SERVER_PAGE +from subscription_manager.ga import GObject as ga_GObject +from subscription_manager.ga import Gtk as ga_Gtk -class RegisterScreenTests(SubManFixture): + +class RegisterWidgetTests(SubManFixture): def setUp(self): - super(RegisterScreenTests, self).setUp() + super(RegisterWidgetTests, self).setUp() self.backend = StubBackend() expected_facts = {'fact1': 'one', 'fact2': 'two', @@ -19,7 +22,7 @@ class RegisterScreenTests(SubManFixture): 'system.uuid': 'MOCKUUID'} self.facts = StubFacts(fact_dict=expected_facts) - self.rs = RegisterScreen(self.backend, self.facts) + self.rs = RegisterWidget(self.backend, self.facts) self.rs._screens[CHOOSE_SERVER_PAGE] = Mock() self.rs._screens[CHOOSE_SERVER_PAGE].index = 0 @@ -29,29 +32,77 @@ class RegisterScreenTests(SubManFixture): def test_show(self): self.rs.initialize() - self.rs.show() - def test_show_registration_returns_to_choose_server_screen(self): - self.rs.initialize() - self.rs.show() - self.rs.register() - self.assertEquals(CREDENTIALS_PAGE, - self.rs.register_notebook.get_current_page() - 1) - self.rs.cancel(self.rs.cancel_button) + # FIXME: unit tests for gtk is a weird universe + def test_registration_error_returns_to_page(self): self.rs.initialize() - self.rs.show() - self.assertEquals(CHOOSE_SERVER_PAGE, - self.rs.register_notebook.get_current_page()) + + self.correct_page = None + + def error_handler(obj, msg, exc_info): + page_after = self.rs.register_notebook.get_current_page() + + # NOTE: these exceptions are not in the nost test context, + # so they don't actually fail nose + self.assertEquals(page_after, 0) + self.correct_page = True + self.quit() + + def emit_proceed(): + self.rs.emit('proceed') + return False + + def emit_error(): + self.rs.emit('register-error', 'Some register error', None) + return False + + self.rs.connect('register-error', error_handler) + + ga_GObject.timeout_add(250, self.quit) + ga_GObject.idle_add(emit_proceed) + ga_GObject.idle_add(emit_error) + + # run till quit or timeout + # if we get to the state we want we can call quit + ga_Gtk.main() + + # verify class scope self.correct_page got set correct in error handler + self.assertTrue(self.correct_page) + + def quit(self): + ga_Gtk.main_quit() + + +def mock_parent(): + parent = Mock() + backend = StubBackend() + parent.backend = backend + parent.async = Mock() + + +class StubReg(object): + def __init__(self): + self.parent_window = Mock() + self.backend = StubBackend() + self.async = Mock() + self.reg_info = Mock() + self.expected_facts = {'fact1': 'one', + 'fact2': 'two', + 'system': '', + 'system.uuid': 'MOCKUUID'} + self.facts = StubFacts(fact_dict=self.expected_facts) class CredentialsScreenTests(SubManFixture): def setUp(self): super(CredentialsScreenTests, self).setUp() - self.backend = StubBackend() - self.parent = Mock() - self.screen = CredentialsScreen(self.backend, self.parent) + stub_reg = StubReg() + self.screen = CredentialsScreen(reg_info=stub_reg.reg_info, + async_backend=stub_reg.async, + facts=stub_reg.facts, + parent_window=stub_reg.parent_window) def test_clear_credentials_dialog(self): # Pull initial value here since it will be different per machine. @@ -71,9 +122,11 @@ class CredentialsScreenTests(SubManFixture): class ActivationKeyScreenTests(SubManFixture): def setUp(self): super(ActivationKeyScreenTests, self).setUp() - self.backend = StubBackend() - self.parent = Mock() - self.screen = ActivationKeyScreen(self.backend, self.parent) + stub_reg = StubReg() + self.screen = ActivationKeyScreen(reg_info=stub_reg.reg_info, + async_backend=stub_reg.async, + facts=stub_reg.facts, + parent_window=stub_reg.parent_window) def test_split_activation_keys(self): expected = ['hello', 'world', 'how', 'are', 'you'] @@ -85,21 +138,23 @@ class ActivationKeyScreenTests(SubManFixture): class ChooseServerScreenTests(SubManFixture): def setUp(self): super(ChooseServerScreenTests, self).setUp() - self.backend = StubBackend() - self.parent = Mock() - self.screen = ChooseServerScreen(self.backend, self.parent) + stub_reg = StubReg() + self.screen = ChooseServerScreen(reg_info=stub_reg.reg_info, + async_backend=stub_reg.async, + facts=stub_reg.facts, + parent_window=stub_reg.parent_window) def test_activation_key_checkbox_sensitive(self): self.screen.server_entry.set_text("foo.bar:443/baz") self.assertTrue(self.screen.activation_key_checkbox.get_property('sensitive')) - def test_activation_key_checkbox_insensitive(self): + def test_activation_key_checkbox_prod_sensitive(self): self.screen.server_entry.set_text("subscription.rhn.redhat.com:443/baz") - self.assertFalse(self.screen.activation_key_checkbox.get_property('sensitive')) + self.assertTrue(self.screen.activation_key_checkbox.get_property('sensitive')) def test_activation_key_checkbox_inactive_when_insensitive(self): self.screen.server_entry.set_text("foo.bar:443/baz") self.screen.activation_key_checkbox.set_active(True) self.screen.server_entry.set_text("subscription.rhn.redhat.com:443/baz") - self.assertFalse(self.screen.activation_key_checkbox.get_property('sensitive')) - self.assertFalse(self.screen.activation_key_checkbox.get_property('active')) + self.assertTrue(self.screen.activation_key_checkbox.get_property('sensitive')) + self.assertTrue(self.screen.activation_key_checkbox.get_property('active'))