blob: c52704a881b47eed7d35d6132c41c31842aaec26 [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
David Patersonce781492014-09-18 01:07:01 -040057from tempest import clients
58from tempest.cmd import cleanup_service
Andrea Frittoli878d5ab2015-01-30 13:22:50 +000059from tempest.common import cred_provider
David Patersonce781492014-09-18 01:07:01 -040060from 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()
David Patersonce781492014-09-18 01:07:01 -040087
88 def run(self):
89 opts = self.options
90 if opts.init_saved_state:
91 self._init_state()
92 return
93
94 self._load_json()
95 self._cleanup()
96
97 def _cleanup(self):
98 LOG.debug("Begin cleanup")
99 is_dry_run = self.options.dry_run
David Patersond6babc52014-10-14 00:11:56 -0400100 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400101 is_save_state = False
102
103 if is_dry_run:
104 self.dry_run_data["_tenants_to_clean"] = {}
105 f = open(DRY_RUN_JSON, 'w+')
106
107 admin_mgr = self.admin_mgr
108 # Always cleanup tempest and alt tempest tenants unless
109 # they are in saved state json. Therefore is_preserve is False
110 kwargs = {'data': self.dry_run_data,
111 'is_dry_run': is_dry_run,
112 'saved_state_json': self.json_data,
113 'is_preserve': False,
114 'is_save_state': is_save_state}
115 tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
116 tenants = tenant_service.list()
117 LOG.debug("Process %s tenants" % len(tenants))
118
119 # Loop through list of tenants and clean them up.
120 for tenant in tenants:
121 self._add_admin(tenant['id'])
122 self._clean_tenant(tenant)
123
124 kwargs = {'data': self.dry_run_data,
125 'is_dry_run': is_dry_run,
126 'saved_state_json': self.json_data,
127 'is_preserve': is_preserve,
128 'is_save_state': is_save_state}
129 for service in self.global_services:
130 svc = service(admin_mgr, **kwargs)
131 svc.run()
132
133 if is_dry_run:
134 f.write(json.dumps(self.dry_run_data, sort_keys=True,
135 indent=2, separators=(',', ': ')))
136 f.close()
137
138 self._remove_admin_user_roles()
139
140 def _remove_admin_user_roles(self):
141 tenant_ids = self.admin_role_added
142 LOG.debug("Removing admin user roles where needed for tenants: %s"
143 % tenant_ids)
144 for tenant_id in tenant_ids:
145 self._remove_admin_role(tenant_id)
146
147 def _clean_tenant(self, tenant):
148 LOG.debug("Cleaning tenant: %s " % tenant['name'])
149 is_dry_run = self.options.dry_run
150 dry_run_data = self.dry_run_data
David Patersond6babc52014-10-14 00:11:56 -0400151 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400152 tenant_id = tenant['id']
153 tenant_name = tenant['name']
154 tenant_data = None
155 if is_dry_run:
156 tenant_data = dry_run_data["_tenants_to_clean"][tenant_id] = {}
157 tenant_data['name'] = tenant_name
158
159 kwargs = {"username": CONF.identity.admin_username,
160 "password": CONF.identity.admin_password,
161 "tenant_name": tenant['name']}
Andrea Frittoli878d5ab2015-01-30 13:22:50 +0000162 mgr = clients.Manager(credentials=cred_provider.get_credentials(
163 **kwargs))
David Patersonce781492014-09-18 01:07:01 -0400164 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
David Kranzb7afa922014-12-30 10:56:26 -0500184 roles = id_cl.list_roles()
David Patersonce781492014-09-18 01:07:01 -0400185 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 "
David Patersond6babc52014-10-14 00:11:56 -0400197 "deployment, specifically object types "
198 "tempest creates and destroys during a run. "
David Patersonce781492014-09-18 01:07:01 -0400199 "You must run with this flag prior to "
David Patersond6babc52014-10-14 00:11:56 -0400200 "executing cleanup in normal mode, which is with "
201 "no arguments.")
David Patersonce781492014-09-18 01:07:01 -0400202 parser.add_argument('--delete-tempest-conf-objects',
David Patersond6babc52014-10-14 00:11:56 -0400203 action="store_true",
204 dest='delete_tempest_conf_objects',
David Patersonce781492014-09-18 01:07:01 -0400205 default=False,
David Patersond6babc52014-10-14 00:11:56 -0400206 help="Force deletion of the tempest and "
David Patersonce781492014-09-18 01:07:01 -0400207 "alternate tempest users and tenants.")
208 parser.add_argument('--dry-run', action="store_true",
209 dest='dry_run', default=False,
210 help="Generate JSON file:" + DRY_RUN_JSON +
211 ", that reports the objects that would have "
212 "been deleted had a full cleanup been run.")
213
214 self.options = parser.parse_args()
215
216 def _add_admin(self, tenant_id):
217 id_cl = self.admin_mgr.identity_client
218 needs_role = True
David Kranzb7afa922014-12-30 10:56:26 -0500219 roles = id_cl.list_user_roles(tenant_id, self.admin_id)
David Patersonce781492014-09-18 01:07:01 -0400220 for role in roles:
221 if role['id'] == self.admin_role_id:
222 needs_role = False
223 LOG.debug("User already had admin privilege for this tenant")
224 if needs_role:
225 LOG.debug("Adding admin priviledge for : %s" % tenant_id)
226 id_cl.assign_user_role(tenant_id, self.admin_id,
227 self.admin_role_id)
228 self.admin_role_added.append(tenant_id)
229
230 def _remove_admin_role(self, tenant_id):
231 LOG.debug("Remove admin user role for tenant: %s" % tenant_id)
232 # Must initialize AdminManager for each user role
233 # Otherwise authentication exception is thrown, weird
234 id_cl = clients.AdminManager().identity_client
235 if (self._tenant_exists(tenant_id)):
236 try:
237 id_cl.remove_user_role(tenant_id, self.admin_id,
238 self.admin_role_id)
239 except Exception as ex:
240 LOG.exception("Failed removing role from tenant which still"
241 "exists, exception: %s" % ex)
242
243 def _tenant_exists(self, tenant_id):
244 id_cl = self.admin_mgr.identity_client
245 try:
246 t = id_cl.get_tenant(tenant_id)
247 LOG.debug("Tenant is: %s" % str(t))
248 return True
249 except Exception as ex:
250 LOG.debug("Tenant no longer exists? %s" % ex)
251 return False
252
253 def _init_state(self):
254 LOG.debug("Initializing saved state.")
255 data = {}
256 admin_mgr = self.admin_mgr
257 kwargs = {'data': data,
258 'is_dry_run': False,
259 'saved_state_json': data,
260 'is_preserve': False,
261 'is_save_state': True}
262 for service in self.global_services:
263 svc = service(admin_mgr, **kwargs)
264 svc.run()
265
266 f = open(SAVED_STATE_JSON, 'w+')
267 f.write(json.dumps(data,
268 sort_keys=True, indent=2, separators=(',', ': ')))
269 f.close()
270
271 def _load_json(self):
272 try:
273 json_file = open(SAVED_STATE_JSON)
274 self.json_data = json.load(json_file)
275 json_file.close()
276 except IOError as ex:
277 LOG.exception("Failed loading saved state, please be sure you"
278 " have first run cleanup with --init-saved-state "
279 "flag prior to running tempest. Exception: %s" % ex)
280 sys.exit(ex)
281 except Exception as ex:
282 LOG.exception("Exception parsing saved state json : %s" % ex)
283 sys.exit(ex)
284
285
286def main():
David Patersond6babc52014-10-14 00:11:56 -0400287 cleanup_service.init_conf()
David Patersonce781492014-09-18 01:07:01 -0400288 cleanup = Cleanup()
289 cleanup.run()
290 LOG.info('Cleanup finished!')
291 return 0
292
293if __name__ == "__main__":
294 sys.exit(main())