18625 new disk space information needs improved formatting, wording
18626 disk space computation doesn't account for cache or filesystem overhead
18647 pkg(1) planning output could be improved
--- a/doc/client_api_versions.txt Thu Jul 07 14:54:17 2011 -0700
+++ b/doc/client_api_versions.txt Thu Jul 07 15:06:18 2011 -0700
@@ -1,3 +1,14 @@
+Version 62:
+Incompatible with clients using versions 0-61.
+
+ pkg.client.api.PackageInfo has changed as follows:
+ * the 'fmri' property is now a PkgFmri object instead of a
+ string.
+
+ pkg.client.api.PlanDescription has changed as follows:
+ * get_services() now returns a list of tuples of strings,
+ see pkg.client.api for details.
+
Version 61:
Compatible with clients using version 60.
pkg.client.api.PlanDescription has changed as follows:
--- a/src/client.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/client.py Thu Jul 07 15:06:18 2011 -0700
@@ -44,6 +44,7 @@
try:
import calendar
+ import collections
import datetime
import errno
import fnmatch
@@ -88,7 +89,7 @@
import sys
sys.exit(1)
-CLIENT_API_VERSION = 61
+CLIENT_API_VERSION = 62
PKG_CLIENT_NAME = "pkg"
JUST_UNKNOWN = 0
@@ -888,95 +889,145 @@
plan = api_inst.describe()
a, r, i, c = [], [], [], []
-
for src, dest in plan.get_changes():
if dest is None:
r.append((src, dest))
elif src is None:
i.append((src, dest))
- elif str(src) != str(dest):
+ elif src != dest:
c.append((src, dest))
else:
a.append((src, dest))
- v = plan.get_varcets()
def bool_str(val):
if val:
return _("Yes")
return _("No")
+ status = []
+ varcets = plan.get_varcets()
if "basic" in disp:
- def cond_show(s, v):
+ def cond_show(s1, s2, v):
if v:
- logger.info(s % v)
-
- cond_show(_(" Packages to remove: %6d"), len(r))
- cond_show(_(" Packages to install: %6d"), len(i))
- cond_show(_(" Packages to update: %6d"), len(c))
- cond_show(_(" Variants/facets to change: %6d"), len(v))
-
- bytes = plan.bytes_added
- if bytes:
- logger.info(_("Additional filesystem space needed: %6s"),
- misc.bytes_to_str(bytes))
- logger.info(_(" Filesystem space available: %6s"),
- misc.bytes_to_str(plan.bytes_avail))
-
- if len(v):
- s = " Packages to change: %6d"
+ status.append((s1, s2 % v))
+
+ cond_show(_("Packages to remove:"), "%d", len(r))
+ cond_show(_("Packages to install:"), "%d", len(i))
+ cond_show(_("Packages to update:"), "%d", len(c))
+ cond_show(_("Variants/Facets to change:"), "%d", len(varcets))
+
+ if verbose:
+ # Only show space information in verbose mode.
+ abytes = plan.bytes_added
+ if abytes:
+ status.append((_("Estimated space available:"),
+ misc.bytes_to_str(plan.bytes_avail)))
+ status.append((
+ _("Estimated space to be consumed:"),
+ misc.bytes_to_str(plan.bytes_added)))
+
+ if len(varcets):
+ cond_show(_("Packages to change:"), "%d", len(a))
else:
- s = " Packages to fix: %6d"
- cond_show(s, len(a))
-
- logger.info(_(" Create boot environment: %6s") %
- bool_str(plan.new_be))
+ cond_show(_("Packages to fix:"), "%d", len(a))
+
+ status.append((_("Create boot environment:"),
+ bool_str(plan.new_be)))
if plan.new_be and (verbose or not plan.activate_be):
# Only show activation status if verbose or if new BE
# will not be activated.
- logger.info(_(" Activate boot environment: %6s") %
- bool_str(plan.activate_be))
+ status.append((_("Activate boot environment:"),
+ bool_str(plan.activate_be)))
if not plan.new_be:
- cond_show(_(" Services to restart: %6d"),
+ cond_show(_("Services to change:"), "%d",
len(plan.get_services()))
if "boot-archive" in disp:
- logger.info(_(" Rebuild boot archive: %6s") %
- bool_str(plan.update_boot_archive))
-
- if "variants/facets" in disp and v:
+ status.append((_("Rebuild boot archive:"),
+ bool_str(plan.update_boot_archive)))
+
+ # Right-justify all status strings based on length of longest string.
+ rjust_status = max(len(s[0]) for s in status)
+ rjust_value = max(len(s[1]) for s in status)
+ for s in status:
+ logger.info("%s %s" % (s[0].rjust(rjust_status),
+ s[1].rjust(rjust_value)))
+
+ if status:
+ # Ensure there is a blank line between status information and
+ # remainder.
+ logger.info("")
+
+ if "variants/facets" in disp and varcets:
logger.info(_("Changed variants/facets:"))
- for x in v:
+ for x in varcets:
logger.info(" %s" % x)
if "solver-errors" in disp:
- logger.info(_("Solver dependency errors:"))
+ first = True
for l in plan.get_solver_errors():
+ if first:
+ logger.info(_("Solver dependency errors:"))
+ first = False
logger.info(l)
if "fmris" in disp:
- if len(c) + len(r) + len(i) > 0:
- logger.info(_("Changed fmris:"))
- for src, dest in r + i + c:
- logger.info(" %s -> %s", src, dest)
+ changed = collections.defaultdict(list)
+ for src, dest in itertools.chain(r, i, c):
+ if src and dest:
+ if src.publisher != dest.publisher:
+ pparent = "%s -> %s" % (src.publisher,
+ dest.publisher)
+ else:
+ pparent = dest.publisher
+ pname = dest.pkg_stem
+ pver = "%s -> %s" % (src.fmri.version,
+ dest.fmri.version)
+ elif dest:
+ pparent = dest.publisher
+ pname = dest.pkg_stem
+ pver = "None -> %s" % dest.fmri.version
+ else:
+ pparent = src.publisher
+ pname = src.pkg_stem
+ pver = "%s -> None" % src.fmri.version
+
+ changed[pparent].append((pname, pver))
+
+ if changed:
+ logger.info(_("Changed packages:"))
+ last_parent = None
+ for pparent, pname, pver in (
+ (pparent, pname, pver)
+ for pparent in sorted(changed)
+ for pname, pver in changed[pparent]
+ ):
+ if pparent != last_parent:
+ logger.info(pparent)
+
+ logger.info(" %s" % pname)
+ logger.info(" %s" % pver)
+ last_parent = pparent
+
if len(a):
logger.info(_("Affected fmris:"))
for src, dest in a:
logger.info(" %s", src)
- if "services" in disp:
- if not plan.new_be:
- logger.info(_("Services:"))
- l = plan.get_services()
- if l:
- for a in l:
- logger.info(" %s" % a)
- else:
- logger.info(_(" None"))
+ if "services" in disp and not plan.new_be:
+ last_action = None
+ for action, smf_fmri in plan.get_services():
+ if last_action is None:
+ logger.info("Services:")
+ if action != last_action:
+ logger.info(" %s:" % action)
+ logger.info(" %s" % smf_fmri)
+ last_action = action
if "actions" in disp:
- logger.info("Actions")
+ logger.info("Actions:")
for a in plan.get_actions():
logger.info(" %s" % a)
--- a/src/gui/modules/misc_non_gui.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/gui/modules/misc_non_gui.py Thu Jul 07 15:06:18 2011 -0700
@@ -41,7 +41,7 @@
# The current version of the Client API the PM, UM and
# WebInstall GUIs have been tested against and are known to work with.
-CLIENT_API_VERSION = 60
+CLIENT_API_VERSION = 62
LOG_DIR = "/var/tmp"
LOG_ERROR_EXT = "_error.log"
LOG_INFO_EXT = "_info.log"
--- a/src/modules/api_common.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/api_common.py Thu Jul 07 15:06:18 2011 -0700
@@ -170,7 +170,7 @@
version=version.release,
build_release=version.build_release, branch=version.branch,
packaging_date=version.get_timestamp().strftime("%c"),
- pfmri=str(f))
+ pfmri=f)
def _get_pkg_cat_data(cat, info_needed, actions=None,
--- a/src/modules/client/api.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/client/api.py Thu Jul 07 15:06:18 2011 -0700
@@ -72,7 +72,7 @@
from pkg.client.pkgdefs import *
from pkg.smf import NonzeroExitException
-CURRENT_API_VERSION = 61
+CURRENT_API_VERSION = 62
CURRENT_P5I_VERSION = 1
# Image type constants.
@@ -282,7 +282,7 @@
other platforms, a value of False will allow any image location.
"""
- compatible_versions = set([CURRENT_API_VERSION, 60])
+ compatible_versions = set([CURRENT_API_VERSION])
if version_id not in compatible_versions:
raise apx.VersionException(CURRENT_API_VERSION,
@@ -3189,7 +3189,7 @@
states=states, publisher=pub, version=release,
build_release=build_release, branch=branch,
packaging_date=packaging_date, size=size,
- pfmri=str(pfmri), licenses=licenses,
+ pfmri=pfmri, licenses=licenses,
links=links, hardlinks=hardlinks, files=files,
dirs=dirs, dependencies=dependencies,
description=description))
@@ -4306,7 +4306,8 @@
and 'dest_pi' is the new version of the package it is
being upgraded to."""
- for pp in self.__plan.pkg_plans:
+ for pp in sorted(self.__plan.pkg_plans,
+ key=operator.attrgetter("origin_fmri", "destination_fmri")):
yield (PackageInfo.build_from_fmri(pp.origin_fmri),
PackageInfo.build_from_fmri(pp.destination_fmri))
@@ -4416,24 +4417,24 @@
@property
def bytes_added(self):
- """Returns approximate number of bytes added"""
+ """Estimated number of bytes added"""
return self.__plan.bytes_added
@property
def cbytes_added(self):
- """Returns approximate number of download cache bytes added"""
+ """Estimated number of download cache bytes added"""
return self.__plan.cbytes_added
@property
def bytes_avail(self):
- """Returns approximate number of bytes available in image /"""
+ """Estimated number of bytes available in image /"""
return self.__plan.bytes_avail
@property
def cbytes_avail(self):
- """Returns approximate number of space in download cache"""
+ """Estimated number of bytes available in download cache"""
return self.__plan.cbytes_avail
-
+
def image_create(pkg_client_name, version_id, root, imgtype, is_zone,
cancel_state_callable=None, facets=misc.EmptyDict, force=False,
--- a/src/modules/client/image.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/client/image.py Thu Jul 07 15:06:18 2011 -0700
@@ -289,7 +289,8 @@
def write_cache_path(self):
"""Returns a path to fs that holds write cache - used to
compute whether we have sufficent space for downloads"""
- return self.__user_cache_dir or os.path.join(self.root, IMG_PUB_DIR)
+ return self.__user_cache_dir or \
+ os.path.join(self.imgdir, IMG_PUB_DIR)
@staticmethod
def alloc(*args, **kwargs):
--- a/src/modules/client/imageplan.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/client/imageplan.py Thu Jul 07 15:06:18 2011 -0700
@@ -239,11 +239,13 @@
@property
def services(self):
- """Returns a list of strings describing affected services"""
- return [
- "%s: %s" % (fmri, smf)
- for fmri, smf in self.__actuators.get_services_list()
- ]
+ """Returns a list of string tuples describing affected services
+ (action, SMF FMRI)."""
+ return sorted(
+ ((str(action), str(smf_fmri))
+ for action, smf_fmri in self.__actuators.get_services_list()),
+ key=operator.itemgetter(0, 1)
+ )
@property
def varcets(self):
@@ -1523,23 +1525,59 @@
self.merge_actions()
for p in self.pkg_plans:
- cbytes, bytes = p.get_bytes_added()
- self.__cbytes_added += cbytes
- self.__bytes_added += bytes
+ cpbytes, pbytes = p.get_bytes_added()
+ if p.destination_fmri:
+ mpath = self.image.get_manifest_path(
+ p.destination_fmri)
+ try:
+ # Manifest data is essentially stored
+ # three times (original, cache, catalog).
+ # For now, include this in cbytes_added
+ # since that's closest to where the
+ # download cache is stored.
+ self.__cbytes_added += \
+ os.stat(mpath).st_size * 3
+ except EnvironmentError, e:
+ raise apx._convert_error(e)
+ self.__cbytes_added += cpbytes
+ self.__bytes_added += pbytes
+
+ # Include state directory in cbytes_added for now since it's
+ # closest to where the download cache is stored. (Twice the
+ # amount is used because image state update involves using
+ # a complete copy of existing state.)
+ self.__cbytes_added += \
+ misc.get_dir_size(self.image._statedir) * 2
+
+ # Our slop factor is 25%; overestimating is safer than under-
+ # estimating. This attempts to approximate how much overhead
+ # the filesystem will impose on the operation. Empirical
+ # testing suggests that overhead can vary wildly depending on
+ # average file size, fragmentation, zfs metadata overhead, etc.
+ # For an install of a package such as solaris-small-server into
+ # an image, a 12% difference between actual size and installed
+ # size was found, so this seems safe enough. (And helps account
+ # for any bootarchives, fs overhead, etc.)
+ self.__cbytes_added *= 1.25
+ self.__bytes_added *= 1.25
+
+ # XXX For now, include cbytes_added in bytes_added total; in the
+ # future, this should only happen if they share the same
+ # filesystem.
+ self.__bytes_added += self.__cbytes_added
self.__update_avail_space()
def __update_avail_space(self):
- """Update amount of available space on FS"""
+ """Update amount of available space on FS"""
self.__cbytes_avail = misc.spaceavail(
self.image.write_cache_path)
-
+
self.__bytes_avail = misc.spaceavail(self.image.root)
# if we don't have a full image yet
if self.__cbytes_avail < 0:
self.__cbytes_avail = self.__bytes_avail
-
def evaluate_pkg_plans(self):
"""Internal helper function that does the work of converting
fmri changes into pkg plans."""
@@ -2036,17 +2074,17 @@
# check if we're going to have enough room
# stat fs again just in case someone else is using space...
self.__update_avail_space()
- if self.__cbytes_added > self.__cbytes_avail * 1.2:
+ if self.__cbytes_added > self.__cbytes_avail:
raise api_errors.ImageInsufficentSpace(
self.__cbytes_added,
- self.__cbytes_avail * 1.2,
+ self.__cbytes_avail,
_("Download cache"))
- if self.__bytes_added > self.__bytes_avail * 1.2:
+ if self.__bytes_added > self.__bytes_avail:
raise api_errors.ImageInsufficentSpace(
self.__bytes_added,
- self.__bytes_avail * 1.2,
+ self.__bytes_avail,
_("Root filesystem"))
-
+
# Remove history about manifest/catalog transactions. This
# helps the stats engine by only considering the performance of
# bulk downloads.
@@ -2115,10 +2153,10 @@
# check for available space
self.__update_avail_space()
- if self.__bytes_added > self.__bytes_avail * 1.2:
+ if self.__bytes_added > self.__bytes_avail:
raise api_errors.ImageInsufficentSpace(
self.__bytes_added,
- self.__bytes_avail * 1.2,
+ self.__bytes_avail,
_("Root filesystem"))
#
--- a/src/modules/client/pkgplan.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/client/pkgplan.py Thu Jul 07 15:06:18 2011 -0700
@@ -360,12 +360,13 @@
because they're usually pinned by snapshots"""
def sum_dest_size(a, b):
if b[1]:
- return (a[0] + int(b[1].attrs.get("pkg.csize",0)),
- a[1] + int(b[1].attrs.get("pkg.size",0)))
- else:
- return (a[0], a[1])
+ return (a[0] + int(b[1].attrs.get("pkg.csize" ,0)),
+ a[1] + int(b[1].attrs.get("pkg.size", 0)))
+ return (a[0], a[1])
- return reduce(sum_dest_size, itertools.chain(*self.actions), (0,0))
+ return reduce(sum_dest_size, itertools.chain(*self.actions),
+ (0, 0))
+
def get_xfername(self):
if self.destination_fmri:
return self.destination_fmri.get_name()
--- a/src/modules/lint/engine.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/lint/engine.py Thu Jul 07 15:06:18 2011 -0700
@@ -39,7 +39,7 @@
import sys
PKG_CLIENT_NAME = "pkglint"
-CLIENT_API_VERSION = 60
+CLIENT_API_VERSION = 62
pkg.client.global_settings.client_name = PKG_CLIENT_NAME
class LintEngineException(Exception):
--- a/src/modules/misc.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/misc.py Thu Jul 07 15:06:18 2011 -0700
@@ -1073,7 +1073,13 @@
except OSError:
return -1
-
-
-
-
+def get_dir_size(path):
+ """Return the size (in bytes) of a directory and all of its contents."""
+ try:
+ return sum(
+ os.path.getsize(os.path.join(d, fname))
+ for d, dnames, fnames in os.walk(path)
+ for fname in fnames
+ )
+ except EnvironmentError, e:
+ raise apx._convert_error(e)
--- a/src/modules/server/api.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/modules/server/api.py Thu Jul 07 15:06:18 2011 -0700
@@ -249,7 +249,7 @@
publisher=pub, version=release,
build_release=build_release, branch=branch,
packaging_date=packaging_date, size=size,
- pfmri=str(f), licenses=licenses, links=links,
+ pfmri=f, licenses=licenses, links=links,
hardlinks=hardlinks, files=files, dirs=dirs,
dependencies=dependencies, description=description))
return {
--- a/src/packagemanager.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/packagemanager.py Thu Jul 07 15:06:18 2011 -0700
@@ -4308,8 +4308,7 @@
info = self.api_o.info(pkg_stems, True, frozenset(
[api.PackageInfo.STATE, api.PackageInfo.IDENTITY]))
for info_s in info.get(0):
- pkg_stem = fmri.PkgFmri(info_s.fmri).get_pkg_stem(
- include_scheme = True)
+ pkg_stem = info_s.fmri.get_pkg_stem()
if api.PackageInfo.INSTALLED in info_s.states:
pkg_stem_states[pkg_stem] = \
api.PackageInfo.INSTALLED
--- a/src/pkgdep.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/pkgdep.py Thu Jul 07 15:06:18 2011 -0700
@@ -41,7 +41,7 @@
import pkg.publish.dependencies as dependencies
from pkg.misc import msg, emsg, PipeError
-CLIENT_API_VERSION = 60
+CLIENT_API_VERSION = 62
PKG_CLIENT_NAME = "pkgdepend"
DEFAULT_SUFFIX = ".res"
--- a/src/sysrepo.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/sysrepo.py Thu Jul 07 15:06:18 2011 -0700
@@ -53,7 +53,7 @@
orig_cwd = None
PKG_CLIENT_NAME = "pkg.sysrepo"
-CLIENT_API_VERSION = 60
+CLIENT_API_VERSION = 62
pkg.client.global_settings.client_name = PKG_CLIENT_NAME
# exit codes
--- a/src/tests/pkg5unittest.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/tests/pkg5unittest.py Thu Jul 07 15:06:18 2011 -0700
@@ -127,7 +127,7 @@
# Version test suite is known to work with.
PKG_CLIENT_NAME = "pkg"
-CLIENT_API_VERSION = 60
+CLIENT_API_VERSION = 62
ELIDABLE_ERRORS = [ TestSkippedException, depotcontroller.DepotStateException ]
--- a/src/util/distro-import/importer.py Thu Jul 07 14:54:17 2011 -0700
+++ b/src/util/distro-import/importer.py Thu Jul 07 15:06:18 2011 -0700
@@ -56,7 +56,7 @@
from pkg.misc import emsg
from pkg.portable import PD_LOCAL_PATH, PD_PROTO_DIR, PD_PROTO_DIR_LIST
-CLIENT_API_VERSION = 60
+CLIENT_API_VERSION = 62
PKG_CLIENT_NAME = "importer.py"
pkg.client.global_settings.client_name = PKG_CLIENT_NAME