--- a/src/modules/actions/file.py Thu Dec 08 03:41:03 2016 +0530
+++ b/src/modules/actions/file.py Thu Dec 08 03:41:05 2016 +0530
@@ -125,6 +125,34 @@
pkgplan.image.cleanup_downloads()
+ def __set_data(self, pkgplan):
+ """Private helper function to set the data field of the
+ action."""
+
+ hash_attr, hash_attr_val, hash_func = \
+ digest.get_least_preferred_hash(self)
+
+ retrieved = pkgplan.image.imageplan._retrieved
+ retrieved.add(self.get_installed_path(
+ pkgplan.image.get_root()))
+ if len(retrieved) > 50 or \
+ DebugValues['max-plan-execute-retrievals'] == 1:
+ raise api_errors.PlanExecutionError(retrieved)
+
+ # This is an unexpected file retrieval, so the retrieved file
+ # will be streamed directly from the source to the final
+ # destination and will not be stored in the image download
+ # cache.
+ try:
+ pub = pkgplan.image.get_publisher(
+ pkgplan.destination_fmri.publisher)
+ data = pkgplan.image.transport.get_datastream(pub,
+ hash_attr_val)
+ return lambda: data
+ finally:
+ pkgplan.image.cleanup_downloads()
+
+
def install(self, pkgplan, orig):
"""Client-side method that installs a file."""
--- a/src/modules/client/api_errors.py Thu Dec 08 03:41:03 2016 +0530
+++ b/src/modules/client/api_errors.py Thu Dec 08 03:41:05 2016 +0530
@@ -490,6 +490,22 @@
"\n".join(list(self.paths))))
+class PlanExecutionError(InvalidPlanError):
+ """Used to indicate that the requested operation could not be executed
+ due to unexpected changes in image state after planning was completed.
+ """
+
+ def __init__(self, paths):
+ self.paths = paths
+
+ def __str__(self):
+ return _("The files listed below were modified after operation "
+ "planning was complete or were missing during plan "
+ "execution; this may indicate an administrative issue or "
+ "system configuration issue:\n{0}".format(
+ "\n".join(list(self.paths))))
+
+
class PlanCreationException(ApiException):
def __init__(self,
already_installed=EmptyI,
--- a/src/modules/client/transport/transport.py Thu Dec 08 03:41:03 2016 +0530
+++ b/src/modules/client/transport/transport.py Thu Dec 08 03:41:05 2016 +0530
@@ -1161,6 +1161,34 @@
raise failures
@LockedTransport()
+ def get_datastream(self, pub, fhash, ccancel=None):
+ retry_count = global_settings.PKG_CLIENT_MAX_TIMEOUT
+ failures = tx.TransportFailures()
+ header = self.__build_header(uuid=self.__get_uuid(pub))
+
+ for d, retries, v in self.__gen_repo(pub, retry_count,
+ operation="file", versions=[0, 1]):
+
+ repouri_key = d.get_repouri_key()
+ repostats = self.stats[repouri_key]
+ header = Transport.__get_request_header(header,
+ repostats, retries, d)
+ try:
+ return d.get_datastream(fhash, v, header,
+ ccancel=ccancel, pub=pub)
+ except tx.ExcessiveTransientFailure as e:
+ # If an endpoint experienced so many failures
+ # that we just gave up, grab the list of
+ # failures that it contains
+ failures.extend(e.failures)
+ except tx.TransportException as e:
+ if e.retryable:
+ failures.append(e)
+ else:
+ raise
+ raise failures
+
+ @LockedTransport()
def get_content(self, pub, fhash, fmri=None, ccancel=None,
hash_func=None):
"""Given a fhash, return the uncompressed content content from
--- a/src/tests/api/t_pkg_api_install.py Thu Dec 08 03:41:03 2016 +0530
+++ b/src/tests/api/t_pkg_api_install.py Thu Dec 08 03:41:05 2016 +0530
@@ -285,6 +285,45 @@
self.assertRaises(api_errors.PlanExecutionError,
lambda *args, **kwargs: api_obj.execute_plan())
+ def test_needsdata(self):
+ """Ensure graceful failure or successful retrieval if preserved
+ files are modified after image planning or a small number of
+ files are missing."""
+
+ self.dc.start()
+ self.pkgsend_bulk(self.durl, self.foo)
+ api_obj = self.image_create(self.durl)
+
+ # Install [email protected]
+ self.__do_install(api_obj, ["[email protected]"])
+
+ # Now plan an upgrade to [email protected] in which only the mode changes,
+ # but the content has not...
+ api_obj.reset()
+ for pd in api_obj.gen_plan_update(["foo"]):
+ continue
+ api_obj.prepare()
+
+ # Now remove the file before we execute the plan to simulate bad
+ # administrative change for a misbehaving program and verify we
+ # do a one-off retrieval of the file and won't fail.
+ self.file_remove("etc/motd")
+ api_obj.execute_plan()
+
+ self.__do_uninstall(api_obj, ["foo"])
+ self.__do_install(api_obj, ["[email protected]"])
+ api_obj.reset()
+ for pd in api_obj.gen_plan_update(["foo"]):
+ continue
+ api_obj.prepare()
+
+ DebugValues['max-plan-execute-retrievals'] = 1
+ self.file_remove("etc/motd")
+ # Test that we raise an exception if we have to retrieve too
+ # many files.
+ self.assertRaises(api_errors.PlanExecutionError,
+ lambda *args, **kwargs: api_obj.execute_plan())
+
def test_basics_1(self):
""" Send empty package [email protected], install and uninstall """