blob: 17b6c19000c426b1c728a380366991a3734ff630 [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
Joe H. Rahme61469fd2014-12-02 17:09:17 +010015"""Javelin is a tool for creating, verifying, and deleting a small set of
Sean Dague655e0af2014-05-29 09:00:22 -040016resources in a declarative way.
17
Joe H. Rahme61469fd2014-12-02 17:09:17 +010018Javelin is meant to be used as a way to validate quickly that resources can
19survive an upgrade process.
20
21Authentication
22--------------
23
24Javelin will be creating (and removing) users and tenants so it needs the admin
25credentials of your cloud to operate properly. The corresponding info can be
26given the usual way, either through CLI options or environment variables.
27
28You're probably familiar with these, but just in case::
29
30 +----------+------------------+----------------------+
31 | Param | CLI | Environment Variable |
32 +----------+------------------+----------------------+
33 | Username | --os-username | OS_USERNAME |
34 | Password | --os-password | OS_PASSWORD |
35 | Tenant | --os-tenant-name | OS_TENANT_NAME |
36 +----------+------------------+----------------------+
37
38
39Runtime Arguments
40-----------------
41
42**-m/--mode**: (Required) Has to be one of 'check', 'create' or 'destroy'. It
43indicates which actions javelin is going to perform.
44
45**-r/--resources**: (Required) The path to a YAML file describing the resources
46used by Javelin.
47
48**-d/--devstack-base**: (Required) The path to the devstack repo used to
49retrieve artefacts (like images) that will be referenced in the resource files.
50
51**-c/--config-file**: (Optional) The path to a valid Tempest config file
52describing your cloud. Javelin may use this to determine if certain services
53are enabled and modify its behavior accordingly.
54
55
56Resource file
57-------------
58
59The resource file is a valid YAML file describing the resources that will be
60created, checked and destroyed by javelin. Here's a canonical example of a
61resource file::
62
63 tenants:
64 - javelin
65 - discuss
66
67 users:
68 - name: javelin
69 pass: gungnir
70 tenant: javelin
71 - name: javelin2
72 pass: gungnir2
73 tenant: discuss
74
75 # resources that we want to create
76 images:
77 - name: javelin_cirros
78 owner: javelin
79 file: cirros-0.3.2-x86_64-blank.img
80 format: ami
81 aki: cirros-0.3.2-x86_64-vmlinuz
82 ari: cirros-0.3.2-x86_64-initrd
83
84 servers:
85 - name: peltast
86 owner: javelin
87 flavor: m1.small
88 image: javelin_cirros
89 - name: hoplite
90 owner: javelin
91 flavor: m1.medium
92 image: javelin_cirros
93
94
95An important piece of the resource definition is the *owner* field, which is
96the user (that we've created) that is the owner of that resource. All
97operations on that resource will happen as that regular user to ensure that
98admin level access does not mask issues.
99
100The check phase will act like a unit test, using well known assert methods to
101verify that the correct resources exist.
102
Sean Dague655e0af2014-05-29 09:00:22 -0400103"""
104
Matthew Treinish96e9e882014-06-09 18:37:19 -0400105import argparse
Chris Dent51e76de2014-10-01 12:07:14 +0100106import collections
Chris Dent878f3782014-06-30 17:04:15 +0100107import datetime
Sean Dague655e0af2014-05-29 09:00:22 -0400108import os
109import sys
110import unittest
Sean Dague655e0af2014-05-29 09:00:22 -0400111
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200112import netaddr
Matthew Treinish96e9e882014-06-09 18:37:19 -0400113import yaml
Sean Dague655e0af2014-05-29 09:00:22 -0400114
115import tempest.auth
Joe Gordon28a84ae2014-07-17 15:38:28 +0000116from tempest import config
Sean Dague655e0af2014-05-29 09:00:22 -0400117from tempest import exceptions
Joe Gordon915eb8e2014-07-17 11:25:46 +0200118from tempest.openstack.common import log as logging
Chris Dent878f3782014-06-30 17:04:15 +0100119from tempest.openstack.common import timeutils
Sean Dague655e0af2014-05-29 09:00:22 -0400120from tempest.services.compute.json import flavors_client
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200121from tempest.services.compute.json import security_groups_client
Sean Dague655e0af2014-05-29 09:00:22 -0400122from tempest.services.compute.json import servers_client
123from tempest.services.identity.json import identity_client
124from tempest.services.image.v2.json import image_client
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200125from tempest.services.network.json import network_client
Sean Dague655e0af2014-05-29 09:00:22 -0400126from tempest.services.object_storage import container_client
127from tempest.services.object_storage import object_client
Chris Dent878f3782014-06-30 17:04:15 +0100128from tempest.services.telemetry.json import telemetry_client
Emilien Macchi626b4f82014-06-15 21:44:29 +0200129from tempest.services.volume.json import volumes_client
Sean Dague655e0af2014-05-29 09:00:22 -0400130
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200131CONF = config.CONF
Sean Dague655e0af2014-05-29 09:00:22 -0400132OPTS = {}
133USERS = {}
Chris Dent51e76de2014-10-01 12:07:14 +0100134RES = collections.defaultdict(list)
Sean Dague655e0af2014-05-29 09:00:22 -0400135
136LOG = None
137
Chris Dent878f3782014-06-30 17:04:15 +0100138JAVELIN_START = datetime.datetime.utcnow()
139
Sean Dague655e0af2014-05-29 09:00:22 -0400140
141class OSClient(object):
142 _creds = None
143 identity = None
144 servers = None
145
146 def __init__(self, user, pw, tenant):
147 _creds = tempest.auth.KeystoneV2Credentials(
148 username=user,
149 password=pw,
150 tenant_name=tenant)
151 _auth = tempest.auth.KeystoneV2AuthProvider(_creds)
152 self.identity = identity_client.IdentityClientJSON(_auth)
153 self.servers = servers_client.ServersClientJSON(_auth)
154 self.objects = object_client.ObjectClient(_auth)
155 self.containers = container_client.ContainerClient(_auth)
156 self.images = image_client.ImageClientV2JSON(_auth)
157 self.flavors = flavors_client.FlavorsClientJSON(_auth)
Chris Dent878f3782014-06-30 17:04:15 +0100158 self.telemetry = telemetry_client.TelemetryClientJSON(_auth)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200159 self.secgroups = security_groups_client.SecurityGroupsClientJSON(_auth)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200160 self.volumes = volumes_client.VolumesClientJSON(_auth)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200161 self.networks = network_client.NetworkClientJSON(_auth)
Sean Dague655e0af2014-05-29 09:00:22 -0400162
163
164def load_resources(fname):
165 """Load the expected resources from a yaml flie."""
166 return yaml.load(open(fname, 'r'))
167
168
169def keystone_admin():
170 return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
171
172
173def client_for_user(name):
174 LOG.debug("Entering client_for_user")
175 if name in USERS:
176 user = USERS[name]
177 LOG.debug("Created client for user %s" % user)
178 return OSClient(user['name'], user['pass'], user['tenant'])
179 else:
180 LOG.error("%s not found in USERS: %s" % (name, USERS))
181
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200182
183def resp_ok(response):
184 return 200 >= int(response['status']) < 300
185
Sean Dague655e0af2014-05-29 09:00:22 -0400186###################
187#
188# TENANTS
189#
190###################
191
192
193def create_tenants(tenants):
194 """Create tenants from resource definition.
195
196 Don't create the tenants if they already exist.
197 """
198 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500199 body = admin.identity.list_tenants()
Sean Dague655e0af2014-05-29 09:00:22 -0400200 existing = [x['name'] for x in body]
201 for tenant in tenants:
202 if tenant not in existing:
203 admin.identity.create_tenant(tenant)
204 else:
205 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
206
Emilien Macchibb71e072014-07-05 19:18:52 +0200207
208def destroy_tenants(tenants):
209 admin = keystone_admin()
210 for tenant in tenants:
211 tenant_id = admin.identity.get_tenant_by_name(tenant)['id']
David Kranzb7afa922014-12-30 10:56:26 -0500212 admin.identity.delete_tenant(tenant_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200213
Sean Dague655e0af2014-05-29 09:00:22 -0400214##############
215#
216# USERS
217#
218##############
219
220
221def _users_for_tenant(users, tenant):
222 u_for_t = []
223 for user in users:
224 for n in user:
225 if user[n]['tenant'] == tenant:
226 u_for_t.append(user[n])
227 return u_for_t
228
229
230def _tenants_from_users(users):
231 tenants = set()
232 for user in users:
233 for n in user:
234 tenants.add(user[n]['tenant'])
235 return tenants
236
237
238def _assign_swift_role(user):
239 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500240 roles = admin.identity.list_roles()
Sean Dague655e0af2014-05-29 09:00:22 -0400241 role = next(r for r in roles if r['name'] == 'Member')
242 LOG.debug(USERS[user])
243 try:
244 admin.identity.assign_user_role(
245 USERS[user]['tenant_id'],
246 USERS[user]['id'],
247 role['id'])
248 except exceptions.Conflict:
249 # don't care if it's already assigned
250 pass
251
252
253def create_users(users):
254 """Create tenants from resource definition.
255
256 Don't create the tenants if they already exist.
257 """
258 global USERS
259 LOG.info("Creating users")
260 admin = keystone_admin()
261 for u in users:
262 try:
263 tenant = admin.identity.get_tenant_by_name(u['tenant'])
264 except exceptions.NotFound:
265 LOG.error("Tenant: %s - not found" % u['tenant'])
266 continue
267 try:
268 admin.identity.get_user_by_username(tenant['id'], u['name'])
269 LOG.warn("User '%s' already exists in this environment"
270 % u['name'])
271 except exceptions.NotFound:
272 admin.identity.create_user(
273 u['name'], u['pass'], tenant['id'],
274 "%s@%s" % (u['name'], tenant['id']),
275 enabled=True)
276
277
Emilien Macchibb71e072014-07-05 19:18:52 +0200278def destroy_users(users):
279 admin = keystone_admin()
280 for user in users:
Emilien Macchi436de862014-09-30 17:09:50 -0400281 tenant_id = admin.identity.get_tenant_by_name(user['tenant'])['id']
282 user_id = admin.identity.get_user_by_username(tenant_id,
283 user['name'])['id']
David Kranzb7afa922014-12-30 10:56:26 -0500284 admin.identity.delete_user(user_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200285
286
Sean Dague655e0af2014-05-29 09:00:22 -0400287def collect_users(users):
288 global USERS
Joe Gordon618c9fb2014-07-16 15:40:01 +0200289 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400290 admin = keystone_admin()
291 for u in users:
292 tenant = admin.identity.get_tenant_by_name(u['tenant'])
293 u['tenant_id'] = tenant['id']
294 USERS[u['name']] = u
295 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
296 USERS[u['name']]['id'] = body['id']
297
298
299class JavelinCheck(unittest.TestCase):
300 def __init__(self, users, resources):
301 super(JavelinCheck, self).__init__()
302 self.users = users
303 self.res = resources
304
305 def runTest(self, *args):
306 pass
307
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200308 def _ping_ip(self, ip_addr, count, namespace=None):
309 if namespace is None:
310 ping_cmd = "ping -c1 " + ip_addr
311 else:
312 ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
313 ip_addr)
314 for current in range(count):
315 return_code = os.system(ping_cmd)
316 if return_code is 0:
317 break
318 self.assertNotEqual(current, count - 1,
319 "Server is not pingable at %s" % ip_addr)
320
Sean Dague655e0af2014-05-29 09:00:22 -0400321 def check(self):
322 self.check_users()
323 self.check_objects()
324 self.check_servers()
Emilien Macchid18fec12014-09-15 14:32:54 -0400325 self.check_volumes()
Chris Dent878f3782014-06-30 17:04:15 +0100326 self.check_telemetry()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200327 self.check_secgroups()
328
329 # validate neutron is enabled and ironic disabled:
330 # Tenant network isolation is not supported when using ironic.
331 # "admin" has set up a neutron flat network environment within a shared
332 # fixed network for all tenants to use.
333 # In this case, network/subnet/router creation can be skipped and the
334 # server booted the same as nova network.
335 if (CONF.service_available.neutron and
336 not CONF.baremetal.driver_enabled):
337 self.check_networking()
Sean Dague655e0af2014-05-29 09:00:22 -0400338
339 def check_users(self):
340 """Check that the users we expect to exist, do.
341
342 We don't use the resource list for this because we need to validate
343 that things like tenantId didn't drift across versions.
344 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200345 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400346 for name, user in self.users.iteritems():
347 client = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500348 found = client.identity.get_user(user['id'])
Sean Dague655e0af2014-05-29 09:00:22 -0400349 self.assertEqual(found['name'], user['name'])
350 self.assertEqual(found['tenantId'], user['tenant_id'])
351
352 # also ensure we can auth with that user, and do something
353 # on the cloud. We don't care about the results except that it
354 # remains authorized.
355 client = client_for_user(user['name'])
356 resp, body = client.servers.list_servers()
357 self.assertEqual(resp['status'], '200')
358
359 def check_objects(self):
360 """Check that the objects created are still there."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000361 if not self.res.get('objects'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000362 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200363 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400364 for obj in self.res['objects']:
365 client = client_for_user(obj['owner'])
366 r, contents = client.objects.get_object(
367 obj['container'], obj['name'])
368 source = _file_contents(obj['file'])
369 self.assertEqual(contents, source)
370
371 def check_servers(self):
372 """Check that the servers are still up and running."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000373 if not self.res.get('servers'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000374 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200375 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400376 for server in self.res['servers']:
377 client = client_for_user(server['owner'])
378 found = _get_server_by_name(client, server['name'])
379 self.assertIsNotNone(
380 found,
381 "Couldn't find expected server %s" % server['name'])
382
383 r, found = client.servers.get_server(found['id'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200384 # validate neutron is enabled and ironic disabled:
385 if (CONF.service_available.neutron and
386 not CONF.baremetal.driver_enabled):
387 for network_name, body in found['addresses'].items():
388 for addr in body:
389 ip = addr['addr']
390 if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
391 namespace = _get_router_namespace(client,
392 network_name)
393 self._ping_ip(ip, 60, namespace)
394 else:
395 self._ping_ip(ip, 60)
396 else:
397 addr = found['addresses']['private'][0]['addr']
398 self._ping_ip(addr, 60)
399
400 def check_secgroups(self):
401 """Check that the security groups are still existing."""
402 LOG.info("Checking security groups")
403 for secgroup in self.res['secgroups']:
404 client = client_for_user(secgroup['owner'])
405 found = _get_resource_by_name(client.secgroups, 'security_groups',
406 secgroup['name'])
407 self.assertIsNotNone(
408 found,
409 "Couldn't find expected secgroup %s" % secgroup['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400410
Chris Dent878f3782014-06-30 17:04:15 +0100411 def check_telemetry(self):
412 """Check that ceilometer provides a sane sample.
413
414 Confirm that there are more than one sample and that they have the
415 expected metadata.
416
417 If in check mode confirm that the oldest sample available is from
418 before the upgrade.
419 """
Chris Dent0b76f2f2014-10-10 14:24:28 +0100420 if not self.res.get('telemetry'):
421 return
Chris Dent878f3782014-06-30 17:04:15 +0100422 LOG.info("checking telemetry")
423 for server in self.res['servers']:
424 client = client_for_user(server['owner'])
425 response, body = client.telemetry.list_samples(
426 'instance',
427 query=('metadata.display_name', 'eq', server['name'])
428 )
429 self.assertEqual(response.status, 200)
430 self.assertTrue(len(body) >= 1, 'expecting at least one sample')
431 self._confirm_telemetry_sample(server, body[-1])
432
Emilien Macchi626b4f82014-06-15 21:44:29 +0200433 def check_volumes(self):
434 """Check that the volumes are still there and attached."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000435 if not self.res.get('volumes'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000436 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200437 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200438 for volume in self.res['volumes']:
439 client = client_for_user(volume['owner'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400440 vol_body = _get_volume_by_name(client, volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200441 self.assertIsNotNone(
Emilien Macchid18fec12014-09-15 14:32:54 -0400442 vol_body,
Emilien Macchi626b4f82014-06-15 21:44:29 +0200443 "Couldn't find expected volume %s" % volume['name'])
444
445 # Verify that a volume's attachment retrieved
446 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400447 attachment = client.volumes.get_attachment_from_volume(vol_body)
448 self.assertEqual(vol_body['id'], attachment['volume_id'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200449 self.assertEqual(server_id, attachment['server_id'])
450
Chris Dent878f3782014-06-30 17:04:15 +0100451 def _confirm_telemetry_sample(self, server, sample):
452 """Check this sample matches the expected resource metadata."""
453 # Confirm display_name
454 self.assertEqual(server['name'],
455 sample['resource_metadata']['display_name'])
456 # Confirm instance_type of flavor
457 flavor = sample['resource_metadata'].get(
458 'flavor.name',
459 sample['resource_metadata'].get('instance_type')
460 )
461 self.assertEqual(server['flavor'], flavor)
462 # Confirm the oldest sample was created before upgrade.
463 if OPTS.mode == 'check':
464 oldest_timestamp = timeutils.normalize_time(
465 timeutils.parse_isotime(sample['timestamp']))
466 self.assertTrue(
467 oldest_timestamp < JAVELIN_START,
468 'timestamp should come before start of second javelin run'
469 )
470
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200471 def check_networking(self):
472 """Check that the networks are still there."""
473 for res_type in ('networks', 'subnets', 'routers'):
474 for res in self.res[res_type]:
475 client = client_for_user(res['owner'])
476 found = _get_resource_by_name(client.networks, res_type,
477 res['name'])
478 self.assertIsNotNone(
479 found,
480 "Couldn't find expected resource %s" % res['name'])
481
Sean Dague655e0af2014-05-29 09:00:22 -0400482
483#######################
484#
485# OBJECTS
486#
487#######################
488
489
490def _file_contents(fname):
491 with open(fname, 'r') as f:
492 return f.read()
493
494
495def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000496 if not objects:
497 return
Sean Dague655e0af2014-05-29 09:00:22 -0400498 LOG.info("Creating objects")
499 for obj in objects:
500 LOG.debug("Object %s" % obj)
501 _assign_swift_role(obj['owner'])
502 client = client_for_user(obj['owner'])
503 client.containers.create_container(obj['container'])
504 client.objects.create_object(
505 obj['container'], obj['name'],
506 _file_contents(obj['file']))
507
Emilien Macchibb71e072014-07-05 19:18:52 +0200508
509def destroy_objects(objects):
510 for obj in objects:
511 client = client_for_user(obj['owner'])
512 r, body = client.objects.delete_object(obj['container'], obj['name'])
Emilien Macchid70f5102014-09-10 09:54:49 -0400513 if not (200 <= int(r['status']) < 299):
Emilien Macchibb71e072014-07-05 19:18:52 +0200514 raise ValueError("unable to destroy object: [%s] %s" % (r, body))
515
516
Sean Dague655e0af2014-05-29 09:00:22 -0400517#######################
518#
519# IMAGES
520#
521#######################
522
523
Sean Dague319b37a2014-07-11 07:28:11 -0400524def _resolve_image(image, imgtype):
525 name = image[imgtype]
526 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
527 return name, fname
528
529
Joe Gordon6f0426c2014-07-25 01:10:28 +0000530def _get_image_by_name(client, name):
531 r, body = client.images.image_list()
532 for image in body:
533 if name == image['name']:
534 return image
535 return None
536
537
Sean Dague655e0af2014-05-29 09:00:22 -0400538def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000539 if not images:
540 return
Joe Gordona18d6862014-07-24 22:55:46 +0000541 LOG.info("Creating images")
Sean Dague655e0af2014-05-29 09:00:22 -0400542 for image in images:
543 client = client_for_user(image['owner'])
544
545 # only upload a new image if the name isn't there
Joe Gordon6f0426c2014-07-25 01:10:28 +0000546 if _get_image_by_name(client, image['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000547 LOG.info("Image '%s' already exists" % image['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400548 continue
549
550 # special handling for 3 part image
551 extras = {}
552 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400553 name, fname = _resolve_image(image, 'aki')
Sean Dague655e0af2014-05-29 09:00:22 -0400554 r, aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400555 'javelin_' + name, 'aki', 'aki')
556 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400557 extras['kernel_id'] = aki.get('id')
558
Sean Dague319b37a2014-07-11 07:28:11 -0400559 name, fname = _resolve_image(image, 'ari')
Sean Dague655e0af2014-05-29 09:00:22 -0400560 r, ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400561 'javelin_' + name, 'ari', 'ari')
562 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400563 extras['ramdisk_id'] = ari.get('id')
564
Sean Dague319b37a2014-07-11 07:28:11 -0400565 _, fname = _resolve_image(image, 'file')
Sean Dague655e0af2014-05-29 09:00:22 -0400566 r, body = client.images.create_image(
567 image['name'], image['format'], image['format'], **extras)
568 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400569 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400570
571
Joe Gordon6f0426c2014-07-25 01:10:28 +0000572def destroy_images(images):
573 if not images:
574 return
575 LOG.info("Destroying images")
576 for image in images:
577 client = client_for_user(image['owner'])
578
579 response = _get_image_by_name(client, image['name'])
580 if not response:
581 LOG.info("Image '%s' does not exists" % image['name'])
582 continue
583 client.images.delete_image(response['id'])
584
585
Sean Dague655e0af2014-05-29 09:00:22 -0400586#######################
587#
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200588# NETWORKS
589#
590#######################
591
592def _get_router_namespace(client, network):
593 network_id = _get_resource_by_name(client.networks,
594 'networks', network)['id']
David Kranz34e88122014-12-11 15:24:05 -0500595 n_body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200596 for router in n_body['routers']:
597 router_id = router['id']
David Kranz34e88122014-12-11 15:24:05 -0500598 r_body = client.networks.list_router_interfaces(router_id)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200599 for port in r_body['ports']:
600 if port['network_id'] == network_id:
601 return "qrouter-%s" % router_id
602
603
604def _get_resource_by_name(client, resource, name):
605 get_resources = getattr(client, 'list_%s' % resource)
606 if get_resources is None:
607 raise AttributeError("client doesn't have method list_%s" % resource)
David Kranz34e88122014-12-11 15:24:05 -0500608 # Until all tempest client methods are changed to return only one value,
609 # we cannot assume they all have the same signature so we need to discard
610 # the unused response first value it two values are being returned.
611 body = get_resources()
612 if type(body) == tuple:
613 body = body[1]
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200614 if isinstance(body, dict):
615 body = body[resource]
616 for res in body:
617 if name == res['name']:
618 return res
619 raise ValueError('%s not found in %s resources' % (name, resource))
620
621
622def create_networks(networks):
623 LOG.info("Creating networks")
624 for network in networks:
625 client = client_for_user(network['owner'])
626
627 # only create a network if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500628 body = client.networks.list_networks()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200629 if any(item['name'] == network['name'] for item in body['networks']):
630 LOG.warning("Dupplicated network name: %s" % network['name'])
631 continue
632
633 client.networks.create_network(name=network['name'])
634
635
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100636def destroy_networks(networks):
637 LOG.info("Destroying subnets")
638 for network in networks:
639 client = client_for_user(network['owner'])
640 network_id = _get_resource_by_name(client.networks, 'networks',
641 network['name'])['id']
642 client.networks.delete_network(network_id)
643
644
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200645def create_subnets(subnets):
646 LOG.info("Creating subnets")
647 for subnet in subnets:
648 client = client_for_user(subnet['owner'])
649
650 network = _get_resource_by_name(client.networks, 'networks',
651 subnet['network'])
652 ip_version = netaddr.IPNetwork(subnet['range']).version
653 # ensure we don't overlap with another subnet in the network
654 try:
655 client.networks.create_subnet(network_id=network['id'],
656 cidr=subnet['range'],
657 name=subnet['name'],
658 ip_version=ip_version)
659 except exceptions.BadRequest as e:
660 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
661 if not is_overlapping_cidr:
662 raise
663
664
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100665def destroy_subnets(subnets):
666 LOG.info("Destroying subnets")
667 for subnet in subnets:
668 client = client_for_user(subnet['owner'])
669 subnet_id = _get_resource_by_name(client.networks,
670 'subnets', subnet['name'])['id']
671 client.networks.delete_subnet(subnet_id)
672
673
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200674def create_routers(routers):
675 LOG.info("Creating routers")
676 for router in routers:
677 client = client_for_user(router['owner'])
678
679 # only create a router if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500680 body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200681 if any(item['name'] == router['name'] for item in body['routers']):
682 LOG.warning("Dupplicated router name: %s" % router['name'])
683 continue
684
685 client.networks.create_router(router['name'])
686
687
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100688def destroy_routers(routers):
689 LOG.info("Destroying routers")
690 for router in routers:
691 client = client_for_user(router['owner'])
692 router_id = _get_resource_by_name(client.networks,
693 'routers', router['name'])['id']
694 for subnet in router['subnet']:
695 subnet_id = _get_resource_by_name(client.networks,
696 'subnets', subnet)['id']
697 client.networks.remove_router_interface_with_subnet_id(router_id,
698 subnet_id)
699 client.networks.delete_router(router_id)
700
701
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200702def add_router_interface(routers):
703 for router in routers:
704 client = client_for_user(router['owner'])
705 router_id = _get_resource_by_name(client.networks,
706 'routers', router['name'])['id']
707
708 for subnet in router['subnet']:
709 subnet_id = _get_resource_by_name(client.networks,
710 'subnets', subnet)['id']
711 # connect routers to their subnets
712 client.networks.add_router_interface_with_subnet_id(router_id,
713 subnet_id)
714 # connect routers to exteral network if set to "gateway"
715 if router['gateway']:
716 if CONF.network.public_network_id:
717 ext_net = CONF.network.public_network_id
718 client.networks._update_router(
719 router_id, set_enable_snat=True,
720 external_gateway_info={"network_id": ext_net})
721 else:
722 raise ValueError('public_network_id is not configured.')
723
724
725#######################
726#
Sean Dague655e0af2014-05-29 09:00:22 -0400727# SERVERS
728#
729#######################
730
731def _get_server_by_name(client, name):
732 r, body = client.servers.list_servers()
733 for server in body['servers']:
734 if name == server['name']:
735 return server
736 return None
737
738
Sean Dague655e0af2014-05-29 09:00:22 -0400739def _get_flavor_by_name(client, name):
740 r, body = client.flavors.list_flavors()
741 for flavor in body:
742 if name == flavor['name']:
743 return flavor
744 return None
745
746
747def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000748 if not servers:
749 return
Joe Gordona18d6862014-07-24 22:55:46 +0000750 LOG.info("Creating servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400751 for server in servers:
752 client = client_for_user(server['owner'])
753
754 if _get_server_by_name(client, server['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000755 LOG.info("Server '%s' already exists" % server['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400756 continue
757
758 image_id = _get_image_by_name(client, server['image'])['id']
759 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200760 # validate neutron is enabled and ironic disabled
761 kwargs = dict()
762 if (CONF.service_available.neutron and
763 not CONF.baremetal.driver_enabled and server.get('networks')):
764 get_net_id = lambda x: (_get_resource_by_name(
765 client.networks, 'networks', x)['id'])
766 kwargs['networks'] = [{'uuid': get_net_id(network)}
767 for network in server['networks']]
768 resp, body = client.servers.create_server(
769 server['name'], image_id, flavor_id, **kwargs)
Joe Gordon10f260b2014-07-24 23:27:19 +0000770 server_id = body['id']
771 client.servers.wait_for_server_status(server_id, 'ACTIVE')
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200772 # create to security group(s) after server spawning
773 for secgroup in server['secgroups']:
774 client.servers.add_security_group(server_id, secgroup)
Sean Dague655e0af2014-05-29 09:00:22 -0400775
776
Joe Gordondb63b1c2014-07-24 23:21:21 +0000777def destroy_servers(servers):
778 if not servers:
779 return
780 LOG.info("Destroying servers")
781 for server in servers:
782 client = client_for_user(server['owner'])
783
784 response = _get_server_by_name(client, server['name'])
785 if not response:
786 LOG.info("Server '%s' does not exist" % server['name'])
787 continue
788
789 client.servers.delete_server(response['id'])
790 client.servers.wait_for_server_termination(response['id'],
Matthew Treinish1d14c542014-06-17 20:25:40 -0400791 ignore_error=True)
Joe Gordondb63b1c2014-07-24 23:21:21 +0000792
793
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200794def create_secgroups(secgroups):
795 LOG.info("Creating security groups")
796 for secgroup in secgroups:
797 client = client_for_user(secgroup['owner'])
798
799 # only create a security group if the name isn't here
800 # i.e. a security group may be used by another server
801 # only create a router if the name isn't here
802 r, body = client.secgroups.list_security_groups()
803 if any(item['name'] == secgroup['name'] for item in body):
804 LOG.warning("Security group '%s' already exists" %
805 secgroup['name'])
806 continue
807
808 resp, body = client.secgroups.create_security_group(
809 secgroup['name'], secgroup['description'])
810 if not resp_ok(resp):
811 raise ValueError("Failed to create security group: [%s] %s" %
812 (resp, body))
813 secgroup_id = body['id']
814 # for each security group, create the rules
815 for rule in secgroup['rules']:
816 ip_proto, from_port, to_port, cidr = rule.split()
817 client.secgroups.create_security_group_rule(
818 secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
819
820
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100821def destroy_secgroups(secgroups):
822 LOG.info("Destroying security groups")
823 for secgroup in secgroups:
824 client = client_for_user(secgroup['owner'])
825 sg_id = _get_resource_by_name(client.secgroups,
826 'security_groups',
827 secgroup['name'])
828 # sg rules are deleted automatically
829 client.secgroups.delete_security_group(sg_id['id'])
830
831
Sean Dague655e0af2014-05-29 09:00:22 -0400832#######################
833#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200834# VOLUMES
835#
836#######################
837
838def _get_volume_by_name(client, name):
839 r, body = client.volumes.list_volumes()
Emilien Macchid18fec12014-09-15 14:32:54 -0400840 for volume in body:
841 if name == volume['display_name']:
Emilien Macchi626b4f82014-06-15 21:44:29 +0200842 return volume
843 return None
844
845
846def create_volumes(volumes):
Chris Dent51e76de2014-10-01 12:07:14 +0100847 if not volumes:
848 return
849 LOG.info("Creating volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200850 for volume in volumes:
851 client = client_for_user(volume['owner'])
852
853 # only create a volume if the name isn't here
Emilien Macchid18fec12014-09-15 14:32:54 -0400854 if _get_volume_by_name(client, volume['name']):
855 LOG.info("volume '%s' already exists" % volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200856 continue
857
Emilien Macchid18fec12014-09-15 14:32:54 -0400858 size = volume['gb']
859 v_name = volume['name']
860 resp, body = client.volumes.create_volume(size=size,
861 display_name=v_name)
862 client.volumes.wait_for_volume_status(body['id'], 'available')
Emilien Macchi626b4f82014-06-15 21:44:29 +0200863
864
Emilien Macchibb71e072014-07-05 19:18:52 +0200865def destroy_volumes(volumes):
866 for volume in volumes:
867 client = client_for_user(volume['owner'])
868 volume_id = _get_volume_by_name(client, volume['name'])['id']
Emilien Macchi5ebc27b2014-09-15 14:30:35 -0400869 client.volumes.detach_volume(volume_id)
870 client.volumes.delete_volume(volume_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200871
872
Emilien Macchi626b4f82014-06-15 21:44:29 +0200873def attach_volumes(volumes):
874 for volume in volumes:
875 client = client_for_user(volume['owner'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200876 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400877 volume_id = _get_volume_by_name(client, volume['name'])['id']
878 device = volume['device']
879 client.volumes.attach_volume(volume_id, server_id, device)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200880
881
882#######################
883#
Sean Dague655e0af2014-05-29 09:00:22 -0400884# MAIN LOGIC
885#
886#######################
887
888def create_resources():
889 LOG.info("Creating Resources")
890 # first create keystone level resources, and we need to be admin
891 # for those.
892 create_tenants(RES['tenants'])
893 create_users(RES['users'])
894 collect_users(RES['users'])
895
896 # next create resources in a well known order
897 create_objects(RES['objects'])
898 create_images(RES['images'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200899
900 # validate neutron is enabled and ironic is disabled
901 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
902 create_networks(RES['networks'])
903 create_subnets(RES['subnets'])
904 create_routers(RES['routers'])
905 add_router_interface(RES['routers'])
906
907 create_secgroups(RES['secgroups'])
Sean Dague655e0af2014-05-29 09:00:22 -0400908 create_servers(RES['servers'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400909 create_volumes(RES['volumes'])
910 attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400911
912
Joe Gordondb63b1c2014-07-24 23:21:21 +0000913def destroy_resources():
914 LOG.info("Destroying Resources")
915 # Destroy in inverse order of create
Joe Gordondb63b1c2014-07-24 23:21:21 +0000916 destroy_servers(RES['servers'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000917 destroy_images(RES['images'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200918 destroy_objects(RES['objects'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200919 destroy_volumes(RES['volumes'])
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100920 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
921 destroy_routers(RES['routers'])
922 destroy_subnets(RES['subnets'])
923 destroy_networks(RES['networks'])
924 destroy_secgroups(RES['secgroups'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200925 destroy_users(RES['users'])
926 destroy_tenants(RES['tenants'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000927 LOG.warn("Destroy mode incomplete")
928
Joe Gordondb63b1c2014-07-24 23:21:21 +0000929
Sean Dague655e0af2014-05-29 09:00:22 -0400930def get_options():
931 global OPTS
932 parser = argparse.ArgumentParser(
933 description='Create and validate a fixed set of OpenStack resources')
934 parser.add_argument('-m', '--mode',
935 metavar='<create|check|destroy>',
936 required=True,
937 help=('One of (create, check, destroy)'))
938 parser.add_argument('-r', '--resources',
939 required=True,
940 metavar='resourcefile.yaml',
941 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000942
Sean Dague319b37a2014-07-11 07:28:11 -0400943 parser.add_argument(
944 '-d', '--devstack-base',
945 required=True,
946 metavar='/opt/stack/old',
947 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000948 parser.add_argument(
949 '-c', '--config-file',
950 metavar='/etc/tempest.conf',
951 help='path to javelin2(tempest) config file')
952
Sean Dague655e0af2014-05-29 09:00:22 -0400953 # auth bits, letting us also just source the devstack openrc
954 parser.add_argument('--os-username',
955 metavar='<auth-user-name>',
956 default=os.environ.get('OS_USERNAME'),
957 help=('Defaults to env[OS_USERNAME].'))
958 parser.add_argument('--os-password',
959 metavar='<auth-password>',
960 default=os.environ.get('OS_PASSWORD'),
961 help=('Defaults to env[OS_PASSWORD].'))
962 parser.add_argument('--os-tenant-name',
963 metavar='<auth-tenant-name>',
964 default=os.environ.get('OS_TENANT_NAME'),
965 help=('Defaults to env[OS_TENANT_NAME].'))
966
967 OPTS = parser.parse_args()
968 if OPTS.mode not in ('create', 'check', 'destroy'):
969 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
970 parser.print_help()
971 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +0000972 if OPTS.config_file:
973 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -0400974
975
Joe Gordon915eb8e2014-07-17 11:25:46 +0200976def setup_logging():
Sean Dague655e0af2014-05-29 09:00:22 -0400977 global LOG
Joe Gordon915eb8e2014-07-17 11:25:46 +0200978 logging.setup(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -0400979 LOG = logging.getLogger(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -0400980
981
982def main():
983 global RES
984 get_options()
985 setup_logging()
Chris Dent51e76de2014-10-01 12:07:14 +0100986 RES.update(load_resources(OPTS.resources))
Sean Dague655e0af2014-05-29 09:00:22 -0400987
988 if OPTS.mode == 'create':
989 create_resources()
Joe Gordon1a097002014-07-24 23:44:08 +0000990 # Make sure the resources we just created actually work
991 checker = JavelinCheck(USERS, RES)
992 checker.check()
Sean Dague655e0af2014-05-29 09:00:22 -0400993 elif OPTS.mode == 'check':
994 collect_users(RES['users'])
995 checker = JavelinCheck(USERS, RES)
996 checker.check()
997 elif OPTS.mode == 'destroy':
Joe Gordondb63b1c2014-07-24 23:21:21 +0000998 collect_users(RES['users'])
999 destroy_resources()
Sean Dague655e0af2014-05-29 09:00:22 -04001000 else:
1001 LOG.error('Unknown mode %s' % OPTS.mode)
1002 return 1
Joe Gordon246353a2014-07-18 00:10:28 +02001003 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -04001004 return 0
1005
1006if __name__ == "__main__":
1007 sys.exit(main())