blob: 889b2dd4ace0358274b6c6ee5403431444af0e47 [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)
Ken'ichi Ohmichif85e9bd2015-01-27 12:51:47 +0000198 self.volumes = volumes_client.VolumesClientJSON(
199 _auth,
200 CONF.volume.catalog_type,
201 CONF.volume.region or CONF.identity.region,
202 endpoint_type=CONF.volume.endpoint_type,
203 build_interval=CONF.volume.build_interval,
204 build_timeout=CONF.volume.build_timeout,
205 **default_params)
Ken'ichi Ohmichia182e862015-01-21 01:16:37 +0000206 self.networks = network_client.NetworkClientJSON(
207 _auth,
208 CONF.network.catalog_type,
209 CONF.network.region or CONF.identity.region,
210 endpoint_type=CONF.network.endpoint_type,
211 build_interval=CONF.network.build_interval,
212 build_timeout=CONF.network.build_timeout,
213 **default_params)
Sean Dague655e0af2014-05-29 09:00:22 -0400214
215
216def load_resources(fname):
Joe H. Rahme02736732015-01-27 18:33:09 +0100217 """Load the expected resources from a yaml file."""
Sean Dague655e0af2014-05-29 09:00:22 -0400218 return yaml.load(open(fname, 'r'))
219
220
221def keystone_admin():
222 return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
223
224
225def client_for_user(name):
226 LOG.debug("Entering client_for_user")
227 if name in USERS:
228 user = USERS[name]
229 LOG.debug("Created client for user %s" % user)
230 return OSClient(user['name'], user['pass'], user['tenant'])
231 else:
232 LOG.error("%s not found in USERS: %s" % (name, USERS))
233
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200234
235def resp_ok(response):
236 return 200 >= int(response['status']) < 300
237
Sean Dague655e0af2014-05-29 09:00:22 -0400238###################
239#
240# TENANTS
241#
242###################
243
244
245def create_tenants(tenants):
246 """Create tenants from resource definition.
247
248 Don't create the tenants if they already exist.
249 """
250 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500251 body = admin.identity.list_tenants()
Sean Dague655e0af2014-05-29 09:00:22 -0400252 existing = [x['name'] for x in body]
253 for tenant in tenants:
254 if tenant not in existing:
255 admin.identity.create_tenant(tenant)
256 else:
257 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
258
Emilien Macchibb71e072014-07-05 19:18:52 +0200259
260def destroy_tenants(tenants):
261 admin = keystone_admin()
262 for tenant in tenants:
263 tenant_id = admin.identity.get_tenant_by_name(tenant)['id']
David Kranzb7afa922014-12-30 10:56:26 -0500264 admin.identity.delete_tenant(tenant_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200265
Sean Dague655e0af2014-05-29 09:00:22 -0400266##############
267#
268# USERS
269#
270##############
271
272
273def _users_for_tenant(users, tenant):
274 u_for_t = []
275 for user in users:
276 for n in user:
277 if user[n]['tenant'] == tenant:
278 u_for_t.append(user[n])
279 return u_for_t
280
281
282def _tenants_from_users(users):
283 tenants = set()
284 for user in users:
285 for n in user:
286 tenants.add(user[n]['tenant'])
287 return tenants
288
289
290def _assign_swift_role(user):
291 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500292 roles = admin.identity.list_roles()
Sean Dague655e0af2014-05-29 09:00:22 -0400293 role = next(r for r in roles if r['name'] == 'Member')
294 LOG.debug(USERS[user])
295 try:
296 admin.identity.assign_user_role(
297 USERS[user]['tenant_id'],
298 USERS[user]['id'],
299 role['id'])
300 except exceptions.Conflict:
301 # don't care if it's already assigned
302 pass
303
304
305def create_users(users):
306 """Create tenants from resource definition.
307
308 Don't create the tenants if they already exist.
309 """
310 global USERS
311 LOG.info("Creating users")
312 admin = keystone_admin()
313 for u in users:
314 try:
315 tenant = admin.identity.get_tenant_by_name(u['tenant'])
316 except exceptions.NotFound:
317 LOG.error("Tenant: %s - not found" % u['tenant'])
318 continue
319 try:
320 admin.identity.get_user_by_username(tenant['id'], u['name'])
321 LOG.warn("User '%s' already exists in this environment"
322 % u['name'])
323 except exceptions.NotFound:
324 admin.identity.create_user(
325 u['name'], u['pass'], tenant['id'],
326 "%s@%s" % (u['name'], tenant['id']),
327 enabled=True)
328
329
Emilien Macchibb71e072014-07-05 19:18:52 +0200330def destroy_users(users):
331 admin = keystone_admin()
332 for user in users:
Emilien Macchi436de862014-09-30 17:09:50 -0400333 tenant_id = admin.identity.get_tenant_by_name(user['tenant'])['id']
334 user_id = admin.identity.get_user_by_username(tenant_id,
335 user['name'])['id']
David Kranzb7afa922014-12-30 10:56:26 -0500336 admin.identity.delete_user(user_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200337
338
Sean Dague655e0af2014-05-29 09:00:22 -0400339def collect_users(users):
340 global USERS
Joe Gordon618c9fb2014-07-16 15:40:01 +0200341 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400342 admin = keystone_admin()
343 for u in users:
344 tenant = admin.identity.get_tenant_by_name(u['tenant'])
345 u['tenant_id'] = tenant['id']
346 USERS[u['name']] = u
347 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
348 USERS[u['name']]['id'] = body['id']
349
350
351class JavelinCheck(unittest.TestCase):
352 def __init__(self, users, resources):
353 super(JavelinCheck, self).__init__()
354 self.users = users
355 self.res = resources
356
357 def runTest(self, *args):
358 pass
359
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200360 def _ping_ip(self, ip_addr, count, namespace=None):
361 if namespace is None:
362 ping_cmd = "ping -c1 " + ip_addr
363 else:
364 ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
365 ip_addr)
366 for current in range(count):
367 return_code = os.system(ping_cmd)
368 if return_code is 0:
369 break
370 self.assertNotEqual(current, count - 1,
371 "Server is not pingable at %s" % ip_addr)
372
Sean Dague655e0af2014-05-29 09:00:22 -0400373 def check(self):
374 self.check_users()
375 self.check_objects()
376 self.check_servers()
Emilien Macchid18fec12014-09-15 14:32:54 -0400377 self.check_volumes()
Chris Dent878f3782014-06-30 17:04:15 +0100378 self.check_telemetry()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200379 self.check_secgroups()
380
381 # validate neutron is enabled and ironic disabled:
382 # Tenant network isolation is not supported when using ironic.
383 # "admin" has set up a neutron flat network environment within a shared
384 # fixed network for all tenants to use.
385 # In this case, network/subnet/router creation can be skipped and the
386 # server booted the same as nova network.
387 if (CONF.service_available.neutron and
388 not CONF.baremetal.driver_enabled):
389 self.check_networking()
Sean Dague655e0af2014-05-29 09:00:22 -0400390
391 def check_users(self):
392 """Check that the users we expect to exist, do.
393
394 We don't use the resource list for this because we need to validate
395 that things like tenantId didn't drift across versions.
396 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200397 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400398 for name, user in self.users.iteritems():
399 client = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500400 found = client.identity.get_user(user['id'])
Sean Dague655e0af2014-05-29 09:00:22 -0400401 self.assertEqual(found['name'], user['name'])
402 self.assertEqual(found['tenantId'], user['tenant_id'])
403
404 # also ensure we can auth with that user, and do something
405 # on the cloud. We don't care about the results except that it
406 # remains authorized.
407 client = client_for_user(user['name'])
408 resp, body = client.servers.list_servers()
409 self.assertEqual(resp['status'], '200')
410
411 def check_objects(self):
412 """Check that the objects created are still there."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000413 if not self.res.get('objects'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000414 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200415 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400416 for obj in self.res['objects']:
417 client = client_for_user(obj['owner'])
418 r, contents = client.objects.get_object(
419 obj['container'], obj['name'])
420 source = _file_contents(obj['file'])
421 self.assertEqual(contents, source)
422
423 def check_servers(self):
424 """Check that the servers are still up and running."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000425 if not self.res.get('servers'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000426 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200427 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400428 for server in self.res['servers']:
429 client = client_for_user(server['owner'])
430 found = _get_server_by_name(client, server['name'])
431 self.assertIsNotNone(
432 found,
433 "Couldn't find expected server %s" % server['name'])
434
435 r, found = client.servers.get_server(found['id'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200436 # validate neutron is enabled and ironic disabled:
437 if (CONF.service_available.neutron and
438 not CONF.baremetal.driver_enabled):
439 for network_name, body in found['addresses'].items():
440 for addr in body:
441 ip = addr['addr']
442 if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
443 namespace = _get_router_namespace(client,
444 network_name)
445 self._ping_ip(ip, 60, namespace)
446 else:
447 self._ping_ip(ip, 60)
448 else:
449 addr = found['addresses']['private'][0]['addr']
450 self._ping_ip(addr, 60)
451
452 def check_secgroups(self):
Joe H. Rahme02736732015-01-27 18:33:09 +0100453 """Check that the security groups still exist."""
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200454 LOG.info("Checking security groups")
455 for secgroup in self.res['secgroups']:
456 client = client_for_user(secgroup['owner'])
457 found = _get_resource_by_name(client.secgroups, 'security_groups',
458 secgroup['name'])
459 self.assertIsNotNone(
460 found,
461 "Couldn't find expected secgroup %s" % secgroup['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400462
Chris Dent878f3782014-06-30 17:04:15 +0100463 def check_telemetry(self):
464 """Check that ceilometer provides a sane sample.
465
466 Confirm that there are more than one sample and that they have the
467 expected metadata.
468
469 If in check mode confirm that the oldest sample available is from
470 before the upgrade.
471 """
Chris Dent0b76f2f2014-10-10 14:24:28 +0100472 if not self.res.get('telemetry'):
473 return
Chris Dent878f3782014-06-30 17:04:15 +0100474 LOG.info("checking telemetry")
475 for server in self.res['servers']:
476 client = client_for_user(server['owner'])
David Kranz20d06f42015-02-09 14:54:15 -0500477 body = client.telemetry.list_samples(
Chris Dent878f3782014-06-30 17:04:15 +0100478 'instance',
479 query=('metadata.display_name', 'eq', server['name'])
480 )
Chris Dent878f3782014-06-30 17:04:15 +0100481 self.assertTrue(len(body) >= 1, 'expecting at least one sample')
482 self._confirm_telemetry_sample(server, body[-1])
483
Emilien Macchi626b4f82014-06-15 21:44:29 +0200484 def check_volumes(self):
485 """Check that the volumes are still there and attached."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000486 if not self.res.get('volumes'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000487 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200488 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200489 for volume in self.res['volumes']:
490 client = client_for_user(volume['owner'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400491 vol_body = _get_volume_by_name(client, volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200492 self.assertIsNotNone(
Emilien Macchid18fec12014-09-15 14:32:54 -0400493 vol_body,
Emilien Macchi626b4f82014-06-15 21:44:29 +0200494 "Couldn't find expected volume %s" % volume['name'])
495
496 # Verify that a volume's attachment retrieved
497 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400498 attachment = client.volumes.get_attachment_from_volume(vol_body)
499 self.assertEqual(vol_body['id'], attachment['volume_id'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200500 self.assertEqual(server_id, attachment['server_id'])
501
Chris Dent878f3782014-06-30 17:04:15 +0100502 def _confirm_telemetry_sample(self, server, sample):
503 """Check this sample matches the expected resource metadata."""
504 # Confirm display_name
505 self.assertEqual(server['name'],
506 sample['resource_metadata']['display_name'])
507 # Confirm instance_type of flavor
508 flavor = sample['resource_metadata'].get(
509 'flavor.name',
510 sample['resource_metadata'].get('instance_type')
511 )
512 self.assertEqual(server['flavor'], flavor)
513 # Confirm the oldest sample was created before upgrade.
514 if OPTS.mode == 'check':
515 oldest_timestamp = timeutils.normalize_time(
516 timeutils.parse_isotime(sample['timestamp']))
517 self.assertTrue(
518 oldest_timestamp < JAVELIN_START,
519 'timestamp should come before start of second javelin run'
520 )
521
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200522 def check_networking(self):
523 """Check that the networks are still there."""
524 for res_type in ('networks', 'subnets', 'routers'):
525 for res in self.res[res_type]:
526 client = client_for_user(res['owner'])
527 found = _get_resource_by_name(client.networks, res_type,
528 res['name'])
529 self.assertIsNotNone(
530 found,
531 "Couldn't find expected resource %s" % res['name'])
532
Sean Dague655e0af2014-05-29 09:00:22 -0400533
534#######################
535#
536# OBJECTS
537#
538#######################
539
540
541def _file_contents(fname):
542 with open(fname, 'r') as f:
543 return f.read()
544
545
546def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000547 if not objects:
548 return
Sean Dague655e0af2014-05-29 09:00:22 -0400549 LOG.info("Creating objects")
550 for obj in objects:
551 LOG.debug("Object %s" % obj)
552 _assign_swift_role(obj['owner'])
553 client = client_for_user(obj['owner'])
554 client.containers.create_container(obj['container'])
555 client.objects.create_object(
556 obj['container'], obj['name'],
557 _file_contents(obj['file']))
558
Emilien Macchibb71e072014-07-05 19:18:52 +0200559
560def destroy_objects(objects):
561 for obj in objects:
562 client = client_for_user(obj['owner'])
563 r, body = client.objects.delete_object(obj['container'], obj['name'])
Emilien Macchid70f5102014-09-10 09:54:49 -0400564 if not (200 <= int(r['status']) < 299):
Emilien Macchibb71e072014-07-05 19:18:52 +0200565 raise ValueError("unable to destroy object: [%s] %s" % (r, body))
566
567
Sean Dague655e0af2014-05-29 09:00:22 -0400568#######################
569#
570# IMAGES
571#
572#######################
573
574
Sean Dague319b37a2014-07-11 07:28:11 -0400575def _resolve_image(image, imgtype):
576 name = image[imgtype]
577 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
578 return name, fname
579
580
Joe Gordon6f0426c2014-07-25 01:10:28 +0000581def _get_image_by_name(client, name):
David Kranz34f18782015-01-06 13:43:55 -0500582 body = client.images.image_list()
Joe Gordon6f0426c2014-07-25 01:10:28 +0000583 for image in body:
584 if name == image['name']:
585 return image
586 return None
587
588
Sean Dague655e0af2014-05-29 09:00:22 -0400589def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000590 if not images:
591 return
Joe Gordona18d6862014-07-24 22:55:46 +0000592 LOG.info("Creating images")
Sean Dague655e0af2014-05-29 09:00:22 -0400593 for image in images:
594 client = client_for_user(image['owner'])
595
596 # only upload a new image if the name isn't there
Joe Gordon6f0426c2014-07-25 01:10:28 +0000597 if _get_image_by_name(client, image['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000598 LOG.info("Image '%s' already exists" % image['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400599 continue
600
601 # special handling for 3 part image
602 extras = {}
603 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400604 name, fname = _resolve_image(image, 'aki')
David Kranz34f18782015-01-06 13:43:55 -0500605 aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400606 'javelin_' + name, 'aki', 'aki')
607 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400608 extras['kernel_id'] = aki.get('id')
609
Sean Dague319b37a2014-07-11 07:28:11 -0400610 name, fname = _resolve_image(image, 'ari')
David Kranz34f18782015-01-06 13:43:55 -0500611 ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400612 'javelin_' + name, 'ari', 'ari')
613 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400614 extras['ramdisk_id'] = ari.get('id')
615
Sean Dague319b37a2014-07-11 07:28:11 -0400616 _, fname = _resolve_image(image, 'file')
David Kranz34f18782015-01-06 13:43:55 -0500617 body = client.images.create_image(
Sean Dague655e0af2014-05-29 09:00:22 -0400618 image['name'], image['format'], image['format'], **extras)
619 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400620 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400621
622
Joe Gordon6f0426c2014-07-25 01:10:28 +0000623def destroy_images(images):
624 if not images:
625 return
626 LOG.info("Destroying images")
627 for image in images:
628 client = client_for_user(image['owner'])
629
630 response = _get_image_by_name(client, image['name'])
631 if not response:
632 LOG.info("Image '%s' does not exists" % image['name'])
633 continue
634 client.images.delete_image(response['id'])
635
636
Sean Dague655e0af2014-05-29 09:00:22 -0400637#######################
638#
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200639# NETWORKS
640#
641#######################
642
643def _get_router_namespace(client, network):
644 network_id = _get_resource_by_name(client.networks,
645 'networks', network)['id']
David Kranz34e88122014-12-11 15:24:05 -0500646 n_body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200647 for router in n_body['routers']:
648 router_id = router['id']
David Kranz34e88122014-12-11 15:24:05 -0500649 r_body = client.networks.list_router_interfaces(router_id)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200650 for port in r_body['ports']:
651 if port['network_id'] == network_id:
652 return "qrouter-%s" % router_id
653
654
655def _get_resource_by_name(client, resource, name):
656 get_resources = getattr(client, 'list_%s' % resource)
657 if get_resources is None:
658 raise AttributeError("client doesn't have method list_%s" % resource)
David Kranz34e88122014-12-11 15:24:05 -0500659 # Until all tempest client methods are changed to return only one value,
660 # we cannot assume they all have the same signature so we need to discard
661 # the unused response first value it two values are being returned.
662 body = get_resources()
663 if type(body) == tuple:
664 body = body[1]
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200665 if isinstance(body, dict):
666 body = body[resource]
667 for res in body:
668 if name == res['name']:
669 return res
670 raise ValueError('%s not found in %s resources' % (name, resource))
671
672
673def create_networks(networks):
674 LOG.info("Creating networks")
675 for network in networks:
676 client = client_for_user(network['owner'])
677
678 # only create a network if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500679 body = client.networks.list_networks()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200680 if any(item['name'] == network['name'] for item in body['networks']):
681 LOG.warning("Dupplicated network name: %s" % network['name'])
682 continue
683
684 client.networks.create_network(name=network['name'])
685
686
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100687def destroy_networks(networks):
688 LOG.info("Destroying subnets")
689 for network in networks:
690 client = client_for_user(network['owner'])
691 network_id = _get_resource_by_name(client.networks, 'networks',
692 network['name'])['id']
693 client.networks.delete_network(network_id)
694
695
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200696def create_subnets(subnets):
697 LOG.info("Creating subnets")
698 for subnet in subnets:
699 client = client_for_user(subnet['owner'])
700
701 network = _get_resource_by_name(client.networks, 'networks',
702 subnet['network'])
703 ip_version = netaddr.IPNetwork(subnet['range']).version
704 # ensure we don't overlap with another subnet in the network
705 try:
706 client.networks.create_subnet(network_id=network['id'],
707 cidr=subnet['range'],
708 name=subnet['name'],
709 ip_version=ip_version)
710 except exceptions.BadRequest as e:
711 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
712 if not is_overlapping_cidr:
713 raise
714
715
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100716def destroy_subnets(subnets):
717 LOG.info("Destroying subnets")
718 for subnet in subnets:
719 client = client_for_user(subnet['owner'])
720 subnet_id = _get_resource_by_name(client.networks,
721 'subnets', subnet['name'])['id']
722 client.networks.delete_subnet(subnet_id)
723
724
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200725def create_routers(routers):
726 LOG.info("Creating routers")
727 for router in routers:
728 client = client_for_user(router['owner'])
729
730 # only create a router if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500731 body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200732 if any(item['name'] == router['name'] for item in body['routers']):
733 LOG.warning("Dupplicated router name: %s" % router['name'])
734 continue
735
736 client.networks.create_router(router['name'])
737
738
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100739def destroy_routers(routers):
740 LOG.info("Destroying routers")
741 for router in routers:
742 client = client_for_user(router['owner'])
743 router_id = _get_resource_by_name(client.networks,
744 'routers', router['name'])['id']
745 for subnet in router['subnet']:
746 subnet_id = _get_resource_by_name(client.networks,
747 'subnets', subnet)['id']
748 client.networks.remove_router_interface_with_subnet_id(router_id,
749 subnet_id)
750 client.networks.delete_router(router_id)
751
752
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200753def add_router_interface(routers):
754 for router in routers:
755 client = client_for_user(router['owner'])
756 router_id = _get_resource_by_name(client.networks,
757 'routers', router['name'])['id']
758
759 for subnet in router['subnet']:
760 subnet_id = _get_resource_by_name(client.networks,
761 'subnets', subnet)['id']
762 # connect routers to their subnets
763 client.networks.add_router_interface_with_subnet_id(router_id,
764 subnet_id)
765 # connect routers to exteral network if set to "gateway"
766 if router['gateway']:
767 if CONF.network.public_network_id:
768 ext_net = CONF.network.public_network_id
769 client.networks._update_router(
770 router_id, set_enable_snat=True,
771 external_gateway_info={"network_id": ext_net})
772 else:
773 raise ValueError('public_network_id is not configured.')
774
775
776#######################
777#
Sean Dague655e0af2014-05-29 09:00:22 -0400778# SERVERS
779#
780#######################
781
782def _get_server_by_name(client, name):
783 r, body = client.servers.list_servers()
784 for server in body['servers']:
785 if name == server['name']:
786 return server
787 return None
788
789
Sean Dague655e0af2014-05-29 09:00:22 -0400790def _get_flavor_by_name(client, name):
David Kranz2fa77b22015-02-09 11:39:50 -0500791 body = client.flavors.list_flavors()
Sean Dague655e0af2014-05-29 09:00:22 -0400792 for flavor in body:
793 if name == flavor['name']:
794 return flavor
795 return None
796
797
798def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000799 if not servers:
800 return
Joe Gordona18d6862014-07-24 22:55:46 +0000801 LOG.info("Creating servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400802 for server in servers:
803 client = client_for_user(server['owner'])
804
805 if _get_server_by_name(client, server['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000806 LOG.info("Server '%s' already exists" % server['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400807 continue
808
809 image_id = _get_image_by_name(client, server['image'])['id']
810 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200811 # validate neutron is enabled and ironic disabled
812 kwargs = dict()
813 if (CONF.service_available.neutron and
814 not CONF.baremetal.driver_enabled and server.get('networks')):
815 get_net_id = lambda x: (_get_resource_by_name(
816 client.networks, 'networks', x)['id'])
817 kwargs['networks'] = [{'uuid': get_net_id(network)}
818 for network in server['networks']]
819 resp, body = client.servers.create_server(
820 server['name'], image_id, flavor_id, **kwargs)
Joe Gordon10f260b2014-07-24 23:27:19 +0000821 server_id = body['id']
822 client.servers.wait_for_server_status(server_id, 'ACTIVE')
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200823 # create to security group(s) after server spawning
824 for secgroup in server['secgroups']:
825 client.servers.add_security_group(server_id, secgroup)
Sean Dague655e0af2014-05-29 09:00:22 -0400826
827
Joe Gordondb63b1c2014-07-24 23:21:21 +0000828def destroy_servers(servers):
829 if not servers:
830 return
831 LOG.info("Destroying servers")
832 for server in servers:
833 client = client_for_user(server['owner'])
834
835 response = _get_server_by_name(client, server['name'])
836 if not response:
837 LOG.info("Server '%s' does not exist" % server['name'])
838 continue
839
840 client.servers.delete_server(response['id'])
841 client.servers.wait_for_server_termination(response['id'],
Matthew Treinish1d14c542014-06-17 20:25:40 -0400842 ignore_error=True)
Joe Gordondb63b1c2014-07-24 23:21:21 +0000843
844
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200845def create_secgroups(secgroups):
846 LOG.info("Creating security groups")
847 for secgroup in secgroups:
848 client = client_for_user(secgroup['owner'])
849
850 # only create a security group if the name isn't here
851 # i.e. a security group may be used by another server
852 # only create a router if the name isn't here
David Kranz9964b4e2015-02-06 15:45:29 -0500853 body = client.secgroups.list_security_groups()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200854 if any(item['name'] == secgroup['name'] for item in body):
855 LOG.warning("Security group '%s' already exists" %
856 secgroup['name'])
857 continue
858
David Kranz9964b4e2015-02-06 15:45:29 -0500859 body = client.secgroups.create_security_group(
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200860 secgroup['name'], secgroup['description'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200861 secgroup_id = body['id']
862 # for each security group, create the rules
863 for rule in secgroup['rules']:
864 ip_proto, from_port, to_port, cidr = rule.split()
865 client.secgroups.create_security_group_rule(
866 secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
867
868
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100869def destroy_secgroups(secgroups):
870 LOG.info("Destroying security groups")
871 for secgroup in secgroups:
872 client = client_for_user(secgroup['owner'])
873 sg_id = _get_resource_by_name(client.secgroups,
874 'security_groups',
875 secgroup['name'])
876 # sg rules are deleted automatically
877 client.secgroups.delete_security_group(sg_id['id'])
878
879
Sean Dague655e0af2014-05-29 09:00:22 -0400880#######################
881#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200882# VOLUMES
883#
884#######################
885
886def _get_volume_by_name(client, name):
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000887 body = client.volumes.list_volumes()
Emilien Macchid18fec12014-09-15 14:32:54 -0400888 for volume in body:
889 if name == volume['display_name']:
Emilien Macchi626b4f82014-06-15 21:44:29 +0200890 return volume
891 return None
892
893
894def create_volumes(volumes):
Chris Dent51e76de2014-10-01 12:07:14 +0100895 if not volumes:
896 return
897 LOG.info("Creating volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200898 for volume in volumes:
899 client = client_for_user(volume['owner'])
900
901 # only create a volume if the name isn't here
Emilien Macchid18fec12014-09-15 14:32:54 -0400902 if _get_volume_by_name(client, volume['name']):
903 LOG.info("volume '%s' already exists" % volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200904 continue
905
Emilien Macchid18fec12014-09-15 14:32:54 -0400906 size = volume['gb']
907 v_name = volume['name']
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000908 body = client.volumes.create_volume(size=size,
909 display_name=v_name)
Emilien Macchid18fec12014-09-15 14:32:54 -0400910 client.volumes.wait_for_volume_status(body['id'], 'available')
Emilien Macchi626b4f82014-06-15 21:44:29 +0200911
912
Emilien Macchibb71e072014-07-05 19:18:52 +0200913def destroy_volumes(volumes):
914 for volume in volumes:
915 client = client_for_user(volume['owner'])
916 volume_id = _get_volume_by_name(client, volume['name'])['id']
Emilien Macchi5ebc27b2014-09-15 14:30:35 -0400917 client.volumes.detach_volume(volume_id)
918 client.volumes.delete_volume(volume_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200919
920
Emilien Macchi626b4f82014-06-15 21:44:29 +0200921def attach_volumes(volumes):
922 for volume in volumes:
923 client = client_for_user(volume['owner'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200924 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400925 volume_id = _get_volume_by_name(client, volume['name'])['id']
926 device = volume['device']
927 client.volumes.attach_volume(volume_id, server_id, device)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200928
929
930#######################
931#
Sean Dague655e0af2014-05-29 09:00:22 -0400932# MAIN LOGIC
933#
934#######################
935
936def create_resources():
937 LOG.info("Creating Resources")
938 # first create keystone level resources, and we need to be admin
939 # for those.
940 create_tenants(RES['tenants'])
941 create_users(RES['users'])
942 collect_users(RES['users'])
943
944 # next create resources in a well known order
945 create_objects(RES['objects'])
946 create_images(RES['images'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200947
948 # validate neutron is enabled and ironic is disabled
949 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
950 create_networks(RES['networks'])
951 create_subnets(RES['subnets'])
952 create_routers(RES['routers'])
953 add_router_interface(RES['routers'])
954
955 create_secgroups(RES['secgroups'])
Sean Dague655e0af2014-05-29 09:00:22 -0400956 create_servers(RES['servers'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400957 create_volumes(RES['volumes'])
958 attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400959
960
Joe Gordondb63b1c2014-07-24 23:21:21 +0000961def destroy_resources():
962 LOG.info("Destroying Resources")
963 # Destroy in inverse order of create
Joe Gordondb63b1c2014-07-24 23:21:21 +0000964 destroy_servers(RES['servers'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000965 destroy_images(RES['images'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200966 destroy_objects(RES['objects'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200967 destroy_volumes(RES['volumes'])
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100968 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
969 destroy_routers(RES['routers'])
970 destroy_subnets(RES['subnets'])
971 destroy_networks(RES['networks'])
972 destroy_secgroups(RES['secgroups'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200973 destroy_users(RES['users'])
974 destroy_tenants(RES['tenants'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000975 LOG.warn("Destroy mode incomplete")
976
Joe Gordondb63b1c2014-07-24 23:21:21 +0000977
Sean Dague655e0af2014-05-29 09:00:22 -0400978def get_options():
979 global OPTS
980 parser = argparse.ArgumentParser(
981 description='Create and validate a fixed set of OpenStack resources')
982 parser.add_argument('-m', '--mode',
983 metavar='<create|check|destroy>',
984 required=True,
985 help=('One of (create, check, destroy)'))
986 parser.add_argument('-r', '--resources',
987 required=True,
988 metavar='resourcefile.yaml',
989 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000990
Sean Dague319b37a2014-07-11 07:28:11 -0400991 parser.add_argument(
992 '-d', '--devstack-base',
993 required=True,
994 metavar='/opt/stack/old',
995 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000996 parser.add_argument(
997 '-c', '--config-file',
998 metavar='/etc/tempest.conf',
999 help='path to javelin2(tempest) config file')
1000
Sean Dague655e0af2014-05-29 09:00:22 -04001001 # auth bits, letting us also just source the devstack openrc
1002 parser.add_argument('--os-username',
1003 metavar='<auth-user-name>',
1004 default=os.environ.get('OS_USERNAME'),
1005 help=('Defaults to env[OS_USERNAME].'))
1006 parser.add_argument('--os-password',
1007 metavar='<auth-password>',
1008 default=os.environ.get('OS_PASSWORD'),
1009 help=('Defaults to env[OS_PASSWORD].'))
1010 parser.add_argument('--os-tenant-name',
1011 metavar='<auth-tenant-name>',
1012 default=os.environ.get('OS_TENANT_NAME'),
1013 help=('Defaults to env[OS_TENANT_NAME].'))
1014
1015 OPTS = parser.parse_args()
1016 if OPTS.mode not in ('create', 'check', 'destroy'):
1017 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
1018 parser.print_help()
1019 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +00001020 if OPTS.config_file:
1021 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -04001022
1023
Joe Gordon915eb8e2014-07-17 11:25:46 +02001024def setup_logging():
Sean Dague655e0af2014-05-29 09:00:22 -04001025 global LOG
Joe Gordon915eb8e2014-07-17 11:25:46 +02001026 logging.setup(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001027 LOG = logging.getLogger(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001028
1029
1030def main():
1031 global RES
1032 get_options()
1033 setup_logging()
Chris Dent51e76de2014-10-01 12:07:14 +01001034 RES.update(load_resources(OPTS.resources))
Sean Dague655e0af2014-05-29 09:00:22 -04001035
1036 if OPTS.mode == 'create':
1037 create_resources()
Joe Gordon1a097002014-07-24 23:44:08 +00001038 # Make sure the resources we just created actually work
1039 checker = JavelinCheck(USERS, RES)
1040 checker.check()
Sean Dague655e0af2014-05-29 09:00:22 -04001041 elif OPTS.mode == 'check':
1042 collect_users(RES['users'])
1043 checker = JavelinCheck(USERS, RES)
1044 checker.check()
1045 elif OPTS.mode == 'destroy':
Joe Gordondb63b1c2014-07-24 23:21:21 +00001046 collect_users(RES['users'])
1047 destroy_resources()
Sean Dague655e0af2014-05-29 09:00:22 -04001048 else:
1049 LOG.error('Unknown mode %s' % OPTS.mode)
1050 return 1
Joe Gordon246353a2014-07-18 00:10:28 +02001051 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -04001052 return 0
1053
1054if __name__ == "__main__":
1055 sys.exit(main())