blob: 503745c159119cffe0e2271eafda60ed06b2291b [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
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)
ghanshyamd26b5cd2015-02-09 14:48:58 +0900180 self.identity = identity_client.IdentityClientJSON(
181 _auth,
182 CONF.identity.catalog_type,
183 CONF.identity.region,
184 endpoint_type='adminURL',
185 **default_params_with_timeout_values)
Ken'ichi Ohmichi4771cbc2015-01-19 23:45:23 +0000186 self.servers = servers_client.ServersClientJSON(_auth,
187 **compute_params)
188 self.flavors = flavors_client.FlavorsClientJSON(_auth,
189 **compute_params)
190 self.secgroups = security_groups_client.SecurityGroupsClientJSON(
191 _auth, **compute_params)
Ken'ichi Ohmichi564b2ad2015-01-22 02:08:59 +0000192 self.objects = object_client.ObjectClient(_auth,
193 **object_storage_params)
194 self.containers = container_client.ContainerClient(
195 _auth, **object_storage_params)
Sean Dague655e0af2014-05-29 09:00:22 -0400196 self.images = image_client.ImageClientV2JSON(_auth)
Ken'ichi Ohmichid5dba1c2015-01-23 02:23:22 +0000197 self.telemetry = telemetry_client.TelemetryClientJSON(
198 _auth,
199 CONF.telemetry.catalog_type,
200 CONF.identity.region,
201 endpoint_type=CONF.telemetry.endpoint_type,
202 **default_params_with_timeout_values)
Ken'ichi Ohmichif85e9bd2015-01-27 12:51:47 +0000203 self.volumes = volumes_client.VolumesClientJSON(
204 _auth,
205 CONF.volume.catalog_type,
206 CONF.volume.region or CONF.identity.region,
207 endpoint_type=CONF.volume.endpoint_type,
208 build_interval=CONF.volume.build_interval,
209 build_timeout=CONF.volume.build_timeout,
210 **default_params)
Ken'ichi Ohmichia182e862015-01-21 01:16:37 +0000211 self.networks = network_client.NetworkClientJSON(
212 _auth,
213 CONF.network.catalog_type,
214 CONF.network.region or CONF.identity.region,
215 endpoint_type=CONF.network.endpoint_type,
216 build_interval=CONF.network.build_interval,
217 build_timeout=CONF.network.build_timeout,
218 **default_params)
Sean Dague655e0af2014-05-29 09:00:22 -0400219
220
221def load_resources(fname):
Joe H. Rahme02736732015-01-27 18:33:09 +0100222 """Load the expected resources from a yaml file."""
Sean Dague655e0af2014-05-29 09:00:22 -0400223 return yaml.load(open(fname, 'r'))
224
225
226def keystone_admin():
227 return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
228
229
230def client_for_user(name):
231 LOG.debug("Entering client_for_user")
232 if name in USERS:
233 user = USERS[name]
234 LOG.debug("Created client for user %s" % user)
235 return OSClient(user['name'], user['pass'], user['tenant'])
236 else:
237 LOG.error("%s not found in USERS: %s" % (name, USERS))
238
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200239
Sean Dague655e0af2014-05-29 09:00:22 -0400240###################
241#
242# TENANTS
243#
244###################
245
246
247def create_tenants(tenants):
248 """Create tenants from resource definition.
249
250 Don't create the tenants if they already exist.
251 """
252 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500253 body = admin.identity.list_tenants()
Sean Dague655e0af2014-05-29 09:00:22 -0400254 existing = [x['name'] for x in body]
255 for tenant in tenants:
256 if tenant not in existing:
257 admin.identity.create_tenant(tenant)
258 else:
259 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
260
Emilien Macchibb71e072014-07-05 19:18:52 +0200261
262def destroy_tenants(tenants):
263 admin = keystone_admin()
264 for tenant in tenants:
265 tenant_id = admin.identity.get_tenant_by_name(tenant)['id']
David Kranzb7afa922014-12-30 10:56:26 -0500266 admin.identity.delete_tenant(tenant_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200267
Sean Dague655e0af2014-05-29 09:00:22 -0400268##############
269#
270# USERS
271#
272##############
273
274
275def _users_for_tenant(users, tenant):
276 u_for_t = []
277 for user in users:
278 for n in user:
279 if user[n]['tenant'] == tenant:
280 u_for_t.append(user[n])
281 return u_for_t
282
283
284def _tenants_from_users(users):
285 tenants = set()
286 for user in users:
287 for n in user:
288 tenants.add(user[n]['tenant'])
289 return tenants
290
291
292def _assign_swift_role(user):
293 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500294 roles = admin.identity.list_roles()
Sean Dague655e0af2014-05-29 09:00:22 -0400295 role = next(r for r in roles if r['name'] == 'Member')
296 LOG.debug(USERS[user])
297 try:
298 admin.identity.assign_user_role(
299 USERS[user]['tenant_id'],
300 USERS[user]['id'],
301 role['id'])
Masayuki Igawad9388762015-01-20 14:56:42 +0900302 except lib_exc.Conflict:
Sean Dague655e0af2014-05-29 09:00:22 -0400303 # don't care if it's already assigned
304 pass
305
306
307def create_users(users):
308 """Create tenants from resource definition.
309
310 Don't create the tenants if they already exist.
311 """
312 global USERS
313 LOG.info("Creating users")
314 admin = keystone_admin()
315 for u in users:
316 try:
317 tenant = admin.identity.get_tenant_by_name(u['tenant'])
Masayuki Igawabfa07602015-01-20 18:47:17 +0900318 except lib_exc.NotFound:
Sean Dague655e0af2014-05-29 09:00:22 -0400319 LOG.error("Tenant: %s - not found" % u['tenant'])
320 continue
321 try:
322 admin.identity.get_user_by_username(tenant['id'], u['name'])
323 LOG.warn("User '%s' already exists in this environment"
324 % u['name'])
Masayuki Igawabfa07602015-01-20 18:47:17 +0900325 except lib_exc.NotFound:
Sean Dague655e0af2014-05-29 09:00:22 -0400326 admin.identity.create_user(
327 u['name'], u['pass'], tenant['id'],
328 "%s@%s" % (u['name'], tenant['id']),
329 enabled=True)
330
331
Emilien Macchibb71e072014-07-05 19:18:52 +0200332def destroy_users(users):
333 admin = keystone_admin()
334 for user in users:
Emilien Macchi436de862014-09-30 17:09:50 -0400335 tenant_id = admin.identity.get_tenant_by_name(user['tenant'])['id']
336 user_id = admin.identity.get_user_by_username(tenant_id,
337 user['name'])['id']
David Kranzb7afa922014-12-30 10:56:26 -0500338 admin.identity.delete_user(user_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200339
340
Sean Dague655e0af2014-05-29 09:00:22 -0400341def collect_users(users):
342 global USERS
Joe Gordon618c9fb2014-07-16 15:40:01 +0200343 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400344 admin = keystone_admin()
345 for u in users:
346 tenant = admin.identity.get_tenant_by_name(u['tenant'])
347 u['tenant_id'] = tenant['id']
348 USERS[u['name']] = u
349 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
350 USERS[u['name']]['id'] = body['id']
351
352
353class JavelinCheck(unittest.TestCase):
354 def __init__(self, users, resources):
355 super(JavelinCheck, self).__init__()
356 self.users = users
357 self.res = resources
358
359 def runTest(self, *args):
360 pass
361
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200362 def _ping_ip(self, ip_addr, count, namespace=None):
363 if namespace is None:
364 ping_cmd = "ping -c1 " + ip_addr
365 else:
366 ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
367 ip_addr)
368 for current in range(count):
369 return_code = os.system(ping_cmd)
370 if return_code is 0:
371 break
372 self.assertNotEqual(current, count - 1,
373 "Server is not pingable at %s" % ip_addr)
374
Sean Dague655e0af2014-05-29 09:00:22 -0400375 def check(self):
376 self.check_users()
377 self.check_objects()
378 self.check_servers()
Emilien Macchid18fec12014-09-15 14:32:54 -0400379 self.check_volumes()
Chris Dent878f3782014-06-30 17:04:15 +0100380 self.check_telemetry()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200381 self.check_secgroups()
382
383 # validate neutron is enabled and ironic disabled:
384 # Tenant network isolation is not supported when using ironic.
385 # "admin" has set up a neutron flat network environment within a shared
386 # fixed network for all tenants to use.
387 # In this case, network/subnet/router creation can be skipped and the
388 # server booted the same as nova network.
389 if (CONF.service_available.neutron and
390 not CONF.baremetal.driver_enabled):
391 self.check_networking()
Sean Dague655e0af2014-05-29 09:00:22 -0400392
393 def check_users(self):
394 """Check that the users we expect to exist, do.
395
396 We don't use the resource list for this because we need to validate
397 that things like tenantId didn't drift across versions.
398 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200399 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400400 for name, user in self.users.iteritems():
401 client = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500402 found = client.identity.get_user(user['id'])
Sean Dague655e0af2014-05-29 09:00:22 -0400403 self.assertEqual(found['name'], user['name'])
404 self.assertEqual(found['tenantId'], user['tenant_id'])
405
406 # also ensure we can auth with that user, and do something
407 # on the cloud. We don't care about the results except that it
408 # remains authorized.
409 client = client_for_user(user['name'])
410 resp, body = client.servers.list_servers()
411 self.assertEqual(resp['status'], '200')
412
413 def check_objects(self):
414 """Check that the objects created are still there."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000415 if not self.res.get('objects'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000416 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200417 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400418 for obj in self.res['objects']:
419 client = client_for_user(obj['owner'])
420 r, contents = client.objects.get_object(
421 obj['container'], obj['name'])
422 source = _file_contents(obj['file'])
423 self.assertEqual(contents, source)
424
425 def check_servers(self):
426 """Check that the servers are still up and running."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000427 if not self.res.get('servers'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000428 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200429 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400430 for server in self.res['servers']:
431 client = client_for_user(server['owner'])
432 found = _get_server_by_name(client, server['name'])
433 self.assertIsNotNone(
434 found,
435 "Couldn't find expected server %s" % server['name'])
436
David Kranz0fb14292015-02-11 15:55:20 -0500437 found = client.servers.get_server(found['id'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200438 # validate neutron is enabled and ironic disabled:
439 if (CONF.service_available.neutron and
440 not CONF.baremetal.driver_enabled):
441 for network_name, body in found['addresses'].items():
442 for addr in body:
443 ip = addr['addr']
444 if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
445 namespace = _get_router_namespace(client,
446 network_name)
447 self._ping_ip(ip, 60, namespace)
448 else:
449 self._ping_ip(ip, 60)
450 else:
451 addr = found['addresses']['private'][0]['addr']
452 self._ping_ip(addr, 60)
453
454 def check_secgroups(self):
Joe H. Rahme02736732015-01-27 18:33:09 +0100455 """Check that the security groups still exist."""
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200456 LOG.info("Checking security groups")
457 for secgroup in self.res['secgroups']:
458 client = client_for_user(secgroup['owner'])
459 found = _get_resource_by_name(client.secgroups, 'security_groups',
460 secgroup['name'])
461 self.assertIsNotNone(
462 found,
463 "Couldn't find expected secgroup %s" % secgroup['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400464
Chris Dent878f3782014-06-30 17:04:15 +0100465 def check_telemetry(self):
466 """Check that ceilometer provides a sane sample.
467
468 Confirm that there are more than one sample and that they have the
469 expected metadata.
470
471 If in check mode confirm that the oldest sample available is from
472 before the upgrade.
473 """
Chris Dent0b76f2f2014-10-10 14:24:28 +0100474 if not self.res.get('telemetry'):
475 return
Chris Dent878f3782014-06-30 17:04:15 +0100476 LOG.info("checking telemetry")
477 for server in self.res['servers']:
478 client = client_for_user(server['owner'])
David Kranz20d06f42015-02-09 14:54:15 -0500479 body = client.telemetry.list_samples(
Chris Dent878f3782014-06-30 17:04:15 +0100480 'instance',
481 query=('metadata.display_name', 'eq', server['name'])
482 )
Chris Dent878f3782014-06-30 17:04:15 +0100483 self.assertTrue(len(body) >= 1, 'expecting at least one sample')
484 self._confirm_telemetry_sample(server, body[-1])
485
Emilien Macchi626b4f82014-06-15 21:44:29 +0200486 def check_volumes(self):
487 """Check that the volumes are still there and attached."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000488 if not self.res.get('volumes'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000489 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200490 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200491 for volume in self.res['volumes']:
492 client = client_for_user(volume['owner'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400493 vol_body = _get_volume_by_name(client, volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200494 self.assertIsNotNone(
Emilien Macchid18fec12014-09-15 14:32:54 -0400495 vol_body,
Emilien Macchi626b4f82014-06-15 21:44:29 +0200496 "Couldn't find expected volume %s" % volume['name'])
497
498 # Verify that a volume's attachment retrieved
499 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400500 attachment = client.volumes.get_attachment_from_volume(vol_body)
501 self.assertEqual(vol_body['id'], attachment['volume_id'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200502 self.assertEqual(server_id, attachment['server_id'])
503
Chris Dent878f3782014-06-30 17:04:15 +0100504 def _confirm_telemetry_sample(self, server, sample):
505 """Check this sample matches the expected resource metadata."""
506 # Confirm display_name
507 self.assertEqual(server['name'],
508 sample['resource_metadata']['display_name'])
509 # Confirm instance_type of flavor
510 flavor = sample['resource_metadata'].get(
511 'flavor.name',
512 sample['resource_metadata'].get('instance_type')
513 )
514 self.assertEqual(server['flavor'], flavor)
515 # Confirm the oldest sample was created before upgrade.
516 if OPTS.mode == 'check':
517 oldest_timestamp = timeutils.normalize_time(
518 timeutils.parse_isotime(sample['timestamp']))
519 self.assertTrue(
520 oldest_timestamp < JAVELIN_START,
521 'timestamp should come before start of second javelin run'
522 )
523
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200524 def check_networking(self):
525 """Check that the networks are still there."""
526 for res_type in ('networks', 'subnets', 'routers'):
527 for res in self.res[res_type]:
528 client = client_for_user(res['owner'])
529 found = _get_resource_by_name(client.networks, res_type,
530 res['name'])
531 self.assertIsNotNone(
532 found,
533 "Couldn't find expected resource %s" % res['name'])
534
Sean Dague655e0af2014-05-29 09:00:22 -0400535
536#######################
537#
538# OBJECTS
539#
540#######################
541
542
543def _file_contents(fname):
544 with open(fname, 'r') as f:
545 return f.read()
546
547
548def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000549 if not objects:
550 return
Sean Dague655e0af2014-05-29 09:00:22 -0400551 LOG.info("Creating objects")
552 for obj in objects:
553 LOG.debug("Object %s" % obj)
554 _assign_swift_role(obj['owner'])
555 client = client_for_user(obj['owner'])
556 client.containers.create_container(obj['container'])
557 client.objects.create_object(
558 obj['container'], obj['name'],
559 _file_contents(obj['file']))
560
Emilien Macchibb71e072014-07-05 19:18:52 +0200561
562def destroy_objects(objects):
563 for obj in objects:
564 client = client_for_user(obj['owner'])
565 r, body = client.objects.delete_object(obj['container'], obj['name'])
Emilien Macchid70f5102014-09-10 09:54:49 -0400566 if not (200 <= int(r['status']) < 299):
Emilien Macchibb71e072014-07-05 19:18:52 +0200567 raise ValueError("unable to destroy object: [%s] %s" % (r, body))
568
569
Sean Dague655e0af2014-05-29 09:00:22 -0400570#######################
571#
572# IMAGES
573#
574#######################
575
576
Sean Dague319b37a2014-07-11 07:28:11 -0400577def _resolve_image(image, imgtype):
578 name = image[imgtype]
579 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
580 return name, fname
581
582
Joe Gordon6f0426c2014-07-25 01:10:28 +0000583def _get_image_by_name(client, name):
David Kranz34f18782015-01-06 13:43:55 -0500584 body = client.images.image_list()
Joe Gordon6f0426c2014-07-25 01:10:28 +0000585 for image in body:
586 if name == image['name']:
587 return image
588 return None
589
590
Sean Dague655e0af2014-05-29 09:00:22 -0400591def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000592 if not images:
593 return
Joe Gordona18d6862014-07-24 22:55:46 +0000594 LOG.info("Creating images")
Sean Dague655e0af2014-05-29 09:00:22 -0400595 for image in images:
596 client = client_for_user(image['owner'])
597
598 # only upload a new image if the name isn't there
Joe Gordon6f0426c2014-07-25 01:10:28 +0000599 if _get_image_by_name(client, image['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000600 LOG.info("Image '%s' already exists" % image['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400601 continue
602
603 # special handling for 3 part image
604 extras = {}
605 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400606 name, fname = _resolve_image(image, 'aki')
David Kranz34f18782015-01-06 13:43:55 -0500607 aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400608 'javelin_' + name, 'aki', 'aki')
609 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400610 extras['kernel_id'] = aki.get('id')
611
Sean Dague319b37a2014-07-11 07:28:11 -0400612 name, fname = _resolve_image(image, 'ari')
David Kranz34f18782015-01-06 13:43:55 -0500613 ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400614 'javelin_' + name, 'ari', 'ari')
615 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400616 extras['ramdisk_id'] = ari.get('id')
617
Sean Dague319b37a2014-07-11 07:28:11 -0400618 _, fname = _resolve_image(image, 'file')
David Kranz34f18782015-01-06 13:43:55 -0500619 body = client.images.create_image(
Sean Dague655e0af2014-05-29 09:00:22 -0400620 image['name'], image['format'], image['format'], **extras)
621 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400622 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400623
624
Joe Gordon6f0426c2014-07-25 01:10:28 +0000625def destroy_images(images):
626 if not images:
627 return
628 LOG.info("Destroying images")
629 for image in images:
630 client = client_for_user(image['owner'])
631
632 response = _get_image_by_name(client, image['name'])
633 if not response:
634 LOG.info("Image '%s' does not exists" % image['name'])
635 continue
636 client.images.delete_image(response['id'])
637
638
Sean Dague655e0af2014-05-29 09:00:22 -0400639#######################
640#
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200641# NETWORKS
642#
643#######################
644
645def _get_router_namespace(client, network):
646 network_id = _get_resource_by_name(client.networks,
647 'networks', network)['id']
David Kranz34e88122014-12-11 15:24:05 -0500648 n_body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200649 for router in n_body['routers']:
650 router_id = router['id']
David Kranz34e88122014-12-11 15:24:05 -0500651 r_body = client.networks.list_router_interfaces(router_id)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200652 for port in r_body['ports']:
653 if port['network_id'] == network_id:
654 return "qrouter-%s" % router_id
655
656
657def _get_resource_by_name(client, resource, name):
658 get_resources = getattr(client, 'list_%s' % resource)
659 if get_resources is None:
660 raise AttributeError("client doesn't have method list_%s" % resource)
David Kranz34e88122014-12-11 15:24:05 -0500661 # Until all tempest client methods are changed to return only one value,
662 # we cannot assume they all have the same signature so we need to discard
663 # the unused response first value it two values are being returned.
664 body = get_resources()
665 if type(body) == tuple:
666 body = body[1]
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200667 if isinstance(body, dict):
668 body = body[resource]
669 for res in body:
670 if name == res['name']:
671 return res
672 raise ValueError('%s not found in %s resources' % (name, resource))
673
674
675def create_networks(networks):
676 LOG.info("Creating networks")
677 for network in networks:
678 client = client_for_user(network['owner'])
679
680 # only create a network if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500681 body = client.networks.list_networks()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200682 if any(item['name'] == network['name'] for item in body['networks']):
683 LOG.warning("Dupplicated network name: %s" % network['name'])
684 continue
685
686 client.networks.create_network(name=network['name'])
687
688
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100689def destroy_networks(networks):
690 LOG.info("Destroying subnets")
691 for network in networks:
692 client = client_for_user(network['owner'])
693 network_id = _get_resource_by_name(client.networks, 'networks',
694 network['name'])['id']
695 client.networks.delete_network(network_id)
696
697
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200698def create_subnets(subnets):
699 LOG.info("Creating subnets")
700 for subnet in subnets:
701 client = client_for_user(subnet['owner'])
702
703 network = _get_resource_by_name(client.networks, 'networks',
704 subnet['network'])
705 ip_version = netaddr.IPNetwork(subnet['range']).version
706 # ensure we don't overlap with another subnet in the network
707 try:
708 client.networks.create_subnet(network_id=network['id'],
709 cidr=subnet['range'],
710 name=subnet['name'],
711 ip_version=ip_version)
Masayuki Igawa4b29e472015-02-16 10:41:54 +0900712 except lib_exc.BadRequest as e:
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200713 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
714 if not is_overlapping_cidr:
715 raise
716
717
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100718def destroy_subnets(subnets):
719 LOG.info("Destroying subnets")
720 for subnet in subnets:
721 client = client_for_user(subnet['owner'])
722 subnet_id = _get_resource_by_name(client.networks,
723 'subnets', subnet['name'])['id']
724 client.networks.delete_subnet(subnet_id)
725
726
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200727def create_routers(routers):
728 LOG.info("Creating routers")
729 for router in routers:
730 client = client_for_user(router['owner'])
731
732 # only create a router if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500733 body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200734 if any(item['name'] == router['name'] for item in body['routers']):
735 LOG.warning("Dupplicated router name: %s" % router['name'])
736 continue
737
738 client.networks.create_router(router['name'])
739
740
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100741def destroy_routers(routers):
742 LOG.info("Destroying routers")
743 for router in routers:
744 client = client_for_user(router['owner'])
745 router_id = _get_resource_by_name(client.networks,
746 'routers', router['name'])['id']
747 for subnet in router['subnet']:
748 subnet_id = _get_resource_by_name(client.networks,
749 'subnets', subnet)['id']
750 client.networks.remove_router_interface_with_subnet_id(router_id,
751 subnet_id)
752 client.networks.delete_router(router_id)
753
754
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200755def add_router_interface(routers):
756 for router in routers:
757 client = client_for_user(router['owner'])
758 router_id = _get_resource_by_name(client.networks,
759 'routers', router['name'])['id']
760
761 for subnet in router['subnet']:
762 subnet_id = _get_resource_by_name(client.networks,
763 'subnets', subnet)['id']
764 # connect routers to their subnets
765 client.networks.add_router_interface_with_subnet_id(router_id,
766 subnet_id)
767 # connect routers to exteral network if set to "gateway"
768 if router['gateway']:
769 if CONF.network.public_network_id:
770 ext_net = CONF.network.public_network_id
771 client.networks._update_router(
772 router_id, set_enable_snat=True,
773 external_gateway_info={"network_id": ext_net})
774 else:
775 raise ValueError('public_network_id is not configured.')
776
777
778#######################
779#
Sean Dague655e0af2014-05-29 09:00:22 -0400780# SERVERS
781#
782#######################
783
784def _get_server_by_name(client, name):
785 r, body = client.servers.list_servers()
786 for server in body['servers']:
787 if name == server['name']:
788 return server
789 return None
790
791
Sean Dague655e0af2014-05-29 09:00:22 -0400792def _get_flavor_by_name(client, name):
David Kranz2fa77b22015-02-09 11:39:50 -0500793 body = client.flavors.list_flavors()
Sean Dague655e0af2014-05-29 09:00:22 -0400794 for flavor in body:
795 if name == flavor['name']:
796 return flavor
797 return None
798
799
800def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000801 if not servers:
802 return
Joe Gordona18d6862014-07-24 22:55:46 +0000803 LOG.info("Creating servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400804 for server in servers:
805 client = client_for_user(server['owner'])
806
807 if _get_server_by_name(client, server['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000808 LOG.info("Server '%s' already exists" % server['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400809 continue
810
811 image_id = _get_image_by_name(client, server['image'])['id']
812 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200813 # validate neutron is enabled and ironic disabled
814 kwargs = dict()
815 if (CONF.service_available.neutron and
816 not CONF.baremetal.driver_enabled and server.get('networks')):
817 get_net_id = lambda x: (_get_resource_by_name(
818 client.networks, 'networks', x)['id'])
819 kwargs['networks'] = [{'uuid': get_net_id(network)}
820 for network in server['networks']]
David Kranz0fb14292015-02-11 15:55:20 -0500821 body = client.servers.create_server(
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200822 server['name'], image_id, flavor_id, **kwargs)
Joe Gordon10f260b2014-07-24 23:27:19 +0000823 server_id = body['id']
824 client.servers.wait_for_server_status(server_id, 'ACTIVE')
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200825 # create to security group(s) after server spawning
826 for secgroup in server['secgroups']:
827 client.servers.add_security_group(server_id, secgroup)
Sean Dague655e0af2014-05-29 09:00:22 -0400828
829
Joe Gordondb63b1c2014-07-24 23:21:21 +0000830def destroy_servers(servers):
831 if not servers:
832 return
833 LOG.info("Destroying servers")
834 for server in servers:
835 client = client_for_user(server['owner'])
836
837 response = _get_server_by_name(client, server['name'])
838 if not response:
839 LOG.info("Server '%s' does not exist" % server['name'])
840 continue
841
842 client.servers.delete_server(response['id'])
843 client.servers.wait_for_server_termination(response['id'],
Matthew Treinish1d14c542014-06-17 20:25:40 -0400844 ignore_error=True)
Joe Gordondb63b1c2014-07-24 23:21:21 +0000845
846
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200847def create_secgroups(secgroups):
848 LOG.info("Creating security groups")
849 for secgroup in secgroups:
850 client = client_for_user(secgroup['owner'])
851
852 # only create a security group if the name isn't here
853 # i.e. a security group may be used by another server
854 # only create a router if the name isn't here
David Kranz9964b4e2015-02-06 15:45:29 -0500855 body = client.secgroups.list_security_groups()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200856 if any(item['name'] == secgroup['name'] for item in body):
857 LOG.warning("Security group '%s' already exists" %
858 secgroup['name'])
859 continue
860
David Kranz9964b4e2015-02-06 15:45:29 -0500861 body = client.secgroups.create_security_group(
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200862 secgroup['name'], secgroup['description'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200863 secgroup_id = body['id']
864 # for each security group, create the rules
865 for rule in secgroup['rules']:
866 ip_proto, from_port, to_port, cidr = rule.split()
867 client.secgroups.create_security_group_rule(
868 secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
869
870
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100871def destroy_secgroups(secgroups):
872 LOG.info("Destroying security groups")
873 for secgroup in secgroups:
874 client = client_for_user(secgroup['owner'])
875 sg_id = _get_resource_by_name(client.secgroups,
876 'security_groups',
877 secgroup['name'])
878 # sg rules are deleted automatically
879 client.secgroups.delete_security_group(sg_id['id'])
880
881
Sean Dague655e0af2014-05-29 09:00:22 -0400882#######################
883#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200884# VOLUMES
885#
886#######################
887
888def _get_volume_by_name(client, name):
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000889 body = client.volumes.list_volumes()
Emilien Macchid18fec12014-09-15 14:32:54 -0400890 for volume in body:
891 if name == volume['display_name']:
Emilien Macchi626b4f82014-06-15 21:44:29 +0200892 return volume
893 return None
894
895
896def create_volumes(volumes):
Chris Dent51e76de2014-10-01 12:07:14 +0100897 if not volumes:
898 return
899 LOG.info("Creating volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200900 for volume in volumes:
901 client = client_for_user(volume['owner'])
902
903 # only create a volume if the name isn't here
Emilien Macchid18fec12014-09-15 14:32:54 -0400904 if _get_volume_by_name(client, volume['name']):
905 LOG.info("volume '%s' already exists" % volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200906 continue
907
Emilien Macchid18fec12014-09-15 14:32:54 -0400908 size = volume['gb']
909 v_name = volume['name']
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000910 body = client.volumes.create_volume(size=size,
911 display_name=v_name)
Emilien Macchid18fec12014-09-15 14:32:54 -0400912 client.volumes.wait_for_volume_status(body['id'], 'available')
Emilien Macchi626b4f82014-06-15 21:44:29 +0200913
914
Emilien Macchibb71e072014-07-05 19:18:52 +0200915def destroy_volumes(volumes):
916 for volume in volumes:
917 client = client_for_user(volume['owner'])
918 volume_id = _get_volume_by_name(client, volume['name'])['id']
Emilien Macchi5ebc27b2014-09-15 14:30:35 -0400919 client.volumes.detach_volume(volume_id)
920 client.volumes.delete_volume(volume_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200921
922
Emilien Macchi626b4f82014-06-15 21:44:29 +0200923def attach_volumes(volumes):
924 for volume in volumes:
925 client = client_for_user(volume['owner'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200926 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400927 volume_id = _get_volume_by_name(client, volume['name'])['id']
928 device = volume['device']
929 client.volumes.attach_volume(volume_id, server_id, device)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200930
931
932#######################
933#
Sean Dague655e0af2014-05-29 09:00:22 -0400934# MAIN LOGIC
935#
936#######################
937
938def create_resources():
939 LOG.info("Creating Resources")
940 # first create keystone level resources, and we need to be admin
941 # for those.
942 create_tenants(RES['tenants'])
943 create_users(RES['users'])
944 collect_users(RES['users'])
945
946 # next create resources in a well known order
947 create_objects(RES['objects'])
948 create_images(RES['images'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200949
950 # validate neutron is enabled and ironic is disabled
951 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
952 create_networks(RES['networks'])
953 create_subnets(RES['subnets'])
954 create_routers(RES['routers'])
955 add_router_interface(RES['routers'])
956
957 create_secgroups(RES['secgroups'])
Sean Dague655e0af2014-05-29 09:00:22 -0400958 create_servers(RES['servers'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400959 create_volumes(RES['volumes'])
960 attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400961
962
Joe Gordondb63b1c2014-07-24 23:21:21 +0000963def destroy_resources():
964 LOG.info("Destroying Resources")
965 # Destroy in inverse order of create
Joe Gordondb63b1c2014-07-24 23:21:21 +0000966 destroy_servers(RES['servers'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000967 destroy_images(RES['images'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200968 destroy_objects(RES['objects'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200969 destroy_volumes(RES['volumes'])
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100970 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
971 destroy_routers(RES['routers'])
972 destroy_subnets(RES['subnets'])
973 destroy_networks(RES['networks'])
974 destroy_secgroups(RES['secgroups'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200975 destroy_users(RES['users'])
976 destroy_tenants(RES['tenants'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000977 LOG.warn("Destroy mode incomplete")
978
Joe Gordondb63b1c2014-07-24 23:21:21 +0000979
Sean Dague655e0af2014-05-29 09:00:22 -0400980def get_options():
981 global OPTS
982 parser = argparse.ArgumentParser(
983 description='Create and validate a fixed set of OpenStack resources')
984 parser.add_argument('-m', '--mode',
985 metavar='<create|check|destroy>',
986 required=True,
987 help=('One of (create, check, destroy)'))
988 parser.add_argument('-r', '--resources',
989 required=True,
990 metavar='resourcefile.yaml',
991 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000992
Sean Dague319b37a2014-07-11 07:28:11 -0400993 parser.add_argument(
994 '-d', '--devstack-base',
995 required=True,
996 metavar='/opt/stack/old',
997 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000998 parser.add_argument(
999 '-c', '--config-file',
1000 metavar='/etc/tempest.conf',
1001 help='path to javelin2(tempest) config file')
1002
Sean Dague655e0af2014-05-29 09:00:22 -04001003 # auth bits, letting us also just source the devstack openrc
1004 parser.add_argument('--os-username',
1005 metavar='<auth-user-name>',
1006 default=os.environ.get('OS_USERNAME'),
1007 help=('Defaults to env[OS_USERNAME].'))
1008 parser.add_argument('--os-password',
1009 metavar='<auth-password>',
1010 default=os.environ.get('OS_PASSWORD'),
1011 help=('Defaults to env[OS_PASSWORD].'))
1012 parser.add_argument('--os-tenant-name',
1013 metavar='<auth-tenant-name>',
1014 default=os.environ.get('OS_TENANT_NAME'),
1015 help=('Defaults to env[OS_TENANT_NAME].'))
1016
1017 OPTS = parser.parse_args()
1018 if OPTS.mode not in ('create', 'check', 'destroy'):
1019 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
1020 parser.print_help()
1021 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +00001022 if OPTS.config_file:
1023 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -04001024
1025
Joe Gordon915eb8e2014-07-17 11:25:46 +02001026def setup_logging():
Sean Dague655e0af2014-05-29 09:00:22 -04001027 global LOG
Joe Gordon915eb8e2014-07-17 11:25:46 +02001028 logging.setup(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001029 LOG = logging.getLogger(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001030
1031
1032def main():
1033 global RES
1034 get_options()
1035 setup_logging()
Chris Dent51e76de2014-10-01 12:07:14 +01001036 RES.update(load_resources(OPTS.resources))
Sean Dague655e0af2014-05-29 09:00:22 -04001037
1038 if OPTS.mode == 'create':
1039 create_resources()
Joe Gordon1a097002014-07-24 23:44:08 +00001040 # Make sure the resources we just created actually work
1041 checker = JavelinCheck(USERS, RES)
1042 checker.check()
Sean Dague655e0af2014-05-29 09:00:22 -04001043 elif OPTS.mode == 'check':
1044 collect_users(RES['users'])
1045 checker = JavelinCheck(USERS, RES)
1046 checker.check()
1047 elif OPTS.mode == 'destroy':
Joe Gordondb63b1c2014-07-24 23:21:21 +00001048 collect_users(RES['users'])
1049 destroy_resources()
Sean Dague655e0af2014-05-29 09:00:22 -04001050 else:
1051 LOG.error('Unknown mode %s' % OPTS.mode)
1052 return 1
Joe Gordon246353a2014-07-18 00:10:28 +02001053 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -04001054 return 0
1055
1056if __name__ == "__main__":
1057 sys.exit(main())