Modernize testing and clients

Update to use a new-style zuul role for devstack testing.
Remove references to Shade/os_client_config and use OpenstackSDK.

Change-Id: Ieecc4043b613e333948c32e7e93ad1d383d1f3ec
This commit is contained in:
Adam Harwell 2019-09-19 22:02:02 -07:00
parent fc73a2e881
commit 0421d12397
20 changed files with 177 additions and 353 deletions

View File

@ -1,14 +1,20 @@
- job: - job:
name: ospurge-functional name: ospurge-functional
parent: legacy-dsvm-base parent: devstack-tox-functional-consumer
run: playbooks/ospurge-functional/run.yaml
post-run: playbooks/ospurge-functional/post.yaml
timeout: 3600 timeout: 3600
required-projects: required-projects:
- openstack/devstack-gate - openstack/devstack-gate
- x/ospurge - x/ospurge
- openstack/designate - openstack/designate
- openstack/python-designateclient - openstack/python-designateclient
roles:
- zuul: openstack-infra/devstack
vars:
devstack_plugins:
designate: https://opendev.org/openstack/designate
devstack_localrc:
DESIGNATE_SERVICE_PORT_DNS: 5322
tox_envlist: functional
irrelevant-files: &dsvm-irrelevant-files irrelevant-files: &dsvm-irrelevant-files
- ^(test-|)requirements.txt$ - ^(test-|)requirements.txt$
- ^.*\.rst$ - ^.*\.rst$

View File

@ -293,7 +293,7 @@ How to contribute
----------------- -----------------
OSPurge is hosted on the OpenStack infrastructure and is using OSPurge is hosted on the OpenStack infrastructure and is using
`Gerrit <https://review.openstack.org/#/q/project:openstack/ospurge>`_ to `Gerrit <https://review.opendev.org/#/q/project:x/ospurge>`_ to
manage contributions. You can contribute to the project by following the manage contributions. You can contribute to the project by following the
`OpenStack Development workflow <http://docs.openstack.org/infra/manual/developers.html#development-workflow>`_. `OpenStack Development workflow <http://docs.openstack.org/infra/manual/developers.html#development-workflow>`_.
@ -301,21 +301,19 @@ Start hacking right away with:
.. code-block:: console .. code-block:: console
$ git clone https://git.openstack.org/openstack/ospurge $ git clone https://opendev.org/x/ospurge
Design decisions Design decisions
---------------- ----------------
* OSPurge depends on `os-client-config`_ to manage authentication. This way, * OSPurge depends on `openstacksdk`_ to manage authentication. This way,
environment variables (OS_*) and CLI options are properly handled. environment variables (OS_*) and CLI options are properly handled.
* OSPurge is built on top of `shade`_. shade is a simple client library for * OSPurge is built on top of `openstacksdk`_. OpenstackSDK is a client library
interacting with OpenStack clouds. With shade, OSPurge can focus on the for interacting with OpenStack clouds. With the SDK, OSPurge can focus on the
cleaning resources logic and not on properly building the various Python cleaning resources logic and not on properly building the various Python
OpenStack clients and dealing with their not-so-intuitive API. OpenStack clients and dealing with their not-so-intuitive API.
.. _shade: https://github.com/openstack-infra/shade/ .. _openstacksdk: https://github.com/openstack/openstacksdk
.. _os-client-config: https://github.com/openstack/os-client-config

View File

@ -18,12 +18,10 @@ import sys
import threading import threading
import typing import typing
from openstack.config import loader
from openstack import connection from openstack import connection
from openstack import exceptions as os_exceptions from openstack import exceptions as os_exceptions
import os_client_config
import shade
from ospurge import exceptions from ospurge import exceptions
from ospurge import utils from ospurge import utils
@ -70,6 +68,10 @@ def create_argument_parser():
help="Purge only the specified resource type. Repeat to delete " help="Purge only the specified resource type. Repeat to delete "
"several types at once." "several types at once."
) )
parser.add_argument(
"--os-identity-api-version", default=3,
help="Identity API version, default=3"
)
group = parser.add_mutually_exclusive_group(required=True) group = parser.add_mutually_exclusive_group(required=True)
group.add_argument( group.add_argument(
@ -93,53 +95,38 @@ class CredentialsManager(object):
self.revoke_role_after_purge = False self.revoke_role_after_purge = False
self.disable_project_after_purge = False self.disable_project_after_purge = False
self.cloud = None # type: Optional[shade.OpenStackCloud] self.cloud = connection.Connection(
self.connection = None # type: Optional[connection.Connection]
if options.purge_own_project:
self.cloud = shade.openstack_cloud(argparse=options, config=config)
self.user_id = self.cloud.keystone_session.get_user_id()
self.project_id = self.cloud.keystone_session.get_project_id()
else:
self.connection = connection.Connection(
config=config.get_one(argparse=options) config=config.get_one(argparse=options)
) )
self.user_id = self.connection.identity.get_user_id() self.admin_cloud = None # type: Optional[connection.Connection]
if not options.purge_own_project:
try: try:
# Only admins can do that. # Only admins can do that.
project = self.connection.identity.get_tenant( project = self.cloud.get_project(options.purge_project)
options.purge_project) if not project:
except ( raise os_exceptions.SDKException()
os_exceptions.ResourceNotFound, os_exceptions.NotFoundException # If project is not enabled, we must disable it after purge.
): self.disable_project_after_purge = not project.is_enabled
except os_exceptions.SDKException:
raise exceptions.OSProjectNotFound( raise exceptions.OSProjectNotFound(
"Unable to find project '{}'".format(options.purge_project) "Unable to find project '{}'".format(options.purge_project)
) )
self.project_id = project.id self.admin_cloud = self.cloud
self.cloud = self.admin_cloud.connect_as_project(
options.purge_project)
# If project is not enabled, we must disable it after purge. self.user_id = self.cloud.current_user_id
self.disable_project_after_purge = not project.is_enabled self.project_id = self.cloud.current_project_id
# Reuse the information passed to get the `OperatorCloud` but
# change the project. This way we bind/re-scope to the project
# we want to purge, not the project we authenticated to.
self.cloud = shade.openstack_cloud(
**utils.replace_project_info(
self.connection.config.config,
self.project_id
)
)
auth_args = self.cloud.cloud_config.get_auth_args()
logging.warning( logging.warning(
"Going to list and/or delete resources from project '%s'", "Going to list and/or delete resources from project '%s'",
options.purge_project or auth_args.get('project_name') options.purge_project or self.cloud.current_project.name
or auth_args.get('project_id') or self.cloud.current_project_id
) )
def ensure_role_on_project(self): def ensure_role_on_project(self):
if self.connection and self.connection.grant_role( if self.admin_cloud and self.admin_cloud.grant_role(
self.options.admin_role_name, self.options.admin_role_name,
project=self.options.purge_project, user=self.user_id project=self.options.purge_project, user=self.user_id
): ):
@ -150,7 +137,7 @@ class CredentialsManager(object):
self.revoke_role_after_purge = True self.revoke_role_after_purge = True
def revoke_role_on_project(self): def revoke_role_on_project(self):
self.connection.revoke_role( self.admin_cloud.revoke_role(
self.options.admin_role_name, user=self.user_id, self.options.admin_role_name, user=self.user_id,
project=self.options.purge_project) project=self.options.purge_project)
logging.warning( logging.warning(
@ -159,13 +146,13 @@ class CredentialsManager(object):
) )
def ensure_enabled_project(self): def ensure_enabled_project(self):
if self.connection and self.disable_project_after_purge: if self.admin_cloud and self.disable_project_after_purge:
self.connection.update_project(self.project_id, enabled=True) self.admin_cloud.update_project(self.project_id, enabled=True)
logging.warning("Project '%s' was disabled before purge and it is " logging.warning("Project '%s' was disabled before purge and it is "
"now enabled", self.options.purge_project) "now enabled", self.options.purge_project)
def disable_project(self): def disable_project(self):
self.connection.update_project(self.project_id, enabled=False) self.admin_cloud.update_project(self.project_id, enabled=False)
logging.warning("Project '%s' was disabled before purge and it is " logging.warning("Project '%s' was disabled before purge and it is "
"now also disabled", self.options.purge_project) "now also disabled", self.options.purge_project)
@ -191,11 +178,7 @@ def runner(resource_mngr, options, exit):
# If we want to delete only specific resources, many things # If we want to delete only specific resources, many things
# can go wrong, so we basically ignore all exceptions. # can go wrong, so we basically ignore all exceptions.
if options.resource: exc = os_exceptions.OpenStackCloudException
exc = shade.OpenStackCloudException
else:
exc = shade.OpenStackCloudResourceNotFound
utils.call_and_ignore_exc(exc, resource_mngr.delete, resource) utils.call_and_ignore_exc(exc, resource_mngr.delete, resource)
except Exception as exc: except Exception as exc:
@ -222,11 +205,10 @@ def runner(resource_mngr, options, exit):
exit.set() exit.set()
@utils.monkeypatch_oscc_logging_warning
def main(): def main():
parser = create_argument_parser() parser = create_argument_parser()
cloud_config = os_client_config.OpenStackConfig() cloud_config = loader.OpenStackConfig()
cloud_config.register_argparse_arguments(parser, sys.argv) cloud_config.register_argparse_arguments(parser, sys.argv)
options = parser.parse_args() options = parser.parse_args()

View File

@ -25,7 +25,7 @@ from ospurge import exceptions
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
import argparse # noqa: F401 import argparse # noqa: F401
import shade # noqa: F401 import openstack.connection as os_connection # noqa: F401
from typing import Optional # noqa: F401 from typing import Optional # noqa: F401
@ -90,7 +90,7 @@ else: # pragma: no cover here
class BaseServiceResource(object): class BaseServiceResource(object):
def __init__(self): def __init__(self):
self.cleanup_project_id = None # type: Optional[str] self.cleanup_project_id = None # type: Optional[str]
self.cloud = None # type: Optional[shade.OpenStackCloud] self.cloud = None # type: Optional[os_connection.Connection]
self.options = None # type: Optional[argparse.Namespace] self.options = None # type: Optional[argparse.Namespace]
@ -126,7 +126,7 @@ class ServiceResource(six.with_metaclass(CodingStyleMixin,
if project_id: if project_id:
return project_id == self.cleanup_project_id return project_id == self.cleanup_project_id
else: else:
# Uncomment the following line once Shade and all OpenStack # Uncomment the following line once the SDK and all OpenStack
# services returns the resource owner. In the mean time no need # services returns the resource owner. In the mean time no need
# to be worrying. # to be worrying.
# logging.warning("Can't determine owner of resource %s", resource) # logging.warning("Can't determine owner of resource %s", resource)

View File

@ -11,7 +11,7 @@
# under the License. # under the License.
import unittest import unittest
import shade import openstack.connection
from ospurge.resources import cinder from ospurge.resources import cinder
from ospurge.tests import mock from ospurge.tests import mock
@ -19,7 +19,7 @@ from ospurge.tests import mock
class TestBackups(unittest.TestCase): class TestBackups(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_list(self): def test_list(self):
@ -40,7 +40,7 @@ class TestBackups(unittest.TestCase):
class TestSnapshots(unittest.TestCase): class TestSnapshots(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_list(self): def test_list(self):
@ -63,7 +63,7 @@ class TestSnapshots(unittest.TestCase):
class TestVolumes(unittest.TestCase): class TestVolumes(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud, project_id=42) self.creds_manager = mock.Mock(cloud=self.cloud, project_id=42)
def test_check_prerequisite(self): def test_check_prerequisite(self):

View File

@ -11,7 +11,7 @@
# under the License. # under the License.
import unittest import unittest
import shade import openstack.connection
from ospurge.resources import designate from ospurge.resources import designate
from ospurge.tests import mock from ospurge.tests import mock
@ -19,7 +19,7 @@ from ospurge.tests import mock
class TestZones(unittest.TestCase): class TestZones(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_list_without_service(self): def test_list_without_service(self):

View File

@ -11,7 +11,7 @@
# under the License. # under the License.
import unittest import unittest
import shade import openstack.connection
from ospurge.resources import glance from ospurge.resources import glance
from ospurge.tests import mock from ospurge.tests import mock
@ -19,7 +19,7 @@ from ospurge.tests import mock
class TestListImagesMixin(unittest.TestCase): class TestListImagesMixin(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.img_lister = glance.ListImagesMixin() self.img_lister = glance.ListImagesMixin()
self.img_lister.cloud = self.cloud self.img_lister.cloud = self.cloud
self.img_lister.cleanup_project_id = 42 self.img_lister.cleanup_project_id = 42
@ -53,7 +53,7 @@ class TestListImagesMixin(unittest.TestCase):
class TestImages(unittest.TestCase): class TestImages(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud, project_id=42) self.creds_manager = mock.Mock(cloud=self.cloud, project_id=42)
@mock.patch.object(glance.ListImagesMixin, 'list_images_by_owner') @mock.patch.object(glance.ListImagesMixin, 'list_images_by_owner')

View File

@ -11,7 +11,7 @@
# under the License. # under the License.
import unittest import unittest
import shade import openstack.connection
from ospurge.resources import heat from ospurge.resources import heat
from ospurge.tests import mock from ospurge.tests import mock
@ -19,7 +19,7 @@ from ospurge.tests import mock
class TestStacks(unittest.TestCase): class TestStacks(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_list_without_service(self): def test_list_without_service(self):

View File

@ -11,7 +11,7 @@
# under the License. # under the License.
import unittest import unittest
import shade import openstack.connection
from ospurge.resources import neutron from ospurge.resources import neutron
from ospurge.tests import mock from ospurge.tests import mock
@ -19,7 +19,7 @@ from ospurge.tests import mock
class TestFloatingIPs(unittest.TestCase): class TestFloatingIPs(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_check_prerequisite(self): def test_check_prerequisite(self):
@ -55,7 +55,7 @@ class TestFloatingIPs(unittest.TestCase):
class TestRouterInterfaces(unittest.TestCase): class TestRouterInterfaces(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_check_prerequisite(self): def test_check_prerequisite(self):
@ -103,7 +103,7 @@ class TestRouterInterfaces(unittest.TestCase):
class TestRouters(unittest.TestCase): class TestRouters(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_check_prerequisite(self): def test_check_prerequisite(self):
@ -139,7 +139,7 @@ class TestRouters(unittest.TestCase):
class TestPorts(unittest.TestCase): class TestPorts(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_list(self): def test_list(self):
@ -167,7 +167,7 @@ class TestPorts(unittest.TestCase):
class TestNetworks(unittest.TestCase): class TestNetworks(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_check_prerequisite(self): def test_check_prerequisite(self):
@ -211,7 +211,7 @@ class TestNetworks(unittest.TestCase):
class TestSecurityGroups(unittest.TestCase): class TestSecurityGroups(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_list(self): def test_list(self):

View File

@ -11,7 +11,7 @@
# under the License. # under the License.
import unittest import unittest
import shade import openstack.connection
from ospurge.resources import nova from ospurge.resources import nova
from ospurge.tests import mock from ospurge.tests import mock
@ -19,7 +19,7 @@ from ospurge.tests import mock
class TestServers(unittest.TestCase): class TestServers(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_list(self): def test_list(self):

View File

@ -11,17 +11,16 @@
# under the License. # under the License.
import unittest import unittest
import openstack.connection
from six.moves import urllib_parse from six.moves import urllib_parse
import shade
from ospurge.resources import swift from ospurge.resources import swift
from ospurge.tests import mock from ospurge.tests import mock
class TestListObjectsMixin(unittest.TestCase): class TestListObjectsMixin(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.obj_lister = swift.ListObjectsMixin() self.obj_lister = swift.ListObjectsMixin()
self.obj_lister.cloud = self.cloud self.obj_lister.cloud = self.cloud
@ -48,7 +47,7 @@ class TestListObjectsMixin(unittest.TestCase):
class TestObjects(unittest.TestCase): class TestObjects(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
def test_check_prerequisite(self): def test_check_prerequisite(self):
@ -98,7 +97,7 @@ class TestObjects(unittest.TestCase):
class TestContainers(unittest.TestCase): class TestContainers(unittest.TestCase):
def setUp(self): def setUp(self):
self.cloud = mock.Mock(spec_set=shade.openstackcloud.OpenStackCloud) self.cloud = mock.Mock(spec_set=openstack.connection.Connection)
self.creds_manager = mock.Mock(cloud=self.cloud) self.creds_manager = mock.Mock(cloud=self.cloud)
@mock.patch('ospurge.resources.swift.ListObjectsMixin.list_objects') @mock.patch('ospurge.resources.swift.ListObjectsMixin.list_objects')

View File

@ -14,15 +14,12 @@ import logging
import types import types
import unittest import unittest
import shade.exc
from openstack import exceptions as os_exceptions from openstack import exceptions as os_exceptions
from ospurge import exceptions from ospurge import exceptions
from ospurge import main from ospurge import main
from ospurge.resources.base import ServiceResource from ospurge.resources.base import ServiceResource
from ospurge.tests import mock from ospurge.tests import mock
from ospurge import utils
try: try:
@ -129,7 +126,7 @@ class TestFunctions(unittest.TestCase):
def test_runner_with_recoverable_exception(self): def test_runner_with_recoverable_exception(self):
class MyEndpointNotFound(Exception): class MyEndpointNotFound(Exception):
pass pass
exc = shade.exc.OpenStackCloudException("") exc = os_exceptions.OpenStackCloudException("")
exc.inner_exception = (MyEndpointNotFound, ) exc.inner_exception = (MyEndpointNotFound, )
resource_manager = mock.Mock(list=mock.Mock(side_effect=exc)) resource_manager = mock.Mock(list=mock.Mock(side_effect=exc))
exit = mock.Mock() exit = mock.Mock()
@ -145,19 +142,18 @@ class TestFunctions(unittest.TestCase):
self.assertFalse(exit.set.called) self.assertFalse(exit.set.called)
@mock.patch.object(main, 'connection') @mock.patch.object(main, 'connection')
@mock.patch.object(main, 'os_client_config', autospec=True) @mock.patch.object(main, 'loader', autospec=True)
@mock.patch.object(main, 'shade')
@mock.patch('argparse.ArgumentParser.parse_args') @mock.patch('argparse.ArgumentParser.parse_args')
@mock.patch('threading.Event', autospec=True) @mock.patch('threading.Event', autospec=True)
@mock.patch('concurrent.futures.ThreadPoolExecutor', autospec=True) @mock.patch('concurrent.futures.ThreadPoolExecutor', autospec=True)
@mock.patch('sys.exit', autospec=True) @mock.patch('sys.exit', autospec=True)
def test_main(self, m_sys_exit, m_tpe, m_event, m_parse_args, m_shade, def test_main(self, m_sys_exit, m_tpe, m_event, m_parse_args,
m_oscc, m_conn): m_oscc, m_conn):
m_tpe.return_value.__enter__.return_value.map.side_effect = \ m_tpe.return_value.__enter__.return_value.map.side_effect = \
KeyboardInterrupt KeyboardInterrupt
m_parse_args.return_value.purge_own_project = False m_parse_args.return_value.purge_own_project = False
m_parse_args.return_value.resource = None m_parse_args.return_value.resource = None
m_conn.Connection().identity.get_tenant().is_enabled = False m_conn.Connection().get_project().is_enabled = False
main.main() main.main()
@ -181,14 +177,13 @@ class TestFunctions(unittest.TestCase):
self.assertIsInstance(m_sys_exit.call_args[0][0], int) self.assertIsInstance(m_sys_exit.call_args[0][0], int)
@mock.patch.object(main, 'connection') @mock.patch.object(main, 'connection')
@mock.patch.object(main, 'os_client_config', autospec=True) @mock.patch.object(main, 'loader', autospec=True)
@mock.patch.object(main, 'shade')
@mock.patch('argparse.ArgumentParser.parse_args') @mock.patch('argparse.ArgumentParser.parse_args')
@mock.patch('threading.Event', autospec=True) @mock.patch('threading.Event', autospec=True)
@mock.patch('concurrent.futures.ThreadPoolExecutor', autospec=True) @mock.patch('concurrent.futures.ThreadPoolExecutor', autospec=True)
@mock.patch('sys.exit', autospec=True) @mock.patch('sys.exit', autospec=True)
def test_main_resource(self, m_sys_exit, m_tpe, m_event, m_parse_args, def test_main_resource(self, m_sys_exit, m_tpe, m_event, m_parse_args,
m_shade, m_oscc, m_conn): m_oscc, m_conn):
m_tpe.return_value.__enter__.return_value.map.side_effect = \ m_tpe.return_value.__enter__.return_value.map.side_effect = \
KeyboardInterrupt KeyboardInterrupt
m_parse_args.return_value.purge_own_project = False m_parse_args.return_value.purge_own_project = False
@ -202,86 +197,88 @@ class TestFunctions(unittest.TestCase):
self.assertIsInstance(obj, ServiceResource) self.assertIsInstance(obj, ServiceResource)
@mock.patch.object(main, 'os_client_config') @mock.patch.object(main, 'loader')
@mock.patch.object(main, 'connection') @mock.patch.object(main, 'connection')
@mock.patch.object(main, 'shade')
class TestCredentialsManager(unittest.TestCase): class TestCredentialsManager(unittest.TestCase):
def test_init_with_purge_own_project(self, m_shade, m_conn, m_osc): def test_init_with_purge_own_project(self, m_conn, m_osc):
_options = SimpleNamespace( _options = SimpleNamespace(
purge_own_project=True, purge_project=None) purge_own_project=True, purge_project=None)
_config = m_osc.OpenStackConfig() _config = m_osc.OpenStackConfig()
creds_mgr = main.CredentialsManager(_options, _config) creds_manager = main.CredentialsManager(_options, _config)
self.assertEqual(_options, creds_mgr.options) self.assertEqual(_options, creds_manager.options)
self.assertEqual(False, creds_mgr.revoke_role_after_purge) self.assertEqual(False, creds_manager.revoke_role_after_purge)
self.assertEqual(False, creds_mgr.disable_project_after_purge) self.assertEqual(False, creds_manager.disable_project_after_purge)
self.assertIsNone(creds_mgr.connection) self.assertEqual(m_conn.Connection.return_value,
creds_manager.cloud)
m_shade.openstack_cloud.assert_called_once_with( m_conn.Connection.assert_called_once_with(
argparse=_options, config=_config) config=_config.get_one())
self.assertEqual(m_shade.openstack_cloud.return_value, self.assertEqual(m_conn.Connection.return_value,
creds_mgr.cloud) creds_manager.cloud)
self.assertEqual( self.assertEqual(
creds_mgr.cloud.keystone_session.get_user_id(), creds_manager.cloud.current_user_id,
creds_mgr.user_id creds_manager.user_id
) )
self.assertEqual( self.assertEqual(
creds_mgr.cloud.keystone_session.get_project_id(), creds_manager.cloud.current_project_id,
creds_mgr.project_id creds_manager.project_id
) )
creds_mgr.cloud.cloud_config.get_auth_args.assert_called_once_with() def test_init_with_purge_project(self, m_conn, m_osc):
@mock.patch.object(utils, 'replace_project_info')
def test_init_with_purge_project(self, m_replace, m_shade, m_conn, m_osc):
_config = m_osc.OpenStackConfig() _config = m_osc.OpenStackConfig()
_options = SimpleNamespace( _options = SimpleNamespace(
purge_own_project=False, purge_project=mock.sentinel.purge_project) purge_own_project=False, purge_project=mock.sentinel.purge_project)
creds_mgr = main.CredentialsManager(_options, _config) creds_manager = main.CredentialsManager(_options, _config)
m_conn.Connection.assert_called_once_with(config=_config.get_one()) m_conn.Connection.assert_called_once_with(config=_config.get_one())
self.assertEqual(m_conn.Connection.return_value, m_conn.Connection().get_project.assert_called_once_with(
creds_mgr.connection)
creds_mgr.connection.identity.get_tenant.assert_called_once_with(
_options.purge_project) _options.purge_project)
self.assertEqual( self.assertEqual(
creds_mgr.connection.identity.get_user_id.return_value, m_conn.Connection.return_value,
creds_mgr.user_id creds_manager.admin_cloud
) )
self.assertEqual( self.assertEqual(
creds_mgr.connection.identity.get_tenant().id, m_conn.Connection().connect_as_project.return_value,
creds_mgr.project_id creds_manager.cloud
) )
self.assertFalse(creds_mgr.disable_project_after_purge)
self.assertEqual(
m_shade.openstack_cloud.return_value,
creds_mgr.cloud
)
m_replace.assert_called_once_with(
creds_mgr.connection.config.config,
creds_mgr.project_id
)
creds_mgr.cloud.cloud_config.get_auth_args.assert_called_once_with()
def test_init_with_project_not_found(self, m_shade, m_conn, m_osc): self.assertEqual(
m_conn.Connection.return_value.identity.get_tenant\ creds_manager.cloud.current_user_id,
.side_effect = os_exceptions.NotFoundException creds_manager.user_id
)
self.assertEqual(
creds_manager.cloud.current_project_id,
creds_manager.project_id
)
self.assertFalse(creds_manager.disable_project_after_purge)
def test_init_with_project_not_found(self, m_conn, m_osc):
m_conn.Connection().get_project.return_value = None
self.assertRaises( self.assertRaises(
exceptions.OSProjectNotFound, exceptions.OSProjectNotFound,
main.CredentialsManager, main.CredentialsManager,
mock.Mock(purge_own_project=False), m_osc.OpenStackConfig() mock.Mock(purge_own_project=False), m_osc.OpenStackConfig()
) )
def test_ensure_role_on_project(self, m_shade, m_conn, m_osc): def test_ensure_role_on_project(self, m_conn, m_osc):
options = mock.Mock(purge_own_project=False) options = mock.Mock(purge_own_project=False)
config = m_osc.OpenStackConfig() config = m_osc.OpenStackConfig()
creds_manager = main.CredentialsManager(options, config) creds_manager = main.CredentialsManager(options, config)
creds_manager.ensure_role_on_project() creds_manager.ensure_role_on_project()
m_conn.Connection.return_value.grant_role.assert_called_once_with( self.assertEqual(
m_conn.Connection.return_value,
creds_manager.admin_cloud
)
self.assertEqual(
m_conn.Connection().connect_as_project.return_value,
creds_manager.cloud
)
creds_manager.admin_cloud.grant_role.assert_called_once_with(
options.admin_role_name, project=options.purge_project, options.admin_role_name, project=options.purge_project,
user=mock.ANY) user=mock.ANY)
self.assertEqual(True, creds_manager.revoke_role_after_purge) self.assertEqual(True, creds_manager.revoke_role_after_purge)
@ -293,24 +290,45 @@ class TestCredentialsManager(unittest.TestCase):
creds_manager.ensure_role_on_project() creds_manager.ensure_role_on_project()
self.assertEqual(False, creds_manager.revoke_role_after_purge) self.assertEqual(False, creds_manager.revoke_role_after_purge)
def test_revoke_role_on_project(self, m_shade, m_conn, m_osc): def test_revoke_role_on_project(self, m_conn, m_osc):
options = mock.Mock(purge_own_project=False) options = mock.Mock(purge_own_project=False)
config = m_osc.OpenStackConfig() config = m_osc.OpenStackConfig()
creds_manager = main.CredentialsManager(options, config) creds_manager = main.CredentialsManager(options, config)
creds_manager.revoke_role_on_project() creds_manager.revoke_role_on_project()
m_conn.Connection().revoke_role.assert_called_once_with( self.assertEqual(
m_conn.Connection.return_value,
creds_manager.admin_cloud
)
self.assertEqual(
m_conn.Connection().connect_as_project.return_value,
creds_manager.cloud
)
creds_manager.admin_cloud.revoke_role.assert_called_once_with(
options.admin_role_name, project=options.purge_project, options.admin_role_name, project=options.purge_project,
user=mock.ANY) user=mock.ANY)
def test_ensure_enabled_project(self, m_shade, m_conn, m_osc): def test_ensure_enabled_project(self, m_conn, m_osc):
m_conn.Connection().identity.get_tenant().is_enabled = False m_conn.Connection().get_project().is_enabled = False
options = mock.Mock(purge_own_project=False)
creds_manager = main.CredentialsManager( creds_manager = main.CredentialsManager(
mock.Mock(purge_own_project=False), m_osc.OpenStackConfig()) options, m_osc.OpenStackConfig())
creds_manager.ensure_enabled_project() creds_manager.ensure_enabled_project()
self.assertEqual(
m_conn.Connection.return_value,
creds_manager.admin_cloud
)
self.assertEqual(
m_conn.Connection().connect_as_project.return_value,
creds_manager.cloud
)
self.assertEqual(True, creds_manager.disable_project_after_purge) self.assertEqual(True, creds_manager.disable_project_after_purge)
m_conn.Connection().update_project.assert_called_once_with( m_conn.Connection().connect_as_project.assert_called_once_with(
options.purge_project)
creds_manager.admin_cloud.update_project.assert_called_once_with(
mock.ANY, enabled=True) mock.ANY, enabled=True)
# If project is enabled before purge, no need to disable it after # If project is enabled before purge, no need to disable it after
@ -321,12 +339,23 @@ class TestCredentialsManager(unittest.TestCase):
self.assertEqual(False, creds_manager.disable_project_after_purge) self.assertEqual(False, creds_manager.disable_project_after_purge)
self.assertEqual(1, m_conn.Connection().update_project.call_count) self.assertEqual(1, m_conn.Connection().update_project.call_count)
def test_disable_project(self, m_shade, m_conn, m_osc): def test_disable_project(self, m_conn, m_osc):
options = mock.Mock(purge_own_project=False) options = mock.Mock(purge_own_project=False)
config = m_osc.OpenStackConfig() config = m_osc.OpenStackConfig()
creds_manager = main.CredentialsManager(options, config) creds_manager = main.CredentialsManager(options, config)
creds_manager.disable_project() creds_manager.disable_project()
self.assertEqual(
m_conn.Connection.return_value,
creds_manager.admin_cloud
)
self.assertEqual(
m_conn.Connection().connect_as_project.return_value,
creds_manager.cloud
)
m_conn.Connection().connect_as_project.assert_called_once_with(
options.purge_project)
m_conn.Connection().update_project.assert_called_once_with( m_conn.Connection().update_project.assert_called_once_with(
mock.ANY, enabled=False mock.ANY, enabled=False
) )

View File

@ -9,18 +9,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
import os import os
import pkg_resources
import types import types
import typing import typing
import unittest import unittest
import pkg_resources from openstack import exceptions as os_exceptions
import six import six
import shade
from ospurge.resources.base import ServiceResource from ospurge.resources.base import ServiceResource
from ospurge.tests import mock from ospurge.tests import mock
from ospurge import utils from ospurge import utils
@ -42,27 +39,6 @@ def register_test_entry_point():
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
def test_replace_project_info_in_config(self):
config = {
'cloud': 'foo',
'auth': {
'project_name': 'bar'
}
}
new_conf = utils.replace_project_info(
config, mock.sentinel.project)
self.assertEqual(new_conf, {
'auth': {
'project_id': mock.sentinel.project
}
})
self.assertEqual(config, {
'cloud': 'foo',
'auth': {
'project_name': 'bar'
}
})
def test_load_ospurge_resource_modules(self): def test_load_ospurge_resource_modules(self):
modules = utils.load_ospurge_resource_modules() modules = utils.load_ospurge_resource_modules()
@ -100,36 +76,15 @@ class TestUtils(unittest.TestCase):
def test_call_and_ignore_notfound(self): def test_call_and_ignore_notfound(self):
def raiser(): def raiser():
raise shade.exc.OpenStackCloudResourceNotFound("") raise os_exceptions.OpenStackCloudException("")
self.assertIsNone( self.assertIsNone(
utils.call_and_ignore_exc( utils.call_and_ignore_exc(
shade.exc.OpenStackCloudResourceNotFound, raiser os_exceptions.OpenStackCloudException, raiser
) )
) )
m = mock.Mock() m = mock.Mock()
utils.call_and_ignore_exc( utils.call_and_ignore_exc(
shade.exc.OpenStackCloudResourceNotFound, m, 42) os_exceptions.OpenStackCloudException, m, 42)
self.assertEqual([mock.call(42)], m.call_args_list) self.assertEqual([mock.call(42)], m.call_args_list)
@mock.patch('logging.getLogger', autospec=True)
def test_monkeypatch_oscc_logging_warning(self, mock_getLogger):
oscc_target = 'os_client_config.cloud_config'
m_oscc_logger, m_other_logger = mock.Mock(), mock.Mock()
mock_getLogger.side_effect = \
lambda m: m_oscc_logger if m == oscc_target else m_other_logger
@utils.monkeypatch_oscc_logging_warning
def f():
logging.getLogger(oscc_target).warning("foo")
logging.getLogger(oscc_target).warning("!catalog entry not found!")
logging.getLogger("other").warning("!catalog entry not found!")
f()
self.assertEqual([mock.call.warning('foo'), ],
m_oscc_logger.mock_calls)
self.assertEqual([mock.call.warning('!catalog entry not found!')],
m_other_logger.mock_calls)

View File

@ -9,8 +9,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import functools
import importlib import importlib
import logging import logging
import os import os
@ -70,48 +68,8 @@ def get_resource_classes(resources=None):
return [c for c in all_classes if regex.match(c.__name__)] return [c for c in all_classes if regex.match(c.__name__)]
def monkeypatch_oscc_logging_warning(f):
"""
Monkey-patch logging.warning() method to silence 'os_client_config' when
it complains that a Keystone catalog entry is not found. This warning
benignly happens when, for instance, we try to cleanup a Neutron resource
but Neutron is not available on the target cloud environment.
"""
oscc_target = 'os_client_config.cloud_config'
orig_logging = logging.getLogger(oscc_target).warning
def logging_warning(msg, *args, **kwargs):
if 'catalog entry not found' not in msg:
orig_logging(msg, *args, **kwargs)
@functools.wraps(f)
def wrapper(*args, **kwargs):
try:
setattr(logging.getLogger(oscc_target), 'warning', logging_warning)
return f(*args, **kwargs)
finally:
setattr(logging.getLogger(oscc_target), 'warning', orig_logging)
return wrapper
def call_and_ignore_exc(exc, f, *args): def call_and_ignore_exc(exc, f, *args):
try: try:
f(*args) f(*args)
except exc as e: except exc as e:
logging.debug("The following exception was ignored: %r", e) logging.debug("The following exception was ignored: %r", e)
def replace_project_info(config, new_project_id):
"""
Replace all tenant/project info in a `os_client_config` config dict with
a new project. This is used to bind/scope to another project.
"""
new_conf = copy.deepcopy(config)
new_conf.pop('cloud', None)
new_conf['auth'].pop('project_name', None)
new_conf['auth'].pop('project_id', None)
new_conf['auth']['project_id'] = new_project_id
return new_conf

View File

@ -1,15 +0,0 @@
- hosts: primary
tasks:
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=/logs/**
- --include=*/
- --exclude=*
- --prune-empty-dirs

View File

@ -1,53 +0,0 @@
- hosts: all
tasks:
- name: Ensure legacy workspace directory
file:
path: '{{ ansible_user_dir }}/workspace'
state: directory
- shell:
cmd: |
set -e
set -x
cat > clonemap.yaml << EOF
clonemap:
- name: openstack/devstack-gate
dest: devstack-gate
EOF
/usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
https://opendev.org \
openstack/devstack-gate
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
export PYTHONUNBUFFERED=true
export DEVSTACK_GATE_TEMPEST=0
export DEVSTACK_GATE_NEUTRON=1
export BRANCH_OVERRIDE=default
export DEVSTACK_LOCAL_CONFIG="enable_plugin designate https://opendev.org/openstack/designate"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"DESIGNATE_SERVICE_PORT_DNS=5322"
export PROJECTS="openstack/designate $PROJECTS"
export PROJECTS="x/ospurge $PROJECTS"
if [ "$BRANCH_OVERRIDE" != "default" ] ; then
export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
fi
function post_test_hook {
bash -xe $BASE/new/ospurge/tools/post_test_hook.sh
}
export -f post_test_hook
cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
./safe-devstack-vm-gate-wrap.sh
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'

View File

@ -1,8 +1,7 @@
os-client-config>=1.22.0 # Apache-2.0
pbr>=1.8 # Apache-2.0 pbr>=1.8 # Apache-2.0
six six
shade>=1.13.1
typing>=3.5.2.2 # PSF typing>=3.5.2.2 # PSF
openstacksdk # Apache-2.0
# Python 2.7 dependencies # Python 2.7 dependencies
funcsigs; python_version < '3.0' funcsigs; python_version < '3.0'

View File

@ -110,7 +110,7 @@ for i in ${!pid[@]}; do
unset "pid[$i]" unset "pid[$i]"
done done
echo "Done populating. Moving on to cleanup."
######################## ########################
### Cleanup ### Cleanup
@ -127,6 +127,9 @@ assert_compute && assert_network && assert_volume
tox -e run -- \ tox -e run -- \
--os-auth-url http://localhost/identity \ --os-auth-url http://localhost/identity \
--os-cacert /opt/stack/data/ca-bundle.pem \
--os-identity-api-version 3 \
--os-region-name $OS_REGION_NAME \
--os-username demo --os-project-name invisible_to_admin \ --os-username demo --os-project-name invisible_to_admin \
--os-password $invisible_to_admin_demo_pass \ --os-password $invisible_to_admin_demo_pass \
--os-domain-id=$OS_PROJECT_DOMAIN_ID \ --os-domain-id=$OS_PROJECT_DOMAIN_ID \

View File

@ -15,8 +15,8 @@
# OS_PROJECT_NAME with various resources. The purpose is to test # OS_PROJECT_NAME with various resources. The purpose is to test
# ospurge. # ospurge.
# Be strict # Be strict but don't exit automatically on error (exit_on_failure handles that)
set -ueo pipefail set -uo pipefail
function exit_on_failure { function exit_on_failure {
RET_CODE=$? RET_CODE=$?

View File

@ -1,37 +0,0 @@
#!/bin/sh
# 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.
export OSPURGE_DIR="$BASE/new/ospurge"
cd $OSPURGE_DIR
sudo chown -R stack:stack $OSPURGE_DIR
CLOUDS_YAML=/etc/openstack/clouds.yaml
if [ ! -e ${CLOUDS_YAML} ]; then
# stable/liberty had clouds.yaml in the home/base directory
sudo mkdir -p /etc/openstack
sudo cp $BASE/new/.config/openstack/clouds.yaml ${CLOUDS_YAML}
sudo chown -R stack:stack /etc/openstack
fi
echo "Running OSpurge functional test suite"
set +e
sudo -E -H -u stack tox -e functional
EXIT_CODE=$?
set -e
exit $EXIT_CODE