Added opportunity to set extra user options.

Change-Id: I191eca8806f92c84896e776ddc8b9263f00947ae
Related-PROD: PROD-28027
(cherry picked from commit a0b79e20af97a54a24e64724d3a3feb81ef28791)
diff --git a/README.rst b/README.rst
index a63393e..e0cd3c1 100644
--- a/README.rst
+++ b/README.rst
@@ -894,6 +894,60 @@
       password_regex_description: 'Your password must contains at least 1 letter, 1 digit, and have a minimum length of 7 characters'
       change_password_upon_first_use: true
 
+
+Define extra user options.
+-------------------------
+
+  To ignore `change_password_upon_first_use` requirement for specific users,
+such as service users, set the `options` attribute `ignore_change_password_upon_first_use`
+to `True` for the desired user via the update user API.
+
+  To ignore `password_expires_days` requirement for specific users,
+such as service users, set the `options` attribute `ignore_password_expiry`
+to `True` for the desired user via the update user API.
+
+  To ignore `lockout_failure_attempts` requirement for specific users,
+such as service users, set the `options` attribute `ignore_lockout_failure_attempts`
+to `True` for the desired user via the update user API.
+
+  Also If there exists a user who should not be able to change her own password via
+the keystone password change API, keystone supports setting that user’s option `lock_password`
+to True via the user update API.
+
+#For release since Q
+.. code-block:: yaml
+
+keystone:
+    client:
+      resources:
+        v3:
+          users:
+            cinder:
+              options:
+                ignore_change_password_upon_first_use: True
+                ignore_password_expiry: False
+                ignore_lockout_failure_attempts: False
+                lock_password: True
+.. code-block::
+
+#For all early releases
+.. code-block:: yaml
+
+keystone:
+  client:
+    server:
+      identity:
+        project:
+          service:
+            user:
+              cinder:
+                options:
+                  ignore_change_password_upon_first_use: True
+                  ignore_password_expiry: False
+                  ignore_lockout_failure_attempts: False
+                  lock_password: True
+.. code-block::
+
 Upgrades
 ========
 
diff --git a/_modules/keystoneng.py b/_modules/keystoneng.py
index 76b6870..ddf0da5 100644
--- a/_modules/keystoneng.py
+++ b/_modules/keystoneng.py
@@ -1082,11 +1082,11 @@
     return ret
 
 
-def user_update(user_id=None, name=None, email=None, enabled=None,
+def user_update(user_id=None, name=None, email=None, enabled=None, options=None,
                 tenant=None, profile=None, project=None, description=None, **connection_args):
     '''
     Update a user's information (keystone user-update)
-    The following fields may be updated: name, email, enabled, tenant.
+    The following fields may be updated: name, email, enabled, tenant, options.
     Because the name is one of the fields, a valid user id is required.
 
     CLI Examples:
@@ -1112,6 +1112,8 @@
         email = user.email
     if enabled is None:
         enabled = user.enabled
+    if not options:
+       options = {}
 
     if _client_version(kstone) > 2:
         if description is None:
@@ -1129,9 +1131,9 @@
             project_id = getattr(user, 'project_id', None)
 
         kstone.users.update(user=user_id, name=name, email=email, enabled=enabled, description=description,
-                            project_id=project_id)
+                            options=options, project_id=project_id)
     else:
-        kstone.users.update(user=user_id, name=name, email=email, enabled=enabled)
+        kstone.users.update(user=user_id, name=name, email=email, options=options, enabled=enabled)
 
         tenant_id = None
         if tenant:
diff --git a/_states/keystoneng.py b/_states/keystoneng.py
index 82ce494..36a0d52 100644
--- a/_states/keystoneng.py
+++ b/_states/keystoneng.py
@@ -105,6 +105,7 @@
                  profile=None,
                  password_reset=True,
                  project=None,
+                 options=None,
                  **connection_args):
     '''
     Ensure that the keystone user is present with the specified properties.
@@ -138,6 +139,9 @@
     enabled
         Availability state for this user
 
+    options
+        Dictionary of extra user options.
+
     roles
         The roles the user should have under given tenants.
         Passed as a dictionary mapping tenant names to a list
@@ -182,6 +186,7 @@
         change_enabled = False
         change_tenant = False
         change_password = False
+        change_options = False
 
         if user[name].get('email', None) != email:
             change_email = True
@@ -200,7 +205,15 @@
                                                           **connection_args)):
             change_password = True
 
-        if __opts__.get('test') and (change_email or change_enabled or change_tenant or change_password):
+        if options:
+            options_to_update = {option: options[option] for option in options
+                                 if (option not in user[name]['options'])
+                                 or (options[option] != user[name]['options'][option])}
+
+            if len(options_to_update):
+                change_options = True
+
+        if __opts__.get('test') and (change_email or change_enabled or change_tenant or change_password or change_options):
             ret['result'] = None
             ret['comment'] = 'User "{0}" will be updated'.format(name)
             if change_email is True:
@@ -211,6 +224,8 @@
                 ret['changes']['Tenant'] = 'Will be added to "{0}" tenant'.format(tenant)
             if change_password is True:
                 ret['changes']['Password'] = 'Will be updated'
+            if change_options is True:
+                ret['changes']['Options'] = 'Will be updated'
             return ret
 
         ret['comment'] = 'User "{0}" is already present'.format(name)
@@ -236,6 +251,11 @@
             ret['comment'] = 'User "{0}" has been updated'.format(name)
             ret['changes']['Password'] = 'Updated'
 
+        if change_options:
+            __salt__['keystoneng.user_update'](name=name, options=options_to_update, profile=profile, **connection_args)
+            ret['comment'] = 'Options has been updated'
+            ret['changes']['Options'] = options_to_update
+
         if roles:
             for tenant in roles:
                 args = dict({'user_name': name, 'tenant_name':
@@ -283,6 +303,7 @@
                                          email=email,
                                          tenant_id=tenant_id,
                                          enabled=enabled,
+                                         options=options,
                                          profile=profile,
                                          **connection_args)
         if roles:
diff --git a/_states/keystonev3.py b/_states/keystonev3.py
index 2dd651d..9f287d5 100644
--- a/_states/keystonev3.py
+++ b/_states/keystonev3.py
@@ -251,15 +251,23 @@
         exact_user = users[0]
         user_id = exact_user['id']
         changable = (
-            'default_project_id', 'domain_id', 'enabled', 'email'
+            'default_project_id', 'domain_id', 'enabled', 'email', 'options'
         )
         if password_reset:
             changable += ('password',)
         to_update = {}
 
         for key in kwargs:
-            if (key in changable and (key not in exact_user or
-                                      kwargs[key] != exact_user[key])):
+            if key in changable:
+                if key == 'options':
+                    to_update['options'] = {option: value for option, value in kwargs['options'].items()
+                                            if (option not in exact_user['options'])
+                                            or (value != exact_user['options'][option])}
+
+                    if not len(to_update['options']):
+                        del to_update['options']
+
+                elif key not in exact_user or kwargs[key] != exact_user[key]:
                     to_update[key] = kwargs[key]
 
         if to_update:
diff --git a/keystone/client/project.sls b/keystone/client/project.sls
index 240c99f..c8179d9 100644
--- a/keystone/client/project.sls
+++ b/keystone/client/project.sls
@@ -33,6 +33,21 @@
   - password: {{ user.password }}
   - email: {{ user.get('email', 'root@localhost') }}
   - tenant: {{ tenant_name }}
+  {%- if user.options is defined %}
+  - options:
+    {%- if user.options.lock_password is defined %}
+      lock_password: {{ user.options.lock_password }}
+    {%- endif %}
+    {%- if user.options.ignore_change_password_upon_first_use is defined %}
+      ignore_change_password_upon_first_use: {{ user.options.ignore_change_password_upon_first_use }}
+    {%- endif %}
+    {%- if user.options.ignore_password_expiry is defined %}
+      ignore_password_expiry: {{ user.options.ignore_password_expiry }}
+    {%- endif %}
+    {%- if user.options.ignore_lockout_failure_attempts is defined %}
+      ignore_lockout_failure_attempts: {{ user.options.ignore_lockout_failure_attempts }}
+    {%- endif %}
+  {%- endif %}
   - roles:
       "{{ tenant_name }}":
         {%- if user.get('is_admin', False) %}
diff --git a/keystone/client/resources/v3.sls b/keystone/client/resources/v3.sls
index 7803233..8f58f0c 100644
--- a/keystone/client/resources/v3.sls
+++ b/keystone/client/resources/v3.sls
@@ -172,6 +172,21 @@
     {%- if user.password_reset is defined %}
     - password_reset: {{ user.password_reset }}
     {%- endif %}
+    {%- if user.options is defined %}
+    - options:
+      {%- if user.options.lock_password is defined %}
+        lock_password: {{ user.options.lock_password }}
+      {%- endif %}
+      {%- if user.options.ignore_change_password_upon_first_use is defined %}
+        ignore_change_password_upon_first_use: {{ user.options.ignore_change_password_upon_first_use }}
+      {%- endif %}
+      {%- if user.options.ignore_password_expiry is defined %}
+        ignore_password_expiry: {{ user.options.ignore_password_expiry }}
+      {%- endif %}
+      {%- if user.options.ignore_lockout_failure_attempts is defined %}
+        ignore_lockout_failure_attempts: {{ user.options.ignore_lockout_failure_attempts }}
+      {%- endif %}
+    {%- endif %}
 
   {%- elif user.get('status', 'present') == 'absent' %}
 
diff --git a/keystone/client/server.sls b/keystone/client/server.sls
index 9f97b74..bf83eeb 100644
--- a/keystone/client/server.sls
+++ b/keystone/client/server.sls
@@ -149,6 +149,21 @@
   - email: {{ user.email }}
   {%- endif %}
   - tenant: {{ tenant_name }}
+  {%- if user.options is defined %}
+  - options:
+    {%- if user.options.lock_password is defined %}
+      lock_password: {{ user.options.lock_password }}
+    {%- endif %}
+    {%- if user.options.ignore_change_password_upon_first_use is defined %}
+      ignore_change_password_upon_first_use: {{ user.options.ignore_change_password_upon_first_use }}
+    {%- endif %}
+    {%- if user.options.ignore_password_expiry is defined %}
+      ignore_password_expiry: {{ user.options.ignore_password_expiry }}
+    {%- endif %}
+    {%- if user.options.ignore_lockout_failure_attempts is defined %}
+      ignore_lockout_failure_attempts: {{ user.options.ignore_lockout_failure_attempts }}
+    {%- endif %}
+  {%- endif %}
   - roles:
       "{{ tenant_name }}":
         {%- if user.get('is_admin', False) %}