blob: 9fb982c7c775dbba6b469e10d9f5a1d0ba6cfb13 [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):
Ken'ichi Ohmichi4771cbc2015-01-19 23:45:23 +0000147 default_params = {
148 'disable_ssl_certificate_validation':
149 CONF.identity.disable_ssl_certificate_validation,
150 'ca_certs': CONF.identity.ca_certificates_file,
151 'trace_requests': CONF.debug.trace_requests
152 }
Ken'ichi Ohmichid5dba1c2015-01-23 02:23:22 +0000153 default_params_with_timeout_values = {
154 'build_interval': CONF.compute.build_interval,
155 'build_timeout': CONF.compute.build_timeout
156 }
157 default_params_with_timeout_values.update(default_params)
158
Ken'ichi Ohmichi4771cbc2015-01-19 23:45:23 +0000159 compute_params = {
160 'service': CONF.compute.catalog_type,
161 'region': CONF.compute.region or CONF.identity.region,
162 'endpoint_type': CONF.compute.endpoint_type,
163 'build_interval': CONF.compute.build_interval,
164 'build_timeout': CONF.compute.build_timeout
165 }
166 compute_params.update(default_params)
167
Ken'ichi Ohmichi564b2ad2015-01-22 02:08:59 +0000168 object_storage_params = {
169 'service': CONF.object_storage.catalog_type,
170 'region': CONF.object_storage.region or CONF.identity.region,
171 'endpoint_type': CONF.object_storage.endpoint_type
172 }
173 object_storage_params.update(default_params)
174
Sean Dague655e0af2014-05-29 09:00:22 -0400175 _creds = tempest.auth.KeystoneV2Credentials(
176 username=user,
177 password=pw,
178 tenant_name=tenant)
179 _auth = tempest.auth.KeystoneV2AuthProvider(_creds)
180 self.identity = identity_client.IdentityClientJSON(_auth)
Ken'ichi Ohmichi4771cbc2015-01-19 23:45:23 +0000181 self.servers = servers_client.ServersClientJSON(_auth,
182 **compute_params)
183 self.flavors = flavors_client.FlavorsClientJSON(_auth,
184 **compute_params)
185 self.secgroups = security_groups_client.SecurityGroupsClientJSON(
186 _auth, **compute_params)
Ken'ichi Ohmichi564b2ad2015-01-22 02:08:59 +0000187 self.objects = object_client.ObjectClient(_auth,
188 **object_storage_params)
189 self.containers = container_client.ContainerClient(
190 _auth, **object_storage_params)
Sean Dague655e0af2014-05-29 09:00:22 -0400191 self.images = image_client.ImageClientV2JSON(_auth)
Ken'ichi Ohmichid5dba1c2015-01-23 02:23:22 +0000192 self.telemetry = telemetry_client.TelemetryClientJSON(
193 _auth,
194 CONF.telemetry.catalog_type,
195 CONF.identity.region,
196 endpoint_type=CONF.telemetry.endpoint_type,
197 **default_params_with_timeout_values)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200198 self.volumes = volumes_client.VolumesClientJSON(_auth)
Ken'ichi Ohmichia182e862015-01-21 01:16:37 +0000199 self.networks = network_client.NetworkClientJSON(
200 _auth,
201 CONF.network.catalog_type,
202 CONF.network.region or CONF.identity.region,
203 endpoint_type=CONF.network.endpoint_type,
204 build_interval=CONF.network.build_interval,
205 build_timeout=CONF.network.build_timeout,
206 **default_params)
Sean Dague655e0af2014-05-29 09:00:22 -0400207
208
209def load_resources(fname):
Joe H. Rahme02736732015-01-27 18:33:09 +0100210 """Load the expected resources from a yaml file."""
Sean Dague655e0af2014-05-29 09:00:22 -0400211 return yaml.load(open(fname, 'r'))
212
213
214def keystone_admin():
215 return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
216
217
218def client_for_user(name):
219 LOG.debug("Entering client_for_user")
220 if name in USERS:
221 user = USERS[name]
222 LOG.debug("Created client for user %s" % user)
223 return OSClient(user['name'], user['pass'], user['tenant'])
224 else:
225 LOG.error("%s not found in USERS: %s" % (name, USERS))
226
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200227
228def resp_ok(response):
229 return 200 >= int(response['status']) < 300
230
Sean Dague655e0af2014-05-29 09:00:22 -0400231###################
232#
233# TENANTS
234#
235###################
236
237
238def create_tenants(tenants):
239 """Create tenants from resource definition.
240
241 Don't create the tenants if they already exist.
242 """
243 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500244 body = admin.identity.list_tenants()
Sean Dague655e0af2014-05-29 09:00:22 -0400245 existing = [x['name'] for x in body]
246 for tenant in tenants:
247 if tenant not in existing:
248 admin.identity.create_tenant(tenant)
249 else:
250 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
251
Emilien Macchibb71e072014-07-05 19:18:52 +0200252
253def destroy_tenants(tenants):
254 admin = keystone_admin()
255 for tenant in tenants:
256 tenant_id = admin.identity.get_tenant_by_name(tenant)['id']
David Kranzb7afa922014-12-30 10:56:26 -0500257 admin.identity.delete_tenant(tenant_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200258
Sean Dague655e0af2014-05-29 09:00:22 -0400259##############
260#
261# USERS
262#
263##############
264
265
266def _users_for_tenant(users, tenant):
267 u_for_t = []
268 for user in users:
269 for n in user:
270 if user[n]['tenant'] == tenant:
271 u_for_t.append(user[n])
272 return u_for_t
273
274
275def _tenants_from_users(users):
276 tenants = set()
277 for user in users:
278 for n in user:
279 tenants.add(user[n]['tenant'])
280 return tenants
281
282
283def _assign_swift_role(user):
284 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500285 roles = admin.identity.list_roles()
Sean Dague655e0af2014-05-29 09:00:22 -0400286 role = next(r for r in roles if r['name'] == 'Member')
287 LOG.debug(USERS[user])
288 try:
289 admin.identity.assign_user_role(
290 USERS[user]['tenant_id'],
291 USERS[user]['id'],
292 role['id'])
293 except exceptions.Conflict:
294 # don't care if it's already assigned
295 pass
296
297
298def create_users(users):
299 """Create tenants from resource definition.
300
301 Don't create the tenants if they already exist.
302 """
303 global USERS
304 LOG.info("Creating users")
305 admin = keystone_admin()
306 for u in users:
307 try:
308 tenant = admin.identity.get_tenant_by_name(u['tenant'])
309 except exceptions.NotFound:
310 LOG.error("Tenant: %s - not found" % u['tenant'])
311 continue
312 try:
313 admin.identity.get_user_by_username(tenant['id'], u['name'])
314 LOG.warn("User '%s' already exists in this environment"
315 % u['name'])
316 except exceptions.NotFound:
317 admin.identity.create_user(
318 u['name'], u['pass'], tenant['id'],
319 "%s@%s" % (u['name'], tenant['id']),
320 enabled=True)
321
322
Emilien Macchibb71e072014-07-05 19:18:52 +0200323def destroy_users(users):
324 admin = keystone_admin()
325 for user in users:
Emilien Macchi436de862014-09-30 17:09:50 -0400326 tenant_id = admin.identity.get_tenant_by_name(user['tenant'])['id']
327 user_id = admin.identity.get_user_by_username(tenant_id,
328 user['name'])['id']
David Kranzb7afa922014-12-30 10:56:26 -0500329 admin.identity.delete_user(user_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200330
331
Sean Dague655e0af2014-05-29 09:00:22 -0400332def collect_users(users):
333 global USERS
Joe Gordon618c9fb2014-07-16 15:40:01 +0200334 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400335 admin = keystone_admin()
336 for u in users:
337 tenant = admin.identity.get_tenant_by_name(u['tenant'])
338 u['tenant_id'] = tenant['id']
339 USERS[u['name']] = u
340 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
341 USERS[u['name']]['id'] = body['id']
342
343
344class JavelinCheck(unittest.TestCase):
345 def __init__(self, users, resources):
346 super(JavelinCheck, self).__init__()
347 self.users = users
348 self.res = resources
349
350 def runTest(self, *args):
351 pass
352
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200353 def _ping_ip(self, ip_addr, count, namespace=None):
354 if namespace is None:
355 ping_cmd = "ping -c1 " + ip_addr
356 else:
357 ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
358 ip_addr)
359 for current in range(count):
360 return_code = os.system(ping_cmd)
361 if return_code is 0:
362 break
363 self.assertNotEqual(current, count - 1,
364 "Server is not pingable at %s" % ip_addr)
365
Sean Dague655e0af2014-05-29 09:00:22 -0400366 def check(self):
367 self.check_users()
368 self.check_objects()
369 self.check_servers()
Emilien Macchid18fec12014-09-15 14:32:54 -0400370 self.check_volumes()
Chris Dent878f3782014-06-30 17:04:15 +0100371 self.check_telemetry()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200372 self.check_secgroups()
373
374 # validate neutron is enabled and ironic disabled:
375 # Tenant network isolation is not supported when using ironic.
376 # "admin" has set up a neutron flat network environment within a shared
377 # fixed network for all tenants to use.
378 # In this case, network/subnet/router creation can be skipped and the
379 # server booted the same as nova network.
380 if (CONF.service_available.neutron and
381 not CONF.baremetal.driver_enabled):
382 self.check_networking()
Sean Dague655e0af2014-05-29 09:00:22 -0400383
384 def check_users(self):
385 """Check that the users we expect to exist, do.
386
387 We don't use the resource list for this because we need to validate
388 that things like tenantId didn't drift across versions.
389 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200390 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400391 for name, user in self.users.iteritems():
392 client = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500393 found = client.identity.get_user(user['id'])
Sean Dague655e0af2014-05-29 09:00:22 -0400394 self.assertEqual(found['name'], user['name'])
395 self.assertEqual(found['tenantId'], user['tenant_id'])
396
397 # also ensure we can auth with that user, and do something
398 # on the cloud. We don't care about the results except that it
399 # remains authorized.
400 client = client_for_user(user['name'])
401 resp, body = client.servers.list_servers()
402 self.assertEqual(resp['status'], '200')
403
404 def check_objects(self):
405 """Check that the objects created are still there."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000406 if not self.res.get('objects'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000407 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200408 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400409 for obj in self.res['objects']:
410 client = client_for_user(obj['owner'])
411 r, contents = client.objects.get_object(
412 obj['container'], obj['name'])
413 source = _file_contents(obj['file'])
414 self.assertEqual(contents, source)
415
416 def check_servers(self):
417 """Check that the servers are still up and running."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000418 if not self.res.get('servers'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000419 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200420 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400421 for server in self.res['servers']:
422 client = client_for_user(server['owner'])
423 found = _get_server_by_name(client, server['name'])
424 self.assertIsNotNone(
425 found,
426 "Couldn't find expected server %s" % server['name'])
427
428 r, found = client.servers.get_server(found['id'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200429 # validate neutron is enabled and ironic disabled:
430 if (CONF.service_available.neutron and
431 not CONF.baremetal.driver_enabled):
432 for network_name, body in found['addresses'].items():
433 for addr in body:
434 ip = addr['addr']
435 if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
436 namespace = _get_router_namespace(client,
437 network_name)
438 self._ping_ip(ip, 60, namespace)
439 else:
440 self._ping_ip(ip, 60)
441 else:
442 addr = found['addresses']['private'][0]['addr']
443 self._ping_ip(addr, 60)
444
445 def check_secgroups(self):
Joe H. Rahme02736732015-01-27 18:33:09 +0100446 """Check that the security groups still exist."""
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200447 LOG.info("Checking security groups")
448 for secgroup in self.res['secgroups']:
449 client = client_for_user(secgroup['owner'])
450 found = _get_resource_by_name(client.secgroups, 'security_groups',
451 secgroup['name'])
452 self.assertIsNotNone(
453 found,
454 "Couldn't find expected secgroup %s" % secgroup['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400455
Chris Dent878f3782014-06-30 17:04:15 +0100456 def check_telemetry(self):
457 """Check that ceilometer provides a sane sample.
458
459 Confirm that there are more than one sample and that they have the
460 expected metadata.
461
462 If in check mode confirm that the oldest sample available is from
463 before the upgrade.
464 """
Chris Dent0b76f2f2014-10-10 14:24:28 +0100465 if not self.res.get('telemetry'):
466 return
Chris Dent878f3782014-06-30 17:04:15 +0100467 LOG.info("checking telemetry")
468 for server in self.res['servers']:
469 client = client_for_user(server['owner'])
470 response, body = client.telemetry.list_samples(
471 'instance',
472 query=('metadata.display_name', 'eq', server['name'])
473 )
474 self.assertEqual(response.status, 200)
475 self.assertTrue(len(body) >= 1, 'expecting at least one sample')
476 self._confirm_telemetry_sample(server, body[-1])
477
Emilien Macchi626b4f82014-06-15 21:44:29 +0200478 def check_volumes(self):
479 """Check that the volumes are still there and attached."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000480 if not self.res.get('volumes'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000481 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200482 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200483 for volume in self.res['volumes']:
484 client = client_for_user(volume['owner'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400485 vol_body = _get_volume_by_name(client, volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200486 self.assertIsNotNone(
Emilien Macchid18fec12014-09-15 14:32:54 -0400487 vol_body,
Emilien Macchi626b4f82014-06-15 21:44:29 +0200488 "Couldn't find expected volume %s" % volume['name'])
489
490 # Verify that a volume's attachment retrieved
491 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400492 attachment = client.volumes.get_attachment_from_volume(vol_body)
493 self.assertEqual(vol_body['id'], attachment['volume_id'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200494 self.assertEqual(server_id, attachment['server_id'])
495
Chris Dent878f3782014-06-30 17:04:15 +0100496 def _confirm_telemetry_sample(self, server, sample):
497 """Check this sample matches the expected resource metadata."""
498 # Confirm display_name
499 self.assertEqual(server['name'],
500 sample['resource_metadata']['display_name'])
501 # Confirm instance_type of flavor
502 flavor = sample['resource_metadata'].get(
503 'flavor.name',
504 sample['resource_metadata'].get('instance_type')
505 )
506 self.assertEqual(server['flavor'], flavor)
507 # Confirm the oldest sample was created before upgrade.
508 if OPTS.mode == 'check':
509 oldest_timestamp = timeutils.normalize_time(
510 timeutils.parse_isotime(sample['timestamp']))
511 self.assertTrue(
512 oldest_timestamp < JAVELIN_START,
513 'timestamp should come before start of second javelin run'
514 )
515
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200516 def check_networking(self):
517 """Check that the networks are still there."""
518 for res_type in ('networks', 'subnets', 'routers'):
519 for res in self.res[res_type]:
520 client = client_for_user(res['owner'])
521 found = _get_resource_by_name(client.networks, res_type,
522 res['name'])
523 self.assertIsNotNone(
524 found,
525 "Couldn't find expected resource %s" % res['name'])
526
Sean Dague655e0af2014-05-29 09:00:22 -0400527
528#######################
529#
530# OBJECTS
531#
532#######################
533
534
535def _file_contents(fname):
536 with open(fname, 'r') as f:
537 return f.read()
538
539
540def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000541 if not objects:
542 return
Sean Dague655e0af2014-05-29 09:00:22 -0400543 LOG.info("Creating objects")
544 for obj in objects:
545 LOG.debug("Object %s" % obj)
546 _assign_swift_role(obj['owner'])
547 client = client_for_user(obj['owner'])
548 client.containers.create_container(obj['container'])
549 client.objects.create_object(
550 obj['container'], obj['name'],
551 _file_contents(obj['file']))
552
Emilien Macchibb71e072014-07-05 19:18:52 +0200553
554def destroy_objects(objects):
555 for obj in objects:
556 client = client_for_user(obj['owner'])
557 r, body = client.objects.delete_object(obj['container'], obj['name'])
Emilien Macchid70f5102014-09-10 09:54:49 -0400558 if not (200 <= int(r['status']) < 299):
Emilien Macchibb71e072014-07-05 19:18:52 +0200559 raise ValueError("unable to destroy object: [%s] %s" % (r, body))
560
561
Sean Dague655e0af2014-05-29 09:00:22 -0400562#######################
563#
564# IMAGES
565#
566#######################
567
568
Sean Dague319b37a2014-07-11 07:28:11 -0400569def _resolve_image(image, imgtype):
570 name = image[imgtype]
571 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
572 return name, fname
573
574
Joe Gordon6f0426c2014-07-25 01:10:28 +0000575def _get_image_by_name(client, name):
David Kranz34f18782015-01-06 13:43:55 -0500576 body = client.images.image_list()
Joe Gordon6f0426c2014-07-25 01:10:28 +0000577 for image in body:
578 if name == image['name']:
579 return image
580 return None
581
582
Sean Dague655e0af2014-05-29 09:00:22 -0400583def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000584 if not images:
585 return
Joe Gordona18d6862014-07-24 22:55:46 +0000586 LOG.info("Creating images")
Sean Dague655e0af2014-05-29 09:00:22 -0400587 for image in images:
588 client = client_for_user(image['owner'])
589
590 # only upload a new image if the name isn't there
Joe Gordon6f0426c2014-07-25 01:10:28 +0000591 if _get_image_by_name(client, image['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000592 LOG.info("Image '%s' already exists" % image['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400593 continue
594
595 # special handling for 3 part image
596 extras = {}
597 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400598 name, fname = _resolve_image(image, 'aki')
David Kranz34f18782015-01-06 13:43:55 -0500599 aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400600 'javelin_' + name, 'aki', 'aki')
601 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400602 extras['kernel_id'] = aki.get('id')
603
Sean Dague319b37a2014-07-11 07:28:11 -0400604 name, fname = _resolve_image(image, 'ari')
David Kranz34f18782015-01-06 13:43:55 -0500605 ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400606 'javelin_' + name, 'ari', 'ari')
607 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400608 extras['ramdisk_id'] = ari.get('id')
609
Sean Dague319b37a2014-07-11 07:28:11 -0400610 _, fname = _resolve_image(image, 'file')
David Kranz34f18782015-01-06 13:43:55 -0500611 body = client.images.create_image(
Sean Dague655e0af2014-05-29 09:00:22 -0400612 image['name'], image['format'], image['format'], **extras)
613 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400614 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400615
616
Joe Gordon6f0426c2014-07-25 01:10:28 +0000617def destroy_images(images):
618 if not images:
619 return
620 LOG.info("Destroying images")
621 for image in images:
622 client = client_for_user(image['owner'])
623
624 response = _get_image_by_name(client, image['name'])
625 if not response:
626 LOG.info("Image '%s' does not exists" % image['name'])
627 continue
628 client.images.delete_image(response['id'])
629
630
Sean Dague655e0af2014-05-29 09:00:22 -0400631#######################
632#
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200633# NETWORKS
634#
635#######################
636
637def _get_router_namespace(client, network):
638 network_id = _get_resource_by_name(client.networks,
639 'networks', network)['id']
David Kranz34e88122014-12-11 15:24:05 -0500640 n_body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200641 for router in n_body['routers']:
642 router_id = router['id']
David Kranz34e88122014-12-11 15:24:05 -0500643 r_body = client.networks.list_router_interfaces(router_id)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200644 for port in r_body['ports']:
645 if port['network_id'] == network_id:
646 return "qrouter-%s" % router_id
647
648
649def _get_resource_by_name(client, resource, name):
650 get_resources = getattr(client, 'list_%s' % resource)
651 if get_resources is None:
652 raise AttributeError("client doesn't have method list_%s" % resource)
David Kranz34e88122014-12-11 15:24:05 -0500653 # Until all tempest client methods are changed to return only one value,
654 # we cannot assume they all have the same signature so we need to discard
655 # the unused response first value it two values are being returned.
656 body = get_resources()
657 if type(body) == tuple:
658 body = body[1]
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200659 if isinstance(body, dict):
660 body = body[resource]
661 for res in body:
662 if name == res['name']:
663 return res
664 raise ValueError('%s not found in %s resources' % (name, resource))
665
666
667def create_networks(networks):
668 LOG.info("Creating networks")
669 for network in networks:
670 client = client_for_user(network['owner'])
671
672 # only create a network if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500673 body = client.networks.list_networks()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200674 if any(item['name'] == network['name'] for item in body['networks']):
675 LOG.warning("Dupplicated network name: %s" % network['name'])
676 continue
677
678 client.networks.create_network(name=network['name'])
679
680
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100681def destroy_networks(networks):
682 LOG.info("Destroying subnets")
683 for network in networks:
684 client = client_for_user(network['owner'])
685 network_id = _get_resource_by_name(client.networks, 'networks',
686 network['name'])['id']
687 client.networks.delete_network(network_id)
688
689
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200690def create_subnets(subnets):
691 LOG.info("Creating subnets")
692 for subnet in subnets:
693 client = client_for_user(subnet['owner'])
694
695 network = _get_resource_by_name(client.networks, 'networks',
696 subnet['network'])
697 ip_version = netaddr.IPNetwork(subnet['range']).version
698 # ensure we don't overlap with another subnet in the network
699 try:
700 client.networks.create_subnet(network_id=network['id'],
701 cidr=subnet['range'],
702 name=subnet['name'],
703 ip_version=ip_version)
704 except exceptions.BadRequest as e:
705 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
706 if not is_overlapping_cidr:
707 raise
708
709
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100710def destroy_subnets(subnets):
711 LOG.info("Destroying subnets")
712 for subnet in subnets:
713 client = client_for_user(subnet['owner'])
714 subnet_id = _get_resource_by_name(client.networks,
715 'subnets', subnet['name'])['id']
716 client.networks.delete_subnet(subnet_id)
717
718
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200719def create_routers(routers):
720 LOG.info("Creating routers")
721 for router in routers:
722 client = client_for_user(router['owner'])
723
724 # only create a router if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500725 body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200726 if any(item['name'] == router['name'] for item in body['routers']):
727 LOG.warning("Dupplicated router name: %s" % router['name'])
728 continue
729
730 client.networks.create_router(router['name'])
731
732
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100733def destroy_routers(routers):
734 LOG.info("Destroying routers")
735 for router in routers:
736 client = client_for_user(router['owner'])
737 router_id = _get_resource_by_name(client.networks,
738 'routers', router['name'])['id']
739 for subnet in router['subnet']:
740 subnet_id = _get_resource_by_name(client.networks,
741 'subnets', subnet)['id']
742 client.networks.remove_router_interface_with_subnet_id(router_id,
743 subnet_id)
744 client.networks.delete_router(router_id)
745
746
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200747def add_router_interface(routers):
748 for router in routers:
749 client = client_for_user(router['owner'])
750 router_id = _get_resource_by_name(client.networks,
751 'routers', router['name'])['id']
752
753 for subnet in router['subnet']:
754 subnet_id = _get_resource_by_name(client.networks,
755 'subnets', subnet)['id']
756 # connect routers to their subnets
757 client.networks.add_router_interface_with_subnet_id(router_id,
758 subnet_id)
759 # connect routers to exteral network if set to "gateway"
760 if router['gateway']:
761 if CONF.network.public_network_id:
762 ext_net = CONF.network.public_network_id
763 client.networks._update_router(
764 router_id, set_enable_snat=True,
765 external_gateway_info={"network_id": ext_net})
766 else:
767 raise ValueError('public_network_id is not configured.')
768
769
770#######################
771#
Sean Dague655e0af2014-05-29 09:00:22 -0400772# SERVERS
773#
774#######################
775
776def _get_server_by_name(client, name):
777 r, body = client.servers.list_servers()
778 for server in body['servers']:
779 if name == server['name']:
780 return server
781 return None
782
783
Sean Dague655e0af2014-05-29 09:00:22 -0400784def _get_flavor_by_name(client, name):
785 r, body = client.flavors.list_flavors()
786 for flavor in body:
787 if name == flavor['name']:
788 return flavor
789 return None
790
791
792def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000793 if not servers:
794 return
Joe Gordona18d6862014-07-24 22:55:46 +0000795 LOG.info("Creating servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400796 for server in servers:
797 client = client_for_user(server['owner'])
798
799 if _get_server_by_name(client, server['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000800 LOG.info("Server '%s' already exists" % server['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400801 continue
802
803 image_id = _get_image_by_name(client, server['image'])['id']
804 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200805 # validate neutron is enabled and ironic disabled
806 kwargs = dict()
807 if (CONF.service_available.neutron and
808 not CONF.baremetal.driver_enabled and server.get('networks')):
809 get_net_id = lambda x: (_get_resource_by_name(
810 client.networks, 'networks', x)['id'])
811 kwargs['networks'] = [{'uuid': get_net_id(network)}
812 for network in server['networks']]
813 resp, body = client.servers.create_server(
814 server['name'], image_id, flavor_id, **kwargs)
Joe Gordon10f260b2014-07-24 23:27:19 +0000815 server_id = body['id']
816 client.servers.wait_for_server_status(server_id, 'ACTIVE')
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200817 # create to security group(s) after server spawning
818 for secgroup in server['secgroups']:
819 client.servers.add_security_group(server_id, secgroup)
Sean Dague655e0af2014-05-29 09:00:22 -0400820
821
Joe Gordondb63b1c2014-07-24 23:21:21 +0000822def destroy_servers(servers):
823 if not servers:
824 return
825 LOG.info("Destroying servers")
826 for server in servers:
827 client = client_for_user(server['owner'])
828
829 response = _get_server_by_name(client, server['name'])
830 if not response:
831 LOG.info("Server '%s' does not exist" % server['name'])
832 continue
833
834 client.servers.delete_server(response['id'])
835 client.servers.wait_for_server_termination(response['id'],
Matthew Treinish1d14c542014-06-17 20:25:40 -0400836 ignore_error=True)
Joe Gordondb63b1c2014-07-24 23:21:21 +0000837
838
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200839def create_secgroups(secgroups):
840 LOG.info("Creating security groups")
841 for secgroup in secgroups:
842 client = client_for_user(secgroup['owner'])
843
844 # only create a security group if the name isn't here
845 # i.e. a security group may be used by another server
846 # only create a router if the name isn't here
847 r, body = client.secgroups.list_security_groups()
848 if any(item['name'] == secgroup['name'] for item in body):
849 LOG.warning("Security group '%s' already exists" %
850 secgroup['name'])
851 continue
852
853 resp, body = client.secgroups.create_security_group(
854 secgroup['name'], secgroup['description'])
855 if not resp_ok(resp):
856 raise ValueError("Failed to create security group: [%s] %s" %
857 (resp, body))
858 secgroup_id = body['id']
859 # for each security group, create the rules
860 for rule in secgroup['rules']:
861 ip_proto, from_port, to_port, cidr = rule.split()
862 client.secgroups.create_security_group_rule(
863 secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
864
865
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100866def destroy_secgroups(secgroups):
867 LOG.info("Destroying security groups")
868 for secgroup in secgroups:
869 client = client_for_user(secgroup['owner'])
870 sg_id = _get_resource_by_name(client.secgroups,
871 'security_groups',
872 secgroup['name'])
873 # sg rules are deleted automatically
874 client.secgroups.delete_security_group(sg_id['id'])
875
876
Sean Dague655e0af2014-05-29 09:00:22 -0400877#######################
878#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200879# VOLUMES
880#
881#######################
882
883def _get_volume_by_name(client, name):
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000884 body = client.volumes.list_volumes()
Emilien Macchid18fec12014-09-15 14:32:54 -0400885 for volume in body:
886 if name == volume['display_name']:
Emilien Macchi626b4f82014-06-15 21:44:29 +0200887 return volume
888 return None
889
890
891def create_volumes(volumes):
Chris Dent51e76de2014-10-01 12:07:14 +0100892 if not volumes:
893 return
894 LOG.info("Creating volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200895 for volume in volumes:
896 client = client_for_user(volume['owner'])
897
898 # only create a volume if the name isn't here
Emilien Macchid18fec12014-09-15 14:32:54 -0400899 if _get_volume_by_name(client, volume['name']):
900 LOG.info("volume '%s' already exists" % volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200901 continue
902
Emilien Macchid18fec12014-09-15 14:32:54 -0400903 size = volume['gb']
904 v_name = volume['name']
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000905 body = client.volumes.create_volume(size=size,
906 display_name=v_name)
Emilien Macchid18fec12014-09-15 14:32:54 -0400907 client.volumes.wait_for_volume_status(body['id'], 'available')
Emilien Macchi626b4f82014-06-15 21:44:29 +0200908
909
Emilien Macchibb71e072014-07-05 19:18:52 +0200910def destroy_volumes(volumes):
911 for volume in volumes:
912 client = client_for_user(volume['owner'])
913 volume_id = _get_volume_by_name(client, volume['name'])['id']
Emilien Macchi5ebc27b2014-09-15 14:30:35 -0400914 client.volumes.detach_volume(volume_id)
915 client.volumes.delete_volume(volume_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200916
917
Emilien Macchi626b4f82014-06-15 21:44:29 +0200918def attach_volumes(volumes):
919 for volume in volumes:
920 client = client_for_user(volume['owner'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200921 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400922 volume_id = _get_volume_by_name(client, volume['name'])['id']
923 device = volume['device']
924 client.volumes.attach_volume(volume_id, server_id, device)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200925
926
927#######################
928#
Sean Dague655e0af2014-05-29 09:00:22 -0400929# MAIN LOGIC
930#
931#######################
932
933def create_resources():
934 LOG.info("Creating Resources")
935 # first create keystone level resources, and we need to be admin
936 # for those.
937 create_tenants(RES['tenants'])
938 create_users(RES['users'])
939 collect_users(RES['users'])
940
941 # next create resources in a well known order
942 create_objects(RES['objects'])
943 create_images(RES['images'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200944
945 # validate neutron is enabled and ironic is disabled
946 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
947 create_networks(RES['networks'])
948 create_subnets(RES['subnets'])
949 create_routers(RES['routers'])
950 add_router_interface(RES['routers'])
951
952 create_secgroups(RES['secgroups'])
Sean Dague655e0af2014-05-29 09:00:22 -0400953 create_servers(RES['servers'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400954 create_volumes(RES['volumes'])
955 attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400956
957
Joe Gordondb63b1c2014-07-24 23:21:21 +0000958def destroy_resources():
959 LOG.info("Destroying Resources")
960 # Destroy in inverse order of create
Joe Gordondb63b1c2014-07-24 23:21:21 +0000961 destroy_servers(RES['servers'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000962 destroy_images(RES['images'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200963 destroy_objects(RES['objects'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200964 destroy_volumes(RES['volumes'])
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100965 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
966 destroy_routers(RES['routers'])
967 destroy_subnets(RES['subnets'])
968 destroy_networks(RES['networks'])
969 destroy_secgroups(RES['secgroups'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200970 destroy_users(RES['users'])
971 destroy_tenants(RES['tenants'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000972 LOG.warn("Destroy mode incomplete")
973
Joe Gordondb63b1c2014-07-24 23:21:21 +0000974
Sean Dague655e0af2014-05-29 09:00:22 -0400975def get_options():
976 global OPTS
977 parser = argparse.ArgumentParser(
978 description='Create and validate a fixed set of OpenStack resources')
979 parser.add_argument('-m', '--mode',
980 metavar='<create|check|destroy>',
981 required=True,
982 help=('One of (create, check, destroy)'))
983 parser.add_argument('-r', '--resources',
984 required=True,
985 metavar='resourcefile.yaml',
986 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000987
Sean Dague319b37a2014-07-11 07:28:11 -0400988 parser.add_argument(
989 '-d', '--devstack-base',
990 required=True,
991 metavar='/opt/stack/old',
992 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000993 parser.add_argument(
994 '-c', '--config-file',
995 metavar='/etc/tempest.conf',
996 help='path to javelin2(tempest) config file')
997
Sean Dague655e0af2014-05-29 09:00:22 -0400998 # auth bits, letting us also just source the devstack openrc
999 parser.add_argument('--os-username',
1000 metavar='<auth-user-name>',
1001 default=os.environ.get('OS_USERNAME'),
1002 help=('Defaults to env[OS_USERNAME].'))
1003 parser.add_argument('--os-password',
1004 metavar='<auth-password>',
1005 default=os.environ.get('OS_PASSWORD'),
1006 help=('Defaults to env[OS_PASSWORD].'))
1007 parser.add_argument('--os-tenant-name',
1008 metavar='<auth-tenant-name>',
1009 default=os.environ.get('OS_TENANT_NAME'),
1010 help=('Defaults to env[OS_TENANT_NAME].'))
1011
1012 OPTS = parser.parse_args()
1013 if OPTS.mode not in ('create', 'check', 'destroy'):
1014 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
1015 parser.print_help()
1016 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +00001017 if OPTS.config_file:
1018 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -04001019
1020
Joe Gordon915eb8e2014-07-17 11:25:46 +02001021def setup_logging():
Sean Dague655e0af2014-05-29 09:00:22 -04001022 global LOG
Joe Gordon915eb8e2014-07-17 11:25:46 +02001023 logging.setup(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001024 LOG = logging.getLogger(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001025
1026
1027def main():
1028 global RES
1029 get_options()
1030 setup_logging()
Chris Dent51e76de2014-10-01 12:07:14 +01001031 RES.update(load_resources(OPTS.resources))
Sean Dague655e0af2014-05-29 09:00:22 -04001032
1033 if OPTS.mode == 'create':
1034 create_resources()
Joe Gordon1a097002014-07-24 23:44:08 +00001035 # Make sure the resources we just created actually work
1036 checker = JavelinCheck(USERS, RES)
1037 checker.check()
Sean Dague655e0af2014-05-29 09:00:22 -04001038 elif OPTS.mode == 'check':
1039 collect_users(RES['users'])
1040 checker = JavelinCheck(USERS, RES)
1041 checker.check()
1042 elif OPTS.mode == 'destroy':
Joe Gordondb63b1c2014-07-24 23:21:21 +00001043 collect_users(RES['users'])
1044 destroy_resources()
Sean Dague655e0af2014-05-29 09:00:22 -04001045 else:
1046 LOG.error('Unknown mode %s' % OPTS.mode)
1047 return 1
Joe Gordon246353a2014-07-18 00:10:28 +02001048 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -04001049 return 0
1050
1051if __name__ == "__main__":
1052 sys.exit(main())