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: debug-statements
- id: check-yaml - id: check-yaml
files: .*\.(yaml|yml)$ files: .*\.(yaml|yml)$
exclude: '^.zuul.yaml'
- repo: local - repo: local
hooks: hooks:
- id: flake8 - id: flake8

View File

@@ -439,16 +439,43 @@
- job: - job:
name: openstacksdk-acceptance-base name: openstacksdk-acceptance-base
parent: openstack-tox 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: pre-run:
- playbooks/acceptance/pre.yaml - playbooks/acceptance/pre.yaml
post-run: post-run:
- playbooks/acceptance/post.yaml - 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: - job:
name: openstacksdk-acceptance-devstack name: openstacksdk-acceptance-devstack
parent: openstacksdk-functional-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: run:
- playbooks/acceptance/run-with-devstack.yaml - playbooks/acceptance/run-with-devstack.yaml
post-run: post-run:
@@ -459,17 +486,25 @@
OPENSTACKSDK_DEMO_CLOUD: acceptance OPENSTACKSDK_DEMO_CLOUD: acceptance
OS_CLOUD: acceptance OS_CLOUD: acceptance
OS_TEST_CLOUD: acceptance OS_TEST_CLOUD: acceptance
openstack_credentials: auth_url: "https://{{ hostvars['controller']['nodepool']['private_ipv4'] }}/identity"
auth: secrets:
auth_url: "https://{{ hostvars['controller']['nodepool']['private_ipv4'] }}/identity" - secret: credentials-devstack
username: demo name: openstack_credentials
password: secretadmin
project_domain_id: default # Devstack secret is not specifying auth_url because of how Zuul treats secrets.
project_name: demo # Auth_url comes extra in the job vars and is being used if no auth_url in the
user_domain_id: default # secret is present.
identity_api_version: '3' - secret:
region_name: RegionOne name: credentials-devstack
volume_api_version: '3' data:
auth:
username: demo
password: secretadmin
project_domain_id: default
project_name: demo
user_domain_id: default
region_name: RegionOne
verify: false
- project-template: - project-template:
name: openstacksdk-functional-tips name: openstacksdk-functional-tips

View File

@@ -106,7 +106,7 @@ class BaseFunctionalTest(base.TestCase):
return None return None
flavors = self.user_cloud.list_flavors(get_extra=False) 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') flavor_name = os.environ.get('OPENSTACKSDK_FLAVOR')
@@ -146,7 +146,7 @@ class BaseFunctionalTest(base.TestCase):
return None return None
images = self.user_cloud.list_images() 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') 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: tasks:
# TODO: # TODO:
# - clean the resources, which might have been created # - clean the resources, which might have been created
# - revoke the temp token explicitly
- name: read token # Token is saved on the zuul executor node
command: "cat {{ zuul.executor.work_root }}/.{{ zuul.build }}" - name: Check token file
register: token_data 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 no_log: true
ansible.builtin.slurp:
src: "{{ token_file.stat.path }}"
register: token_data
when: "token_file.stat.exists"
- name: delete data file - name: Delete data file
command: "shred {{ zuul.executor.work_root }}/.{{ zuul.build }}" delegate_to: localhost
command: "shred {{ token_file.stat.path }}"
when: "token_file.stat.exists"
- include_role: # no_log is important since content WILL appear in logs
name: revoke_token - name: Revoke token
vars: no_log: true
cloud: "{{ openstack_credentials }}" ansible.builtin.uri:
token: "{{ token_data.stdout }}" 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 - hosts: all
tasks: tasks:
- name: Get temporary token for the cloud - 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 no_log: true
os_auth: ansible.builtin.uri:
cloud: url: "{{ openstack_credentials.auth.auth_url | default(auth_url) }}/v3/auth/tokens"
profile: "{{ openstack_credentials.profile | default(omit) }}" method: "POST"
body_format: "json"
body:
auth: auth:
auth_url: "{{ openstack_credentials.auth.auth_url }}" identity:
username: "{{ openstack_credentials.auth.username }}" methods: ["password"]
password: "{{ openstack_credentials.auth.password }}" password:
user_domain_name: "{{ openstack_credentials.auth.user_domain_name | default(omit) }}" user:
user_domain_id: "{{ openstack_credentials.auth.user_domain_id | default(omit) }}" name: "{{ openstack_credentials.auth.username | default(omit) }}"
domain_name: "{{ openstack_credentials.auth.domain_name | default(omit) }}" id: "{{ openstack_credentials.auth.user_id | default(omit) }}"
domain_id: "{{ openstack_credentials.auth.domain_id | default(omit) }}" password: "{{ openstack_credentials.auth.password }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}" domain:
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}" name: "{{ openstack_credentials.auth.user_domain_name | default(omit) }}"
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}" id: "{{ openstack_credentials.auth.user_domain_id | default(omit) }}"
project_domain_id: "{{ openstack_credentials.auth.project_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 register: os_auth
delegate_to: localhost
- name: Verify token - name: Verify token
# nolog is important since content WILL appear in logs
no_log: true no_log: true
os_auth: ansible.builtin.uri:
cloud: url: "{{ openstack_credentials.auth.auth_url | default(auth_url) }}/v3/auth/tokens"
profile: "{{ openstack_credentials.profile | default(omit) }}" method: "GET"
auth_type: token headers:
auth: X-Auth-Token: "{{ os_auth.x_subject_token }}"
auth_url: "{{ openstack_credentials.auth.auth_url }}" X-Subject-Token: "{{ os_auth.x_subject_token }}"
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
- name: Include deploy-clouds-config role - name: Include deploy-clouds-config role
include_role: include_role:
@@ -43,18 +48,22 @@
cloud_config: cloud_config:
clouds: clouds:
acceptance: acceptance:
profile: "{{ openstack_credentials.profile | default(omit) }}" profile: "{{ openstack_credentials.profile | default('') }}"
auth_type: "token" auth_type: "token"
auth: auth:
auth_url: "{{ openstack_credentials.auth.auth_url | default(omit) }}" auth_url: "{{ openstack_credentials.auth.auth_url | default(auth_url) }}"
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}" project_name: "{{ openstack_credentials.auth.project_name | default('') }}"
token: "{{ os_auth.auth_token }}" 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 # 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. # To prevent this we save token on the executor for later use.
- name: Save token - name: Save the token
delegate_to: localhost delegate_to: localhost
copy: copy:
dest: "{{ zuul.executor.work_root }}/.{{ zuul.build }}" dest: "{{ zuul.executor.work_root }}/.{{ zuul.build }}"
content: "{{ os_auth.auth_token }}" content: "{{ os_auth.x_subject_token }}"
mode: "0440" mode: "0640"

View File

@@ -1,75 +1,11 @@
---
# Need to actually start devstack first # Need to actually start devstack first
- hosts: all - hosts: all
roles: roles:
- run-devstack - run-devstack
# Prepare local clouds.yaml - name: Get the token
# We can't rely on pre.yaml, since it is specifically delegates to ansible.builtin.import_playbook: pre.yaml
# 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"
# Run the rest # Run the rest
- hosts: all - 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