blob: 9ae3dfbfaa389f16103b69ec32e0522cce064235 [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.
15# @author: David Paterson
16
17"""
18Utility for cleaning up environment after Tempest run
19
20Runtime Arguments
21-----------------
22
23--init-saved-state: Before you can execute cleanup you must initialize
24the saved state by running it with the --init-saved-state flag
25(creating ./saved_state.json), which protects your deployment from
26cleanup deleting objects you want to keep. Typically you would run
27cleanup with --init-saved-state prior to a tempest run. If this is not
28the case saved_state.json must be edited, removing objects you want
29cleanup to delete.
30
31--dry-run: Creates a report (dry_run.json) of the tenants that will be
32cleaned up (in the "_tenants_to_clean" array), and the global objects
33that will be removed (tenants, users, flavors and images). Once
34cleanup is executed in normal mode, running it again with --dry-run
35should yield an empty report.
36
37**NOTE**: The _tenants_to_clean array in dry-run.json lists the
38tenants that cleanup will loop through and delete child objects, not
39delete the tenant itself. This may differ from the tenants array as you
40can clean the tempest and alternate tempest tenants but not delete the
41tenants themselves. This is actually the default behavior.
42
43**Normal mode**: running with no arguments, will query your deployment and
44build a list of objects to delete after filtering out out the objects
45found in saved_state.json and based on the
46--preserve-tempest-conf-objects and
47--delete-tempest-conf-objects flags.
48
49By default the tempest and alternate tempest users and tenants are not
50deleted and the admin user specified in tempest.conf is never deleted.
51
52Please run with --help to see full list of options.
53"""
54import argparse
55import json
56import sys
57
58from tempest import auth
59from tempest import clients
60from tempest.cmd import cleanup_service
61from tempest import config
62from tempest.openstack.common import log as logging
63
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()
88 cleanup_service.init_conf()
89
90 def run(self):
91 opts = self.options
92 if opts.init_saved_state:
93 self._init_state()
94 return
95
96 self._load_json()
97 self._cleanup()
98
99 def _cleanup(self):
100 LOG.debug("Begin cleanup")
101 is_dry_run = self.options.dry_run
102 is_preserve = self.options.preserve_tempest_conf_objects
103 is_save_state = False
104
105 if is_dry_run:
106 self.dry_run_data["_tenants_to_clean"] = {}
107 f = open(DRY_RUN_JSON, 'w+')
108
109 admin_mgr = self.admin_mgr
110 # Always cleanup tempest and alt tempest tenants unless
111 # they are in saved state json. Therefore is_preserve is False
112 kwargs = {'data': self.dry_run_data,
113 'is_dry_run': is_dry_run,
114 'saved_state_json': self.json_data,
115 'is_preserve': False,
116 'is_save_state': is_save_state}
117 tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
118 tenants = tenant_service.list()
119 LOG.debug("Process %s tenants" % len(tenants))
120
121 # Loop through list of tenants and clean them up.
122 for tenant in tenants:
123 self._add_admin(tenant['id'])
124 self._clean_tenant(tenant)
125
126 kwargs = {'data': self.dry_run_data,
127 'is_dry_run': is_dry_run,
128 'saved_state_json': self.json_data,
129 'is_preserve': is_preserve,
130 'is_save_state': is_save_state}
131 for service in self.global_services:
132 svc = service(admin_mgr, **kwargs)
133 svc.run()
134
135 if is_dry_run:
136 f.write(json.dumps(self.dry_run_data, sort_keys=True,
137 indent=2, separators=(',', ': ')))
138 f.close()
139
140 self._remove_admin_user_roles()
141
142 def _remove_admin_user_roles(self):
143 tenant_ids = self.admin_role_added
144 LOG.debug("Removing admin user roles where needed for tenants: %s"
145 % tenant_ids)
146 for tenant_id in tenant_ids:
147 self._remove_admin_role(tenant_id)
148
149 def _clean_tenant(self, tenant):
150 LOG.debug("Cleaning tenant: %s " % tenant['name'])
151 is_dry_run = self.options.dry_run
152 dry_run_data = self.dry_run_data
153 is_preserve = self.options.preserve_tempest_conf_objects
154 tenant_id = tenant['id']
155 tenant_name = tenant['name']
156 tenant_data = None
157 if is_dry_run:
158 tenant_data = dry_run_data["_tenants_to_clean"][tenant_id] = {}
159 tenant_data['name'] = tenant_name
160
161 kwargs = {"username": CONF.identity.admin_username,
162 "password": CONF.identity.admin_password,
163 "tenant_name": tenant['name']}
164 mgr = clients.Manager(credentials=auth.get_credentials(**kwargs))
165 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
185 _, roles = id_cl.list_roles()
186 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 "
198 "deployment, specifically objects types "
199 "Tempest creates and destroys during a run. "
200 "You must run with this flag prior to "
201 "executing cleanup.")
202 parser.add_argument('--preserve-tempest-conf-objects',
203 action="store_true",
204 dest='preserve_tempest_conf_objects',
205 default=True, help="Do not delete the "
206 "tempest and alternate tempest users and "
207 "tenants, so they may be used for future "
208 "tempest runs. By default this is argument "
209 "is true.")
210 parser.add_argument('--delete-tempest-conf-objects',
211 action="store_false",
212 dest='preserve_tempest_conf_objects',
213 default=False,
214 help="Delete the tempest and "
215 "alternate tempest users and tenants.")
216 parser.add_argument('--dry-run', action="store_true",
217 dest='dry_run', default=False,
218 help="Generate JSON file:" + DRY_RUN_JSON +
219 ", that reports the objects that would have "
220 "been deleted had a full cleanup been run.")
221
222 self.options = parser.parse_args()
223
224 def _add_admin(self, tenant_id):
225 id_cl = self.admin_mgr.identity_client
226 needs_role = True
227 _, roles = id_cl.list_user_roles(tenant_id, self.admin_id)
228 for role in roles:
229 if role['id'] == self.admin_role_id:
230 needs_role = False
231 LOG.debug("User already had admin privilege for this tenant")
232 if needs_role:
233 LOG.debug("Adding admin priviledge for : %s" % tenant_id)
234 id_cl.assign_user_role(tenant_id, self.admin_id,
235 self.admin_role_id)
236 self.admin_role_added.append(tenant_id)
237
238 def _remove_admin_role(self, tenant_id):
239 LOG.debug("Remove admin user role for tenant: %s" % tenant_id)
240 # Must initialize AdminManager for each user role
241 # Otherwise authentication exception is thrown, weird
242 id_cl = clients.AdminManager().identity_client
243 if (self._tenant_exists(tenant_id)):
244 try:
245 id_cl.remove_user_role(tenant_id, self.admin_id,
246 self.admin_role_id)
247 except Exception as ex:
248 LOG.exception("Failed removing role from tenant which still"
249 "exists, exception: %s" % ex)
250
251 def _tenant_exists(self, tenant_id):
252 id_cl = self.admin_mgr.identity_client
253 try:
254 t = id_cl.get_tenant(tenant_id)
255 LOG.debug("Tenant is: %s" % str(t))
256 return True
257 except Exception as ex:
258 LOG.debug("Tenant no longer exists? %s" % ex)
259 return False
260
261 def _init_state(self):
262 LOG.debug("Initializing saved state.")
263 data = {}
264 admin_mgr = self.admin_mgr
265 kwargs = {'data': data,
266 'is_dry_run': False,
267 'saved_state_json': data,
268 'is_preserve': False,
269 'is_save_state': True}
270 for service in self.global_services:
271 svc = service(admin_mgr, **kwargs)
272 svc.run()
273
274 f = open(SAVED_STATE_JSON, 'w+')
275 f.write(json.dumps(data,
276 sort_keys=True, indent=2, separators=(',', ': ')))
277 f.close()
278
279 def _load_json(self):
280 try:
281 json_file = open(SAVED_STATE_JSON)
282 self.json_data = json.load(json_file)
283 json_file.close()
284 except IOError as ex:
285 LOG.exception("Failed loading saved state, please be sure you"
286 " have first run cleanup with --init-saved-state "
287 "flag prior to running tempest. Exception: %s" % ex)
288 sys.exit(ex)
289 except Exception as ex:
290 LOG.exception("Exception parsing saved state json : %s" % ex)
291 sys.exit(ex)
292
293
294def main():
295 cleanup = Cleanup()
296 cleanup.run()
297 LOG.info('Cleanup finished!')
298 return 0
299
300if __name__ == "__main__":
301 sys.exit(main())