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
diff --git a/README.rst b/README.rst
index 1d3daa9..ff0ce8d 100644
--- a/README.rst
+++ b/README.rst
@@ -1206,6 +1206,34 @@
        connection_debug: 10
        pool_timeout: 120
 
+
+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..585db21 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
+  {%- if compute.service_user is defined %}
+  {%- set _data=compute.service_user %}
+  {%- else %}
+  {%- set _data=compute.identity %}
+  {%- 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 }}
+uth_url={{ compute.identity.get('protocol', 'http') }}://{{ compute.identity.host }}:5000
+  {%- if compute.identity.get('protocol', 'http') == 'https' %}
+cafile={{ compute.identity.get('cacert_file', compute.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 11cec7e..3d6736e 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
+  {%- if controller.service_user is defined %}
+  {%- set _data=controller.service_user %}
+  {%- else %}
+  {%- set _data=controller.identity %}
+  {%- 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 }}
+uth_url={{ controller.identity.get('protocol', 'http') }}://{{ controller.identity.host }}:5000
+  {%- if controller.identity.get('protocol', 'http') == 'https' %}
+cafile={{ controller.identity.get('cacert_file', controller.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 2d3f8dd..38f8ca6 100644
--- a/nova/files/pike/nova-compute.conf.Debian
+++ b/nova/files/pike/nova-compute.conf.Debian
@@ -9249,6 +9249,24 @@
 #
 # From nova.conf
 #
+{%- if compute.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+auth_type = password
+  {%- if compute.service_user is defined %}
+  {%- set _data=compute.service_user %}
+  {%- else %}
+  {%- set _data=compute.identity %}
+  {%- 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 }}
+uth_url={{ compute.identity.get('protocol', 'http') }}://{{ compute.identity.host }}:5000
+  {%- if compute.identity.get('protocol', 'http') == 'https' %}
+cafile={{ compute.identity.get('cacert_file', compute.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 910b0de..b9a3c5a 100644
--- a/nova/files/pike/nova-controller.conf.Debian
+++ b/nova/files/pike/nova-controller.conf.Debian
@@ -9239,6 +9239,24 @@
 #
 # From nova.conf
 #
+{%- if controller.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+auth_type = password
+  {%- if controller.service_user is defined %}
+  {%- set _data=controller.service_user %}
+  {%- else %}
+  {%- set _data=controller.identity %}
+  {%- 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 }}
+uth_url={{ controller.identity.get('protocol', 'http') }}://{{ controller.identity.host }}:5000
+  {%- if controller.identity.get('protocol', 'http') == 'https' %}
+cafile={{ controller.identity.get('cacert_file', controller.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 d1763dd..59714d3 100644
--- a/nova/files/queens/nova-compute.conf.Debian
+++ b/nova/files/queens/nova-compute.conf.Debian
@@ -9173,6 +9173,16 @@
 # middleware.
 #  (boolean value)
 #send_service_user_token = false
+{%- if compute.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+  {%- if compute.service_user is defined %}
+  {%- set _data=compute.service_user %}
+  {%- else %}
+  {%- set _data=compute.identity %}
+  {%- endif %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- include "oslo_templates/files/queens/keystoneauth/_type_" + auth_type + ".conf" %}
+{%- else %}
 
 # PEM encoded Certificate Authority to use when verifying HTTPs
 # connections. (string value)
@@ -9256,6 +9266,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 948084c..5114380 100644
--- a/nova/files/queens/nova-controller.conf.Debian
+++ b/nova/files/queens/nova-controller.conf.Debian
@@ -8894,6 +8894,16 @@
 # middleware.
 #  (boolean value)
 #send_service_user_token = false
+{%- if controller.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+  {%- if controller.service_user is defined %}
+  {%- set _data=controller.service_user %}
+  {%- else %}
+  {%- set _data=controller.identity %}
+  {%- endif %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- include "oslo_templates/files/queens/keystoneauth/_type_" + auth_type + ".conf" %}
+{%- else %}
 
 # PEM encoded Certificate Authority to use when verifying HTTPs
 # connections. (string value)
@@ -8977,6 +8987,7 @@
 
 # Tenant Name (string value)
 #tenant_name = <None>
+{%- endif %}
 
 
 [spice]
diff --git a/nova/files/rocky/nova-compute.conf.Debian b/nova/files/rocky/nova-compute.conf.Debian
index 62d8503..a0b9fea 100644
--- a/nova/files/rocky/nova-compute.conf.Debian
+++ b/nova/files/rocky/nova-compute.conf.Debian
@@ -8506,6 +8506,16 @@
 # middleware.
 #  (boolean value)
 #send_service_user_token = false
+{%- if compute.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+  {%- if compute.service_user is defined %}
+  {%- set _data=compute.service_user %}
+  {%- else %}
+  {%- set _data=compute.identity %}
+  {%- endif %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- include "oslo_templates/files/" ~ compute.version ~ "/keystoneauth/_type_" + auth_type + ".conf" %}
+{%- else %}
 
 # PEM encoded Certificate Authority to use when verifying HTTPs connections.
 # (string value)
@@ -8594,6 +8604,7 @@
 
 # Tenant Name (string value)
 #tenant_name = <None>
+{%- endif %}
 
 
 [spice]
diff --git a/nova/files/rocky/nova-controller.conf.Debian b/nova/files/rocky/nova-controller.conf.Debian
index bf72a10..421f7ec 100644
--- a/nova/files/rocky/nova-controller.conf.Debian
+++ b/nova/files/rocky/nova-controller.conf.Debian
@@ -8226,6 +8226,16 @@
 # middleware.
 #  (boolean value)
 #send_service_user_token = false
+{%- if controller.get('service_user', {}).get('enabled', True) %}
+send_service_user_token = True
+  {%- if controller.service_user is defined %}
+  {%- set _data=controller.service_user %}
+  {%- else %}
+  {%- set _data=controller.identity %}
+  {%- endif %}
+{%- if not _data.port == '5000' %}{% do _data.update({'port': '5000'}) %}{% endif %}
+{%- include "oslo_templates/files/" ~ controller.version ~ "/keystoneauth/_type_" + auth_type + ".conf" %}
+{%- else %}
 
 # PEM encoded Certificate Authority to use when verifying HTTPs connections.
 # (string value)
@@ -8314,6 +8324,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 ab37b5f..cc88269 100644
--- a/tests/pillar/control_cluster.sls
+++ b/tests/pillar/control_cluster.sls
@@ -43,6 +43,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: true
       log_handlers: