Exemple #1
0
 def __init__(self, mainwindow):
     self.mainwindow = mainwindow
     self._interval = _BASE_INTERVAL
     self.addToInfoArea = mainwindow.addToInfoArea
     self.client = mainwindow.client
     self.splash = SplashScreen()
     self._component_to_name = ComponentToName()
     self.reset()
Exemple #2
0
 def joinSecondary(self):
     self.mainwindow.addToInfoArea(
         tr("Attempting to enslave the secondary, please wait...")
         )
     self.splash = SplashScreen()
     self.splash.setText(tr("Attempting to enslave the secondary..."))
     self.splash.show()
     async = self.client.async()
     async.call('ha', 'startHA',
         callback = self.successJoin,
         errback = self.errorJoin
         )
    def buildGui(self):
        self.__splash = SplashScreen()
        vbox = QHBoxLayout(self)
        self.setTitle(tr("Appliance restoration system"))

        save = QPushButton(QIcon(":/icons/down"), tr("Download appliance configuration"), self)
        restore = QPushButton(QIcon(":/icons/up"), tr("Restore a previously saved configuration"), self)

        for item in save, restore:
            vbox.addWidget(item)
        self.mainwindow.writeAccessNeeded(restore)

        self.connect(restore, SIGNAL("clicked()"), self.upload_file)
        self.connect(save, SIGNAL("clicked()"), self.download_file)
Exemple #4
0
class Applier(object):
    def __init__(self, mainwindow):
        self.mainwindow = mainwindow
        self._interval = _BASE_INTERVAL
        self.addToInfoArea = mainwindow.addToInfoArea
        self.client = mainwindow.client
        self.splash = SplashScreen()
        self._component_to_name = ComponentToName()
        self.reset()

    def setPhase(self, phase):
        self._last_fired_component = ''
        self._last_fired_component_formatted = ''

    def reset(self):
        self._last_read_index = 0
        self._apply_error = None
        self._applied_component_list = []
        self._rolled_back_component_list = []
        self._rollback_occurred = False
        self._component_counter = 0
        self._final_success_msg = ''
        self._final_error_msg = ''
        self._rollback_error_msg = ''
        self.setPhase('')

    def rollback(self):
        self._component_counter = 0
        self._rollback_occurred = True

    def start(self):
        self.__start_info()
        async = self.client.async()
        async.call('config', 'applyStart',
            callback=self._start_ok,
            errback=self._start_err
            )

    def __force_start(self):
        async = self.client.async()
        async.call('config', 'forceApplyStart',
            callback=self._start_ok,
            errback=self._start_err
            )

    def __start_info(self):
        # Log an empty line
        self.mainwindow.addHTMLToInfoArea(BR)
        self.splash.setText(tr("Applying the new configuration"))
        #TODO: disable hiding on clic.
        self.splash.show()

    def __end_all(self):
        self.splash.hide()
        if self.mainwindow._standalone != STANDALONE:
            self.mainwindow.eas_window.setModified(False)
        # Log an empty line
        self.mainwindow.addHTMLToInfoArea(BR)
        if self._rollback_occurred:
            message = tr("The application of the new configuration failed: the previous configuration has been restored.")
            if self._apply_error:
                message = message + BR + BR + self._apply_error
                message = unicode(message)
            QMessageBox.critical(
                self.mainwindow,
                tr("Application failed"),
                message
                )
        else:
            self.mainwindow.apply_done()

    def component_label(self, comp_name):
        return self._component_to_name.display_name(comp_name)

    def __handle_SessionError(self, err):
        """
        Only acts if action is relevant.
        You safely can pass any error to this function.
        """
        if isinstance(err, SessionError):
            QMessageBox.critical(
                self.mainwindow,
                tr("Disconnected!"),
                tr("You have been disconnected. "
                "Your changes are lost. You may log in again.")
                )
                #FIXME: exit?

    def __handle_errors(self, err):
        debug = self.mainwindow.debug
        if debug:
            html = Html(formatException(err, debug), escape=False) + BR
            self.mainwindow.addHTMLToInfoArea(html, category=COLOR_WARNING)
        else:
            text = exceptionAsUnicode(err)
            self.addToInfoArea(text, category=COLOR_WARNING)
        self.__handle_SessionError(err)

    def logWithTimestamp(self, timestamp, html, prefix=None):
        html = Html(html, escape=False) + BR
        timestamp = formatTimestamp(timestamp, True)
        if prefix is not None:
            timestamp = timestamp + prefix
        self.mainwindow.addHTMLToInfoArea(html, prefix=timestamp)

    def __process_logs(self, logs):
        """
        Does everything that has to be done with logs

        -Write them on the console.
        -take any relevant action

        returns True if there are other messages to process
                False if all messages have been processed
        """
        has_more = True
        if len(logs) == 0:
            if self._interval < _MAX_INTERVAL:
                self._interval *= 2
        for log in logs:
            self._interval = _BASE_INTERVAL
            self._last_read_index += 1
            timestamp, message_type, content = log
            timestamp = parseDatetime(timestamp)
            if message_type == PHASE_CHANGE:
                has_more = self._change_phase(timestamp, content)
            elif message_type in ERRORS:
                self._display_err(timestamp, content)
            elif message_type in (GLOBAL_ERROR, GLOBAL_WARNING):
                colors = {GLOBAL_ERROR:COLOR_ERROR ,GLOBAL_WARNING:COLOR_WARNING}
                format, substitutions = content
                message = tr(format) % substitutions
                self.display_message(timestamp, message, color=colors[message_type])
            elif message_type in COMPONENT_LISTS:
                self._process_component_list(timestamp, message_type, content)
            elif message_type in COMPONENT_FIRED:
                self._set_last_fired_component(content)
                message = tr("%(COMPONENT_NAME)s is being configured") % {
                    "COMPONENT_NAME": self._last_fired_component_formatted}
                splash_message = self._last_fired_component
                self._component_counter += 1
                if self._rollback_occurred:
                    components = self._rolled_back_component_list
                else:
                    components = self._applied_component_list
                if components:
                    progress = " (%s/%s)" % (self._component_counter, len(components))
                    message += progress
                    splash_message += progress
                self.logWithTimestamp(timestamp, message)
                self.splash.setText(splash_message)
            elif COMPONENT_MESSAGE == message_type:
                format, substitutions = content
                message = tr(format) % substitutions
                message = htmlColor(message, COLOR_VERBOSE)
                message = tr("%s: %s") % (
                    self._last_fired_component,
                    unicode(message))
                self.logWithTimestamp(timestamp, message)
            else:
                # unknow message type
                message = tr("%s (unknown message type: '%s')")
                message = message % (content, message_type)
                message = htmlColor(message, COLOR_INVALID)
                self.logWithTimestamp(timestamp, message)

        return has_more

    def _set_last_fired_component(self, component_name):
        translated = self.component_label(component_name)
        self._last_fired_component = translated
        self._last_fired_component_formatted = htmlBold(
            htmlColor(translated, COLOR_EMPHASIZED)
            )

    def _display_err(self, timestamp, err):
        name = self._last_fired_component
        html = tr("%s triggered the following error") % name
        html = htmlColor(html, COLOR_ERROR)
        err = Html(err) # content is an error
        html = tr("%s: %s") % (unicode(html), unicode(err))
        if not self._rollback_occurred:
            self._apply_error = Html(html, escape=False)
        self.logWithTimestamp(timestamp, html)

    def display_message(self, timestamp, message, color=None, prefix=None):
        if color is not None:
            html = htmlColor(message, color)
        else:
            html = Html(message)
        self.logWithTimestamp(timestamp, html)

    def _change_phase(self, timestamp, phase):
        """
        returns True if there are other messages to process
                False if all messages have been processed
        """
        self.setPhase(phase)
        has_more = True
        hexacolor = None

        if phase == GLOBAL_DONE:
            if self._rollback_occurred:
                return False
            hexacolor = COLOR_SUCCESS
            message = tr("The new configuration has been successfully applied")
            if self._final_success_msg:
                message = self._final_success_msg
            message = htmlBold(message)
            has_more = False
        elif phase == GLOBAL_APPLY_SKIPPED:
            hexacolor = COLOR_SUCCESS
            message = tr("Application skipped")
            message = htmlBold(message)
            has_more = False
        elif phase == GLOBAL:
            hexacolor = COLOR_EMPHASIZED
            message = tr("Applying the new configuration")
            message = htmlBold(message)
        elif phase == APPLYING:
            hexacolor = COLOR_EMPHASIZED
            message = tr("Normal application phase started")
        elif phase == APPLYING_DONE:
            hexacolor = COLOR_EMPHASIZED
            message = tr("Normal application phase completed")
        elif phase == ROLLING_BACK:
            self.rollback()
            hexacolor = COLOR_ERROR
            message = tr("The application of the new configuration failed: "
                "restoring the previous configuration")
            if self._rollback_error_msg:
                message = self._rollback_error_msg
            message = htmlBold(message)
        elif phase == ROLLING_BACK_DONE:
            self._rollback_occurred = True
            hexacolor = COLOR_ERROR
            message = tr("The application of the new configuration failed: "
                "the previous configuration has been restored")
            if self._final_error_msg:
                message = self._final_error_msg
            message = htmlBold(message)
        else:
            message = Html(phase)

        if self.mainwindow.debug \
        or phase not in (APPLYING, APPLYING_DONE):
            prefix = formatTimestamp(timestamp, True)
            html = message + BR
            self.mainwindow.addHTMLToInfoArea(html, hexacolor, prefix)
        return has_more

    def _process_component_list(self, timestamp, message_type, component_list):
        displayed_list = map(self.component_label, component_list)
        displayed_list = u', '.join(displayed_list)
        if message_type == APPLIED_COMPONENT_LIST:
            format = tr("Applying the new configuration of components: %s")
            self._applied_component_list = component_list
        elif message_type == ROLLED_BACK_COMPONENT_LIST:
            format = tr("Restoring the previous configuration of components: %s")
            self._rolled_back_component_list = component_list
        else:
            return
        html = Html(format % displayed_list, escape=False)
        self.logWithTimestamp(timestamp, html)

    def _start_err(self, err):
        """
        called when we could not start the poll dance
        """
        if not(isinstance(err, RpcdError) and err.type == "CoreError"):
            self.__handle_errors(err)
            self.__end_all()
            return

        # old ufwi_conf backend version: falling back to old protocol
        async = self.client.async()
        async.call(
            'config', 'apply',
            callback=self._apply_ok,
            errback=self._apply_err
            )

    def __ask_if_force(self, title, message):
        ok = QMessageBox.question(
            self.mainwindow,
            title,
            message,
            QMessageBox.Ok | QMessageBox.Cancel
            )
        if ok == QMessageBox.Ok:
            self.__force_start()
            return

        self.addToInfoArea(tr("Apply operation cancelled."))
        self.__end_all()

    def _start_ok(self, value):
        """
        the poll dance protocol is implemented on the server, go on!
        """

        if value == "trigger reboot? then use force=True":
            self.__ask_if_force(
                tr("Trigger a reboot?"),
                tr(
                    "Applying now (after a restoration) will trigger a reboot of the appliance. "
                    "You can cancel now or proceed."
                  )
                )
            #Anyway, return (also with cancel)
            return
        elif "then use force=True" in value:
            self.__ask_if_force(
                RESTORATION_WARNING_TITLE,
                RESTORATION_WARNING
                )
            #Anyway, return (also with cancel)
            return


        if value == "reboot":
            self.addToInfoArea(
                tr("This will trigger a reboot of the appliance (after restoration)"),
                category=COLOR_CRITICAL
            )

            QMessageBox.information(
                self.mainwindow,
                tr("EdenWall reboot scheduled"),
                tr("First application after restoration: EdenWall will reboot afterwards.")
            )

        #reset the last read index
        self.reset()
        self._poll()

    # FIXME: why is this callback never called?
    def _apply_ok(self, arg):
        """
        End of the apply process.
        """
        self.addToInfoArea(tr("Application successful"), category=COLOR_FANCY)
        self.__end_all()

    def _apply_err(self, err):
        """
        End of the apply process.
        """
        self.addToInfoArea(tr("Application failed"), category=COLOR_WARNING)
        self.__end_all()
        self.__handle_errors(err)

    def _poll(self):
        async = self.client.async()
        async.call(
            'config', 'applyLog', self._last_read_index,
            callback=self._poll_ok,
            errback=self._poll_err
            )

    def _poll_ok(self, logs):
        has_more = self.__process_logs(logs)
        if has_more:
            #loop
            QTimer.singleShot(self._interval, self._poll)
        else:
            self.__end_all()

    def _poll_err(self, err):
        """
        A problem in the apply log dance.

        After that, we'll rely on a future feature: the getState
        dance, to know more about the server
        """
        self.__end_all()
        self.__handle_errors(err)

    def start_polling(
            self,
            final_success_message='',
            final_error_message='',
            rollback_error_message='',
            ):
        self.reset()
        self._final_success_msg = final_success_message
        self._final_error_message = final_error_message
        self._rollback_error_msg = rollback_error_message
        self._poll()
Exemple #5
0
class HAConfigFrontend(FullFeaturedScrollArea):
    COMPONENT = 'ha'
    LABEL = tr('High Availability')
    REQUIREMENTS = ('ha',)
    ICON = ':/icons/picto_ha.png'

    if EDENWALL:
        LINK_STATE = {
            NOT_REGISTERED: tr('Not registered'),
            NOT_CONNECTED: tr('Not connected'),
            CONNECTED: tr('Connected'),
        }

        NODE_STATE = {
            NOT_REGISTERED: tr('Not registered'),
            ACTIVE: tr('Active'),
            INACTIVE: tr('Inactive'),
        }

        STATE_DESCRIPTIONS = {
            ENOHA: span(tr('High availability is not configured.')),
            PENDING_PRIMARY: span(tr('Primary; click "Join" to <br/>complete high availability configuration.')),
            PRIMARY: span(tr('Primary, in function.')),
            SECONDARY: span(tr('Secondary, in function.')),
            PENDING_SECONDARY: span(tr('Secondary; connect EAS to the primary server to <br/>complete high availability configuration.')),
        }

    def __init__(self, client, parent):
        self.auth_page = None
        self.group_page = None
        self.auth_configs = {}
        self.group_configs = {}
        self.mainwindow = parent

        self.node_status_label = QLabel()   # status of current node (active / inactive)
        self.link_status_label = QLabel() # status of dedicaced link
        self.interface_label = QLabel()
        self.activity_label = QLabel()
        self.last_error_text = QTextEdit()
        self.last_error_text.setReadOnly(True)
        self.last_error_text.setMaximumHeight(100)
        self.type_label = QLabel()
        self.join = None

        self.link_state = None
        self.ha_last_date = None

        self.version = self.mainwindow.init_call('ha', 'getComponentVersion')

        FullFeaturedScrollArea.__init__(self, client, parent)
        self.missing_upgrades = []

        # create timer only if HA activated
        if self.__ha_type() != ENOHA:
            self.timer = Timer(
                self.setViewData,
                REFRESH_INTERVAL_MILLISECONDS,
                self.mainwindow.keep_alive.thread,
                self
                )
        else:
            self.timer = None

        self.force_join = QAction(QIcon(":/icons/force_join"), tr("Force joining secondary"), self)
        self.connect(self.force_join, SIGNAL('triggered(bool)'), self.joinSecondary)
        self.force_takeover = QAction(QIcon(":/icons/ha_takeover"), tr("Force to become active"), self)
        self.connect(self.force_takeover, SIGNAL('triggered(bool)'), self.takeover)

        buttons = [self.force_join, self.force_takeover]
        self.contextual_toolbar = ToolBar(buttons, name=tr("High Availability"))

    @staticmethod
    def get_calls():
        """
        services called by initial multicall
        """
        return (('ha', 'getState'), ('ha', 'getFullState'),
            ('ha', 'getComponentVersion'))

    def __ha_type(self):
        config = QHAObject.getInstance().cfg
        if config is None:
            return ENOHA
        return config.ha_type

    def buildInterface(self):
        frame = QFrame()
        self.setWidget(frame)
        self.setWidgetResizable(True)
        layout = QGridLayout(frame)

        title = u'<h1>%s</h1>' % self.tr('High Availability Configuration')
        layout.addWidget(QLabel(title), 0, 0, 1, -1)

        configure = QPushButton(QIcon(":/icons/configurationha.png"), tr('Configure'))
        self.mainwindow.writeAccessNeeded(configure)
        layout.addWidget(configure, 1, 3)

        self.join = QPushButton(QIcon(":/icons/joinha.png"), tr('Join Secondary'))
        layout.addWidget(self.join, 2, 3)

        if "1.1" == self.version:
            layout.addWidget(QLabel(tr('Appliance status')), 3, 0)
            layout.addWidget(self.node_status_label, 3, 1)
            row = 4
        else:
            row = 3

        self.last_error_title = QLabel(tr('Last error'))
        self.missing_upgrade_text_label = QLabel()
        self.missing_upgrade_nums_label = QLabel()
        widgets = [
            (QLabel(tr('Link status')), self.link_status_label),
            (QLabel(tr('Type')),self.type_label),
            (QLabel(tr('Interface')),self.interface_label),
            (QLabel(tr('Last activity')), self.activity_label),
            (self.last_error_title, self.last_error_text),
            (self.missing_upgrade_text_label, self.missing_upgrade_nums_label),
        ]

        for index, (label, widget) in enumerate(widgets):
            layout.addWidget(label, row+index, 0)
            layout.addWidget(widget, row+index, 1)

        # syncUpgrades_button = QPushButton(tr('Synchronize upgrades'))
        # layout.addWidget(syncUpgrades_button, 7, 2)
        # self.connect(syncUpgrades_button, SIGNAL('clicked()'), self.syncUpgrades)

        layout.setColumnStretch(0,  1)
        layout.setColumnStretch(1,  5)
        layout.setColumnStretch(2, 10)
        layout.setColumnStretch(3,  1)
        layout.setRowStretch(row+6, row+7)
        self.connect(configure, SIGNAL("clicked()"), self.displayConfig)
        self.connect(self.join, SIGNAL("clicked()"), self.joinSecondary)

    def displayConfig(self):
        config = QHAObject.getInstance().hacfg
        if config.ha_type == PRIMARY:
            QMessageBox.warning(
                self,
                tr('High availability already configured'),
                tr('High availability status disallows editing the configuration'))
            return

        ha_wizard = ConfigWizard(self)
        ret = ha_wizard.exec_()
        if ret != QDialog.Accepted:
            return False

        qhaobject = QHAObject.getInstance()
        qhaobject.pre_modify()

        config = qhaobject.hacfg

        qnetobject = QNetObject.getInstance()
        qnetobject.pre_modify()
        net_cfg = qnetobject.netcfg

        old_type = config.ha_type
        new_config = ha_wizard.getData()
        config.ha_type = new_config.ha_type
        config.interface_id = new_config.interface_id
        config.interface_name = new_config.interface_name
        if config.ha_type in (PENDING_PRIMARY, PENDING_SECONDARY):
            iface = net_cfg.getIfaceByHardLabel(config.interface_id)
            configureHA(net_cfg, iface)
            network_modified = True
        elif config.ha_type != old_type:
            deconfigureHA(net_cfg)
            network_modified = True
            # XXX should not reconfigure now ?
        else:
            network_modified = False

        valid = qnetobject.post_modify()
        if not valid:
            qhaobject.revert()
        else:
            # FIXME: use post_modify() result?
            qhaobject.post_modify()
            self.setModified(True)
            if network_modified:
                network = self.mainwindow.getPage('network')
                network.setModified(True)
                dhcp = self.mainwindow.getPage('dhcp')
                dhcp.dhcp_widget.setModified(True)
                dhcp.dhcp_widget.fillView()

        self.setViewData()
        return True

    def hide_last_error(self):
        self.last_error_text.clear()
        self.last_error_text.hide()
        self.last_error_title.hide()

    def show_last_error(self, last_error):
        if last_error:
            self.last_error_text.setText(last_error)
            self.last_error_title.show()
            self.last_error_text.show()
        else:
            self.hide_last_error()

    def syncUpgrades(self):
        # First, update the list of missing upgrades:
        defer = self.setViewData()
        if defer:
            defer.addCallback(self._syncUpgrades)

    def _syncUpgrades(self):
        pass

    def fetchConfig(self):
        # we use QHAObject
        pass

    def __disable(self):
        self.close()
        raise NuConfModuleDisabled("Disabling high availability interface")

    def setViewData(self):
        config = QHAObject.getInstance().hacfg

        if "1.0" == self.version:
            raw_state = self.mainwindow.init_call('ha', 'getState')
            if raw_state is None:
                self.__disable()
                return
            self.link_state, self.ha_last_date, last_error = raw_state
            self.link_status_label.setText(self.LINK_STATE.get(self.link_state,
                tr('(unknown)')))
        else:
            raw_state = self.mainwindow.init_call('ha', 'getFullState')
            if raw_state is None:
                self.__disable()
                return
            node_state = raw_state['node_state']
            self.link_state = raw_state['link_state']
            self.ha_last_date = raw_state['seen_other']
            last_error = raw_state['last_error']

            self.link_status_label.setText(self.LINK_STATE.get(self.link_state,
                tr('(unknown)')))
            self.node_status_label.setText(self.NODE_STATE.get(node_state,
                tr('(unknown)')))

        # TEMP : use compatibility instead
        try:
            try:
                if raw_state.get('link_state', None) == CONNECTED:
                    self.join.setEnabled(False)
            except Exception:
                if isinstance(raw_state, list) and len(raw_state) > 0:
                    if raw_state[0] == CONNECTED:
                        self.join.setEnabled(False)
        except TypeError:
            pass

        ha_type = self.__ha_type()

        self.type_label.setText(
            HAConfigFrontend.STATE_DESCRIPTIONS[ha_type]
            )

        if ha_type != ENOHA:
            if self.ha_last_date not in (0, None):
                fmt = '%Y-%m-%d %H:%M:%S'
                seen = time.strftime(fmt, time.localtime(self.ha_last_date))
                self.activity_label.setText(unicode(seen))
            if config.interface_id is not None:
                qnetobject = QNetObject.getInstance()
                iface = qnetobject.netcfg.getIfaceByHardLabel(config.interface_id)
                self.interface_label.setText(iface.fullName())
            try:
                last_error = self.client.call('ha', 'getLastError')
                self.show_last_error(last_error)
            except Exception:
                self.hide_last_error()
        else:
            self.interface_label.clear()
            self.activity_label.clear()
            self.hide_last_error()
        if ha_type == PRIMARY:
            async = self.client.async()
            async.call('ha', 'getMissingUpgradeNums',
                       callback=self._get_missing_upgrade_nums,
                       errback=self.writeError)
        self.mainwindow.writeAccessNeeded(self.join)
        if self.join.isEnabled():
            self.join.setEnabled(PENDING_PRIMARY == ha_type)

    def _get_missing_upgrade_nums(self, missing_upgrade_nums):
        try:
            self.missing_upgrade_nums = missing_upgrade_nums
            if type(missing_upgrade_nums) != type([]):
                self.missing_upgrade_nums_label.setText(tr('N/A'))
            elif not missing_upgrade_nums:
                self.missing_upgrade_nums_label.setText(tr('None '))
            else:
                sample = sorted(missing_upgrade_nums)
                if len(sample) > MAX_MISSING_UPGRADE_NUMS:
                    sample = sample[:MAX_MISSING_UPGRADE_NUMS] + ['...']
                self.missing_upgrade_nums_label.setText(
                    ', '.join([unicode(num) for num in sample]))
            self.missing_upgrade_text_label.setText(
                tr('Missing upgrades on the secondary'))
        except Exception:
            pass

    def sendConfig(self, message):
        """
        Save HA config
        """
        serialized =  QHAObject.getInstance().hacfg.serialize()
        self.client.call('ha', 'configureHA', serialized, message)

    def joinSecondary(self):
        self.mainwindow.addToInfoArea(
            tr("Attempting to enslave the secondary, please wait...")
            )
        self.splash = SplashScreen()
        self.splash.setText(tr("Attempting to enslave the secondary..."))
        self.splash.show()
        async = self.client.async()
        async.call('ha', 'startHA',
            callback = self.successJoin,
            errback = self.errorJoin
            )

    def successJoin(self, ok):
        self.splash.hide()
        self.timer = Timer(
            self.setViewData,
            REFRESH_INTERVAL_MILLISECONDS,
            self.mainwindow.keep_alive.thread,
            self
            )
        self.join.setEnabled(False)
        self.mainwindow.addToInfoArea(tr("Joining secondary: success"), category=COLOR_SUCCESS)

    def errorJoin(self, error):
        self.splash.hide()
        self.mainwindow.addToInfoArea(tr('Joining secondary: fail'), category=COLOR_ERROR)
        warning = QMessageBox(self)
        warning.setWindowTitle(tr('Joining secondary: fail'))
        warning.setText(tr('An error was encountered while joining secondary.'))
        errmsg = exceptionAsUnicode(error)
        if "current state=ENOHA" in errmsg:
            errmsg = tr(
                "Can not join yet: the appliance is still not configured for "
                "high availability. Did you save and apply your changes?"
                )
        warning.setDetailedText(errmsg)
        warning.setIcon(QMessageBox.Warning)
        warning.exec_()

    def takeover(self):
        try:
            self.client.call('ha', 'takeover')
        except Exception, err:
            self.mainwindow.exception(err)
            return
        self.mainwindow.addToInfoArea(tr('Take over request sent.'), category=COLOR_SUCCESS)
class SaveRestoreWidget(QGroupBox):
    last_save_position = expanduser("~")

    def __init__(self, mainwindow, parent=None):
        QGroupBox.__init__(self, parent)
        self.mainwindow = mainwindow

        self.buildGui()

    def buildGui(self):
        self.__splash = SplashScreen()
        vbox = QHBoxLayout(self)
        self.setTitle(tr("Appliance restoration system"))

        save = QPushButton(QIcon(":/icons/down"), tr("Download appliance configuration"), self)
        restore = QPushButton(QIcon(":/icons/up"), tr("Restore a previously saved configuration"), self)

        for item in save, restore:
            vbox.addWidget(item)
        self.mainwindow.writeAccessNeeded(restore)

        self.connect(restore, SIGNAL("clicked()"), self.upload_file)
        self.connect(save, SIGNAL("clicked()"), self.download_file)

    #        restore.setEnabled(False)

    def _start_splash(self, message):
        self.__splash.setText(tr("Please wait..."))
        self.mainwindow.addToInfoArea(message, category=COLOR_FANCY)
        self.__splash.show()

    def _stop_splash(self):
        self.__splash.hide()

    def upload_file(self):
        dialog = RestoreConfirmDialog()
        accept = dialog.exec_()
        if not accept:
            return

        dialog = UploadDialog(
            selector_label=tr("Select an EdenWall archive"), filter=tr("EdenWall archive (*.tar.gz *)")
        )
        accepted = dialog.exec_()

        if accepted != QDialog.Accepted:
            return

        filename = dialog.filename

        if not filename:
            return
        with open(filename, "rb") as fd:
            content = fd.read()

        content = encodeFileContent(content)

        self.mainwindow.addToInfoArea(tr("Uploading of an archive file to restore the appliance"))
        async = self.mainwindow.client.async()
        async.call("nurestore", "restore", content, callback=self.success_upload, errback=self.error_upload)
        self._start_splash(tr("Uploading EdenWall restoration archive..."))

    def success_upload(self, value):
        self._stop_splash()
        message = tr("Successfully uploaded the archive file.")
        self.mainwindow.addToInfoArea(message, COLOR_SUCCESS)
        restoration_restart(self.mainwindow)

    def error_upload(self, value):
        self._stop_splash()
        message = tr("Error restoring appliance: ")
        self.mainwindow.addToInfoArea(message + unicode(value), COLOR_ERROR)

    def download_file(self):
        async = self.mainwindow.client.async()
        async.call("nurestore", "export", callback=self.success_download, errback=self.error_download)
        self._start_splash(tr("Downloading EdenWall restoration archive..."))

    def success_download(self, value):
        self._stop_splash()
        encoded, components = value
        self.mainwindow.addToInfoArea(tr("Downloaded EdenWall configuration"))

        extension = "*.tar.gz"
        archive_description = tr("Edenwall archive file")
        filter = "%s (%s)" % (archive_description, extension)

        date = datetime.now().strftime("%c")
        date = toUnicode(date)
        date = date.replace(".", "")
        date = date.replace(" ", "_")
        date = date.replace(":", "-")

        host = self.mainwindow.client.host
        host = host.replace(".", "_")
        host = host.replace(":", "-")
        suggestion = u"edenwall-config-%s-%s.tar.gz" % (host, date)

        filename = QFileDialog.getSaveFileName(
            self, tr("Choose a filename to save under"), join(SaveRestoreWidget.last_save_position, suggestion), filter
        )

        filename = unicode(filename)

        if not filename:
            self.mainwindow.addToInfoArea(tr("EdenWall configuration save cancelled"))
            return

        SaveRestoreWidget.last_save_position = split(filename)[0]

        try:
            with open(filename, "wb") as fd:
                fd.write(decodeFileContent(encoded))
        except IOError, err:
            message_vars = (tr("An error occured while saving EdenWall configuration:"), toUnicode(strerror(err.errno)))

            text_message = "%s\n%s" % message_vars
            self.mainwindow.addToInfoArea(text_message, category=COLOR_ERROR)

            html_message = "<span>%s<br/><b>%s</b></span>" % message_vars
            QMessageBox.critical(self, tr("Save error"), html_message)
            return

        self.mainwindow.addToInfoArea(tr("Saved EdenWall configuration as %(filename)s") % {"filename": filename})