blob: 1d460283768e7593554ade97afdb5bb8203f22f6 [file] [log] [blame]
Sean Dague655e0af2014-05-29 09:00:22 -04001#!/usr/bin/env python
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15"""Javelin makes resources that should survive an upgrade.
16
17Javelin is a tool for creating, verifying, and deleting a small set of
18resources in a declarative way.
19
20"""
21
22import logging
23import os
24import sys
25import unittest
26import yaml
27
28import argparse
29
30import tempest.auth
31from tempest import exceptions
32from tempest.services.compute.json import flavors_client
33from tempest.services.compute.json import servers_client
34from tempest.services.identity.json import identity_client
35from tempest.services.image.v2.json import image_client
36from tempest.services.object_storage import container_client
37from tempest.services.object_storage import object_client
Emilien Macchi626b4f82014-06-15 21:44:29 +020038from tempest.services.volume.json import volumes_client
Sean Dague655e0af2014-05-29 09:00:22 -040039
40OPTS = {}
41USERS = {}
42RES = {}
43
44LOG = None
45
46
47class OSClient(object):
48 _creds = None
49 identity = None
50 servers = None
51
52 def __init__(self, user, pw, tenant):
53 _creds = tempest.auth.KeystoneV2Credentials(
54 username=user,
55 password=pw,
56 tenant_name=tenant)
57 _auth = tempest.auth.KeystoneV2AuthProvider(_creds)
58 self.identity = identity_client.IdentityClientJSON(_auth)
59 self.servers = servers_client.ServersClientJSON(_auth)
60 self.objects = object_client.ObjectClient(_auth)
61 self.containers = container_client.ContainerClient(_auth)
62 self.images = image_client.ImageClientV2JSON(_auth)
63 self.flavors = flavors_client.FlavorsClientJSON(_auth)
Emilien Macchi626b4f82014-06-15 21:44:29 +020064 self.volumes = volumes_client.VolumesClientJSON(_auth)
Sean Dague655e0af2014-05-29 09:00:22 -040065
66
67def load_resources(fname):
68 """Load the expected resources from a yaml flie."""
69 return yaml.load(open(fname, 'r'))
70
71
72def keystone_admin():
73 return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
74
75
76def client_for_user(name):
77 LOG.debug("Entering client_for_user")
78 if name in USERS:
79 user = USERS[name]
80 LOG.debug("Created client for user %s" % user)
81 return OSClient(user['name'], user['pass'], user['tenant'])
82 else:
83 LOG.error("%s not found in USERS: %s" % (name, USERS))
84
85###################
86#
87# TENANTS
88#
89###################
90
91
92def create_tenants(tenants):
93 """Create tenants from resource definition.
94
95 Don't create the tenants if they already exist.
96 """
97 admin = keystone_admin()
98 _, body = admin.identity.list_tenants()
99 existing = [x['name'] for x in body]
100 for tenant in tenants:
101 if tenant not in existing:
102 admin.identity.create_tenant(tenant)
103 else:
104 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
105
106##############
107#
108# USERS
109#
110##############
111
112
113def _users_for_tenant(users, tenant):
114 u_for_t = []
115 for user in users:
116 for n in user:
117 if user[n]['tenant'] == tenant:
118 u_for_t.append(user[n])
119 return u_for_t
120
121
122def _tenants_from_users(users):
123 tenants = set()
124 for user in users:
125 for n in user:
126 tenants.add(user[n]['tenant'])
127 return tenants
128
129
130def _assign_swift_role(user):
131 admin = keystone_admin()
132 resp, roles = admin.identity.list_roles()
133 role = next(r for r in roles if r['name'] == 'Member')
134 LOG.debug(USERS[user])
135 try:
136 admin.identity.assign_user_role(
137 USERS[user]['tenant_id'],
138 USERS[user]['id'],
139 role['id'])
140 except exceptions.Conflict:
141 # don't care if it's already assigned
142 pass
143
144
145def create_users(users):
146 """Create tenants from resource definition.
147
148 Don't create the tenants if they already exist.
149 """
150 global USERS
151 LOG.info("Creating users")
152 admin = keystone_admin()
153 for u in users:
154 try:
155 tenant = admin.identity.get_tenant_by_name(u['tenant'])
156 except exceptions.NotFound:
157 LOG.error("Tenant: %s - not found" % u['tenant'])
158 continue
159 try:
160 admin.identity.get_user_by_username(tenant['id'], u['name'])
161 LOG.warn("User '%s' already exists in this environment"
162 % u['name'])
163 except exceptions.NotFound:
164 admin.identity.create_user(
165 u['name'], u['pass'], tenant['id'],
166 "%s@%s" % (u['name'], tenant['id']),
167 enabled=True)
168
169
170def collect_users(users):
171 global USERS
172 LOG.info("Creating users")
173 admin = keystone_admin()
174 for u in users:
175 tenant = admin.identity.get_tenant_by_name(u['tenant'])
176 u['tenant_id'] = tenant['id']
177 USERS[u['name']] = u
178 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
179 USERS[u['name']]['id'] = body['id']
180
181
182class JavelinCheck(unittest.TestCase):
183 def __init__(self, users, resources):
184 super(JavelinCheck, self).__init__()
185 self.users = users
186 self.res = resources
187
188 def runTest(self, *args):
189 pass
190
191 def check(self):
192 self.check_users()
193 self.check_objects()
194 self.check_servers()
Emilien Macchi626b4f82014-06-15 21:44:29 +0200195 self.check_volumes()
Sean Dague655e0af2014-05-29 09:00:22 -0400196
197 def check_users(self):
198 """Check that the users we expect to exist, do.
199
200 We don't use the resource list for this because we need to validate
201 that things like tenantId didn't drift across versions.
202 """
203 for name, user in self.users.iteritems():
204 client = keystone_admin()
205 _, found = client.identity.get_user(user['id'])
206 self.assertEqual(found['name'], user['name'])
207 self.assertEqual(found['tenantId'], user['tenant_id'])
208
209 # also ensure we can auth with that user, and do something
210 # on the cloud. We don't care about the results except that it
211 # remains authorized.
212 client = client_for_user(user['name'])
213 resp, body = client.servers.list_servers()
214 self.assertEqual(resp['status'], '200')
215
216 def check_objects(self):
217 """Check that the objects created are still there."""
218 for obj in self.res['objects']:
219 client = client_for_user(obj['owner'])
220 r, contents = client.objects.get_object(
221 obj['container'], obj['name'])
222 source = _file_contents(obj['file'])
223 self.assertEqual(contents, source)
224
225 def check_servers(self):
226 """Check that the servers are still up and running."""
227 for server in self.res['servers']:
228 client = client_for_user(server['owner'])
229 found = _get_server_by_name(client, server['name'])
230 self.assertIsNotNone(
231 found,
232 "Couldn't find expected server %s" % server['name'])
233
234 r, found = client.servers.get_server(found['id'])
235 # get the ipv4 address
236 addr = found['addresses']['private'][0]['addr']
237 self.assertEqual(os.system("ping -c 1 " + addr), 0,
238 "Server %s is not pingable at %s" % (
239 server['name'], addr))
240
Emilien Macchi626b4f82014-06-15 21:44:29 +0200241 def check_volumes(self):
242 """Check that the volumes are still there and attached."""
243 for volume in self.res['volumes']:
244 client = client_for_user(volume['owner'])
245 found = _get_volume_by_name(client, volume['name'])
246 self.assertIsNotNone(
247 found,
248 "Couldn't find expected volume %s" % volume['name'])
249
250 # Verify that a volume's attachment retrieved
251 server_id = _get_server_by_name(client, volume['server'])['id']
252 attachment = self.client.get_attachment_from_volume(volume)
253 self.assertEqual(volume['id'], attachment['volume_id'])
254 self.assertEqual(server_id, attachment['server_id'])
255
Sean Dague655e0af2014-05-29 09:00:22 -0400256
257#######################
258#
259# OBJECTS
260#
261#######################
262
263
264def _file_contents(fname):
265 with open(fname, 'r') as f:
266 return f.read()
267
268
269def create_objects(objects):
270 LOG.info("Creating objects")
271 for obj in objects:
272 LOG.debug("Object %s" % obj)
273 _assign_swift_role(obj['owner'])
274 client = client_for_user(obj['owner'])
275 client.containers.create_container(obj['container'])
276 client.objects.create_object(
277 obj['container'], obj['name'],
278 _file_contents(obj['file']))
279
280#######################
281#
282# IMAGES
283#
284#######################
285
286
287def create_images(images):
288 for image in images:
289 client = client_for_user(image['owner'])
290
291 # only upload a new image if the name isn't there
292 r, body = client.images.image_list()
293 names = [x['name'] for x in body]
294 if image['name'] in names:
295 continue
296
297 # special handling for 3 part image
298 extras = {}
299 if image['format'] == 'ami':
300 r, aki = client.images.create_image(
301 'javelin_' + image['aki'], 'aki', 'aki')
302 client.images.store_image(aki.get('id'), open(image['aki'], 'r'))
303 extras['kernel_id'] = aki.get('id')
304
305 r, ari = client.images.create_image(
306 'javelin_' + image['ari'], 'ari', 'ari')
307 client.images.store_image(ari.get('id'), open(image['ari'], 'r'))
308 extras['ramdisk_id'] = ari.get('id')
309
310 r, body = client.images.create_image(
311 image['name'], image['format'], image['format'], **extras)
312 image_id = body.get('id')
313 client.images.store_image(image_id, open(image['file'], 'r'))
314
315
316#######################
317#
318# SERVERS
319#
320#######################
321
322def _get_server_by_name(client, name):
323 r, body = client.servers.list_servers()
324 for server in body['servers']:
325 if name == server['name']:
326 return server
327 return None
328
329
330def _get_image_by_name(client, name):
331 r, body = client.images.image_list()
332 for image in body:
333 if name == image['name']:
334 return image
335 return None
336
337
338def _get_flavor_by_name(client, name):
339 r, body = client.flavors.list_flavors()
340 for flavor in body:
341 if name == flavor['name']:
342 return flavor
343 return None
344
345
346def create_servers(servers):
347 for server in servers:
348 client = client_for_user(server['owner'])
349
350 if _get_server_by_name(client, server['name']):
351 continue
352
353 image_id = _get_image_by_name(client, server['image'])['id']
354 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
355 client.servers.create_server(server['name'], image_id, flavor_id)
356
357
358#######################
359#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200360# VOLUMES
361#
362#######################
363
364def _get_volume_by_name(client, name):
365 r, body = client.volumes.list_volumes()
366 for volume in body['volumes']:
367 if name == volume['name']:
368 return volume
369 return None
370
371
372def create_volumes(volumes):
373 for volume in volumes:
374 client = client_for_user(volume['owner'])
375
376 # only create a volume if the name isn't here
377 r, body = client.volumes.list_volumes()
378 if any(item['name'] == volume['name'] for item in body):
379 continue
380
381 client.volumes.create_volume(volume['name'], volume['size'])
382
383
384def attach_volumes(volumes):
385 for volume in volumes:
386 client = client_for_user(volume['owner'])
387
388 server_id = _get_server_by_name(client, volume['server'])['id']
389 client.volumes.attach_volume(volume['name'], server_id)
390
391
392#######################
393#
Sean Dague655e0af2014-05-29 09:00:22 -0400394# MAIN LOGIC
395#
396#######################
397
398def create_resources():
399 LOG.info("Creating Resources")
400 # first create keystone level resources, and we need to be admin
401 # for those.
402 create_tenants(RES['tenants'])
403 create_users(RES['users'])
404 collect_users(RES['users'])
405
406 # next create resources in a well known order
407 create_objects(RES['objects'])
408 create_images(RES['images'])
409 create_servers(RES['servers'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200410 create_volumes(RES['volumes'])
411 attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400412
413
414def get_options():
415 global OPTS
416 parser = argparse.ArgumentParser(
417 description='Create and validate a fixed set of OpenStack resources')
418 parser.add_argument('-m', '--mode',
419 metavar='<create|check|destroy>',
420 required=True,
421 help=('One of (create, check, destroy)'))
422 parser.add_argument('-r', '--resources',
423 required=True,
424 metavar='resourcefile.yaml',
425 help='Resources definition yaml file')
426 # auth bits, letting us also just source the devstack openrc
427 parser.add_argument('--os-username',
428 metavar='<auth-user-name>',
429 default=os.environ.get('OS_USERNAME'),
430 help=('Defaults to env[OS_USERNAME].'))
431 parser.add_argument('--os-password',
432 metavar='<auth-password>',
433 default=os.environ.get('OS_PASSWORD'),
434 help=('Defaults to env[OS_PASSWORD].'))
435 parser.add_argument('--os-tenant-name',
436 metavar='<auth-tenant-name>',
437 default=os.environ.get('OS_TENANT_NAME'),
438 help=('Defaults to env[OS_TENANT_NAME].'))
439
440 OPTS = parser.parse_args()
441 if OPTS.mode not in ('create', 'check', 'destroy'):
442 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
443 parser.print_help()
444 sys.exit(1)
445
446
447def setup_logging(debug=True):
448 global LOG
449 LOG = logging.getLogger(__name__)
450 if debug:
451 LOG.setLevel(logging.DEBUG)
452 else:
453 LOG.setLevel(logging.INFO)
454
455 ch = logging.StreamHandler(sys.stdout)
456 ch.setLevel(logging.DEBUG)
457 formatter = logging.Formatter(
458 datefmt='%Y-%m-%d %H:%M:%S',
459 fmt='%(asctime)s.%(msecs).03d - %(levelname)s - %(message)s')
460 ch.setFormatter(formatter)
461 LOG.addHandler(ch)
462
463
464def main():
465 global RES
466 get_options()
467 setup_logging()
468 RES = load_resources(OPTS.resources)
469
470 if OPTS.mode == 'create':
471 create_resources()
472 elif OPTS.mode == 'check':
473 collect_users(RES['users'])
474 checker = JavelinCheck(USERS, RES)
475 checker.check()
476 elif OPTS.mode == 'destroy':
477 LOG.warn("Destroy mode not yet implemented")
478 else:
479 LOG.error('Unknown mode %s' % OPTS.mode)
480 return 1
481 return 0
482
483if __name__ == "__main__":
484 sys.exit(main())