blob: 6761a690cdfe1974e42120a28d223f601a8d328e [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
Matthew Treinish96e9e882014-06-09 18:37:19 -040022import argparse
Sean Dague655e0af2014-05-29 09:00:22 -040023import logging
24import os
25import sys
26import unittest
Sean Dague655e0af2014-05-29 09:00:22 -040027
Matthew Treinish96e9e882014-06-09 18:37:19 -040028import yaml
Sean Dague655e0af2014-05-29 09:00:22 -040029
30import tempest.auth
Joe Gordon28a84ae2014-07-17 15:38:28 +000031from tempest import config
Sean Dague655e0af2014-05-29 09:00:22 -040032from tempest import exceptions
33from tempest.services.compute.json import flavors_client
34from tempest.services.compute.json import servers_client
35from tempest.services.identity.json import identity_client
36from tempest.services.image.v2.json import image_client
37from tempest.services.object_storage import container_client
38from tempest.services.object_storage import object_client
Emilien Macchi626b4f82014-06-15 21:44:29 +020039from tempest.services.volume.json import volumes_client
Sean Dague655e0af2014-05-29 09:00:22 -040040
41OPTS = {}
42USERS = {}
43RES = {}
44
45LOG = None
46
47
48class OSClient(object):
49 _creds = None
50 identity = None
51 servers = None
52
53 def __init__(self, user, pw, tenant):
54 _creds = tempest.auth.KeystoneV2Credentials(
55 username=user,
56 password=pw,
57 tenant_name=tenant)
58 _auth = tempest.auth.KeystoneV2AuthProvider(_creds)
59 self.identity = identity_client.IdentityClientJSON(_auth)
60 self.servers = servers_client.ServersClientJSON(_auth)
61 self.objects = object_client.ObjectClient(_auth)
62 self.containers = container_client.ContainerClient(_auth)
63 self.images = image_client.ImageClientV2JSON(_auth)
64 self.flavors = flavors_client.FlavorsClientJSON(_auth)
Emilien Macchi626b4f82014-06-15 21:44:29 +020065 self.volumes = volumes_client.VolumesClientJSON(_auth)
Sean Dague655e0af2014-05-29 09:00:22 -040066
67
68def load_resources(fname):
69 """Load the expected resources from a yaml flie."""
70 return yaml.load(open(fname, 'r'))
71
72
73def keystone_admin():
74 return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
75
76
77def client_for_user(name):
78 LOG.debug("Entering client_for_user")
79 if name in USERS:
80 user = USERS[name]
81 LOG.debug("Created client for user %s" % user)
82 return OSClient(user['name'], user['pass'], user['tenant'])
83 else:
84 LOG.error("%s not found in USERS: %s" % (name, USERS))
85
86###################
87#
88# TENANTS
89#
90###################
91
92
93def create_tenants(tenants):
94 """Create tenants from resource definition.
95
96 Don't create the tenants if they already exist.
97 """
98 admin = keystone_admin()
99 _, body = admin.identity.list_tenants()
100 existing = [x['name'] for x in body]
101 for tenant in tenants:
102 if tenant not in existing:
103 admin.identity.create_tenant(tenant)
104 else:
105 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
106
107##############
108#
109# USERS
110#
111##############
112
113
114def _users_for_tenant(users, tenant):
115 u_for_t = []
116 for user in users:
117 for n in user:
118 if user[n]['tenant'] == tenant:
119 u_for_t.append(user[n])
120 return u_for_t
121
122
123def _tenants_from_users(users):
124 tenants = set()
125 for user in users:
126 for n in user:
127 tenants.add(user[n]['tenant'])
128 return tenants
129
130
131def _assign_swift_role(user):
132 admin = keystone_admin()
133 resp, roles = admin.identity.list_roles()
134 role = next(r for r in roles if r['name'] == 'Member')
135 LOG.debug(USERS[user])
136 try:
137 admin.identity.assign_user_role(
138 USERS[user]['tenant_id'],
139 USERS[user]['id'],
140 role['id'])
141 except exceptions.Conflict:
142 # don't care if it's already assigned
143 pass
144
145
146def create_users(users):
147 """Create tenants from resource definition.
148
149 Don't create the tenants if they already exist.
150 """
151 global USERS
152 LOG.info("Creating users")
153 admin = keystone_admin()
154 for u in users:
155 try:
156 tenant = admin.identity.get_tenant_by_name(u['tenant'])
157 except exceptions.NotFound:
158 LOG.error("Tenant: %s - not found" % u['tenant'])
159 continue
160 try:
161 admin.identity.get_user_by_username(tenant['id'], u['name'])
162 LOG.warn("User '%s' already exists in this environment"
163 % u['name'])
164 except exceptions.NotFound:
165 admin.identity.create_user(
166 u['name'], u['pass'], tenant['id'],
167 "%s@%s" % (u['name'], tenant['id']),
168 enabled=True)
169
170
171def collect_users(users):
172 global USERS
Joe Gordon618c9fb2014-07-16 15:40:01 +0200173 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400174 admin = keystone_admin()
175 for u in users:
176 tenant = admin.identity.get_tenant_by_name(u['tenant'])
177 u['tenant_id'] = tenant['id']
178 USERS[u['name']] = u
179 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
180 USERS[u['name']]['id'] = body['id']
181
182
183class JavelinCheck(unittest.TestCase):
184 def __init__(self, users, resources):
185 super(JavelinCheck, self).__init__()
186 self.users = users
187 self.res = resources
188
189 def runTest(self, *args):
190 pass
191
192 def check(self):
193 self.check_users()
194 self.check_objects()
195 self.check_servers()
Sean Dague319b37a2014-07-11 07:28:11 -0400196 # TODO(sdague): Volumes not yet working, bring it back once the
197 # code is self testing.
198 # self.check_volumes()
Sean Dague655e0af2014-05-29 09:00:22 -0400199
200 def check_users(self):
201 """Check that the users we expect to exist, do.
202
203 We don't use the resource list for this because we need to validate
204 that things like tenantId didn't drift across versions.
205 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200206 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400207 for name, user in self.users.iteritems():
208 client = keystone_admin()
209 _, found = client.identity.get_user(user['id'])
210 self.assertEqual(found['name'], user['name'])
211 self.assertEqual(found['tenantId'], user['tenant_id'])
212
213 # also ensure we can auth with that user, and do something
214 # on the cloud. We don't care about the results except that it
215 # remains authorized.
216 client = client_for_user(user['name'])
217 resp, body = client.servers.list_servers()
218 self.assertEqual(resp['status'], '200')
219
220 def check_objects(self):
221 """Check that the objects created are still there."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000222 if not self.res.get('objects'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000223 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200224 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400225 for obj in self.res['objects']:
226 client = client_for_user(obj['owner'])
227 r, contents = client.objects.get_object(
228 obj['container'], obj['name'])
229 source = _file_contents(obj['file'])
230 self.assertEqual(contents, source)
231
232 def check_servers(self):
233 """Check that the servers are still up and running."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000234 if not self.res.get('servers'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000235 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200236 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400237 for server in self.res['servers']:
238 client = client_for_user(server['owner'])
239 found = _get_server_by_name(client, server['name'])
240 self.assertIsNotNone(
241 found,
242 "Couldn't find expected server %s" % server['name'])
243
244 r, found = client.servers.get_server(found['id'])
245 # get the ipv4 address
246 addr = found['addresses']['private'][0]['addr']
Joe Gordonb6faa8b2014-07-24 23:42:35 +0000247 for count in range(60):
248 return_code = os.system("ping -c1 " + addr)
249 if return_code is 0:
250 break
251 self.assertNotEqual(count, 59,
Matthew Treinish1d14c542014-06-17 20:25:40 -0400252 "Server %s is not pingable at %s" % (
253 server['name'], addr))
Sean Dague655e0af2014-05-29 09:00:22 -0400254
Emilien Macchi626b4f82014-06-15 21:44:29 +0200255 def check_volumes(self):
256 """Check that the volumes are still there and attached."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000257 if not self.res.get('volumes'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000258 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200259 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200260 for volume in self.res['volumes']:
261 client = client_for_user(volume['owner'])
262 found = _get_volume_by_name(client, volume['name'])
263 self.assertIsNotNone(
264 found,
265 "Couldn't find expected volume %s" % volume['name'])
266
267 # Verify that a volume's attachment retrieved
268 server_id = _get_server_by_name(client, volume['server'])['id']
269 attachment = self.client.get_attachment_from_volume(volume)
270 self.assertEqual(volume['id'], attachment['volume_id'])
271 self.assertEqual(server_id, attachment['server_id'])
272
Sean Dague655e0af2014-05-29 09:00:22 -0400273
274#######################
275#
276# OBJECTS
277#
278#######################
279
280
281def _file_contents(fname):
282 with open(fname, 'r') as f:
283 return f.read()
284
285
286def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000287 if not objects:
288 return
Sean Dague655e0af2014-05-29 09:00:22 -0400289 LOG.info("Creating objects")
290 for obj in objects:
291 LOG.debug("Object %s" % obj)
292 _assign_swift_role(obj['owner'])
293 client = client_for_user(obj['owner'])
294 client.containers.create_container(obj['container'])
295 client.objects.create_object(
296 obj['container'], obj['name'],
297 _file_contents(obj['file']))
298
299#######################
300#
301# IMAGES
302#
303#######################
304
305
Sean Dague319b37a2014-07-11 07:28:11 -0400306def _resolve_image(image, imgtype):
307 name = image[imgtype]
308 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
309 return name, fname
310
311
Joe Gordon6f0426c2014-07-25 01:10:28 +0000312def _get_image_by_name(client, name):
313 r, body = client.images.image_list()
314 for image in body:
315 if name == image['name']:
316 return image
317 return None
318
319
Sean Dague655e0af2014-05-29 09:00:22 -0400320def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000321 if not images:
322 return
Joe Gordona18d6862014-07-24 22:55:46 +0000323 LOG.info("Creating images")
Sean Dague655e0af2014-05-29 09:00:22 -0400324 for image in images:
325 client = client_for_user(image['owner'])
326
327 # only upload a new image if the name isn't there
Joe Gordon6f0426c2014-07-25 01:10:28 +0000328 if _get_image_by_name(client, image['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000329 LOG.info("Image '%s' already exists" % image['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400330 continue
331
332 # special handling for 3 part image
333 extras = {}
334 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400335 name, fname = _resolve_image(image, 'aki')
Sean Dague655e0af2014-05-29 09:00:22 -0400336 r, aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400337 'javelin_' + name, 'aki', 'aki')
338 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400339 extras['kernel_id'] = aki.get('id')
340
Sean Dague319b37a2014-07-11 07:28:11 -0400341 name, fname = _resolve_image(image, 'ari')
Sean Dague655e0af2014-05-29 09:00:22 -0400342 r, ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400343 'javelin_' + name, 'ari', 'ari')
344 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400345 extras['ramdisk_id'] = ari.get('id')
346
Sean Dague319b37a2014-07-11 07:28:11 -0400347 _, fname = _resolve_image(image, 'file')
Sean Dague655e0af2014-05-29 09:00:22 -0400348 r, body = client.images.create_image(
349 image['name'], image['format'], image['format'], **extras)
350 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400351 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400352
353
Joe Gordon6f0426c2014-07-25 01:10:28 +0000354def destroy_images(images):
355 if not images:
356 return
357 LOG.info("Destroying images")
358 for image in images:
359 client = client_for_user(image['owner'])
360
361 response = _get_image_by_name(client, image['name'])
362 if not response:
363 LOG.info("Image '%s' does not exists" % image['name'])
364 continue
365 client.images.delete_image(response['id'])
366
367
Sean Dague655e0af2014-05-29 09:00:22 -0400368#######################
369#
370# SERVERS
371#
372#######################
373
374def _get_server_by_name(client, name):
375 r, body = client.servers.list_servers()
376 for server in body['servers']:
377 if name == server['name']:
378 return server
379 return None
380
381
Sean Dague655e0af2014-05-29 09:00:22 -0400382def _get_flavor_by_name(client, name):
383 r, body = client.flavors.list_flavors()
384 for flavor in body:
385 if name == flavor['name']:
386 return flavor
387 return None
388
389
390def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000391 if not servers:
392 return
Joe Gordona18d6862014-07-24 22:55:46 +0000393 LOG.info("Creating servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400394 for server in servers:
395 client = client_for_user(server['owner'])
396
397 if _get_server_by_name(client, server['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000398 LOG.info("Server '%s' already exists" % server['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400399 continue
400
401 image_id = _get_image_by_name(client, server['image'])['id']
402 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
Joe Gordon10f260b2014-07-24 23:27:19 +0000403 resp, body = client.servers.create_server(server['name'], image_id,
Matthew Treinish1d14c542014-06-17 20:25:40 -0400404 flavor_id)
Joe Gordon10f260b2014-07-24 23:27:19 +0000405 server_id = body['id']
406 client.servers.wait_for_server_status(server_id, 'ACTIVE')
Sean Dague655e0af2014-05-29 09:00:22 -0400407
408
Joe Gordondb63b1c2014-07-24 23:21:21 +0000409def destroy_servers(servers):
410 if not servers:
411 return
412 LOG.info("Destroying servers")
413 for server in servers:
414 client = client_for_user(server['owner'])
415
416 response = _get_server_by_name(client, server['name'])
417 if not response:
418 LOG.info("Server '%s' does not exist" % server['name'])
419 continue
420
421 client.servers.delete_server(response['id'])
422 client.servers.wait_for_server_termination(response['id'],
Matthew Treinish1d14c542014-06-17 20:25:40 -0400423 ignore_error=True)
Joe Gordondb63b1c2014-07-24 23:21:21 +0000424
425
Sean Dague655e0af2014-05-29 09:00:22 -0400426#######################
427#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200428# VOLUMES
429#
430#######################
431
432def _get_volume_by_name(client, name):
433 r, body = client.volumes.list_volumes()
434 for volume in body['volumes']:
435 if name == volume['name']:
436 return volume
437 return None
438
439
440def create_volumes(volumes):
441 for volume in volumes:
442 client = client_for_user(volume['owner'])
443
444 # only create a volume if the name isn't here
445 r, body = client.volumes.list_volumes()
446 if any(item['name'] == volume['name'] for item in body):
447 continue
448
449 client.volumes.create_volume(volume['name'], volume['size'])
450
451
452def attach_volumes(volumes):
453 for volume in volumes:
454 client = client_for_user(volume['owner'])
455
456 server_id = _get_server_by_name(client, volume['server'])['id']
457 client.volumes.attach_volume(volume['name'], server_id)
458
459
460#######################
461#
Sean Dague655e0af2014-05-29 09:00:22 -0400462# MAIN LOGIC
463#
464#######################
465
466def create_resources():
467 LOG.info("Creating Resources")
468 # first create keystone level resources, and we need to be admin
469 # for those.
470 create_tenants(RES['tenants'])
471 create_users(RES['users'])
472 collect_users(RES['users'])
473
474 # next create resources in a well known order
475 create_objects(RES['objects'])
476 create_images(RES['images'])
477 create_servers(RES['servers'])
Sean Dague319b37a2014-07-11 07:28:11 -0400478 # TODO(sdague): volumes definition doesn't work yet, bring it
479 # back once we're actually executing the code
480 # create_volumes(RES['volumes'])
481 # attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400482
483
Joe Gordondb63b1c2014-07-24 23:21:21 +0000484def destroy_resources():
485 LOG.info("Destroying Resources")
486 # Destroy in inverse order of create
487
488 # Future
489 # detach_volumes
490 # destroy_volumes
491
492 destroy_servers(RES['servers'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000493 destroy_images(RES['images'])
Joe Gordondb63b1c2014-07-24 23:21:21 +0000494 # destroy_objects
495
496 # destroy_users
497 # destroy_tenants
498
Joe Gordon6f0426c2014-07-25 01:10:28 +0000499 LOG.warn("Destroy mode incomplete")
500
Joe Gordondb63b1c2014-07-24 23:21:21 +0000501
Sean Dague655e0af2014-05-29 09:00:22 -0400502def get_options():
503 global OPTS
504 parser = argparse.ArgumentParser(
505 description='Create and validate a fixed set of OpenStack resources')
506 parser.add_argument('-m', '--mode',
507 metavar='<create|check|destroy>',
508 required=True,
509 help=('One of (create, check, destroy)'))
510 parser.add_argument('-r', '--resources',
511 required=True,
512 metavar='resourcefile.yaml',
513 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000514
Sean Dague319b37a2014-07-11 07:28:11 -0400515 parser.add_argument(
516 '-d', '--devstack-base',
517 required=True,
518 metavar='/opt/stack/old',
519 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000520 parser.add_argument(
521 '-c', '--config-file',
522 metavar='/etc/tempest.conf',
523 help='path to javelin2(tempest) config file')
524
Sean Dague655e0af2014-05-29 09:00:22 -0400525 # auth bits, letting us also just source the devstack openrc
526 parser.add_argument('--os-username',
527 metavar='<auth-user-name>',
528 default=os.environ.get('OS_USERNAME'),
529 help=('Defaults to env[OS_USERNAME].'))
530 parser.add_argument('--os-password',
531 metavar='<auth-password>',
532 default=os.environ.get('OS_PASSWORD'),
533 help=('Defaults to env[OS_PASSWORD].'))
534 parser.add_argument('--os-tenant-name',
535 metavar='<auth-tenant-name>',
536 default=os.environ.get('OS_TENANT_NAME'),
537 help=('Defaults to env[OS_TENANT_NAME].'))
538
539 OPTS = parser.parse_args()
540 if OPTS.mode not in ('create', 'check', 'destroy'):
541 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
542 parser.print_help()
543 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +0000544 if OPTS.config_file:
545 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -0400546
547
548def setup_logging(debug=True):
549 global LOG
550 LOG = logging.getLogger(__name__)
551 if debug:
552 LOG.setLevel(logging.DEBUG)
553 else:
554 LOG.setLevel(logging.INFO)
555
556 ch = logging.StreamHandler(sys.stdout)
557 ch.setLevel(logging.DEBUG)
558 formatter = logging.Formatter(
559 datefmt='%Y-%m-%d %H:%M:%S',
560 fmt='%(asctime)s.%(msecs).03d - %(levelname)s - %(message)s')
561 ch.setFormatter(formatter)
562 LOG.addHandler(ch)
563
564
565def main():
566 global RES
567 get_options()
568 setup_logging()
569 RES = load_resources(OPTS.resources)
570
571 if OPTS.mode == 'create':
572 create_resources()
Joe Gordon1a097002014-07-24 23:44:08 +0000573 # Make sure the resources we just created actually work
574 checker = JavelinCheck(USERS, RES)
575 checker.check()
Sean Dague655e0af2014-05-29 09:00:22 -0400576 elif OPTS.mode == 'check':
577 collect_users(RES['users'])
578 checker = JavelinCheck(USERS, RES)
579 checker.check()
580 elif OPTS.mode == 'destroy':
Joe Gordondb63b1c2014-07-24 23:21:21 +0000581 collect_users(RES['users'])
582 destroy_resources()
Sean Dague655e0af2014-05-29 09:00:22 -0400583 else:
584 LOG.error('Unknown mode %s' % OPTS.mode)
585 return 1
Joe Gordon246353a2014-07-18 00:10:28 +0200586 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -0400587 return 0
588
589if __name__ == "__main__":
590 sys.exit(main())