blob: ed6716e48861c00d9119b31905c198e702db8ea4 [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"""
17Utility for cleaning up environment after Tempest run
18
19Runtime Arguments
20-----------------
21
Joe H. Rahme8bd59e02014-12-19 13:53:50 +020022**--init-saved-state**: Before you can execute cleanup you must initialize
23the saved state by running it with the **--init-saved-state** flag
David Patersonce781492014-09-18 01:07:01 -040024(creating ./saved_state.json), which protects your deployment from
25cleanup deleting objects you want to keep. Typically you would run
Joe H. Rahme8bd59e02014-12-19 13:53:50 +020026cleanup with **--init-saved-state** prior to a tempest run. If this is not
David Patersonce781492014-09-18 01:07:01 -040027the case saved_state.json must be edited, removing objects you want
28cleanup to delete.
29
Joe H. Rahme8bd59e02014-12-19 13:53:50 +020030**--dry-run**: Creates a report (dry_run.json) of the tenants that will be
David Patersonce781492014-09-18 01:07:01 -040031cleaned up (in the "_tenants_to_clean" array), and the global objects
32that will be removed (tenants, users, flavors and images). Once
Joe H. Rahme8bd59e02014-12-19 13:53:50 +020033cleanup is executed in normal mode, running it again with **--dry-run**
David Patersonce781492014-09-18 01:07:01 -040034should yield an empty report.
35
36**NOTE**: The _tenants_to_clean array in dry-run.json lists the
37tenants that cleanup will loop through and delete child objects, not
38delete the tenant itself. This may differ from the tenants array as you
David Patersond6babc52014-10-14 00:11:56 -040039can clean the tempest and alternate tempest tenants but by default,
40cleanup deletes the objects in the tempest and alternate tempest tenants
Joe H. Rahme8bd59e02014-12-19 13:53:50 +020041but does not delete those tenants unless the **--delete-tempest-conf-objects**
David Patersond6babc52014-10-14 00:11:56 -040042flag is used to force their deletion.
David Patersonce781492014-09-18 01:07:01 -040043
44**Normal mode**: running with no arguments, will query your deployment and
David Patersond6babc52014-10-14 00:11:56 -040045build a list of objects to delete after filtering out the objects found in
Joe H. Rahme8bd59e02014-12-19 13:53:50 +020046saved_state.json and based on the **--delete-tempest-conf-objects** flag.
David Patersonce781492014-09-18 01:07:01 -040047
48By default the tempest and alternate tempest users and tenants are not
49deleted and the admin user specified in tempest.conf is never deleted.
50
Joe H. Rahme8bd59e02014-12-19 13:53:50 +020051Please run with **--help** to see full list of options.
David Patersonce781492014-09-18 01:07:01 -040052"""
53import argparse
54import json
55import sys
56
Doug Hellmann583ce2c2015-03-11 14:55:46 +000057from oslo_log import log as logging
58
David Patersonce781492014-09-18 01:07:01 -040059from tempest import clients
60from tempest.cmd import cleanup_service
Andrea Frittoli878d5ab2015-01-30 13:22:50 +000061from tempest.common import cred_provider
David Patersonce781492014-09-18 01:07:01 -040062from tempest import config
David Patersonce781492014-09-18 01:07:01 -040063
64SAVED_STATE_JSON = "saved_state.json"
65DRY_RUN_JSON = "dry_run.json"
66LOG = logging.getLogger(__name__)
67CONF = config.CONF
68
69
70class Cleanup(object):
71
72 def __init__(self):
73 self.admin_mgr = clients.AdminManager()
74 self.dry_run_data = {}
75 self.json_data = {}
76 self._init_options()
77
78 self.admin_id = ""
79 self.admin_role_id = ""
80 self.admin_tenant_id = ""
81 self._init_admin_ids()
82
83 self.admin_role_added = []
84
85 # available services
86 self.tenant_services = cleanup_service.get_tenant_cleanup_services()
87 self.global_services = cleanup_service.get_global_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -040088
89 def run(self):
90 opts = self.options
91 if opts.init_saved_state:
92 self._init_state()
93 return
94
95 self._load_json()
96 self._cleanup()
97
98 def _cleanup(self):
99 LOG.debug("Begin cleanup")
100 is_dry_run = self.options.dry_run
David Patersond6babc52014-10-14 00:11:56 -0400101 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400102 is_save_state = False
103
104 if is_dry_run:
105 self.dry_run_data["_tenants_to_clean"] = {}
106 f = open(DRY_RUN_JSON, 'w+')
107
108 admin_mgr = self.admin_mgr
109 # Always cleanup tempest and alt tempest tenants unless
110 # they are in saved state json. Therefore is_preserve is False
111 kwargs = {'data': self.dry_run_data,
112 'is_dry_run': is_dry_run,
113 'saved_state_json': self.json_data,
114 'is_preserve': False,
115 'is_save_state': is_save_state}
116 tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
117 tenants = tenant_service.list()
118 LOG.debug("Process %s tenants" % len(tenants))
119
120 # Loop through list of tenants and clean them up.
121 for tenant in tenants:
122 self._add_admin(tenant['id'])
123 self._clean_tenant(tenant)
124
125 kwargs = {'data': self.dry_run_data,
126 'is_dry_run': is_dry_run,
127 'saved_state_json': self.json_data,
128 'is_preserve': is_preserve,
129 'is_save_state': is_save_state}
130 for service in self.global_services:
131 svc = service(admin_mgr, **kwargs)
132 svc.run()
133
134 if is_dry_run:
135 f.write(json.dumps(self.dry_run_data, sort_keys=True,
136 indent=2, separators=(',', ': ')))
137 f.close()
138
139 self._remove_admin_user_roles()
140
141 def _remove_admin_user_roles(self):
142 tenant_ids = self.admin_role_added
143 LOG.debug("Removing admin user roles where needed for tenants: %s"
144 % tenant_ids)
145 for tenant_id in tenant_ids:
146 self._remove_admin_role(tenant_id)
147
148 def _clean_tenant(self, tenant):
149 LOG.debug("Cleaning tenant: %s " % tenant['name'])
150 is_dry_run = self.options.dry_run
151 dry_run_data = self.dry_run_data
David Patersond6babc52014-10-14 00:11:56 -0400152 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400153 tenant_id = tenant['id']
154 tenant_name = tenant['name']
155 tenant_data = None
156 if is_dry_run:
157 tenant_data = dry_run_data["_tenants_to_clean"][tenant_id] = {}
158 tenant_data['name'] = tenant_name
159
160 kwargs = {"username": CONF.identity.admin_username,
161 "password": CONF.identity.admin_password,
162 "tenant_name": tenant['name']}
Andrea Frittoli878d5ab2015-01-30 13:22:50 +0000163 mgr = clients.Manager(credentials=cred_provider.get_credentials(
164 **kwargs))
David Patersonce781492014-09-18 01:07:01 -0400165 kwargs = {'data': tenant_data,
166 'is_dry_run': is_dry_run,
167 'saved_state_json': None,
168 'is_preserve': is_preserve,
169 'is_save_state': False,
170 'tenant_id': tenant_id}
171 for service in self.tenant_services:
172 svc = service(mgr, **kwargs)
173 svc.run()
174
175 def _init_admin_ids(self):
176 id_cl = self.admin_mgr.identity_client
177
178 tenant = id_cl.get_tenant_by_name(CONF.identity.admin_tenant_name)
179 self.admin_tenant_id = tenant['id']
180
181 user = id_cl.get_user_by_username(self.admin_tenant_id,
182 CONF.identity.admin_username)
183 self.admin_id = user['id']
184
David Kranzb7afa922014-12-30 10:56:26 -0500185 roles = id_cl.list_roles()
David Patersonce781492014-09-18 01:07:01 -0400186 for role in roles:
187 if role['name'] == CONF.identity.admin_role:
188 self.admin_role_id = role['id']
189 break
190
191 def _init_options(self):
192 parser = argparse.ArgumentParser(
193 description='Cleanup after tempest run')
194 parser.add_argument('--init-saved-state', action="store_true",
195 dest='init_saved_state', default=False,
196 help="Creates JSON file: " + SAVED_STATE_JSON +
197 ", representing the current state of your "
David Patersond6babc52014-10-14 00:11:56 -0400198 "deployment, specifically object types "
199 "tempest creates and destroys during a run. "
David Patersonce781492014-09-18 01:07:01 -0400200 "You must run with this flag prior to "
David Patersond6babc52014-10-14 00:11:56 -0400201 "executing cleanup in normal mode, which is with "
202 "no arguments.")
David Patersonce781492014-09-18 01:07:01 -0400203 parser.add_argument('--delete-tempest-conf-objects',
David Patersond6babc52014-10-14 00:11:56 -0400204 action="store_true",
205 dest='delete_tempest_conf_objects',
David Patersonce781492014-09-18 01:07:01 -0400206 default=False,
David Patersond6babc52014-10-14 00:11:56 -0400207 help="Force deletion of the tempest and "
David Patersonce781492014-09-18 01:07:01 -0400208 "alternate tempest users and tenants.")
209 parser.add_argument('--dry-run', action="store_true",
210 dest='dry_run', default=False,
211 help="Generate JSON file:" + DRY_RUN_JSON +
212 ", that reports the objects that would have "
213 "been deleted had a full cleanup been run.")
214
215 self.options = parser.parse_args()
216
217 def _add_admin(self, tenant_id):
218 id_cl = self.admin_mgr.identity_client
219 needs_role = True
David Kranzb7afa922014-12-30 10:56:26 -0500220 roles = id_cl.list_user_roles(tenant_id, self.admin_id)
David Patersonce781492014-09-18 01:07:01 -0400221 for role in roles:
222 if role['id'] == self.admin_role_id:
223 needs_role = False
224 LOG.debug("User already had admin privilege for this tenant")
225 if needs_role:
Tingting Bao2b513342015-02-15 22:54:55 -0800226 LOG.debug("Adding admin privilege for : %s" % tenant_id)
David Patersonce781492014-09-18 01:07:01 -0400227 id_cl.assign_user_role(tenant_id, self.admin_id,
228 self.admin_role_id)
229 self.admin_role_added.append(tenant_id)
230
231 def _remove_admin_role(self, tenant_id):
232 LOG.debug("Remove admin user role for tenant: %s" % tenant_id)
233 # Must initialize AdminManager for each user role
234 # Otherwise authentication exception is thrown, weird
235 id_cl = clients.AdminManager().identity_client
236 if (self._tenant_exists(tenant_id)):
237 try:
238 id_cl.remove_user_role(tenant_id, self.admin_id,
239 self.admin_role_id)
240 except Exception as ex:
241 LOG.exception("Failed removing role from tenant which still"
242 "exists, exception: %s" % ex)
243
244 def _tenant_exists(self, tenant_id):
245 id_cl = self.admin_mgr.identity_client
246 try:
247 t = id_cl.get_tenant(tenant_id)
248 LOG.debug("Tenant is: %s" % str(t))
249 return True
250 except Exception as ex:
251 LOG.debug("Tenant no longer exists? %s" % ex)
252 return False
253
254 def _init_state(self):
255 LOG.debug("Initializing saved state.")
256 data = {}
257 admin_mgr = self.admin_mgr
258 kwargs = {'data': data,
259 'is_dry_run': False,
260 'saved_state_json': data,
261 'is_preserve': False,
262 'is_save_state': True}
263 for service in self.global_services:
264 svc = service(admin_mgr, **kwargs)
265 svc.run()
266
267 f = open(SAVED_STATE_JSON, 'w+')
268 f.write(json.dumps(data,
269 sort_keys=True, indent=2, separators=(',', ': ')))
270 f.close()
271
272 def _load_json(self):
273 try:
274 json_file = open(SAVED_STATE_JSON)
275 self.json_data = json.load(json_file)
276 json_file.close()
277 except IOError as ex:
278 LOG.exception("Failed loading saved state, please be sure you"
279 " have first run cleanup with --init-saved-state "
280 "flag prior to running tempest. Exception: %s" % ex)
281 sys.exit(ex)
282 except Exception as ex:
283 LOG.exception("Exception parsing saved state json : %s" % ex)
284 sys.exit(ex)
285
286
287def main():
David Patersond6babc52014-10-14 00:11:56 -0400288 cleanup_service.init_conf()
David Patersonce781492014-09-18 01:07:01 -0400289 cleanup = Cleanup()
290 cleanup.run()
291 LOG.info('Cleanup finished!')
292 return 0
293
294if __name__ == "__main__":
295 sys.exit(main())