blob: 2f54f9a64283e4c4e1e5f6f3edf94b159a70ae16 [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
David Paterson07661de2015-10-29 20:15:04 -070097 def take_action(self, parsed_args):
Marc Kodererf3397942015-12-21 11:17:09 +010098 try:
99 self.init(parsed_args)
Ghanshyam41711d32016-02-17 17:11:22 +0900100 if not parsed_args.init_saved_state:
101 self._cleanup()
Marc Kodererf3397942015-12-21 11:17:09 +0100102 except Exception:
103 LOG.exception("Failure during cleanup")
104 traceback.print_exc()
105 raise
Marc Kodererf3397942015-12-21 11:17:09 +0100106
107 def init(self, parsed_args):
David Paterson07661de2015-10-29 20:15:04 -0700108 cleanup_service.init_conf()
109 self.options = parsed_args
ghanshyam009a1f62017-08-08 10:22:57 +0300110 self.admin_mgr = clients.Manager(
111 credentials.get_configured_admin_credentials())
David Patersonce781492014-09-18 01:07:01 -0400112 self.dry_run_data = {}
113 self.json_data = {}
David Patersonce781492014-09-18 01:07:01 -0400114
115 self.admin_id = ""
116 self.admin_role_id = ""
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200117 self.admin_project_id = ""
David Patersonce781492014-09-18 01:07:01 -0400118 self._init_admin_ids()
119
120 self.admin_role_added = []
121
122 # available services
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200123 self.project_services = cleanup_service.get_project_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -0400124 self.global_services = cleanup_service.get_global_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -0400125
David Paterson07661de2015-10-29 20:15:04 -0700126 if parsed_args.init_saved_state:
David Patersonce781492014-09-18 01:07:01 -0400127 self._init_state()
128 return
129
130 self._load_json()
David Patersonce781492014-09-18 01:07:01 -0400131
132 def _cleanup(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800133 print("Begin cleanup")
David Patersonce781492014-09-18 01:07:01 -0400134 is_dry_run = self.options.dry_run
David Patersond6babc52014-10-14 00:11:56 -0400135 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400136 is_save_state = False
137
138 if is_dry_run:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200139 self.dry_run_data["_projects_to_clean"] = {}
David Patersonce781492014-09-18 01:07:01 -0400140
141 admin_mgr = self.admin_mgr
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200142 # Always cleanup tempest and alt tempest projects unless
David Patersonce781492014-09-18 01:07:01 -0400143 # they are in saved state json. Therefore is_preserve is False
144 kwargs = {'data': self.dry_run_data,
145 'is_dry_run': is_dry_run,
146 'saved_state_json': self.json_data,
147 'is_preserve': False,
148 'is_save_state': is_save_state}
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200149 project_service = cleanup_service.ProjectService(admin_mgr, **kwargs)
150 projects = project_service.list()
151 print("Process %s projects" % len(projects))
David Patersonce781492014-09-18 01:07:01 -0400152
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200153 # Loop through list of projects and clean them up.
154 for project in projects:
155 self._add_admin(project['id'])
156 self._clean_project(project)
David Patersonce781492014-09-18 01:07:01 -0400157
158 kwargs = {'data': self.dry_run_data,
159 'is_dry_run': is_dry_run,
160 'saved_state_json': self.json_data,
161 'is_preserve': is_preserve,
162 'is_save_state': is_save_state}
163 for service in self.global_services:
164 svc = service(admin_mgr, **kwargs)
165 svc.run()
166
167 if is_dry_run:
zhang.leia4b1cef2016-03-01 10:50:01 +0800168 with open(DRY_RUN_JSON, 'w+') as f:
169 f.write(json.dumps(self.dry_run_data, sort_keys=True,
170 indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400171
172 self._remove_admin_user_roles()
173
174 def _remove_admin_user_roles(self):
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200175 project_ids = self.admin_role_added
176 LOG.debug("Removing admin user roles where needed for projects: %s",
177 project_ids)
178 for project_id in project_ids:
179 self._remove_admin_role(project_id)
David Patersonce781492014-09-18 01:07:01 -0400180
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200181 def _clean_project(self, project):
182 print("Cleaning project: %s " % project['name'])
David Patersonce781492014-09-18 01:07:01 -0400183 is_dry_run = self.options.dry_run
184 dry_run_data = self.dry_run_data
David Patersond6babc52014-10-14 00:11:56 -0400185 is_preserve = not self.options.delete_tempest_conf_objects
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200186 project_id = project['id']
187 project_name = project['name']
188 project_data = None
David Patersonce781492014-09-18 01:07:01 -0400189 if is_dry_run:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200190 project_data = dry_run_data["_projects_to_clean"][project_id] = {}
191 project_data['name'] = project_name
David Patersonce781492014-09-18 01:07:01 -0400192
David Paterson07661de2015-10-29 20:15:04 -0700193 kwargs = {"username": CONF.auth.admin_username,
194 "password": CONF.auth.admin_password,
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200195 "project_name": project['name']}
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +0100196 mgr = clients.Manager(credentials=credentials.get_credentials(
Andrea Frittoli878d5ab2015-01-30 13:22:50 +0000197 **kwargs))
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200198 kwargs = {'data': project_data,
David Patersonce781492014-09-18 01:07:01 -0400199 'is_dry_run': is_dry_run,
200 'saved_state_json': None,
201 'is_preserve': is_preserve,
202 'is_save_state': False,
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200203 'project_id': project_id}
204 for service in self.project_services:
David Patersonce781492014-09-18 01:07:01 -0400205 svc = service(mgr, **kwargs)
206 svc.run()
207
208 def _init_admin_ids(self):
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200209 pr_cl = self.admin_mgr.projects_client
210 rl_cl = self.admin_mgr.roles_v3_client
211 rla_cl = self.admin_mgr.role_assignments_client
212 us_cl = self.admin_mgr.users_v3_client
David Patersonce781492014-09-18 01:07:01 -0400213
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200214 project = identity.get_project_by_name(pr_cl,
215 CONF.auth.admin_project_name)
216 self.admin_project_id = project['id']
217 user = identity.get_user_by_project(us_cl, rla_cl,
218 self.admin_project_id,
219 CONF.auth.admin_username)
David Patersonce781492014-09-18 01:07:01 -0400220 self.admin_id = user['id']
221
Daniel Mellado6b16b922015-12-07 12:43:08 +0000222 roles = rl_cl.list_roles()['roles']
David Patersonce781492014-09-18 01:07:01 -0400223 for role in roles:
224 if role['name'] == CONF.identity.admin_role:
225 self.admin_role_id = role['id']
226 break
227
David Paterson07661de2015-10-29 20:15:04 -0700228 def get_parser(self, prog_name):
229 parser = super(TempestCleanup, self).get_parser(prog_name)
David Patersonce781492014-09-18 01:07:01 -0400230 parser.add_argument('--init-saved-state', action="store_true",
231 dest='init_saved_state', default=False,
232 help="Creates JSON file: " + SAVED_STATE_JSON +
233 ", representing the current state of your "
David Patersond6babc52014-10-14 00:11:56 -0400234 "deployment, specifically object types "
235 "tempest creates and destroys during a run. "
David Patersonce781492014-09-18 01:07:01 -0400236 "You must run with this flag prior to "
David Patersond6babc52014-10-14 00:11:56 -0400237 "executing cleanup in normal mode, which is with "
238 "no arguments.")
David Patersonce781492014-09-18 01:07:01 -0400239 parser.add_argument('--delete-tempest-conf-objects',
David Patersond6babc52014-10-14 00:11:56 -0400240 action="store_true",
241 dest='delete_tempest_conf_objects',
David Patersonce781492014-09-18 01:07:01 -0400242 default=False,
David Patersond6babc52014-10-14 00:11:56 -0400243 help="Force deletion of the tempest and "
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200244 "alternate tempest users and projects.")
David Patersonce781492014-09-18 01:07:01 -0400245 parser.add_argument('--dry-run', action="store_true",
246 dest='dry_run', default=False,
247 help="Generate JSON file:" + DRY_RUN_JSON +
248 ", that reports the objects that would have "
249 "been deleted had a full cleanup been run.")
David Paterson07661de2015-10-29 20:15:04 -0700250 return parser
David Patersonce781492014-09-18 01:07:01 -0400251
David Paterson07661de2015-10-29 20:15:04 -0700252 def get_description(self):
253 return 'Cleanup after tempest run'
David Patersonce781492014-09-18 01:07:01 -0400254
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200255 def _add_admin(self, project_id):
256 rl_cl = self.admin_mgr.roles_v3_client
David Patersonce781492014-09-18 01:07:01 -0400257 needs_role = True
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200258 roles = rl_cl.list_user_roles_on_project(project_id,
ghanshyam50894fc2016-06-17 13:20:25 +0900259 self.admin_id)['roles']
David Patersonce781492014-09-18 01:07:01 -0400260 for role in roles:
261 if role['id'] == self.admin_role_id:
262 needs_role = False
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200263 LOG.debug("User already had admin privilege for this project")
David Patersonce781492014-09-18 01:07:01 -0400264 if needs_role:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200265 LOG.debug("Adding admin privilege for : %s", project_id)
266 rl_cl.create_user_role_on_project(project_id, self.admin_id,
ghanshyam50894fc2016-06-17 13:20:25 +0900267 self.admin_role_id)
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200268 self.admin_role_added.append(project_id)
David Patersonce781492014-09-18 01:07:01 -0400269
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200270 def _remove_admin_role(self, project_id):
271 LOG.debug("Remove admin user role for projectt: %s", project_id)
ghanshyam009a1f62017-08-08 10:22:57 +0300272 # Must initialize Admin Manager for each user role
David Patersonce781492014-09-18 01:07:01 -0400273 # Otherwise authentication exception is thrown, weird
ghanshyam009a1f62017-08-08 10:22:57 +0300274 id_cl = clients.Manager(
275 credentials.get_configured_admin_credentials()).identity_client
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200276 if (self._project_exists(project_id)):
David Patersonce781492014-09-18 01:07:01 -0400277 try:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200278 id_cl.delete_role_from_user_on_project(project_id,
ghanshyam50894fc2016-06-17 13:20:25 +0900279 self.admin_id,
280 self.admin_role_id)
David Patersonce781492014-09-18 01:07:01 -0400281 except Exception as ex:
zhuflde676372018-11-16 15:34:56 +0800282 LOG.exception("Failed removing role from project which still "
Jordan Pittier525ec712016-12-07 17:51:26 +0100283 "exists, exception: %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400284
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200285 def _project_exists(self, project_id):
286 pr_cl = self.admin_mgr.projects_client
David Patersonce781492014-09-18 01:07:01 -0400287 try:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200288 p = pr_cl.show_project(project_id)
289 LOG.debug("Project is: %s", str(p))
David Patersonce781492014-09-18 01:07:01 -0400290 return True
291 except Exception as ex:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200292 LOG.debug("Project no longer exists? %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400293 return False
294
295 def _init_state(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800296 print("Initializing saved state.")
David Patersonce781492014-09-18 01:07:01 -0400297 data = {}
298 admin_mgr = self.admin_mgr
299 kwargs = {'data': data,
300 'is_dry_run': False,
301 'saved_state_json': data,
302 'is_preserve': False,
303 'is_save_state': True}
304 for service in self.global_services:
305 svc = service(admin_mgr, **kwargs)
306 svc.run()
307
zhang.leia4b1cef2016-03-01 10:50:01 +0800308 with open(SAVED_STATE_JSON, 'w+') as f:
309 f.write(json.dumps(data,
310 sort_keys=True, indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400311
312 def _load_json(self):
313 try:
zhang.leia4b1cef2016-03-01 10:50:01 +0800314 with open(SAVED_STATE_JSON) as json_file:
315 self.json_data = json.load(json_file)
316
David Patersonce781492014-09-18 01:07:01 -0400317 except IOError as ex:
318 LOG.exception("Failed loading saved state, please be sure you"
319 " have first run cleanup with --init-saved-state "
Jordan Pittier525ec712016-12-07 17:51:26 +0100320 "flag prior to running tempest. Exception: %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400321 sys.exit(ex)
322 except Exception as ex:
Jordan Pittier525ec712016-12-07 17:51:26 +0100323 LOG.exception("Exception parsing saved state json : %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400324 sys.exit(ex)