pkgplan.py revision 384
50N/A#!/usr/bin/python
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
289N/A# Copyright 2008 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
72N/Aimport pkg.manifest as manifest
119N/Aimport pkg.client.filelist as filelist
307N/Aimport pkg.actions.directory as directory
384N/Afrom pkg.misc import msg
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
235N/A def __init__(self, image, progtrack):
50N/A self.origin_fmri = None
50N/A self.destination_fmri = None
72N/A self.origin_mfst = manifest.null
72N/A self.destination_mfst = manifest.null
50N/A
50N/A self.image = image
235N/A self.progtrack = progtrack
50N/A
50N/A self.actions = []
50N/A
235N/A self.xfersize = -1
235N/A self.xferfiles = -1
235N/A
242N/A self.origin_filters = []
242N/A self.destination_filters = []
242N/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
242N/A def propose_destination(self, fmri, mfst):
50N/A self.destination_fmri = fmri
242N/A self.destination_mfst = mfst
50N/A
195N/A if self.image.install_file_present(fmri):
59N/A raise RuntimeError, "already installed"
59N/A
242N/A def propose_removal(self, fmri, mfst):
66N/A self.origin_fmri = fmri
242N/A self.origin_mfst = mfst
66N/A
195N/A if not self.image.install_file_present(fmri):
66N/A raise RuntimeError, "not installed"
66N/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)
307N/A
111N/A def evaluate(self, filters = []):
66N/A """Determine the actions required to transition the package."""
50N/A # if origin unset, determine if we're dealing with an previously
50N/A # installed version or if we're dealing with the null package
66N/A #
66N/A # XXX Perhaps make the pkgplan creator make this explicit, so we
66N/A # don't have to check?
50N/A f = None
72N/A if not self.origin_fmri:
258N/A f = self.image.older_version_installed(
258N/A self.destination_fmri)
258N/A if f:
59N/A self.origin_fmri = f
72N/A self.origin_mfst = self.image.get_manifest(f)
50N/A
111N/A self.destination_filters = filters
111N/A
111N/A # Try to load the filter used for the last install of the
111N/A # package.
111N/A self.origin_filters = []
111N/A if self.origin_fmri:
111N/A try:
111N/A f = file("%s/pkg/%s/filters" % \
111N/A (self.image.imgdir,
111N/A self.origin_fmri.get_dir_path()), "r")
289N/A except EnvironmentError, e:
111N/A if e.errno != errno.ENOENT:
111N/A raise
111N/A else:
111N/A self.origin_filters = [
111N/A (l.strip(), compile(
111N/A l.strip(), "<filter string>", "eval"))
111N/A for l in f.readlines()
111N/A ]
111N/A
111N/A self.destination_mfst.filter(self.destination_filters)
111N/A self.origin_mfst.filter(self.origin_filters)
111N/A
111N/A # Assume that origin actions are unique, but make sure that
111N/A # destination ones are.
111N/A ddups = self.destination_mfst.duplicates()
111N/A if ddups:
111N/A raise RuntimeError, ["Duplicate actions", ddups]
111N/A
72N/A self.actions = self.destination_mfst.difference(
72N/A self.origin_mfst)
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
307N/A tmpset = set()
307N/A
307N/A for a in self.origin_mfst.actions:
307N/A tmpset.update(a.directory_references())
307N/A
307N/A absent_dirs = self.image.expanddirs(tmpset)
307N/A
307N/A tmpset = set()
307N/A
307N/A for a in self.destination_mfst.actions:
307N/A tmpset.update(a.directory_references())
307N/A
307N/A absent_dirs.difference_update(self.image.expanddirs(tmpset))
307N/A
307N/A for a in absent_dirs:
307N/A self.actions[2].append([directory.DirectoryAction(path=a), None])
307N/A
237N/A # over the list of update actions, check for any that are the
237N/A # target of hardlink actions, and add the renewal of those hardlinks
237N/A # to the install set
237N/A link_actions = self.image.get_link_actions()
237N/A
237N/A # iterate over copy since we're appending to list
237N/A
237N/A for a in self.actions[1][:]:
237N/A if a[1].name == "file" and a[1].attrs["path"] in link_actions:
237N/A la = link_actions[a[1].attrs["path"]]
237N/A self.actions[1].extend([(a, a) for a in la])
237N/A
235N/A def get_xferstats(self):
235N/A if self.xfersize != -1:
235N/A return (self.xferfiles, self.xfersize)
235N/A
235N/A self.xfersize = 0
235N/A self.xferfiles = 0
235N/A for src, dest in itertools.chain(*self.actions):
235N/A if dest and dest.needsdata(src):
235N/A self.xfersize += \
235N/A int(dest.attrs.get("pkg.size", 0))
235N/A self.xferfiles += 1
235N/A
235N/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):
66N/A """Perform actions required prior to installation or removal of 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 """
119N/A flist = None
119N/A flist_supported = True
119N/A
235N/A if flist_supported:
235N/A self.progtrack.download_start_pkg(self.get_xfername())
235N/A
50N/A # retrieval step
66N/A if self.destination_fmri == None:
195N/A self.image.remove_install_file(self.origin_fmri)
59N/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
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
119N/A if dest and dest.needsdata(src) and flist_supported:
119N/A
119N/A if flist and flist.is_full():
119N/A try:
119N/A flist.get_files()
235N/A self.progtrack.download_add_progress(flist.get_nfiles(), flist.get_nbytes())
119N/A except filelist.FileListException:
119N/A flist_supported = False
119N/A flist = None
119N/A continue
119N/A
119N/A flist = None
119N/A
119N/A if flist is None:
119N/A flist = filelist.FileList(
119N/A self.image,
119N/A self.destination_fmri)
119N/A
119N/A flist.add_action(dest)
119N/A
119N/A
119N/A # Get any remaining files
119N/A if flist:
119N/A try:
119N/A flist.get_files()
235N/A self.progtrack.download_add_progress(flist.get_nfiles(), flist.get_nbytes())
119N/A except filelist.FileListException:
119N/A pass
119N/A flist = None
119N/A
235N/A if flist_supported:
235N/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:
384N/A msg("Action install failed for '%s' (%s):\n %s: %s" % \
237N/A (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):
237N/A """ handle action updates"""
237N/A try:
237N/A dest.install(self, src)
237N/A except Exception, e:
384N/A msg("Action upgrade failed for '%s' (%s):\n %s: %s" % \
237N/A (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:
384N/A msg("Action removal failed for '%s' (%s):\n %s: %s" % \
237N/A (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):
66N/A """Perform actions required after installation or removal of a package.
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
111N/A # In the case of an upgrade, remove the installation turds from
111N/A # the origin's directory.
66N/A # XXX should this just go in preexecute?
66N/A if self.origin_fmri != None and self.destination_fmri != None:
195N/A self.image.remove_install_file(self.origin_fmri)
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:
195N/A self.image.add_install_file(self.destination_fmri)
60N/A
111N/A # Save the filters we used to install the package, so
111N/A # they can be referenced later.
111N/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"
242N/A for myfilter, code in self.destination_filters
111N/A ])
111N/A f.close()
235N/A