blob: 094f37ed654ad690161809e053493468774e239c [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
Masayuki Igawad9388762015-01-20 14:56:42 +0900113from tempest_lib import exceptions as lib_exc
Matthew Treinish96e9e882014-06-09 18:37:19 -0400114import yaml
Sean Dague655e0af2014-05-29 09:00:22 -0400115
116import tempest.auth
Joe Gordon28a84ae2014-07-17 15:38:28 +0000117from tempest import config
Sean Dague655e0af2014-05-29 09:00:22 -0400118from tempest import exceptions
Joe Gordon915eb8e2014-07-17 11:25:46 +0200119from tempest.openstack.common import log as logging
Chris Dent878f3782014-06-30 17:04:15 +0100120from tempest.openstack.common import timeutils
Sean Dague655e0af2014-05-29 09:00:22 -0400121from tempest.services.compute.json import flavors_client
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200122from tempest.services.compute.json import security_groups_client
Sean Dague655e0af2014-05-29 09:00:22 -0400123from tempest.services.compute.json import servers_client
124from tempest.services.identity.json import identity_client
125from tempest.services.image.v2.json import image_client
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200126from tempest.services.network.json import network_client
Sean Dague655e0af2014-05-29 09:00:22 -0400127from tempest.services.object_storage import container_client
128from tempest.services.object_storage import object_client
Chris Dent878f3782014-06-30 17:04:15 +0100129from tempest.services.telemetry.json import telemetry_client
Emilien Macchi626b4f82014-06-15 21:44:29 +0200130from tempest.services.volume.json import volumes_client
Sean Dague655e0af2014-05-29 09:00:22 -0400131
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200132CONF = config.CONF
Sean Dague655e0af2014-05-29 09:00:22 -0400133OPTS = {}
134USERS = {}
Chris Dent51e76de2014-10-01 12:07:14 +0100135RES = collections.defaultdict(list)
Sean Dague655e0af2014-05-29 09:00:22 -0400136
137LOG = None
138
Chris Dent878f3782014-06-30 17:04:15 +0100139JAVELIN_START = datetime.datetime.utcnow()
140
Sean Dague655e0af2014-05-29 09:00:22 -0400141
142class OSClient(object):
143 _creds = None
144 identity = None
145 servers = None
146
147 def __init__(self, user, pw, tenant):
Ken'ichi Ohmichi4771cbc2015-01-19 23:45:23 +0000148 default_params = {
149 'disable_ssl_certificate_validation':
150 CONF.identity.disable_ssl_certificate_validation,
151 'ca_certs': CONF.identity.ca_certificates_file,
152 'trace_requests': CONF.debug.trace_requests
153 }
Ken'ichi Ohmichid5dba1c2015-01-23 02:23:22 +0000154 default_params_with_timeout_values = {
155 'build_interval': CONF.compute.build_interval,
156 'build_timeout': CONF.compute.build_timeout
157 }
158 default_params_with_timeout_values.update(default_params)
159
Ken'ichi Ohmichi4771cbc2015-01-19 23:45:23 +0000160 compute_params = {
161 'service': CONF.compute.catalog_type,
162 'region': CONF.compute.region or CONF.identity.region,
163 'endpoint_type': CONF.compute.endpoint_type,
164 'build_interval': CONF.compute.build_interval,
165 'build_timeout': CONF.compute.build_timeout
166 }
167 compute_params.update(default_params)
168
Ken'ichi Ohmichi564b2ad2015-01-22 02:08:59 +0000169 object_storage_params = {
170 'service': CONF.object_storage.catalog_type,
171 'region': CONF.object_storage.region or CONF.identity.region,
172 'endpoint_type': CONF.object_storage.endpoint_type
173 }
174 object_storage_params.update(default_params)
175
Sean Dague655e0af2014-05-29 09:00:22 -0400176 _creds = tempest.auth.KeystoneV2Credentials(
177 username=user,
178 password=pw,
179 tenant_name=tenant)
180 _auth = tempest.auth.KeystoneV2AuthProvider(_creds)
181 self.identity = identity_client.IdentityClientJSON(_auth)
Ken'ichi Ohmichi4771cbc2015-01-19 23:45:23 +0000182 self.servers = servers_client.ServersClientJSON(_auth,
183 **compute_params)
184 self.flavors = flavors_client.FlavorsClientJSON(_auth,
185 **compute_params)
186 self.secgroups = security_groups_client.SecurityGroupsClientJSON(
187 _auth, **compute_params)
Ken'ichi Ohmichi564b2ad2015-01-22 02:08:59 +0000188 self.objects = object_client.ObjectClient(_auth,
189 **object_storage_params)
190 self.containers = container_client.ContainerClient(
191 _auth, **object_storage_params)
Sean Dague655e0af2014-05-29 09:00:22 -0400192 self.images = image_client.ImageClientV2JSON(_auth)
Ken'ichi Ohmichid5dba1c2015-01-23 02:23:22 +0000193 self.telemetry = telemetry_client.TelemetryClientJSON(
194 _auth,
195 CONF.telemetry.catalog_type,
196 CONF.identity.region,
197 endpoint_type=CONF.telemetry.endpoint_type,
198 **default_params_with_timeout_values)
Ken'ichi Ohmichif85e9bd2015-01-27 12:51:47 +0000199 self.volumes = volumes_client.VolumesClientJSON(
200 _auth,
201 CONF.volume.catalog_type,
202 CONF.volume.region or CONF.identity.region,
203 endpoint_type=CONF.volume.endpoint_type,
204 build_interval=CONF.volume.build_interval,
205 build_timeout=CONF.volume.build_timeout,
206 **default_params)
Ken'ichi Ohmichia182e862015-01-21 01:16:37 +0000207 self.networks = network_client.NetworkClientJSON(
208 _auth,
209 CONF.network.catalog_type,
210 CONF.network.region or CONF.identity.region,
211 endpoint_type=CONF.network.endpoint_type,
212 build_interval=CONF.network.build_interval,
213 build_timeout=CONF.network.build_timeout,
214 **default_params)
Sean Dague655e0af2014-05-29 09:00:22 -0400215
216
217def load_resources(fname):
Joe H. Rahme02736732015-01-27 18:33:09 +0100218 """Load the expected resources from a yaml file."""
Sean Dague655e0af2014-05-29 09:00:22 -0400219 return yaml.load(open(fname, 'r'))
220
221
222def keystone_admin():
223 return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
224
225
226def client_for_user(name):
227 LOG.debug("Entering client_for_user")
228 if name in USERS:
229 user = USERS[name]
230 LOG.debug("Created client for user %s" % user)
231 return OSClient(user['name'], user['pass'], user['tenant'])
232 else:
233 LOG.error("%s not found in USERS: %s" % (name, USERS))
234
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200235
236def resp_ok(response):
237 return 200 >= int(response['status']) < 300
238
Sean Dague655e0af2014-05-29 09:00:22 -0400239###################
240#
241# TENANTS
242#
243###################
244
245
246def create_tenants(tenants):
247 """Create tenants from resource definition.
248
249 Don't create the tenants if they already exist.
250 """
251 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500252 body = admin.identity.list_tenants()
Sean Dague655e0af2014-05-29 09:00:22 -0400253 existing = [x['name'] for x in body]
254 for tenant in tenants:
255 if tenant not in existing:
256 admin.identity.create_tenant(tenant)
257 else:
258 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
259
Emilien Macchibb71e072014-07-05 19:18:52 +0200260
261def destroy_tenants(tenants):
262 admin = keystone_admin()
263 for tenant in tenants:
264 tenant_id = admin.identity.get_tenant_by_name(tenant)['id']
David Kranzb7afa922014-12-30 10:56:26 -0500265 admin.identity.delete_tenant(tenant_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200266
Sean Dague655e0af2014-05-29 09:00:22 -0400267##############
268#
269# USERS
270#
271##############
272
273
274def _users_for_tenant(users, tenant):
275 u_for_t = []
276 for user in users:
277 for n in user:
278 if user[n]['tenant'] == tenant:
279 u_for_t.append(user[n])
280 return u_for_t
281
282
283def _tenants_from_users(users):
284 tenants = set()
285 for user in users:
286 for n in user:
287 tenants.add(user[n]['tenant'])
288 return tenants
289
290
291def _assign_swift_role(user):
292 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500293 roles = admin.identity.list_roles()
Sean Dague655e0af2014-05-29 09:00:22 -0400294 role = next(r for r in roles if r['name'] == 'Member')
295 LOG.debug(USERS[user])
296 try:
297 admin.identity.assign_user_role(
298 USERS[user]['tenant_id'],
299 USERS[user]['id'],
300 role['id'])
Masayuki Igawad9388762015-01-20 14:56:42 +0900301 except lib_exc.Conflict:
Sean Dague655e0af2014-05-29 09:00:22 -0400302 # don't care if it's already assigned
303 pass
304
305
306def create_users(users):
307 """Create tenants from resource definition.
308
309 Don't create the tenants if they already exist.
310 """
311 global USERS
312 LOG.info("Creating users")
313 admin = keystone_admin()
314 for u in users:
315 try:
316 tenant = admin.identity.get_tenant_by_name(u['tenant'])
317 except exceptions.NotFound:
318 LOG.error("Tenant: %s - not found" % u['tenant'])
319 continue
320 try:
321 admin.identity.get_user_by_username(tenant['id'], u['name'])
322 LOG.warn("User '%s' already exists in this environment"
323 % u['name'])
324 except exceptions.NotFound:
325 admin.identity.create_user(
326 u['name'], u['pass'], tenant['id'],
327 "%s@%s" % (u['name'], tenant['id']),
328 enabled=True)
329
330
Emilien Macchibb71e072014-07-05 19:18:52 +0200331def destroy_users(users):
332 admin = keystone_admin()
333 for user in users:
Emilien Macchi436de862014-09-30 17:09:50 -0400334 tenant_id = admin.identity.get_tenant_by_name(user['tenant'])['id']
335 user_id = admin.identity.get_user_by_username(tenant_id,
336 user['name'])['id']
David Kranzb7afa922014-12-30 10:56:26 -0500337 admin.identity.delete_user(user_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200338
339
Sean Dague655e0af2014-05-29 09:00:22 -0400340def collect_users(users):
341 global USERS
Joe Gordon618c9fb2014-07-16 15:40:01 +0200342 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400343 admin = keystone_admin()
344 for u in users:
345 tenant = admin.identity.get_tenant_by_name(u['tenant'])
346 u['tenant_id'] = tenant['id']
347 USERS[u['name']] = u
348 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
349 USERS[u['name']]['id'] = body['id']
350
351
352class JavelinCheck(unittest.TestCase):
353 def __init__(self, users, resources):
354 super(JavelinCheck, self).__init__()
355 self.users = users
356 self.res = resources
357
358 def runTest(self, *args):
359 pass
360
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200361 def _ping_ip(self, ip_addr, count, namespace=None):
362 if namespace is None:
363 ping_cmd = "ping -c1 " + ip_addr
364 else:
365 ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
366 ip_addr)
367 for current in range(count):
368 return_code = os.system(ping_cmd)
369 if return_code is 0:
370 break
371 self.assertNotEqual(current, count - 1,
372 "Server is not pingable at %s" % ip_addr)
373
Sean Dague655e0af2014-05-29 09:00:22 -0400374 def check(self):
375 self.check_users()
376 self.check_objects()
377 self.check_servers()
Emilien Macchid18fec12014-09-15 14:32:54 -0400378 self.check_volumes()
Chris Dent878f3782014-06-30 17:04:15 +0100379 self.check_telemetry()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200380 self.check_secgroups()
381
382 # validate neutron is enabled and ironic disabled:
383 # Tenant network isolation is not supported when using ironic.
384 # "admin" has set up a neutron flat network environment within a shared
385 # fixed network for all tenants to use.
386 # In this case, network/subnet/router creation can be skipped and the
387 # server booted the same as nova network.
388 if (CONF.service_available.neutron and
389 not CONF.baremetal.driver_enabled):
390 self.check_networking()
Sean Dague655e0af2014-05-29 09:00:22 -0400391
392 def check_users(self):
393 """Check that the users we expect to exist, do.
394
395 We don't use the resource list for this because we need to validate
396 that things like tenantId didn't drift across versions.
397 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200398 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400399 for name, user in self.users.iteritems():
400 client = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500401 found = client.identity.get_user(user['id'])
Sean Dague655e0af2014-05-29 09:00:22 -0400402 self.assertEqual(found['name'], user['name'])
403 self.assertEqual(found['tenantId'], user['tenant_id'])
404
405 # also ensure we can auth with that user, and do something
406 # on the cloud. We don't care about the results except that it
407 # remains authorized.
408 client = client_for_user(user['name'])
409 resp, body = client.servers.list_servers()
410 self.assertEqual(resp['status'], '200')
411
412 def check_objects(self):
413 """Check that the objects created are still there."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000414 if not self.res.get('objects'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000415 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200416 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400417 for obj in self.res['objects']:
418 client = client_for_user(obj['owner'])
419 r, contents = client.objects.get_object(
420 obj['container'], obj['name'])
421 source = _file_contents(obj['file'])
422 self.assertEqual(contents, source)
423
424 def check_servers(self):
425 """Check that the servers are still up and running."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000426 if not self.res.get('servers'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000427 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200428 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400429 for server in self.res['servers']:
430 client = client_for_user(server['owner'])
431 found = _get_server_by_name(client, server['name'])
432 self.assertIsNotNone(
433 found,
434 "Couldn't find expected server %s" % server['name'])
435
436 r, found = client.servers.get_server(found['id'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200437 # validate neutron is enabled and ironic disabled:
438 if (CONF.service_available.neutron and
439 not CONF.baremetal.driver_enabled):
440 for network_name, body in found['addresses'].items():
441 for addr in body:
442 ip = addr['addr']
443 if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
444 namespace = _get_router_namespace(client,
445 network_name)
446 self._ping_ip(ip, 60, namespace)
447 else:
448 self._ping_ip(ip, 60)
449 else:
450 addr = found['addresses']['private'][0]['addr']
451 self._ping_ip(addr, 60)
452
453 def check_secgroups(self):
Joe H. Rahme02736732015-01-27 18:33:09 +0100454 """Check that the security groups still exist."""
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200455 LOG.info("Checking security groups")
456 for secgroup in self.res['secgroups']:
457 client = client_for_user(secgroup['owner'])
458 found = _get_resource_by_name(client.secgroups, 'security_groups',
459 secgroup['name'])
460 self.assertIsNotNone(
461 found,
462 "Couldn't find expected secgroup %s" % secgroup['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400463
Chris Dent878f3782014-06-30 17:04:15 +0100464 def check_telemetry(self):
465 """Check that ceilometer provides a sane sample.
466
467 Confirm that there are more than one sample and that they have the
468 expected metadata.
469
470 If in check mode confirm that the oldest sample available is from
471 before the upgrade.
472 """
Chris Dent0b76f2f2014-10-10 14:24:28 +0100473 if not self.res.get('telemetry'):
474 return
Chris Dent878f3782014-06-30 17:04:15 +0100475 LOG.info("checking telemetry")
476 for server in self.res['servers']:
477 client = client_for_user(server['owner'])
David Kranz20d06f42015-02-09 14:54:15 -0500478 body = client.telemetry.list_samples(
Chris Dent878f3782014-06-30 17:04:15 +0100479 'instance',
480 query=('metadata.display_name', 'eq', server['name'])
481 )
Chris Dent878f3782014-06-30 17:04:15 +0100482 self.assertTrue(len(body) >= 1, 'expecting at least one sample')
483 self._confirm_telemetry_sample(server, body[-1])
484
Emilien Macchi626b4f82014-06-15 21:44:29 +0200485 def check_volumes(self):
486 """Check that the volumes are still there and attached."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000487 if not self.res.get('volumes'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000488 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200489 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200490 for volume in self.res['volumes']:
491 client = client_for_user(volume['owner'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400492 vol_body = _get_volume_by_name(client, volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200493 self.assertIsNotNone(
Emilien Macchid18fec12014-09-15 14:32:54 -0400494 vol_body,
Emilien Macchi626b4f82014-06-15 21:44:29 +0200495 "Couldn't find expected volume %s" % volume['name'])
496
497 # Verify that a volume's attachment retrieved
498 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400499 attachment = client.volumes.get_attachment_from_volume(vol_body)
500 self.assertEqual(vol_body['id'], attachment['volume_id'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200501 self.assertEqual(server_id, attachment['server_id'])
502
Chris Dent878f3782014-06-30 17:04:15 +0100503 def _confirm_telemetry_sample(self, server, sample):
504 """Check this sample matches the expected resource metadata."""
505 # Confirm display_name
506 self.assertEqual(server['name'],
507 sample['resource_metadata']['display_name'])
508 # Confirm instance_type of flavor
509 flavor = sample['resource_metadata'].get(
510 'flavor.name',
511 sample['resource_metadata'].get('instance_type')
512 )
513 self.assertEqual(server['flavor'], flavor)
514 # Confirm the oldest sample was created before upgrade.
515 if OPTS.mode == 'check':
516 oldest_timestamp = timeutils.normalize_time(
517 timeutils.parse_isotime(sample['timestamp']))
518 self.assertTrue(
519 oldest_timestamp < JAVELIN_START,
520 'timestamp should come before start of second javelin run'
521 )
522
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200523 def check_networking(self):
524 """Check that the networks are still there."""
525 for res_type in ('networks', 'subnets', 'routers'):
526 for res in self.res[res_type]:
527 client = client_for_user(res['owner'])
528 found = _get_resource_by_name(client.networks, res_type,
529 res['name'])
530 self.assertIsNotNone(
531 found,
532 "Couldn't find expected resource %s" % res['name'])
533
Sean Dague655e0af2014-05-29 09:00:22 -0400534
535#######################
536#
537# OBJECTS
538#
539#######################
540
541
542def _file_contents(fname):
543 with open(fname, 'r') as f:
544 return f.read()
545
546
547def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000548 if not objects:
549 return
Sean Dague655e0af2014-05-29 09:00:22 -0400550 LOG.info("Creating objects")
551 for obj in objects:
552 LOG.debug("Object %s" % obj)
553 _assign_swift_role(obj['owner'])
554 client = client_for_user(obj['owner'])
555 client.containers.create_container(obj['container'])
556 client.objects.create_object(
557 obj['container'], obj['name'],
558 _file_contents(obj['file']))
559
Emilien Macchibb71e072014-07-05 19:18:52 +0200560
561def destroy_objects(objects):
562 for obj in objects:
563 client = client_for_user(obj['owner'])
564 r, body = client.objects.delete_object(obj['container'], obj['name'])
Emilien Macchid70f5102014-09-10 09:54:49 -0400565 if not (200 <= int(r['status']) < 299):
Emilien Macchibb71e072014-07-05 19:18:52 +0200566 raise ValueError("unable to destroy object: [%s] %s" % (r, body))
567
568
Sean Dague655e0af2014-05-29 09:00:22 -0400569#######################
570#
571# IMAGES
572#
573#######################
574
575
Sean Dague319b37a2014-07-11 07:28:11 -0400576def _resolve_image(image, imgtype):
577 name = image[imgtype]
578 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
579 return name, fname
580
581
Joe Gordon6f0426c2014-07-25 01:10:28 +0000582def _get_image_by_name(client, name):
David Kranz34f18782015-01-06 13:43:55 -0500583 body = client.images.image_list()
Joe Gordon6f0426c2014-07-25 01:10:28 +0000584 for image in body:
585 if name == image['name']:
586 return image
587 return None
588
589
Sean Dague655e0af2014-05-29 09:00:22 -0400590def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000591 if not images:
592 return
Joe Gordona18d6862014-07-24 22:55:46 +0000593 LOG.info("Creating images")
Sean Dague655e0af2014-05-29 09:00:22 -0400594 for image in images:
595 client = client_for_user(image['owner'])
596
597 # only upload a new image if the name isn't there
Joe Gordon6f0426c2014-07-25 01:10:28 +0000598 if _get_image_by_name(client, image['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000599 LOG.info("Image '%s' already exists" % image['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400600 continue
601
602 # special handling for 3 part image
603 extras = {}
604 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400605 name, fname = _resolve_image(image, 'aki')
David Kranz34f18782015-01-06 13:43:55 -0500606 aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400607 'javelin_' + name, 'aki', 'aki')
608 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400609 extras['kernel_id'] = aki.get('id')
610
Sean Dague319b37a2014-07-11 07:28:11 -0400611 name, fname = _resolve_image(image, 'ari')
David Kranz34f18782015-01-06 13:43:55 -0500612 ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400613 'javelin_' + name, 'ari', 'ari')
614 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400615 extras['ramdisk_id'] = ari.get('id')
616
Sean Dague319b37a2014-07-11 07:28:11 -0400617 _, fname = _resolve_image(image, 'file')
David Kranz34f18782015-01-06 13:43:55 -0500618 body = client.images.create_image(
Sean Dague655e0af2014-05-29 09:00:22 -0400619 image['name'], image['format'], image['format'], **extras)
620 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400621 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400622
623
Joe Gordon6f0426c2014-07-25 01:10:28 +0000624def destroy_images(images):
625 if not images:
626 return
627 LOG.info("Destroying images")
628 for image in images:
629 client = client_for_user(image['owner'])
630
631 response = _get_image_by_name(client, image['name'])
632 if not response:
633 LOG.info("Image '%s' does not exists" % image['name'])
634 continue
635 client.images.delete_image(response['id'])
636
637
Sean Dague655e0af2014-05-29 09:00:22 -0400638#######################
639#
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200640# NETWORKS
641#
642#######################
643
644def _get_router_namespace(client, network):
645 network_id = _get_resource_by_name(client.networks,
646 'networks', network)['id']
David Kranz34e88122014-12-11 15:24:05 -0500647 n_body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200648 for router in n_body['routers']:
649 router_id = router['id']
David Kranz34e88122014-12-11 15:24:05 -0500650 r_body = client.networks.list_router_interfaces(router_id)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200651 for port in r_body['ports']:
652 if port['network_id'] == network_id:
653 return "qrouter-%s" % router_id
654
655
656def _get_resource_by_name(client, resource, name):
657 get_resources = getattr(client, 'list_%s' % resource)
658 if get_resources is None:
659 raise AttributeError("client doesn't have method list_%s" % resource)
David Kranz34e88122014-12-11 15:24:05 -0500660 # Until all tempest client methods are changed to return only one value,
661 # we cannot assume they all have the same signature so we need to discard
662 # the unused response first value it two values are being returned.
663 body = get_resources()
664 if type(body) == tuple:
665 body = body[1]
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200666 if isinstance(body, dict):
667 body = body[resource]
668 for res in body:
669 if name == res['name']:
670 return res
671 raise ValueError('%s not found in %s resources' % (name, resource))
672
673
674def create_networks(networks):
675 LOG.info("Creating networks")
676 for network in networks:
677 client = client_for_user(network['owner'])
678
679 # only create a network if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500680 body = client.networks.list_networks()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200681 if any(item['name'] == network['name'] for item in body['networks']):
682 LOG.warning("Dupplicated network name: %s" % network['name'])
683 continue
684
685 client.networks.create_network(name=network['name'])
686
687
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100688def destroy_networks(networks):
689 LOG.info("Destroying subnets")
690 for network in networks:
691 client = client_for_user(network['owner'])
692 network_id = _get_resource_by_name(client.networks, 'networks',
693 network['name'])['id']
694 client.networks.delete_network(network_id)
695
696
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200697def create_subnets(subnets):
698 LOG.info("Creating subnets")
699 for subnet in subnets:
700 client = client_for_user(subnet['owner'])
701
702 network = _get_resource_by_name(client.networks, 'networks',
703 subnet['network'])
704 ip_version = netaddr.IPNetwork(subnet['range']).version
705 # ensure we don't overlap with another subnet in the network
706 try:
707 client.networks.create_subnet(network_id=network['id'],
708 cidr=subnet['range'],
709 name=subnet['name'],
710 ip_version=ip_version)
711 except exceptions.BadRequest as e:
712 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
713 if not is_overlapping_cidr:
714 raise
715
716
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100717def destroy_subnets(subnets):
718 LOG.info("Destroying subnets")
719 for subnet in subnets:
720 client = client_for_user(subnet['owner'])
721 subnet_id = _get_resource_by_name(client.networks,
722 'subnets', subnet['name'])['id']
723 client.networks.delete_subnet(subnet_id)
724
725
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200726def create_routers(routers):
727 LOG.info("Creating routers")
728 for router in routers:
729 client = client_for_user(router['owner'])
730
731 # only create a router if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500732 body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200733 if any(item['name'] == router['name'] for item in body['routers']):
734 LOG.warning("Dupplicated router name: %s" % router['name'])
735 continue
736
737 client.networks.create_router(router['name'])
738
739
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100740def destroy_routers(routers):
741 LOG.info("Destroying routers")
742 for router in routers:
743 client = client_for_user(router['owner'])
744 router_id = _get_resource_by_name(client.networks,
745 'routers', router['name'])['id']
746 for subnet in router['subnet']:
747 subnet_id = _get_resource_by_name(client.networks,
748 'subnets', subnet)['id']
749 client.networks.remove_router_interface_with_subnet_id(router_id,
750 subnet_id)
751 client.networks.delete_router(router_id)
752
753
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200754def add_router_interface(routers):
755 for router in routers:
756 client = client_for_user(router['owner'])
757 router_id = _get_resource_by_name(client.networks,
758 'routers', router['name'])['id']
759
760 for subnet in router['subnet']:
761 subnet_id = _get_resource_by_name(client.networks,
762 'subnets', subnet)['id']
763 # connect routers to their subnets
764 client.networks.add_router_interface_with_subnet_id(router_id,
765 subnet_id)
766 # connect routers to exteral network if set to "gateway"
767 if router['gateway']:
768 if CONF.network.public_network_id:
769 ext_net = CONF.network.public_network_id
770 client.networks._update_router(
771 router_id, set_enable_snat=True,
772 external_gateway_info={"network_id": ext_net})
773 else:
774 raise ValueError('public_network_id is not configured.')
775
776
777#######################
778#
Sean Dague655e0af2014-05-29 09:00:22 -0400779# SERVERS
780#
781#######################
782
783def _get_server_by_name(client, name):
784 r, body = client.servers.list_servers()
785 for server in body['servers']:
786 if name == server['name']:
787 return server
788 return None
789
790
Sean Dague655e0af2014-05-29 09:00:22 -0400791def _get_flavor_by_name(client, name):
David Kranz2fa77b22015-02-09 11:39:50 -0500792 body = client.flavors.list_flavors()
Sean Dague655e0af2014-05-29 09:00:22 -0400793 for flavor in body:
794 if name == flavor['name']:
795 return flavor
796 return None
797
798
799def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000800 if not servers:
801 return
Joe Gordona18d6862014-07-24 22:55:46 +0000802 LOG.info("Creating servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400803 for server in servers:
804 client = client_for_user(server['owner'])
805
806 if _get_server_by_name(client, server['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000807 LOG.info("Server '%s' already exists" % server['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400808 continue
809
810 image_id = _get_image_by_name(client, server['image'])['id']
811 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200812 # validate neutron is enabled and ironic disabled
813 kwargs = dict()
814 if (CONF.service_available.neutron and
815 not CONF.baremetal.driver_enabled and server.get('networks')):
816 get_net_id = lambda x: (_get_resource_by_name(
817 client.networks, 'networks', x)['id'])
818 kwargs['networks'] = [{'uuid': get_net_id(network)}
819 for network in server['networks']]
820 resp, body = client.servers.create_server(
821 server['name'], image_id, flavor_id, **kwargs)
Joe Gordon10f260b2014-07-24 23:27:19 +0000822 server_id = body['id']
823 client.servers.wait_for_server_status(server_id, 'ACTIVE')
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200824 # create to security group(s) after server spawning
825 for secgroup in server['secgroups']:
826 client.servers.add_security_group(server_id, secgroup)
Sean Dague655e0af2014-05-29 09:00:22 -0400827
828
Joe Gordondb63b1c2014-07-24 23:21:21 +0000829def destroy_servers(servers):
830 if not servers:
831 return
832 LOG.info("Destroying servers")
833 for server in servers:
834 client = client_for_user(server['owner'])
835
836 response = _get_server_by_name(client, server['name'])
837 if not response:
838 LOG.info("Server '%s' does not exist" % server['name'])
839 continue
840
841 client.servers.delete_server(response['id'])
842 client.servers.wait_for_server_termination(response['id'],
Matthew Treinish1d14c542014-06-17 20:25:40 -0400843 ignore_error=True)
Joe Gordondb63b1c2014-07-24 23:21:21 +0000844
845
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200846def create_secgroups(secgroups):
847 LOG.info("Creating security groups")
848 for secgroup in secgroups:
849 client = client_for_user(secgroup['owner'])
850
851 # only create a security group if the name isn't here
852 # i.e. a security group may be used by another server
853 # only create a router if the name isn't here
David Kranz9964b4e2015-02-06 15:45:29 -0500854 body = client.secgroups.list_security_groups()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200855 if any(item['name'] == secgroup['name'] for item in body):
856 LOG.warning("Security group '%s' already exists" %
857 secgroup['name'])
858 continue
859
David Kranz9964b4e2015-02-06 15:45:29 -0500860 body = client.secgroups.create_security_group(
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200861 secgroup['name'], secgroup['description'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200862 secgroup_id = body['id']
863 # for each security group, create the rules
864 for rule in secgroup['rules']:
865 ip_proto, from_port, to_port, cidr = rule.split()
866 client.secgroups.create_security_group_rule(
867 secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
868
869
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100870def destroy_secgroups(secgroups):
871 LOG.info("Destroying security groups")
872 for secgroup in secgroups:
873 client = client_for_user(secgroup['owner'])
874 sg_id = _get_resource_by_name(client.secgroups,
875 'security_groups',
876 secgroup['name'])
877 # sg rules are deleted automatically
878 client.secgroups.delete_security_group(sg_id['id'])
879
880
Sean Dague655e0af2014-05-29 09:00:22 -0400881#######################
882#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200883# VOLUMES
884#
885#######################
886
887def _get_volume_by_name(client, name):
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000888 body = client.volumes.list_volumes()
Emilien Macchid18fec12014-09-15 14:32:54 -0400889 for volume in body:
890 if name == volume['display_name']:
Emilien Macchi626b4f82014-06-15 21:44:29 +0200891 return volume
892 return None
893
894
895def create_volumes(volumes):
Chris Dent51e76de2014-10-01 12:07:14 +0100896 if not volumes:
897 return
898 LOG.info("Creating volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200899 for volume in volumes:
900 client = client_for_user(volume['owner'])
901
902 # only create a volume if the name isn't here
Emilien Macchid18fec12014-09-15 14:32:54 -0400903 if _get_volume_by_name(client, volume['name']):
904 LOG.info("volume '%s' already exists" % volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200905 continue
906
Emilien Macchid18fec12014-09-15 14:32:54 -0400907 size = volume['gb']
908 v_name = volume['name']
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000909 body = client.volumes.create_volume(size=size,
910 display_name=v_name)
Emilien Macchid18fec12014-09-15 14:32:54 -0400911 client.volumes.wait_for_volume_status(body['id'], 'available')
Emilien Macchi626b4f82014-06-15 21:44:29 +0200912
913
Emilien Macchibb71e072014-07-05 19:18:52 +0200914def destroy_volumes(volumes):
915 for volume in volumes:
916 client = client_for_user(volume['owner'])
917 volume_id = _get_volume_by_name(client, volume['name'])['id']
Emilien Macchi5ebc27b2014-09-15 14:30:35 -0400918 client.volumes.detach_volume(volume_id)
919 client.volumes.delete_volume(volume_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200920
921
Emilien Macchi626b4f82014-06-15 21:44:29 +0200922def attach_volumes(volumes):
923 for volume in volumes:
924 client = client_for_user(volume['owner'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200925 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400926 volume_id = _get_volume_by_name(client, volume['name'])['id']
927 device = volume['device']
928 client.volumes.attach_volume(volume_id, server_id, device)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200929
930
931#######################
932#
Sean Dague655e0af2014-05-29 09:00:22 -0400933# MAIN LOGIC
934#
935#######################
936
937def create_resources():
938 LOG.info("Creating Resources")
939 # first create keystone level resources, and we need to be admin
940 # for those.
941 create_tenants(RES['tenants'])
942 create_users(RES['users'])
943 collect_users(RES['users'])
944
945 # next create resources in a well known order
946 create_objects(RES['objects'])
947 create_images(RES['images'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200948
949 # validate neutron is enabled and ironic is disabled
950 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
951 create_networks(RES['networks'])
952 create_subnets(RES['subnets'])
953 create_routers(RES['routers'])
954 add_router_interface(RES['routers'])
955
956 create_secgroups(RES['secgroups'])
Sean Dague655e0af2014-05-29 09:00:22 -0400957 create_servers(RES['servers'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400958 create_volumes(RES['volumes'])
959 attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400960
961
Joe Gordondb63b1c2014-07-24 23:21:21 +0000962def destroy_resources():
963 LOG.info("Destroying Resources")
964 # Destroy in inverse order of create
Joe Gordondb63b1c2014-07-24 23:21:21 +0000965 destroy_servers(RES['servers'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000966 destroy_images(RES['images'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200967 destroy_objects(RES['objects'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200968 destroy_volumes(RES['volumes'])
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100969 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
970 destroy_routers(RES['routers'])
971 destroy_subnets(RES['subnets'])
972 destroy_networks(RES['networks'])
973 destroy_secgroups(RES['secgroups'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200974 destroy_users(RES['users'])
975 destroy_tenants(RES['tenants'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000976 LOG.warn("Destroy mode incomplete")
977
Joe Gordondb63b1c2014-07-24 23:21:21 +0000978
Sean Dague655e0af2014-05-29 09:00:22 -0400979def get_options():
980 global OPTS
981 parser = argparse.ArgumentParser(
982 description='Create and validate a fixed set of OpenStack resources')
983 parser.add_argument('-m', '--mode',
984 metavar='<create|check|destroy>',
985 required=True,
986 help=('One of (create, check, destroy)'))
987 parser.add_argument('-r', '--resources',
988 required=True,
989 metavar='resourcefile.yaml',
990 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000991
Sean Dague319b37a2014-07-11 07:28:11 -0400992 parser.add_argument(
993 '-d', '--devstack-base',
994 required=True,
995 metavar='/opt/stack/old',
996 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000997 parser.add_argument(
998 '-c', '--config-file',
999 metavar='/etc/tempest.conf',
1000 help='path to javelin2(tempest) config file')
1001
Sean Dague655e0af2014-05-29 09:00:22 -04001002 # auth bits, letting us also just source the devstack openrc
1003 parser.add_argument('--os-username',
1004 metavar='<auth-user-name>',
1005 default=os.environ.get('OS_USERNAME'),
1006 help=('Defaults to env[OS_USERNAME].'))
1007 parser.add_argument('--os-password',
1008 metavar='<auth-password>',
1009 default=os.environ.get('OS_PASSWORD'),
1010 help=('Defaults to env[OS_PASSWORD].'))
1011 parser.add_argument('--os-tenant-name',
1012 metavar='<auth-tenant-name>',
1013 default=os.environ.get('OS_TENANT_NAME'),
1014 help=('Defaults to env[OS_TENANT_NAME].'))
1015
1016 OPTS = parser.parse_args()
1017 if OPTS.mode not in ('create', 'check', 'destroy'):
1018 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
1019 parser.print_help()
1020 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +00001021 if OPTS.config_file:
1022 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -04001023
1024
Joe Gordon915eb8e2014-07-17 11:25:46 +02001025def setup_logging():
Sean Dague655e0af2014-05-29 09:00:22 -04001026 global LOG
Joe Gordon915eb8e2014-07-17 11:25:46 +02001027 logging.setup(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001028 LOG = logging.getLogger(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001029
1030
1031def main():
1032 global RES
1033 get_options()
1034 setup_logging()
Chris Dent51e76de2014-10-01 12:07:14 +01001035 RES.update(load_resources(OPTS.resources))
Sean Dague655e0af2014-05-29 09:00:22 -04001036
1037 if OPTS.mode == 'create':
1038 create_resources()
Joe Gordon1a097002014-07-24 23:44:08 +00001039 # Make sure the resources we just created actually work
1040 checker = JavelinCheck(USERS, RES)
1041 checker.check()
Sean Dague655e0af2014-05-29 09:00:22 -04001042 elif OPTS.mode == 'check':
1043 collect_users(RES['users'])
1044 checker = JavelinCheck(USERS, RES)
1045 checker.check()
1046 elif OPTS.mode == 'destroy':
Joe Gordondb63b1c2014-07-24 23:21:21 +00001047 collect_users(RES['users'])
1048 destroy_resources()
Sean Dague655e0af2014-05-29 09:00:22 -04001049 else:
1050 LOG.error('Unknown mode %s' % OPTS.mode)
1051 return 1
Joe Gordon246353a2014-07-18 00:10:28 +02001052 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -04001053 return 0
1054
1055if __name__ == "__main__":
1056 sys.exit(main())