blob: f0d72646f68e438234d3e18a8f6fad6f6b2eb229 [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
Masayuki Igawabbbaad62017-11-21 16:04:03 +090031.. warning::
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050032
Masayuki Igawabbbaad62017-11-21 16:04:03 +090033 If step 1 is skipped in the example below, the cleanup procedure
34 may delete resources that existed in the cloud before the test run. This
35 may cause an unwanted destruction of cloud resources, so use caution with
36 this command.
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050037
Masayuki Igawabbbaad62017-11-21 16:04:03 +090038 Examples::
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050039
Masayuki Igawabbbaad62017-11-21 16:04:03 +090040 $ tempest cleanup --init-saved-state
41 $ # Actual running of Tempest tests
42 $ tempest cleanup
David Patersonce781492014-09-18 01:07:01 -040043
44Runtime Arguments
45-----------------
46
Masayuki Igawabbbaad62017-11-21 16:04:03 +090047* ``--init-saved-state``: Initializes the saved state of the OpenStack
48 deployment and will output a ``saved_state.json`` file containing resources
49 from your deployment that will be preserved from the cleanup command. This
50 should be done prior to running Tempest tests.
David Patersonce781492014-09-18 01:07:01 -040051
Masayuki Igawabbbaad62017-11-21 16:04:03 +090052* ``--delete-tempest-conf-objects``: If option is present, then the command
53 will delete the admin project in addition to the resources associated with
54 them on clean up. If option is not present, the command will delete the
55 resources associated with the Tempest and alternate Tempest users and
56 projects but will not delete the projects themselves.
David Patersonce781492014-09-18 01:07:01 -040057
Masayuki Igawabbbaad62017-11-21 16:04:03 +090058* ``--dry-run``: Creates a report (``./dry_run.json``) of the projects that
59 will be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the
60 global objects that will be removed (domains, flavors, images, roles,
61 projects, and users). Once the cleanup command is executed (e.g. run without
62 parameters), running it again with ``--dry-run`` should yield an empty
63 report.
David Patersonce781492014-09-18 01:07:01 -040064
Masayuki Igawabbbaad62017-11-21 16:04:03 +090065* ``--help``: Print the help text for the command and parameters.
David Patersonce781492014-09-18 01:07:01 -040066
Arx Cruz05fe4bc2017-10-20 10:48:28 +020067.. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050068 projects that ``tempest cleanup`` will loop through to delete child
69 objects, but the command will, by default, not delete the projects
Arx Cruz05fe4bc2017-10-20 10:48:28 +020070 themselves. This may differ from the ``projects`` list as you can clean
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050071 the Tempest and alternate Tempest users and projects but they will not be
Masayuki Igawabbbaad62017-11-21 16:04:03 +090072 deleted unless the ``--delete-tempest-conf-objects`` flag is used to
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050073 force their deletion.
David Patersonce781492014-09-18 01:07:01 -040074
David Patersonce781492014-09-18 01:07:01 -040075"""
David Patersonce781492014-09-18 01:07:01 -040076import sys
Marc Kodererf3397942015-12-21 11:17:09 +010077import traceback
David Patersonce781492014-09-18 01:07:01 -040078
David Paterson07661de2015-10-29 20:15:04 -070079from cliff import command
Doug Hellmann583ce2c2015-03-11 14:55:46 +000080from oslo_log import log as logging
Matthew Treinish21905512015-07-13 10:33:35 -040081from oslo_serialization import jsonutils as json
Doug Hellmann583ce2c2015-03-11 14:55:46 +000082
David Patersonce781492014-09-18 01:07:01 -040083from tempest import clients
84from tempest.cmd import cleanup_service
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010085from tempest.common import credentials_factory as credentials
Ken'ichi Ohmichi6ea3f982015-11-09 12:41:13 +000086from tempest.common import identity
David Patersonce781492014-09-18 01:07:01 -040087from tempest import config
David Patersonce781492014-09-18 01:07:01 -040088
89SAVED_STATE_JSON = "saved_state.json"
90DRY_RUN_JSON = "dry_run.json"
91LOG = logging.getLogger(__name__)
92CONF = config.CONF
93
94
David Paterson07661de2015-10-29 20:15:04 -070095class TempestCleanup(command.Command):
David Patersonce781492014-09-18 01:07:01 -040096
Martin Kopec97857942019-06-12 15:23:21 +000097 GOT_EXCEPTIONS = []
98
David Paterson07661de2015-10-29 20:15:04 -070099 def take_action(self, parsed_args):
Marc Kodererf3397942015-12-21 11:17:09 +0100100 try:
101 self.init(parsed_args)
Ghanshyam41711d32016-02-17 17:11:22 +0900102 if not parsed_args.init_saved_state:
103 self._cleanup()
Marc Kodererf3397942015-12-21 11:17:09 +0100104 except Exception:
105 LOG.exception("Failure during cleanup")
106 traceback.print_exc()
107 raise
Martin Kopec97857942019-06-12 15:23:21 +0000108 if self.GOT_EXCEPTIONS:
109 raise Exception(self.GOT_EXCEPTIONS)
Marc Kodererf3397942015-12-21 11:17:09 +0100110
111 def init(self, parsed_args):
David Paterson07661de2015-10-29 20:15:04 -0700112 cleanup_service.init_conf()
113 self.options = parsed_args
ghanshyam009a1f62017-08-08 10:22:57 +0300114 self.admin_mgr = clients.Manager(
115 credentials.get_configured_admin_credentials())
David Patersonce781492014-09-18 01:07:01 -0400116 self.dry_run_data = {}
117 self.json_data = {}
David Patersonce781492014-09-18 01:07:01 -0400118
119 self.admin_id = ""
120 self.admin_role_id = ""
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200121 self.admin_project_id = ""
David Patersonce781492014-09-18 01:07:01 -0400122 self._init_admin_ids()
123
124 self.admin_role_added = []
125
126 # available services
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200127 self.project_services = cleanup_service.get_project_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -0400128 self.global_services = cleanup_service.get_global_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -0400129
David Paterson07661de2015-10-29 20:15:04 -0700130 if parsed_args.init_saved_state:
David Patersonce781492014-09-18 01:07:01 -0400131 self._init_state()
132 return
133
134 self._load_json()
David Patersonce781492014-09-18 01:07:01 -0400135
136 def _cleanup(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800137 print("Begin cleanup")
David Patersonce781492014-09-18 01:07:01 -0400138 is_dry_run = self.options.dry_run
David Patersond6babc52014-10-14 00:11:56 -0400139 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400140 is_save_state = False
141
142 if is_dry_run:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200143 self.dry_run_data["_projects_to_clean"] = {}
David Patersonce781492014-09-18 01:07:01 -0400144
145 admin_mgr = self.admin_mgr
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200146 # Always cleanup tempest and alt tempest projects unless
David Patersonce781492014-09-18 01:07:01 -0400147 # they are in saved state json. Therefore is_preserve is False
148 kwargs = {'data': self.dry_run_data,
149 'is_dry_run': is_dry_run,
150 'saved_state_json': self.json_data,
151 'is_preserve': False,
152 'is_save_state': is_save_state}
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200153 project_service = cleanup_service.ProjectService(admin_mgr, **kwargs)
154 projects = project_service.list()
155 print("Process %s projects" % len(projects))
David Patersonce781492014-09-18 01:07:01 -0400156
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200157 # Loop through list of projects and clean them up.
158 for project in projects:
159 self._add_admin(project['id'])
160 self._clean_project(project)
David Patersonce781492014-09-18 01:07:01 -0400161
162 kwargs = {'data': self.dry_run_data,
163 'is_dry_run': is_dry_run,
164 'saved_state_json': self.json_data,
165 'is_preserve': is_preserve,
Martin Kopec97857942019-06-12 15:23:21 +0000166 'is_save_state': is_save_state,
167 'got_exceptions': self.GOT_EXCEPTIONS}
David Patersonce781492014-09-18 01:07:01 -0400168 for service in self.global_services:
169 svc = service(admin_mgr, **kwargs)
170 svc.run()
171
172 if is_dry_run:
zhang.leia4b1cef2016-03-01 10:50:01 +0800173 with open(DRY_RUN_JSON, 'w+') as f:
174 f.write(json.dumps(self.dry_run_data, sort_keys=True,
175 indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400176
177 self._remove_admin_user_roles()
178
179 def _remove_admin_user_roles(self):
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200180 project_ids = self.admin_role_added
181 LOG.debug("Removing admin user roles where needed for projects: %s",
182 project_ids)
183 for project_id in project_ids:
184 self._remove_admin_role(project_id)
David Patersonce781492014-09-18 01:07:01 -0400185
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200186 def _clean_project(self, project):
187 print("Cleaning project: %s " % project['name'])
David Patersonce781492014-09-18 01:07:01 -0400188 is_dry_run = self.options.dry_run
189 dry_run_data = self.dry_run_data
David Patersond6babc52014-10-14 00:11:56 -0400190 is_preserve = not self.options.delete_tempest_conf_objects
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200191 project_id = project['id']
192 project_name = project['name']
193 project_data = None
David Patersonce781492014-09-18 01:07:01 -0400194 if is_dry_run:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200195 project_data = dry_run_data["_projects_to_clean"][project_id] = {}
196 project_data['name'] = project_name
David Patersonce781492014-09-18 01:07:01 -0400197
David Paterson07661de2015-10-29 20:15:04 -0700198 kwargs = {"username": CONF.auth.admin_username,
199 "password": CONF.auth.admin_password,
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200200 "project_name": project['name']}
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +0100201 mgr = clients.Manager(credentials=credentials.get_credentials(
Andrea Frittoli878d5ab2015-01-30 13:22:50 +0000202 **kwargs))
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200203 kwargs = {'data': project_data,
David Patersonce781492014-09-18 01:07:01 -0400204 'is_dry_run': is_dry_run,
Martin Kopec5a884bf2019-02-11 18:10:55 +0000205 'saved_state_json': self.json_data,
David Patersonce781492014-09-18 01:07:01 -0400206 'is_preserve': is_preserve,
207 'is_save_state': False,
Martin Kopec97857942019-06-12 15:23:21 +0000208 'project_id': project_id,
209 'got_exceptions': self.GOT_EXCEPTIONS}
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200210 for service in self.project_services:
David Patersonce781492014-09-18 01:07:01 -0400211 svc = service(mgr, **kwargs)
212 svc.run()
213
214 def _init_admin_ids(self):
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200215 pr_cl = self.admin_mgr.projects_client
216 rl_cl = self.admin_mgr.roles_v3_client
217 rla_cl = self.admin_mgr.role_assignments_client
218 us_cl = self.admin_mgr.users_v3_client
David Patersonce781492014-09-18 01:07:01 -0400219
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200220 project = identity.get_project_by_name(pr_cl,
221 CONF.auth.admin_project_name)
222 self.admin_project_id = project['id']
223 user = identity.get_user_by_project(us_cl, rla_cl,
224 self.admin_project_id,
225 CONF.auth.admin_username)
David Patersonce781492014-09-18 01:07:01 -0400226 self.admin_id = user['id']
227
Daniel Mellado6b16b922015-12-07 12:43:08 +0000228 roles = rl_cl.list_roles()['roles']
David Patersonce781492014-09-18 01:07:01 -0400229 for role in roles:
230 if role['name'] == CONF.identity.admin_role:
231 self.admin_role_id = role['id']
232 break
233
David Paterson07661de2015-10-29 20:15:04 -0700234 def get_parser(self, prog_name):
235 parser = super(TempestCleanup, self).get_parser(prog_name)
David Patersonce781492014-09-18 01:07:01 -0400236 parser.add_argument('--init-saved-state', action="store_true",
237 dest='init_saved_state', default=False,
238 help="Creates JSON file: " + SAVED_STATE_JSON +
239 ", representing the current state of your "
David Patersond6babc52014-10-14 00:11:56 -0400240 "deployment, specifically object types "
241 "tempest creates and destroys during a run. "
David Patersonce781492014-09-18 01:07:01 -0400242 "You must run with this flag prior to "
David Patersond6babc52014-10-14 00:11:56 -0400243 "executing cleanup in normal mode, which is with "
244 "no arguments.")
David Patersonce781492014-09-18 01:07:01 -0400245 parser.add_argument('--delete-tempest-conf-objects',
David Patersond6babc52014-10-14 00:11:56 -0400246 action="store_true",
247 dest='delete_tempest_conf_objects',
David Patersonce781492014-09-18 01:07:01 -0400248 default=False,
David Patersond6babc52014-10-14 00:11:56 -0400249 help="Force deletion of the tempest and "
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200250 "alternate tempest users and projects.")
David Patersonce781492014-09-18 01:07:01 -0400251 parser.add_argument('--dry-run', action="store_true",
252 dest='dry_run', default=False,
253 help="Generate JSON file:" + DRY_RUN_JSON +
254 ", that reports the objects that would have "
255 "been deleted had a full cleanup been run.")
David Paterson07661de2015-10-29 20:15:04 -0700256 return parser
David Patersonce781492014-09-18 01:07:01 -0400257
David Paterson07661de2015-10-29 20:15:04 -0700258 def get_description(self):
259 return 'Cleanup after tempest run'
David Patersonce781492014-09-18 01:07:01 -0400260
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200261 def _add_admin(self, project_id):
262 rl_cl = self.admin_mgr.roles_v3_client
David Patersonce781492014-09-18 01:07:01 -0400263 needs_role = True
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200264 roles = rl_cl.list_user_roles_on_project(project_id,
ghanshyam50894fc2016-06-17 13:20:25 +0900265 self.admin_id)['roles']
David Patersonce781492014-09-18 01:07:01 -0400266 for role in roles:
267 if role['id'] == self.admin_role_id:
268 needs_role = False
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200269 LOG.debug("User already had admin privilege for this project")
David Patersonce781492014-09-18 01:07:01 -0400270 if needs_role:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200271 LOG.debug("Adding admin privilege for : %s", project_id)
272 rl_cl.create_user_role_on_project(project_id, self.admin_id,
ghanshyam50894fc2016-06-17 13:20:25 +0900273 self.admin_role_id)
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200274 self.admin_role_added.append(project_id)
David Patersonce781492014-09-18 01:07:01 -0400275
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200276 def _remove_admin_role(self, project_id):
277 LOG.debug("Remove admin user role for projectt: %s", project_id)
ghanshyam009a1f62017-08-08 10:22:57 +0300278 # Must initialize Admin Manager for each user role
David Patersonce781492014-09-18 01:07:01 -0400279 # Otherwise authentication exception is thrown, weird
ghanshyam009a1f62017-08-08 10:22:57 +0300280 id_cl = clients.Manager(
281 credentials.get_configured_admin_credentials()).identity_client
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200282 if (self._project_exists(project_id)):
David Patersonce781492014-09-18 01:07:01 -0400283 try:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200284 id_cl.delete_role_from_user_on_project(project_id,
ghanshyam50894fc2016-06-17 13:20:25 +0900285 self.admin_id,
286 self.admin_role_id)
David Patersonce781492014-09-18 01:07:01 -0400287 except Exception as ex:
zhuflde676372018-11-16 15:34:56 +0800288 LOG.exception("Failed removing role from project which still "
Jordan Pittier525ec712016-12-07 17:51:26 +0100289 "exists, exception: %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400290
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200291 def _project_exists(self, project_id):
292 pr_cl = self.admin_mgr.projects_client
David Patersonce781492014-09-18 01:07:01 -0400293 try:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200294 p = pr_cl.show_project(project_id)
295 LOG.debug("Project is: %s", str(p))
David Patersonce781492014-09-18 01:07:01 -0400296 return True
297 except Exception as ex:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200298 LOG.debug("Project no longer exists? %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400299 return False
300
301 def _init_state(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800302 print("Initializing saved state.")
David Patersonce781492014-09-18 01:07:01 -0400303 data = {}
304 admin_mgr = self.admin_mgr
305 kwargs = {'data': data,
306 'is_dry_run': False,
307 'saved_state_json': data,
308 'is_preserve': False,
Martin Kopec97857942019-06-12 15:23:21 +0000309 'is_save_state': True,
310 'got_exceptions': self.GOT_EXCEPTIONS}
David Patersonce781492014-09-18 01:07:01 -0400311 for service in self.global_services:
312 svc = service(admin_mgr, **kwargs)
313 svc.run()
314
Martin Kopec5a884bf2019-02-11 18:10:55 +0000315 for service in self.project_services:
316 svc = service(admin_mgr, **kwargs)
317 svc.run()
318
zhang.leia4b1cef2016-03-01 10:50:01 +0800319 with open(SAVED_STATE_JSON, 'w+') as f:
afazekas40fcb9b2019-03-08 11:25:11 +0100320 f.write(json.dumps(data, sort_keys=True,
321 indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400322
Martin Kopec6caf3fa2019-01-20 15:24:10 +0000323 def _load_json(self, saved_state_json=SAVED_STATE_JSON):
David Patersonce781492014-09-18 01:07:01 -0400324 try:
Martin Kopec6caf3fa2019-01-20 15:24:10 +0000325 with open(saved_state_json, 'rb') as json_file:
zhang.leia4b1cef2016-03-01 10:50:01 +0800326 self.json_data = json.load(json_file)
327
David Patersonce781492014-09-18 01:07:01 -0400328 except IOError as ex:
329 LOG.exception("Failed loading saved state, please be sure you"
330 " have first run cleanup with --init-saved-state "
Jordan Pittier525ec712016-12-07 17:51:26 +0100331 "flag prior to running tempest. Exception: %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400332 sys.exit(ex)
333 except Exception as ex:
Jordan Pittier525ec712016-12-07 17:51:26 +0100334 LOG.exception("Exception parsing saved state json : %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400335 sys.exit(ex)