blob: 3a61dd1c43d6b5f60a90dd9beaf56c829f96cbd3 [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
Sean Dague655e0af2014-05-29 09:00:22 -0400236###################
237#
238# TENANTS
239#
240###################
241
242
243def create_tenants(tenants):
244 """Create tenants from resource definition.
245
246 Don't create the tenants if they already exist.
247 """
248 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500249 body = admin.identity.list_tenants()
Sean Dague655e0af2014-05-29 09:00:22 -0400250 existing = [x['name'] for x in body]
251 for tenant in tenants:
252 if tenant not in existing:
253 admin.identity.create_tenant(tenant)
254 else:
255 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
256
Emilien Macchibb71e072014-07-05 19:18:52 +0200257
258def destroy_tenants(tenants):
259 admin = keystone_admin()
260 for tenant in tenants:
261 tenant_id = admin.identity.get_tenant_by_name(tenant)['id']
David Kranzb7afa922014-12-30 10:56:26 -0500262 admin.identity.delete_tenant(tenant_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200263
Sean Dague655e0af2014-05-29 09:00:22 -0400264##############
265#
266# USERS
267#
268##############
269
270
271def _users_for_tenant(users, tenant):
272 u_for_t = []
273 for user in users:
274 for n in user:
275 if user[n]['tenant'] == tenant:
276 u_for_t.append(user[n])
277 return u_for_t
278
279
280def _tenants_from_users(users):
281 tenants = set()
282 for user in users:
283 for n in user:
284 tenants.add(user[n]['tenant'])
285 return tenants
286
287
288def _assign_swift_role(user):
289 admin = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500290 roles = admin.identity.list_roles()
Sean Dague655e0af2014-05-29 09:00:22 -0400291 role = next(r for r in roles if r['name'] == 'Member')
292 LOG.debug(USERS[user])
293 try:
294 admin.identity.assign_user_role(
295 USERS[user]['tenant_id'],
296 USERS[user]['id'],
297 role['id'])
Masayuki Igawad9388762015-01-20 14:56:42 +0900298 except lib_exc.Conflict:
Sean Dague655e0af2014-05-29 09:00:22 -0400299 # don't care if it's already assigned
300 pass
301
302
303def create_users(users):
304 """Create tenants from resource definition.
305
306 Don't create the tenants if they already exist.
307 """
308 global USERS
309 LOG.info("Creating users")
310 admin = keystone_admin()
311 for u in users:
312 try:
313 tenant = admin.identity.get_tenant_by_name(u['tenant'])
Masayuki Igawabfa07602015-01-20 18:47:17 +0900314 except lib_exc.NotFound:
Sean Dague655e0af2014-05-29 09:00:22 -0400315 LOG.error("Tenant: %s - not found" % u['tenant'])
316 continue
317 try:
318 admin.identity.get_user_by_username(tenant['id'], u['name'])
319 LOG.warn("User '%s' already exists in this environment"
320 % u['name'])
Masayuki Igawabfa07602015-01-20 18:47:17 +0900321 except lib_exc.NotFound:
Sean Dague655e0af2014-05-29 09:00:22 -0400322 admin.identity.create_user(
323 u['name'], u['pass'], tenant['id'],
324 "%s@%s" % (u['name'], tenant['id']),
325 enabled=True)
326
327
Emilien Macchibb71e072014-07-05 19:18:52 +0200328def destroy_users(users):
329 admin = keystone_admin()
330 for user in users:
Emilien Macchi436de862014-09-30 17:09:50 -0400331 tenant_id = admin.identity.get_tenant_by_name(user['tenant'])['id']
332 user_id = admin.identity.get_user_by_username(tenant_id,
333 user['name'])['id']
David Kranzb7afa922014-12-30 10:56:26 -0500334 admin.identity.delete_user(user_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200335
336
Sean Dague655e0af2014-05-29 09:00:22 -0400337def collect_users(users):
338 global USERS
Joe Gordon618c9fb2014-07-16 15:40:01 +0200339 LOG.info("Collecting users")
Sean Dague655e0af2014-05-29 09:00:22 -0400340 admin = keystone_admin()
341 for u in users:
342 tenant = admin.identity.get_tenant_by_name(u['tenant'])
343 u['tenant_id'] = tenant['id']
344 USERS[u['name']] = u
345 body = admin.identity.get_user_by_username(tenant['id'], u['name'])
346 USERS[u['name']]['id'] = body['id']
347
348
349class JavelinCheck(unittest.TestCase):
350 def __init__(self, users, resources):
351 super(JavelinCheck, self).__init__()
352 self.users = users
353 self.res = resources
354
355 def runTest(self, *args):
356 pass
357
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200358 def _ping_ip(self, ip_addr, count, namespace=None):
359 if namespace is None:
360 ping_cmd = "ping -c1 " + ip_addr
361 else:
362 ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
363 ip_addr)
364 for current in range(count):
365 return_code = os.system(ping_cmd)
366 if return_code is 0:
367 break
368 self.assertNotEqual(current, count - 1,
369 "Server is not pingable at %s" % ip_addr)
370
Sean Dague655e0af2014-05-29 09:00:22 -0400371 def check(self):
372 self.check_users()
373 self.check_objects()
374 self.check_servers()
Emilien Macchid18fec12014-09-15 14:32:54 -0400375 self.check_volumes()
Chris Dent878f3782014-06-30 17:04:15 +0100376 self.check_telemetry()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200377 self.check_secgroups()
378
379 # validate neutron is enabled and ironic disabled:
380 # Tenant network isolation is not supported when using ironic.
381 # "admin" has set up a neutron flat network environment within a shared
382 # fixed network for all tenants to use.
383 # In this case, network/subnet/router creation can be skipped and the
384 # server booted the same as nova network.
385 if (CONF.service_available.neutron and
386 not CONF.baremetal.driver_enabled):
387 self.check_networking()
Sean Dague655e0af2014-05-29 09:00:22 -0400388
389 def check_users(self):
390 """Check that the users we expect to exist, do.
391
392 We don't use the resource list for this because we need to validate
393 that things like tenantId didn't drift across versions.
394 """
Joe Gordon618c9fb2014-07-16 15:40:01 +0200395 LOG.info("checking users")
Sean Dague655e0af2014-05-29 09:00:22 -0400396 for name, user in self.users.iteritems():
397 client = keystone_admin()
David Kranzb7afa922014-12-30 10:56:26 -0500398 found = client.identity.get_user(user['id'])
Sean Dague655e0af2014-05-29 09:00:22 -0400399 self.assertEqual(found['name'], user['name'])
400 self.assertEqual(found['tenantId'], user['tenant_id'])
401
402 # also ensure we can auth with that user, and do something
403 # on the cloud. We don't care about the results except that it
404 # remains authorized.
405 client = client_for_user(user['name'])
David Kranzae99b9a2015-02-16 13:37:01 -0500406 client.servers.list_servers()
Sean Dague655e0af2014-05-29 09:00:22 -0400407
408 def check_objects(self):
409 """Check that the objects created are still there."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000410 if not self.res.get('objects'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000411 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200412 LOG.info("checking objects")
Sean Dague655e0af2014-05-29 09:00:22 -0400413 for obj in self.res['objects']:
414 client = client_for_user(obj['owner'])
415 r, contents = client.objects.get_object(
416 obj['container'], obj['name'])
417 source = _file_contents(obj['file'])
418 self.assertEqual(contents, source)
419
420 def check_servers(self):
421 """Check that the servers are still up and running."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000422 if not self.res.get('servers'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000423 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200424 LOG.info("checking servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400425 for server in self.res['servers']:
426 client = client_for_user(server['owner'])
427 found = _get_server_by_name(client, server['name'])
428 self.assertIsNotNone(
429 found,
430 "Couldn't find expected server %s" % server['name'])
431
David Kranz0fb14292015-02-11 15:55:20 -0500432 found = client.servers.get_server(found['id'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200433 # validate neutron is enabled and ironic disabled:
434 if (CONF.service_available.neutron and
435 not CONF.baremetal.driver_enabled):
436 for network_name, body in found['addresses'].items():
437 for addr in body:
438 ip = addr['addr']
439 if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
440 namespace = _get_router_namespace(client,
441 network_name)
442 self._ping_ip(ip, 60, namespace)
443 else:
444 self._ping_ip(ip, 60)
445 else:
446 addr = found['addresses']['private'][0]['addr']
447 self._ping_ip(addr, 60)
448
449 def check_secgroups(self):
Joe H. Rahme02736732015-01-27 18:33:09 +0100450 """Check that the security groups still exist."""
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200451 LOG.info("Checking security groups")
452 for secgroup in self.res['secgroups']:
453 client = client_for_user(secgroup['owner'])
454 found = _get_resource_by_name(client.secgroups, 'security_groups',
455 secgroup['name'])
456 self.assertIsNotNone(
457 found,
458 "Couldn't find expected secgroup %s" % secgroup['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400459
Chris Dent878f3782014-06-30 17:04:15 +0100460 def check_telemetry(self):
461 """Check that ceilometer provides a sane sample.
462
463 Confirm that there are more than one sample and that they have the
464 expected metadata.
465
466 If in check mode confirm that the oldest sample available is from
467 before the upgrade.
468 """
Chris Dent0b76f2f2014-10-10 14:24:28 +0100469 if not self.res.get('telemetry'):
470 return
Chris Dent878f3782014-06-30 17:04:15 +0100471 LOG.info("checking telemetry")
472 for server in self.res['servers']:
473 client = client_for_user(server['owner'])
David Kranz20d06f42015-02-09 14:54:15 -0500474 body = client.telemetry.list_samples(
Chris Dent878f3782014-06-30 17:04:15 +0100475 'instance',
476 query=('metadata.display_name', 'eq', server['name'])
477 )
Chris Dent878f3782014-06-30 17:04:15 +0100478 self.assertTrue(len(body) >= 1, 'expecting at least one sample')
479 self._confirm_telemetry_sample(server, body[-1])
480
Emilien Macchi626b4f82014-06-15 21:44:29 +0200481 def check_volumes(self):
482 """Check that the volumes are still there and attached."""
Joe Gordon22cd6de2014-07-25 00:16:17 +0000483 if not self.res.get('volumes'):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000484 return
Joe Gordon618c9fb2014-07-16 15:40:01 +0200485 LOG.info("checking volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200486 for volume in self.res['volumes']:
487 client = client_for_user(volume['owner'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400488 vol_body = _get_volume_by_name(client, volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200489 self.assertIsNotNone(
Emilien Macchid18fec12014-09-15 14:32:54 -0400490 vol_body,
Emilien Macchi626b4f82014-06-15 21:44:29 +0200491 "Couldn't find expected volume %s" % volume['name'])
492
493 # Verify that a volume's attachment retrieved
494 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400495 attachment = client.volumes.get_attachment_from_volume(vol_body)
496 self.assertEqual(vol_body['id'], attachment['volume_id'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200497 self.assertEqual(server_id, attachment['server_id'])
498
Chris Dent878f3782014-06-30 17:04:15 +0100499 def _confirm_telemetry_sample(self, server, sample):
500 """Check this sample matches the expected resource metadata."""
501 # Confirm display_name
502 self.assertEqual(server['name'],
503 sample['resource_metadata']['display_name'])
504 # Confirm instance_type of flavor
505 flavor = sample['resource_metadata'].get(
506 'flavor.name',
507 sample['resource_metadata'].get('instance_type')
508 )
509 self.assertEqual(server['flavor'], flavor)
510 # Confirm the oldest sample was created before upgrade.
511 if OPTS.mode == 'check':
512 oldest_timestamp = timeutils.normalize_time(
513 timeutils.parse_isotime(sample['timestamp']))
514 self.assertTrue(
515 oldest_timestamp < JAVELIN_START,
516 'timestamp should come before start of second javelin run'
517 )
518
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200519 def check_networking(self):
520 """Check that the networks are still there."""
521 for res_type in ('networks', 'subnets', 'routers'):
522 for res in self.res[res_type]:
523 client = client_for_user(res['owner'])
524 found = _get_resource_by_name(client.networks, res_type,
525 res['name'])
526 self.assertIsNotNone(
527 found,
528 "Couldn't find expected resource %s" % res['name'])
529
Sean Dague655e0af2014-05-29 09:00:22 -0400530
531#######################
532#
533# OBJECTS
534#
535#######################
536
537
538def _file_contents(fname):
539 with open(fname, 'r') as f:
540 return f.read()
541
542
543def create_objects(objects):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000544 if not objects:
545 return
Sean Dague655e0af2014-05-29 09:00:22 -0400546 LOG.info("Creating objects")
547 for obj in objects:
548 LOG.debug("Object %s" % obj)
549 _assign_swift_role(obj['owner'])
550 client = client_for_user(obj['owner'])
551 client.containers.create_container(obj['container'])
552 client.objects.create_object(
553 obj['container'], obj['name'],
554 _file_contents(obj['file']))
555
Emilien Macchibb71e072014-07-05 19:18:52 +0200556
557def destroy_objects(objects):
558 for obj in objects:
559 client = client_for_user(obj['owner'])
560 r, body = client.objects.delete_object(obj['container'], obj['name'])
Emilien Macchid70f5102014-09-10 09:54:49 -0400561 if not (200 <= int(r['status']) < 299):
Emilien Macchibb71e072014-07-05 19:18:52 +0200562 raise ValueError("unable to destroy object: [%s] %s" % (r, body))
563
564
Sean Dague655e0af2014-05-29 09:00:22 -0400565#######################
566#
567# IMAGES
568#
569#######################
570
571
Sean Dague319b37a2014-07-11 07:28:11 -0400572def _resolve_image(image, imgtype):
573 name = image[imgtype]
574 fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
575 return name, fname
576
577
Joe Gordon6f0426c2014-07-25 01:10:28 +0000578def _get_image_by_name(client, name):
David Kranz34f18782015-01-06 13:43:55 -0500579 body = client.images.image_list()
Joe Gordon6f0426c2014-07-25 01:10:28 +0000580 for image in body:
581 if name == image['name']:
582 return image
583 return None
584
585
Sean Dague655e0af2014-05-29 09:00:22 -0400586def create_images(images):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000587 if not images:
588 return
Joe Gordona18d6862014-07-24 22:55:46 +0000589 LOG.info("Creating images")
Sean Dague655e0af2014-05-29 09:00:22 -0400590 for image in images:
591 client = client_for_user(image['owner'])
592
593 # only upload a new image if the name isn't there
Joe Gordon6f0426c2014-07-25 01:10:28 +0000594 if _get_image_by_name(client, image['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000595 LOG.info("Image '%s' already exists" % image['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400596 continue
597
598 # special handling for 3 part image
599 extras = {}
600 if image['format'] == 'ami':
Sean Dague319b37a2014-07-11 07:28:11 -0400601 name, fname = _resolve_image(image, 'aki')
David Kranz34f18782015-01-06 13:43:55 -0500602 aki = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400603 'javelin_' + name, 'aki', 'aki')
604 client.images.store_image(aki.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400605 extras['kernel_id'] = aki.get('id')
606
Sean Dague319b37a2014-07-11 07:28:11 -0400607 name, fname = _resolve_image(image, 'ari')
David Kranz34f18782015-01-06 13:43:55 -0500608 ari = client.images.create_image(
Sean Dague319b37a2014-07-11 07:28:11 -0400609 'javelin_' + name, 'ari', 'ari')
610 client.images.store_image(ari.get('id'), open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400611 extras['ramdisk_id'] = ari.get('id')
612
Sean Dague319b37a2014-07-11 07:28:11 -0400613 _, fname = _resolve_image(image, 'file')
David Kranz34f18782015-01-06 13:43:55 -0500614 body = client.images.create_image(
Sean Dague655e0af2014-05-29 09:00:22 -0400615 image['name'], image['format'], image['format'], **extras)
616 image_id = body.get('id')
Sean Dague319b37a2014-07-11 07:28:11 -0400617 client.images.store_image(image_id, open(fname, 'r'))
Sean Dague655e0af2014-05-29 09:00:22 -0400618
619
Joe Gordon6f0426c2014-07-25 01:10:28 +0000620def destroy_images(images):
621 if not images:
622 return
623 LOG.info("Destroying images")
624 for image in images:
625 client = client_for_user(image['owner'])
626
627 response = _get_image_by_name(client, image['name'])
628 if not response:
629 LOG.info("Image '%s' does not exists" % image['name'])
630 continue
631 client.images.delete_image(response['id'])
632
633
Sean Dague655e0af2014-05-29 09:00:22 -0400634#######################
635#
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200636# NETWORKS
637#
638#######################
639
640def _get_router_namespace(client, network):
641 network_id = _get_resource_by_name(client.networks,
642 'networks', network)['id']
David Kranz34e88122014-12-11 15:24:05 -0500643 n_body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200644 for router in n_body['routers']:
645 router_id = router['id']
David Kranz34e88122014-12-11 15:24:05 -0500646 r_body = client.networks.list_router_interfaces(router_id)
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200647 for port in r_body['ports']:
648 if port['network_id'] == network_id:
649 return "qrouter-%s" % router_id
650
651
652def _get_resource_by_name(client, resource, name):
653 get_resources = getattr(client, 'list_%s' % resource)
654 if get_resources is None:
655 raise AttributeError("client doesn't have method list_%s" % resource)
David Kranz34e88122014-12-11 15:24:05 -0500656 # Until all tempest client methods are changed to return only one value,
657 # we cannot assume they all have the same signature so we need to discard
658 # the unused response first value it two values are being returned.
659 body = get_resources()
660 if type(body) == tuple:
661 body = body[1]
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200662 if isinstance(body, dict):
663 body = body[resource]
664 for res in body:
665 if name == res['name']:
666 return res
667 raise ValueError('%s not found in %s resources' % (name, resource))
668
669
670def create_networks(networks):
671 LOG.info("Creating networks")
672 for network in networks:
673 client = client_for_user(network['owner'])
674
675 # only create a network if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500676 body = client.networks.list_networks()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200677 if any(item['name'] == network['name'] for item in body['networks']):
678 LOG.warning("Dupplicated network name: %s" % network['name'])
679 continue
680
681 client.networks.create_network(name=network['name'])
682
683
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100684def destroy_networks(networks):
685 LOG.info("Destroying subnets")
686 for network in networks:
687 client = client_for_user(network['owner'])
688 network_id = _get_resource_by_name(client.networks, 'networks',
689 network['name'])['id']
690 client.networks.delete_network(network_id)
691
692
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200693def create_subnets(subnets):
694 LOG.info("Creating subnets")
695 for subnet in subnets:
696 client = client_for_user(subnet['owner'])
697
698 network = _get_resource_by_name(client.networks, 'networks',
699 subnet['network'])
700 ip_version = netaddr.IPNetwork(subnet['range']).version
701 # ensure we don't overlap with another subnet in the network
702 try:
703 client.networks.create_subnet(network_id=network['id'],
704 cidr=subnet['range'],
705 name=subnet['name'],
706 ip_version=ip_version)
707 except exceptions.BadRequest as e:
708 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
709 if not is_overlapping_cidr:
710 raise
711
712
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100713def destroy_subnets(subnets):
714 LOG.info("Destroying subnets")
715 for subnet in subnets:
716 client = client_for_user(subnet['owner'])
717 subnet_id = _get_resource_by_name(client.networks,
718 'subnets', subnet['name'])['id']
719 client.networks.delete_subnet(subnet_id)
720
721
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200722def create_routers(routers):
723 LOG.info("Creating routers")
724 for router in routers:
725 client = client_for_user(router['owner'])
726
727 # only create a router if the name isn't here
David Kranz34e88122014-12-11 15:24:05 -0500728 body = client.networks.list_routers()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200729 if any(item['name'] == router['name'] for item in body['routers']):
730 LOG.warning("Dupplicated router name: %s" % router['name'])
731 continue
732
733 client.networks.create_router(router['name'])
734
735
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100736def destroy_routers(routers):
737 LOG.info("Destroying routers")
738 for router in routers:
739 client = client_for_user(router['owner'])
740 router_id = _get_resource_by_name(client.networks,
741 'routers', router['name'])['id']
742 for subnet in router['subnet']:
743 subnet_id = _get_resource_by_name(client.networks,
744 'subnets', subnet)['id']
745 client.networks.remove_router_interface_with_subnet_id(router_id,
746 subnet_id)
747 client.networks.delete_router(router_id)
748
749
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200750def add_router_interface(routers):
751 for router in routers:
752 client = client_for_user(router['owner'])
753 router_id = _get_resource_by_name(client.networks,
754 'routers', router['name'])['id']
755
756 for subnet in router['subnet']:
757 subnet_id = _get_resource_by_name(client.networks,
758 'subnets', subnet)['id']
759 # connect routers to their subnets
760 client.networks.add_router_interface_with_subnet_id(router_id,
761 subnet_id)
762 # connect routers to exteral network if set to "gateway"
763 if router['gateway']:
764 if CONF.network.public_network_id:
765 ext_net = CONF.network.public_network_id
766 client.networks._update_router(
767 router_id, set_enable_snat=True,
768 external_gateway_info={"network_id": ext_net})
769 else:
770 raise ValueError('public_network_id is not configured.')
771
772
773#######################
774#
Sean Dague655e0af2014-05-29 09:00:22 -0400775# SERVERS
776#
777#######################
778
779def _get_server_by_name(client, name):
David Kranzae99b9a2015-02-16 13:37:01 -0500780 body = client.servers.list_servers()
Sean Dague655e0af2014-05-29 09:00:22 -0400781 for server in body['servers']:
782 if name == server['name']:
783 return server
784 return None
785
786
Sean Dague655e0af2014-05-29 09:00:22 -0400787def _get_flavor_by_name(client, name):
David Kranz2fa77b22015-02-09 11:39:50 -0500788 body = client.flavors.list_flavors()
Sean Dague655e0af2014-05-29 09:00:22 -0400789 for flavor in body:
790 if name == flavor['name']:
791 return flavor
792 return None
793
794
795def create_servers(servers):
Joe Gordonb9bcdd82014-07-17 15:44:57 +0000796 if not servers:
797 return
Joe Gordona18d6862014-07-24 22:55:46 +0000798 LOG.info("Creating servers")
Sean Dague655e0af2014-05-29 09:00:22 -0400799 for server in servers:
800 client = client_for_user(server['owner'])
801
802 if _get_server_by_name(client, server['name']):
Joe Gordona18d6862014-07-24 22:55:46 +0000803 LOG.info("Server '%s' already exists" % server['name'])
Sean Dague655e0af2014-05-29 09:00:22 -0400804 continue
805
806 image_id = _get_image_by_name(client, server['image'])['id']
807 flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200808 # validate neutron is enabled and ironic disabled
809 kwargs = dict()
810 if (CONF.service_available.neutron and
811 not CONF.baremetal.driver_enabled and server.get('networks')):
812 get_net_id = lambda x: (_get_resource_by_name(
813 client.networks, 'networks', x)['id'])
814 kwargs['networks'] = [{'uuid': get_net_id(network)}
815 for network in server['networks']]
David Kranz0fb14292015-02-11 15:55:20 -0500816 body = client.servers.create_server(
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200817 server['name'], image_id, flavor_id, **kwargs)
Joe Gordon10f260b2014-07-24 23:27:19 +0000818 server_id = body['id']
819 client.servers.wait_for_server_status(server_id, 'ACTIVE')
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200820 # create to security group(s) after server spawning
821 for secgroup in server['secgroups']:
822 client.servers.add_security_group(server_id, secgroup)
Sean Dague655e0af2014-05-29 09:00:22 -0400823
824
Joe Gordondb63b1c2014-07-24 23:21:21 +0000825def destroy_servers(servers):
826 if not servers:
827 return
828 LOG.info("Destroying servers")
829 for server in servers:
830 client = client_for_user(server['owner'])
831
832 response = _get_server_by_name(client, server['name'])
833 if not response:
834 LOG.info("Server '%s' does not exist" % server['name'])
835 continue
836
837 client.servers.delete_server(response['id'])
838 client.servers.wait_for_server_termination(response['id'],
Matthew Treinish1d14c542014-06-17 20:25:40 -0400839 ignore_error=True)
Joe Gordondb63b1c2014-07-24 23:21:21 +0000840
841
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200842def create_secgroups(secgroups):
843 LOG.info("Creating security groups")
844 for secgroup in secgroups:
845 client = client_for_user(secgroup['owner'])
846
847 # only create a security group if the name isn't here
848 # i.e. a security group may be used by another server
849 # only create a router if the name isn't here
David Kranz9964b4e2015-02-06 15:45:29 -0500850 body = client.secgroups.list_security_groups()
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200851 if any(item['name'] == secgroup['name'] for item in body):
852 LOG.warning("Security group '%s' already exists" %
853 secgroup['name'])
854 continue
855
David Kranz9964b4e2015-02-06 15:45:29 -0500856 body = client.secgroups.create_security_group(
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200857 secgroup['name'], secgroup['description'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200858 secgroup_id = body['id']
859 # for each security group, create the rules
860 for rule in secgroup['rules']:
861 ip_proto, from_port, to_port, cidr = rule.split()
862 client.secgroups.create_security_group_rule(
863 secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
864
865
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100866def destroy_secgroups(secgroups):
867 LOG.info("Destroying security groups")
868 for secgroup in secgroups:
869 client = client_for_user(secgroup['owner'])
870 sg_id = _get_resource_by_name(client.secgroups,
871 'security_groups',
872 secgroup['name'])
873 # sg rules are deleted automatically
874 client.secgroups.delete_security_group(sg_id['id'])
875
876
Sean Dague655e0af2014-05-29 09:00:22 -0400877#######################
878#
Emilien Macchi626b4f82014-06-15 21:44:29 +0200879# VOLUMES
880#
881#######################
882
883def _get_volume_by_name(client, name):
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000884 body = client.volumes.list_volumes()
Emilien Macchid18fec12014-09-15 14:32:54 -0400885 for volume in body:
886 if name == volume['display_name']:
Emilien Macchi626b4f82014-06-15 21:44:29 +0200887 return volume
888 return None
889
890
891def create_volumes(volumes):
Chris Dent51e76de2014-10-01 12:07:14 +0100892 if not volumes:
893 return
894 LOG.info("Creating volumes")
Emilien Macchi626b4f82014-06-15 21:44:29 +0200895 for volume in volumes:
896 client = client_for_user(volume['owner'])
897
898 # only create a volume if the name isn't here
Emilien Macchid18fec12014-09-15 14:32:54 -0400899 if _get_volume_by_name(client, volume['name']):
900 LOG.info("volume '%s' already exists" % volume['name'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200901 continue
902
Emilien Macchid18fec12014-09-15 14:32:54 -0400903 size = volume['gb']
904 v_name = volume['name']
Joseph Lanoux6809bab2014-12-18 14:57:18 +0000905 body = client.volumes.create_volume(size=size,
906 display_name=v_name)
Emilien Macchid18fec12014-09-15 14:32:54 -0400907 client.volumes.wait_for_volume_status(body['id'], 'available')
Emilien Macchi626b4f82014-06-15 21:44:29 +0200908
909
Emilien Macchibb71e072014-07-05 19:18:52 +0200910def destroy_volumes(volumes):
911 for volume in volumes:
912 client = client_for_user(volume['owner'])
913 volume_id = _get_volume_by_name(client, volume['name'])['id']
Emilien Macchi5ebc27b2014-09-15 14:30:35 -0400914 client.volumes.detach_volume(volume_id)
915 client.volumes.delete_volume(volume_id)
Emilien Macchibb71e072014-07-05 19:18:52 +0200916
917
Emilien Macchi626b4f82014-06-15 21:44:29 +0200918def attach_volumes(volumes):
919 for volume in volumes:
920 client = client_for_user(volume['owner'])
Emilien Macchi626b4f82014-06-15 21:44:29 +0200921 server_id = _get_server_by_name(client, volume['server'])['id']
Emilien Macchid18fec12014-09-15 14:32:54 -0400922 volume_id = _get_volume_by_name(client, volume['name'])['id']
923 device = volume['device']
924 client.volumes.attach_volume(volume_id, server_id, device)
Emilien Macchi626b4f82014-06-15 21:44:29 +0200925
926
927#######################
928#
Sean Dague655e0af2014-05-29 09:00:22 -0400929# MAIN LOGIC
930#
931#######################
932
933def create_resources():
934 LOG.info("Creating Resources")
935 # first create keystone level resources, and we need to be admin
936 # for those.
937 create_tenants(RES['tenants'])
938 create_users(RES['users'])
939 collect_users(RES['users'])
940
941 # next create resources in a well known order
942 create_objects(RES['objects'])
943 create_images(RES['images'])
Emilien Macchi7a2348b2014-06-16 07:32:11 +0200944
945 # validate neutron is enabled and ironic is disabled
946 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
947 create_networks(RES['networks'])
948 create_subnets(RES['subnets'])
949 create_routers(RES['routers'])
950 add_router_interface(RES['routers'])
951
952 create_secgroups(RES['secgroups'])
Sean Dague655e0af2014-05-29 09:00:22 -0400953 create_servers(RES['servers'])
Emilien Macchid18fec12014-09-15 14:32:54 -0400954 create_volumes(RES['volumes'])
955 attach_volumes(RES['volumes'])
Sean Dague655e0af2014-05-29 09:00:22 -0400956
957
Joe Gordondb63b1c2014-07-24 23:21:21 +0000958def destroy_resources():
959 LOG.info("Destroying Resources")
960 # Destroy in inverse order of create
Joe Gordondb63b1c2014-07-24 23:21:21 +0000961 destroy_servers(RES['servers'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000962 destroy_images(RES['images'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200963 destroy_objects(RES['objects'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200964 destroy_volumes(RES['volumes'])
Jakub Libosvar3791ac92014-11-11 13:23:44 +0100965 if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
966 destroy_routers(RES['routers'])
967 destroy_subnets(RES['subnets'])
968 destroy_networks(RES['networks'])
969 destroy_secgroups(RES['secgroups'])
Emilien Macchibb71e072014-07-05 19:18:52 +0200970 destroy_users(RES['users'])
971 destroy_tenants(RES['tenants'])
Joe Gordon6f0426c2014-07-25 01:10:28 +0000972 LOG.warn("Destroy mode incomplete")
973
Joe Gordondb63b1c2014-07-24 23:21:21 +0000974
Sean Dague655e0af2014-05-29 09:00:22 -0400975def get_options():
976 global OPTS
977 parser = argparse.ArgumentParser(
978 description='Create and validate a fixed set of OpenStack resources')
979 parser.add_argument('-m', '--mode',
980 metavar='<create|check|destroy>',
981 required=True,
982 help=('One of (create, check, destroy)'))
983 parser.add_argument('-r', '--resources',
984 required=True,
985 metavar='resourcefile.yaml',
986 help='Resources definition yaml file')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000987
Sean Dague319b37a2014-07-11 07:28:11 -0400988 parser.add_argument(
989 '-d', '--devstack-base',
990 required=True,
991 metavar='/opt/stack/old',
992 help='Devstack base directory for retrieving artifacts')
Joe Gordon28a84ae2014-07-17 15:38:28 +0000993 parser.add_argument(
994 '-c', '--config-file',
995 metavar='/etc/tempest.conf',
996 help='path to javelin2(tempest) config file')
997
Sean Dague655e0af2014-05-29 09:00:22 -0400998 # auth bits, letting us also just source the devstack openrc
999 parser.add_argument('--os-username',
1000 metavar='<auth-user-name>',
1001 default=os.environ.get('OS_USERNAME'),
1002 help=('Defaults to env[OS_USERNAME].'))
1003 parser.add_argument('--os-password',
1004 metavar='<auth-password>',
1005 default=os.environ.get('OS_PASSWORD'),
1006 help=('Defaults to env[OS_PASSWORD].'))
1007 parser.add_argument('--os-tenant-name',
1008 metavar='<auth-tenant-name>',
1009 default=os.environ.get('OS_TENANT_NAME'),
1010 help=('Defaults to env[OS_TENANT_NAME].'))
1011
1012 OPTS = parser.parse_args()
1013 if OPTS.mode not in ('create', 'check', 'destroy'):
1014 print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
1015 parser.print_help()
1016 sys.exit(1)
Joe Gordon28a84ae2014-07-17 15:38:28 +00001017 if OPTS.config_file:
1018 config.CONF.set_config_path(OPTS.config_file)
Sean Dague655e0af2014-05-29 09:00:22 -04001019
1020
Joe Gordon915eb8e2014-07-17 11:25:46 +02001021def setup_logging():
Sean Dague655e0af2014-05-29 09:00:22 -04001022 global LOG
Joe Gordon915eb8e2014-07-17 11:25:46 +02001023 logging.setup(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001024 LOG = logging.getLogger(__name__)
Sean Dague655e0af2014-05-29 09:00:22 -04001025
1026
1027def main():
1028 global RES
1029 get_options()
1030 setup_logging()
Chris Dent51e76de2014-10-01 12:07:14 +01001031 RES.update(load_resources(OPTS.resources))
Sean Dague655e0af2014-05-29 09:00:22 -04001032
1033 if OPTS.mode == 'create':
1034 create_resources()
Joe Gordon1a097002014-07-24 23:44:08 +00001035 # Make sure the resources we just created actually work
1036 checker = JavelinCheck(USERS, RES)
1037 checker.check()
Sean Dague655e0af2014-05-29 09:00:22 -04001038 elif OPTS.mode == 'check':
1039 collect_users(RES['users'])
1040 checker = JavelinCheck(USERS, RES)
1041 checker.check()
1042 elif OPTS.mode == 'destroy':
Joe Gordondb63b1c2014-07-24 23:21:21 +00001043 collect_users(RES['users'])
1044 destroy_resources()
Sean Dague655e0af2014-05-29 09:00:22 -04001045 else:
1046 LOG.error('Unknown mode %s' % OPTS.mode)
1047 return 1
Joe Gordon246353a2014-07-18 00:10:28 +02001048 LOG.info('javelin2 successfully finished')
Sean Dague655e0af2014-05-29 09:00:22 -04001049 return 0
1050
1051if __name__ == "__main__":
1052 sys.exit(main())