blob: ea85816a71a94320b5884c7d1b576361d0a0c601 [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
Joe Gordon618c9fb2014-07-16 15:40:01 +0200172 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400173 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()
Sean Dague319b37a2014-07-11 07:28:11 -0400195 # TODO(sdague): Volumes not yet working, bring it back once the
196 # code is self testing.
197 # self.check_volumes()
Sean Dague655e0af2014-05-29 09:00:22 -0400198
199 def check_users(self):
200 """Check that the users we expect to exist, do.
201
202 We don't use the resource list for this because we need to validate
203 that things like tenantId didn't drift across versions.
204 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200205 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400206 for name, user in self.users.iteritems():
207 client = keystone_admin()
208 _, found = client.identity.get_user(user['id'])
209 self.assertEqual(found['name'], user['name'])
210 self.assertEqual(found['tenantId'], user['tenant_id'])
211
212 # also ensure we can auth with that user, and do something
213 # on the cloud. We don't care about the results except that it
214 # remains authorized.
215 client = client_for_user(user['name'])
216 resp, body = client.servers.list_servers()
217 self.assertEqual(resp['status'], '200')
218
219 def check_objects(self):
220 """Check that the objects created are still there."""
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000221 if 'objects' not in self.res:
222 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200223 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400224 for obj in self.res['objects']:
225 client = client_for_user(obj['owner'])
226 r, contents = client.objects.get_object(
227 obj['container'], obj['name'])
228 source = _file_contents(obj['file'])
229 self.assertEqual(contents, source)
230
231 def check_servers(self):
232 """Check that the servers are still up and running."""
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000233 if 'servers' not in self.res:
234 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200235 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400236 for server in self.res['servers']:
237 client = client_for_user(server['owner'])
238 found = _get_server_by_name(client, server['name'])
239 self.assertIsNotNone(
240 found,
241 "Couldn't find expected server %s" % server['name'])
242
243 r, found = client.servers.get_server(found['id'])
244 # get the ipv4 address
245 addr = found['addresses']['private'][0]['addr']
246 self.assertEqual(os.system("ping -c 1 " + addr), 0,
247 "Server %s is not pingable at %s" % (
248 server['name'], addr))
249
Emilien Macchi626b4f82014-06-15 21:44:29 +0200250 def check_volumes(self):
251 """Check that the volumes are still there and attached."""
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000252 if 'volumes' not in self.res:
253 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200254 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200255 for volume in self.res['volumes']:
256 client = client_for_user(volume['owner'])
257 found = _get_volume_by_name(client, volume['name'])
258 self.assertIsNotNone(
259 found,
260 "Couldn't find expected volume %s" % volume['name'])
261
262 # Verify that a volume's attachment retrieved
263 server_id = _get_server_by_name(client, volume['server'])['id']
264 attachment = self.client.get_attachment_from_volume(volume)
265 self.assertEqual(volume['id'], attachment['volume_id'])
266 self.assertEqual(server_id, attachment['server_id'])
267
Sean Dague655e0af2014-05-29 09:00:22 -0400268
269#######################
270#
271# OBJECTS
272#
273#######################
274
275
276def _file_contents(fname):
277 with open(fname, 'r') as f:
278 return f.read()
279
280
281def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000282 if not objects:
283 return
Sean Dague655e0af2014-05-29 09:00:22 -0400284 LOG.info("Creating objects")
285 for obj in objects:
286 LOG.debug("Object %s" % obj)
287 _assign_swift_role(obj['owner'])
288 client = client_for_user(obj['owner'])
289 client.containers.create_container(obj['container'])
290 client.objects.create_object(
291 obj['container'], obj['name'],
292 _file_contents(obj['file']))
293
294#######################
295#
296# IMAGES
297#
298#######################
299
300
Sean Dague319b37a2014-07-11 07:28:11 -0400301def _resolve_image(image, imgtype):
302 name = image[imgtype]
303 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
304 return name, fname
305
306
Sean Dague655e0af2014-05-29 09:00:22 -0400307def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000308 if not images:
309 return
Sean Dague655e0af2014-05-29 09:00:22 -0400310 for image in images:
311 client = client_for_user(image['owner'])
312
313 # only upload a new image if the name isn't there
314 r, body = client.images.image_list()
315 names = [x['name'] for x in body]
316 if image['name'] in names:
317 continue
318
319 # special handling for 3 part image
320 extras = {}
321 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400322 name, fname = _resolve_image(image, 'aki')
Sean Dague655e0af2014-05-29 09:00:22 -0400323 r, aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400324 'javelin_' + name, 'aki', 'aki')
325 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400326 extras['kernel_id'] = aki.get('id')
327
Sean Dague319b37a2014-07-11 07:28:11 -0400328 name, fname = _resolve_image(image, 'ari')
Sean Dague655e0af2014-05-29 09:00:22 -0400329 r, ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400330 'javelin_' + name, 'ari', 'ari')
331 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400332 extras['ramdisk_id'] = ari.get('id')
333
Sean Dague319b37a2014-07-11 07:28:11 -0400334 _, fname = _resolve_image(image, 'file')
Sean Dague655e0af2014-05-29 09:00:22 -0400335 r, body = client.images.create_image(
336 image['name'], image['format'], image['format'], **extras)
337 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400338 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400339
340
341#######################
342#
343# SERVERS
344#
345#######################
346
347def _get_server_by_name(client, name):
348 r, body = client.servers.list_servers()
349 for server in body['servers']:
350 if name == server['name']:
351 return server
352 return None
353
354
355def _get_image_by_name(client, name):
356 r, body = client.images.image_list()
357 for image in body:
358 if name == image['name']:
359 return image
360 return None
361
362
363def _get_flavor_by_name(client, name):
364 r, body = client.flavors.list_flavors()
365 for flavor in body:
366 if name == flavor['name']:
367 return flavor
368 return None
369
370
371def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000372 if not servers:
373 return
Sean Dague655e0af2014-05-29 09:00:22 -0400374 for server in servers:
375 client = client_for_user(server['owner'])
376
377 if _get_server_by_name(client, server['name']):
378 continue
379
380 image_id = _get_image_by_name(client, server['image'])['id']
381 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
382 client.servers.create_server(server['name'], image_id, flavor_id)
383
384
385#######################
386#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200387# VOLUMES
388#
389#######################
390
391def _get_volume_by_name(client, name):
392 r, body = client.volumes.list_volumes()
393 for volume in body['volumes']:
394 if name == volume['name']:
395 return volume
396 return None
397
398
399def create_volumes(volumes):
400 for volume in volumes:
401 client = client_for_user(volume['owner'])
402
403 # only create a volume if the name isn't here
404 r, body = client.volumes.list_volumes()
405 if any(item['name'] == volume['name'] for item in body):
406 continue
407
408 client.volumes.create_volume(volume['name'], volume['size'])
409
410
411def attach_volumes(volumes):
412 for volume in volumes:
413 client = client_for_user(volume['owner'])
414
415 server_id = _get_server_by_name(client, volume['server'])['id']
416 client.volumes.attach_volume(volume['name'], server_id)
417
418
419#######################
420#
Sean Dague655e0af2014-05-29 09:00:22 -0400421# MAIN LOGIC
422#
423#######################
424
425def create_resources():
426 LOG.info("Creating Resources")
427 # first create keystone level resources, and we need to be admin
428 # for those.
429 create_tenants(RES['tenants'])
430 create_users(RES['users'])
431 collect_users(RES['users'])
432
433 # next create resources in a well known order
434 create_objects(RES['objects'])
435 create_images(RES['images'])
436 create_servers(RES['servers'])
Sean Dague319b37a2014-07-11 07:28:11 -0400437 # TODO(sdague): volumes definition doesn't work yet, bring it
438 # back once we're actually executing the code
439 # create_volumes(RES['volumes'])
440 # attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400441
442
443def get_options():
444 global OPTS
445 parser = argparse.ArgumentParser(
446 description='Create and validate a fixed set of OpenStack resources')
447 parser.add_argument('-m', '--mode',
448 metavar='<create|check|destroy>',
449 required=True,
450 help=('One of (create, check, destroy)'))
451 parser.add_argument('-r', '--resources',
452 required=True,
453 metavar='resourcefile.yaml',
454 help='Resources definition yaml file')
Sean Dague319b37a2014-07-11 07:28:11 -0400455 parser.add_argument(
456 '-d', '--devstack-base',
457 required=True,
458 metavar='/opt/stack/old',
459 help='Devstack base directory for retrieving artifacts')
Sean Dague655e0af2014-05-29 09:00:22 -0400460 # auth bits, letting us also just source the devstack openrc
461 parser.add_argument('--os-username',
462 metavar='<auth-user-name>',
463 default=os.environ.get('OS_USERNAME'),
464 help=('Defaults to env[OS_USERNAME].'))
465 parser.add_argument('--os-password',
466 metavar='<auth-password>',
467 default=os.environ.get('OS_PASSWORD'),
468 help=('Defaults to env[OS_PASSWORD].'))
469 parser.add_argument('--os-tenant-name',
470 metavar='<auth-tenant-name>',
471 default=os.environ.get('OS_TENANT_NAME'),
472 help=('Defaults to env[OS_TENANT_NAME].'))
473
474 OPTS = parser.parse_args()
475 if OPTS.mode not in ('create', 'check', 'destroy'):
476 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
477 parser.print_help()
478 sys.exit(1)
479
480
481def setup_logging(debug=True):
482 global LOG
483 LOG = logging.getLogger(__name__)
484 if debug:
485 LOG.setLevel(logging.DEBUG)
486 else:
487 LOG.setLevel(logging.INFO)
488
489 ch = logging.StreamHandler(sys.stdout)
490 ch.setLevel(logging.DEBUG)
491 formatter = logging.Formatter(
492 datefmt='%Y-%m-%d %H:%M:%S',
493 fmt='%(asctime)s.%(msecs).03d - %(levelname)s - %(message)s')
494 ch.setFormatter(formatter)
495 LOG.addHandler(ch)
496
497
498def main():
499 global RES
500 get_options()
501 setup_logging()
502 RES = load_resources(OPTS.resources)
503
504 if OPTS.mode == 'create':
505 create_resources()
506 elif OPTS.mode == 'check':
507 collect_users(RES['users'])
508 checker = JavelinCheck(USERS, RES)
509 checker.check()
510 elif OPTS.mode == 'destroy':
511 LOG.warn("Destroy mode not yet implemented")
512 else:
513 LOG.error('Unknown mode %s' % OPTS.mode)
514 return 1
515 return 0
516
517if __name__ == "__main__":
518 sys.exit(main())