blob: a305e4251e40c024b007be2b2a7ef956da2ed514 [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
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# 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
22--init-saved-state: Before you can execute cleanup you must initialize
23the saved state by running it with the --init-saved-state flag
24(creating ./saved_state.json), which protects your deployment from
25cleanup deleting objects you want to keep. Typically you would run
26cleanup with --init-saved-state prior to a tempest run. If this is not
27the case saved_state.json must be edited, removing objects you want
28cleanup to delete.
29
30--dry-run: Creates a report (dry_run.json) of the tenants that will be
31cleaned up (in the "_tenants_to_clean" array), and the global objects
32that will be removed (tenants, users, flavors and images). Once
33cleanup is executed in normal mode, running it again with --dry-run
34should 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
39can clean the tempest and alternate tempest tenants but not delete the
40tenants themselves. This is actually the default behavior.
41
42**Normal mode**: running with no arguments, will query your deployment and
43build a list of objects to delete after filtering out out the objects
44found in saved_state.json and based on the
45--preserve-tempest-conf-objects and
46--delete-tempest-conf-objects flags.
47
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
51Please run with --help to see full list of options.
52"""
53import argparse
54import json
55import sys
56
57from tempest import auth
58from tempest import clients
59from tempest.cmd import cleanup_service
60from tempest import config
61from tempest.openstack.common import log as logging
62
63SAVED_STATE_JSON = "saved_state.json"
64DRY_RUN_JSON = "dry_run.json"
65LOG = logging.getLogger(__name__)
66CONF = config.CONF
67
68
69class Cleanup(object):
70
71 def __init__(self):
72 self.admin_mgr = clients.AdminManager()
73 self.dry_run_data = {}
74 self.json_data = {}
75 self._init_options()
76
77 self.admin_id = ""
78 self.admin_role_id = ""
79 self.admin_tenant_id = ""
80 self._init_admin_ids()
81
82 self.admin_role_added = []
83
84 # available services
85 self.tenant_services = cleanup_service.get_tenant_cleanup_services()
86 self.global_services = cleanup_service.get_global_cleanup_services()
87 cleanup_service.init_conf()
88
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
101 is_preserve = self.options.preserve_tempest_conf_objects
102 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
152 is_preserve = self.options.preserve_tempest_conf_objects
153 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']}
163 mgr = clients.Manager(credentials=auth.get_credentials(**kwargs))
164 kwargs = {'data': tenant_data,
165 'is_dry_run': is_dry_run,
166 'saved_state_json': None,
167 'is_preserve': is_preserve,
168 'is_save_state': False,
169 'tenant_id': tenant_id}
170 for service in self.tenant_services:
171 svc = service(mgr, **kwargs)
172 svc.run()
173
174 def _init_admin_ids(self):
175 id_cl = self.admin_mgr.identity_client
176
177 tenant = id_cl.get_tenant_by_name(CONF.identity.admin_tenant_name)
178 self.admin_tenant_id = tenant['id']
179
180 user = id_cl.get_user_by_username(self.admin_tenant_id,
181 CONF.identity.admin_username)
182 self.admin_id = user['id']
183
184 _, roles = id_cl.list_roles()
185 for role in roles:
186 if role['name'] == CONF.identity.admin_role:
187 self.admin_role_id = role['id']
188 break
189
190 def _init_options(self):
191 parser = argparse.ArgumentParser(
192 description='Cleanup after tempest run')
193 parser.add_argument('--init-saved-state', action="store_true",
194 dest='init_saved_state', default=False,
195 help="Creates JSON file: " + SAVED_STATE_JSON +
196 ", representing the current state of your "
197 "deployment, specifically objects types "
198 "Tempest creates and destroys during a run. "
199 "You must run with this flag prior to "
200 "executing cleanup.")
201 parser.add_argument('--preserve-tempest-conf-objects',
202 action="store_true",
203 dest='preserve_tempest_conf_objects',
204 default=True, help="Do not delete the "
205 "tempest and alternate tempest users and "
206 "tenants, so they may be used for future "
207 "tempest runs. By default this is argument "
208 "is true.")
209 parser.add_argument('--delete-tempest-conf-objects',
210 action="store_false",
211 dest='preserve_tempest_conf_objects',
212 default=False,
213 help="Delete the tempest and "
214 "alternate tempest users and tenants.")
215 parser.add_argument('--dry-run', action="store_true",
216 dest='dry_run', default=False,
217 help="Generate JSON file:" + DRY_RUN_JSON +
218 ", that reports the objects that would have "
219 "been deleted had a full cleanup been run.")
220
221 self.options = parser.parse_args()
222
223 def _add_admin(self, tenant_id):
224 id_cl = self.admin_mgr.identity_client
225 needs_role = True
226 _, roles = id_cl.list_user_roles(tenant_id, self.admin_id)
227 for role in roles:
228 if role['id'] == self.admin_role_id:
229 needs_role = False
230 LOG.debug("User already had admin privilege for this tenant")
231 if needs_role:
232 LOG.debug("Adding admin priviledge for : %s" % tenant_id)
233 id_cl.assign_user_role(tenant_id, self.admin_id,
234 self.admin_role_id)
235 self.admin_role_added.append(tenant_id)
236
237 def _remove_admin_role(self, tenant_id):
238 LOG.debug("Remove admin user role for tenant: %s" % tenant_id)
239 # Must initialize AdminManager for each user role
240 # Otherwise authentication exception is thrown, weird
241 id_cl = clients.AdminManager().identity_client
242 if (self._tenant_exists(tenant_id)):
243 try:
244 id_cl.remove_user_role(tenant_id, self.admin_id,
245 self.admin_role_id)
246 except Exception as ex:
247 LOG.exception("Failed removing role from tenant which still"
248 "exists, exception: %s" % ex)
249
250 def _tenant_exists(self, tenant_id):
251 id_cl = self.admin_mgr.identity_client
252 try:
253 t = id_cl.get_tenant(tenant_id)
254 LOG.debug("Tenant is: %s" % str(t))
255 return True
256 except Exception as ex:
257 LOG.debug("Tenant no longer exists? %s" % ex)
258 return False
259
260 def _init_state(self):
261 LOG.debug("Initializing saved state.")
262 data = {}
263 admin_mgr = self.admin_mgr
264 kwargs = {'data': data,
265 'is_dry_run': False,
266 'saved_state_json': data,
267 'is_preserve': False,
268 'is_save_state': True}
269 for service in self.global_services:
270 svc = service(admin_mgr, **kwargs)
271 svc.run()
272
273 f = open(SAVED_STATE_JSON, 'w+')
274 f.write(json.dumps(data,
275 sort_keys=True, indent=2, separators=(',', ': ')))
276 f.close()
277
278 def _load_json(self):
279 try:
280 json_file = open(SAVED_STATE_JSON)
281 self.json_data = json.load(json_file)
282 json_file.close()
283 except IOError as ex:
284 LOG.exception("Failed loading saved state, please be sure you"
285 " have first run cleanup with --init-saved-state "
286 "flag prior to running tempest. Exception: %s" % ex)
287 sys.exit(ex)
288 except Exception as ex:
289 LOG.exception("Exception parsing saved state json : %s" % ex)
290 sys.exit(ex)
291
292
293def main():
294 cleanup = Cleanup()
295 cleanup.run()
296 LOG.info('Cleanup finished!')
297 return 0
298
299if __name__ == "__main__":
300 sys.exit(main())