7140893 Hide user account configuration in SCI tool if parent of home ZFS dataset is missing
authorJan Damborsky <dambi@opensolaris.org>
Wed, 29 Feb 2012 08:45:58 +0100
changeset 1598 d109a52c861f
parent 1597 595a9de41d71
child 1599 118f135d9d1e
7140893 Hide user account configuration in SCI tool if parent of home ZFS dataset is missing 7140902 In AI manifests, clarify linkage between shared ZFS datasets and sysconfig(1m)
usr/src/cmd/auto-install/manifest/ai_manifest.xml.src
usr/src/cmd/auto-install/manifest/default.xml.src
usr/src/cmd/auto-install/manifest/zone_default.xml.src
usr/src/cmd/system-config/__init__.py
usr/src/cmd/system-config/helpfiles/users.txt
usr/src/cmd/system-config/test/test_users.py
usr/src/cmd/system-config/users.py
--- a/usr/src/cmd/auto-install/manifest/ai_manifest.xml.src	Mon Feb 27 13:40:50 2012 -0800
+++ b/usr/src/cmd/auto-install/manifest/ai_manifest.xml.src	Wed Feb 29 08:45:58 2012 +0100
@@ -19,7 +19,7 @@
 
 CDDL HEADER END
 
-Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 -->
 <!--
 ===============================================================================
@@ -192,6 +192,24 @@
       </disk>
       <logical>
         <zpool name="rpool" is_root="true">
+          <!--
+            Subsequent <filesystem> entries instruct an installer to create
+            following ZFS datasets:
+
+                <root_pool>/export         (mounted on /export)
+                <root_pool>/export/home    (mounted on /export/home)
+
+            Those datasets are part of standard environment and should be
+            always created.
+
+            In rare cases, if there is a need to deploy an installed system
+            without these datasets, either comment out or remove <filesystem>
+            entries. In such scenario, it has to be also assured that
+            in case of non-interactive post-install configuration, creation
+            of initial user account is disabled in related system
+            configuration profile. Otherwise the installed system would fail
+            to boot.
+          -->
           <filesystem name="export" mountpoint="/export"/>
           <filesystem name="export/home"/>
           <be name="solaris"/>
--- a/usr/src/cmd/auto-install/manifest/default.xml.src	Mon Feb 27 13:40:50 2012 -0800
+++ b/usr/src/cmd/auto-install/manifest/default.xml.src	Wed Feb 29 08:45:58 2012 +0100
@@ -19,7 +19,7 @@
 
  CDDL HEADER END
 
- Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 
 -->
 <!DOCTYPE auto_install SYSTEM "file:///usr/share/install/ai.dtd.@DTD_VERSION_AI@">
@@ -28,6 +28,24 @@
     <target>
       <logical>
         <zpool name="rpool" is_root="true">
+          <!--
+            Subsequent <filesystem> entries instruct an installer to create
+            following ZFS datasets:
+
+                <root_pool>/export         (mounted on /export)
+                <root_pool>/export/home    (mounted on /export/home)
+
+            Those datasets are part of standard environment and should be
+            always created.
+
+            In rare cases, if there is a need to deploy an installed system
+            without these datasets, either comment out or remove <filesystem>
+            entries. In such scenario, it has to be also assured that
+            in case of non-interactive post-install configuration, creation
+            of initial user account is disabled in related system
+            configuration profile. Otherwise the installed system would fail
+            to boot.
+          -->
           <filesystem name="export" mountpoint="/export"/>
           <filesystem name="export/home"/>
           <be name="solaris"/>
--- a/usr/src/cmd/auto-install/manifest/zone_default.xml.src	Mon Feb 27 13:40:50 2012 -0800
+++ b/usr/src/cmd/auto-install/manifest/zone_default.xml.src	Wed Feb 29 08:45:58 2012 +0100
@@ -19,7 +19,7 @@
 
  CDDL HEADER END
 
- Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 
 -->
 <!DOCTYPE auto_install SYSTEM "file:///usr/share/install/ai.dtd.@DTD_VERSION_AI@">
@@ -29,6 +29,24 @@
         <target>
             <logical>
                 <zpool name="rpool">
+                    <!--
+                      Subsequent <filesystem> entries instruct an installer
+                      to create following ZFS datasets:
+
+                          <root_pool>/export         (mounted on /export)
+                          <root_pool>/export/home    (mounted on /export/home)
+
+                      Those datasets are part of standard environment
+                      and should be always created.
+
+                      In rare cases, if there is a need to deploy a zone
+                      without these datasets, either comment out or remove
+                      <filesystem> entries. In such scenario, it has to be also
+                      assured that in case of non-interactive post-install
+                      configuration, creation of initial user account is
+                      disabled in related system configuration profile.
+                      Otherwise the installed zone would fail to boot.
+                    -->
                     <filesystem name="export" mountpoint="/export"/>
                     <filesystem name="export/home"/>
                     <be name="solaris">
--- a/usr/src/cmd/system-config/__init__.py	Mon Feb 27 13:40:50 2012 -0800
+++ b/usr/src/cmd/system-config/__init__.py	Wed Feb 29 08:45:58 2012 +0100
@@ -234,7 +234,10 @@
     result.append(TimeZone(main_win, screen=TimeZone.LOCATIONS))
     result.append(TimeZone(main_win))
     result.append(DateTimeScreen(main_win))
-    result.append(UserScreen(main_win))
+
+    # All screens are to be brought up, so make sure complete user screen
+    # (root password as well as initial user account) is displayed.
+    result.append(UserScreen(main_win, show_user_account=True))
 
     return result
 
--- a/usr/src/cmd/system-config/helpfiles/users.txt	Mon Feb 27 13:40:50 2012 -0800
+++ b/usr/src/cmd/system-config/helpfiles/users.txt	Wed Feb 29 08:45:58 2012 +0100
@@ -2,6 +2,8 @@
 
 In this screen, you can type a root password and define a user account for your installed system.
 
+Note: When a user account is added, the home directory is created on the <root_pool>/export/home/<login> ZFS dataset. The parent of that dataset, <root_pool>/export/home, must exist in order for you to be allowed to create the user account.
+
 GUIDELINE
 
      * Both the root password and user account are optional. But, completing all fields in the screen is recommended.
--- a/usr/src/cmd/system-config/test/test_users.py	Mon Feb 27 13:40:50 2012 -0800
+++ b/usr/src/cmd/system-config/test/test_users.py	Wed Feb 29 08:45:58 2012 +0100
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 '''
@@ -101,6 +101,7 @@
         self.UserScreen__init__ = UserScreen.__init__
         UserScreen.__init__ = lambda x, y: None
         self.user_screen = UserScreen(None)
+        self.user_screen.show_user_account = True
     
     def tearDown(self):
         UserScreen.__init__ = self.UserScreen__init__
--- a/usr/src/cmd/system-config/users.py	Mon Feb 27 13:40:50 2012 -0800
+++ b/usr/src/cmd/system-config/users.py	Wed Feb 29 08:45:58 2012 +0100
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 '''
@@ -28,6 +28,7 @@
 
 import logging
 
+from solaris_install import CalledProcessError, run
 from solaris_install.logger import INSTALL_LOGGER_NAME
 from solaris_install.sysconfig import _, SCI_HELP
 import solaris_install.sysconfig.profile
@@ -44,6 +45,9 @@
 
 LOGGER = None
 
+# Consumed CLIs
+ZFS = "/usr/sbin/zfs"
+
 
 class UserScreen(BaseScreen):
     '''Allow user to set:
@@ -55,8 +59,11 @@
     '''
     
     HEADER_TEXT = _("Users")
-    INTRO = _("Define a root password for the system and user account for"
-              " yourself.")
+    INTRO_SHOW_USER = _("Define a root password for the system and user "
+                        "account for yourself.")
+    INTRO_HIDE_USER = _("Define a root password for the system. Creation "
+                        "of user account is disabled. See Help for "
+                        "additional information.")
     ROOT_TEXT = _("System Root Password")
     ROOT_LABEL = _("Root password:")
     CONFIRM_LABEL = _("Confirm password:")
@@ -70,7 +77,7 @@
     PASS_SCREEN_LEN = 16  # also used as column width for user input
     ITEM_OFFSET = 2
     
-    def __init__(self, main_win):
+    def __init__(self, main_win, show_user_account=None):
         global LOGGER
         if LOGGER is None:
             LOGGER = logging.getLogger(INSTALL_LOGGER_NAME + ".sysconfig")
@@ -116,7 +123,27 @@
         self.user_confirm_err = None
         self.user_confirm_list = None
         self.user_confirm_edit = None
-    
+
+        #
+        # If show_user_account is True, complete Users screen will be
+        # displayed (root password edit fields as well as widgets related
+        # to creating initial user account). Otherwise, widgets for initial
+        # user account are hidden.
+        #
+        # If not initialized explicitly by caller (e.g. text installer),
+        # show_user_account is set to True only if all conditions needed
+        # for successful creation of user account are met.
+        #
+        # In particular, existence of parent of home ZFS dataset is checked,
+        # since if parent of home ZFS dataset was missing, then
+        # svc:/system/config-user smf service (responsible for configuring
+        # root and user accounts) would fail to create user account. As
+        # a result of that, the whole system would end up in maintenance mode.
+        #
+        self.show_user_account = show_user_account
+        if self.show_user_account is None:
+            self.show_user_account = self.home_zfs_parent_exists()
+
     def _show(self):
         '''Display the user name, real name, and password fields'''
         
@@ -128,10 +155,10 @@
         #
         if sc_profile.users is None:
             #
-            # assume root is a role. root is later changed to normal account
-            # if user account is not created
+            # Assume root is a normal account. It will be changed to a role
+            # if the user account is created.
             #
-            root = UserInfo(login_name="root", is_role=True)
+            root = UserInfo(login_name="root", is_role=False)
 
             #
             # Initialize attributes of user account which can't be configured
@@ -153,8 +180,13 @@
         
         y_loc = 1
         
-        self.center_win.add_paragraph(UserScreen.INTRO, y_loc, 1, y_loc + 2,
-                                      self.win_size_x - 1)
+        if self.show_user_account:
+            intro_text = UserScreen.INTRO_SHOW_USER
+        else:
+            intro_text = UserScreen.INTRO_HIDE_USER
+
+        self.center_win.add_paragraph(intro_text, y_loc, 1,
+                                      y_loc + 2, self.win_size_x - 1)
         
         y_loc += 3
         self.center_win.add_text(UserScreen.ROOT_TEXT, y_loc, 1,
@@ -187,7 +219,13 @@
                                                error_win=self.root_confirm_err)
         rc_edit_kwargs = {"linked_win": self.root_pass_edit}
         self.root_confirm_edit.on_exit_kwargs = rc_edit_kwargs
-        
+
+        # User account disabled, do not display related widgets.
+        if not self.show_user_account:
+            self.main_win.do_update()
+            self.center_win.activate_object(self.root_pass_list)
+            return
+
         y_loc += 2
         self.center_win.add_text(UserScreen.USER_TEXT, y_loc, 1,
                                  self.win_size_x - 1)
@@ -251,11 +289,8 @@
         self.center_win.activate_object(self.root_pass_list)
     
     def on_change_screen(self):
-        '''Save real name and login name always'''
-        self.user.real_name = self.real_name_edit.get_text()
-        self.user.login_name = self.username_edit.get_text()
-        self.root.is_role = bool(self.user.login_name)
-        
+        '''Store entered information into data object cache.'''
+
         if self.root_pass_edit.modified:
             if self.root_pass_edit.compare(self.root_confirm_edit):
                 self.root.password = self.root_pass_edit.get_text()
@@ -267,6 +302,19 @@
         
         self.root_pass_edit.clear_text()
         self.root_confirm_edit.clear_text()
+
+        #
+        # User account disabled, do not store related info into data object
+        # cache.
+        #
+        if not self.show_user_account:
+            return
+
+        self.user.real_name = self.real_name_edit.get_text()
+        self.user.login_name = self.username_edit.get_text()
+
+        # If user account is to be created, configure root account as a role.
+        self.root.is_role = bool(self.user.login_name)
         
         if self.user_pass_edit.modified:
             if self.user_pass_edit.compare(self.user_confirm_edit):
@@ -282,26 +330,16 @@
     
     def validate(self):
         '''Check for mismatched passwords, bad login names, etc.'''
-        if self.user_pass_edit.modified:
-            user_pass_set = bool(self.user_pass_edit.get_text())
-        else:
-            user_pass_set = (self.user.passlen != 0)
-        
-        if self.root_pass_edit.modified:
-            root_pass_set = bool(self.root_pass_edit.get_text())
-        else:
-            root_pass_set = (self.root.passlen != 0)
-        
-        login_name = self.username_edit.get_text()
-        LOGGER.debug("login_name=%s", login_name)
-        real_name = self.real_name_edit.get_text()
-        LOGGER.debug("real_name=%s", real_name)
-        
         # Note: the Root and User password fields may be filled with
         # asterisks if the user previously invoked this screen.  Therefore,
         # if not empty we check their modified flags before validating the
         # contents.
 
+        if self.root_pass_edit.modified:
+            root_pass_set = bool(self.root_pass_edit.get_text())
+        else:
+            root_pass_set = (self.root.passlen != 0)      
+
         # Root password is mandatory and, if modified, must be valid
         if not root_pass_set or self.root_pass_edit.modified:
             pass_valid(self.root_pass_edit,
@@ -310,6 +348,20 @@
         if not self.root_pass_edit.compare(self.root_confirm_edit):
             raise UIMessage(_("Root passwords don't match"))
 
+        # User account disabled, skip related validation steps.
+        if not self.show_user_account:
+            return
+
+        if self.user_pass_edit.modified:
+            user_pass_set = bool(self.user_pass_edit.get_text())
+        else:
+            user_pass_set = (self.user.passlen != 0)
+        
+        login_name = self.username_edit.get_text()
+        LOGGER.debug("login_name=%s", login_name)
+        real_name = self.real_name_edit.get_text()
+        LOGGER.debug("real_name=%s", real_name)
+        
         # Username (if specified) must be valid
         login_valid(self.username_edit)
 
@@ -329,6 +381,35 @@
                 raise UIMessage(_("Enter username or clear all user "
                                   "account fields"))
 
+    def home_zfs_parent_exists(self):
+        '''Return True if parent of to-be-created home ZFS dataset exists.
+        Otherwise, return False.
+
+        Home ZFS dataset is created in form of <root_pool>/export/home/<login>
+        with mountpoint /export/home/<login> inherited from parent dataset.
+
+        The check verifies that ZFS dataset with to-be-inherited /export/home
+        mountpoint exists.
+        '''
+
+        # obtain mountpoints for all existing ZFS datasets
+        cmd = [ZFS, "list", "-H", "-o", "mountpoint"]
+        try:
+            mountpoint_list = run(cmd, logger=LOGGER)
+        except CalledProcessError:
+            LOGGER.warn("Could not determine if parent of home ZFS dataset "
+                        "exists.")
+            return False
+
+        if "/export/home" not in mountpoint_list.stdout.splitlines():
+            LOGGER.debug("Parent of home ZFS dataset does not exist, creation "
+                         "of user account disabled.")
+            return False
+
+        LOGGER.debug("Parent of home ZFS dataset exists, creation of user "
+                     "account enabled.")
+        return True
+
 
 def username_valid(edit_field):
     '''Validate username'''