src/checkforupdates.py
author Edward Pilatowicz <edward.pilatowicz@oracle.com>
Sat, 07 May 2011 00:25:10 -0700
changeset 2339 aa5954c06b9d
parent 2092 0ef66bf272d3
child 2415 2df3e2e5922f
permissions -rw-r--r--
16148 need linked image support for zones, phase 1 16568 zoneadm install can create out of sync zones if entire has been removed

#!/usr/bin/python2.6
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
#

import getopt
import gettext
import locale
import os
import sys

import pkg.client.api_errors as api_errors
import pkg.client.progress as progress
import pkg.gui.enumerations as enumerations
import pkg.gui.misc_non_gui as nongui_misc
import pkg.misc as misc
import pkg.nrlock as nrlock
from cPickle import UnpicklingError
from pkg.client import global_settings

logger = global_settings.logger

# Put _() in the global namespace
import __builtin__
__builtin__._ = gettext.gettext

IMAGE_DIRECTORY_DEFAULT = "/"   # Image default directory
IMAGE_DIR_COMMAND = "svcprop -p update/image_dir svc:/application/pkg/update"

PKG_CLIENT_NAME = "updatemanager"
CACHE_VERSION =  3
CACHE_NAME = ".last_refresh_cache"

class CheckForUpdates:
        def __init__(self, image_directory, application_path, nice, check_all,
            check_cache):
                global_settings.client_name = nongui_misc.get_um_name()
                self.api_lock = nrlock.NRLock()
                self.image_dir_arg = image_directory
                if self.image_dir_arg == None:
                        self.image_dir_arg = nongui_misc.get_image_path()
                self.application_path = application_path
                self.nice = nice
                self.check_all = check_all
                self.check_cache_only = check_cache
                try:
                        self.application_dir = os.environ["PACKAGE_MANAGER_ROOT"]
                except KeyError:
                        self.application_dir = "/"
                misc.setlocale(locale.LC_ALL, "")

                self.progress_tracker = progress.NullProgressTracker()
                self.api_obj = None
                self.return_status = enumerations.UPDATES_UNDETERMINED
                self.pylintstub = None

                # Check Updates - by default check all
                if self.check_all:
                        self.api_obj = self.__get_api_obj()
                        self.__check_for_updates()
                elif self.check_cache_only:
                        self.api_obj = self.__get_api_obj()
                        ret = self.__check_for_updates_cache_only()
                        if ret == enumerations.UPDATES_UNDETERMINED:
                                self.__send_return(enumerations.UPDATES_UNDETERMINED)
                
        def __get_api_obj(self):
                if self.api_obj == None:
                        api_obj = nongui_misc.get_api_object(self.image_dir_arg,
                            self.progress_tracker)
                return api_obj

        def __check_for_updates_cache_only(self):
                if self.nice:
                        os.nice(20)

                if self.api_obj == None:
                        return enumerations.UPDATES_UNDETERMINED

                ret = self.__check_last_refresh()
                if ret == enumerations.UPDATES_AVAILABLE:
                        if debug:
                                print >> sys.stderr, "From cache: Updates Available"
                        self.__send_return(ret)
                elif ret == enumerations.NO_UPDATES_AVAILABLE:
                        if debug:
                                print >> sys.stderr, \
                                        "From cache: No Updates Available"
                        self.__send_return(ret)
                elif debug:
                        print >> sys.stderr, "From cache: Updates Undetermined"
                return ret

        def __check_for_updates(self):
                ret = self.__check_for_updates_cache_only()
                if ret != enumerations.UPDATES_UNDETERMINED:
                        return
                if debug:
                        print >> sys.stderr, \
                                "Checking image for updates..."
                if self.api_obj == None:
                        self.__send_return(enumerations.UPDATES_UNDETERMINED)
                        return
                try:
                        #
                        # Since this program is intended to primarily be a
                        # helper for the gui components, and since the gui
                        # components are currently unaware of child images,
                        # we'll limit the available update check we're about
                        # to do to just the parent image.  If we didn't do
                        # this we could end up in a situation where the parent
                        # has no available updates, but a child image does,
                        # and then the gui (which is unaware of children)
                        # would show that no updates are available to the
                        # parent.
                        #
                        for pd in self.api_obj.gen_plan_update(
                            refresh_catalogs=True, noexecute=True,
                            force=True, li_ignore=[]):
                                continue
                        stuff_to_do = not self.api_obj.planned_nothingtodo()
                except api_errors.CatalogRefreshException, cre:
                        crerr = nongui_misc.get_catalogrefresh_exception_msg(cre)
                        if debug:
                                print >> sys.stderr, "Exception occurred: %s" % crerr
                        logger.error(crerr)
                        self.__send_return(enumerations.UPDATES_UNDETERMINED)
                        return
                except api_errors.ApiException, e:
                        err = str(e)
                        if debug:
                                print >> sys.stderr, "Exception occurred: %s" % err
                        logger.error(err)
                        self.__send_return(enumerations.UPDATES_UNDETERMINED)
                        return

                self.__dump_updates_available(stuff_to_do)
                if stuff_to_do:
                        if debug:
                                print >> sys.stderr, "From image: Updates Available"
                        self.__send_return(enumerations.UPDATES_AVAILABLE)
                else:
                        if debug:
                                print >> sys.stderr, "From image: No Updates Available"
                        self.__send_return(enumerations.NO_UPDATES_AVAILABLE)

        def __send_return(self, status):
                self.return_status = status

        def __check_last_refresh(self):
                cache_dir = nongui_misc.get_cache_dir(self.api_obj)
                if not cache_dir:
                        return enumerations.UPDATES_UNDETERMINED
                try:
                        info = nongui_misc.read_cache_file(os.path.join(
                            cache_dir, CACHE_NAME + '.cpl'))
                        if len(info) == 0:
                                if debug:
                                        print >> sys.stderr, "No cache"
                                return enumerations.UPDATES_UNDETERMINED
                        # pylint: disable-msg=E1103
                        if info.get("version") != CACHE_VERSION:
                                if debug:
                                        print >> sys.stderr, "Cache version mismatch: %s"\
                                            % (info.get("version") + " " + CACHE_VERSION)
                                return enumerations.UPDATES_UNDETERMINED
                        if info.get("os_release") != os.uname()[2]:
                                if debug:
                                        print >> sys.stderr, "OS release mismatch: %s"\
                                            % (info.get("os_release") + " " + \
                                            os.uname()[2])
                                return enumerations.UPDATES_UNDETERMINED
                        if info.get("os_version") != os.uname()[3]:
                                if debug:
                                        print >> sys.stderr, "OS version mismatch: %s"\
                                            % (info.get("os_version") + " " + \
                                            os.uname()[3])
                                return enumerations.UPDATES_UNDETERMINED
                        old_publishers = info.get("publishers")
                        count = 0
                        for p in self.api_obj.get_publishers():
                                if p.disabled:
                                        continue
                                try:
                                        if old_publishers[p.prefix] != p.last_refreshed:
                                                return enumerations.UPDATES_UNDETERMINED
                                except KeyError:
                                        return enumerations.UPDATES_UNDETERMINED
                                count += 1

                        if count != len(old_publishers):
                                return enumerations.UPDATES_UNDETERMINED
                        n_updates = 0
                        n_installs = 0
                        n_removes = 0
                        if info.get("updates_available"):
                                n_updates = info.get("updates")
                                n_installs = info.get("installs")
                                n_removes = info.get("removes")
                        # pylint: enable-msg=E1103
                        if self.check_cache_only:
                                print "n_updates: %d\nn_installs: %d\nn_removes: %d" % \
                                        (n_updates, n_installs, n_removes)
                        if (n_updates + n_installs + n_removes) > 0:
                                return enumerations.UPDATES_AVAILABLE
                        else:
                                return enumerations.NO_UPDATES_AVAILABLE

                except (UnpicklingError, IOError):
                        return enumerations.UPDATES_UNDETERMINED

        def __dump_updates_available(self, stuff_to_do):
                cache_dir = nongui_misc.get_cache_dir(self.api_obj)
                if not cache_dir:
                        return
                publisher_list = {}
                for p in self.api_obj.get_publishers():
                        if p.disabled:
                                continue
                        publisher_list[p.prefix] = p.last_refreshed
                n_installs = 0
                n_removes = 0
                n_updates = 0
                plan_desc = self.api_obj.describe()
                if plan_desc:
                        plan = plan_desc.get_changes()
                        for pkg_plan in plan:
                                orig = pkg_plan[0]
                                dest = pkg_plan[1]
                                if orig and dest:
                                        n_updates += 1
                                elif not orig and dest:
                                        n_installs += 1
                                elif orig and not dest:
                                        n_removes += 1
                dump_info = {}
                dump_info["version"] = CACHE_VERSION
                dump_info["os_release"] = os.uname()[2]
                dump_info["os_version"] = os.uname()[3]
                dump_info["updates_available"] = stuff_to_do
                dump_info["publishers"] = publisher_list
                dump_info["updates"] = n_updates
                dump_info["installs"] = n_installs
                dump_info["removes"] = n_removes

                try:
                        nongui_misc.dump_cache_file(os.path.join(
                            cache_dir, CACHE_NAME + '.cpl'), dump_info)
                except IOError, e:
                        err = str(e)
                        if debug:
                                print >> sys.stderr, "Failed to dump cache: %s" % err
                        logger.error(err)
                return

###############################################################################
#-----------------------------------------------------------------------------#
# Main
#-----------------------------------------------------------------------------#

def main():
        sys.exit(checkforupdates.return_status)
        return 0

if __name__ == '__main__':
        misc.setlocale(locale.LC_ALL, "")
        gettext.install("pkg", "/usr/share/locale")
        debug = False
        set_nice = False
        set_check_all = True
        set_check_cache = False
        image_dir = "/"
        try:
                opts, pargs = getopt.getopt(sys.argv[1:], "hdnacR:",
                    ["help", "debug", "nice", "checkupdates-all", "checkupdates-cache",
                    "image-dir="])
        except getopt.GetoptError, oex:
                print >> sys.stderr, \
                        ("Usage: illegal option -- %s, for help use -h or --help" %
                            oex.opt )
                sys.exit(enumerations.UPDATES_UNDETERMINED)
        for opt, arg in opts:
                if opt in ("-h", "--help"):
                        print >> sys.stderr, """\n\
Use -h (--help) to print out help.
Use -d (--debug) to run in debug mode.
Use -n (--nice) to run at nice level 20.
Use -c (--checkupdates-cache) to check for updates from cache only (output results to stdout).
Use -R (--image-dir) to specify image directory (defaults to '/')"""
                        sys.exit(0)
                elif opt in ( "-n", "--nice"):
                        set_nice = True
                elif opt in ("-d", "--debug"):
                        debug = True
                elif opt in ( "-c", "--checkupdates-cache"):
                        set_check_cache = True
                        set_check_all = False
                elif opt in ("-R", "--image-dir"):
                        image_dir = arg

        if os.path.isabs(sys.argv[0]):
                app_path = sys.argv[0]
        else:
                cmd = os.path.join(os.getcwd(), sys.argv[0])
                app_path = os.path.realpath(cmd)

        checkforupdates = CheckForUpdates(image_dir, app_path, set_nice,
            set_check_all, set_check_cache)

        main()