5
from charmhelpers.contrib.openstack.amulet.deployment import (
6
OpenStackAmuletDeployment
9
from charmhelpers.contrib.openstack.amulet.utils import (
15
# Use DEBUG to turn on debug logging
16
u = OpenStackAmuletUtils(ERROR)
19
class NovaBasicDeployment(OpenStackAmuletDeployment):
20
"""Amulet tests on a basic nova compute deployment."""
22
def __init__(self, series=None, openstack=None, source=None):
23
"""Deploy the entire test environment."""
24
super(NovaBasicDeployment, self).__init__(series, openstack, source)
27
self._configure_services()
29
self._initialize_tests()
31
def _add_services(self):
32
"""Add the service that we're testing, including the number of units,
33
where nova-compute is local, and the other charms are from
35
this_service = ('nova-compute', 1)
36
other_services = [('mysql', 1), ('rabbitmq-server', 1),
37
('nova-cloud-controller', 1), ('keystone', 1),
39
super(NovaBasicDeployment, self)._add_services(this_service,
42
def _add_relations(self):
43
"""Add all of the relations for the services."""
45
'nova-compute:image-service': 'glance:image-service',
46
'nova-compute:shared-db': 'mysql:shared-db',
47
'nova-compute:amqp': 'rabbitmq-server:amqp',
48
'nova-cloud-controller:shared-db': 'mysql:shared-db',
49
'nova-cloud-controller:identity-service': 'keystone:identity-service',
50
'nova-cloud-controller:amqp': 'rabbitmq-server:amqp',
51
'nova-cloud-controller:cloud-compute': 'nova-compute:cloud-compute',
52
'nova-cloud-controller:image-service': 'glance:image-service',
53
'keystone:shared-db': 'mysql:shared-db',
54
'glance:identity-service': 'keystone:identity-service',
55
'glance:shared-db': 'mysql:shared-db',
56
'glance:amqp': 'rabbitmq-server:amqp'
58
super(NovaBasicDeployment, self)._add_relations(relations)
60
def _configure_services(self):
61
"""Configure all of the services."""
62
nova_config = {'config-flags': 'auto_assign_floating_ip=False',
63
'enable-live-migration': 'False'}
64
keystone_config = {'admin-password': 'openstack',
65
'admin-token': 'ubuntutesting'}
66
configs = {'nova-compute': nova_config, 'keystone': keystone_config}
67
super(NovaBasicDeployment, self)._configure_services(configs)
69
def _initialize_tests(self):
70
"""Perform final initialization before tests get run."""
71
# Access the sentries for inspecting service units
72
self.mysql_sentry = self.d.sentry.unit['mysql/0']
73
self.keystone_sentry = self.d.sentry.unit['keystone/0']
74
self.rabbitmq_sentry = self.d.sentry.unit['rabbitmq-server/0']
75
self.nova_compute_sentry = self.d.sentry.unit['nova-compute/0']
76
self.nova_cc_sentry = self.d.sentry.unit['nova-cloud-controller/0']
77
self.glance_sentry = self.d.sentry.unit['glance/0']
79
# Authenticate admin with keystone
80
self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
85
# Authenticate admin with glance endpoint
86
self.glance = u.authenticate_glance_admin(self.keystone)
88
# Create a demo tenant/role/user
89
self.demo_tenant = 'demoTenant'
90
self.demo_role = 'demoRole'
91
self.demo_user = 'demoUser'
92
if not u.tenant_exists(self.keystone, self.demo_tenant):
93
tenant = self.keystone.tenants.create(tenant_name=self.demo_tenant,
94
description='demo tenant',
96
self.keystone.roles.create(name=self.demo_role)
97
self.keystone.users.create(name=self.demo_user,
100
email='demo@demo.com')
102
# Authenticate demo user with keystone
103
self.keystone_demo = \
104
u.authenticate_keystone_user(self.keystone, user=self.demo_user,
106
tenant=self.demo_tenant)
108
# Authenticate demo user with nova-api
109
self.nova_demo = u.authenticate_nova_user(self.keystone,
112
tenant=self.demo_tenant)
114
def test_services(self):
115
"""Verify the expected services are running on the corresponding
118
self.mysql_sentry: ['status mysql'],
119
self.rabbitmq_sentry: ['sudo service rabbitmq-server status'],
120
self.nova_compute_sentry: ['status nova-compute',
121
'status nova-network',
123
self.nova_cc_sentry: ['status nova-api-ec2',
124
'status nova-api-os-compute',
125
'status nova-objectstore',
127
'status nova-scheduler'],
128
self.keystone_sentry: ['status keystone'],
129
self.glance_sentry: ['status glance-registry', 'status glance-api']
131
if self._get_openstack_release() >= self.precise_grizzly:
132
commands[self.nova_cc_sentry] = ['status nova-conductor']
134
ret = u.validate_services(commands)
136
amulet.raise_status(amulet.FAIL, msg=ret)
138
def test_service_catalog(self):
139
"""Verify that the service catalog endpoint data is valid."""
140
endpoint_vol = {'adminURL': u.valid_url,
141
'region': 'RegionOne',
142
'publicURL': u.valid_url,
143
'internalURL': u.valid_url}
144
endpoint_id = {'adminURL': u.valid_url,
145
'region': 'RegionOne',
146
'publicURL': u.valid_url,
147
'internalURL': u.valid_url}
148
if self._get_openstack_release() >= self.precise_folsom:
149
endpoint_vol['id'] = u.not_null
150
endpoint_id['id'] = u.not_null
151
expected = {'s3': [endpoint_vol], 'compute': [endpoint_vol],
152
'ec2': [endpoint_vol], 'identity': [endpoint_id]}
153
actual = self.keystone_demo.service_catalog.get_endpoints()
155
ret = u.validate_svc_catalog_endpoint_data(expected, actual)
157
amulet.raise_status(amulet.FAIL, msg=ret)
159
def test_openstack_compute_api_endpoint(self):
160
"""Verify the openstack compute api (osapi) endpoint data."""
161
endpoints = self.keystone.endpoints.list()
162
admin_port = internal_port = public_port = '8774'
163
expected = {'id': u.not_null,
164
'region': 'RegionOne',
165
'adminurl': u.valid_url,
166
'internalurl': u.valid_url,
167
'publicurl': u.valid_url,
168
'service_id': u.not_null}
170
ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
171
public_port, expected)
173
message = 'osapi endpoint: {}'.format(ret)
174
amulet.raise_status(amulet.FAIL, msg=message)
176
def test_ec2_api_endpoint(self):
177
"""Verify the EC2 api endpoint data."""
178
endpoints = self.keystone.endpoints.list()
179
admin_port = internal_port = public_port = '8773'
180
expected = {'id': u.not_null,
181
'region': 'RegionOne',
182
'adminurl': u.valid_url,
183
'internalurl': u.valid_url,
184
'publicurl': u.valid_url,
185
'service_id': u.not_null}
187
ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
188
public_port, expected)
190
message = 'EC2 endpoint: {}'.format(ret)
191
amulet.raise_status(amulet.FAIL, msg=message)
193
def test_s3_api_endpoint(self):
194
"""Verify the S3 api endpoint data."""
195
endpoints = self.keystone.endpoints.list()
196
admin_port = internal_port = public_port = '3333'
197
expected = {'id': u.not_null,
198
'region': 'RegionOne',
199
'adminurl': u.valid_url,
200
'internalurl': u.valid_url,
201
'publicurl': u.valid_url,
202
'service_id': u.not_null}
204
ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
205
public_port, expected)
207
message = 'S3 endpoint: {}'.format(ret)
208
amulet.raise_status(amulet.FAIL, msg=message)
210
def test_nova_shared_db_relation(self):
211
"""Verify the nova-compute to mysql shared-db relation data"""
212
unit = self.nova_compute_sentry
213
relation = ['shared-db', 'mysql:shared-db']
215
'private-address': u.valid_ip,
216
'nova_database': 'nova',
217
'nova_username': 'nova',
218
'nova_hostname': u.valid_ip
221
ret = u.validate_relation_data(unit, relation, expected)
223
message = u.relation_error('nova-compute shared-db', ret)
224
amulet.raise_status(amulet.FAIL, msg=message)
226
def test_mysql_shared_db_relation(self):
227
"""Verify the mysql to nova-compute shared-db relation data"""
228
unit = self.mysql_sentry
229
relation = ['shared-db', 'nova-compute:shared-db']
231
'private-address': u.valid_ip,
232
'nova_password': u.not_null,
233
'db_host': u.valid_ip
236
ret = u.validate_relation_data(unit, relation, expected)
238
message = u.relation_error('mysql shared-db', ret)
239
amulet.raise_status(amulet.FAIL, msg=message)
241
def test_nova_amqp_relation(self):
242
"""Verify the nova-compute to rabbitmq-server amqp relation data"""
243
unit = self.nova_compute_sentry
244
relation = ['amqp', 'rabbitmq-server:amqp']
247
'private-address': u.valid_ip,
251
ret = u.validate_relation_data(unit, relation, expected)
253
message = u.relation_error('nova-compute amqp', ret)
254
amulet.raise_status(amulet.FAIL, msg=message)
256
def test_rabbitmq_amqp_relation(self):
257
"""Verify the rabbitmq-server to nova-compute amqp relation data"""
258
unit = self.rabbitmq_sentry
259
relation = ['amqp', 'nova-compute:amqp']
261
'private-address': u.valid_ip,
262
'password': u.not_null,
263
'hostname': u.valid_ip
266
ret = u.validate_relation_data(unit, relation, expected)
268
message = u.relation_error('rabbitmq amqp', ret)
269
amulet.raise_status(amulet.FAIL, msg=message)
271
def test_nova_cloud_compute_relation(self):
272
"""Verify the nova-compute to nova-cc cloud-compute relation data"""
273
unit = self.nova_compute_sentry
274
relation = ['cloud-compute', 'nova-cloud-controller:cloud-compute']
276
'private-address': u.valid_ip,
279
ret = u.validate_relation_data(unit, relation, expected)
281
message = u.relation_error('nova-compute cloud-compute', ret)
282
amulet.raise_status(amulet.FAIL, msg=message)
284
def test_nova_cc_cloud_compute_relation(self):
285
"""Verify the nova-cc to nova-compute cloud-compute relation data"""
286
unit = self.nova_cc_sentry
287
relation = ['cloud-compute', 'nova-compute:cloud-compute']
289
'volume_service': 'cinder',
290
'network_manager': 'flatdhcpmanager',
291
'ec2_host': u.valid_ip,
292
'private-address': u.valid_ip,
293
'restart_trigger': u.not_null
295
if self._get_openstack_release() == self.precise_essex:
296
expected['volume_service'] = 'nova-volume'
298
ret = u.validate_relation_data(unit, relation, expected)
300
message = u.relation_error('nova-cc cloud-compute', ret)
301
amulet.raise_status(amulet.FAIL, msg=message)
303
def test_restart_on_config_change(self):
304
"""Verify that the specified services are restarted when the config
306
# NOTE(coreycb): Skipping failing test on essex until resolved.
307
# config-flags don't take effect on essex.
308
if self._get_openstack_release() == self.precise_essex:
309
u.log.error("Skipping failing test until resolved")
312
services = ['nova-compute', 'nova-api', 'nova-network']
313
self.d.configure('nova-compute', {'config-flags': 'verbose=False'})
317
if not u.service_restarted(self.nova_compute_sentry, s,
318
'/etc/nova/nova.conf', sleep_time=time):
319
msg = "service {} didn't restart after config change".format(s)
320
amulet.raise_status(amulet.FAIL, msg=msg)
323
self.d.configure('nova-compute', {'config-flags': 'verbose=True'})
325
def test_nova_config(self):
326
"""Verify the data in the nova config file."""
327
# NOTE(coreycb): Currently no way to test on essex because config file
328
# has no section headers.
329
if self._get_openstack_release() == self.precise_essex:
332
unit = self.nova_compute_sentry
333
conf = '/etc/nova/nova.conf'
334
rabbitmq_relation = self.rabbitmq_sentry.relation('amqp',
336
glance_relation = self.glance_sentry.relation('image-service',
337
'nova-compute:image-service')
338
mysql_relation = self.mysql_sentry.relation('shared-db',
339
'nova-compute:shared-db')
340
db_uri = "mysql://{}:{}@{}/{}".format('nova',
341
mysql_relation['nova_password'],
342
mysql_relation['db_host'],
345
expected = {'dhcpbridge_flagfile': '/etc/nova/nova.conf',
346
'dhcpbridge': '/usr/bin/nova-dhcpbridge',
347
'logdir': '/var/log/nova',
348
'state_path': '/var/lib/nova',
349
'lock_path': '/var/lock/nova',
350
'force_dhcp_release': 'True',
351
'libvirt_use_virtio_for_bridges': 'True',
353
'use_syslog': 'False',
354
'ec2_private_dns_show_ip': 'True',
355
'api_paste_config': '/etc/nova/api-paste.ini',
356
'enabled_apis': 'ec2,osapi_compute,metadata',
357
'auth_strategy': 'keystone',
358
'compute_driver': 'libvirt.LibvirtDriver',
359
'sql_connection': db_uri,
360
'rabbit_userid': 'nova',
361
'rabbit_virtual_host': 'openstack',
362
'rabbit_password': rabbitmq_relation['password'],
363
'rabbit_host': rabbitmq_relation['hostname'],
364
'glance_api_servers': glance_relation['glance-api-server'],
365
'flat_interface': 'eth1',
366
'network_manager': 'nova.network.manager.FlatDHCPManager',
367
'volume_api_class': 'nova.volume.cinder.API',
370
ret = u.validate_config_data(unit, conf, 'DEFAULT', expected)
372
message = "nova config error: {}".format(ret)
373
amulet.raise_status(amulet.FAIL, msg=message)
375
def test_image_instance_create(self):
376
"""Create an image/instance, verify they exist, and delete them."""
377
# NOTE(coreycb): Skipping failing test on essex until resolved. essex
378
# nova API calls are getting "Malformed request url (HTTP
380
if self._get_openstack_release() == self.precise_essex:
381
u.log.error("Skipping failing test until resolved")
384
image = u.create_cirros_image(self.glance, "cirros-image")
386
amulet.raise_status(amulet.FAIL, msg="Image create failed")
388
instance = u.create_instance(self.nova_demo, "cirros-image", "cirros",
391
amulet.raise_status(amulet.FAIL, msg="Instance create failed")
394
for instance in self.nova_demo.servers.list():
395
if instance.name == 'cirros':
397
if instance.status != 'ACTIVE':
398
msg = "cirros instance is not active"
399
amulet.raise_status(amulet.FAIL, msg=message)
402
message = "nova cirros instance does not exist"
403
amulet.raise_status(amulet.FAIL, msg=message)
405
u.delete_image(self.glance, image)
406
u.delete_instance(self.nova_demo, instance)