2022 client should provide operational intent to server
3565 client should provide version information when providing intent to server
--- a/src/client.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/client.py Tue Sep 30 19:37:17 2008 -0500
@@ -204,6 +204,7 @@
if not check_fmri_args(pargs):
return 1
+ img.history.operation_name = "list"
img.load_catalogs(progress.NullProgressTracker())
seen_one_pkg = False
@@ -260,6 +261,8 @@
if not found:
if not seen_one_pkg and not all_known:
emsg(_("no packages installed"))
+ img.history.operation_result = \
+ history.RESULT_NOTHING_TO_DO
return 1
if upgradable_only:
@@ -269,14 +272,23 @@
else:
emsg(_("No installed packages have " \
"available updates"))
+ img.history.operation_result = \
+ history.RESULT_NOTHING_TO_DO
return 1
+
+ img.history.operation_result = \
+ history.RESULT_NOTHING_TO_DO
return 1
+
+ img.history.operation_result = history.RESULT_SUCCEEDED
return 0
except image.InventoryException, e:
if e.illegal:
for i in e.illegal:
error(i)
+ img.history.operation_result = \
+ history.RESULT_FAILED_BAD_REQUEST
return 1
if all_known:
@@ -285,6 +297,7 @@
state = image.PKG_STATE_INSTALLED
for pat in e.notfound:
error(_("no packages matching '%s' %s") % (pat, state))
+ img.history.operation_result = history.RESULT_NOTHING_TO_DO
return 1
def get_tracker(quiet = False):
@@ -828,7 +841,8 @@
img.load_catalogs(progresstracker)
- ip = imageplan.ImagePlan(img, progresstracker, recursive_removal)
+ ip = imageplan.ImagePlan(img, progresstracker, recursive_removal,
+ noexecute)
err = 0
@@ -1088,6 +1102,7 @@
if not check_fmri_args(pargs):
return 1
+ img.history.operation_name = "info"
img.load_catalogs(progress.NullProgressTracker())
err = 0
@@ -1098,15 +1113,21 @@
if illegals:
for i in illegals:
emsg(str(i))
+ img.history.operation_result = \
+ history.RESULT_FAILED_BAD_REQUEST
return 1
if not fmris and not notfound:
error(_("no packages installed"))
+ img.history.operation_result = \
+ history.RESULT_NOTHING_TO_DO
return 1
elif info_remote:
# Verify validity of certificates before attempting network
# operations
if not img.check_cert_validity():
+ img.history.operation_result = \
+ history.RESULT_FAILED_TRANSPORT
return 1
fmris = []
@@ -1214,7 +1235,9 @@
emsg()
for p in notfound:
emsg(" %s" % p)
-
+ img.history.operation_result = history.RESULT_NOTHING_TO_DO
+ else:
+ img.history.operation_result = history.RESULT_SUCCEEDED
return err
def display_contents_results(actionlist, attrs, sort_attrs, action_types,
@@ -1396,6 +1419,7 @@
if a.startswith("pkg.") and not a in valid_special_attrs:
usage(_("Invalid attribute '%s'") % a)
+ img.history.operation_name = "contents"
img.load_catalogs(progress.NullProgressTracker())
err = 0
@@ -1407,15 +1431,21 @@
if illegals:
for i in illegals:
emsg(i)
+ img.history.operation_result = \
+ history.RESULT_FAILED_BAD_REQUEST
return 1
if not fmris and not notfound:
error(_("no packages installed"))
+ img.history.operation_result = \
+ history.RESULT_NOTHING_TO_DO
return 1
elif remote:
# Verify validity of certificates before attempting network
# operations
if not img.check_cert_validity():
+ img.history.operation_result = \
+ history.RESULT_FAILED_TRANSPORT
return 1
fmris = []
@@ -1512,7 +1542,9 @@
emsg()
for p in notfound:
emsg(" %s" % p)
-
+ img.history.operation_result = history.RESULT_NOTHING_TO_DO
+ else:
+ img.history.operation_result = history.RESULT_SUCCEEDED
return err
def display_catalog_failures(cre):
@@ -1672,9 +1704,9 @@
error(_("set-authority failed: %s") % e)
return 1
except image.CatalogRefreshException, cre:
- msg = "Could not refresh the catalog for %s" % \
+ text = "Could not refresh the catalog for %s" % \
auth
- error(_(msg))
+ error(_(text))
ret_code = 1
if preferred:
--- a/src/gui/modules/installupdate.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/gui/modules/installupdate.py Tue Sep 30 19:37:17 2008 -0500
@@ -42,6 +42,7 @@
import pkg.client.bootenv as bootenv
import pkg.client.history as history
import pkg.client.imageplan as imageplan
+import pkg.client.imagestate as imagestate
import pkg.client.progress as progress
import pkg.fmri as fmri
import pkg.client.indexer as indexer
@@ -480,6 +481,7 @@
self.parent._("Evaluating: %s\n") % pfmri.get_fmri())
self.ip.progtrack.evaluate_progress()
+ self.ip.image.state.set_target(pfmri, imagestate.INTENT_PROCESS)
m = image.get_manifest(pfmri)
# [manifest] examine manifest for dependencies
@@ -532,6 +534,7 @@
continue
if excluded:
+ self.ip.image.state.set_target()
raise RuntimeError, "excluded by '%s'" % f
# treat-as-required, treat-as-required-unless-pinned,
@@ -559,6 +562,8 @@
self.ip.propose_fmri(cf)
self.__evaluate_fmri(cf, image)
+ self.ip.image.state.set_target()
+
def __download_stage(self, rebuild=False):
'''Parts of the code duplicated from install and image-update from pkg(1)
and pkg.client.ImagePlan.preexecute()'''
--- a/src/modules/client/history.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/modules/client/history.py Tue Sep 30 19:37:17 2008 -0500
@@ -56,6 +56,9 @@
# Indicates that a transport error caused the operation to fail.
RESULT_FAILED_TRANSPORT = ["Failed", "Transport"]
+# Operations that are discarded, not saved, when recorded by history.
+DISCARDED_OPERATIONS = ["contents", "info", "list"]
+
class _HistoryOperation(object):
"""A _HistoryOperation object is a representation of data about an
operation that a pkg(5) client has performed. This class is private
@@ -192,10 +195,17 @@
elif name == "operation_result":
# Record when the operation ended.
op.end_time = misc.time_to_timestamp(None)
- # Write current history and last operation to a file.
- if self.__save():
- # Discard it now that it is no longer needed.
- ops.pop()
+
+ # Some operations shouldn't be saved -- they're merely
+ # included in the stack for completeness or to support
+ # client functionality.
+ if op.name not in DISCARDED_OPERATIONS:
+ # Write current history and last operation to a
+ # file.
+ self.__save()
+
+ # Discard it now that it is no longer needed.
+ ops.pop()
def __init__(self, root_dir=".", filename=None):
"""'root_dir' should be the path of the directory where the
--- a/src/modules/client/image.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/modules/client/image.py Tue Sep 30 19:37:17 2008 -0500
@@ -52,6 +52,7 @@
import pkg.client.history as history
import pkg.client.imageconfig as imageconfig
import pkg.client.imageplan as imageplan
+import pkg.client.imagestate as imagestate
import pkg.client.retrieve as retrieve
import pkg.portable as portable
import pkg.client.query_engine as query_e
@@ -192,10 +193,13 @@
self.dl_cache_dir = None
self.dl_cache_incoming = None
self.is_user_cache_dir = False
+ self.state = imagestate.ImageState()
+ self.attrs = {
+ "Policy-Require-Optional": False,
+ "Policy-Pursue-Latest": True
+ }
- self.attrs = {}
-
- self.imageplan = None # valid after evaluation succceds
+ self.imageplan = None # valid after evaluation succeeds
# contains a dictionary w/ key = pkgname, value is miminum
# frmi.XXX Needs rewrite using graph follower
@@ -203,7 +207,11 @@
# a place to keep info about saved_files; needed by file action
self.saved_files = {}
-
+
+ # A place to keep track of which manifests (based on fmri and
+ # operation) have already provided intent information.
+ self.__touched_manifests = {}
+
def find_root(self, d):
def check_subdirs(sub_d, prefix):
@@ -402,7 +410,7 @@
res = cmp(a.errors, b.errors)
if res == 0:
return cmp(a.good_tx, b.good_tx)
- return res
+ return res
slst.sort(cmp = cmp_depotstatus)
@@ -477,7 +485,7 @@
" not found") % pfx)
emsg(_("File was supposed to exist at" \
" path %s") % ssl_cert)
- return False
+ return False
else:
raise
# OpenSSL.crypto.Error
@@ -508,7 +516,7 @@
emsg(_("Certificate effective date is in" \
" the future"))
return False
-
+
na = cert.get_notAfter()
t = time.strptime(na, "%Y%m%d%H%M%SZ")
nadt = datetime.datetime.utcfromtimestamp(
@@ -624,7 +632,7 @@
refresh_needed = True
self.save_config()
-
+
if refresh_needed and refresh_allowed:
self.destroy_catalog(auth_name)
self.destroy_catalog_cache()
@@ -717,8 +725,8 @@
return False
- def _fetch_manifest_with_retries(self, fmri):
- """Wrapper function around _fetch_manifest to handle some
+ def __fetch_manifest_with_retries(self, fmri):
+ """Wrapper function around __fetch_manifest to handle some
exceptions and keep track of additional state."""
m = None
@@ -726,15 +734,99 @@
while not m:
try:
- m = self._fetch_manifest(fmri)
+ m = self.__fetch_manifest(fmri)
except TransferTimedOutException:
retry_count -= 1
if retry_count <= 0:
raise
+
return m
- def _fetch_manifest(self, fmri):
+ def __get_touched_manifest(self, fmri):
+ """Returns whether intent information has been provided for the
+ given fmri."""
+
+ op = self.history.operation_name
+ if not op:
+ # The client may not have provided the name of the
+ # operation it is performing.
+ op = "unknown"
+
+ if op not in self.__touched_manifests:
+ # No intent information has been provided for fmris
+ # for the current operation.
+ return False
+
+ f = str(fmri)
+ if f not in self.__touched_manifests[op]:
+ # No intent information has been provided for this
+ # fmri for the current operation.
+ return False
+
+ return True
+
+ def __set_touched_manifest(self, fmri):
+ """Records that intent information has been provided for the
+ given fmri's manifest."""
+
+ op = self.history.operation_name
+ if not op:
+ # The client may not have provided the name of the
+ # operation it is performing.
+ op = "unknown"
+
+ if op not in self.__touched_manifests:
+ # No intent information has yet been provided for fmris
+ # for the current operation.
+ self.__touched_manifests[op] = {}
+
+ f = str(fmri)
+ if f not in self.__touched_manifests[op]:
+ # No intent information has yet been provided for this
+ # fmri for the current operation.
+ self.__touched_manifests[op][f] = None
+
+ def __touch_manifest(self, fmri):
+ """Perform steps necessary to 'touch' a manifest to provide
+ intent information. Ignores most exceptions as this operation
+ is only for informational purposes."""
+
+ if not self.__get_touched_manifest(fmri):
+ # If the manifest for this fmri hasn't been "seen"
+ # before, determine if intent information needs to be
+ # provided.
+
+ # What is the client currently processing?
+ target, intent = self.state.get_target()
+
+ if target and intent != imagestate.INTENT_EVALUATE:
+ # If the client is currently performing an
+ # image-modifying operation, not just an
+ # an evaluation, then perform further checks.
+
+ # Ignore the authority for comparison.
+ na_target = target.get_fmri(anarchy=True)
+ na_fmri = target.get_fmri(anarchy=True)
+
+ if na_target == na_fmri:
+ # If the client is currently processing
+ # the given fmri (for an install, etc.)
+ # then intent information is needed.
+ try:
+ retrieve.touch_manifest(self,
+ fmri)
+ except NameError:
+ pass
+
+ # Manifests should only be marked as
+ # processed if the touch is actually
+ # performed since multiple retrievals
+ # may occur during the same operation
+ # for different reasons.
+ self.__set_touched_manifest(fmri)
+
+ def __fetch_manifest(self, fmri):
"""Perform steps necessary to get manifest from remote host
and write resulting contents to disk. Helper routine for
get_manifest. Does not filter the results, caller must do
@@ -771,6 +863,8 @@
if e.errno not in (errno.EROFS, errno.EACCES):
raise
+ self.__set_touched_manifest(fmri)
+
return m
def _valid_manifest(self, fmri, manifest):
@@ -796,39 +890,50 @@
fmri.get_dir_path(), "manifest")
return mpath
+ def __get_manifest(self, fmri):
+ """Find on-disk manifest and create in-memory Manifest
+ object."""
+
+ m = None
+ mpath = os.path.join(self.imgdir, "pkg", fmri.get_dir_path(),
+ "manifest")
+ if os.path.exists(mpath):
+ # If the manifest already exists, load it from storage.
+ m = manifest.Manifest()
+ mcontent = file(mpath).read()
+ m.set_fmri(self, fmri)
+ m.set_content(mcontent)
+
+ try:
+ # If the manifest didn't already exist, or isn't from
+ # the correct authority, or no authority is attached
+ # to the manifest, attempt to download a new one.
+ if not m or not self._valid_manifest(fmri, m):
+ m = self.__fetch_manifest_with_retries(fmri)
+ except NameError:
+ # In this case, the client has failed to download a new
+ # manifest or re-download an existing one with the same
+ # name.
+ if not m:
+ # Since an older copy doesn't exist, give up.
+ raise
+
+ # Since the old manifest exists, keep it, and drive on.
+
+ return m
+
def get_manifest(self, fmri, filtered = False):
"""Find on-disk manifest and create in-memory Manifest
object, applying appropriate filters as needed."""
- m = manifest.Manifest()
-
- fmri_dir_path = os.path.join(self.imgdir, "pkg",
- fmri.get_dir_path())
- mpath = os.path.join(fmri_dir_path, "manifest")
-
- # If the manifest isn't there, download.
- if not os.path.exists(mpath):
- m = self._fetch_manifest_with_retries(fmri)
- else:
- mcontent = file(mpath).read()
- m.set_fmri(self, fmri)
- m.set_content(mcontent)
-
- # If the manifest isn't from the correct authority, or
- # no authority is attached to the manifest, download a new one.
- if not self._valid_manifest(fmri, m):
- try:
- m = self._fetch_manifest_with_retries(fmri)
- except NameError:
- # In thise case, the client has failed to
- # download a new manifest with the same name.
- # We can either give up or drive on. It makes
- # the most sense to do the best we can with what
- # we have. Keep the old manifest and drive on.
- pass
+ m = self.__get_manifest(fmri)
+ self.__touch_manifest(fmri)
# XXX perhaps all of the below should live in Manifest.filter()?
if filtered:
+ fmri_dir_path = os.path.join(self.imgdir, "pkg",
+ fmri.get_dir_path())
+
filters = []
try:
f = file("%s/filters" % fmri_dir_path, "r")
@@ -996,6 +1101,15 @@
raise
shutil.rmtree(tmpdir)
+ def get_version_installed(self, pfmri):
+ """Returns an fmri of the installed package matching the
+ package stem of the given fmri or None if no match is found."""
+ target = pfmri.get_pkg_stem()
+ for f in self.gen_installed_pkgs():
+ if f.get_pkg_stem() == target:
+ return f
+ return None
+
def _get_version_installed(self, pfmri):
pd = pfmri.get_pkg_stem()
pdir = "%s/pkg/%s" % (self.imgdir,
@@ -1185,7 +1299,7 @@
total += 1
full_refresh_this_auth = False
-
+
if auth["prefix"] in self.catalogs:
cat = self.catalogs[auth["prefix"]]
ts = cat.last_modified()
@@ -1240,7 +1354,7 @@
succeeded += 1
self.cache_catalogs()
-
+
if failed:
raise CatalogRefreshException(failed, total, succeeded)
@@ -1341,7 +1455,7 @@
except OSError, e:
if e.errno != errno.ENOENT:
raise
-
+
def destroy_catalog(self, auth_name):
try:
shutil.rmtree("%s/catalog/%s" %
@@ -1538,7 +1652,6 @@
def load_optional_dependencies(self):
for fmri in self.gen_installed_pkgs():
mfst = self.get_manifest(fmri, filtered = True)
-
for dep in mfst.gen_actions_by_type("depend"):
required, min_fmri, max_fmri = dep.parse(self)
if required == False:
@@ -1822,7 +1935,7 @@
return pkg.fmri.PkgFmri(urllib.unquote(os.path.dirname(
index[len(idxdir) + 1:]).replace(os.path.sep, "@")),
None)
-
+
res = []
for fmri, mfst in self.get_fmri_manifest_pairs():
@@ -1845,7 +1958,7 @@
action, keyval))
return res
-
+
def local_search(self, args, case_sensitive):
"""Search the image for the token in args[0]."""
assert args[0]
@@ -1985,7 +2098,8 @@
to name the package."""
error = 0
- ip = imageplan.ImagePlan(self, progress, filters = filters)
+ ip = imageplan.ImagePlan(self, progress, filters = filters,
+ noexecute = noexecute)
self.load_optional_dependencies()
@@ -2062,8 +2176,8 @@
msg(ip.display())
def rebuild_search_index(self, progtracker):
- """Rebuilds the search indexes. Removes all
- existing indexes and replaces them from scratch rather than
+ """Rebuilds the search indexes. Removes all
+ existing indexes and replaces them from scratch rather than
performing the incremental update which is usually used."""
self.update_index_dir()
if not os.path.isdir(self.index_dir):
--- a/src/modules/client/imageplan.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/modules/client/imageplan.py Tue Sep 30 19:37:17 2008 -0500
@@ -25,9 +25,8 @@
import os
import pkg.fmri as fmri
+import pkg.client.imagestate as imagestate
import pkg.client.pkgplan as pkgplan
-import pkg.client.retrieve as retrieve # XXX inventory??
-import pkg.version as version
import pkg.client.indexer as indexer
import pkg.search_errors as se
from pkg.client.filter import compile_filter
@@ -78,12 +77,19 @@
"pkg delete fmri; pkg install fmri@v(n - 1)", then we'd better have a
plan to identify when this operation is safe or unsafe."""
- def __init__(self, image, progtrack, recursive_removal = False, filters = []):
+ def __init__(self, image, progtrack, recursive_removal = False,
+ noexecute = False, filters = []):
self.image = image
self.state = UNEVALUATED
self.recursive_removal = recursive_removal
self.progtrack = progtrack
+ self.noexecute = noexecute
+ if noexecute:
+ self.__intent = imagestate.INTENT_EVALUATE
+ else:
+ self.__intent = imagestate.INTENT_PROCESS
+
self.target_fmris = []
self.target_rem_fmris = []
self.pkg_plans = []
@@ -240,6 +246,7 @@
def evaluate_fmri(self, pfmri):
self.progtrack.evaluate_progress()
+ self.image.state.set_target(pfmri, self.__intent)
m = self.image.get_manifest(pfmri)
# [manifest] examine manifest for dependencies
@@ -292,6 +299,7 @@
continue
if excluded:
+ self.image.state.set_target()
raise RuntimeError, "excluded by '%s'" % f
# treat-as-required, treat-as-required-unless-pinned,
@@ -319,6 +327,8 @@
self.propose_fmri(cf)
self.evaluate_fmri(cf)
+ self.image.state.set_target()
+
def add_pkg_plan(self, pfmri):
"""add a pkg plan to imageplan for fully evaluated frmi"""
m = self.image.get_manifest(pfmri)
@@ -351,13 +361,15 @@
if dependents and not self.recursive_removal:
raise NonLeafPackageException(pfmri, dependents)
- m = self.image.get_manifest(pfmri)
+ pp = pkgplan.PkgPlan(self.image, self.progtrack)
- pp = pkgplan.PkgPlan(self.image, self.progtrack)
+ self.image.state.set_target(pfmri, self.__intent)
+ m = self.image.get_manifest(pfmri)
try:
pp.propose_removal(pfmri, m)
except RuntimeError:
+ self.image.state.set_target()
msg("pkg %s not installed" % pfmri)
return
@@ -376,6 +388,7 @@
# dependency graphs. Cycles need to be arbitrarily broken, and
# are done so in the loop above.
self.pkg_plans.append(pp)
+ self.image.state.set_target()
def evaluate(self):
assert self.state == UNEVALUATED
@@ -390,8 +403,8 @@
try:
self.evaluate_fmri(f)
except KeyError, e:
- outstring += "Attemping to install %s causes:\n\t%s\n" % \
- (f.get_name(), e)
+ outstring += "Attempting to install %s " \
+ "causes:\n\t%s\n" % (f.get_name(), e)
if outstring:
raise RuntimeError("No packages were installed because "
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/client/imagestate.py Tue Sep 30 19:37:17 2008 -0500
@@ -0,0 +1,77 @@
+#!/usr/bin/python2.4
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+
+# Indicates that the fmri is being used strictly for information.
+INTENT_INFO = "info"
+
+# Indicates that the fmri is being used to perform a dry-run evaluation of an
+# image-modifying operation.
+INTENT_EVALUATE = "evaluate"
+
+# Indicates that the fmri is being processed as part of an image-modifying
+# operation.
+INTENT_PROCESS = "process"
+
+class ImageState(object):
+ """An ImageState object provides a temporary place to store information
+ about operations that are being performed on an image (e.g. fmris of
+ packages that are being installed, uninstalled, etc.).
+ """
+
+ def __init__(self):
+ self.__fmri_intent_stack = []
+
+ def __str__(self):
+ return "%s" % self.__fmri_intent_stack
+
+ def set_target(self, fmri=None, intent=INTENT_INFO):
+ """Indicates that the given fmri is currently being evaluated
+ or manipulated for an image operation. A value of None for
+ fmri will clear the current target.
+ """
+ if fmri:
+ self.__fmri_intent_stack.append((fmri, intent))
+ else:
+ del self.__fmri_intent_stack[-1]
+
+ def get_target(self):
+ """Returns a tuple of the format (fmri, intent) representing an
+ fmri currently being evaluated or manipulated for an image
+ operation. A tuple containing (None, None) will be returned if
+ no target has been set.
+ """
+ try:
+ return self.__fmri_intent_stack[-1]
+ except IndexError:
+ return (None, None)
+
+ def get_targets(self):
+ """Returns a list of tuples of the format (fmri, intent)
+ representing fmris currently being evaluated or manipulated for
+ an image operation. An empty list is returned if there are no
+ targets.
+ """
+ return self.__fmri_intent_stack[:]
+
--- a/src/modules/client/retrieve.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/modules/client/retrieve.py Tue Sep 30 19:37:17 2008 -0500
@@ -25,58 +25,148 @@
import socket
import urllib2
-import httplib
import pkg.fmri
+import pkg.client.imagestate as imagestate
from pkg.misc import versioned_urlopen
from pkg.misc import TransferTimedOutException
from pkg.misc import retryable_http_errors
# client/retrieve.py - collected methods for retrieval of pkg components
# from repositories
+def __get_intent_str(img, fmri):
+ """Returns a string representing the intent of the client in retrieving
+ information based on the operation information provided by the image
+ history object.
+ """
-def get_datastream(img, fmri, hash):
- """Retrieve a file handle based on a package fmri and a file hash."""
+ op = img.history.operation_name
+ if not op:
+ # The client hasn't indicated what operation is executing.
+ op = "unknown"
+
+ reason = imagestate.INTENT_INFO
+ initial_pkg = ""
+ parent_pkg = ""
+ try:
+ targets = img.state.get_targets()
+ # Attempt to determine why the client is retrieving the
+ # manifest for this fmri and what its current target is.
+ target, reason = targets[-1]
+
+ na_current = fmri.get_fmri(anarchy=True)
+ na_target = target.get_fmri(anarchy=True)
+ if na_target == na_current:
+ # If the fmri for the manifest being retrieved does not
+ # match the fmri of the target, then this manifest is
+ # being retrieved for information purposes only, so don't
+ # provide this information.
+
+ # The fmri responsible for the current one being processed
+ # should immediately precede the current one in the target
+ # list.
+ parent = targets[-2][0]
+ parent_pkg = parent.get_fmri(anarchy=True)[len("pkg:/"):]
- authority, pkg_name, version = fmri.tuple()
+ if len(targets) > 2:
+ # If there are more than two targets in the list, then
+ # the very first fmri is the one that caused the
+ # current and parent fmris to be retrieved.
+ initial = targets[0][0]
+ initial_pkg = initial.get_fmri(
+ anarchy=True)[len("pkg:/"):]
+ else:
+ initial_pkg = parent_pkg
+ parent_pkg = ""
+ except IndexError:
+ # Any part of the target information may not be available.
+ # Ignore it, and move on.
+ pass
+
+ version = ""
+ if reason != imagestate.INTENT_INFO:
+ # Only provide version information for non-informational
+ # operations.
+ version = "none"
+ try:
+ version = "%s" % img.get_version_installed(fmri).version
+ except AttributeError:
+ # We didn't get a match back, drive on.
+ pass
+
+ info = {
+ "operation": op,
+ "version": version,
+ "reason": reason,
+ "initial_target": initial_pkg,
+ "parent_target": parent_pkg,
+ }
+
+ # op/installed_version/reason/initial_target/immediate_parent/
+ return "(%s)" % ";".join([
+ "%s=%s" % (key, info[key]) for key in info.keys()
+ if info[key] != ""
+ ])
+
+def get_datastream(img, fmri, fhash):
+ """Retrieve a file handle based on a package fmri and a file hash.
+ """
+
+ authority = fmri.get_authority_str()
authority = pkg.fmri.strip_auth_pfx(authority)
url_prefix = img.get_url_by_authority(authority)
ssl_tuple = img.get_ssl_credentials(authority)
uuid = img.get_uuid(authority)
try:
- f, v = versioned_urlopen(url_prefix, "file", [0], hash,
- ssl_creds=ssl_tuple, imgtype=img.type,
- uuid=uuid)
+ f = versioned_urlopen(url_prefix, "file", [0], fhash,
+ ssl_creds=ssl_tuple, imgtype=img.type, uuid=uuid)[0]
except urllib2.HTTPError, e:
raise NameError, "could not retrieve file '%s' from '%s'" % \
- (hash, url_prefix)
+ (fhash, url_prefix)
except urllib2.URLError, e:
if len(e.args) == 1 and isinstance(e.args[0], socket.sslerror):
raise RuntimeError, e
raise NameError, "could not retrieve file '%s' from '%s'" % \
- (hash, url_prefix)
+ (fhash, url_prefix)
except:
raise NameError, "could not retrieve file '%s' from '%s'" % \
- (hash, url_prefix)
+ (fhash, url_prefix)
return f
-def get_manifest(img, fmri):
- """ Calculate URI and retrieve manifest. Return it as a buffer to
- the caller. """
+def __get_manifest(img, fmri, method):
+ """Given an image object, fmri, and http method; return a file object
+ for the related manifest and send intent information.
+ """
- authority, pkg_name, version = fmri.tuple()
+ authority = fmri.get_authority_str()
authority = pkg.fmri.strip_auth_pfx(authority)
url_prefix = img.get_url_by_authority(authority)
ssl_tuple = img.get_ssl_credentials(authority)
uuid = img.get_uuid(authority)
+ # Tell the server why this resource is being requested.
+ headers = {
+ "X-IPkg-Intent": __get_intent_str(img, fmri)
+ }
+
+ return versioned_urlopen(url_prefix, "manifest", [0],
+ fmri.get_url_path(), ssl_creds=ssl_tuple, imgtype=img.type,
+ method=method, headers=headers, uuid=uuid)[0]
+
+def get_manifest(img, fmri):
+ """Retrieve the manifest for the given fmri. Return it as a buffer to
+ the caller.
+ """
+
+ authority = fmri.tuple()[0]
+ authority = pkg.fmri.strip_auth_pfx(authority)
+ url_prefix = img.get_url_by_authority(authority)
+
try:
- m, v = versioned_urlopen(url_prefix, "manifest", [0],
- fmri.get_url_path(), ssl_creds=ssl_tuple,
- imgtype=img.type, uuid=uuid)
+ m = __get_manifest(img, fmri, "GET")
except urllib2.HTTPError, e:
if e.code in retryable_http_errors:
raise TransferTimedOutException
@@ -86,7 +176,8 @@
except urllib2.URLError, e:
if len(e.args) == 1 and isinstance(e.args[0], socket.sslerror):
raise RuntimeError, e
- elif len(e.args) == 1 and isinstance(e.args[0], socket.timeout):
+ elif len(e.args) == 1 and isinstance(e.args[0],
+ socket.timeout):
raise TransferTimedOutException
raise NameError, "could not retrieve manifest '%s' from '%s'" % \
@@ -96,3 +187,18 @@
(fmri.get_url_path(), url_prefix)
return m.read()
+
+def touch_manifest(img, fmri):
+ """Perform a HEAD operation on the manifest for the given fmri.
+ """
+
+ authority = fmri.get_authority_str()
+ authority = pkg.fmri.strip_auth_pfx(authority)
+ url_prefix = img.get_url_by_authority(authority)
+
+ try:
+ __get_manifest(img, fmri, "HEAD")
+ except:
+ raise NameError, "could not 'touch' manifest '%s' at '%s'" % \
+ (fmri.get_url_path(), url_prefix)
+
--- a/src/modules/misc.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/modules/misc.py Tue Sep 30 19:37:17 2008 -0500
@@ -32,7 +32,6 @@
import socket
import urllib
import urllib2
-import httplib
import urlparse
import sys
import zlib
@@ -81,9 +80,9 @@
(VERSION, portable.util.get_canonical_os_name(), platform.machine(),
portable.util.get_os_release(), platform.version())
-def versioned_urlopen(base_uri, operation, versions = [], tail = None,
+def versioned_urlopen(base_uri, operation, versions = None, tail = None,
data = None, headers = None, ssl_creds = None, imgtype = IMG_NONE,
- uuid = None):
+ method = "GET", uuid = None):
"""Open the best URI for an operation given a set of versions.
Both the client and the server may support multiple versions of
@@ -115,6 +114,9 @@
else:
url_opener = urllib2.urlopen
+ if not versions:
+ versions = []
+
if not headers:
headers = {}
@@ -134,7 +136,11 @@
if uuid:
headers["X-IPkg-UUID"] = uuid
req = urllib2.Request(url = uri, headers = headers)
- if data is not None:
+ if method == "HEAD":
+ # Must override urllib2's get_method since it doesn't
+ # natively support this operation.
+ req.get_method = lambda: "HEAD"
+ elif data is not None:
req.add_data(data)
try:
@@ -432,9 +438,9 @@
self.hashval = hashval
def __str__(self):
- str = "Action with path %s should have hash %s. Computed hash %s instead." % \
- (self.action.attrs["path"], self.action.attrs["chash"],
- self.hashval)
+ str = "Action with path %s should have hash %s. Computed " \
+ "hash %s instead." % (self.action.attrs["path"],
+ self.action.attrs["chash"], self.hashval)
return str
# Default maximum memory useage during indexing
--- a/src/packagemanager.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/packagemanager.py Tue Sep 30 19:37:17 2008 -0500
@@ -917,10 +917,17 @@
gobject.idle_add(self.__update_package_info, pkg, icon,
True, None)
man = None
+ img.history.operation_name = "info"
try:
man = img.get_manifest(pkg, filtered = True)
except IOError:
man = "NotAvailable"
+ img.history.operation_result = \
+ history.RESULT_FAILED_STORAGE
+ except:
+ img.history.operation_result = \
+ history.RESULT_FAILED_UNKNOWN
+
if cmp(self.pkginfo_thread, pkg) == 0:
if not pkg:
gobject.idle_add(self.__update_package_info, pkg, icon, \
@@ -928,7 +935,11 @@
else:
gobject.idle_add(self.__update_package_info, pkg, icon, \
True, man)
+ img.history.operation_result = \
+ history.RESULT_SUCCEEDED
else:
+ img.history.operation_result = \
+ history.RESULT_SUCCEEDED
return
# This function is ported from pkg.actions.generic.distinguished_name()
@@ -1672,6 +1683,8 @@
for the particular version (local operation only), if the package is
not installed than the newest one'''
self.description_thread_running = True
+ img = self.image_o
+ img.history.operation_name = "info"
for pkg in self.application_list:
if self.cancelled:
self.description_thread_running = False
@@ -1698,6 +1711,7 @@
# XXX workaround, this should be done nicer
gobject.idle_add(self.update_desc, info, pkg, package)
time.sleep(0.01)
+ img.history.operation_result = history.RESULT_SUCCEEDED
self.description_thread_running = False
def update_statusbar(self):
--- a/src/tests/api/t_history.py Tue Sep 30 17:59:56 2008 -0500
+++ b/src/tests/api/t_history.py Tue Sep 30 19:37:17 2008 -0500
@@ -209,6 +209,30 @@
loaded_data = loaded_ops[op_name]
self.assertEqual(op_data, loaded_data)
+ def test_5_discarded_operations(self):
+ """Verify that discarded operations are not saved."""
+
+ h = self.__h
+ h.client_name = "pkg-test"
+
+ for op_name in sorted(history.DISCARDED_OPERATIONS):
+ h.operation_name = op_name
+ h.operation_result = history.RESULT_NOTHING_TO_DO
+
+ # Now load all operation data that's been saved during testing
+ # for comparison.
+ loaded_ops = []
+ for entry in sorted(os.listdir(h.path)):
+ # Load the history entry.
+ he = history.History(root_dir=h.root_dir,
+ filename=entry)
+ loaded_ops.append(he.operation_name)
+
+ # Now verify that none of the saved operations are one that
+ # should have been discarded.
+ for op_name in sorted(history.DISCARDED_OPERATIONS):
+ self.assert_(op_name not in loaded_ops)
+
if __name__ == "__main__":
unittest.main()
--- a/src/tests/baseline.txt Tue Sep 30 17:59:56 2008 -0500
+++ b/src/tests/baseline.txt Tue Sep 30 19:37:17 2008 -0500
@@ -95,6 +95,7 @@
api.t_history.py TestHistory.test_2_clear|pass
api.t_history.py TestHistory.test_3_client_load|pass
api.t_history.py TestHistory.test_4_stacked_operations|pass
+api.t_history.py TestHistory.test_5_discarded_operations|pass
api.t_imageconfig.py TestImageConfig.test_missing_conffile|pass
api.t_imageconfig.py TestImageConfig.test_read|pass
api.t_imageconfig.py TestImageConfig.test_unicode|pass