pkgplan.py revision 1505
409N/A#!/usr/bin/python2.4
50N/A#
50N/A# CDDL HEADER START
50N/A#
50N/A# The contents of this file are subject to the terms of the
50N/A# Common Development and Distribution License (the "License").
50N/A# You may not use this file except in compliance with the License.
50N/A#
50N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
50N/A# or http://www.opensolaris.org/os/licensing.
50N/A# See the License for the specific language governing permissions
50N/A# and limitations under the License.
50N/A#
50N/A# When distributing Covered Code, include this CDDL HEADER in each
50N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
50N/A# If applicable, add the following below this CDDL HEADER, with the
50N/A# fields enclosed by brackets "[]" replaced with your own identifying
50N/A# information: Portions Copyright [yyyy] [name of copyright owner]
50N/A#
50N/A# CDDL HEADER END
50N/A#
50N/A
1191N/A# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
50N/A# Use is subject to license terms.
50N/A
60N/Aimport errno
202N/Aimport itertools
50N/Aimport os
50N/A
1431N/Afrom pkg.client import global_settings
1431N/Alogger = global_settings.logger
1431N/A
1352N/Aimport pkg.actions.directory as directory
72N/Aimport pkg.manifest as manifest
1431N/Afrom pkg.misc import expanddirs, get_pkg_otw_size, EmptyI
50N/A
50N/Aclass PkgPlan(object):
50N/A """A package plan takes two package FMRIs and an Image, and produces the
50N/A set of actions required to take the Image from the origin FMRI to the
66N/A destination FMRI.
135N/A
66N/A If the destination FMRI is None, the package is removed.
66N/A """
50N/A
580N/A def __init__(self, image, progtrack, check_cancelation):
50N/A self.origin_fmri = None
50N/A self.destination_fmri = None
400N/A self.actions = []
583N/A self.__repair_actions = []
400N/A
964N/A self.__origin_mfst = manifest.NullCachedManifest
964N/A self.__destination_mfst = manifest.NullCachedManifest
400N/A self.__legacy_info = {}
50N/A
50N/A self.image = image
400N/A self.__progtrack = progtrack
50N/A
400N/A self.__xfersize = -1
400N/A self.__xferfiles = -1
235N/A
400N/A self.__destination_filters = []
242N/A
580N/A self.check_cancelation = check_cancelation
580N/A
63N/A def __str__(self):
72N/A s = "%s -> %s\n" % (self.origin_fmri, self.destination_fmri)
72N/A
202N/A for src, dest in itertools.chain(*self.actions):
72N/A s += " %s -> %s\n" % (src, dest)
72N/A
72N/A return s
63N/A
1505N/A def propose(self, of, om, df, dm):
1505N/A """Propose origin and dest fmri, manifest"""
1505N/A self.origin_fmri = of
1505N/A self.__origin_mfst = om
1505N/A self.destination_fmri = df
1505N/A self.__destination_mfst = dm
1505N/A if self.destination_fmri:
1505N/A self.__legacy_info["version"] = self.destination_fmri.version
1271N/A
1271N/A def propose_repair(self, fmri, mfst, actions):
1505N/A self.propose(fmri, mfst, fmri, mfst)
1505N/A # self.origin_fmri = None
1505N/A # I'd like a cleaner solution than this; we need to actually
1505N/A # construct a list of actions as things currently are rather than
1505N/A # just re-applying the current set of actions.
1505N/A #
583N/A # Create a list of (src, dst) pairs for the actions to send to
583N/A # execute_repair. src is none in this case since we aren't
583N/A # upgrading, just repairing.
583N/A lst = [(None, x) for x in actions]
1271N/A
583N/A # Only install actions, no update or remove
583N/A self.__repair_actions = lst
583N/A
50N/A def get_actions(self):
235N/A raise NotImplementedError()
235N/A
235N/A def get_nactions(self):
235N/A return len(self.actions[0]) + len(self.actions[1]) + \
235N/A len(self.actions[2])
50N/A
307N/A def update_pkg_set(self, fmri_set):
307N/A """ updates a set of installed fmris to reflect
307N/A proposed new state"""
307N/A
307N/A if self.origin_fmri:
307N/A fmri_set.discard(self.origin_fmri)
307N/A
307N/A if self.destination_fmri:
307N/A fmri_set.add(self.destination_fmri)
1271N/A
838N/A def evaluate(self, old_excludes=EmptyI, new_excludes=EmptyI):
66N/A """Determine the actions required to transition the package."""
50N/A
111N/A # Assume that origin actions are unique, but make sure that
111N/A # destination ones are.
838N/A ddups = self.__destination_mfst.duplicates(new_excludes)
111N/A if ddups:
1352N/A raise RuntimeError(["Duplicate actions", ddups])
111N/A
1271N/A self.actions = self.__destination_mfst.difference(
1271N/A self.__origin_mfst, old_excludes, new_excludes)
50N/A
307N/A # figure out how many implicit directories disappear in this
307N/A # transition and add directory remove actions. These won't
307N/A # do anything unless no pkgs reference that directory in
307N/A # new state....
307N/A
1045N/A # Retrieving origin_dirs first and then checking it for any
1045N/A # entries allows avoiding an unnecessary expanddirs for the
1045N/A # destination manifest when it isn't needed.
1045N/A origin_dirs = expanddirs(self.__origin_mfst.get_directories(
1045N/A old_excludes))
307N/A
1045N/A if origin_dirs:
1045N/A absent_dirs = origin_dirs - \
1045N/A expanddirs(self.__destination_mfst.get_directories(
1045N/A new_excludes))
1045N/A
1045N/A for a in absent_dirs:
1045N/A self.actions[2].append(
1045N/A [directory.DirectoryAction(path=a), None])
307N/A
400N/A # Stash information needed by legacy actions.
400N/A self.__legacy_info["description"] = \
1174N/A self.__destination_mfst.get("pkg.summary",
1174N/A self.__destination_mfst.get("description", "none provided"))
400N/A
583N/A # Add any repair actions to the update list
583N/A self.actions[1].extend(self.__repair_actions)
583N/A
400N/A #
400N/A # We cross a point of no return here, and throw away the origin
1271N/A # and destination manifests; we also delete them from the
964N/A # image cache.
400N/A self.__origin_mfst = None
400N/A self.__destination_mfst = None
400N/A
400N/A def get_legacy_info(self):
400N/A """ Returns information needed by the legacy action to
400N/A populate the SVR4 packaging info. """
400N/A return self.__legacy_info
400N/A
235N/A def get_xferstats(self):
400N/A if self.__xfersize != -1:
400N/A return (self.__xferfiles, self.__xfersize)
235N/A
400N/A self.__xfersize = 0
400N/A self.__xferfiles = 0
235N/A for src, dest in itertools.chain(*self.actions):
235N/A if dest and dest.needsdata(src):
487N/A self.__xfersize += get_pkg_otw_size(dest)
400N/A self.__xferfiles += 1
235N/A
400N/A return (self.__xferfiles, self.__xfersize)
235N/A
235N/A def will_xfer(self):
235N/A nf, nb = self.get_xferstats()
235N/A if nf > 0:
235N/A return True
235N/A else:
235N/A return False
235N/A
235N/A def get_xfername(self):
235N/A if self.destination_fmri:
235N/A return self.destination_fmri.get_name()
235N/A if self.origin_fmri:
235N/A return self.origin_fmri.get_name()
235N/A return None
289N/A
50N/A def preexecute(self):
388N/A """Perform actions required prior to installation or removal of
388N/A a package.
135N/A
66N/A This method executes each action's preremove() or preinstall()
66N/A methods, as well as any package-wide steps that need to be taken
66N/A at such a time.
66N/A """
111N/A
202N/A for src, dest in itertools.chain(*self.actions):
72N/A if dest:
136N/A dest.preinstall(self, src)
66N/A else:
136N/A src.preremove(self)
66N/A
481N/A def download(self):
481N/A """Download data for any actions that need it."""
1191N/A self.__progtrack.download_start_pkg(self.get_xfername())
1191N/A mfile = self.image.transport.multi_file(self.destination_fmri,
580N/A self.__progtrack, self.check_cancelation)
1191N/A
1448N/A if mfile is None:
1191N/A self.__progtrack.download_end_pkg()
1191N/A return
1191N/A
481N/A for src, dest in itertools.chain(*self.actions):
1191N/A if dest and dest.needsdata(src):
1191N/A mfile.add_action(dest)
481N/A
1191N/A mfile.wait_files()
400N/A self.__progtrack.download_end_pkg()
235N/A
237N/A def gen_install_actions(self):
202N/A for src, dest in self.actions[0]:
237N/A yield src, dest
289N/A
237N/A def gen_removal_actions(self):
237N/A for src, dest in self.actions[2]:
237N/A yield src, dest
289N/A
237N/A def gen_update_actions(self):
202N/A for src, dest in self.actions[1]:
237N/A yield src, dest
237N/A
237N/A def execute_install(self, src, dest):
237N/A """ perform action for installation of package"""
237N/A try:
237N/A dest.install(self, src)
237N/A except Exception, e:
1431N/A logger.error("Action install failed for '%s' (%s):\n "
1431N/A "%s: %s" % (dest.attrs.get(dest.key_attr, id(dest)),
237N/A self.destination_fmri.get_pkg_stem(),
384N/A e.__class__.__name__, e))
237N/A raise
202N/A
237N/A def execute_update(self, src, dest):
1505N/A """ handle action updates"""
237N/A try:
237N/A dest.install(self, src)
237N/A except Exception, e:
1431N/A logger.error("Action upgrade failed for '%s' (%s):\n "
1431N/A "%s: %s" % (dest.attrs.get(dest.key_attr, id(dest)),
237N/A self.destination_fmri.get_pkg_stem(),
384N/A e.__class__.__name__, e))
237N/A raise
59N/A
237N/A def execute_removal(self, src, dest):
237N/A """ handle action removals"""
237N/A try:
237N/A src.remove(self)
237N/A except Exception, e:
1431N/A logger.error("Action removal failed for '%s' (%s):\n "
1431N/A "%s: %s" % (src.attrs.get(src.key_attr, id(src)),
237N/A self.origin_fmri.get_pkg_stem(),
384N/A e.__class__.__name__, e))
237N/A raise
289N/A
66N/A def postexecute(self):
400N/A """Perform actions required after install or remove of a pkg.
135N/A
66N/A This method executes each action's postremove() or postinstall()
66N/A methods, as well as any package-wide steps that need to be taken
66N/A at such a time.
66N/A """
66N/A # record that package states are consistent
202N/A for src, dest in itertools.chain(*self.actions):
72N/A if dest:
136N/A dest.postinstall(self, src)
66N/A else:
136N/A src.postremove(self)
66N/A
1271N/A # For an uninstall or an upgrade, remove the installation
481N/A # turds from the origin's directory.
66N/A # XXX should this just go in preexecute?
481N/A if self.destination_fmri == None or self.origin_fmri != None:
1352N/A self.image.set_pkg_state(self.origin_fmri,
1352N/A self.image.PKG_STATE_UNINSTALLED)
66N/A
135N/A try:
135N/A os.unlink("%s/pkg/%s/filters" % (
135N/A self.image.imgdir,
135N/A self.origin_fmri.get_dir_path()))
289N/A except EnvironmentError, e:
135N/A if e.errno != errno.ENOENT:
135N/A raise
111N/A
66N/A if self.destination_fmri != None:
1352N/A self.image.set_pkg_state(self.destination_fmri,
1352N/A self.image.PKG_STATE_INSTALLED)
60N/A
111N/A # Save the filters we used to install the package, so
111N/A # they can be referenced later.
400N/A if self.__destination_filters:
111N/A f = file("%s/pkg/%s/filters" % \
111N/A (self.image.imgdir,
111N/A self.destination_fmri.get_dir_path()), "w")
111N/A
111N/A f.writelines([
242N/A myfilter + "\n"
400N/A for myfilter, code in \
400N/A self.__destination_filters
111N/A ])
111N/A f.close()
235N/A