diff --git a/releasenotes/notes/resource-list-cbf9779e8b434654.yaml b/releasenotes/notes/resource-list-cbf9779e8b434654.yaml new file mode 100644 index 0000000000..bbd2f16850 --- /dev/null +++ b/releasenotes/notes/resource-list-cbf9779e8b434654.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + A new interface ``--resource-list`` has been introduced in the + ``tempest cleanup`` command to remove the resources created by + Tempest. A new config option in the default section, ``record_resources``, + is added to allow the recording of all resources created by Tempest. + A list of these resources will be saved in ``resource_list.json`` file, + which will be appended in case of multiple Tempest runs. This file + is intended to be used with the ``tempest cleanup`` command if it is + used with the newly added option ``--resource-list``. diff --git a/tempest/clients.py b/tempest/clients.py index 5338ed4c7c..e432120eba 100644 --- a/tempest/clients.py +++ b/tempest/clients.py @@ -13,8 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. +import os + +from oslo_concurrency import lockutils + from tempest import config from tempest.lib import auth +from tempest.lib.common.rest_client import RestClient from tempest.lib import exceptions as lib_exc from tempest.lib.services import clients @@ -35,6 +40,11 @@ class Manager(clients.ServiceClients): super(Manager, self).__init__( credentials=credentials, identity_uri=identity_uri, scope=scope, region=CONF.identity.region) + if CONF.record_resources: + RestClient.lock_dir = os.path.join( + lockutils.get_lock_path(CONF), + 'tempest-rec-rw-lock') + RestClient.record_resources = True # TODO(andreaf) When clients are initialised without the right # parameters available, the calls below will trigger a KeyError. # We should catch that and raise a better error. diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py index 2a406dedcf..8d06f930cf 100644 --- a/tempest/cmd/cleanup.py +++ b/tempest/cmd/cleanup.py @@ -87,6 +87,23 @@ Runtime Arguments ``saved_state.json`` file will be ignored and cleanup will be done based on the passed prefix only. +* ``--resource-list``: Allows the use of file ``./resource_list.json``, which + contains all resources created by Tempest during all Tempest runs, to + create another method for removing only resources created by Tempest. + List of these resources is created when config option ``record_resources`` + in default section is set to true. After using this option for cleanup, + the existing ``./resource_list.json`` is cleared from deleted resources. + + When this option is used, ``saved_state.json`` file is not needed (no + need to run with ``--init-saved-state`` first). If there is any + ``saved_state.json`` file present and you run the tempest cleanup with + ``--resource-list``, the ``saved_state.json`` file will be ignored and + cleanup will be done based on the ``resource_list.json`` only. + + If you run tempest cleanup with both ``--prefix`` and ``--resource-list``, + the ``--resource-list`` option will be ignored and cleanup will be done + based on the ``--prefix`` option only. + * ``--help``: Print the help text for the command and parameters. .. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the @@ -122,6 +139,7 @@ from tempest.lib import exceptions SAVED_STATE_JSON = "saved_state.json" DRY_RUN_JSON = "dry_run.json" +RESOURCE_LIST_JSON = "resource_list.json" LOG = logging.getLogger(__name__) CONF = config.CONF @@ -164,6 +182,7 @@ class TempestCleanup(command.Command): self.admin_mgr = clients.Manager( credentials.get_configured_admin_credentials()) self.dry_run_data = {} + self.resource_data = {} self.json_data = {} # available services @@ -177,12 +196,20 @@ class TempestCleanup(command.Command): self._init_state() return - self._load_json() + if parsed_args.prefix: + return + + if parsed_args.resource_list: + self._load_resource_list() + return + + self._load_saved_state() def _cleanup(self): LOG.info("Begin cleanup") is_dry_run = self.options.dry_run is_preserve = not self.options.delete_tempest_conf_objects + is_resource_list = self.options.resource_list is_save_state = False cleanup_prefix = self.options.prefix @@ -194,8 +221,10 @@ class TempestCleanup(command.Command): # they are in saved state json. Therefore is_preserve is False kwargs = {'data': self.dry_run_data, 'is_dry_run': is_dry_run, + 'resource_list_json': self.resource_data, 'saved_state_json': self.json_data, 'is_preserve': False, + 'is_resource_list': is_resource_list, 'is_save_state': is_save_state, 'prefix': cleanup_prefix} project_service = cleanup_service.ProjectService(admin_mgr, **kwargs) @@ -208,8 +237,10 @@ class TempestCleanup(command.Command): kwargs = {'data': self.dry_run_data, 'is_dry_run': is_dry_run, + 'resource_list_json': self.resource_data, 'saved_state_json': self.json_data, 'is_preserve': is_preserve, + 'is_resource_list': is_resource_list, 'is_save_state': is_save_state, 'prefix': cleanup_prefix, 'got_exceptions': self.GOT_EXCEPTIONS} @@ -228,11 +259,17 @@ class TempestCleanup(command.Command): f.write(json.dumps(self.dry_run_data, sort_keys=True, indent=2, separators=(',', ': '))) + if is_resource_list: + LOG.info("Clearing 'resource_list.json' file.") + with open(RESOURCE_LIST_JSON, 'w') as f: + f.write('{}') + def _clean_project(self, project): LOG.debug("Cleaning project: %s ", project['name']) is_dry_run = self.options.dry_run dry_run_data = self.dry_run_data is_preserve = not self.options.delete_tempest_conf_objects + is_resource_list = self.options.resource_list project_id = project['id'] project_name = project['name'] project_data = None @@ -244,7 +281,9 @@ class TempestCleanup(command.Command): kwargs = {'data': project_data, 'is_dry_run': is_dry_run, 'saved_state_json': self.json_data, + 'resource_list_json': self.resource_data, 'is_preserve': is_preserve, + 'is_resource_list': is_resource_list, 'is_save_state': False, 'project_id': project_id, 'prefix': cleanup_prefix, @@ -287,6 +326,19 @@ class TempestCleanup(command.Command): "ignored when --init-saved-state is used so that " "it can capture the true init state - all " "resources present at that moment.") + parser.add_argument('--resource-list', action="store_true", + dest='resource_list', default=False, + help="Runs tempest cleanup with generated " + "JSON file: " + RESOURCE_LIST_JSON + " to " + "erase resources created during Tempest run. " + "NOTE: To create " + RESOURCE_LIST_JSON + " " + "set config option record_resources under default " + "section in tempest.conf file to true. This " + "option will be ignored when --init-saved-state " + "is used so that it can capture the true init " + "state - all resources present at that moment. " + "This option will be ignored if passed with " + "--prefix.") return parser def get_description(self): @@ -304,6 +356,7 @@ class TempestCleanup(command.Command): 'is_dry_run': False, 'saved_state_json': data, 'is_preserve': False, + 'is_resource_list': False, 'is_save_state': True, # must be None as we want to capture true init state # (all resources present) thus no filtering based @@ -326,15 +379,31 @@ class TempestCleanup(command.Command): f.write(json.dumps(data, sort_keys=True, indent=2, separators=(',', ': '))) - def _load_json(self, saved_state_json=SAVED_STATE_JSON): + def _load_resource_list(self, resource_list_json=RESOURCE_LIST_JSON): + try: + with open(resource_list_json, 'rb') as json_file: + self.resource_data = json.load(json_file) + except IOError as ex: + LOG.exception( + "Failed loading 'resource_list.json', please " + "be sure you created this file by setting config " + "option record_resources in default section to true " + "prior to running tempest. Exception: %s", ex) + sys.exit(ex) + except Exception as ex: + LOG.exception( + "Exception parsing 'resource_list.json' : %s", ex) + sys.exit(ex) + + def _load_saved_state(self, saved_state_json=SAVED_STATE_JSON): try: with open(saved_state_json, 'rb') as json_file: self.json_data = json.load(json_file) - except IOError as ex: - LOG.exception("Failed loading saved state, please be sure you" - " have first run cleanup with --init-saved-state " - "flag prior to running tempest. Exception: %s", ex) + LOG.exception( + "Failed loading saved state, please be sure you" + " have first run cleanup with --init-saved-state " + "flag prior to running tempest. Exception: %s", ex) sys.exit(ex) except Exception as ex: LOG.exception("Exception parsing saved state json : %s", ex) diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py index 8651ab064d..b20294003b 100644 --- a/tempest/cmd/cleanup_service.py +++ b/tempest/cmd/cleanup_service.py @@ -120,6 +120,13 @@ class BaseService(object): if item['name'].startswith(self.prefix)] return items + def _filter_by_resource_list(self, item_list, attr): + if attr not in self.resource_list_json: + return [] + items = [item for item in item_list if item['id'] + in self.resource_list_json[attr].keys()] + return items + def _filter_out_ids_from_saved(self, item_list, attr): items = [item for item in item_list if item['id'] not in self.saved_state_json[attr].keys()] @@ -166,8 +173,11 @@ class SnapshotService(BaseService): def list(self): client = self.client snaps = client.list_snapshots()['snapshots'] + if self.prefix: snaps = self._filter_by_prefix(snaps) + elif self.is_resource_list: + snaps = self._filter_by_resource_list(snaps, 'snapshots') elif not self.is_save_state: # recreate list removing saved snapshots snaps = self._filter_out_ids_from_saved(snaps, 'snapshots') @@ -205,8 +215,11 @@ class ServerService(BaseService): client = self.client servers_body = client.list_servers() servers = servers_body['servers'] + if self.prefix: servers = self._filter_by_prefix(servers) + elif self.is_resource_list: + servers = self._filter_by_resource_list(servers, 'servers') elif not self.is_save_state: # recreate list removing saved servers servers = self._filter_out_ids_from_saved(servers, 'servers') @@ -238,9 +251,12 @@ class ServerGroupService(ServerService): def list(self): client = self.server_groups_client - sgs = client.list_server_groups()['server_groups'] + sgs = client.list_server_groups(all_projects=True)['server_groups'] + if self.prefix: sgs = self._filter_by_prefix(sgs) + elif self.is_resource_list: + sgs = self._filter_by_resource_list(sgs, 'server_groups') elif not self.is_save_state: # recreate list removing saved server_groups sgs = self._filter_out_ids_from_saved(sgs, 'server_groups') @@ -276,8 +292,13 @@ class KeyPairService(BaseService): def list(self): client = self.client keypairs = client.list_keypairs()['keypairs'] + if self.prefix: keypairs = self._filter_by_prefix(keypairs) + elif self.is_resource_list: + keypairs = [keypair for keypair in keypairs + if keypair['keypair']['name'] + in self.resource_list_json['keypairs'].keys()] elif not self.is_save_state: # recreate list removing saved keypairs keypairs = [keypair for keypair in keypairs @@ -317,8 +338,11 @@ class VolumeService(BaseService): def list(self): client = self.client vols = client.list_volumes()['volumes'] + if self.prefix: vols = self._filter_by_prefix(vols) + elif self.is_resource_list: + vols = self._filter_by_resource_list(vols, 'volumes') elif not self.is_save_state: # recreate list removing saved volumes vols = self._filter_out_ids_from_saved(vols, 'volumes') @@ -462,8 +486,11 @@ class NetworkService(BaseNetworkService): client = self.networks_client networks = client.list_networks(**self.tenant_filter) networks = networks['networks'] + if self.prefix: networks = self._filter_by_prefix(networks) + elif self.is_resource_list: + networks = self._filter_by_resource_list(networks, 'networks') else: if not self.is_save_state: # recreate list removing saved networks @@ -500,15 +527,17 @@ class NetworkService(BaseNetworkService): class NetworkFloatingIpService(BaseNetworkService): def list(self): - if self.prefix: - # this means we're cleaning resources based on a certain prefix, - # this resource doesn't have a name, therefore return empty list - return [] client = self.floating_ips_client flips = client.list_floatingips(**self.tenant_filter) flips = flips['floatingips'] - if not self.is_save_state: + if self.prefix: + # this means we're cleaning resources based on a certain prefix, + # this resource doesn't have a name, therefore return empty list + return [] + elif self.is_resource_list: + flips = self._filter_by_resource_list(flips, 'floatingips') + elif not self.is_save_state: # recreate list removing saved flips flips = self._filter_out_ids_from_saved(flips, 'floatingips') LOG.debug("List count, %s Network Floating IPs", len(flips)) @@ -543,8 +572,11 @@ class NetworkRouterService(BaseNetworkService): client = self.routers_client routers = client.list_routers(**self.tenant_filter) routers = routers['routers'] + if self.prefix: routers = self._filter_by_prefix(routers) + elif self.is_resource_list: + routers = self._filter_by_resource_list(routers, 'routers') else: if not self.is_save_state: # recreate list removing saved routers @@ -592,16 +624,19 @@ class NetworkRouterService(BaseNetworkService): class NetworkMeteringLabelRuleService(NetworkService): def list(self): - if self.prefix: - # this means we're cleaning resources based on a certain prefix, - # this resource doesn't have a name, therefore return empty list - return [] client = self.metering_label_rules_client rules = client.list_metering_label_rules() rules = rules['metering_label_rules'] rules = self._filter_by_tenant_id(rules) - if not self.is_save_state: + if self.prefix: + # this means we're cleaning resources based on a certain prefix, + # this resource doesn't have a name, therefore return empty list + return [] + elif self.is_resource_list: + rules = self._filter_by_resource_list( + rules, 'metering_label_rules') + elif not self.is_save_state: rules = self._filter_out_ids_from_saved( rules, 'metering_label_rules') # recreate list removing saved rules @@ -638,8 +673,12 @@ class NetworkMeteringLabelService(BaseNetworkService): labels = client.list_metering_labels() labels = labels['metering_labels'] labels = self._filter_by_tenant_id(labels) + if self.prefix: labels = self._filter_by_prefix(labels) + elif self.is_resource_list: + labels = self._filter_by_resource_list( + labels, 'metering_labels') elif not self.is_save_state: # recreate list removing saved labels labels = self._filter_out_ids_from_saved( @@ -677,8 +716,11 @@ class NetworkPortService(BaseNetworkService): client.list_ports(**self.tenant_filter)['ports'] if port["device_owner"] == "" or port["device_owner"].startswith("compute:")] + if self.prefix: ports = self._filter_by_prefix(ports) + elif self.is_resource_list: + ports = self._filter_by_resource_list(ports, 'ports') else: if not self.is_save_state: # recreate list removing saved ports @@ -717,8 +759,12 @@ class NetworkSecGroupService(BaseNetworkService): secgroups = [secgroup for secgroup in client.list_security_groups(**filter)['security_groups'] if secgroup['name'] != 'default'] + if self.prefix: secgroups = self._filter_by_prefix(secgroups) + elif self.is_resource_list: + secgroups = self._filter_by_resource_list( + secgroups, 'security_groups') else: if not self.is_save_state: # recreate list removing saved security_groups @@ -760,8 +806,11 @@ class NetworkSubnetService(BaseNetworkService): client = self.subnets_client subnets = client.list_subnets(**self.tenant_filter) subnets = subnets['subnets'] + if self.prefix: subnets = self._filter_by_prefix(subnets) + elif self.is_resource_list: + subnets = self._filter_by_resource_list(subnets, 'subnets') else: if not self.is_save_state: # recreate list removing saved subnets @@ -797,8 +846,11 @@ class NetworkSubnetPoolsService(BaseNetworkService): def list(self): client = self.subnetpools_client pools = client.list_subnetpools(**self.tenant_filter)['subnetpools'] + if self.prefix: pools = self._filter_by_prefix(pools) + elif self.is_resource_list: + pools = self._filter_by_resource_list(pools, 'subnetpools') else: if not self.is_save_state: # recreate list removing saved subnet pools @@ -838,13 +890,18 @@ class RegionService(BaseService): self.client = manager.regions_client def list(self): + client = self.client + regions = client.list_regions() + if self.prefix: # this means we're cleaning resources based on a certain prefix, # this resource doesn't have a name, therefore return empty list return [] - client = self.client - regions = client.list_regions() - if not self.is_save_state: + elif self.is_resource_list: + regions = self._filter_by_resource_list( + regions['regions'], 'regions') + return regions + elif not self.is_save_state: regions = self._filter_out_ids_from_saved( regions['regions'], 'regions') LOG.debug("List count, %s Regions", len(regions)) @@ -884,8 +941,11 @@ class FlavorService(BaseService): def list(self): client = self.client flavors = client.list_flavors({"is_public": None})['flavors'] + if self.prefix: flavors = self._filter_by_prefix(flavors) + elif self.is_resource_list: + flavors = self._filter_by_resource_list(flavors, 'flavors') else: if not self.is_save_state: # recreate list removing saved flavors @@ -932,8 +992,11 @@ class ImageService(BaseService): marker = urllib.parse_qs(parsed.query)['marker'][0] response = client.list_images(params={"marker": marker}) images.extend(response['images']) + if self.prefix: images = self._filter_by_prefix(images) + elif self.is_resource_list: + images = self._filter_by_resource_list(images, 'images') else: if not self.is_save_state: images = self._filter_out_ids_from_saved(images, 'images') @@ -974,6 +1037,8 @@ class UserService(BaseService): users = self.client.list_users()['users'] if self.prefix: users = self._filter_by_prefix(users) + elif self.is_resource_list: + users = self._filter_by_resource_list(users, 'users') else: if not self.is_save_state: users = self._filter_out_ids_from_saved(users, 'users') @@ -1015,8 +1080,11 @@ class RoleService(BaseService): def list(self): try: roles = self.client.list_roles()['roles'] + if self.prefix: roles = self._filter_by_prefix(roles) + elif self.is_resource_list: + roles = self._filter_by_resource_list(roles, 'roles') elif not self.is_save_state: # reconcile roles with saved state and never list admin role roles = self._filter_out_ids_from_saved(roles, 'roles') @@ -1056,8 +1124,11 @@ class ProjectService(BaseService): def list(self): projects = self.client.list_projects()['projects'] + if self.prefix: projects = self._filter_by_prefix(projects) + elif self.is_resource_list: + projects = self._filter_by_resource_list(projects, 'projects') else: if not self.is_save_state: projects = self._filter_out_ids_from_saved( @@ -1099,8 +1170,11 @@ class DomainService(BaseService): def list(self): client = self.client domains = client.list_domains()['domains'] + if self.prefix: domains = self._filter_by_prefix(domains) + elif self.is_resource_list: + domains = self._filter_by_resource_list(domains, 'domains') elif not self.is_save_state: domains = self._filter_out_ids_from_saved(domains, 'domains') LOG.debug("List count, %s Domains after reconcile", len(domains)) diff --git a/tempest/config.py b/tempest/config.py index 0a084eaf1f..507d1161db 100644 --- a/tempest/config.py +++ b/tempest/config.py @@ -1317,6 +1317,15 @@ or "to cleanup only the resources that match the prefix. " "Make sure this prefix does not match with the resource " "name you do not want Tempest cleanup CLI to delete."), + cfg.BoolOpt('record_resources', + default=False, + help="Allows to record all resources created by Tempest. " + "These resources are stored in file resource_list.json, " + "which can be later used for resource deletion by " + "command tempest cleanup. The resource_list.json file " + "will be appended in case of multiple Tempest runs, " + "so the file will contain a list of resources created " + "during all Tempest runs."), ] _opts = [ diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py index 6cf5b734cb..4f9e9ba663 100644 --- a/tempest/lib/common/rest_client.py +++ b/tempest/lib/common/rest_client.py @@ -21,6 +21,7 @@ import time import urllib import urllib3 +from fasteners import process_lock import jsonschema from oslo_log import log as logging from oslo_log import versionutils @@ -78,6 +79,17 @@ class RestClient(object): # The version of the API this client implements api_version = None + # Directory for storing read-write lock + lock_dir = None + + # An interprocess lock used when the recording of all resources created by + # Tempest is allowed. + rec_rw_lock = None + + # Variable mirrors value in config option 'record_resources' that allows + # the recording of all resources created by Tempest. + record_resources = False + LOG = logging.getLogger(__name__) def __init__(self, auth_provider, service, region, @@ -297,7 +309,13 @@ class RestClient(object): and the second the response body :rtype: tuple """ - return self.request('POST', url, extra_headers, headers, body, chunked) + resp_header, resp_body = self.request( + 'POST', url, extra_headers, headers, body, chunked) + + if self.record_resources: + self.resource_record(resp_body) + + return resp_header, resp_body def get(self, url, headers=None, extra_headers=False, chunked=False): """Send a HTTP GET request using keystone service catalog and auth @@ -1006,6 +1024,66 @@ class RestClient(object): """Returns the primary type of resource this client works with.""" return 'resource' + def resource_update(self, data, res_type, res_dict): + """Updates resource_list.json file with current resource.""" + if not isinstance(res_dict, dict): + return + + if not res_type.endswith('s'): + res_type += 's' + + if res_type not in data: + data[res_type] = {} + + if 'uuid' in res_dict: + data[res_type].update( + {res_dict.get('uuid'): res_dict.get('name')}) + elif 'id' in res_dict: + data[res_type].update( + {res_dict.get('id'): res_dict.get('name')}) + elif 'name' in res_dict: + data[res_type].update({res_dict.get('name'): ""}) + + self.rec_rw_lock.acquire_write_lock() + with open("resource_list.json", 'w+') as f: + f.write(json.dumps(data, indent=2, separators=(',', ': '))) + self.rec_rw_lock.release_write_lock() + + def resource_record(self, resp_dict): + """Records resources into resource_list.json file.""" + if self.rec_rw_lock is None: + path = self.lock_dir + self.rec_rw_lock = ( + process_lock.InterProcessReaderWriterLock(path) + ) + + self.rec_rw_lock.acquire_read_lock() + try: + with open('resource_list.json', 'rb') as f: + data = json.load(f) + except IOError: + data = {} + self.rec_rw_lock.release_read_lock() + + try: + resp_dict = json.loads(resp_dict.decode('utf-8')) + except (AttributeError, TypeError, ValueError): + return + + # check if response has any keys + if not resp_dict.keys(): + return + + resource_type = list(resp_dict.keys())[0] + + resource_dict = resp_dict[resource_type] + + if isinstance(resource_dict, list): + for resource in resource_dict: + self.resource_update(data, resource_type, resource) + else: + self.resource_update(data, resource_type, resource_dict) + @classmethod def validate_response(cls, schema, resp, body): # Only check the response if the status code is a success code diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py index 98956538e3..5c1e623a17 100644 --- a/tempest/lib/services/compute/server_groups_client.py +++ b/tempest/lib/services/compute/server_groups_client.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +from urllib import parse as urllib + from oslo_serialization import jsonutils as json from tempest.lib.api_schema.response.compute.v2_1 import server_groups \ @@ -55,9 +57,14 @@ class ServerGroupsClient(base_compute_client.BaseComputeClient): self.validate_response(schema.delete_server_group, resp, body) return rest_client.ResponseBody(resp, body) - def list_server_groups(self): + def list_server_groups(self, **params): """List the server-groups.""" - resp, body = self.get("os-server-groups") + + url = 'os-server-groups' + if params: + url += '?%s' % urllib.urlencode(params) + + resp, body = self.get(url) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.list_server_groups, resp, body) diff --git a/tempest/tests/cmd/test_cleanup.py b/tempest/tests/cmd/test_cleanup.py index 69e735b55b..3efc9bd8fe 100644 --- a/tempest/tests/cmd/test_cleanup.py +++ b/tempest/tests/cmd/test_cleanup.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json from unittest import mock from tempest.cmd import cleanup @@ -20,12 +21,30 @@ from tempest.tests import base class TestTempestCleanup(base.TestCase): - def test_load_json(self): + def test_load_json_saved_state(self): # instantiate "empty" TempestCleanup c = cleanup.TempestCleanup(None, None, 'test') test_saved_json = 'tempest/tests/cmd/test_saved_state_json.json' + with open(test_saved_json, 'r') as f: + test_saved_json_content = json.load(f) # test if the file is loaded without any issues/exceptions - c._load_json(test_saved_json) + c.options = mock.Mock() + c.options.init_saved_state = True + c._load_saved_state(test_saved_json) + self.assertEqual(c.json_data, test_saved_json_content) + + def test_load_json_resource_list(self): + # instantiate "empty" TempestCleanup + c = cleanup.TempestCleanup(None, None, 'test') + test_resource_list = 'tempest/tests/cmd/test_resource_list.json' + with open(test_resource_list, 'r') as f: + test_resource_list_content = json.load(f) + # test if the file is loaded without any issues/exceptions + c.options = mock.Mock() + c.options.init_saved_state = False + c.options.resource_list = True + c._load_resource_list(test_resource_list) + self.assertEqual(c.resource_data, test_resource_list_content) @mock.patch('tempest.cmd.cleanup.TempestCleanup.init') @mock.patch('tempest.cmd.cleanup.TempestCleanup._cleanup') diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py index 6b3b4b7a59..2557145311 100644 --- a/tempest/tests/cmd/test_cleanup_services.py +++ b/tempest/tests/cmd/test_cleanup_services.py @@ -41,8 +41,10 @@ class TestBaseService(base.TestCase): def test_base_service_init(self): kwargs = {'data': {'data': 'test'}, 'is_dry_run': False, + 'resource_list_json': {'resp': 'data'}, 'saved_state_json': {'saved': 'data'}, 'is_preserve': False, + 'is_resource_list': False, 'is_save_state': True, 'prefix': 'tempest', 'tenant_id': 'project_id', @@ -50,8 +52,10 @@ class TestBaseService(base.TestCase): base = cleanup_service.BaseService(kwargs) self.assertEqual(base.data, kwargs['data']) self.assertFalse(base.is_dry_run) + self.assertEqual(base.resource_list_json, kwargs['resource_list_json']) self.assertEqual(base.saved_state_json, kwargs['saved_state_json']) self.assertFalse(base.is_preserve) + self.assertFalse(base.is_resource_list) self.assertTrue(base.is_save_state) self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id']) self.assertEqual(base.got_exceptions, kwargs['got_exceptions']) @@ -60,8 +64,10 @@ class TestBaseService(base.TestCase): def test_not_implemented_ex(self): kwargs = {'data': {'data': 'test'}, 'is_dry_run': False, + 'resource_list_json': {'resp': 'data'}, 'saved_state_json': {'saved': 'data'}, 'is_preserve': False, + 'is_resource_list': False, 'is_save_state': False, 'prefix': 'tempest', 'tenant_id': 'project_id', @@ -181,10 +187,20 @@ class BaseCmdServiceTests(MockFunctionsBase): "subnetpools": {'8acf64c1-43fc': 'saved-subnet-pool'}, "regions": {'RegionOne': {}} } + + resource_list = { + "keypairs": {'saved-key-pair': ""} + } + # Mocked methods get_method = 'tempest.lib.common.rest_client.RestClient.get' delete_method = 'tempest.lib.common.rest_client.RestClient.delete' log_method = 'tempest.cmd.cleanup_service.LOG.exception' + filter_saved_state = 'tempest.cmd.cleanup_service.' \ + 'BaseService._filter_out_ids_from_saved' + filter_resource_list = 'tempest.cmd.cleanup_service.' \ + 'BaseService._filter_by_resource_list' + filter_prefix = 'tempest.cmd.cleanup_service.BaseService._filter_by_prefix' # Override parameters service_class = 'BaseService' response = None @@ -192,17 +208,19 @@ class BaseCmdServiceTests(MockFunctionsBase): def _create_cmd_service(self, service_type, is_save_state=False, is_preserve=False, is_dry_run=False, - prefix=''): + prefix='', is_resource_list=False): creds = fake_credentials.FakeKeystoneV3Credentials() os = clients.Manager(creds) return getattr(cleanup_service, service_type)( os, + is_resource_list=is_resource_list, is_save_state=is_save_state, is_preserve=is_preserve, is_dry_run=is_dry_run, prefix=prefix, project_id='b8e3ece07bb049138d224436756e3b57', data={}, + resource_list_json=self.resource_list, saved_state_json=self.saved_state ) @@ -266,6 +284,38 @@ class BaseCmdServiceTests(MockFunctionsBase): self.assertNotIn(rsp['id'], self.conf_values.values()) self.assertNotIn(rsp['name'], self.conf_values.values()) + def _test_prefix_opt_precedence(self, delete_mock): + serv = self._create_cmd_service( + self.service_class, is_resource_list=True, prefix='tempest') + _, fixtures = self.run_function_with_mocks( + serv.run, + delete_mock + ) + + # Check that prefix was used for filtering + fixtures[2].mock.assert_called_once() + + # Check that neither saved_state.json nor resource list was + # used for filtering + fixtures[0].mock.assert_not_called() + fixtures[1].mock.assert_not_called() + + def _test_resource_list_opt_precedence(self, delete_mock): + serv = self._create_cmd_service( + self.service_class, is_resource_list=True) + _, fixtures = self.run_function_with_mocks( + serv.run, + delete_mock + ) + + # Check that resource list was used for filtering + fixtures[1].mock.assert_called_once() + + # Check that neither saved_state.json nor prefix was + # used for filtering + fixtures[0].mock.assert_not_called() + fixtures[2].mock.assert_not_called() + class TestSnapshotService(BaseCmdServiceTests): @@ -320,6 +370,24 @@ class TestSnapshotService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestServerService(BaseCmdServiceTests): @@ -378,6 +446,24 @@ class TestServerService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestServerGroupService(BaseCmdServiceTests): @@ -429,6 +515,26 @@ class TestServerGroupService(BaseCmdServiceTests): (self.validate_response, 'validate', None) ]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.validate_response, 'validate', None), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.validate_response, 'validate', None), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestKeyPairService(BaseCmdServiceTests): @@ -493,6 +599,33 @@ class TestKeyPairService(BaseCmdServiceTests): (self.validate_response, 'validate', None) ]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.validate_response, 'validate', None), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.validate_response, 'validate', None), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + serv = self._create_cmd_service( + self.service_class, is_resource_list=True) + + _, fixtures = self.run_function_with_mocks( + serv.delete, + delete_mock + ) + + # Check that prefix was not used for filtering + fixtures[0].mock.assert_not_called() + class TestVolumeService(BaseCmdServiceTests): @@ -542,6 +675,24 @@ class TestVolumeService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestVolumeQuotaService(BaseCmdServiceTests): @@ -761,6 +912,24 @@ class TestNetworkService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkFloatingIpService(BaseCmdServiceTests): @@ -823,6 +992,34 @@ class TestNetworkFloatingIpService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + serv = self._create_cmd_service( + self.service_class, is_resource_list=True, prefix='tempest') + _, fixtures = self.run_function_with_mocks( + serv.run, + delete_mock + ) + + # cleanup returns [] + fixtures[0].mock.assert_not_called() + fixtures[1].mock.assert_not_called() + fixtures[2].mock.assert_not_called() + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkRouterService(BaseCmdServiceTests): @@ -937,6 +1134,24 @@ class TestNetworkRouterService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkMeteringLabelRuleService(BaseCmdServiceTests): @@ -978,6 +1193,34 @@ class TestNetworkMeteringLabelRuleService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + serv = self._create_cmd_service( + self.service_class, is_resource_list=True, prefix='tempest') + _, fixtures = self.run_function_with_mocks( + serv.run, + delete_mock + ) + + # cleanup returns [] + fixtures[0].mock.assert_not_called() + fixtures[1].mock.assert_not_called() + fixtures[2].mock.assert_not_called() + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkMeteringLabelService(BaseCmdServiceTests): @@ -1020,6 +1263,24 @@ class TestNetworkMeteringLabelService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkPortService(BaseCmdServiceTests): @@ -1118,6 +1379,24 @@ class TestNetworkPortService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkSecGroupService(BaseCmdServiceTests): @@ -1196,6 +1475,24 @@ class TestNetworkSecGroupService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkSubnetService(BaseCmdServiceTests): @@ -1272,6 +1569,24 @@ class TestNetworkSubnetService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestNetworkSubnetPoolsService(BaseCmdServiceTests): @@ -1340,6 +1655,24 @@ class TestNetworkSubnetPoolsService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + # begin global services class TestRegionService(BaseCmdServiceTests): @@ -1392,6 +1725,34 @@ class TestRegionService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + serv = self._create_cmd_service( + self.service_class, is_resource_list=True, prefix='tempest') + _, fixtures = self.run_function_with_mocks( + serv.run, + delete_mock + ) + + # cleanup returns [] + fixtures[0].mock.assert_not_called() + fixtures[1].mock.assert_not_called() + fixtures[2].mock.assert_not_called() + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestDomainService(BaseCmdServiceTests): @@ -1445,6 +1806,26 @@ class TestDomainService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None), + (self.mock_update, 'update', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None), + (self.mock_update, 'update', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestProjectsService(BaseCmdServiceTests): @@ -1518,6 +1899,24 @@ class TestProjectsService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestImagesService(BaseCmdServiceTests): @@ -1597,6 +1996,24 @@ class TestImagesService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestFlavorService(BaseCmdServiceTests): @@ -1670,6 +2087,24 @@ class TestFlavorService(BaseCmdServiceTests): }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestRoleService(BaseCmdServiceTests): @@ -1716,6 +2151,24 @@ class TestRoleService(BaseCmdServiceTests): def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) + class TestUserService(BaseCmdServiceTests): @@ -1782,3 +2235,21 @@ class TestUserService(BaseCmdServiceTests): "password_expires_at": "1893-11-06T15:32:17.000000", }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) + + def test_prefix_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_prefix_opt_precedence(delete_mock) + + def test_resource_list_opt_precedence(self): + delete_mock = [(self.filter_saved_state, [], None), + (self.filter_resource_list, [], None), + (self.filter_prefix, [], None), + (self.get_method, self.response, 200), + (self.delete_method, 'error', None), + (self.log_method, 'exception', None)] + self._test_resource_list_opt_precedence(delete_mock) diff --git a/tempest/tests/cmd/test_resource_list.json b/tempest/tests/cmd/test_resource_list.json new file mode 100644 index 0000000000..dfbc7900f6 --- /dev/null +++ b/tempest/tests/cmd/test_resource_list.json @@ -0,0 +1,11 @@ +{ + "project": { + "ce4e7edf051c439d8b81c4bfe581c5ef": "test" + }, + "keypairs": { + "tempest-keypair-1215039183": "" + }, + "users": { + "74463c83f9d640fe84c4376527ceff26": "test" + } +} diff --git a/tempest/tests/lib/common/test_rest_client.py b/tempest/tests/lib/common/test_rest_client.py index 81a76e02c2..9bc6f60717 100644 --- a/tempest/tests/lib/common/test_rest_client.py +++ b/tempest/tests/lib/common/test_rest_client.py @@ -13,6 +13,7 @@ # under the License. import copy +from unittest import mock import fixtures import jsonschema @@ -749,6 +750,110 @@ class TestExpectedSuccess(BaseRestClientTestClass): expected_code, read_code) +class TestRecordResources(BaseRestClientTestClass): + + def setUp(self): + self.fake_http = fake_http.fake_httplib2() + super(TestRecordResources, self).setUp() + + def _cleanup_test_resource_record(self): + # clear resource_list.json file + with open('resource_list.json', 'w') as f: + f.write('{}') + + def test_post_record_resources(self): + self.rest_client.record_resources = True + __, return_dict = self.rest_client.post(self.url, {}, {}) + self.assertEqual({}, return_dict['headers']) + self.assertEqual({}, return_dict['body']) + + def test_resource_record_no_top_key(self): + test_body_no_key = b'{}' + self.rest_client.resource_record(test_body_no_key) + + def test_resource_record_dict(self): + test_dict_body = b'{"project": {"id": "test-id", "name": ""}}\n' + self.rest_client.resource_record(test_dict_body) + + with open('resource_list.json', 'r') as f: + content = f.read() + resource_list_content = json.loads(content) + + test_resource_list = { + "projects": {"test-id": ""} + } + self.assertEqual(resource_list_content, test_resource_list) + + # cleanup + self._cleanup_test_resource_record() + + def test_resource_record_list(self): + test_list_body = '''{ + "user": [ + { + "id": "test-uuid", + "name": "test-name" + }, + { + "id": "test-uuid2", + "name": "test-name2" + } + ] + }''' + test_list_body = test_list_body.encode('utf-8') + self.rest_client.resource_record(test_list_body) + + with open('resource_list.json', 'r') as f: + content = f.read() + resource_list_content = json.loads(content) + + test_resource_list = { + "users": { + "test-uuid": "test-name", + "test-uuid2": "test-name2" + } + } + self.assertEqual(resource_list_content, test_resource_list) + + # cleanup + self._cleanup_test_resource_record() + + def test_resource_update_id(self): + data = {} + res_dict = {'id': 'test-uuid', 'name': 'test-name'} + + self.rest_client.rec_rw_lock = mock.MagicMock() + self.rest_client.resource_update(data, 'user', res_dict) + result = {'users': {'test-uuid': 'test-name'}} + self.assertEqual(data, result) + + def test_resource_update_name(self): + data = {'keypairs': {}} + res_dict = {'name': 'test-keypair'} + + self.rest_client.rec_rw_lock = mock.MagicMock() + self.rest_client.resource_update(data, 'keypair', res_dict) + result = {'keypairs': {'test-keypair': ""}} + self.assertEqual(data, result) + + def test_resource_update_no_id(self): + data = {} + res_dict = {'type': 'test', 'description': 'example'} + + self.rest_client.rec_rw_lock = mock.MagicMock() + self.rest_client.resource_update(data, 'projects', res_dict) + result = {'projects': {}} + self.assertEqual(data, result) + + def test_resource_update_not_dict(self): + data = {} + res_dict = 'test-string' + + self.rest_client.rec_rw_lock = mock.MagicMock() + self.rest_client.resource_update(data, 'user', res_dict) + self.assertEqual(data, {}) + + class TestResponseBody(base.TestCase): def test_str(self):