735 install -n uninformative
1118 pkg list -a is way way too slow
1192 ipkg brand is broken in RC0 -- config.xml/platform.xml out of sync with Nevada
1193 snapshot/clone logic in client.py declares a mulligan
1201 manifest filters don't seem to be working properly, can't pkg verify a zone
1206 driver.py has some lurking syntactic issues
1242 ipkg brand: Add NFS and autofs to list of packages which zones deliver by default
1243 modify test cases to reflect 1162 (image-update should refresh...)
--- a/src/brand/config.xml Wed Apr 16 14:26:36 2008 -0700
+++ b/src/brand/config.xml Wed Apr 16 14:39:01 2008 -0700
@@ -47,6 +47,7 @@
<postinstall></postinstall>
<privilege set="default" name="contract_event" />
+ <privilege set="default" name="contract_identity" />
<privilege set="default" name="contract_observer" />
<privilege set="default" name="file_chown" />
<privilege set="default" name="file_chown_self" />
--- a/src/brand/pkgcreatezone Wed Apr 16 14:26:36 2008 -0700
+++ b/src/brand/pkgcreatezone Wed Apr 16 14:39:01 2008 -0700
@@ -72,10 +72,21 @@
trap trap_cleanup INT
-authority="opensolaris.org=http://pkg.opensolaris.org"
zonename=""
zonepath=""
+#
+# If there's a preferred authority set for the system, set that as our
+# default. Otherwise use opensolaris.org.
+#
+authority="opensolaris.org=http://pkg.opensolaris.org"
+if [[ -x /usr/bin/pkg ]]; then
+ sysauth=`LC_ALL=C /usr/bin/pkg authority | grep preferred | awk '{printf "%s=%s", $1, $3}'`
+ if [[ $? -eq 0 && -n "$sysauth" ]]; then
+ authority=$sysauth
+ fi
+fi
+
# Setup i18n output
TEXTDOMAIN="SUNW_OST_OSCMD"
export TEXTDOMAIN
@@ -147,6 +158,12 @@
pkglist="$pkglist SUNWnis SUNWlldap"
#
+# Get nfs client and autofs; it's a pain not to have them.
+#
+pkglist="$pkglist SUNWnfsc SUNWnfsckr SUNWatfs"
+
+
+#
# Get man(1) but not the man pages
#
pkglist="$pkglist SUNWdoc"
--- a/src/brand/platform.xml Wed Apr 16 14:26:36 2008 -0700
+++ b/src/brand/platform.xml Wed Apr 16 14:39:01 2008 -0700
@@ -60,6 +60,8 @@
<device match="log" />
<device match="logindmux" />
<device match="null" />
+ <device match="nsmb" />
+ <device match="net/*" />
<device match="openprom" arch="sparc" />
<device match="poll" />
<device match="pool" />
@@ -103,6 +105,7 @@
<device match="rawip" ip-type="exclusive" />
<device match="rawip6" ip-type="exclusive" />
<device match="rts" ip-type="exclusive" />
+ <device match="sad/admin" ip-type="exclusive" />
<device match="sctp" ip-type="exclusive" />
<device match="sctp6" ip-type="exclusive" />
<device match="spdsock" ip-type="exclusive" />
--- a/src/client.py Wed Apr 16 14:26:36 2008 -0700
+++ b/src/client.py Wed Apr 16 14:39:01 2008 -0700
@@ -350,31 +350,52 @@
return 1
else:
return 3
- try:
- be = bootenv.BootEnv(img.get_root())
- except RuntimeError:
- be = bootenv.BootEnvNull(img.get_root())
# Reload catalog. This picks up the update from retrieve_catalogs.
img.load_catalogs(progresstracker)
pkg_list = [ ipkg.get_pkg_stem() for ipkg in img.gen_installed_pkgs() ]
+ try:
+ img.make_install_plan(pkg_list, progresstracker, verbose = verbose,
+ noexecute = noexecute)
+ except RuntimeError, e:
+ error(_("image-update failed: %s") % e)
+
+ assert img.imageplan
+
+ if img.imageplan.nothingtodo():
+ print _("No updates available for this image.")
+ return 0
+
+ if noexecute:
+ return 0
+
+ try:
+ be = bootenv.BootEnv(img.get_root())
+ except RuntimeError:
+ be = bootenv.BootEnvNull(img.get_root())
+
be.init_image_recovery(img)
try:
- img.list_install(pkg_list, progresstracker, verbose = verbose,
- noexecute = noexecute)
+ img.imageplan.execute()
+ be.activate_image()
+ ret_code = 0
except RuntimeError, e:
error(_("image_update failed: %s") % e)
be.restore_image()
ret_code = 1
- else:
- be.activate_image()
- ret_code = 0
+ except Exception, e:
+ error(_("An unexpected error happened during image-update: %s") % e)
+ be.restore_image()
+ img.cleanup_downloads()
+ raise
+
img.cleanup_downloads()
return ret_code
+
def install(img, args):
"""Attempt to take package specified to INSTALLED state. The operands
are interpreted as glob patterns."""
@@ -403,11 +424,6 @@
if verbose and quiet:
usage(_("install: -v and -q may not be combined"))
- try:
- be = bootenv.BootEnv(img.get_root())
- except RuntimeError:
- be = bootenv.BootEnvNull(img.get_root())
-
progresstracker = get_tracker(quiet)
img.load_catalogs(progresstracker)
@@ -416,23 +432,48 @@
for pat in pargs ]
try:
- img.list_install(pkg_list, progresstracker, filters = filters,
+ img.make_install_plan(pkg_list, progresstracker, filters = filters,
verbose = verbose, noexecute = noexecute)
except RuntimeError, e:
error(_("install failed: %s") % e)
+ return 1
+
+ assert img.imageplan
+
+ #
+ # The result of make_install_plan is that an imageplan is now filled out
+ # for the image.
+ #
+ if img.imageplan.nothingtodo():
+ print _("Nothing to install in this image (is this package already installed?)")
+ return 0
+
+ if noexecute:
+ return 0
+
+ try:
+ be = bootenv.BootEnv(img.get_root())
+ except RuntimeError:
+ be = bootenv.BootEnvNull(img.get_root())
+
+ try:
+ img.imageplan.execute()
+ be.activate_install_uninstall()
+ ret_code = 0
+ except RuntimeError, e:
+ error(_("installation failed: %s") % e)
be.restore_install_uninstall()
ret_code = 1
- except:
+ except Exception, e:
+ error(_("An unexpected error happened during installation: %s") % e)
be.restore_install_uninstall()
img.cleanup_downloads()
raise
- else:
- be.activate_install_uninstall()
- ret_code = 0
img.cleanup_downloads()
return ret_code
+
def uninstall(img, args):
"""Attempt to take package specified to DELETED state."""
@@ -454,11 +495,6 @@
progresstracker = get_tracker(quiet)
- try:
- be = bootenv.BootEnv(img.get_root())
- except RuntimeError:
- be = bootenv.BootEnvNull(img.get_root())
-
img.load_catalogs(progresstracker)
ip = imageplan.ImagePlan(img, progresstracker, recursive_removal)
@@ -482,15 +518,20 @@
error(_("'%s' matches multiple packages") % ppat)
for k in pnames:
print "\t%s" % k
+ err = 1
continue
if len(pnames) < 1:
error(_("'%s' matches no installed packages") % \
ppat)
+ err = 1
continue
ip.propose_fmri_removal(pnames[0])
+ if err == 1:
+ return err
+
if verbose:
print _("Before evaluation:")
print ip
@@ -502,12 +543,31 @@
print _("After evaluation:")
ip.display()
- if not noexecute:
+ assert not ip.nothingtodo()
+
+ if noexecute:
+ return 0
+
+ try:
+ be = bootenv.BootEnv(img.get_root())
+ except RuntimeError:
+ be = bootenv.BootEnvNull(img.get_root())
+
+ try:
ip.execute()
- if ip.state == imageplan.EXECUTED_OK:
- be.activate_install_uninstall()
- else:
- be.restore_install_uninstall()
+ except RuntimeError, e:
+ error(_("installation failed: %s") % e)
+ be.restore_install_uninstall()
+ ret_code = 1
+ except:
+ error(_("An unexpected error happened during uninstallation: %s") % e)
+ be.restore_install_uninstall()
+ raise
+
+ if ip.state == imageplan.EXECUTED_OK:
+ be.activate_install_uninstall()
+ else:
+ be.restore_install_uninstall()
return err
@@ -556,12 +616,21 @@
retcode = 1
try:
- for index, fmri, action, value in itertools.chain(*searches):
+ first = True
+ for index, mfmri, action, value in itertools.chain(*searches):
retcode = 0
+ if first:
+ if action and value:
+ print "%-10s %-9s %-25s %s" % ("INDEX",
+ "ACTION", "VALUE", "PACKAGE")
+ else:
+ print "%-10s %s" % ("INDEX", "PACKAGE")
+ first = False
if action and value:
- print index, action, value, fmri
+ print "%-10s %-9s %-25s %s" % (index, action,
+ value, fmri.PkgFmri(str(mfmri)).get_short_fmri())
else:
- print index, fmri
+ print "%-10s %s" % (index, mfmri)
except RuntimeError, failed:
print >> sys.stderr, "Some servers failed to respond:"
@@ -753,7 +822,7 @@
# XXX Need remote-info option, to request equivalent information
# from repository.
- opts, pargs = getopt.getopt(args, "Ho:s:t:m")
+ opts, pargs = getopt.getopt(args, "Ho:s:t:mf")
valid_special_attrs = [ "action.name", "action.key", "action.raw",
"pkg.name", "pkg.fmri", "pkg.shortfmri", "pkg.authority",
@@ -761,6 +830,7 @@
display_headers = True
display_raw = False
+ display_nofilters = False
attrs = []
sort_attrs = []
action_types = []
@@ -775,6 +845,9 @@
action_types.extend(arg.split(","))
elif opt == "-m":
display_raw = True
+ elif opt == "-f":
+ # Undocumented, for now.
+ display_nofilters = True
if display_raw:
display_headers = False
@@ -820,7 +893,8 @@
else:
sort_attrs = attrs[:1]
- manifests = ( img.get_manifest(f, filtered = True) for f in fmris )
+ filt = not display_nofilters
+ manifests = ( img.get_manifest(f, filtered = filt) for f in fmris )
actionlist = [ (m, a)
for m in manifests
--- a/src/modules/actions/driver.py Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/actions/driver.py Wed Apr 16 14:39:01 2008 -0700
@@ -374,7 +374,7 @@
dcf = file(os.path.normpath(os.path.join(
img.get_root(), "etc/driver_classes")))
except IOError, e:
- e.args += ("etc/driver_classes",e)
+ e.args += ("etc/driver_classes",)
if collect_errs:
errors.append(e)
else:
@@ -392,7 +392,7 @@
dmf = file(os.path.normpath(os.path.join(
img.get_root(), "etc/minor_perm")))
except IOError, e:
- e.args += ("etc/minor_perm")
+ e.args += ("etc/minor_perm",)
if collect_errs:
errors.append(e)
else:
@@ -478,9 +478,9 @@
self.__get_image_data(img, name, collect_errs = True)
for i, err in enumerate(errors):
- if err.isinstance(IOError):
+ if isinstance(err, IOError):
errors[i] = "%s: %s" % (err.args[2], err)
- elif err.isinstance(RuntimeError):
+ elif isinstance(err, RuntimeError):
errors[i] = "etc/name_to_major: more than " \
"one entry for '%s' is present" % name
--- a/src/modules/client/bootenv.py Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/client/bootenv.py Wed Apr 16 14:39:01 2008 -0700
@@ -66,6 +66,10 @@
self.beList = be.beList()
+ # Happens e.g. in zones (at least, for now)
+ if not self.beList:
+ raise RuntimeError, "nobootenvironments"
+
# Need to find the name of the BE we're operating on in order
# to create a snapshot and/or a clone of the BE.
--- a/src/modules/client/image.py Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/client/image.py Wed Apr 16 14:39:01 2008 -0700
@@ -444,9 +444,11 @@
return False
- def _fetch_manifest(self, fmri, filtered):
+ def _fetch_manifest(self, fmri):
"""Perform steps necessary to get manifest from remote host
- and write resulting contents to disk."""
+ and write resulting contents to disk. Helper routine for
+ get_manifest. Does not filter the results, caller must do
+ that. """
m = manifest.Manifest()
m.set_fmri(self, fmri)
@@ -473,22 +475,6 @@
m.store(mpath, ipath)
- if filtered:
- filters = []
- try:
- f = file("%s/filters" % fmri_dir_path, "r")
- except IOError, e:
- if e.errno != errno.ENOENT:
- raise
- else:
- filters = [
- (l.strip(), compile(
- l.strip(), "<filter string>", "eval"))
- for l in f.readlines()
- ]
-
- m.filter(filters)
-
return m
def _valid_manifest(self, fmri, manifest):
@@ -509,7 +495,7 @@
def get_manifest(self, fmri, filtered = False):
"""Find on-disk manifest and create in-memory Manifest
- object."""
+ object, applying appropriate filters as needed."""
m = manifest.Manifest()
@@ -519,7 +505,7 @@
# If the manifest isn't there, download.
if not os.path.exists(mpath):
- m = self._fetch_manifest(fmri, filtered)
+ m = self._fetch_manifest(fmri)
else:
mcontent = file(mpath).read()
m.set_fmri(self, fmri)
@@ -529,7 +515,7 @@
# no authority is attached to the manifest, download a new one.
if not self._valid_manifest(fmri, m):
try:
- m = self._fetch_manifest(fmri, filtered)
+ m = self._fetch_manifest(fmri)
except NameError:
# In thise case, the client has failed to
# download a new manifest with the same name.
@@ -538,6 +524,22 @@
# we have. Keep the old manifest and drive on.
pass
+ # XXX perhaps all of the below should live in Manifest.filter()?
+ if filtered:
+ filters = []
+ try:
+ f = file("%s/filters" % fmri_dir_path, "r")
+ except IOError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ else:
+ filters = [
+ (l.strip(), compile(
+ l.strip(), "<filter string>", "eval"))
+ for l in f.readlines()
+ ]
+ m.filter(filters)
+
return m
@staticmethod
@@ -852,17 +854,16 @@
if not pkg.fmri.is_same_authority(cfmri.authority, pfmri.authority):
return False
- # Get the catalog for the correct authority
- cat = self.get_catalog(cfmri)
-
# If the catalog has a rename record that names fmri as a
# destination, it's possible that pfmri could be the same pkg by
# rename.
-
if cfmri.is_same_pkg(pfmri):
return True
- else:
- return cat.rename_is_same_pkg(cfmri, pfmri)
+
+ # Get the catalog for the correct authority
+ cat = self.get_catalog(cfmri)
+ return cat.rename_is_same_pkg(cfmri, pfmri)
+
def fmri_is_successor(self, cfmri, pfmri):
"""Since the catalog keeps track of renames, it's no longer
@@ -1021,19 +1022,30 @@
for p in patterns
if pkg.fmri.fmri_match(x.get_pkg_stem(), p)
and not x in pkgs_known ] )
- elif all_known:
- pkgs_known = [ pf for pf in
- sorted(self.gen_known_package_fmris()) ]
else:
pkgs_known = sorted(self.gen_installed_pkgs())
- if pkgs_known:
- counthash = {}
- self.get_matching_fmris(pkgs_known,
- counthash = counthash)
+ counthash = {}
+ if pkgs_known:
+ #
+ # Walk the installed packages looking for those
+ # which have upgrades available.
+ #
+ self.get_matching_fmris(pkgs_known,
+ counthash = counthash)
+
+ #
+ # If needed, merge in the rest of the known packages; we don't
+ # compute upgradability for those, since it's very expensive.
+ #
+ if all_known and not patterns:
+ pkgs_all_known = [ pf for pf in
+ self.gen_known_package_fmris() ]
+ pkgs_known += pkgs_all_known
+ pkgs_known = sorted(set(pkgs_known))
for p in pkgs_known:
- if counthash[p] > 1:
+ if counthash.get(p, 0) > 1:
upgradable = True
else:
upgradable = False
@@ -1165,12 +1177,13 @@
out.add(p)
p = os.path.dirname(p)
return out
-
-
- def list_install(self, pkg_list, progress, filters = [],
+
+
+ def make_install_plan(self, pkg_list, progress, filters = [],
verbose = False, noexecute = False):
"""Take a list of packages, specified in pkg_list, and attempt
- to install them on the system.
+ to assemble an appropriate image plan. This is a helper
+ routine for some common operations in the client.
This method checks all authorities for a package match;
however, it defaults to choosing the preferred authority
@@ -1245,10 +1258,7 @@
if verbose:
print _("After evaluation:")
- ip.display()
-
- if not noexecute:
- ip.execute()
+ print ip.display()
if __name__ == "__main__":
pass
--- a/src/modules/client/imageplan.py Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/client/imageplan.py Wed Apr 16 14:39:01 2008 -0700
@@ -355,6 +355,11 @@
self.state = EVALUATED_OK
+ def nothingtodo(self):
+ """ Test whether this image plan contains any work to do """
+
+ return not self.pkg_plans
+
def execute(self):
"""Invoke the evaluated image plan
preexecute, execute and postexecute
@@ -363,6 +368,10 @@
assert self.state == EVALUATED_OK
+ if self.nothingtodo():
+ self.state = EXECUTED_OK
+ return
+
npkgs = 0
nfiles = 0
nbytes = 0
--- a/src/modules/fmri.py Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/fmri.py Wed Apr 16 14:39:01 2008 -0700
@@ -35,6 +35,13 @@
# the FMRI. PREF_AUTH_PFX => preferred authority prefix.
PREF_AUTH_PFX = "_PRE"
+#
+# For is_same_authority(), we need a version of this constant with the
+# trailing _ attached.
+#
+PREF_AUTH_PFX_ = PREF_AUTH_PFX + "_"
+
+
class PkgFmri(object):
"""The authority is the anchor of a package namespace. Clients can
choose to take packages from multiple authorities, and specify a default
@@ -333,37 +340,26 @@
def is_same_authority(auth1, auth2):
"""Compare two authorities. Return true if they are the same, false
- otherwise."""
+ otherwise. """
+ #
+ # This code is performance sensitive. Ensure that you benchmark
+ # changes to it.
+ #
- # Cope with authorities that are None
- if not auth1 and not auth2:
- return True
- elif not auth1 or not auth2:
- return False
+ # Fastest path for most common case.
+ if auth1 == auth2:
+ return True
- matchstr = "%s_" % PREF_AUTH_PFX
+ if auth1 == None:
+ auth1 = ""
+ if auth2 == None:
+ auth2 = ""
- # Check if the authorities are preferred. If they are, match.
-
- r1 = auth1.startswith(matchstr)
- r2 = auth2.startswith(matchstr)
-
- if r1 and r2:
+ # String concatenation and string equality are both pretty fast.
+ if ((PREF_AUTH_PFX_ + auth1) == auth2) or \
+ (auth1 == (PREF_AUTH_PFX_ + auth2)):
+ return True
+ if auth1.startswith(PREF_AUTH_PFX_) and auth2.startswith(PREF_AUTH_PFX_):
return True
-
- # extract the authority suffix
- if r1:
- a1 = auth1[len(matchstr):]
- else:
- a1 = auth1
+ return False
- if r2:
- a2 = auth2[len(matchstr):]
- else:
- a2 = auth2
-
- # Do authorities match?
- if a1 == a2:
- return True
-
- return False
--- a/src/tests/cli/t_upgrade.py Wed Apr 16 14:26:36 2008 -0700
+++ b/src/tests/cli/t_upgrade.py Wed Apr 16 14:39:01 2008 -0700
@@ -46,14 +46,14 @@
close
"""
- incorpA = """
+ incorpA = """
open [email protected],5.11-0
add depend type=incorporate fmri=pkg:/[email protected]
add depend type=incorporate fmri=pkg:/[email protected]
close
"""
- incorpB = """
+ incorpB = """
open [email protected],5.11-0
add depend type=incorporate fmri=pkg:/[email protected]
add depend type=incorporate fmri=pkg:/[email protected]
@@ -115,7 +115,7 @@
add file /tmp/bronze2 mode=0444 owner=root group=bin path=/etc/amber2
add license /tmp/copyright3 license=copyright
add file /tmp/bronzeA2 mode=0444 owner=root group=bin path=/A1/B2/C3/D4/E5/F6/bronzeA2
- add depend fmri=pkg:/[email protected] type=require
+ add depend fmri=pkg:/[email protected] type=require
close
"""
@@ -156,7 +156,17 @@
self.pkgsend_bulk(durl, self.amber10)
self.pkgsend_bulk(durl, self.bronze10)
- # Now send 2.0 versions of packages.
+ self.image_create(durl)
+ self.pkg("install [email protected]")
+ self.pkg("install bronze")
+
+ self.pkg("list [email protected]")
+ self.pkg("list [email protected]")
+ self.pkg("verify -v")
+
+ #
+ # Now send 2.0 versions of packages. image-update will (should)
+ # implicitly refresh.
#
# In version 2.0, several things happen:
#
@@ -171,15 +181,6 @@
self.pkgsend_bulk(durl, self.amber20)
self.pkgsend_bulk(durl, self.bronze20)
-
- self.image_create(durl)
- self.pkg("install [email protected]")
- self.pkg("install bronze")
-
- self.pkg("list [email protected]")
- self.pkg("list [email protected]")
- self.pkg("verify -v")
-
# Now image-update to get new versions of amber and bronze
self.pkg("image-update")
@@ -198,16 +199,15 @@
# make sure all directories are gone save /var in test image
self.assert_(os.listdir(self.get_img_path()) == ["var"])
- def test_upgrade2(self):
-
+ def test_upgrade2(self):
+ """ Basic test for incorporations """
# Send all pkgs
-
durl = self.dc.get_depot_url()
- self.pkgsend_bulk(durl, self.incorpA)
+ self.pkgsend_bulk(durl, self.incorpA)
self.pkgsend_bulk(durl, self.amber10)
self.pkgsend_bulk(durl, self.bronze10)
- self.pkgsend_bulk(durl, self.incorpB)
+ self.pkgsend_bulk(durl, self.incorpB)
self.pkgsend_bulk(durl, self.amber20)
self.pkgsend_bulk(durl, self.bronze20)
@@ -215,8 +215,8 @@
self.pkg("install incorpA")
self.pkg("install incorpB")
self.pkg("install bronze")
- self.pkg("list [email protected]")
+ self.pkg("list [email protected]")
self.pkg("verify -v")
-
+
if __name__ == "__main__":
unittest.main()