7145997 support for avoid lists and reject during install action should be added.
authorDarren Kenny <Darren.Kenny@Oracle.COM>
Tue, 24 Apr 2012 17:53:52 +0100
changeset 1650 0a799935ac50
parent 1649 7dc7a57d562a
child 1651 4765297a2603
7145997 support for avoid lists and reject during install action should be added.
usr/src/cmd/auto-install/manifest/ai_manifest.xml.src
usr/src/lib/install_manifest/dtd/software.dtd
usr/src/lib/install_transfer/info.py
usr/src/lib/install_transfer/ips.py
usr/src/lib/install_transfer/test/test_info.py
usr/src/lib/install_transfer/test/test_ips.py
--- a/usr/src/cmd/auto-install/manifest/ai_manifest.xml.src	Mon Apr 23 22:00:33 2012 -0600
+++ b/usr/src/cmd/auto-install/manifest/ai_manifest.xml.src	Tue Apr 24 17:53:52 2012 +0100
@@ -273,11 +273,45 @@
         form:
       
             <name="[email protected]#"/>
+
+        It is possible to specify one or more packages that are to be
+        rejected, when evaluating what is to be installed, by using one or
+        more 'reject' tags, for example:
+
+          <software_data>
+            <name>pkg:/entire@latest</name>
+            <name>pkg:/group/system/solaris-large-server</name>
+            <reject>package/to/reject</reject>
+            <reject>another/package/to/reject</reject>
+          </software_data>
+
+        this is the equivalent of the 'pkg install -reject' option.
+
+        The main benefit of using the <reject> over an 'uninstall' action is
+        that these packages are rejected during the plan for the install
+        action as opposed to requiring a new plan like the 'uninstall' action
+        does.
       -->
       <software_data>
         <name>pkg:/entire@latest</name>
         <name>pkg:/group/system/solaris-large-server</name>
       </software_data>
+
+      <!--
+        It is possible to request that any future install specifically avoids
+        any attempts to install some packages that would be otherwise selected
+        by the 'install' action (above), or future 'pkg install' commands.
+
+        The avoid list is stored as part of the image, so is persistent. 
+
+        To remove a package from the avoid list, you can use the action of
+        'unavoid' to remove it.
+       -->
+      <!--
+      <software_data action="avoid">
+        <name>pkg:/unwanted/pkg</name>
+      </software_data>
+      -->
     </software>
     <add_drivers>
       <!--
--- a/usr/src/lib/install_manifest/dtd/software.dtd	Mon Apr 23 22:00:33 2012 -0600
+++ b/usr/src/lib/install_manifest/dtd/software.dtd	Tue Apr 24 17:53:52 2012 +0100
@@ -18,7 +18,7 @@
 
  CDDL HEADER END
 
- Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 
 -->
 
@@ -39,19 +39,26 @@
 <!ATTLIST software name CDATA #IMPLIED>
 <!ATTLIST software type (IPS|SVR4|CPIO|ARCHIVE|IMAGE|P5I|DU|P5P) "IPS">
 
-<!ELEMENT software_data (name*)>
-<!ATTLIST software_data action (install|uninstall|unpack|noinstall) "install">
+<!ELEMENT software_data (name|reject)*>
+<!ATTLIST software_data action (install|uninstall|unpack|avoid|unavoid) "install">
 <!--
 	size is the total size of all the data specified
-        in this software_data section.  This value can
-        be specified for both install and uninstall actions.
-        Size specified is in bytes.  This attribute is optional.
+	in this software_data section.  This value can
+	be specified for both install and uninstall actions.
+	Size specified is in bytes.  This attribute is optional.
 -->
 <!ATTLIST software_data size CDATA #IMPLIED>
 
 <!ELEMENT name (#PCDATA)>
 
 <!--
+    reject is used to signal to an IPS install action that the value
+    specified is to be rejected. It has no effect for any other
+    transfer type.
+-->
+<!ELEMENT reject (#PCDATA)>
+
+<!--
 	Destination element is not required. If specified there can only
 	be one destination per software element. If not specified,
 	the destination is assumed to be an ipkg image and will be
--- a/usr/src/lib/install_transfer/info.py	Mon Apr 23 22:00:33 2012 -0600
+++ b/usr/src/lib/install_transfer/info.py	Tue Apr 24 17:53:52 2012 +0100
@@ -38,6 +38,7 @@
 IPS = "IPS"
 IPS_ARGS = "ips_args"
 PURGE_HISTORY = "purge_history"
+REJECT_LIST = "reject_list"
 SIZE = "size"
 SOFTWARE_DATA = "software_data"
 SVR4_ARGS = "svr4_args"
@@ -125,11 +126,16 @@
                         action = IPSSpec.INSTALL
 
                     pkg_list = list()
-                    for name in sub.iterchildren(tag="name"):
-                        pkg_list.append(name.text.strip('"\n\t '))
+                    reject_list = list()
+                    for child in sub.getchildren():
+                        if child.tag == IPSSpec.IPS_NAME_LABEL:
+                            pkg_list.append(child.text.strip('"\n\t '))
+                        elif child.tag == IPSSpec.IPS_REJECT_LABEL:
+                            reject_list.append(child.text.strip('"\n\t '))
 
                     transfer_obj.action = action
                     transfer_obj.contents = pkg_list
+                    transfer_obj.reject_list = reject_list
 
                 elif val == "CPIO":
                     transfer_obj = CPIOSpec()
@@ -623,14 +629,16 @@
     IPS_SOFTWARE_DATA_LABEL = "software_data"
     IPS_ACTION_LABEL = "action"
     IPS_NAME_LABEL = "name"
+    IPS_REJECT_LABEL = "reject"
     INSTALL = "install"
     UNINSTALL = "uninstall"
 
-    def __init__(self, action=None, contents=None,
+    def __init__(self, action=None, contents=None, reject_list=None,
                  app_callback=None, purge_history=False):
         super(IPSSpec, self).__init__(IPSSpec.IPS_TRANSFER_LABEL)
         self.action = action
         self.contents = contents
+        self.reject_list = reject_list
         self.app_callback = app_callback
         self.purge_history = purge_history
 
@@ -647,6 +655,9 @@
         for pkg in self.contents:
             sub_element = etree.SubElement(element, IPSSpec.IPS_NAME_LABEL)
             sub_element.text = pkg
+        for pkg in self.reject_list:
+            sub_element = etree.SubElement(element, IPSSpec.IPS_REJECT_LABEL)
+            sub_element.text = pkg
         return element
 
     @classmethod
@@ -667,14 +678,17 @@
         if action is None:
             action = IPSSpec.INSTALL
 
-        pkg_list = []
-        names = element.getchildren()
-
-        for name in names:
-            pkg_list.append(name.text.strip('"\n\t '))
+        pkg_list = list()
+        reject_list = list()
+        for child in element.getchildren():
+            if child.tag == IPSSpec.IPS_NAME_LABEL:
+                pkg_list.append(child.text.strip('"\n\t '))
+            elif child.tag == IPSSpec.IPS_REJECT_LABEL:
+                reject_list.append(child.text.strip('"\n\t '))
 
         transfer_obj.action = action
         transfer_obj.contents = pkg_list
+        transfer_obj.reject_list = reject_list
         return transfer_obj
 
 
--- a/usr/src/lib/install_transfer/ips.py	Mon Apr 23 22:00:33 2012 -0600
+++ b/usr/src/lib/install_transfer/ips.py	Tue Apr 24 17:53:52 2012 +0100
@@ -57,7 +57,7 @@
 from solaris_install.transfer.info import Software
 from solaris_install.transfer.info import Source
 from solaris_install.transfer.info import ACTION, CONTENTS, \
-PURGE_HISTORY, APP_CALLBACK, IPS_ARGS, UPDATE_INDEX
+PURGE_HISTORY, APP_CALLBACK, IPS_ARGS, UPDATE_INDEX, REJECT_LIST
 from solaris_install.transfer.prog import ProgressMon
 
 LICENSE_ACCEPTED = "automatically accepted"
@@ -619,6 +619,11 @@
                         self.properties[prop] = str(self.properties[prop])
                     img.set_property(prop, self.properties[prop])
 
+        # Refresh publishers now that we've set the publishers,  otherwise
+        # avoid/unavoid will not work and will cause install failures
+        if not self.dry_run:
+            self.api_inst.refresh(immediate=True)
+
         # Perform the transfer specific operations.
         for trans_val in self._transfer_list:
             if trans_val.get(ACTION) == "install":
@@ -628,15 +633,30 @@
                     self.logger.info("Installing packages from:")
                     self.print_repository_uris()
 
+                    reject_list = trans_val.get(REJECT_LIST)
+                    if reject_list:
+                        self.logger.info(
+                            "Transfer set to reject packages matching:")
+                        for pkg in reject_list:
+                            self.logger.info("  %s", pkg)
+                    else:
+                        # Set the reject list to be the default value rather
+                        # than None
+                        self.logger.debug(
+                            "Transfer reject package list is empty")
+                        reject_list = misc.EmptyI
+
                     if not self.dry_run:
                         # Install packages
                         if trans_val.get(IPS_ARGS):
                             self.api_inst.plan_install(
                                      pkg_list=trans_val.get(CONTENTS),
+                                     reject_list=reject_list,
                                      **trans_val.get(IPS_ARGS))
                         else:
                             self.api_inst.plan_install(
-                                    pkg_list=trans_val.get(CONTENTS))
+                                    pkg_list=trans_val.get(CONTENTS),
+                                    reject_list=reject_list)
 
                         if callback:
                             # execute the callback function passing it
@@ -714,8 +734,23 @@
                     self.api_inst.execute_plan()
                     self.api_inst.reset()
 
-                else:
-                    self.logger.debug("Dry Run: Uninstalling packages")
+            elif trans_val.get(ACTION) == "avoid":
+                self.logger.info("Setting packages to avoid:")
+                avoid_list = trans_val.get(CONTENTS)
+                for pkg in avoid_list:
+                    self.logger.info("  %s", pkg)
+                if not self.dry_run:
+                    # Avoid packages
+                    self.api_inst.avoid_pkgs(avoid_list)
+
+            elif trans_val.get(ACTION) == "unavoid":
+                self.logger.info("Removing packages from list to avoid:")
+                unavoid_list = trans_val.get(CONTENTS)
+                for pkg in unavoid_list:
+                    self.logger.info("  %s", pkg)
+                if not self.dry_run:
+                    # Avoid packages
+                    self.api_inst.avoid_pkgs(unavoid_list, unavoid=True)
 
             if trans_val.get(PURGE_HISTORY):
                 # purge history if requested.
@@ -812,7 +847,7 @@
                     pkg_client_name=PKG_CLIENT_NAME,
                     version_id=PKG5_API_VERSION, root=self.dst,
                     imgtype=self.completeness, is_zone=self.is_zone,
-                    force=True, **self._image_args)
+                    refresh_allowed=False, force=True, **self._image_args)
 
                 # The above call will end up leaving our process's cwd in
                 # the image's root area, which will cause pain later on
@@ -994,6 +1029,7 @@
             trans_attr = dict()
             trans_attr[ACTION] = trans.action
             trans_attr[CONTENTS] = trans.contents
+            trans_attr[REJECT_LIST] = trans.reject_list
             trans_attr[PURGE_HISTORY] = trans.purge_history
             trans_attr[APP_CALLBACK] = trans.app_callback
 
@@ -1146,6 +1182,7 @@
         # Attributes per transfer
         self.action = None
         self.contents = None
+        self.reject_list = None
         self.purge_history = False
         self.app_callback = None
         self.args = {}
@@ -1217,6 +1254,7 @@
 
         trans_attr[ACTION] = self.action
         trans_attr[CONTENTS] = self.contents
+        trans_attr[REJECT_LIST] = self.reject_list
         trans_attr[PURGE_HISTORY] = self.purge_history
         trans_attr[APP_CALLBACK] = self.app_callback
         if not self.index:
--- a/usr/src/lib/install_transfer/test/test_info.py	Mon Apr 23 22:00:33 2012 -0600
+++ b/usr/src/lib/install_transfer/test/test_info.py	Tue Apr 24 17:53:52 2012 +0100
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 '''Tests for the Transfer Info interface'''
@@ -358,6 +358,7 @@
                 self.assertEqual(img_type[0].zone, False)
                 self.assertEqual(tr.action, None)
                 self.assertEqual(tr.contents, None)
+                self.assertEqual(tr.reject_list, None)
                 self.assertEqual(tr.app_callback, None)
                 self.assertEqual(tr.purge_history, False)
 
@@ -393,6 +394,138 @@
                 self.assertEqual(tr.purge_history, False)
                 self.assertEqual(tr.app_callback, None)
 
+        # pkg reject_list is set
+        tr_node.action = "install"
+        tr_node.contents = ["SUNWcs"]
+        tr_node.reject_list = ["to/be/rejected"]
+        soft_list = self.doc.get_children("transfer test 1", Software)
+        for soft in soft_list:
+            tr_list = soft.get_children(class_type=IPSSpec)
+            for tr in tr_list:
+                src_list = soft.get_children("source", Source)
+                self.assertEqual(len(src_list), 1)
+
+                pub = src_list[0].get_children("publisher", Publisher)
+                origin = pub[0].get_children("origin", Origin)
+                self.assertEqual(origin[0].origin,
+                    "http://pkg.oracle.com/solaris/release")
+
+                dst_list = soft.get_children("destination", Destination)
+                self.assertEqual(len(dst_list), 1)
+
+                image = dst_list[0].get_children("image", Image)
+                self.assertEqual(len(image), 1)
+
+                img_type = image[0].get_children("img_type", ImType)
+                self.assertEqual(len(img_type), 1)
+
+                self.assertEqual(image[0].img_root, "/rpool/dc")
+                self.assertEqual(image[0].action, "create")
+                self.assertEqual(img_type[0].completeness, "full")
+                self.assertEqual(img_type[0].zone, False)
+                self.assertEqual(tr.action, "install")
+                self.assertEqual(tr.contents, ["SUNWcs"])
+                self.assertEqual(tr.reject_list, ["to/be/rejected"])
+                self.assertEqual(tr.purge_history, False)
+                self.assertEqual(tr.app_callback, None)
+
+        # pkg uninstall list is set
+        tr_node.action = "uninstall"
+        tr_node.contents = ["SUNWcs"]
+        soft_list = self.doc.get_children("transfer test 1", Software)
+        for soft in soft_list:
+            tr_list = soft.get_children(class_type=IPSSpec)
+            for tr in tr_list:
+                src_list = soft.get_children("source", Source)
+                self.assertEqual(len(src_list), 1)
+
+                pub = src_list[0].get_children("publisher", Publisher)
+                origin = pub[0].get_children("origin", Origin)
+                self.assertEqual(origin[0].origin,
+                    "http://pkg.oracle.com/solaris/release")
+
+                dst_list = soft.get_children("destination", Destination)
+                self.assertEqual(len(dst_list), 1)
+
+                image = dst_list[0].get_children("image", Image)
+                self.assertEqual(len(image), 1)
+
+                img_type = image[0].get_children("img_type", ImType)
+                self.assertEqual(len(img_type), 1)
+
+                self.assertEqual(image[0].img_root, "/rpool/dc")
+                self.assertEqual(image[0].action, "create")
+                self.assertEqual(img_type[0].completeness, "full")
+                self.assertEqual(img_type[0].zone, False)
+                self.assertEqual(tr.action, "uninstall")
+                self.assertEqual(tr.contents, ["SUNWcs"])
+                self.assertEqual(tr.purge_history, False)
+                self.assertEqual(tr.app_callback, None)
+
+        # pkg avoid list is set
+        tr_node.action = "avoid"
+        tr_node.contents = ["SUNWcs", "SUNWcsr"]
+        soft_list = self.doc.get_children("transfer test 1", Software)
+        for soft in soft_list:
+            tr_list = soft.get_children(class_type=IPSSpec)
+            for tr in tr_list:
+                src_list = soft.get_children("source", Source)
+                self.assertEqual(len(src_list), 1)
+
+                pub = src_list[0].get_children("publisher", Publisher)
+                origin = pub[0].get_children("origin", Origin)
+                self.assertEqual(origin[0].origin,
+                    "http://pkg.oracle.com/solaris/release")
+
+                dst_list = soft.get_children("destination", Destination)
+                self.assertEqual(len(dst_list), 1)
+
+                image = dst_list[0].get_children("image", Image)
+                self.assertEqual(len(image), 1)
+                img_type = image[0].get_children("img_type", ImType)
+                self.assertEqual(len(img_type), 1)
+
+                self.assertEqual(image[0].img_root, "/rpool/dc")
+                self.assertEqual(image[0].action, "create")
+                self.assertEqual(img_type[0].completeness, "full")
+                self.assertEqual(img_type[0].zone, False)
+                self.assertEqual(tr.contents, ["SUNWcs", "SUNWcsr"])
+                self.assertEqual(tr.action, "avoid")
+                self.assertEqual(tr.purge_history, False)
+                self.assertEqual(tr.app_callback, None)
+
+        # pkg unavoid list is set
+        tr_node.action = "unavoid"
+        tr_node.contents = ["SUNWcs", "SUNWcsr"]
+        soft_list = self.doc.get_children("transfer test 1", Software)
+        for soft in soft_list:
+            tr_list = soft.get_children(class_type=IPSSpec)
+            for tr in tr_list:
+                src_list = soft.get_children("source", Source)
+                self.assertEqual(len(src_list), 1)
+
+                pub = src_list[0].get_children("publisher", Publisher)
+                origin = pub[0].get_children("origin", Origin)
+                self.assertEqual(origin[0].origin,
+                    "http://pkg.oracle.com/solaris/release")
+
+                dst_list = soft.get_children("destination", Destination)
+                self.assertEqual(len(dst_list), 1)
+
+                image = dst_list[0].get_children("image", Image)
+                self.assertEqual(len(image), 1)
+                img_type = image[0].get_children("img_type", ImType)
+                self.assertEqual(len(img_type), 1)
+
+                self.assertEqual(image[0].img_root, "/rpool/dc")
+                self.assertEqual(image[0].action, "create")
+                self.assertEqual(img_type[0].completeness, "full")
+                self.assertEqual(img_type[0].zone, False)
+                self.assertEqual(tr.contents, ["SUNWcs", "SUNWcsr"])
+                self.assertEqual(tr.action, "unavoid")
+                self.assertEqual(tr.purge_history, False)
+                self.assertEqual(tr.app_callback, None)
+
         # pkg uninstall list is set
         tr_node.action = "uninstall"
         tr_node.contents = ["SUNWcs"]
--- a/usr/src/lib/install_transfer/test/test_ips.py	Mon Apr 23 22:00:33 2012 -0600
+++ b/usr/src/lib/install_transfer/test/test_ips.py	Tue Apr 24 17:53:52 2012 +0100
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import unittest
@@ -381,6 +381,92 @@
         except Exception as err:
             self.fail(str(err))
 
+    def test_install_reject_noargs(self):
+        '''Test that an IPS package can be rejected'''
+        self.tr_node.action = "install"
+        self.tr_node.contents = ["solaris-large-server"]
+        self.tr_node.reject_list = ["slocate"]
+        self.tr_node2 = IPSSpec()
+        self.tr_node2.action = "install"
+        self.tr_node2.contents = ["system/library/svm-rcm"]
+        self.soft_node.insert_children([self.tr_node2])
+        try:
+            self.tr_ips.execute(dry_run=DRY_RUN)
+        except Exception as err:
+            self.fail(str(err))
+
+    def test_install_reject_with_args(self):
+        '''Test that an IPS package can be rejected with args'''
+        install_args = Args(arg_dict={"update_index": False})
+        self.tr_node.action = "install"
+        self.tr_node.reject_list = ["slocate"]
+        self.tr_node.contents = ["solaris-large-server"]
+        self.tr_node.insert_children([install_args])
+        self.tr_node2 = IPSSpec()
+        self.tr_node2.action = "install"
+        self.tr_node2.contents = ["system/library/svm-rcm"]
+        self.soft_node.insert_children([self.tr_node2])
+        try:
+            self.tr_ips.execute(dry_run=DRY_RUN)
+        except Exception as err:
+            self.fail(str(err))
+
+    def test_avoid_noargs(self):
+        '''Test that an IPS package can be avoided'''
+        self.tr_node.action = "avoid"
+        self.tr_node.contents = ["solaris-large-server"]
+        self.tr_node2 = IPSSpec()
+        self.tr_node2.action = "avoid"
+        self.tr_node2.contents = ["system/library/svm-rcm"]
+        self.soft_node.insert_children([self.tr_node2])
+        try:
+            self.tr_ips.execute(dry_run=DRY_RUN)
+        except Exception as err:
+            self.fail(str(err))
+
+    def test_avoid_with_args(self):
+        '''Test that an IPS package can be avoided with args'''
+        install_args = Args(arg_dict={"update_index": False})
+        self.tr_node.action = "avoid"
+        self.tr_node.contents = ["solaris-large-server"]
+        self.tr_node.insert_children([install_args])
+        self.tr_node2 = IPSSpec()
+        self.tr_node2.action = "avoid"
+        self.tr_node2.contents = ["system/library/svm-rcm"]
+        self.soft_node.insert_children([self.tr_node2])
+        try:
+            self.tr_ips.execute(dry_run=DRY_RUN)
+        except Exception as err:
+            self.fail(str(err))
+
+    def test_unavoid_noargs(self):
+        '''Test that an IPS package can be unavoided'''
+        self.tr_node.action = "unavoid"
+        self.tr_node.contents = ["solaris-large-server"]
+        self.tr_node2 = IPSSpec()
+        self.tr_node2.action = "unavoid"
+        self.tr_node2.contents = ["system/library/svm-rcm"]
+        self.soft_node.insert_children([self.tr_node2])
+        try:
+            self.tr_ips.execute(dry_run=DRY_RUN)
+        except Exception as err:
+            self.fail(str(err))
+
+    def test_unavoid_with_args(self):
+        '''Test that an IPS package can be rejected with args'''
+        install_args = Args(arg_dict={"update_index": False})
+        self.tr_node.action = "unavoid"
+        self.tr_node.contents = ["solaris-large-server"]
+        self.tr_node.insert_children([install_args])
+        self.tr_node2 = IPSSpec()
+        self.tr_node2.action = "unavoid"
+        self.tr_node2.contents = ["system/library/svm-rcm"]
+        self.soft_node.insert_children([self.tr_node2])
+        try:
+            self.tr_ips.execute(dry_run=DRY_RUN)
+        except Exception as err:
+            self.fail(str(err))
+
     def test_default_publisher(self):
         '''Test that using the default publisher succeeds'''
         src = Source()