blob: 0b72b1c293b980760c8cd0cc576d7c1b7bcdc449 [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 Gordon618c9fb2014-07-16 15:40:01 +0200221 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400222 for obj in self.res['objects']:
223 client = client_for_user(obj['owner'])
224 r, contents = client.objects.get_object(
225 obj['container'], obj['name'])
226 source = _file_contents(obj['file'])
227 self.assertEqual(contents, source)
228
229 def check_servers(self):
230 """Check that the servers are still up and running."""
Joe Gordon618c9fb2014-07-16 15:40:01 +0200231 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400232 for server in self.res['servers']:
233 client = client_for_user(server['owner'])
234 found = _get_server_by_name(client, server['name'])
235 self.assertIsNotNone(
236 found,
237 "Couldn't find expected server %s" % server['name'])
238
239 r, found = client.servers.get_server(found['id'])
240 # get the ipv4 address
241 addr = found['addresses']['private'][0]['addr']
242 self.assertEqual(os.system("ping -c 1 " + addr), 0,
243 "Server %s is not pingable at %s" % (
244 server['name'], addr))
245
Emilien Macchi626b4f82014-06-15 21:44:29 +0200246 def check_volumes(self):
247 """Check that the volumes are still there and attached."""
Joe Gordon618c9fb2014-07-16 15:40:01 +0200248 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200249 for volume in self.res['volumes']:
250 client = client_for_user(volume['owner'])
251 found = _get_volume_by_name(client, volume['name'])
252 self.assertIsNotNone(
253 found,
254 "Couldn't find expected volume %s" % volume['name'])
255
256 # Verify that a volume's attachment retrieved
257 server_id = _get_server_by_name(client, volume['server'])['id']
258 attachment = self.client.get_attachment_from_volume(volume)
259 self.assertEqual(volume['id'], attachment['volume_id'])
260 self.assertEqual(server_id, attachment['server_id'])
261
Sean Dague655e0af2014-05-29 09:00:22 -0400262
263#######################
264#
265# OBJECTS
266#
267#######################
268
269
270def _file_contents(fname):
271 with open(fname, 'r') as f:
272 return f.read()
273
274
275def create_objects(objects):
276 LOG.info("Creating objects")
277 for obj in objects:
278 LOG.debug("Object %s" % obj)
279 _assign_swift_role(obj['owner'])
280 client = client_for_user(obj['owner'])
281 client.containers.create_container(obj['container'])
282 client.objects.create_object(
283 obj['container'], obj['name'],
284 _file_contents(obj['file']))
285
286#######################
287#
288# IMAGES
289#
290#######################
291
292
Sean Dague319b37a2014-07-11 07:28:11 -0400293def _resolve_image(image, imgtype):
294 name = image[imgtype]
295 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
296 return name, fname
297
298
Sean Dague655e0af2014-05-29 09:00:22 -0400299def create_images(images):
300 for image in images:
301 client = client_for_user(image['owner'])
302
303 # only upload a new image if the name isn't there
304 r, body = client.images.image_list()
305 names = [x['name'] for x in body]
306 if image['name'] in names:
307 continue
308
309 # special handling for 3 part image
310 extras = {}
311 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400312 name, fname = _resolve_image(image, 'aki')
Sean Dague655e0af2014-05-29 09:00:22 -0400313 r, aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400314 'javelin_' + name, 'aki', 'aki')
315 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400316 extras['kernel_id'] = aki.get('id')
317
Sean Dague319b37a2014-07-11 07:28:11 -0400318 name, fname = _resolve_image(image, 'ari')
Sean Dague655e0af2014-05-29 09:00:22 -0400319 r, ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400320 'javelin_' + name, 'ari', 'ari')
321 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400322 extras['ramdisk_id'] = ari.get('id')
323
Sean Dague319b37a2014-07-11 07:28:11 -0400324 _, fname = _resolve_image(image, 'file')
Sean Dague655e0af2014-05-29 09:00:22 -0400325 r, body = client.images.create_image(
326 image['name'], image['format'], image['format'], **extras)
327 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400328 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400329
330
331#######################
332#
333# SERVERS
334#
335#######################
336
337def _get_server_by_name(client, name):
338 r, body = client.servers.list_servers()
339 for server in body['servers']:
340 if name == server['name']:
341 return server
342 return None
343
344
345def _get_image_by_name(client, name):
346 r, body = client.images.image_list()
347 for image in body:
348 if name == image['name']:
349 return image
350 return None
351
352
353def _get_flavor_by_name(client, name):
354 r, body = client.flavors.list_flavors()
355 for flavor in body:
356 if name == flavor['name']:
357 return flavor
358 return None
359
360
361def create_servers(servers):
362 for server in servers:
363 client = client_for_user(server['owner'])
364
365 if _get_server_by_name(client, server['name']):
366 continue
367
368 image_id = _get_image_by_name(client, server['image'])['id']
369 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
370 client.servers.create_server(server['name'], image_id, flavor_id)
371
372
373#######################
374#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200375# VOLUMES
376#
377#######################
378
379def _get_volume_by_name(client, name):
380 r, body = client.volumes.list_volumes()
381 for volume in body['volumes']:
382 if name == volume['name']:
383 return volume
384 return None
385
386
387def create_volumes(volumes):
388 for volume in volumes:
389 client = client_for_user(volume['owner'])
390
391 # only create a volume if the name isn't here
392 r, body = client.volumes.list_volumes()
393 if any(item['name'] == volume['name'] for item in body):
394 continue
395
396 client.volumes.create_volume(volume['name'], volume['size'])
397
398
399def attach_volumes(volumes):
400 for volume in volumes:
401 client = client_for_user(volume['owner'])
402
403 server_id = _get_server_by_name(client, volume['server'])['id']
404 client.volumes.attach_volume(volume['name'], server_id)
405
406
407#######################
408#
Sean Dague655e0af2014-05-29 09:00:22 -0400409# MAIN LOGIC
410#
411#######################
412
413def create_resources():
414 LOG.info("Creating Resources")
415 # first create keystone level resources, and we need to be admin
416 # for those.
417 create_tenants(RES['tenants'])
418 create_users(RES['users'])
419 collect_users(RES['users'])
420
421 # next create resources in a well known order
422 create_objects(RES['objects'])
423 create_images(RES['images'])
424 create_servers(RES['servers'])
Sean Dague319b37a2014-07-11 07:28:11 -0400425 # TODO(sdague): volumes definition doesn't work yet, bring it
426 # back once we're actually executing the code
427 # create_volumes(RES['volumes'])
428 # attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400429
430
431def get_options():
432 global OPTS
433 parser = argparse.ArgumentParser(
434 description='Create and validate a fixed set of OpenStack resources')
435 parser.add_argument('-m', '--mode',
436 metavar='<create|check|destroy>',
437 required=True,
438 help=('One of (create, check, destroy)'))
439 parser.add_argument('-r', '--resources',
440 required=True,
441 metavar='resourcefile.yaml',
442 help='Resources definition yaml file')
Sean Dague319b37a2014-07-11 07:28:11 -0400443 parser.add_argument(
444 '-d', '--devstack-base',
445 required=True,
446 metavar='/opt/stack/old',
447 help='Devstack base directory for retrieving artifacts')
Sean Dague655e0af2014-05-29 09:00:22 -0400448 # auth bits, letting us also just source the devstack openrc
449 parser.add_argument('--os-username',
450 metavar='<auth-user-name>',
451 default=os.environ.get('OS_USERNAME'),
452 help=('Defaults to env[OS_USERNAME].'))
453 parser.add_argument('--os-password',
454 metavar='<auth-password>',
455 default=os.environ.get('OS_PASSWORD'),
456 help=('Defaults to env[OS_PASSWORD].'))
457 parser.add_argument('--os-tenant-name',
458 metavar='<auth-tenant-name>',
459 default=os.environ.get('OS_TENANT_NAME'),
460 help=('Defaults to env[OS_TENANT_NAME].'))
461
462 OPTS = parser.parse_args()
463 if OPTS.mode not in ('create', 'check', 'destroy'):
464 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
465 parser.print_help()
466 sys.exit(1)
467
468
469def setup_logging(debug=True):
470 global LOG
471 LOG = logging.getLogger(__name__)
472 if debug:
473 LOG.setLevel(logging.DEBUG)
474 else:
475 LOG.setLevel(logging.INFO)
476
477 ch = logging.StreamHandler(sys.stdout)
478 ch.setLevel(logging.DEBUG)
479 formatter = logging.Formatter(
480 datefmt='%Y-%m-%d %H:%M:%S',
481 fmt='%(asctime)s.%(msecs).03d - %(levelname)s - %(message)s')
482 ch.setFormatter(formatter)
483 LOG.addHandler(ch)
484
485
486def main():
487 global RES
488 get_options()
489 setup_logging()
490 RES = load_resources(OPTS.resources)
491
492 if OPTS.mode == 'create':
493 create_resources()
494 elif OPTS.mode == 'check':
495 collect_users(RES['users'])
496 checker = JavelinCheck(USERS, RES)
497 checker.check()
498 elif OPTS.mode == 'destroy':
499 LOG.warn("Destroy mode not yet implemented")
500 else:
501 LOG.error('Unknown mode %s' % OPTS.mode)
502 return 1
503 return 0
504
505if __name__ == "__main__":
506 sys.exit(main())