15631061 file action ops should gracefully handle needsdata changes s11u3-sru
authorsaurabh.vyas@oracle.com
Thu, 08 Dec 2016 03:41:05 +0530
branchs11u3-sru
changeset 3489 8d0be82a8f91
parent 3488 6f6b89648375
child 3490 64baf88ca981
15631061 file action ops should gracefully handle needsdata changes
src/modules/actions/file.py
src/modules/client/api_errors.py
src/modules/client/transport/transport.py
src/tests/api/t_pkg_api_install.py
--- 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 """