Configure nova to use service user tokens

Long-running operations such as live migration or snapshot can
sometimes overrun the expiry of the user token.
In such cases, post operations such as cleaning up after a live
migration can fail when the nova-compute service needs to cleanup
resources in other services, such as in the block-storage (cinder)
or networking (neutron) services.

This patch enables nova to use service user tokens to supplement
the regular user token used to initiate the operation.
The identity service (keystone) will then authenticate a request
using the service user token if the user token has already expired.

Change-Id: I203f2dfc97bdc65dd424e1085ce2e20a5e9dbf40
Related-Prod: PROD-27591
(cherry picked from commit 6d821f5893a02b40b5bc0477b568b6de03590ad7)

Fix typo in nova service user configuration

Change-Id: I2f16217d3c3ba8abf0313527b813fc1592de4088
Related-Prod: https://mirantis.jira.com/browse/PROD-27864
Related-Prod: https://mirantis.jira.com/browse/PROD-27591
(cherry picked from commit f7c76187fb1cfbc81f23ae82dcfe58bfc3582739)

Rework nova service_user section

This patch overrides the service user data taken from identity
pillar by the data from service user pillar if specified.

Change-Id: Iaec201ae949c3dae44b1efc6b91a21623407b711
Related-Prod: PROD-27591
(cherry picked from commit f4f91a1c23fb75714d23e3df200f4297fb218b9e)
diff --git a/README.rst b/README.rst
index 8eaa2e2..9dcec6d 100644
--- a/README.rst
+++ b/README.rst
@@ -1178,6 +1178,33 @@
 You can read more about it here:
     https://docs.openstack.org/security-guide/databases/database-access-control.html
 
+Configure nova to use service user tokens:
+========
+Long-running operations such as live migration or snapshot can sometimes overrun the
+expiry of the user token. In such cases, post operations such as cleaning up after a
+live migration can fail when the nova-compute service needs to cleanup resources in
+other services, such as in the block-storage (cinder) or networking (neutron) services.
+
+This patch enables nova to use service user tokens to supplement the regular user token
+used to initiate the operation. The identity service (keystone) will then authenticate
+a request using the service user token if the user token has already expired.
+
+.. code-block:: yaml
+
+   nova:
+     controller:
+     enabled: True
+     ...
+       service_user:
+         enabled: True
+         user_domain_id: default
+         project_domain_id: default
+         project_name: service
+         username: nova
+         password: pswd
+
+
+
 Upgrades
 ========
 
diff --git a/nova/files/ocata/nova-compute.conf.Debian b/nova/files/ocata/nova-compute.conf.Debian
index 3c069ed..9dc2694 100644
--- a/nova/files/ocata/nova-compute.conf.Debian
+++ b/nova/files/ocata/nova-compute.conf.Debian
@@ -9046,6 +9046,24 @@
 #
 # From nova.conf
 #
+{%- if compute.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+auth_type = password
+{%- set _data = {} %}
+{%- do _data.update(compute.get('identity', {})) %}
+{%- do _data.update(compute.get('service_user', {})) %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- if 'cacert_file' not in _data.keys() %}{% do _data.update({'cacert_file': compute.cacert_file}) %}{% endif %}
+user_domain_id = {{ _data.get('domain', 'default') }}
+project_domain_id = {{ _data.get('domain', 'default') }}
+project_name = {{ _data.get('tenant', 'service') }}
+username = {{ _data.get('user', 'nova') }}
+password = {{ _data.password }}
+auth_url={{ _data.get('protocol', 'http') }}://{{ _data.host }}:{{ _data.port }}
+  {%- if _data.get('protocol', 'http') == 'https' %}
+cafile={{ _data.cacert_file }}
+  {%- endif %}
+{%- endif %}
 
 #
 # When True, if sending a user token to an REST API, also send a service token.
diff --git a/nova/files/ocata/nova-controller.conf.Debian b/nova/files/ocata/nova-controller.conf.Debian
index 23db54e..b7c38ff 100644
--- a/nova/files/ocata/nova-controller.conf.Debian
+++ b/nova/files/ocata/nova-controller.conf.Debian
@@ -9025,6 +9025,24 @@
 #
 # From nova.conf
 #
+{%- if controller.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+auth_type = password
+{%- set _data = {} %}
+{%- do _data.update(controller.get('identity', {})) %}
+{%- do _data.update(controller.get('service_user', {})) %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- if 'cacert_file' not in _data.keys() %}{% do _data.update({'cacert_file': controller.cacert_file}) %}{% endif %}
+user_domain_id = {{ _data.get('domain', 'default') }}
+project_domain_id = {{ _data.get('domain', 'default') }}
+project_name = {{ _data.get('tenant', 'service') }}
+username = {{ _data.get('user', 'nova') }}
+password = {{ _data.password }}
+auth_url={{ _data.get('protocol', 'http') }}://{{ _data.host }}:{{ _data.port }}
+  {%- if _data.get('protocol', 'http') == 'https' %}
+cafile={{ _data.cacert_file }}
+  {%- endif %}
+{%- endif %}
 
 #
 # When True, if sending a user token to an REST API, also send a service token.
diff --git a/nova/files/pike/nova-compute.conf.Debian b/nova/files/pike/nova-compute.conf.Debian
index 67bb430..8acfe3f 100644
--- a/nova/files/pike/nova-compute.conf.Debian
+++ b/nova/files/pike/nova-compute.conf.Debian
@@ -9245,6 +9245,24 @@
 #
 # From nova.conf
 #
+{%- if compute.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+auth_type = password
+{%- set _data = {} %}
+{%- do _data.update(compute.get('identity', {})) %}
+{%- do _data.update(compute.get('service_user', {})) %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- if 'cacert_file' not in _data.keys() %}{% do _data.update({'cacert_file': compute.cacert_file}) %}{% endif %}
+user_domain_id = {{ _data.get('domain', 'default') }}
+project_domain_id = {{ _data.get('domain', 'default') }}
+project_name = {{ _data.get('tenant', 'service') }}
+username = {{ _data.get('user', 'nova') }}
+password = {{ _data.password }}
+auth_url={{ _data.get('protocol', 'http') }}://{{ _data.host }}:{{ _data.port }}
+  {%- if _data.get('protocol', 'http') == 'https' %}
+cafile={{ _data.cacert_file }}
+  {%- endif %}
+{%- endif %}
 
 #
 # When True, if sending a user token to an REST API, also send a service token.
diff --git a/nova/files/pike/nova-controller.conf.Debian b/nova/files/pike/nova-controller.conf.Debian
index a61c236..717cfe5 100644
--- a/nova/files/pike/nova-controller.conf.Debian
+++ b/nova/files/pike/nova-controller.conf.Debian
@@ -9235,6 +9235,24 @@
 #
 # From nova.conf
 #
+{%- if controller.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+auth_type = password
+{%- set _data = {} %}
+{%- do _data.update(controller.get('identity', {})) %}
+{%- do _data.update(controller.get('service_user', {})) %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- if 'cacert_file' not in _data.keys() %}{% do _data.update({'cacert_file': controller.cacert_file}) %}{% endif %}
+user_domain_id = {{ _data.get('domain', 'default') }}
+project_domain_id = {{ _data.get('domain', 'default') }}
+project_name = {{ _data.get('tenant', 'service') }}
+username = {{ _data.get('user', 'nova') }}
+password = {{ _data.password }}
+auth_url={{ _data.get('protocol', 'http') }}://{{ _data.host }}:{{ _data.port }}
+  {%- if _data.get('protocol', 'http') == 'https' %}
+cafile={{ _data.cacert_file }}
+  {%- endif %}
+{%- endif %}
 
 #
 # When True, if sending a user token to an REST API, also send a service token.
diff --git a/nova/files/queens/nova-compute.conf.Debian b/nova/files/queens/nova-compute.conf.Debian
index 54dfd87..1e9e5d0 100644
--- a/nova/files/queens/nova-compute.conf.Debian
+++ b/nova/files/queens/nova-compute.conf.Debian
@@ -9252,6 +9252,15 @@
 # middleware.
 #  (boolean value)
 #send_service_user_token = false
+{%- if compute.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+{%- set _data = {} %}
+{%- do _data.update(compute.get('identity', {})) %}
+{%- do _data.update(compute.get('service_user', {})) %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- if 'cacert_file' not in _data.keys() %}{% do _data.update({'cacert_file': compute.cacert_file}) %}{% endif %}
+{%- include "oslo_templates/files/queens/keystoneauth/_type_"+ _data.get('auth_type','password') +".conf" %}
+{%- else %}
 
 # PEM encoded Certificate Authority to use when verifying HTTPs
 # connections. (string value)
@@ -9335,6 +9344,7 @@
 
 # Tenant Name (string value)
 #tenant_name = <None>
+{%- endif %}
 
 
 [spice]
diff --git a/nova/files/queens/nova-controller.conf.Debian b/nova/files/queens/nova-controller.conf.Debian
index fbf978b..04498c7 100644
--- a/nova/files/queens/nova-controller.conf.Debian
+++ b/nova/files/queens/nova-controller.conf.Debian
@@ -8967,6 +8967,15 @@
 # middleware.
 #  (boolean value)
 #send_service_user_token = false
+{%- if controller.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+{%- set _data = {} %}
+{%- do _data.update(controller.get('identity', {})) %}
+{%- do _data.update(controller.get('service_user', {})) %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- if 'cacert_file' not in _data.keys() %}{% do _data.update({'cacert_file': controller.cacert_file}) %}{% endif %}
+{%- include "oslo_templates/files/queens/keystoneauth/_type_"+ _data.get('auth_type','password') +".conf" %}
+{%- else %}
 
 # PEM encoded Certificate Authority to use when verifying HTTPs
 # connections. (string value)
@@ -9050,6 +9059,7 @@
 
 # Tenant Name (string value)
 #tenant_name = <None>
+{%- endif %}
 
 
 [spice]
diff --git a/tests/pillar/compute_cluster_vmware.sls b/tests/pillar/compute_cluster_vmware.sls
index 8953178..27de501 100644
--- a/tests/pillar/compute_cluster_vmware.sls
+++ b/tests/pillar/compute_cluster_vmware.sls
@@ -21,6 +21,13 @@
       user: nova
       password: password
       tenant: service
+      service_user:
+        enabled: True
+        user_domain_id: default
+        project_domain_id: default
+        project_name: service
+        username: nova
+        password: pswd
     logging:
       log_appender: false
       log_handlers:
diff --git a/tests/pillar/control_cluster.sls b/tests/pillar/control_cluster.sls
index e3bf663..97e8e43 100644
--- a/tests/pillar/control_cluster.sls
+++ b/tests/pillar/control_cluster.sls
@@ -32,6 +32,14 @@
       user: nova
       password: password
       tenant: service
+      service_user:
+        enabled: True
+        user_domain_id: default
+        project_domain_id: default
+        project_name: service
+        username: nova
+        password: pswd
+
     logging:
       log_appender: false
       log_handlers: