blob: c54b16bc94e950f84123e9c6e690dbf49500b182 [file] [log] [blame]
David Patersonce781492014-09-18 01:07:01 -04001# Copyright 2014 Dell Inc.
2# Licensed under the Apache License, Version 2.0 (the "License"); you may
3# not use this file except in compliance with the License. You may obtain
4# a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
David Patersond6babc52014-10-14 00:11:56 -040010# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
David Patersonce781492014-09-18 01:07:01 -040011# License for the specific language governing permissions and limitations
12# under the License.
David Patersonce781492014-09-18 01:07:01 -040013
14"""
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050015Utility for cleaning up environment after Tempest test run
16
17**Usage:** ``tempest cleanup [--help] [OPTIONS]``
18
19If run with no arguments, ``tempest cleanup`` will query your OpenStack
20deployment and build a list of resources to delete and destroy them. This list
21will exclude the resources from ``saved_state.json`` and will include the
22configured admin account if the ``--delete-tempest-conf-objects`` flag is
23specified. By default the admin project is not deleted and the admin user
24specified in ``tempest.conf`` is never deleted.
25
26Example Run
27-----------
28
Masayuki Igawabbbaad62017-11-21 16:04:03 +090029.. warning::
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050030
Masayuki Igawabbbaad62017-11-21 16:04:03 +090031 If step 1 is skipped in the example below, the cleanup procedure
32 may delete resources that existed in the cloud before the test run. This
33 may cause an unwanted destruction of cloud resources, so use caution with
34 this command.
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050035
Masayuki Igawabbbaad62017-11-21 16:04:03 +090036 Examples::
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050037
Masayuki Igawabbbaad62017-11-21 16:04:03 +090038 $ tempest cleanup --init-saved-state
39 $ # Actual running of Tempest tests
40 $ tempest cleanup
David Patersonce781492014-09-18 01:07:01 -040041
42Runtime Arguments
43-----------------
44
Masayuki Igawabbbaad62017-11-21 16:04:03 +090045* ``--init-saved-state``: Initializes the saved state of the OpenStack
46 deployment and will output a ``saved_state.json`` file containing resources
47 from your deployment that will be preserved from the cleanup command. This
48 should be done prior to running Tempest tests.
David Patersonce781492014-09-18 01:07:01 -040049
Masayuki Igawabbbaad62017-11-21 16:04:03 +090050* ``--delete-tempest-conf-objects``: If option is present, then the command
51 will delete the admin project in addition to the resources associated with
52 them on clean up. If option is not present, the command will delete the
53 resources associated with the Tempest and alternate Tempest users and
54 projects but will not delete the projects themselves.
David Patersonce781492014-09-18 01:07:01 -040055
Masayuki Igawabbbaad62017-11-21 16:04:03 +090056* ``--dry-run``: Creates a report (``./dry_run.json``) of the projects that
57 will be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the
58 global objects that will be removed (domains, flavors, images, roles,
59 projects, and users). Once the cleanup command is executed (e.g. run without
60 parameters), running it again with ``--dry-run`` should yield an empty
61 report.
David Patersonce781492014-09-18 01:07:01 -040062
Masayuki Igawabbbaad62017-11-21 16:04:03 +090063* ``--help``: Print the help text for the command and parameters.
David Patersonce781492014-09-18 01:07:01 -040064
Arx Cruz05fe4bc2017-10-20 10:48:28 +020065.. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050066 projects that ``tempest cleanup`` will loop through to delete child
67 objects, but the command will, by default, not delete the projects
Arx Cruz05fe4bc2017-10-20 10:48:28 +020068 themselves. This may differ from the ``projects`` list as you can clean
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050069 the Tempest and alternate Tempest users and projects but they will not be
Masayuki Igawabbbaad62017-11-21 16:04:03 +090070 deleted unless the ``--delete-tempest-conf-objects`` flag is used to
Dustin Schoenbrund4d83462017-03-09 18:54:03 -050071 force their deletion.
David Patersonce781492014-09-18 01:07:01 -040072
Martin Kopec219e3222019-08-05 20:02:20 +000073.. note::
74
75 If during execution of ``tempest cleanup`` NotImplemented exception
76 occurres, ``tempest cleanup`` won't fail on that, it will be logged only.
77 NotImplemented errors are ignored because they are an outcome of some
78 extensions being disabled and ``tempest cleanup`` is not checking their
79 availability as it tries to clean up as much as possible without any
80 complicated logic.
81
David Patersonce781492014-09-18 01:07:01 -040082"""
David Patersonce781492014-09-18 01:07:01 -040083import sys
Marc Kodererf3397942015-12-21 11:17:09 +010084import traceback
David Patersonce781492014-09-18 01:07:01 -040085
David Paterson07661de2015-10-29 20:15:04 -070086from cliff import command
Doug Hellmann583ce2c2015-03-11 14:55:46 +000087from oslo_log import log as logging
Matthew Treinish21905512015-07-13 10:33:35 -040088from oslo_serialization import jsonutils as json
Doug Hellmann583ce2c2015-03-11 14:55:46 +000089
David Patersonce781492014-09-18 01:07:01 -040090from tempest import clients
91from tempest.cmd import cleanup_service
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010092from tempest.common import credentials_factory as credentials
Ken'ichi Ohmichi6ea3f982015-11-09 12:41:13 +000093from tempest.common import identity
David Patersonce781492014-09-18 01:07:01 -040094from tempest import config
Martin Kopec219e3222019-08-05 20:02:20 +000095from tempest.lib import exceptions
David Patersonce781492014-09-18 01:07:01 -040096
97SAVED_STATE_JSON = "saved_state.json"
98DRY_RUN_JSON = "dry_run.json"
99LOG = logging.getLogger(__name__)
100CONF = config.CONF
101
102
David Paterson07661de2015-10-29 20:15:04 -0700103class TempestCleanup(command.Command):
David Patersonce781492014-09-18 01:07:01 -0400104
Martin Kopec97857942019-06-12 15:23:21 +0000105 GOT_EXCEPTIONS = []
106
David Paterson07661de2015-10-29 20:15:04 -0700107 def take_action(self, parsed_args):
Marc Kodererf3397942015-12-21 11:17:09 +0100108 try:
109 self.init(parsed_args)
Ghanshyam41711d32016-02-17 17:11:22 +0900110 if not parsed_args.init_saved_state:
111 self._cleanup()
Marc Kodererf3397942015-12-21 11:17:09 +0100112 except Exception:
113 LOG.exception("Failure during cleanup")
114 traceback.print_exc()
115 raise
Martin Kopec219e3222019-08-05 20:02:20 +0000116 # ignore NotImplemented errors as those are an outcome of some
117 # extensions being disabled and cleanup is not checking their
118 # availability as it tries to clean up as much as possible without
119 # any complicated logic
120 critical_exceptions = [ex for ex in self.GOT_EXCEPTIONS if
121 not isinstance(ex, exceptions.NotImplemented)]
122 if critical_exceptions:
Martin Kopec97857942019-06-12 15:23:21 +0000123 raise Exception(self.GOT_EXCEPTIONS)
Marc Kodererf3397942015-12-21 11:17:09 +0100124
125 def init(self, parsed_args):
David Paterson07661de2015-10-29 20:15:04 -0700126 cleanup_service.init_conf()
127 self.options = parsed_args
ghanshyam009a1f62017-08-08 10:22:57 +0300128 self.admin_mgr = clients.Manager(
129 credentials.get_configured_admin_credentials())
David Patersonce781492014-09-18 01:07:01 -0400130 self.dry_run_data = {}
131 self.json_data = {}
David Patersonce781492014-09-18 01:07:01 -0400132
133 self.admin_id = ""
134 self.admin_role_id = ""
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200135 self.admin_project_id = ""
David Patersonce781492014-09-18 01:07:01 -0400136 self._init_admin_ids()
137
David Patersonce781492014-09-18 01:07:01 -0400138 # available services
Martin Kopec56c0b2b2019-11-13 12:50:07 +0000139 self.project_associated_services = (
140 cleanup_service.get_project_associated_cleanup_services())
141 self.resource_cleanup_services = (
142 cleanup_service.get_resource_cleanup_services())
David Patersonce781492014-09-18 01:07:01 -0400143 self.global_services = cleanup_service.get_global_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -0400144
David Paterson07661de2015-10-29 20:15:04 -0700145 if parsed_args.init_saved_state:
David Patersonce781492014-09-18 01:07:01 -0400146 self._init_state()
147 return
148
149 self._load_json()
David Patersonce781492014-09-18 01:07:01 -0400150
151 def _cleanup(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800152 print("Begin cleanup")
David Patersonce781492014-09-18 01:07:01 -0400153 is_dry_run = self.options.dry_run
David Patersond6babc52014-10-14 00:11:56 -0400154 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400155 is_save_state = False
156
157 if is_dry_run:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200158 self.dry_run_data["_projects_to_clean"] = {}
David Patersonce781492014-09-18 01:07:01 -0400159
160 admin_mgr = self.admin_mgr
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200161 # Always cleanup tempest and alt tempest projects unless
David Patersonce781492014-09-18 01:07:01 -0400162 # they are in saved state json. Therefore is_preserve is False
163 kwargs = {'data': self.dry_run_data,
164 'is_dry_run': is_dry_run,
165 'saved_state_json': self.json_data,
166 'is_preserve': False,
167 'is_save_state': is_save_state}
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200168 project_service = cleanup_service.ProjectService(admin_mgr, **kwargs)
169 projects = project_service.list()
170 print("Process %s projects" % len(projects))
David Patersonce781492014-09-18 01:07:01 -0400171
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200172 # Loop through list of projects and clean them up.
173 for project in projects:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200174 self._clean_project(project)
David Patersonce781492014-09-18 01:07:01 -0400175
176 kwargs = {'data': self.dry_run_data,
177 'is_dry_run': is_dry_run,
178 'saved_state_json': self.json_data,
179 'is_preserve': is_preserve,
Martin Kopec97857942019-06-12 15:23:21 +0000180 'is_save_state': is_save_state,
181 'got_exceptions': self.GOT_EXCEPTIONS}
David Patersonce781492014-09-18 01:07:01 -0400182 for service in self.global_services:
183 svc = service(admin_mgr, **kwargs)
184 svc.run()
185
Martin Kopec56c0b2b2019-11-13 12:50:07 +0000186 for service in self.resource_cleanup_services:
187 svc = service(self.admin_mgr, **kwargs)
188 svc.run()
189
David Patersonce781492014-09-18 01:07:01 -0400190 if is_dry_run:
zhang.leia4b1cef2016-03-01 10:50:01 +0800191 with open(DRY_RUN_JSON, 'w+') as f:
192 f.write(json.dumps(self.dry_run_data, sort_keys=True,
193 indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400194
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200195 def _clean_project(self, project):
196 print("Cleaning project: %s " % project['name'])
David Patersonce781492014-09-18 01:07:01 -0400197 is_dry_run = self.options.dry_run
198 dry_run_data = self.dry_run_data
David Patersond6babc52014-10-14 00:11:56 -0400199 is_preserve = not self.options.delete_tempest_conf_objects
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200200 project_id = project['id']
201 project_name = project['name']
202 project_data = None
David Patersonce781492014-09-18 01:07:01 -0400203 if is_dry_run:
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200204 project_data = dry_run_data["_projects_to_clean"][project_id] = {}
205 project_data['name'] = project_name
David Patersonce781492014-09-18 01:07:01 -0400206
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200207 kwargs = {'data': project_data,
David Patersonce781492014-09-18 01:07:01 -0400208 'is_dry_run': is_dry_run,
Martin Kopec5a884bf2019-02-11 18:10:55 +0000209 'saved_state_json': self.json_data,
David Patersonce781492014-09-18 01:07:01 -0400210 'is_preserve': is_preserve,
211 'is_save_state': False,
Martin Kopec97857942019-06-12 15:23:21 +0000212 'project_id': project_id,
213 'got_exceptions': self.GOT_EXCEPTIONS}
Martin Kopec56c0b2b2019-11-13 12:50:07 +0000214 for service in self.project_associated_services:
Martin Kopec7ca86022019-10-04 14:13:59 +0000215 svc = service(self.admin_mgr, **kwargs)
David Patersonce781492014-09-18 01:07:01 -0400216 svc.run()
217
218 def _init_admin_ids(self):
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200219 pr_cl = self.admin_mgr.projects_client
220 rl_cl = self.admin_mgr.roles_v3_client
221 rla_cl = self.admin_mgr.role_assignments_client
222 us_cl = self.admin_mgr.users_v3_client
David Patersonce781492014-09-18 01:07:01 -0400223
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200224 project = identity.get_project_by_name(pr_cl,
225 CONF.auth.admin_project_name)
226 self.admin_project_id = project['id']
227 user = identity.get_user_by_project(us_cl, rla_cl,
228 self.admin_project_id,
229 CONF.auth.admin_username)
David Patersonce781492014-09-18 01:07:01 -0400230 self.admin_id = user['id']
231
Daniel Mellado6b16b922015-12-07 12:43:08 +0000232 roles = rl_cl.list_roles()['roles']
David Patersonce781492014-09-18 01:07:01 -0400233 for role in roles:
234 if role['name'] == CONF.identity.admin_role:
235 self.admin_role_id = role['id']
236 break
237
David Paterson07661de2015-10-29 20:15:04 -0700238 def get_parser(self, prog_name):
239 parser = super(TempestCleanup, self).get_parser(prog_name)
David Patersonce781492014-09-18 01:07:01 -0400240 parser.add_argument('--init-saved-state', action="store_true",
241 dest='init_saved_state', default=False,
242 help="Creates JSON file: " + SAVED_STATE_JSON +
243 ", representing the current state of your "
David Patersond6babc52014-10-14 00:11:56 -0400244 "deployment, specifically object types "
245 "tempest creates and destroys during a run. "
David Patersonce781492014-09-18 01:07:01 -0400246 "You must run with this flag prior to "
David Patersond6babc52014-10-14 00:11:56 -0400247 "executing cleanup in normal mode, which is with "
248 "no arguments.")
David Patersonce781492014-09-18 01:07:01 -0400249 parser.add_argument('--delete-tempest-conf-objects',
David Patersond6babc52014-10-14 00:11:56 -0400250 action="store_true",
251 dest='delete_tempest_conf_objects',
David Patersonce781492014-09-18 01:07:01 -0400252 default=False,
David Patersond6babc52014-10-14 00:11:56 -0400253 help="Force deletion of the tempest and "
Arx Cruz05fe4bc2017-10-20 10:48:28 +0200254 "alternate tempest users and projects.")
David Patersonce781492014-09-18 01:07:01 -0400255 parser.add_argument('--dry-run', action="store_true",
256 dest='dry_run', default=False,
257 help="Generate JSON file:" + DRY_RUN_JSON +
258 ", that reports the objects that would have "
259 "been deleted had a full cleanup been run.")
David Paterson07661de2015-10-29 20:15:04 -0700260 return parser
David Patersonce781492014-09-18 01:07:01 -0400261
David Paterson07661de2015-10-29 20:15:04 -0700262 def get_description(self):
263 return 'Cleanup after tempest run'
David Patersonce781492014-09-18 01:07:01 -0400264
David Patersonce781492014-09-18 01:07:01 -0400265 def _init_state(self):
yuyafeie2dbc1f2016-07-06 16:09:19 +0800266 print("Initializing saved state.")
David Patersonce781492014-09-18 01:07:01 -0400267 data = {}
268 admin_mgr = self.admin_mgr
269 kwargs = {'data': data,
270 'is_dry_run': False,
271 'saved_state_json': data,
272 'is_preserve': False,
Martin Kopec97857942019-06-12 15:23:21 +0000273 'is_save_state': True,
274 'got_exceptions': self.GOT_EXCEPTIONS}
David Patersonce781492014-09-18 01:07:01 -0400275 for service in self.global_services:
276 svc = service(admin_mgr, **kwargs)
277 svc.run()
278
Martin Kopec56c0b2b2019-11-13 12:50:07 +0000279 for service in self.project_associated_services:
280 svc = service(admin_mgr, **kwargs)
281 svc.run()
282
283 for service in self.resource_cleanup_services:
Martin Kopec5a884bf2019-02-11 18:10:55 +0000284 svc = service(admin_mgr, **kwargs)
285 svc.run()
286
zhang.leia4b1cef2016-03-01 10:50:01 +0800287 with open(SAVED_STATE_JSON, 'w+') as f:
afazekas40fcb9b2019-03-08 11:25:11 +0100288 f.write(json.dumps(data, sort_keys=True,
289 indent=2, separators=(',', ': ')))
David Patersonce781492014-09-18 01:07:01 -0400290
Martin Kopec6caf3fa2019-01-20 15:24:10 +0000291 def _load_json(self, saved_state_json=SAVED_STATE_JSON):
David Patersonce781492014-09-18 01:07:01 -0400292 try:
Martin Kopec6caf3fa2019-01-20 15:24:10 +0000293 with open(saved_state_json, 'rb') as json_file:
zhang.leia4b1cef2016-03-01 10:50:01 +0800294 self.json_data = json.load(json_file)
295
David Patersonce781492014-09-18 01:07:01 -0400296 except IOError as ex:
297 LOG.exception("Failed loading saved state, please be sure you"
298 " have first run cleanup with --init-saved-state "
Jordan Pittier525ec712016-12-07 17:51:26 +0100299 "flag prior to running tempest. Exception: %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400300 sys.exit(ex)
301 except Exception as ex:
Jordan Pittier525ec712016-12-07 17:51:26 +0100302 LOG.exception("Exception parsing saved state json : %s", ex)
David Patersonce781492014-09-18 01:07:01 -0400303 sys.exit(ex)