Prepare acceptance tests for real clouds

- reworked job not to use custom modules, but rather send direct API
  requests (due to mess with ensuring openstacksdk availability for
  Ansible).
- add exclude for pre-commit-config to stop it from complaining on
  zuul.yaml
- removed too verbose logging from functests with list of images and
  flavors (on real clouds this is simply too much).

Change-Id: I555127f410b696e1584dc07cafac25597ab1abeb
This commit is contained in:
Artem Goncharov 2023-03-28 15:49:22 +02:00
parent 4ae03c0ab4
commit 50711b4662
11 changed files with 134 additions and 254 deletions

View File

@ -16,6 +16,7 @@ repos:
- id: debug-statements
- id: check-yaml
files: .*\.(yaml|yml)$
exclude: '^.zuul.yaml'
- repo: local
hooks:
- id: flake8

View File

@ -439,16 +439,43 @@
- job:
name: openstacksdk-acceptance-base
parent: openstack-tox
description: Acceptance test of the OpenStackSDK on real clouds
description: |
Acceptance test of the OpenStackSDK on real clouds.
.. zuul:jobsvar::openstack_credentials
:type: dict
This is expected to be a Zuul Secret with these keys:
.. zuul:jobvar: auth
:type: dict
Dictionary with authentication information with mandatory auth_url
and others. The structure mimics `clouds.yaml` structure.
By default all jobs that inherit from here are non voting.
attempts: 1
voting: false
pre-run:
- playbooks/acceptance/pre.yaml
post-run:
- playbooks/acceptance/post.yaml
vars:
tox_envlist: acceptance-regular-user
tox_environment:
OPENSTACKSDK_DEMO_CLOUD: acceptance
OS_CLOUD: acceptance
OS_TEST_CLOUD: acceptance
# Acceptance tests for devstack are different from running for real cloud since
# we need to actually deploy devstack first and API is available only on the
# devstack host.
- job:
name: openstacksdk-acceptance-devstack
parent: openstacksdk-functional-devstack
description: Acceptance test of the OpenStackSDK on real clouds
description: Acceptance test of the OpenStackSDK on real clouds.
attempts: 1
run:
- playbooks/acceptance/run-with-devstack.yaml
post-run:
@ -459,17 +486,25 @@
OPENSTACKSDK_DEMO_CLOUD: acceptance
OS_CLOUD: acceptance
OS_TEST_CLOUD: acceptance
openstack_credentials:
auth:
auth_url: "https://{{ hostvars['controller']['nodepool']['private_ipv4'] }}/identity"
username: demo
password: secretadmin
project_domain_id: default
project_name: demo
user_domain_id: default
identity_api_version: '3'
region_name: RegionOne
volume_api_version: '3'
auth_url: "https://{{ hostvars['controller']['nodepool']['private_ipv4'] }}/identity"
secrets:
- secret: credentials-devstack
name: openstack_credentials
# Devstack secret is not specifying auth_url because of how Zuul treats secrets.
# Auth_url comes extra in the job vars and is being used if no auth_url in the
# secret is present.
- secret:
name: credentials-devstack
data:
auth:
username: demo
password: secretadmin
project_domain_id: default
project_name: demo
user_domain_id: default
region_name: RegionOne
verify: false
- project-template:
name: openstacksdk-functional-tips

View File

@ -106,7 +106,7 @@ class BaseFunctionalTest(base.TestCase):
return None
flavors = self.user_cloud.list_flavors(get_extra=False)
self.add_info_on_exception('flavors', flavors)
# self.add_info_on_exception('flavors', flavors)
flavor_name = os.environ.get('OPENSTACKSDK_FLAVOR')
@ -146,7 +146,7 @@ class BaseFunctionalTest(base.TestCase):
return None
images = self.user_cloud.list_images()
self.add_info_on_exception('images', images)
# self.add_info_on_exception('images', images)
image_name = os.environ.get('OPENSTACKSDK_IMAGE')

View File

@ -1 +0,0 @@
../library

View File

@ -1,18 +1,42 @@
- hosts: localhost
---
# This could be running on localhost only, but then the devstack job would need
# to perform API call on the worker node. To keep the code a bit less crazy
# rather address all hosts and perform certain steps on the localhost (zuul
# executor).
- hosts: all
tasks:
# TODO:
# - clean the resources, which might have been created
# - revoke the temp token explicitly
- name: read token
command: "cat {{ zuul.executor.work_root }}/.{{ zuul.build }}"
register: token_data
# Token is saved on the zuul executor node
- name: Check token file
delegate_to: localhost
ansible.builtin.stat:
path: "{{ zuul.executor.work_root }}/.{{ zuul.build }}"
register: token_file
# no_log is important since content WILL in logs
- name: Read the token from file
delegate_to: localhost
no_log: true
ansible.builtin.slurp:
src: "{{ token_file.stat.path }}"
register: token_data
when: "token_file.stat.exists"
- name: delete data file
command: "shred {{ zuul.executor.work_root }}/.{{ zuul.build }}"
- name: Delete data file
delegate_to: localhost
command: "shred {{ token_file.stat.path }}"
when: "token_file.stat.exists"
- include_role:
name: revoke_token
vars:
cloud: "{{ openstack_credentials }}"
token: "{{ token_data.stdout }}"
# no_log is important since content WILL appear in logs
- name: Revoke token
no_log: true
ansible.builtin.uri:
url: "{{ openstack_credentials.auth.auth_url | default(auth_url) }}/v3/auth/tokens"
method: "DELETE"
headers:
X-Auth-Token: "{{ token_data['content'] | b64decode }}"
X-Subject-Token: "{{ token_data['content'] | b64decode }}"
status_code: 204
when: "token_file.stat.exists and 'content' in token_data"

View File

@ -1,40 +1,45 @@
---
- hosts: all
tasks:
- name: Get temporary token for the cloud
# nolog is important to keep job-output.json clean
# nolog is important since content WILL appear in logs
no_log: true
os_auth:
cloud:
profile: "{{ openstack_credentials.profile | default(omit) }}"
ansible.builtin.uri:
url: "{{ openstack_credentials.auth.auth_url | default(auth_url) }}/v3/auth/tokens"
method: "POST"
body_format: "json"
body:
auth:
auth_url: "{{ openstack_credentials.auth.auth_url }}"
username: "{{ openstack_credentials.auth.username }}"
password: "{{ openstack_credentials.auth.password }}"
user_domain_name: "{{ openstack_credentials.auth.user_domain_name | default(omit) }}"
user_domain_id: "{{ openstack_credentials.auth.user_domain_id | default(omit) }}"
domain_name: "{{ openstack_credentials.auth.domain_name | default(omit) }}"
domain_id: "{{ openstack_credentials.auth.domain_id | default(omit) }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
identity:
methods: ["password"]
password:
user:
name: "{{ openstack_credentials.auth.username | default(omit) }}"
id: "{{ openstack_credentials.auth.user_id | default(omit) }}"
password: "{{ openstack_credentials.auth.password }}"
domain:
name: "{{ openstack_credentials.auth.user_domain_name | default(omit) }}"
id: "{{ openstack_credentials.auth.user_domain_id | default(omit) }}"
scope:
project:
name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
domain:
name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
return_content: true
status_code: 201
register: os_auth
delegate_to: localhost
- name: Verify token
# nolog is important since content WILL appear in logs
no_log: true
os_auth:
cloud:
profile: "{{ openstack_credentials.profile | default(omit) }}"
auth_type: token
auth:
auth_url: "{{ openstack_credentials.auth.auth_url }}"
token: "{{ os_auth.auth_token }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
delegate_to: localhost
ansible.builtin.uri:
url: "{{ openstack_credentials.auth.auth_url | default(auth_url) }}/v3/auth/tokens"
method: "GET"
headers:
X-Auth-Token: "{{ os_auth.x_subject_token }}"
X-Subject-Token: "{{ os_auth.x_subject_token }}"
- name: Include deploy-clouds-config role
include_role:
@ -43,18 +48,22 @@
cloud_config:
clouds:
acceptance:
profile: "{{ openstack_credentials.profile | default(omit) }}"
profile: "{{ openstack_credentials.profile | default('') }}"
auth_type: "token"
auth:
auth_url: "{{ openstack_credentials.auth.auth_url | default(omit) }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
token: "{{ os_auth.auth_token }}"
auth_url: "{{ openstack_credentials.auth.auth_url | default(auth_url) }}"
project_name: "{{ openstack_credentials.auth.project_name | default('') }}"
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default('') }}"
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default('') }}"
token: "{{ os_auth.x_subject_token }}"
region_name: "{{ openstack_credentials.region_name | default('') }}"
verify: "{{ openstack_credentials.verify | default(true) }}"
# Intruders might want to corrupt clouds.yaml to avoid revoking token in the post phase
# To prevent this we save token on the executor for later use.
- name: Save token
- name: Save the token
delegate_to: localhost
copy:
dest: "{{ zuul.executor.work_root }}/.{{ zuul.build }}"
content: "{{ os_auth.auth_token }}"
mode: "0440"
content: "{{ os_auth.x_subject_token }}"
mode: "0640"

View File

@ -1,75 +1,11 @@
---
# Need to actually start devstack first
- hosts: all
roles:
- run-devstack
# Prepare local clouds.yaml
# We can't rely on pre.yaml, since it is specifically delegates to
# localhost, while on devstack it will not work unless APIs are available
# over the net.
- hosts: all
tasks:
- name: Get temporary token for the cloud
# nolog is important to keep job-output.json clean
no_log: true
os_auth:
cloud:
profile: "{{ openstack_credentials.profile | default(omit) }}"
auth:
auth_url: "{{ openstack_credentials.auth.auth_url }}"
username: "{{ openstack_credentials.auth.username }}"
password: "{{ openstack_credentials.auth.password }}"
user_domain_name: "{{ openstack_credentials.auth.user_domain_name | default(omit) }}"
user_domain_id: "{{ openstack_credentials.auth.user_domain_id | default(omit) }}"
domain_name: "{{ openstack_credentials.auth.domain_name | default(omit) }}"
domain_id: "{{ openstack_credentials.auth.domain_id | default(omit) }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
register: os_auth
- name: Verify token
# nolog is important to keep job-output.json clean
no_log: true
os_auth:
cloud:
profile: "{{ openstack_credentials.profile | default(omit) }}"
auth_type: token
auth:
auth_url: "{{ openstack_credentials.auth.auth_url }}"
token: "{{ os_auth.auth_token }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
- name: Include deploy-clouds-config role
include_role:
name: deploy-clouds-config
vars:
cloud_config:
clouds:
acceptance:
profile: "{{ openstack_credentials.profile | default(omit) }}"
auth_type: "token"
auth:
auth_url: "{{ openstack_credentials.auth.auth_url }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
token: "{{ os_auth.auth_token }}"
verify: false
# Intruders might want to corrupt clouds.yaml to avoid revoking token in
# the post phase. To prevent this we save token on the executor for later
# use.
- name: Save token
delegate_to: localhost
copy:
dest: "{{ zuul.executor.work_root }}/.{{ zuul.build }}"
content: "{{ os_auth.auth_token }}"
mode: "0640"
- name: Get the token
ansible.builtin.import_playbook: pre.yaml
# Run the rest
- hosts: all

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python3
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility to get Keystone token
"""
from ansible.module_utils.basic import AnsibleModule
import openstack
def get_cloud(cloud):
if isinstance(cloud, dict):
config = openstack.config.loader.OpenStackConfig().get_one(**cloud)
return openstack.connection.Connection(config=config)
else:
return openstack.connect(cloud=cloud)
def main():
module = AnsibleModule(
argument_spec=dict(
cloud=dict(required=True, type='raw', no_log=True),
)
)
cloud = get_cloud(module.params.get('cloud'))
module.exit_json(
changed=True,
auth_token=cloud.auth_token
)
if __name__ == '__main__':
main()

View File

@ -1,72 +0,0 @@
#!/usr/bin/env python3
#
# Copyright 2014 Rackspace Australia
# Copyright 2018 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility to revoke Keystone token
"""
import logging
import traceback
from ansible.module_utils.basic import AnsibleModule
import keystoneauth1.exceptions
import requests
import requests.exceptions
import openstack
def get_cloud(cloud):
if isinstance(cloud, dict):
config = openstack.config.loader.OpenStackConfig().get_one(**cloud)
return openstack.connection.Connection(config=config)
else:
return openstack.connect(cloud=cloud)
def main():
module = AnsibleModule(
argument_spec=dict(
cloud=dict(required=True, type='raw', no_log=True),
revoke_token=dict(required=True, type='str', no_log=True)
)
)
p = module.params
cloud = get_cloud(p.get('cloud'))
try:
cloud.identity.delete(
'/auth/tokens',
headers={
'X-Subject-Token': p.get('revoke_token')
}
)
except (keystoneauth1.exceptions.http.HttpError,
requests.exceptions.RequestException):
s = "Error performing token revoke"
logging.exception(s)
s += "\n" + traceback.format_exc()
module.fail_json(
changed=False,
msg=s,
cloud=cloud.name,
region_name=cloud.config.region_name)
module.exit_json(changed=True)
if __name__ == '__main__':
main()

View File

@ -1,7 +0,0 @@
- name: Revoke token
delegate_to: localhost
no_log: true
os_auth_revoke:
cloud: "{{ cloud }}"
revoke_token: "{{ token }}"
failed_when: false