Merge "Implement purge list for tempest cleanup"

This commit is contained in:
Zuul 2024-05-21 06:13:54 +00:00 committed by Gerrit Code Review
commit e838ec9fa8
11 changed files with 890 additions and 26 deletions

View File

@ -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``.

View File

@ -13,8 +13,13 @@
# 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 os
from oslo_concurrency import lockutils
from tempest import config from tempest import config
from tempest.lib import auth from tempest.lib import auth
from tempest.lib.common.rest_client import RestClient
from tempest.lib import exceptions as lib_exc from tempest.lib import exceptions as lib_exc
from tempest.lib.services import clients from tempest.lib.services import clients
@ -35,6 +40,11 @@ class Manager(clients.ServiceClients):
super(Manager, self).__init__( super(Manager, self).__init__(
credentials=credentials, identity_uri=identity_uri, scope=scope, credentials=credentials, identity_uri=identity_uri, scope=scope,
region=CONF.identity.region) 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 # TODO(andreaf) When clients are initialised without the right
# parameters available, the calls below will trigger a KeyError. # parameters available, the calls below will trigger a KeyError.
# We should catch that and raise a better error. # We should catch that and raise a better error.

View File

@ -87,6 +87,23 @@ Runtime Arguments
``saved_state.json`` file will be ignored and cleanup will be done based on ``saved_state.json`` file will be ignored and cleanup will be done based on
the passed prefix only. 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. * ``--help``: Print the help text for the command and parameters.
.. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the .. [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" SAVED_STATE_JSON = "saved_state.json"
DRY_RUN_JSON = "dry_run.json" DRY_RUN_JSON = "dry_run.json"
RESOURCE_LIST_JSON = "resource_list.json"
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = config.CONF CONF = config.CONF
@ -164,6 +182,7 @@ class TempestCleanup(command.Command):
self.admin_mgr = clients.Manager( self.admin_mgr = clients.Manager(
credentials.get_configured_admin_credentials()) credentials.get_configured_admin_credentials())
self.dry_run_data = {} self.dry_run_data = {}
self.resource_data = {}
self.json_data = {} self.json_data = {}
# available services # available services
@ -177,12 +196,20 @@ class TempestCleanup(command.Command):
self._init_state() self._init_state()
return 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): def _cleanup(self):
LOG.info("Begin cleanup") LOG.info("Begin cleanup")
is_dry_run = self.options.dry_run is_dry_run = self.options.dry_run
is_preserve = not self.options.delete_tempest_conf_objects is_preserve = not self.options.delete_tempest_conf_objects
is_resource_list = self.options.resource_list
is_save_state = False is_save_state = False
cleanup_prefix = self.options.prefix cleanup_prefix = self.options.prefix
@ -194,8 +221,10 @@ class TempestCleanup(command.Command):
# they are in saved state json. Therefore is_preserve is False # they are in saved state json. Therefore is_preserve is False
kwargs = {'data': self.dry_run_data, kwargs = {'data': self.dry_run_data,
'is_dry_run': is_dry_run, 'is_dry_run': is_dry_run,
'resource_list_json': self.resource_data,
'saved_state_json': self.json_data, 'saved_state_json': self.json_data,
'is_preserve': False, 'is_preserve': False,
'is_resource_list': is_resource_list,
'is_save_state': is_save_state, 'is_save_state': is_save_state,
'prefix': cleanup_prefix} 'prefix': cleanup_prefix}
project_service = cleanup_service.ProjectService(admin_mgr, **kwargs) project_service = cleanup_service.ProjectService(admin_mgr, **kwargs)
@ -208,8 +237,10 @@ class TempestCleanup(command.Command):
kwargs = {'data': self.dry_run_data, kwargs = {'data': self.dry_run_data,
'is_dry_run': is_dry_run, 'is_dry_run': is_dry_run,
'resource_list_json': self.resource_data,
'saved_state_json': self.json_data, 'saved_state_json': self.json_data,
'is_preserve': is_preserve, 'is_preserve': is_preserve,
'is_resource_list': is_resource_list,
'is_save_state': is_save_state, 'is_save_state': is_save_state,
'prefix': cleanup_prefix, 'prefix': cleanup_prefix,
'got_exceptions': self.GOT_EXCEPTIONS} 'got_exceptions': self.GOT_EXCEPTIONS}
@ -228,11 +259,17 @@ class TempestCleanup(command.Command):
f.write(json.dumps(self.dry_run_data, sort_keys=True, f.write(json.dumps(self.dry_run_data, sort_keys=True,
indent=2, separators=(',', ': '))) 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): def _clean_project(self, project):
LOG.debug("Cleaning project: %s ", project['name']) LOG.debug("Cleaning project: %s ", project['name'])
is_dry_run = self.options.dry_run is_dry_run = self.options.dry_run
dry_run_data = self.dry_run_data dry_run_data = self.dry_run_data
is_preserve = not self.options.delete_tempest_conf_objects is_preserve = not self.options.delete_tempest_conf_objects
is_resource_list = self.options.resource_list
project_id = project['id'] project_id = project['id']
project_name = project['name'] project_name = project['name']
project_data = None project_data = None
@ -244,7 +281,9 @@ class TempestCleanup(command.Command):
kwargs = {'data': project_data, kwargs = {'data': project_data,
'is_dry_run': is_dry_run, 'is_dry_run': is_dry_run,
'saved_state_json': self.json_data, 'saved_state_json': self.json_data,
'resource_list_json': self.resource_data,
'is_preserve': is_preserve, 'is_preserve': is_preserve,
'is_resource_list': is_resource_list,
'is_save_state': False, 'is_save_state': False,
'project_id': project_id, 'project_id': project_id,
'prefix': cleanup_prefix, 'prefix': cleanup_prefix,
@ -287,6 +326,19 @@ class TempestCleanup(command.Command):
"ignored when --init-saved-state is used so that " "ignored when --init-saved-state is used so that "
"it can capture the true init state - all " "it can capture the true init state - all "
"resources present at that moment.") "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 return parser
def get_description(self): def get_description(self):
@ -304,6 +356,7 @@ class TempestCleanup(command.Command):
'is_dry_run': False, 'is_dry_run': False,
'saved_state_json': data, 'saved_state_json': data,
'is_preserve': False, 'is_preserve': False,
'is_resource_list': False,
'is_save_state': True, 'is_save_state': True,
# must be None as we want to capture true init state # must be None as we want to capture true init state
# (all resources present) thus no filtering based # (all resources present) thus no filtering based
@ -326,15 +379,31 @@ class TempestCleanup(command.Command):
f.write(json.dumps(data, sort_keys=True, f.write(json.dumps(data, sort_keys=True,
indent=2, separators=(',', ': '))) 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: try:
with open(saved_state_json, 'rb') as json_file: with open(saved_state_json, 'rb') as json_file:
self.json_data = json.load(json_file) self.json_data = json.load(json_file)
except IOError as ex: except IOError as ex:
LOG.exception("Failed loading saved state, please be sure you" LOG.exception(
" have first run cleanup with --init-saved-state " "Failed loading saved state, please be sure you"
"flag prior to running tempest. Exception: %s", ex) " have first run cleanup with --init-saved-state "
"flag prior to running tempest. Exception: %s", ex)
sys.exit(ex) sys.exit(ex)
except Exception as ex: except Exception as ex:
LOG.exception("Exception parsing saved state json : %s", ex) LOG.exception("Exception parsing saved state json : %s", ex)

View File

@ -120,6 +120,13 @@ class BaseService(object):
if item['name'].startswith(self.prefix)] if item['name'].startswith(self.prefix)]
return items 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): def _filter_out_ids_from_saved(self, item_list, attr):
items = [item for item in item_list if item['id'] items = [item for item in item_list if item['id']
not in self.saved_state_json[attr].keys()] not in self.saved_state_json[attr].keys()]
@ -166,8 +173,11 @@ class SnapshotService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
snaps = client.list_snapshots()['snapshots'] snaps = client.list_snapshots()['snapshots']
if self.prefix: if self.prefix:
snaps = self._filter_by_prefix(snaps) 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: elif not self.is_save_state:
# recreate list removing saved snapshots # recreate list removing saved snapshots
snaps = self._filter_out_ids_from_saved(snaps, 'snapshots') snaps = self._filter_out_ids_from_saved(snaps, 'snapshots')
@ -205,8 +215,11 @@ class ServerService(BaseService):
client = self.client client = self.client
servers_body = client.list_servers() servers_body = client.list_servers()
servers = servers_body['servers'] servers = servers_body['servers']
if self.prefix: if self.prefix:
servers = self._filter_by_prefix(servers) 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: elif not self.is_save_state:
# recreate list removing saved servers # recreate list removing saved servers
servers = self._filter_out_ids_from_saved(servers, 'servers') servers = self._filter_out_ids_from_saved(servers, 'servers')
@ -238,9 +251,12 @@ class ServerGroupService(ServerService):
def list(self): def list(self):
client = self.server_groups_client 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: if self.prefix:
sgs = self._filter_by_prefix(sgs) 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: elif not self.is_save_state:
# recreate list removing saved server_groups # recreate list removing saved server_groups
sgs = self._filter_out_ids_from_saved(sgs, 'server_groups') sgs = self._filter_out_ids_from_saved(sgs, 'server_groups')
@ -276,8 +292,13 @@ class KeyPairService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
keypairs = client.list_keypairs()['keypairs'] keypairs = client.list_keypairs()['keypairs']
if self.prefix: if self.prefix:
keypairs = self._filter_by_prefix(keypairs) 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: elif not self.is_save_state:
# recreate list removing saved keypairs # recreate list removing saved keypairs
keypairs = [keypair for keypair in keypairs keypairs = [keypair for keypair in keypairs
@ -317,8 +338,11 @@ class VolumeService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
vols = client.list_volumes()['volumes'] vols = client.list_volumes()['volumes']
if self.prefix: if self.prefix:
vols = self._filter_by_prefix(vols) 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: elif not self.is_save_state:
# recreate list removing saved volumes # recreate list removing saved volumes
vols = self._filter_out_ids_from_saved(vols, 'volumes') vols = self._filter_out_ids_from_saved(vols, 'volumes')
@ -462,8 +486,11 @@ class NetworkService(BaseNetworkService):
client = self.networks_client client = self.networks_client
networks = client.list_networks(**self.tenant_filter) networks = client.list_networks(**self.tenant_filter)
networks = networks['networks'] networks = networks['networks']
if self.prefix: if self.prefix:
networks = self._filter_by_prefix(networks) networks = self._filter_by_prefix(networks)
elif self.is_resource_list:
networks = self._filter_by_resource_list(networks, 'networks')
else: else:
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved networks # recreate list removing saved networks
@ -500,15 +527,17 @@ class NetworkService(BaseNetworkService):
class NetworkFloatingIpService(BaseNetworkService): class NetworkFloatingIpService(BaseNetworkService):
def list(self): 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 client = self.floating_ips_client
flips = client.list_floatingips(**self.tenant_filter) flips = client.list_floatingips(**self.tenant_filter)
flips = flips['floatingips'] 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 # recreate list removing saved flips
flips = self._filter_out_ids_from_saved(flips, 'floatingips') flips = self._filter_out_ids_from_saved(flips, 'floatingips')
LOG.debug("List count, %s Network Floating IPs", len(flips)) LOG.debug("List count, %s Network Floating IPs", len(flips))
@ -543,8 +572,11 @@ class NetworkRouterService(BaseNetworkService):
client = self.routers_client client = self.routers_client
routers = client.list_routers(**self.tenant_filter) routers = client.list_routers(**self.tenant_filter)
routers = routers['routers'] routers = routers['routers']
if self.prefix: if self.prefix:
routers = self._filter_by_prefix(routers) routers = self._filter_by_prefix(routers)
elif self.is_resource_list:
routers = self._filter_by_resource_list(routers, 'routers')
else: else:
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved routers # recreate list removing saved routers
@ -592,16 +624,19 @@ class NetworkRouterService(BaseNetworkService):
class NetworkMeteringLabelRuleService(NetworkService): class NetworkMeteringLabelRuleService(NetworkService):
def list(self): 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 client = self.metering_label_rules_client
rules = client.list_metering_label_rules() rules = client.list_metering_label_rules()
rules = rules['metering_label_rules'] rules = rules['metering_label_rules']
rules = self._filter_by_tenant_id(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 = self._filter_out_ids_from_saved(
rules, 'metering_label_rules') rules, 'metering_label_rules')
# recreate list removing saved rules # recreate list removing saved rules
@ -638,8 +673,12 @@ class NetworkMeteringLabelService(BaseNetworkService):
labels = client.list_metering_labels() labels = client.list_metering_labels()
labels = labels['metering_labels'] labels = labels['metering_labels']
labels = self._filter_by_tenant_id(labels) labels = self._filter_by_tenant_id(labels)
if self.prefix: if self.prefix:
labels = self._filter_by_prefix(labels) 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: elif not self.is_save_state:
# recreate list removing saved labels # recreate list removing saved labels
labels = self._filter_out_ids_from_saved( labels = self._filter_out_ids_from_saved(
@ -677,8 +716,11 @@ class NetworkPortService(BaseNetworkService):
client.list_ports(**self.tenant_filter)['ports'] client.list_ports(**self.tenant_filter)['ports']
if port["device_owner"] == "" or if port["device_owner"] == "" or
port["device_owner"].startswith("compute:")] port["device_owner"].startswith("compute:")]
if self.prefix: if self.prefix:
ports = self._filter_by_prefix(ports) ports = self._filter_by_prefix(ports)
elif self.is_resource_list:
ports = self._filter_by_resource_list(ports, 'ports')
else: else:
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved ports # recreate list removing saved ports
@ -717,8 +759,12 @@ class NetworkSecGroupService(BaseNetworkService):
secgroups = [secgroup for secgroup in secgroups = [secgroup for secgroup in
client.list_security_groups(**filter)['security_groups'] client.list_security_groups(**filter)['security_groups']
if secgroup['name'] != 'default'] if secgroup['name'] != 'default']
if self.prefix: if self.prefix:
secgroups = self._filter_by_prefix(secgroups) secgroups = self._filter_by_prefix(secgroups)
elif self.is_resource_list:
secgroups = self._filter_by_resource_list(
secgroups, 'security_groups')
else: else:
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved security_groups # recreate list removing saved security_groups
@ -760,8 +806,11 @@ class NetworkSubnetService(BaseNetworkService):
client = self.subnets_client client = self.subnets_client
subnets = client.list_subnets(**self.tenant_filter) subnets = client.list_subnets(**self.tenant_filter)
subnets = subnets['subnets'] subnets = subnets['subnets']
if self.prefix: if self.prefix:
subnets = self._filter_by_prefix(subnets) subnets = self._filter_by_prefix(subnets)
elif self.is_resource_list:
subnets = self._filter_by_resource_list(subnets, 'subnets')
else: else:
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved subnets # recreate list removing saved subnets
@ -797,8 +846,11 @@ class NetworkSubnetPoolsService(BaseNetworkService):
def list(self): def list(self):
client = self.subnetpools_client client = self.subnetpools_client
pools = client.list_subnetpools(**self.tenant_filter)['subnetpools'] pools = client.list_subnetpools(**self.tenant_filter)['subnetpools']
if self.prefix: if self.prefix:
pools = self._filter_by_prefix(pools) pools = self._filter_by_prefix(pools)
elif self.is_resource_list:
pools = self._filter_by_resource_list(pools, 'subnetpools')
else: else:
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved subnet pools # recreate list removing saved subnet pools
@ -838,13 +890,18 @@ class RegionService(BaseService):
self.client = manager.regions_client self.client = manager.regions_client
def list(self): def list(self):
client = self.client
regions = client.list_regions()
if self.prefix: if self.prefix:
# this means we're cleaning resources based on a certain prefix, # this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore return empty list # this resource doesn't have a name, therefore return empty list
return [] return []
client = self.client elif self.is_resource_list:
regions = client.list_regions() regions = self._filter_by_resource_list(
if not self.is_save_state: regions['regions'], 'regions')
return regions
elif not self.is_save_state:
regions = self._filter_out_ids_from_saved( regions = self._filter_out_ids_from_saved(
regions['regions'], 'regions') regions['regions'], 'regions')
LOG.debug("List count, %s Regions", len(regions)) LOG.debug("List count, %s Regions", len(regions))
@ -884,8 +941,11 @@ class FlavorService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
flavors = client.list_flavors({"is_public": None})['flavors'] flavors = client.list_flavors({"is_public": None})['flavors']
if self.prefix: if self.prefix:
flavors = self._filter_by_prefix(flavors) flavors = self._filter_by_prefix(flavors)
elif self.is_resource_list:
flavors = self._filter_by_resource_list(flavors, 'flavors')
else: else:
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved flavors # recreate list removing saved flavors
@ -932,8 +992,11 @@ class ImageService(BaseService):
marker = urllib.parse_qs(parsed.query)['marker'][0] marker = urllib.parse_qs(parsed.query)['marker'][0]
response = client.list_images(params={"marker": marker}) response = client.list_images(params={"marker": marker})
images.extend(response['images']) images.extend(response['images'])
if self.prefix: if self.prefix:
images = self._filter_by_prefix(images) images = self._filter_by_prefix(images)
elif self.is_resource_list:
images = self._filter_by_resource_list(images, 'images')
else: else:
if not self.is_save_state: if not self.is_save_state:
images = self._filter_out_ids_from_saved(images, 'images') images = self._filter_out_ids_from_saved(images, 'images')
@ -974,6 +1037,8 @@ class UserService(BaseService):
users = self.client.list_users()['users'] users = self.client.list_users()['users']
if self.prefix: if self.prefix:
users = self._filter_by_prefix(users) users = self._filter_by_prefix(users)
elif self.is_resource_list:
users = self._filter_by_resource_list(users, 'users')
else: else:
if not self.is_save_state: if not self.is_save_state:
users = self._filter_out_ids_from_saved(users, 'users') users = self._filter_out_ids_from_saved(users, 'users')
@ -1015,8 +1080,11 @@ class RoleService(BaseService):
def list(self): def list(self):
try: try:
roles = self.client.list_roles()['roles'] roles = self.client.list_roles()['roles']
if self.prefix: if self.prefix:
roles = self._filter_by_prefix(roles) 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: elif not self.is_save_state:
# reconcile roles with saved state and never list admin role # reconcile roles with saved state and never list admin role
roles = self._filter_out_ids_from_saved(roles, 'roles') roles = self._filter_out_ids_from_saved(roles, 'roles')
@ -1056,8 +1124,11 @@ class ProjectService(BaseService):
def list(self): def list(self):
projects = self.client.list_projects()['projects'] projects = self.client.list_projects()['projects']
if self.prefix: if self.prefix:
projects = self._filter_by_prefix(projects) projects = self._filter_by_prefix(projects)
elif self.is_resource_list:
projects = self._filter_by_resource_list(projects, 'projects')
else: else:
if not self.is_save_state: if not self.is_save_state:
projects = self._filter_out_ids_from_saved( projects = self._filter_out_ids_from_saved(
@ -1099,8 +1170,11 @@ class DomainService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
domains = client.list_domains()['domains'] domains = client.list_domains()['domains']
if self.prefix: if self.prefix:
domains = self._filter_by_prefix(domains) 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: elif not self.is_save_state:
domains = self._filter_out_ids_from_saved(domains, 'domains') domains = self._filter_out_ids_from_saved(domains, 'domains')
LOG.debug("List count, %s Domains after reconcile", len(domains)) LOG.debug("List count, %s Domains after reconcile", len(domains))

View File

@ -1309,6 +1309,15 @@ or
"to cleanup only the resources that match the prefix. " "to cleanup only the resources that match the prefix. "
"Make sure this prefix does not match with the resource " "Make sure this prefix does not match with the resource "
"name you do not want Tempest cleanup CLI to delete."), "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 = [ _opts = [

View File

@ -21,6 +21,7 @@ import time
import urllib import urllib
import urllib3 import urllib3
from fasteners import process_lock
import jsonschema import jsonschema
from oslo_log import log as logging from oslo_log import log as logging
from oslo_log import versionutils from oslo_log import versionutils
@ -78,6 +79,17 @@ class RestClient(object):
# The version of the API this client implements # The version of the API this client implements
api_version = None 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__) LOG = logging.getLogger(__name__)
def __init__(self, auth_provider, service, region, def __init__(self, auth_provider, service, region,
@ -297,7 +309,13 @@ class RestClient(object):
and the second the response body and the second the response body
:rtype: tuple :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): def get(self, url, headers=None, extra_headers=False, chunked=False):
"""Send a HTTP GET request using keystone service catalog and auth """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.""" """Returns the primary type of resource this client works with."""
return 'resource' 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 @classmethod
def validate_response(cls, schema, resp, body): def validate_response(cls, schema, resp, body):
# Only check the response if the status code is a success code # Only check the response if the status code is a success code

View File

@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from urllib import parse as urllib
from oslo_serialization import jsonutils as json from oslo_serialization import jsonutils as json
from tempest.lib.api_schema.response.compute.v2_1 import server_groups \ 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) self.validate_response(schema.delete_server_group, resp, body)
return rest_client.ResponseBody(resp, body) return rest_client.ResponseBody(resp, body)
def list_server_groups(self): def list_server_groups(self, **params):
"""List the server-groups.""" """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) body = json.loads(body)
schema = self.get_schema(self.schema_versions_info) schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.list_server_groups, resp, body) self.validate_response(schema.list_server_groups, resp, body)

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
from unittest import mock from unittest import mock
from tempest.cmd import cleanup from tempest.cmd import cleanup
@ -20,12 +21,30 @@ from tempest.tests import base
class TestTempestCleanup(base.TestCase): class TestTempestCleanup(base.TestCase):
def test_load_json(self): def test_load_json_saved_state(self):
# instantiate "empty" TempestCleanup # instantiate "empty" TempestCleanup
c = cleanup.TempestCleanup(None, None, 'test') c = cleanup.TempestCleanup(None, None, 'test')
test_saved_json = 'tempest/tests/cmd/test_saved_state_json.json' 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 # 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.init')
@mock.patch('tempest.cmd.cleanup.TempestCleanup._cleanup') @mock.patch('tempest.cmd.cleanup.TempestCleanup._cleanup')

View File

@ -41,8 +41,10 @@ class TestBaseService(base.TestCase):
def test_base_service_init(self): def test_base_service_init(self):
kwargs = {'data': {'data': 'test'}, kwargs = {'data': {'data': 'test'},
'is_dry_run': False, 'is_dry_run': False,
'resource_list_json': {'resp': 'data'},
'saved_state_json': {'saved': 'data'}, 'saved_state_json': {'saved': 'data'},
'is_preserve': False, 'is_preserve': False,
'is_resource_list': False,
'is_save_state': True, 'is_save_state': True,
'prefix': 'tempest', 'prefix': 'tempest',
'tenant_id': 'project_id', 'tenant_id': 'project_id',
@ -50,8 +52,10 @@ class TestBaseService(base.TestCase):
base = cleanup_service.BaseService(kwargs) base = cleanup_service.BaseService(kwargs)
self.assertEqual(base.data, kwargs['data']) self.assertEqual(base.data, kwargs['data'])
self.assertFalse(base.is_dry_run) 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.assertEqual(base.saved_state_json, kwargs['saved_state_json'])
self.assertFalse(base.is_preserve) self.assertFalse(base.is_preserve)
self.assertFalse(base.is_resource_list)
self.assertTrue(base.is_save_state) self.assertTrue(base.is_save_state)
self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id']) self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id'])
self.assertEqual(base.got_exceptions, kwargs['got_exceptions']) self.assertEqual(base.got_exceptions, kwargs['got_exceptions'])
@ -60,8 +64,10 @@ class TestBaseService(base.TestCase):
def test_not_implemented_ex(self): def test_not_implemented_ex(self):
kwargs = {'data': {'data': 'test'}, kwargs = {'data': {'data': 'test'},
'is_dry_run': False, 'is_dry_run': False,
'resource_list_json': {'resp': 'data'},
'saved_state_json': {'saved': 'data'}, 'saved_state_json': {'saved': 'data'},
'is_preserve': False, 'is_preserve': False,
'is_resource_list': False,
'is_save_state': False, 'is_save_state': False,
'prefix': 'tempest', 'prefix': 'tempest',
'tenant_id': 'project_id', 'tenant_id': 'project_id',
@ -181,10 +187,20 @@ class BaseCmdServiceTests(MockFunctionsBase):
"subnetpools": {'8acf64c1-43fc': 'saved-subnet-pool'}, "subnetpools": {'8acf64c1-43fc': 'saved-subnet-pool'},
"regions": {'RegionOne': {}} "regions": {'RegionOne': {}}
} }
resource_list = {
"keypairs": {'saved-key-pair': ""}
}
# Mocked methods # Mocked methods
get_method = 'tempest.lib.common.rest_client.RestClient.get' get_method = 'tempest.lib.common.rest_client.RestClient.get'
delete_method = 'tempest.lib.common.rest_client.RestClient.delete' delete_method = 'tempest.lib.common.rest_client.RestClient.delete'
log_method = 'tempest.cmd.cleanup_service.LOG.exception' 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 # Override parameters
service_class = 'BaseService' service_class = 'BaseService'
response = None response = None
@ -192,17 +208,19 @@ class BaseCmdServiceTests(MockFunctionsBase):
def _create_cmd_service(self, service_type, is_save_state=False, def _create_cmd_service(self, service_type, is_save_state=False,
is_preserve=False, is_dry_run=False, is_preserve=False, is_dry_run=False,
prefix=''): prefix='', is_resource_list=False):
creds = fake_credentials.FakeKeystoneV3Credentials() creds = fake_credentials.FakeKeystoneV3Credentials()
os = clients.Manager(creds) os = clients.Manager(creds)
return getattr(cleanup_service, service_type)( return getattr(cleanup_service, service_type)(
os, os,
is_resource_list=is_resource_list,
is_save_state=is_save_state, is_save_state=is_save_state,
is_preserve=is_preserve, is_preserve=is_preserve,
is_dry_run=is_dry_run, is_dry_run=is_dry_run,
prefix=prefix, prefix=prefix,
project_id='b8e3ece07bb049138d224436756e3b57', project_id='b8e3ece07bb049138d224436756e3b57',
data={}, data={},
resource_list_json=self.resource_list,
saved_state_json=self.saved_state saved_state_json=self.saved_state
) )
@ -266,6 +284,38 @@ class BaseCmdServiceTests(MockFunctionsBase):
self.assertNotIn(rsp['id'], self.conf_values.values()) self.assertNotIn(rsp['id'], self.conf_values.values())
self.assertNotIn(rsp['name'], 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): class TestSnapshotService(BaseCmdServiceTests):
@ -320,6 +370,24 @@ class TestSnapshotService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestServerService(BaseCmdServiceTests):
@ -378,6 +446,24 @@ class TestServerService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestServerGroupService(BaseCmdServiceTests):
@ -429,6 +515,26 @@ class TestServerGroupService(BaseCmdServiceTests):
(self.validate_response, 'validate', None) (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): class TestKeyPairService(BaseCmdServiceTests):
@ -493,6 +599,33 @@ class TestKeyPairService(BaseCmdServiceTests):
(self.validate_response, 'validate', None) (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): class TestVolumeService(BaseCmdServiceTests):
@ -542,6 +675,24 @@ class TestVolumeService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestVolumeQuotaService(BaseCmdServiceTests):
@ -761,6 +912,24 @@ class TestNetworkService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestNetworkFloatingIpService(BaseCmdServiceTests):
@ -823,6 +992,34 @@ class TestNetworkFloatingIpService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestNetworkRouterService(BaseCmdServiceTests):
@ -937,6 +1134,24 @@ class TestNetworkRouterService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestNetworkMeteringLabelRuleService(BaseCmdServiceTests):
@ -978,6 +1193,34 @@ class TestNetworkMeteringLabelRuleService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestNetworkMeteringLabelService(BaseCmdServiceTests):
@ -1020,6 +1263,24 @@ class TestNetworkMeteringLabelService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestNetworkPortService(BaseCmdServiceTests):
@ -1118,6 +1379,24 @@ class TestNetworkPortService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestNetworkSecGroupService(BaseCmdServiceTests):
@ -1196,6 +1475,24 @@ class TestNetworkSecGroupService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestNetworkSubnetService(BaseCmdServiceTests):
@ -1272,6 +1569,24 @@ class TestNetworkSubnetService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestNetworkSubnetPoolsService(BaseCmdServiceTests):
@ -1340,6 +1655,24 @@ class TestNetworkSubnetPoolsService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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 # begin global services
class TestRegionService(BaseCmdServiceTests): class TestRegionService(BaseCmdServiceTests):
@ -1392,6 +1725,34 @@ class TestRegionService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestDomainService(BaseCmdServiceTests):
@ -1445,6 +1806,26 @@ class TestDomainService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestProjectsService(BaseCmdServiceTests):
@ -1518,6 +1899,24 @@ class TestProjectsService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestImagesService(BaseCmdServiceTests):
@ -1597,6 +1996,24 @@ class TestImagesService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestFlavorService(BaseCmdServiceTests):
@ -1670,6 +2087,24 @@ class TestFlavorService(BaseCmdServiceTests):
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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): class TestRoleService(BaseCmdServiceTests):
@ -1716,6 +2151,24 @@ class TestRoleService(BaseCmdServiceTests):
def test_save_state(self): def test_save_state(self):
self._test_saved_state_true([(self.get_method, self.response, 200)]) 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): class TestUserService(BaseCmdServiceTests):
@ -1782,3 +2235,21 @@ class TestUserService(BaseCmdServiceTests):
"password_expires_at": "1893-11-06T15:32:17.000000", "password_expires_at": "1893-11-06T15:32:17.000000",
}) })
self._test_is_preserve_true([(self.get_method, self.response, 200)]) 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)

View File

@ -0,0 +1,11 @@
{
"project": {
"ce4e7edf051c439d8b81c4bfe581c5ef": "test"
},
"keypairs": {
"tempest-keypair-1215039183": ""
},
"users": {
"74463c83f9d640fe84c4376527ceff26": "test"
}
}

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
import copy import copy
from unittest import mock
import fixtures import fixtures
import jsonschema import jsonschema
@ -749,6 +750,110 @@ class TestExpectedSuccess(BaseRestClientTestClass):
expected_code, read_code) 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): class TestResponseBody(base.TestCase):
def test_str(self): def test_str(self):