예제 #1
0
def DoDownload(train, cache_dir, pkg_type, verbose, ignore_space=False):

    try:
        if not verbose:
            with ProgressBar() as progress_bar:
                handler = UpdateHandler(progress_bar.update)
                rv = Update.DownloadUpdate(
                    train,
                    cache_dir,
                    get_handler=handler.get_handler,
                    check_handler=handler.check_handler,
                    pkg_type=pkg_type,
                )
                if rv is False:
                    progress_bar.update(message="No updates available")
        else:
            rv = Update.DownloadUpdate(train,
                                       cache_dir,
                                       pkg_type=pkg_type,
                                       ignore_space=ignore_space)
    except Exceptions.ManifestInvalidSignature:
        log.error("Manifest has invalid signature")
        print("Manifest has invalid signature", file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateBusyCacheException as e:
        log.error(str(e))
        print("Download cache directory is busy", file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateIncompleteCacheException:
        log.error(str(e))
        print("Incomplete download cache, cannot update", file=sys.stderr)
        sys.exit(1)
    except Exceptions.ChecksumFailException as e:
        log.error(str(e))
        print("Checksum error, cannot update", file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateInvalidUpdateException as e:
        log.error(str(e))
        print("Update not permitted:\n{0}".format(e.value), file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateInsufficientSpace as e:
        log.error(str(e), exc_info=True)
        print(e.value if e.value else "Insufficient space for download")
        sys.exit(1)
    except BaseException as e:
        log.error(str(e), exc_info=True)
        print("Received exception during download phase, cannot update",
              file=sys.stderr)
        sys.exit(1)

    return rv
예제 #2
0
def DoDownload(train, cache_dir, pkg_type):
    import freenasOS.Update as Update
    import freenasOS.Exceptions as Exceptions

    try:
        rv = Update.DownloadUpdate(train, cache_dir, pkg_type=pkg_type)
    except Exceptions.ManifestInvalidSignature:
        log.error("Manifest has invalid signature")
        print("Manifest has invalid signature", file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateBusyCacheException as e:
        log.error(str(e))
        print("Download cache directory is busy", file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateIncompleteCacheException:
        log.error(str(e))
        print("Incomplete download cache, cannot update", file=sys.stderr)
        sys.exit(1)
    except BaseException as e:
        log.error(str(e))
        print("Received exception during download phase, cannot update",
              file=sys.stderr)
        sys.exit(1)

    return rv
예제 #3
0
def main(handler, args):

    setproctitle('updated')

    handler.pid = os.getpid()
    handler.dump()

    if args.download:
        log.debug('Starting DownloadUpdate')
        Update.DownloadUpdate(
            args.train,
            args.cache,
            check_handler=handler.get_handler,
            get_handler=handler.get_file_handler,
        )
        log.debug('DownloadUpdate finished')

    new_manifest = Manifest.Manifest(require_signature=True)
    try:
        new_manifest.LoadPath(args.cache + '/MANIFEST')
    except ManifestInvalidSignature as e:
        log.error("Cached manifest has invalid signature: %s" % str(e))
        raise
    update_version = new_manifest.Version()

    if args.apply:
        log.debug('Starting ApplyUpdate')
        handler.reboot = Update.ApplyUpdate(
            args.cache,
            install_handler=handler.install_handler,
        )
        log.debug('ApplyUpdate finished')
        if handler.reboot:
            # Create Alert that update is applied and system should now be rebooted
            create_update_alert(update_version)
예제 #4
0
파일: update.py 프로젝트: MrYHM/freenas
    def download(self, job):
        train = self.middleware.call_sync('update.get_trains')['selected']
        location = self.middleware.call_sync('update.get_update_location')

        job.set_progress(0, 'Retrieving update manifest')

        handler = UpdateHandler(self, job, 100)

        Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        update = Update.CheckForUpdates(train=train, cache_dir=location)

        if not update:
            return False

        notified = False
        try:
            if self.middleware.call_sync('cache.has_key', 'update.notified'):
                notified = self.middleware.call_sync('cache.get',
                                                     'update.notified')
        except Exception:
            pass

        if not notified:
            self.middleware.call_sync('cache.put', 'update.notified', True)
            conf = Configuration.Configuration()
            sys_mani = conf.SystemManifest()
            if sys_mani:
                sequence = sys_mani.Sequence()
            else:
                sequence = ''

            changelog = get_changelog(train,
                                      start=sequence,
                                      end=update.Sequence())

            try:
                # FIXME: Translation
                self.middleware.call_sync(
                    'mail.send', {
                        'subject': 'Update Available',
                        'text':
                        '''A new update is available for the %(train)s train.
Version: %(version)s
Changelog:
%(changelog)s
''' % {
                            'train': train,
                            'version': update.Version(),
                            'changelog': changelog,
                        },
                    }).wait_sync()
            except Exception:
                self.logger.warn('Failed to send email about new update',
                                 exc_info=True)
        return True
예제 #5
0
    def update(self, job, attrs=None):
        """
        Downloads (if not already in cache) and apply an update.
        """
        train = (attrs or {}).get('train') or self.get_train()
        location = self.middleware.call('notifier.get_update_location')

        handler = UpdateHandler(self, job)

        update = Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        if update is False:
            raise ValueError('No update available')

        new_manifest = Manifest.Manifest(require_signature=True)
        new_manifest.LoadPath('{}/MANIFEST'.format(location))

        Update.ApplyUpdate(
            location,
            install_handler=handler.install_handler,
        )
        self.middleware.call('cache.put', 'update.applied', True)
        return True
예제 #6
0
    def download_impl(self, job, train, location, progress_proportion):
        scale_flag = os.path.join(location, 'scale')

        if 'SCALE' in train:
            result = self.middleware.call_sync('update.download_impl_scale', job, train, location, progress_proportion)

            if result:
                with open(scale_flag, 'w'):
                    pass

            return result

        job.set_progress(0, 'Retrieving update manifest')

        handler = UpdateHandler(self, job, progress_proportion)

        Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        update = Update.CheckForUpdates(train=train, cache_dir=location)

        result = bool(update)

        if result:
            with contextlib.suppress(FileNotFoundError):
                os.unlink(scale_flag)

        return result
예제 #7
0
파일: update.py 프로젝트: xpcom-bsd/freenas
    async def update(self, job, attrs=None):
        """
        Downloads (if not already in cache) and apply an update.
        """
        attrs = attrs or {}
        train = attrs.get('train') or (
            await self.middleware.call('update.get_trains'))['selected']
        location = await self.middleware.call('notifier.get_update_location')

        job.set_progress(0, 'Retrieving update manifest')

        handler = UpdateHandler(self, job)

        update = Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        if update is False:
            raise ValueError('No update available')

        new_manifest = Manifest.Manifest(require_signature=True)
        new_manifest.LoadPath('{}/MANIFEST'.format(location))

        Update.ApplyUpdate(
            location,
            install_handler=handler.install_handler,
        )
        await self.middleware.call('cache.put', 'update.applied', True)

        if attrs.get('reboot'):
            await self.middleware.call('system.reboot', {'delay': 10})
        return True
예제 #8
0
def main():

    try:
        updateobj = mUpdate.objects.order_by('-id')[0]
    except IndexError:
        updateobj = mUpdate.objects.create()

    if updateobj.upd_autocheck is False:
        return

    location = notifier().get_update_location()

    Update.DownloadUpdate(updateobj.get_train(), location)

    update = Update.CheckForUpdates(
        train=updateobj.get_train(),
        cache_dir=location,
    )

    if not update:
        return

    conf = Configuration.Configuration()
    sys_mani = conf.SystemManifest()
    if sys_mani:
        sequence = sys_mani.Sequence()
    else:
        sequence = ''

    changelog = get_changelog(
        updateobj.get_train(),
        start=sequence,
        end=update.Sequence(),
    )

    hostname = socket.gethostname()

    send_mail(
        subject='%s: %s' % (
            hostname,
            _('Update Available'),
        ),
        extra_headers={
            'X-Mailer': get_sw_name(),
            'X-%s-Host' % get_sw_name(): socket.gethostname()
        },
        text=_('''A new update is available for the %(train)s train.

Version: %(version)s
Changelog:
%(changelog)s
''') % {
            'train': updateobj.get_train(),
            'version': update.Version(),
            'changelog': changelog,
        },
    )
예제 #9
0
파일: update.py 프로젝트: yiqideren/freenas
    async def download(self, job):
        train = (await self.get_trains())['selected']
        location = await self.middleware.call('notifier.get_update_location')

        Update.DownloadUpdate(
            train,
            location,
        )
        update = Update.CheckForUpdates(train=train, cache_dir=location)

        if not update:
            return False

        notified = False
        try:
            if await self.middleware.call('cache.has_key', 'update.notified'):
                notified = await self.middleware.call('cache.get',
                                                      'update.notified')
        except Exception:
            pass

        if not notified:
            await self.middleware.call('cache.put', 'update.notified', True)
            conf = Configuration.Configuration()
            sys_mani = conf.SystemManifest()
            if sys_mani:
                sequence = sys_mani.Sequence()
            else:
                sequence = ''

            changelog = get_changelog(train,
                                      start=sequence,
                                      end=update.Sequence())
            hostname = socket.gethostname()

            try:
                # FIXME: Translation
                await self.middleware.call(
                    'mail.send', {
                        'subject': '{}: {}'.format(hostname,
                                                   'Update Available'),
                        'text':
                        '''A new update is available for the %(train)s train.
Version: %(version)s
Changelog:
%(changelog)s
''' % {
                            'train': train,
                            'version': update.Version(),
                            'changelog': changelog,
                        },
                    })
            except Exception:
                self.logger.warn('Failed to send email about new update',
                                 exc_info=True)
        return True
예제 #10
0
    def download_impl(self, job, train, location, progress_proportion):
        job.set_progress(0, 'Retrieving update manifest')

        handler = UpdateHandler(self, job, progress_proportion)

        Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        update = Update.CheckForUpdates(train=train, cache_dir=location)

        return bool(update)
예제 #11
0
def DoDownload(train, cache_dir, pkg_type, verbose):

    try:
        if not verbose:
            progress_bar = ProgressBar()
            handler = UpdateHandler(progress_bar.update)
            rv = Update.DownloadUpdate(
                train,
                cache_dir,
                get_handler=handler.get_handler,
                check_handler=handler.check_handler,
                pkg_type=pkg_type,
            )
            if rv is False:
                progress_bar.update(message="No updates available")
            progress_bar.finish()
        else:
            rv = Update.DownloadUpdate(train, cache_dir, pkg_type=pkg_type)
    except Exceptions.ManifestInvalidSignature:
        log.error("Manifest has invalid signature")
        print("Manifest has invalid signature", file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateBusyCacheException as e:
        log.error(str(e))
        print("Download cache directory is busy", file=sys.stderr)
        sys.exit(1)
    except Exceptions.UpdateIncompleteCacheException:
        log.error(str(e))
        print("Incomplete download cache, cannot update", file=sys.stderr)
        sys.exit(1)
    except BaseException as e:
        log.error(str(e))
        print("Received exception during download phase, cannot update",
              file=sys.stderr)
        sys.exit(1)

    return rv
예제 #12
0
    async def update(self, job, attrs=None):
        """
        Downloads (if not already in cache) and apply an update.
        """
        attrs = attrs or {}

        trains = await self.middleware.call('update.get_trains')
        train = attrs.get('train') or trains['selected']

        if attrs.get('train'):
            await self.middleware.run_in_thread(self.__set_train, attrs.get('train'), trains)

        location = await self.middleware.call('update.get_update_location')

        job.set_progress(0, 'Retrieving update manifest')

        handler = UpdateHandler(self, job)

        update = Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        if update is False:
            raise ValueError('No update available')

        new_manifest = Manifest.Manifest(require_signature=True)
        new_manifest.LoadPath('{}/MANIFEST'.format(location))

        Update.ApplyUpdate(
            location,
            install_handler=handler.install_handler,
        )
        await self.middleware.call('cache.put', 'update.applied', True)

        if (
            await self.middleware.call('system.is_freenas') or
            (
                await self.middleware.call('failover.licensed') and
                await self.middleware.call('failover.status') != 'BACKUP'
            )
        ):
            await self.middleware.call('update.take_systemdataset_samba4_snapshot')

        if attrs.get('reboot'):
            await self.middleware.call('system.reboot', {'delay': 10})
        return True
예제 #13
0
    def download(self, job):
        train = self.middleware.call_sync('update.get_trains')['selected']
        location = self.middleware.call_sync('update.get_update_location')

        job.set_progress(0, 'Retrieving update manifest')

        handler = UpdateHandler(self, job, 100)

        Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        update = Update.CheckForUpdates(train=train, cache_dir=location)

        self.middleware.call_sync('alert.alert_source_clear_run', 'HasUpdate')

        return bool(update)
예제 #14
0
def main(handler, args):

    set_proc_name('updated')

    handler.pid = os.getpid()
    handler.dump()

    if args.download:
        log.debug('Starting DownloadUpdate')
        Update.DownloadUpdate(
            args.train,
            args.cache,
            check_handler=handler.get_handler,
            get_handler=handler.get_file_handler,
        )
        log.debug('DownloadUpdate finished')

    if args.apply:
        log.debug('Starting ApplyUpdate')
        Update.ApplyUpdate(
            args.cache,
            install_handler=handler.install_handler,
        )
        log.debug('ApplyUpdate finished')
예제 #15
0
def main():
    global log

    def usage():
        print >> sys.stderr, """Usage: %s [-C cache_dir] [-d] [-T train] [-v] <cmd>, where cmd is one of:
        check\tCheck for updates
        update\tDo an update""" % sys.argv[0]
        sys.exit(1)

    try:
        opts, args = getopt.getopt(sys.argv[1:], "C:dT:v")
    except getopt.GetoptError as err:
        print str(err)
        usage()

    verbose = False
    debug = 0
    config = None
    cache_dir = None
    train = None

    for o, a in opts:
        if o == "-v":
            verbose = True
        elif o == "-d":
            debug += 1
        elif o == '-C':
            cache_dir = a
        elif o == "-T":
            train = a
        elif o == '-c':
            cachedir = a
        else:
            assert False, "unhandled option %s" % o

    logging.config.dictConfig({
        'version': 1,
        'disable_existing_loggers': True,
        'formatters': {
            'simple': {
                'format': '[%(name)s:%(lineno)s] %(message)s',
            },
        },
        'handlers': {
            'std': {
                'class': 'logging.StreamHandler',
                'level': 'DEBUG',
                'stream': 'ext://sys.stdout',
            },
        },
        'loggers': {
            '': {
                'handlers': ['std'],
                'level': 'DEBUG',
                'propagate': True,
            },
        },
    })

    log = logging.getLogger('freenas-update')

    sys.path.append("/usr/local/lib")

    import freenasOS.Configuration as Configuration
    import freenasOS.Manifest as Manifest
    import freenasOS.Update as Update

    config = Configuration.Configuration()
    if train is None:
        train = config.SystemManifest().Train()

    if len(args) != 1:
        usage()

    if args[0] == "check":
        # To see if we have an update available, we
        # call Update.DownloadUpdate.  If we have been
        # given a cache directory, we pass that in; otherwise,
        # we make a temporary directory and use that.  We
        # have to clean up afterwards in that case.

        if cache_dir is None:
            download_dir = tempfile.mkdtemp(prefix="UpdateCheck-",
                                            dir=config.TemporaryDirectory())
            if download_dir is None:
                print >> sys.stderr, "Unable to create temporary directory"
                sys.exit(1)
        else:
            download_dir = cache_dir

        rv = Update.DownloadUpdate(train, download_dir)
        if rv is False:
            if verbose:
                print "No updates available"
                if cache_dir is None:
                    Update.RemoveUpdate(download_dir)
                sys.exit(1)
        else:
            if verbose:
                diffs = Update.PendingUpdates(download_dir)
                for (pkg, op, old) in diffs:
                    if op == "delete":
                        print >> sys.stderr, "Delete package %s" % pkg.Name()
                    elif op == "install":
                        print >> sys.stderr, "Install package %s-%s" % (
                            pkg.Name(), pkg.Version())
                    elif op == "upgrade":
                        print >> sys.stderr, "Upgrade package %s %s->%s" % (
                            pkg.Name(), old.Version(), pkg.Version())
            if cache_dir is None:
                Update.RemoveUpdate(download_dir)
            sys.exit(0)

    elif args[0] == "update":
        # This will attempt to apply an update.
        # If cache_dir is given, then we will only check that directory,
        # not force a download if it is already there.  If cache_dir is not
        # given, however, then it downloads.  (The reason is that you would
        # want to run "freenas-update -c /foo check" to look for an update,
        # and it will download the latest one as necessary, and then run
        # "freenas-update -c /foo update" if it said there was an update.
        if cache_dir is None:
            download_dir = tempfile.mkdtemp(prefix="UpdateUpdate-",
                                            dir=config.TemporaryDirectory())
            if download_dir is None:
                print >> sys.stderr, "Unable to create temporary directory"
                sys.exit(1)
            rv = Update.DownloadUpdate(train, download_dir)
            if rv is False:
                if verbose or debug:
                    print >> sys.stderr, "DownloadUpdate returned False"
                sys.exit(1)
        else:
            download_dir = cache_dir

        diffs = Update.PendingUpdates(download_dir)
        if diffs is None or diffs == {}:
            if verbose:
                print >> sys.stderr, "No updates to apply"
        else:
            if verbose:
                for (pkg, op, old) in diffs:
                    if op == "delete":
                        print >> sys.stderr, "Delete package %s" % pkg.Name()
                    elif op == "install":
                        print >> sys.stderr, "Install package %s-%s" % (
                            pkg.Name(), pkg.Version())
                    elif op == "upgrade":
                        print >> sys.stderr, "Upgrade package %s %s -> %s" % (
                            pkg.Name(), old.Version(), pkg.Version())
            rv = Update.ApplyUpdate(download_dir)
            if rv is False:
                if verbose:
                    print >> sys.stderr, "ApplyUpdates failed"
                if cache_dir is None:
                    Update.RemoveUpdate(download_dir)
                sys.exit(1)
            Update.RemoveUpdate(download_dir)
            # Change this if/when we can do an update without a reboot.
            print >> sys.stderr, "System should be rebooted now"
            sys.exit(0)
    else:
        usage()
예제 #16
0
def main():
    global log

    logging.config.dictConfig({
        'version': 1,
        'disable_existing_loggers': True,
        'formatters': {
            'simple': {
                'format': '[%(name)s:%(lineno)s] %(message)s',
            },
        },
        'handlers': {
            'std': {
                'class': 'logging.StreamHandler',
                'level': 'DEBUG',
                'stream': 'ext://sys.stderr',
            },
        },
        'loggers': {
            '': {
                'handlers': ['std'],
                'level': 'DEBUG',
                'propagate': True,
            },
        },
    })

    log = logging.getLogger('freenas-update')

    sys.path.append("/usr/local/lib")

    import freenasOS.Configuration as Configuration
    import freenasOS.Manifest as Manifest
    import freenasOS.Update as Update

    def usage():
        print >> sys.stderr, """Usage: %s [-C cache_dir] [-d] [-T train] [--no-delta] [-v] <cmd>, where cmd is one of:
        check\tCheck for updates
        update\tDo an update""" % sys.argv[0]
        sys.exit(1)

    try:
        short_opts = "C:dT:v"
        long_opts = ["cache=", "debug", "train=", "verbose", "no-delta"]
        opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
    except getopt.GetoptError as err:
        print str(err)
        usage()

    verbose = False
    debug = 0
    config = None
    cache_dir = None
    train = None
    pkg_type = None

    for o, a in opts:
        if o in ("-v", "--verbose"):
            verbose = True
        elif o in ("-d", "--debug"):
            debug += 1
        elif o in ('-C', "--cache"):
            cache_dir = a
        elif o in ("-T", "--train"):
            train = a
        elif o in ("--no-delta"):
            pkg_type = Update.PkgFileFullOnly
        else:
            assert False, "unhandled option %s" % o

    config = Configuration.Configuration()
    if train is None:
        train = config.SystemManifest().Train()

    if len(args) != 1:
        usage()

    if args[0] == "check":
        # To see if we have an update available, we
        # call Update.DownloadUpdate.  If we have been
        # given a cache directory, we pass that in; otherwise,
        # we make a temporary directory and use that.  We
        # have to clean up afterwards in that case.

        if cache_dir is None:
            download_dir = tempfile.mkdtemp(prefix="UpdateCheck-",
                                            dir=config.TemporaryDirectory())
            if download_dir is None:
                print >> sys.stderr, "Unable to create temporary directory"
                sys.exit(1)
        else:
            download_dir = cache_dir

        rv = Update.DownloadUpdate(train, download_dir, pkg_type=pkg_type)
        if rv is False:
            if verbose:
                print "No updates available"
            if cache_dir is None:
                Update.RemoveUpdate(download_dir)
            sys.exit(1)
        else:
            diffs = Update.PendingUpdatesChanges(download_dir)
            if diffs is None or len(diffs) == 0:
                print >> sys.stderr, "Strangely, DownloadUpdate says there updates, but PendingUpdates says otherwise"
                sys.exit(1)
            PrintDifferences(diffs)
            if cache_dir is None:
                Update.RemoveUpdate(download_dir)
            sys.exit(0)

    elif args[0] == "update":
        # This will attempt to apply an update.
        # If cache_dir is given, then we will only check that directory,
        # not force a download if it is already there.  If cache_dir is not
        # given, however, then it downloads.  (The reason is that you would
        # want to run "freenas-update -c /foo check" to look for an update,
        # and it will download the latest one as necessary, and then run
        # "freenas-update -c /foo update" if it said there was an update.
        try:
            update_opts, update_args = getopt.getopt(args[1:], "R", "--reboot")
        except getopt.GetoptError as err:
            print str(err)
            usage()

        force_reboot = False
        for o, a in update_opts:
            if o in ("-R", "--reboot"):
                force_reboot = True
            else:
                assert False, "Unhandled option %s" % o

        if cache_dir is None:
            download_dir = tempfile.mkdtemp(prefix="UpdateUpdate-",
                                            dir=config.TemporaryDirectory())
            if download_dir is None:
                print >> sys.stderr, "Unable to create temporary directory"
                sys.exit(1)
            rv = Update.DownloadUpdate(train, download_dir, pkg_type=pkg_type)
            if rv is False:
                if verbose or debug:
                    print >> sys.stderr, "DownloadUpdate returned False"
                sys.exit(1)
        else:
            download_dir = cache_dir

        diffs = Update.PendingUpdatesChanges(download_dir)
        if diffs is None or diffs == {}:
            if verbose:
                print >> sys.stderr, "No updates to apply"
        else:
            if verbose:
                PrintDifferences(diffs)
            try:
                rv = Update.ApplyUpdate(download_dir,
                                        force_reboot=force_reboot)
            except BaseException as e:
                print >> sys.stderr, "Unable to apply update: %s" % str(e)
                sys.exit(1)
            if cache_dir is None:
                Update.RemoveUpdate(download_dir)
            if rv:
                print >> sys.stderr, "System should be rebooted now"
            sys.exit(0)
    else:
        usage()
예제 #17
0
파일: update.py 프로젝트: MrYHM/freenas
    async def update(self, job, attrs=None):
        """
        Downloads (if not already in cache) and apply an update.
        """
        attrs = attrs or {}

        trains = await self.middleware.call('update.get_trains')
        train = attrs.get('train') or trains['selected']
        try:
            result = compare_trains(trains['current'], train)
        except Exception:
            self.logger.warning("Failed to compare trains %r and %r",
                                trains['current'],
                                train,
                                exc_info=True)
        else:
            errors = {
                CompareTrainsResult.NIGHTLY_DOWNGRADE:
                textwrap.dedent("""\
                    You're not allowed to change away from the nightly train, it is considered a downgrade.
                    If you have an existing boot environment that uses that train, boot into it in order to upgrade
                    that train.
                """),
                CompareTrainsResult.MINOR_DOWNGRADE:
                textwrap.dedent("""\
                    Changing minor version is considered a downgrade, thus not a supported operation.
                    If you have an existing boot environment that uses that train, boot into it in order to upgrade
                    that train.
                """),
                CompareTrainsResult.MAJOR_DOWNGRADE:
                textwrap.dedent("""\
                    Changing major version is considered a downgrade, thus not a supported operation.
                    If you have an existing boot environment that uses that train, boot into it in order to upgrade
                    that train.
                """),
            }
            if result in errors:
                raise CallError(errors[result])

        location = await self.middleware.call('update.get_update_location')

        job.set_progress(0, 'Retrieving update manifest')

        handler = UpdateHandler(self, job)

        update = Update.DownloadUpdate(
            train,
            location,
            check_handler=handler.check_handler,
            get_handler=handler.get_handler,
        )
        if update is False:
            raise ValueError('No update available')

        new_manifest = Manifest.Manifest(require_signature=True)
        new_manifest.LoadPath('{}/MANIFEST'.format(location))

        Update.ApplyUpdate(
            location,
            install_handler=handler.install_handler,
        )
        await self.middleware.call('cache.put', 'update.applied', True)

        if attrs.get('reboot'):
            await self.middleware.call('system.reboot', {'delay': 10})
        return True