blob: 9c852c5c4d0fd5dc2a019f382d43d7f5a6876c2f [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"""
David Patersonce781492014-09-18 01:07:01 -040053import sys
54
David Paterson07661de2015-10-29 20:15:04 -070055from cliff import command
Doug Hellmann583ce2c2015-03-11 14:55:46 +000056from oslo_log import log as logging
Matthew Treinish21905512015-07-13 10:33:35 -040057from oslo_serialization import jsonutils as json
Doug Hellmann583ce2c2015-03-11 14:55:46 +000058
David Patersonce781492014-09-18 01:07:01 -040059from tempest import clients
60from tempest.cmd import cleanup_service
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010061from tempest.common import credentials_factory as credentials
Ken'ichi Ohmichi6ea3f982015-11-09 12:41:13 +000062from tempest.common import identity
David Patersonce781492014-09-18 01:07:01 -040063from tempest import config
David Patersonce781492014-09-18 01:07:01 -040064
65SAVED_STATE_JSON = "saved_state.json"
66DRY_RUN_JSON = "dry_run.json"
67LOG = logging.getLogger(__name__)
68CONF = config.CONF
69
70
David Paterson07661de2015-10-29 20:15:04 -070071class TempestCleanup(command.Command):
David Patersonce781492014-09-18 01:07:01 -040072
David Paterson07661de2015-10-29 20:15:04 -070073 def __init__(self, app, cmd):
74 super(TempestCleanup, self).__init__(app, cmd)
75
76 def take_action(self, parsed_args):
77 cleanup_service.init_conf()
78 self.options = parsed_args
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010079 self.admin_mgr = credentials.AdminManager()
David Patersonce781492014-09-18 01:07:01 -040080 self.dry_run_data = {}
81 self.json_data = {}
David Patersonce781492014-09-18 01:07:01 -040082
83 self.admin_id = ""
84 self.admin_role_id = ""
85 self.admin_tenant_id = ""
86 self._init_admin_ids()
87
88 self.admin_role_added = []
89
90 # available services
91 self.tenant_services = cleanup_service.get_tenant_cleanup_services()
92 self.global_services = cleanup_service.get_global_cleanup_services()
David Patersonce781492014-09-18 01:07:01 -040093
David Paterson07661de2015-10-29 20:15:04 -070094 if parsed_args.init_saved_state:
David Patersonce781492014-09-18 01:07:01 -040095 self._init_state()
96 return
97
98 self._load_json()
99 self._cleanup()
100
101 def _cleanup(self):
102 LOG.debug("Begin cleanup")
103 is_dry_run = self.options.dry_run
David Patersond6babc52014-10-14 00:11:56 -0400104 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400105 is_save_state = False
106
107 if is_dry_run:
108 self.dry_run_data["_tenants_to_clean"] = {}
109 f = open(DRY_RUN_JSON, 'w+')
110
111 admin_mgr = self.admin_mgr
112 # Always cleanup tempest and alt tempest tenants unless
113 # they are in saved state json. Therefore is_preserve is False
114 kwargs = {'data': self.dry_run_data,
115 'is_dry_run': is_dry_run,
116 'saved_state_json': self.json_data,
117 'is_preserve': False,
118 'is_save_state': is_save_state}
119 tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
120 tenants = tenant_service.list()
121 LOG.debug("Process %s tenants" % len(tenants))
122
123 # Loop through list of tenants and clean them up.
124 for tenant in tenants:
125 self._add_admin(tenant['id'])
126 self._clean_tenant(tenant)
127
128 kwargs = {'data': self.dry_run_data,
129 'is_dry_run': is_dry_run,
130 'saved_state_json': self.json_data,
131 'is_preserve': is_preserve,
132 'is_save_state': is_save_state}
133 for service in self.global_services:
134 svc = service(admin_mgr, **kwargs)
135 svc.run()
136
137 if is_dry_run:
138 f.write(json.dumps(self.dry_run_data, sort_keys=True,
139 indent=2, separators=(',', ': ')))
140 f.close()
141
142 self._remove_admin_user_roles()
143
144 def _remove_admin_user_roles(self):
145 tenant_ids = self.admin_role_added
146 LOG.debug("Removing admin user roles where needed for tenants: %s"
147 % tenant_ids)
148 for tenant_id in tenant_ids:
149 self._remove_admin_role(tenant_id)
150
151 def _clean_tenant(self, tenant):
152 LOG.debug("Cleaning tenant: %s " % tenant['name'])
153 is_dry_run = self.options.dry_run
154 dry_run_data = self.dry_run_data
David Patersond6babc52014-10-14 00:11:56 -0400155 is_preserve = not self.options.delete_tempest_conf_objects
David Patersonce781492014-09-18 01:07:01 -0400156 tenant_id = tenant['id']
157 tenant_name = tenant['name']
158 tenant_data = None
159 if is_dry_run:
160 tenant_data = dry_run_data["_tenants_to_clean"][tenant_id] = {}
161 tenant_data['name'] = tenant_name
162
David Paterson07661de2015-10-29 20:15:04 -0700163 kwargs = {"username": CONF.auth.admin_username,
164 "password": CONF.auth.admin_password,
David Patersonce781492014-09-18 01:07:01 -0400165 "tenant_name": tenant['name']}
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +0100166 mgr = clients.Manager(credentials=credentials.get_credentials(
Andrea Frittoli878d5ab2015-01-30 13:22:50 +0000167 **kwargs))
David Patersonce781492014-09-18 01:07:01 -0400168 kwargs = {'data': tenant_data,
169 'is_dry_run': is_dry_run,
170 'saved_state_json': None,
171 'is_preserve': is_preserve,
172 'is_save_state': False,
173 'tenant_id': tenant_id}
174 for service in self.tenant_services:
175 svc = service(mgr, **kwargs)
176 svc.run()
177
178 def _init_admin_ids(self):
179 id_cl = self.admin_mgr.identity_client
180
Ken'ichi Ohmichi6ea3f982015-11-09 12:41:13 +0000181 tenant = identity.get_tenant_by_name(id_cl,
182 CONF.auth.admin_tenant_name)
David Patersonce781492014-09-18 01:07:01 -0400183 self.admin_tenant_id = tenant['id']
184
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000185 user = identity.get_user_by_username(id_cl, self.admin_tenant_id,
186 CONF.auth.admin_username)
David Patersonce781492014-09-18 01:07:01 -0400187 self.admin_id = user['id']
188
David Paterson07661de2015-10-29 20:15:04 -0700189 roles = id_cl.list_roles()['roles']
David Patersonce781492014-09-18 01:07:01 -0400190 for role in roles:
191 if role['name'] == CONF.identity.admin_role:
192 self.admin_role_id = role['id']
193 break
194
David Paterson07661de2015-10-29 20:15:04 -0700195 def get_parser(self, prog_name):
196 parser = super(TempestCleanup, self).get_parser(prog_name)
David Patersonce781492014-09-18 01:07:01 -0400197 parser.add_argument('--init-saved-state', action="store_true",
198 dest='init_saved_state', default=False,
199 help="Creates JSON file: " + SAVED_STATE_JSON +
200 ", representing the current state of your "
David Patersond6babc52014-10-14 00:11:56 -0400201 "deployment, specifically object types "
202 "tempest creates and destroys during a run. "
David Patersonce781492014-09-18 01:07:01 -0400203 "You must run with this flag prior to "
David Patersond6babc52014-10-14 00:11:56 -0400204 "executing cleanup in normal mode, which is with "
205 "no arguments.")
David Patersonce781492014-09-18 01:07:01 -0400206 parser.add_argument('--delete-tempest-conf-objects',
David Patersond6babc52014-10-14 00:11:56 -0400207 action="store_true",
208 dest='delete_tempest_conf_objects',
David Patersonce781492014-09-18 01:07:01 -0400209 default=False,
David Patersond6babc52014-10-14 00:11:56 -0400210 help="Force deletion of the tempest and "
David Patersonce781492014-09-18 01:07:01 -0400211 "alternate tempest users and tenants.")
212 parser.add_argument('--dry-run', action="store_true",
213 dest='dry_run', default=False,
214 help="Generate JSON file:" + DRY_RUN_JSON +
215 ", that reports the objects that would have "
216 "been deleted had a full cleanup been run.")
David Paterson07661de2015-10-29 20:15:04 -0700217 return parser
David Patersonce781492014-09-18 01:07:01 -0400218
David Paterson07661de2015-10-29 20:15:04 -0700219 def get_description(self):
220 return 'Cleanup after tempest run'
David Patersonce781492014-09-18 01:07:01 -0400221
222 def _add_admin(self, tenant_id):
223 id_cl = self.admin_mgr.identity_client
224 needs_role = True
David Paterson07661de2015-10-29 20:15:04 -0700225 roles = id_cl.list_user_roles(tenant_id, self.admin_id)['roles']
David Patersonce781492014-09-18 01:07:01 -0400226 for role in roles:
227 if role['id'] == self.admin_role_id:
228 needs_role = False
229 LOG.debug("User already had admin privilege for this tenant")
230 if needs_role:
Tingting Bao2b513342015-02-15 22:54:55 -0800231 LOG.debug("Adding admin privilege for : %s" % tenant_id)
David Patersonce781492014-09-18 01:07:01 -0400232 id_cl.assign_user_role(tenant_id, self.admin_id,
233 self.admin_role_id)
234 self.admin_role_added.append(tenant_id)
235
236 def _remove_admin_role(self, tenant_id):
237 LOG.debug("Remove admin user role for tenant: %s" % tenant_id)
238 # Must initialize AdminManager for each user role
239 # Otherwise authentication exception is thrown, weird
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +0100240 id_cl = credentials.AdminManager().identity_client
David Patersonce781492014-09-18 01:07:01 -0400241 if (self._tenant_exists(tenant_id)):
242 try:
243 id_cl.remove_user_role(tenant_id, self.admin_id,
244 self.admin_role_id)
245 except Exception as ex:
246 LOG.exception("Failed removing role from tenant which still"
247 "exists, exception: %s" % ex)
248
249 def _tenant_exists(self, tenant_id):
250 id_cl = self.admin_mgr.identity_client
251 try:
Ken'ichi Ohmichi402b8752015-11-09 10:47:16 +0000252 t = id_cl.show_tenant(tenant_id)
David Patersonce781492014-09-18 01:07:01 -0400253 LOG.debug("Tenant is: %s" % str(t))
254 return True
255 except Exception as ex:
256 LOG.debug("Tenant no longer exists? %s" % ex)
257 return False
258
259 def _init_state(self):
260 LOG.debug("Initializing saved state.")
261 data = {}
262 admin_mgr = self.admin_mgr
263 kwargs = {'data': data,
264 'is_dry_run': False,
265 'saved_state_json': data,
266 'is_preserve': False,
267 'is_save_state': True}
268 for service in self.global_services:
269 svc = service(admin_mgr, **kwargs)
270 svc.run()
271
272 f = open(SAVED_STATE_JSON, 'w+')
273 f.write(json.dumps(data,
274 sort_keys=True, indent=2, separators=(',', ': ')))
275 f.close()
276
277 def _load_json(self):
278 try:
279 json_file = open(SAVED_STATE_JSON)
280 self.json_data = json.load(json_file)
281 json_file.close()
282 except IOError as ex:
283 LOG.exception("Failed loading saved state, please be sure you"
284 " have first run cleanup with --init-saved-state "
285 "flag prior to running tempest. Exception: %s" % ex)
286 sys.exit(ex)
287 except Exception as ex:
288 LOG.exception("Exception parsing saved state json : %s" % ex)
289 sys.exit(ex)