blob: 96bbd0327db5b3a6071a61ec341805714bbbdafb [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
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 Gordonb9bcdd82014-07-17 15:44:57 +0000222 if 'objects' not in self.res:
223 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 Gordonb9bcdd82014-07-17 15:44:57 +0000234 if 'servers' not in self.res:
235 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']
247 self.assertEqual(os.system("ping -c 1 " + addr), 0,
248 "Server %s is not pingable at %s" % (
249 server['name'], addr))
250
Emilien Macchi626b4f82014-06-15 21:44:29 +0200251 def check_volumes(self):
252 """Check that the volumes are still there and attached."""
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000253 if 'volumes' not in self.res:
254 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200255 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200256 for volume in self.res['volumes']:
257 client = client_for_user(volume['owner'])
258 found = _get_volume_by_name(client, volume['name'])
259 self.assertIsNotNone(
260 found,
261 "Couldn't find expected volume %s" % volume['name'])
262
263 # Verify that a volume's attachment retrieved
264 server_id = _get_server_by_name(client, volume['server'])['id']
265 attachment = self.client.get_attachment_from_volume(volume)
266 self.assertEqual(volume['id'], attachment['volume_id'])
267 self.assertEqual(server_id, attachment['server_id'])
268
Sean Dague655e0af2014-05-29 09:00:22 -0400269
270#######################
271#
272# OBJECTS
273#
274#######################
275
276
277def _file_contents(fname):
278 with open(fname, 'r') as f:
279 return f.read()
280
281
282def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000283 if not objects:
284 return
Sean Dague655e0af2014-05-29 09:00:22 -0400285 LOG.info("Creating objects")
286 for obj in objects:
287 LOG.debug("Object %s" % obj)
288 _assign_swift_role(obj['owner'])
289 client = client_for_user(obj['owner'])
290 client.containers.create_container(obj['container'])
291 client.objects.create_object(
292 obj['container'], obj['name'],
293 _file_contents(obj['file']))
294
295#######################
296#
297# IMAGES
298#
299#######################
300
301
Sean Dague319b37a2014-07-11 07:28:11 -0400302def _resolve_image(image, imgtype):
303 name = image[imgtype]
304 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
305 return name, fname
306
307
Sean Dague655e0af2014-05-29 09:00:22 -0400308def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000309 if not images:
310 return
Sean Dague655e0af2014-05-29 09:00:22 -0400311 for image in images:
312 client = client_for_user(image['owner'])
313
314 # only upload a new image if the name isn't there
315 r, body = client.images.image_list()
316 names = [x['name'] for x in body]
317 if image['name'] in names:
318 continue
319
320 # special handling for 3 part image
321 extras = {}
322 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400323 name, fname = _resolve_image(image, 'aki')
Sean Dague655e0af2014-05-29 09:00:22 -0400324 r, aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400325 'javelin_' + name, 'aki', 'aki')
326 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400327 extras['kernel_id'] = aki.get('id')
328
Sean Dague319b37a2014-07-11 07:28:11 -0400329 name, fname = _resolve_image(image, 'ari')
Sean Dague655e0af2014-05-29 09:00:22 -0400330 r, ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400331 'javelin_' + name, 'ari', 'ari')
332 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400333 extras['ramdisk_id'] = ari.get('id')
334
Sean Dague319b37a2014-07-11 07:28:11 -0400335 _, fname = _resolve_image(image, 'file')
Sean Dague655e0af2014-05-29 09:00:22 -0400336 r, body = client.images.create_image(
337 image['name'], image['format'], image['format'], **extras)
338 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400339 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400340
341
342#######################
343#
344# SERVERS
345#
346#######################
347
348def _get_server_by_name(client, name):
349 r, body = client.servers.list_servers()
350 for server in body['servers']:
351 if name == server['name']:
352 return server
353 return None
354
355
356def _get_image_by_name(client, name):
357 r, body = client.images.image_list()
358 for image in body:
359 if name == image['name']:
360 return image
361 return None
362
363
364def _get_flavor_by_name(client, name):
365 r, body = client.flavors.list_flavors()
366 for flavor in body:
367 if name == flavor['name']:
368 return flavor
369 return None
370
371
372def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000373 if not servers:
374 return
Sean Dague655e0af2014-05-29 09:00:22 -0400375 for server in servers:
376 client = client_for_user(server['owner'])
377
378 if _get_server_by_name(client, server['name']):
379 continue
380
381 image_id = _get_image_by_name(client, server['image'])['id']
382 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
383 client.servers.create_server(server['name'], image_id, flavor_id)
384
385
386#######################
387#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200388# VOLUMES
389#
390#######################
391
392def _get_volume_by_name(client, name):
393 r, body = client.volumes.list_volumes()
394 for volume in body['volumes']:
395 if name == volume['name']:
396 return volume
397 return None
398
399
400def create_volumes(volumes):
401 for volume in volumes:
402 client = client_for_user(volume['owner'])
403
404 # only create a volume if the name isn't here
405 r, body = client.volumes.list_volumes()
406 if any(item['name'] == volume['name'] for item in body):
407 continue
408
409 client.volumes.create_volume(volume['name'], volume['size'])
410
411
412def attach_volumes(volumes):
413 for volume in volumes:
414 client = client_for_user(volume['owner'])
415
416 server_id = _get_server_by_name(client, volume['server'])['id']
417 client.volumes.attach_volume(volume['name'], server_id)
418
419
420#######################
421#
Sean Dague655e0af2014-05-29 09:00:22 -0400422# MAIN LOGIC
423#
424#######################
425
426def create_resources():
427 LOG.info("Creating Resources")
428 # first create keystone level resources, and we need to be admin
429 # for those.
430 create_tenants(RES['tenants'])
431 create_users(RES['users'])
432 collect_users(RES['users'])
433
434 # next create resources in a well known order
435 create_objects(RES['objects'])
436 create_images(RES['images'])
437 create_servers(RES['servers'])
Sean Dague319b37a2014-07-11 07:28:11 -0400438 # TODO(sdague): volumes definition doesn't work yet, bring it
439 # back once we're actually executing the code
440 # create_volumes(RES['volumes'])
441 # attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400442
443
444def get_options():
445 global OPTS
446 parser = argparse.ArgumentParser(
447 description='Create and validate a fixed set of OpenStack resources')
448 parser.add_argument('-m', '--mode',
449 metavar='<create|check|destroy>',
450 required=True,
451 help=('One of (create, check, destroy)'))
452 parser.add_argument('-r', '--resources',
453 required=True,
454 metavar='resourcefile.yaml',
455 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000456
Sean Dague319b37a2014-07-11 07:28:11 -0400457 parser.add_argument(
458 '-d', '--devstack-base',
459 required=True,
460 metavar='/opt/stack/old',
461 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000462 parser.add_argument(
463 '-c', '--config-file',
464 metavar='/etc/tempest.conf',
465 help='path to javelin2(tempest) config file')
466
Sean Dague655e0af2014-05-29 09:00:22 -0400467 # auth bits, letting us also just source the devstack openrc
468 parser.add_argument('--os-username',
469 metavar='<auth-user-name>',
470 default=os.environ.get('OS_USERNAME'),
471 help=('Defaults to env[OS_USERNAME].'))
472 parser.add_argument('--os-password',
473 metavar='<auth-password>',
474 default=os.environ.get('OS_PASSWORD'),
475 help=('Defaults to env[OS_PASSWORD].'))
476 parser.add_argument('--os-tenant-name',
477 metavar='<auth-tenant-name>',
478 default=os.environ.get('OS_TENANT_NAME'),
479 help=('Defaults to env[OS_TENANT_NAME].'))
480
481 OPTS = parser.parse_args()
482 if OPTS.mode not in ('create', 'check', 'destroy'):
483 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
484 parser.print_help()
485 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +0000486 if OPTS.config_file:
487 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -0400488
489
490def setup_logging(debug=True):
491 global LOG
492 LOG = logging.getLogger(__name__)
493 if debug:
494 LOG.setLevel(logging.DEBUG)
495 else:
496 LOG.setLevel(logging.INFO)
497
498 ch = logging.StreamHandler(sys.stdout)
499 ch.setLevel(logging.DEBUG)
500 formatter = logging.Formatter(
501 datefmt='%Y-%m-%d %H:%M:%S',
502 fmt='%(asctime)s.%(msecs).03d - %(levelname)s - %(message)s')
503 ch.setFormatter(formatter)
504 LOG.addHandler(ch)
505
506
507def main():
508 global RES
509 get_options()
510 setup_logging()
511 RES = load_resources(OPTS.resources)
512
513 if OPTS.mode == 'create':
514 create_resources()
515 elif OPTS.mode == 'check':
516 collect_users(RES['users'])
517 checker = JavelinCheck(USERS, RES)
518 checker.check()
519 elif OPTS.mode == 'destroy':
520 LOG.warn("Destroy mode not yet implemented")
521 else:
522 LOG.error('Unknown mode %s' % OPTS.mode)
523 return 1
Joe Gordon246353a2014-07-18 00:10:28 +0200524 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -0400525 return 0
526
527if __name__ == "__main__":
528 sys.exit(main())