usr/src/lib/install_ict/cleanup_cpio_install.py
author Drew Fisher <drew.fisher@oracle.com>
Fri, 29 Apr 2011 09:05:38 -0700
changeset 1099 9685582c2e8a
parent 1055 26f4df2e408e
child 1161 5c1b6d445efc
permissions -rw-r--r--
7039499 Need to change slim_source for IPS API_VERSION change of 57 for snv_165 7040051 Unit test failure in test_p5i.py even after 7013385 is fixed.

#!/usr/bin/python
#
# 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) 2011, Oracle and/or its affiliates. All rights reserved.
#

import os
import shutil
import sys

import pkg.client.api as api
import pkg.client.api_errors as api_errors
import pkg.client.progress as progress
import solaris_install.ict as ICT

from stat import S_IREAD, S_IRGRP, S_IROTH

from solaris_install.data_object import ObjectNotFoundError
from solaris_install.transfer.info import Args
from solaris_install.transfer.info import CPIOSpec
from solaris_install.transfer.info import IPSSpec
from solaris_install.transfer.ips import RedirectIPSTrans
from solaris_install.transfer.info import Software


class CleanupCPIOInstall(ICT.ICTBaseClass):
    '''
       The CleanupCPIOInstall checkpoint performs clean up on the system after
       a live CD install. This checkpoint performs the following when the
       the execute method is called:
       - Creates /etc/mnttab if it doesn't already exist and chmods it to the
         correct values
       - Removes files and directories that are not needed by the installed
         system
       - Remove miscellanous directory trees used as work areas during
         installation
       - Relocate configuration files from the save directory
       - Remove install-specific packages that are not needed by the installed
         system
       - Reset pkg(1) image UUID for preferred publisher
    '''
    def __init__(self, name):
        '''Initializes the class
           Parameters:
               -name - this arg is required by the AbstractCheckpoint
        '''
        super(CleanupCPIOInstall, self).__init__(name)

        self.cleanup_list = ['.livecd', '.volsetid', '.textinstall',
                             'etc/sysconfig/language', '.liveusb',
                             'a', 'bootcd_microroot']

    def execute(self, dry_run=False):
        '''
            The AbstractCheckpoint class requires this method
            in sub-classes.

            Parameters:
            - the dry_run keyword paramater. The default value is False.
              If set to True, the log message describes the checkpoint tasks.

            Returns:
            - Nothing
              On failure, errors raised are managed by the engine.
        '''

        # Variable used to store the result of a pkg api plan_uninstall
        pkg_rval = False

        # List to hold requested packages to be removed
        pkg_rm_node = []

        # Dictionary and lists used to hold the results of package info check
        # for packages in the list of packages requested for removal
        pkg_ret_info = {}
        pkgs_found = []
        pkgs_notfound = []
        pkgs_illegal = []

        # Variables used to check for valid packages
        info_local = True
        info_needed = api.PackageInfo.ALL_OPTIONS

        # The software node containing packages, directories and files that
        # need removal
        soft_list = None

        self.logger.debug('ICT current task: cleanup install')

        # parse_doc populates variables necessary to execute the checkpoint
        self.parse_doc()

        # Get the list of install specific items that need to be removed.
        # Check for both IPS packages and files and directories
        # If no items are designated, an empty list is returned.
        soft_list = self.doc.get_descendants(name=self.name,
                                             class_type=Software)

        if soft_list:
            soft_node = soft_list[0]

            # Get the list of install specific packages that need to
            # be removed.
            try:
                pkg_rm_node = soft_node.get_children(class_type=IPSSpec)[0]
            except IndexError, err:
                # No IPS packages have been specified
                pass

            # Get the list of install specific files that need to be removed.
            try:
                file_rm_node = soft_node.get_children(class_type=CPIOSpec)[0]
                self.cleanup_list.extend(file_rm_node.contents)
            except IndexError, err:
                # No additional CPIO contents have been specified
                pass

        # Create the mnttab file
        self.logger.debug('Executing: Create /etc/mnttab file')
        if not dry_run:
            mnttab = os.path.join(self.target_dir, ICT.MNTTAB)
            mnttab_dir = os.path.dirname(mnttab)

            # Create the directory that holds the mnttab file,
            # if it does not exist.
            if not os.access(mnttab_dir, os.F_OK):
                os.makedirs(mnttab_dir)

            # Create the mnttab file if it does not exist.
            if not os.access(mnttab, os.F_OK):
                open(mnttab, 'w').close()
                os.chmod(mnttab, S_IREAD | S_IRGRP | S_IROTH)

        # Remove and miscellaneous directories used as work areas
        self.logger.debug('Executing: Remove miscellaneous work directories '
                          'from /var/tmp')
        for root, dirs, files in os.walk(os.path.join(self.target_dir,
                                         'var/tmp'), topdown=False):
            for name in files:
                self.logger.debug('Removing %s', name)
                if not dry_run:
                    os.unlink(os.path.join(root, name))

            for work_dir in dirs:
                self.logger.debug('Removing %s', work_dir)
                if not dry_run:
                    os.rmdir(os.path.join(root, work_dir))

        self.logger.debug('Executing: Remove miscellaneous work directories '
                          'from /mnt')
        for root, dirs, files in os.walk(os.path.join(self.target_dir,
                                         'mnt'), topdown=False):
            for name in files:
                self.logger.debug('Removing %s', name)
                if not dry_run:
                    os.unlink(os.path.join(root, name))
            for work_dir in dirs:
                self.logger.debug('Removing %s', work_dir)
                if not dry_run:
                    os.rmdir(os.path.join(root, work_dir))

        # Relocate configuration files from the save directory
        savedir = os.path.join(self.target_dir, 'save')
        if os.path.exists(savedir):
            self.logger.debug('Executing: Relocate configuration files')
            for root, dirs, files in os.walk(savedir, topdown=False):
                if not files:
                    continue

                target = root.replace('/save', '')
                if not dry_run:
                    if not os.access(target, os.F_OK):
                        os.makedirs(target, 0755)

                for name in files:
                    move_file = os.path.join(root, name)
                    self.logger.debug('Moving %s to %s', move_file, target)
                    if not dry_run:
                        if os.access(os.path.join(target, name), os.F_OK):
                            self.logger.debug('%s exists at %s --skipping',
                                    name, target)
                        else:
                            shutil.copy2(move_file, target)

        if not dry_run:
            try:
                api_inst = api.ImageInterface(self.target_dir,
                               ICT.CLIENT_API_VERSION,
                               progress.CommandLineProgressTracker(),
                               None,
                               ICT.PKG_CLIENT_NAME)

            except api_errors.VersionException, ips_err:
                raise ValueError("The IPS API version specified, "
                                 + str(ips_err.received_version) +
                                 " does not agree with "
                                 "the expected version, "
                                 + str(ips_err.expected_version))

        # Remove install-specific packages that are not needed
        if pkg_rm_node and len(pkg_rm_node.contents) > 0:
            self.logger.debug("Executing: Remove unneeded install-specific "
                              "packages")

            # Check that all of the packages are valid packages
            # before trying to uninstall them
            self.logger.debug('Validating the packages to be uninstalled '
                '%s', pkg_rm_node.contents)

            if not dry_run:
                try:
                    pkg_ret_info = api_inst.info(pkg_rm_node.contents,
                                                 info_local,
                                                 info_needed)
                except api_errors.NoPackagesInstalledException:
                    self.logger.debug("no packages from the uninstall list "
                                      "are installed")

                if pkg_ret_info:
                    pkgs_found = pkg_ret_info[api.ImageInterface.INFO_FOUND]
                    pkgs_notfound = pkg_ret_info[
                                    api.ImageInterface.INFO_MISSING]
                    pkgs_illegal = pkg_ret_info[
                                   api.ImageInterface.INFO_ILLEGALS]

                    for notfound in pkgs_notfound:
                        self.logger.debug("'%s' is not installed - skipping"
                                          % notfound)
                        pkg_rm_node.contents.remove(notfound)

                    for illegal in pkgs_illegal:
                        self.logger.debug("'%s' is not a legal package for "
                                          "this install image - skipping"
                                          % illegal)
                        pkg_rm_node.contents.remove(illegal)

            # Uninstall the packages
            if not pkgs_found:
                self.logger.debug('No packages to uninstall')
            else:
                self.logger.debug('Uninstalling the packages...')
                self.logger.debug('%s', pkg_rm_node.contents)
                if not dry_run:
                    # Reset the value to false
                    pkg_rval = False
                    try:
                        pkg_args = pkg_rm_node.get_first_child(
                                       Args.ARGS_LABEL, Args)
                    except ObjectNotFoundError, err:
                        # No package arguments have been defined
                        pass

                    if pkg_args:
                        if "recursive_removal" in pkg_args.arg_dict:
                            pkg_rval = api_inst.plan_uninstall(
                                pkg_list=pkg_rm_node.contents,
                                recursive_removal=pkg_args.arg_dict.pop(
                                "recursive_removal", **pkg_args.arg_dict))
                        else:
                            pkg_rval = api_inst.plan_uninstall(
                                pkg_list=pkg_rm_node.contents,
                                recursive_removal=False, **pkg_args.arg_dict)
                    else:
                        pkg_rval = api_inst.plan_uninstall(
                            pkg_list=pkg_rm_node.contents,
                            recursive_removal=False)

                # Redirect stdout and stderr from the pkg image in order
                # to capture the command line output from the pkg
                # progress tracker into the transfer logs.
                if pkg_rval:
                    tmp_stdout = sys.stdout
                    tmp_stderr = sys.stderr
                    sys.stdout = sys.stderr = RedirectIPSTrans(self.logger)

                    api_inst.prepare()
                    api_inst.execute_plan()
                    api_inst.reset()

                    # Release stdout and stderr
                    sys.stdout = tmp_stdout
                    sys.stderr = tmp_stderr
                else:
                    self.logger.debug('Unable to uninstall install specific '
                                      'packages')

        # Reset the pkg(1) image UUID to the preferred publisher
        self.logger.debug('Executing: Setting the UUID to the preferred '
                          'publisher')
        if not dry_run:
            publisher = api_inst.get_highest_ranked_publisher()
            publisher.reset_client_uuid()

        # Remove the files and directories in the cleanup_list
        self.logger.debug('Executing: Cleanup of %s', self.cleanup_list)
        for cleanup_name in self.cleanup_list:
            cleanup = os.path.join(self.target_dir, cleanup_name)
            self.logger.debug("Removing %s", cleanup)
            if not dry_run:
                if os.access(cleanup, os.F_OK):
                    if os.path.isfile(cleanup):
                        os.unlink(cleanup)
                    else:
                        os.rmdir(cleanup)

    def get_progress_estimate(self):
        '''
            The AbstractCheckpoint class requires this method
            in sub-classes.

            This returns an estimate of how long the execute() method
            will take to run.
        '''
        #XXXThis needs to be determined more accurately
        return 60