blob: ac73cbfa916e4f6e4bc20be1dfc14e034ea8ae34 [file] [log] [blame]
David Patersonce781492014-09-18 01:07:01 -04001#!/usr/bin/env python
2#
3# Copyright 2014 Dell Inc.
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
David Patersond6babc52014-10-14 00:11:56 -040012# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
David Patersonce781492014-09-18 01:07:01 -040013# License for the specific language governing permissions and limitations
14# under the License.
David Patersonce781492014-09-18 01:07:01 -040015
16"""
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050017Utility for cleaning up environment after Tempest test run
18
19**Usage:** ``tempest cleanup [--help] [OPTIONS]``
20
21If run with no arguments, ``tempest cleanup`` will query your OpenStack
22deployment and build a list of resources to delete and destroy them. This list
23will exclude the resources from ``saved_state.json`` and will include the
24configured admin account if the ``--delete-tempest-conf-objects`` flag is
25specified. By default the admin project is not deleted and the admin user
26specified in ``tempest.conf`` is never deleted.
27
28Example Run
29-----------
30
31**WARNING: If step 1 is skipped in the example below, the cleanup procedure
32may delete resources that existed in the cloud before the test run. This
33may cause an unwanted destruction of cloud resources, so use caution with
34this command.**
35
36``$ tempest cleanup --init-saved-state``
37
38``$ # Actual running of Tempest tests``
39
40``$ tempest cleanup``
David Patersonce781492014-09-18 01:07:01 -040041
42Runtime Arguments
43-----------------
44
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050045**--init-saved-state**: Initializes the saved state of the OpenStack deployment
46and will output a ``saved_state.json`` file containing resources from your
47deployment that will be preserved from the cleanup command. This should be
48done prior to running Tempest tests.
David Patersonce781492014-09-18 01:07:01 -040049
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050050**--delete-tempest-conf-objects**: If option is present, then the command will
51delete the admin project in addition to the resources associated with them on
52clean up. If option is not present, the command will delete the resources
53associated with the Tempest and alternate Tempest users and projects but will
54not delete the projects themselves.
David Patersonce781492014-09-18 01:07:01 -040055
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050056**--dry-run**: Creates a report (``./dry_run.json``) of the projects that will
57be cleaned up (in the ``_tenants_to_clean`` dictionary [1]_) and the global
58objects that will be removed (domains, flavors, images, roles, projects,
59and users). Once the cleanup command is executed (e.g. run without
60parameters), running it again with **--dry-run** should yield an empty report.
David Patersonce781492014-09-18 01:07:01 -040061
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050062**--help**: Print the help text for the command and parameters.
David Patersonce781492014-09-18 01:07:01 -040063
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050064.. [1] The ``_tenants_to_clean`` dictionary in ``dry_run.json`` lists the
65 projects that ``tempest cleanup`` will loop through to delete child
66 objects, but the command will, by default, not delete the projects
67 themselves. This may differ from the ``tenants`` list as you can clean
68 the Tempest and alternate Tempest users and projects but they will not be
69 deleted unless the **--delete-tempest-conf-objects** flag is used to
70 force their deletion.
David Patersonce781492014-09-18 01:07:01 -040071
David Patersonce781492014-09-18 01:07:01 -040072"""
David Patersonce781492014-09-18 01:07:01 -040073import sys
Marc Kodererf3397942015-12-21 11:17:09 +010074import traceback
David Patersonce781492014-09-18 01:07:01 -040075
David Paterson07661de2015-10-29 20:15:04 -070076from cliff import command
Doug Hellmann583ce2c2015-03-11 14:55:46 +000077from oslo_log import log as logging
Matthew Treinish21905512015-07-13 10:33:35 -040078from oslo_serialization import jsonutils as json
Doug Hellmann583ce2c2015-03-11 14:55:46 +000079
David Patersonce781492014-09-18 01:07:01 -040080from tempest import clients
81from tempest.cmd import cleanup_service
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010082from tempest.common import credentials_factory as credentials
Ken'ichi Ohmichi6ea3f982015-11-09 12:41:13 +000083from tempest.common import identity
David Patersonce781492014-09-18 01:07:01 -040084from tempest import config
David Patersonce781492014-09-18 01:07:01 -040085
86SAVED_STATE_JSON = "saved_state.json"
87DRY_RUN_JSON = "dry_run.json"
88LOG = logging.getLogger(__name__)
89CONF = config.CONF
90
91
David Paterson07661de2015-10-29 20:15:04 -070092class TempestCleanup(command.Command):
David Patersonce781492014-09-18 01:07:01 -040093
David Paterson07661de2015-10-29 20:15:04 -070094 def take_action(self, parsed_args):
Marc Kodererf3397942015-12-21 11:17:09 +010095 try:
96 self.init(parsed_args)
Ghanshyam41711d32016-02-17 17:11:22 +090097 if not parsed_args.init_saved_state:
98 self._cleanup()
Marc Kodererf3397942015-12-21 11:17:09 +010099 except Exception:
100 LOG.exception("Failure during cleanup")
101 traceback.print_exc()
102 raise
Marc Kodererf3397942015-12-21 11:17:09 +0100103
104 def init(self, parsed_args):
David Paterson07661de2015-10-29 20:15:04 -0700105 cleanup_service.init_conf()
106 self.options = parsed_args
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +0100107 self.admin_mgr = credentials.AdminManager()
David Patersonce781492014-09-18 01:07:01 -0400108 self.dry_run_data = {}
109 self.json_data = {}
David Patersonce781492014-09-18 01:07:01 -0400110
111 self.admin_id = ""
112 self.admin_role_id = ""
113 self.admin_tenant_id = ""
114 self._init_admin_ids()
115
116 self.admin_role_added = []
117
118 # available services
119 self.tenant_services = cleanup_service.get_tenant_cleanup_services()
120 self.global_services = cleanup_service.get_global_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -0400121
David Paterson07661de2015-10-29 20:15:04 -0700122 if parsed_args.init_saved_state:
David Patersonce781492014-09-18 01:07:01 -0400123 self._init_state()
124 return
125
126 self._load_json()
David Patersonce781492014-09-18 01:07:01 -0400127
128 def _cleanup(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800129 print("Begin cleanup")
David Patersonce781492014-09-18 01:07:01 -0400130 is_dry_run = self.options.dry_run
David Patersond6babc52014-10-14 00:11:56 -0400131 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400132 is_save_state = False
133
134 if is_dry_run:
135 self.dry_run_data["_tenants_to_clean"] = {}
David Patersonce781492014-09-18 01:07:01 -0400136
137 admin_mgr = self.admin_mgr
138 # Always cleanup tempest and alt tempest tenants unless
139 # they are in saved state json. Therefore is_preserve is False
140 kwargs = {'data': self.dry_run_data,
141 'is_dry_run': is_dry_run,
142 'saved_state_json': self.json_data,
143 'is_preserve': False,
144 'is_save_state': is_save_state}
145 tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
146 tenants = tenant_service.list()
yuyafeie2dbc1f2016-07-06 16:09:19 +0800147 print("Process %s tenants" % len(tenants))
David Patersonce781492014-09-18 01:07:01 -0400148
149 # Loop through list of tenants and clean them up.
150 for tenant in tenants:
151 self._add_admin(tenant['id'])
152 self._clean_tenant(tenant)
153
154 kwargs = {'data': self.dry_run_data,
155 'is_dry_run': is_dry_run,
156 'saved_state_json': self.json_data,
157 'is_preserve': is_preserve,
158 'is_save_state': is_save_state}
159 for service in self.global_services:
160 svc = service(admin_mgr, **kwargs)
161 svc.run()
162
163 if is_dry_run:
zhang.leia4b1cef2016-03-01 10:50:01 +0800164 with open(DRY_RUN_JSON, 'w+') as f:
165 f.write(json.dumps(self.dry_run_data, sort_keys=True,
166 indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400167
168 self._remove_admin_user_roles()
169
170 def _remove_admin_user_roles(self):
171 tenant_ids = self.admin_role_added
Jordan Pittier525ec712016-12-07 17:51:26 +0100172 LOG.debug("Removing admin user roles where needed for tenants: %s",
173 tenant_ids)
David Patersonce781492014-09-18 01:07:01 -0400174 for tenant_id in tenant_ids:
175 self._remove_admin_role(tenant_id)
176
177 def _clean_tenant(self, tenant):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800178 print("Cleaning tenant: %s " % tenant['name'])
David Patersonce781492014-09-18 01:07:01 -0400179 is_dry_run = self.options.dry_run
180 dry_run_data = self.dry_run_data
David Patersond6babc52014-10-14 00:11:56 -0400181 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400182 tenant_id = tenant['id']
183 tenant_name = tenant['name']
184 tenant_data = None
185 if is_dry_run:
186 tenant_data = dry_run_data["_tenants_to_clean"][tenant_id] = {}
187 tenant_data['name'] = tenant_name
188
David Paterson07661de2015-10-29 20:15:04 -0700189 kwargs = {"username": CONF.auth.admin_username,
190 "password": CONF.auth.admin_password,
David Patersonce781492014-09-18 01:07:01 -0400191 "tenant_name": tenant['name']}
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +0100192 mgr = clients.Manager(credentials=credentials.get_credentials(
Andrea Frittoli878d5ab2015-01-30 13:22:50 +0000193 **kwargs))
David Patersonce781492014-09-18 01:07:01 -0400194 kwargs = {'data': tenant_data,
195 'is_dry_run': is_dry_run,
196 'saved_state_json': None,
197 'is_preserve': is_preserve,
198 'is_save_state': False,
199 'tenant_id': tenant_id}
200 for service in self.tenant_services:
201 svc = service(mgr, **kwargs)
202 svc.run()
203
204 def _init_admin_ids(self):
Daniel Melladob83ea562015-12-18 09:12:49 +0000205 tn_cl = self.admin_mgr.tenants_client
Daniel Mellado6b16b922015-12-07 12:43:08 +0000206 rl_cl = self.admin_mgr.roles_client
David Patersonce781492014-09-18 01:07:01 -0400207
Daniel Melladob83ea562015-12-18 09:12:49 +0000208 tenant = identity.get_tenant_by_name(tn_cl,
Daniel Melladod4d0b932016-04-08 08:57:29 +0000209 CONF.auth.admin_project_name)
David Patersonce781492014-09-18 01:07:01 -0400210 self.admin_tenant_id = tenant['id']
211
Daniel Melladob83ea562015-12-18 09:12:49 +0000212 user = identity.get_user_by_username(tn_cl, self.admin_tenant_id,
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000213 CONF.auth.admin_username)
David Patersonce781492014-09-18 01:07:01 -0400214 self.admin_id = user['id']
215
Daniel Mellado6b16b922015-12-07 12:43:08 +0000216 roles = rl_cl.list_roles()['roles']
David Patersonce781492014-09-18 01:07:01 -0400217 for role in roles:
218 if role['name'] == CONF.identity.admin_role:
219 self.admin_role_id = role['id']
220 break
221
David Paterson07661de2015-10-29 20:15:04 -0700222 def get_parser(self, prog_name):
223 parser = super(TempestCleanup, self).get_parser(prog_name)
David Patersonce781492014-09-18 01:07:01 -0400224 parser.add_argument('--init-saved-state', action="store_true",
225 dest='init_saved_state', default=False,
226 help="Creates JSON file: " + SAVED_STATE_JSON +
227 ", representing the current state of your "
David Patersond6babc52014-10-14 00:11:56 -0400228 "deployment, specifically object types "
229 "tempest creates and destroys during a run. "
David Patersonce781492014-09-18 01:07:01 -0400230 "You must run with this flag prior to "
David Patersond6babc52014-10-14 00:11:56 -0400231 "executing cleanup in normal mode, which is with "
232 "no arguments.")
David Patersonce781492014-09-18 01:07:01 -0400233 parser.add_argument('--delete-tempest-conf-objects',
David Patersond6babc52014-10-14 00:11:56 -0400234 action="store_true",
235 dest='delete_tempest_conf_objects',
David Patersonce781492014-09-18 01:07:01 -0400236 default=False,
David Patersond6babc52014-10-14 00:11:56 -0400237 help="Force deletion of the tempest and "
David Patersonce781492014-09-18 01:07:01 -0400238 "alternate tempest users and tenants.")
239 parser.add_argument('--dry-run', action="store_true",
240 dest='dry_run', default=False,
241 help="Generate JSON file:" + DRY_RUN_JSON +
242 ", that reports the objects that would have "
243 "been deleted had a full cleanup been run.")
David Paterson07661de2015-10-29 20:15:04 -0700244 return parser
David Patersonce781492014-09-18 01:07:01 -0400245
David Paterson07661de2015-10-29 20:15:04 -0700246 def get_description(self):
247 return 'Cleanup after tempest run'
David Patersonce781492014-09-18 01:07:01 -0400248
249 def _add_admin(self, tenant_id):
Daniel Mellado6b16b922015-12-07 12:43:08 +0000250 rl_cl = self.admin_mgr.roles_client
David Patersonce781492014-09-18 01:07:01 -0400251 needs_role = True
ghanshyam50894fc2016-06-17 13:20:25 +0900252 roles = rl_cl.list_user_roles_on_project(tenant_id,
253 self.admin_id)['roles']
David Patersonce781492014-09-18 01:07:01 -0400254 for role in roles:
255 if role['id'] == self.admin_role_id:
256 needs_role = False
257 LOG.debug("User already had admin privilege for this tenant")
258 if needs_role:
Jordan Pittier525ec712016-12-07 17:51:26 +0100259 LOG.debug("Adding admin privilege for : %s", tenant_id)
ghanshyam50894fc2016-06-17 13:20:25 +0900260 rl_cl.create_user_role_on_project(tenant_id, self.admin_id,
261 self.admin_role_id)
David Patersonce781492014-09-18 01:07:01 -0400262 self.admin_role_added.append(tenant_id)
263
264 def _remove_admin_role(self, tenant_id):
Jordan Pittier525ec712016-12-07 17:51:26 +0100265 LOG.debug("Remove admin user role for tenant: %s", tenant_id)
David Patersonce781492014-09-18 01:07:01 -0400266 # Must initialize AdminManager for each user role
267 # Otherwise authentication exception is thrown, weird
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +0100268 id_cl = credentials.AdminManager().identity_client
David Patersonce781492014-09-18 01:07:01 -0400269 if (self._tenant_exists(tenant_id)):
270 try:
ghanshyam50894fc2016-06-17 13:20:25 +0900271 id_cl.delete_role_from_user_on_project(tenant_id,
272 self.admin_id,
273 self.admin_role_id)
David Patersonce781492014-09-18 01:07:01 -0400274 except Exception as ex:
275 LOG.exception("Failed removing role from tenant which still"
Jordan Pittier525ec712016-12-07 17:51:26 +0100276 "exists, exception: %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400277
278 def _tenant_exists(self, tenant_id):
Daniel Melladob83ea562015-12-18 09:12:49 +0000279 tn_cl = self.admin_mgr.tenants_client
David Patersonce781492014-09-18 01:07:01 -0400280 try:
Daniel Melladob83ea562015-12-18 09:12:49 +0000281 t = tn_cl.show_tenant(tenant_id)
Jordan Pittier525ec712016-12-07 17:51:26 +0100282 LOG.debug("Tenant is: %s", str(t))
David Patersonce781492014-09-18 01:07:01 -0400283 return True
284 except Exception as ex:
Jordan Pittier525ec712016-12-07 17:51:26 +0100285 LOG.debug("Tenant no longer exists? %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400286 return False
287
288 def _init_state(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800289 print("Initializing saved state.")
David Patersonce781492014-09-18 01:07:01 -0400290 data = {}
291 admin_mgr = self.admin_mgr
292 kwargs = {'data': data,
293 'is_dry_run': False,
294 'saved_state_json': data,
295 'is_preserve': False,
296 'is_save_state': True}
297 for service in self.global_services:
298 svc = service(admin_mgr, **kwargs)
299 svc.run()
300
zhang.leia4b1cef2016-03-01 10:50:01 +0800301 with open(SAVED_STATE_JSON, 'w+') as f:
302 f.write(json.dumps(data,
303 sort_keys=True, indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400304
305 def _load_json(self):
306 try:
zhang.leia4b1cef2016-03-01 10:50:01 +0800307 with open(SAVED_STATE_JSON) as json_file:
308 self.json_data = json.load(json_file)
309
David Patersonce781492014-09-18 01:07:01 -0400310 except IOError as ex:
311 LOG.exception("Failed loading saved state, please be sure you"
312 " have first run cleanup with --init-saved-state "
Jordan Pittier525ec712016-12-07 17:51:26 +0100313 "flag prior to running tempest. Exception: %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400314 sys.exit(ex)
315 except Exception as ex:
Jordan Pittier525ec712016-12-07 17:51:26 +0100316 LOG.exception("Exception parsing saved state json : %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400317 sys.exit(ex)