1
# Basic functional black-box test
2
# See tests/README before modifying or adding new tests.
7
from charmhelpers.contrib.openstack.amulet.deployment import (
8
OpenStackAmuletDeployment
12
# LXDAmuletUtils inherits and extends OpenStackAmuletUtils, with
13
# the intention of ultimately moving the relevant helpers into
14
# OpenStackAmuletUtils.
16
# from charmhelpers.contrib.openstack.amulet.utils import (
17
# OpenStackAmuletUtils,
18
from lxd_amulet_utils import (
24
# u = OpenStackAmuletUtils(DEBUG)
25
u = LXDAmuletUtils(DEBUG)
27
LXD_IMAGE_URL = 'http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-root.tar.xz' # noqa
28
LXD_IMAGE_NAME = 'trusty-server-cloudimg-amd64-root.tar.xz'
31
class LXDBasicDeployment(OpenStackAmuletDeployment):
32
"""Amulet tests on a basic nova compute deployment."""
34
def __init__(self, series=None, openstack=None, source=None,
36
"""Deploy the test environment."""
37
super(LXDBasicDeployment, self).__init__(series, openstack,
41
self._configure_services()
44
u.log.info('Waiting on extended status checks...')
45
exclude_services = ['mysql']
46
self._auto_wait_for_status(exclude_services=exclude_services)
48
self._initialize_tests()
50
def _add_services(self):
53
Add the services that we're testing, where lxd is local,
54
and the rest of the service are from lp branches that are
55
compatible with the local charm (e.g. stable or next).
57
this_service = {'name': 'lxd'}
59
other_services = [{'name': 'mysql'},
60
{'name': 'nova-compute', 'units': 2},
61
{'name': 'rabbitmq-server'},
62
{'name': 'nova-cloud-controller'},
66
super(LXDBasicDeployment, self)._add_services(this_service,
69
def _add_relations(self):
70
"""Add all of the relations for the services."""
72
'lxd:lxd': 'nova-compute:lxd',
73
'nova-compute:image-service': 'glance:image-service',
74
'nova-compute:shared-db': 'mysql:shared-db',
75
'nova-compute:amqp': 'rabbitmq-server:amqp',
76
'nova-cloud-controller:shared-db': 'mysql:shared-db',
77
'nova-cloud-controller:identity-service': 'keystone:'
79
'nova-cloud-controller:amqp': 'rabbitmq-server:amqp',
80
'nova-cloud-controller:cloud-compute': 'nova-compute:'
82
'nova-cloud-controller:image-service': 'glance:image-service',
83
'keystone:shared-db': 'mysql:shared-db',
84
'glance:identity-service': 'keystone:identity-service',
85
'glance:shared-db': 'mysql:shared-db',
86
'glance:amqp': 'rabbitmq-server:amqp'
88
super(LXDBasicDeployment, self)._add_relations(relations)
90
def _configure_services(self):
91
"""Configure all of the services."""
93
'ram-allocation-ratio': '5.0'
97
'block-device': '/dev/vdb',
98
'ephemeral-unmount': '/mnt',
103
'config-flags': 'auto_assign_floating_ip=False',
104
'enable-live-migration': True,
105
'enable-resize': True,
106
'migration-auth-type': 'ssh',
111
'admin-password': 'openstack',
112
'admin-token': 'ubuntutesting'
116
'nova-compute': nova_config,
118
'keystone': keystone_config,
119
'nova-cloud-controller': nova_cc_config
122
super(LXDBasicDeployment, self)._configure_services(configs)
124
def _initialize_tests(self):
125
"""Perform final initialization before tests get run."""
127
# Access the sentries for inspecting service units
128
self.lxd0_sentry = self.d.sentry['lxd'][0]
129
self.lxd1_sentry = self.d.sentry['lxd'][1]
130
self.compute0_sentry = self.d.sentry['nova-compute'][0]
131
self.compute1_sentry = self.d.sentry['nova-compute'][1]
133
self.mysql_sentry = self.d.sentry['mysql'][0]
134
self.keystone_sentry = self.d.sentry['keystone'][0]
135
self.rabbitmq_sentry = self.d.sentry['rabbitmq-server'][0]
136
self.nova_cc_sentry = self.d.sentry['nova-cloud-controller'][0]
137
self.glance_sentry = self.d.sentry['glance'][0]
139
u.log.debug('openstack release val: {}'.format(
140
self._get_openstack_release()))
141
u.log.debug('openstack release str: {}'.format(
142
self._get_openstack_release_string()))
144
# Authenticate admin with keystone
145
self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
147
password='openstack',
150
# Authenticate admin with glance endpoint
151
self.glance = u.authenticate_glance_admin(self.keystone)
153
# Create a demo tenant/role/user
154
self.demo_tenant = 'demoTenant'
155
self.demo_role = 'demoRole'
156
self.demo_user = 'demoUser'
157
if not u.tenant_exists(self.keystone, self.demo_tenant):
158
tenant = self.keystone.tenants.create(tenant_name=self.demo_tenant,
159
description='demo tenant',
161
self.keystone.roles.create(name=self.demo_role)
162
self.keystone.users.create(name=self.demo_user,
165
email='demo@demo.com')
167
# Authenticate demo user with keystone
168
self.keystone_demo = \
169
u.authenticate_keystone_user(self.keystone, user=self.demo_user,
171
tenant=self.demo_tenant)
173
# Authenticate demo user with nova-api
174
self.nova_demo = u.authenticate_nova_user(self.keystone,
177
tenant=self.demo_tenant)
179
def test_100_services(self):
180
"""Verify the expected services are running on the corresponding
182
u.log.debug('Checking system services on units...')
185
self.lxd0_sentry: ['lxd'],
186
self.lxd1_sentry: ['lxd'],
187
self.compute0_sentry: ['nova-compute',
190
self.compute1_sentry: ['nova-compute',
193
self.mysql_sentry: ['mysql'],
194
self.rabbitmq_sentry: ['rabbitmq-server'],
195
self.nova_cc_sentry: ['nova-api-os-compute',
199
self.keystone_sentry: ['keystone'],
200
self.glance_sentry: ['glance-registry',
204
ret = u.validate_services_by_name(services)
206
amulet.raise_status(amulet.FAIL, msg=ret)
210
def test_102_service_catalog(self):
211
"""Verify that the service catalog endpoint data is valid."""
212
u.log.debug('Checking keystone service catalog...')
214
endpoint_vol = {'adminURL': u.valid_url,
215
'region': 'RegionOne',
216
'publicURL': u.valid_url,
217
'internalURL': u.valid_url}
219
endpoint_id = {'adminURL': u.valid_url,
220
'region': 'RegionOne',
221
'publicURL': u.valid_url,
222
'internalURL': u.valid_url}
224
if self._get_openstack_release() >= self.precise_folsom:
225
endpoint_vol['id'] = u.not_null
226
endpoint_id['id'] = u.not_null
229
'compute': [endpoint_vol],
230
'identity': [endpoint_id]
233
if self._get_openstack_release() < self.trusty_kilo:
235
's3': [endpoint_vol],
236
'ec2': [endpoint_vol]
239
actual = self.keystone_demo.service_catalog.get_endpoints()
241
ret = u.validate_svc_catalog_endpoint_data(expected, actual)
243
amulet.raise_status(amulet.FAIL, msg=ret)
247
def test_104_openstack_compute_api_endpoint(self):
248
"""Verify the openstack compute api (osapi) endpoint data."""
249
u.log.debug('Checking compute endpoint data...')
251
endpoints = self.keystone.endpoints.list()
252
admin_port = internal_port = public_port = '8774'
255
'region': 'RegionOne',
256
'adminurl': u.valid_url,
257
'internalurl': u.valid_url,
258
'publicurl': u.valid_url,
259
'service_id': u.not_null
262
ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
263
public_port, expected)
265
message = 'osapi endpoint: {}'.format(ret)
266
amulet.raise_status(amulet.FAIL, msg=message)
270
# TODO: Add bi-directional lxd service relation introspection
272
def test_200_nova_compute_shared_db_relation(self):
273
"""Verify the nova-compute to mysql shared-db relation data"""
274
u.log.debug('Checking n-c:mysql db relation data...')
276
unit = self.compute0_sentry
277
relation = ['shared-db', 'mysql:shared-db']
279
'private-address': u.valid_ip,
280
'nova_database': 'nova',
281
'nova_username': 'nova',
282
'nova_hostname': u.valid_ip
285
ret = u.validate_relation_data(unit, relation, expected)
287
message = u.relation_error('nova-compute shared-db', ret)
288
amulet.raise_status(amulet.FAIL, msg=message)
292
def test_202_mysql_nova_compute_shared_db_relation(self):
293
"""Verify the mysql to nova-compute shared-db relation data"""
294
u.log.debug('Checking mysql:n-c db relation data...')
295
unit = self.mysql_sentry
296
relation = ['shared-db', 'nova-compute:shared-db']
298
'private-address': u.valid_ip,
299
'nova_password': u.not_null,
300
'db_host': u.valid_ip
303
ret = u.validate_relation_data(unit, relation, expected)
305
message = u.relation_error('mysql shared-db', ret)
306
amulet.raise_status(amulet.FAIL, msg=message)
310
def test_204_nova_compute_amqp_relation(self):
311
"""Verify the nova-compute to rabbitmq-server amqp relation data"""
312
u.log.debug('Checking n-c:rmq amqp relation data...')
313
unit = self.compute0_sentry
314
relation = ['amqp', 'rabbitmq-server:amqp']
317
'private-address': u.valid_ip,
321
ret = u.validate_relation_data(unit, relation, expected)
323
message = u.relation_error('nova-compute amqp', ret)
324
amulet.raise_status(amulet.FAIL, msg=message)
328
def test_206_rabbitmq_nova_compute_amqp_relation(self):
329
"""Verify the rabbitmq-server to nova-compute amqp relation data"""
330
u.log.debug('Checking rmq:n-c amqp relation data...')
331
unit = self.rabbitmq_sentry
332
relation = ['amqp', 'nova-compute:amqp']
334
'private-address': u.valid_ip,
335
'password': u.not_null,
336
'hostname': u.valid_ip
339
ret = u.validate_relation_data(unit, relation, expected)
341
message = u.relation_error('rabbitmq amqp', ret)
342
amulet.raise_status(amulet.FAIL, msg=message)
346
def test_208_nova_compute_cloud_compute_relation(self):
347
"""Verify the nova-compute to nova-cc cloud-compute relation data"""
348
u.log.debug('Checking n-c:n-c-c cloud-compute relation data...')
349
unit = self.compute0_sentry
350
relation = ['cloud-compute', 'nova-cloud-controller:cloud-compute']
352
'private-address': u.valid_ip,
355
ret = u.validate_relation_data(unit, relation, expected)
357
message = u.relation_error('nova-compute cloud-compute', ret)
358
amulet.raise_status(amulet.FAIL, msg=message)
362
def test_300_nova_compute_config(self):
363
"""Verify the data in the nova-compute config file."""
364
u.log.debug('Checking nova-compute config file data...')
365
units = [self.compute0_sentry, self.compute1_sentry]
366
conf = '/etc/nova/nova-compute.conf'
370
'compute_driver': 'nclxd.nova.virt.lxd.LXDDriver'
375
for section, pairs in expected.iteritems():
376
ret = u.validate_config_data(unit, conf, section, pairs)
378
message = "nova config error: {}".format(ret)
379
amulet.raise_status(amulet.FAIL, msg=message)
383
def test_302_nova_compute_nova_config(self):
384
"""Verify the data in the nova config file."""
385
u.log.debug('Checking nova config file data...')
386
units = [self.compute0_sentry, self.compute1_sentry]
387
conf = '/etc/nova/nova.conf'
388
rmq_nc_rel = self.rabbitmq_sentry.relation('amqp',
390
gl_nc_rel = self.glance_sentry.relation('image-service',
391
'nova-compute:image-service')
392
db_nc_rel = self.mysql_sentry.relation('shared-db',
393
'nova-compute:shared-db')
394
db_uri = "mysql://{}:{}@{}/{}".format('nova',
395
db_nc_rel['nova_password'],
396
db_nc_rel['db_host'],
400
'dhcpbridge_flagfile': '/etc/nova/nova.conf',
401
'dhcpbridge': '/usr/bin/nova-dhcpbridge',
402
'logdir': '/var/log/nova',
403
'state_path': '/var/lib/nova',
404
'force_dhcp_release': 'True',
406
'use_syslog': 'False',
407
'ec2_private_dns_show_ip': 'True',
408
'api_paste_config': '/etc/nova/api-paste.ini',
409
'enabled_apis': 'ec2,osapi_compute,metadata',
410
'auth_strategy': 'keystone',
411
'flat_interface': 'eth1',
412
'network_manager': 'nova.network.manager.FlatDHCPManager',
413
'volume_api_class': 'nova.volume.cinder.API',
415
'oslo_concurrency': {
416
'lock_path': '/var/lock/nova'
421
'oslo_messaging_rabbit': {
422
'rabbit_userid': 'nova',
423
'rabbit_virtual_host': 'openstack',
424
'rabbit_password': rmq_nc_rel['password'],
425
'rabbit_host': rmq_nc_rel['hostname'],
428
'api_servers': gl_nc_rel['glance-api-server']
433
for section, pairs in expected.iteritems():
434
ret = u.validate_config_data(unit, conf, section, pairs)
436
message = "nova config error: {}".format(ret)
437
amulet.raise_status(amulet.FAIL, msg=message)
441
def test_400_check_logical_volume_groups(self):
442
"""Inspect and validate vgs on all lxd units."""
443
u.log.debug('Checking logical volume groups on lxd units...')
445
u.log.warn('Skipping test due to http://pastebin.ubuntu.com/15111453/')
446
# Disabled, pending resolution of lxd lvm issue, where LXDPool
447
# and logical volumes don't always get created on all units.
448
# http://pastebin.ubuntu.com/15111453/
452
expected = ['lxd_vg']
455
for sentry_unit in self.d.sentry['lxd']:
456
host = sentry_unit.info['public-address']
457
unit_name = sentry_unit.info['unit_name']
459
output, _ = u.run_cmd_unit(sentry_unit, cmd)
460
for expected_content in expected:
461
if expected_content not in output:
462
invalid.append('{} {} vgs does not contain '
463
'{}'.format(unit_name, host,
467
u.log.error('Logical volume group check failed.')
468
amulet.raise_status(amulet.FAIL, msg='; '.join(invalid))
472
def test_401_check_logical_volumes(self):
473
"""Inspect and validate lvs on all lxd units."""
474
u.log.debug('Checking logical volumes on lxd units...')
476
u.log.warn('Skipping test due to http://pastebin.ubuntu.com/15111453/')
477
# Disabled, pending resolution of lxd lvm issue, where LXDPool
478
# and logical volumes don't always get created on all units.
479
# http://pastebin.ubuntu.com/15111453/
483
expected = ['LXDPool']
486
for sentry_unit in self.d.sentry['lxd']:
487
host = sentry_unit.info['public-address']
488
unit_name = sentry_unit.info['unit_name']
490
output, _ = u.run_cmd_unit(sentry_unit, cmd)
491
for expected_content in expected:
492
if expected_content not in output:
493
invalid.append('{} {} lvs does not contain '
494
'{}'.format(unit_name, host,
498
u.log.error('Logical volume check failed.')
499
amulet.raise_status(amulet.FAIL, msg='; '.join(invalid))
503
def test_402_lxc_config_validate(self):
504
"""Inspect and validate lxc running config on all lxd units."""
505
u.log.debug('Checking lxc config on lxd units...')
507
u.log.warn('Skipping test due to http://pastebin.ubuntu.com/15111453/')
508
# Disabled, pending resolution of lxd lvm issue, where LXDPool
509
# and logical volumes don't always get created on all units.
510
# http://pastebin.ubuntu.com/15111453/
513
cmd = 'sudo lxc config show'
515
'core.https_address: \'[::]\'',
516
'core.trust_password: true',
517
'images.remote_cache_expiry: "10"',
518
'storage.lvm_thinpool_name: LXDPool',
519
'storage.lvm_vg_name: lxd_vg',
523
for sentry_unit in self.d.sentry['lxd']:
524
host = sentry_unit.info['public-address']
525
unit_name = sentry_unit.info['unit_name']
527
output, _ = u.run_cmd_unit(sentry_unit, cmd)
528
for expected_content in expected:
529
if expected_content not in output:
530
invalid.append('{} {} lxc config does not contain '
531
'{}'.format(unit_name, host,
535
u.log.error('lxc config check failed')
536
amulet.raise_status(amulet.FAIL, msg='; '.join(invalid))
540
def test_410_image_instance_create(self):
541
"""Create an image/instance, verify they exist, and delete them."""
542
u.log.debug('Create glance image, nova LXD instance...')
545
# TODO: Nova keypair create
548
image = u.glance_create_image(self.glance,
551
disk_format='root-tar',
552
hypervisor_type='lxc')
554
amulet.raise_status(amulet.FAIL, msg='Image create failed')
556
# Create nova instance
557
instance_name = 'lxd-instance-{}'.format(time.time())
558
instance = u.create_instance(self.nova_demo, LXD_IMAGE_NAME,
559
instance_name, 'm1.tiny')
561
amulet.raise_status(amulet.FAIL, msg='Nova instance create failed')
564
for instance in self.nova_demo.servers.list():
565
if instance.name == instance_name:
567
# TODO: Get instance IP address
568
if instance.status != 'ACTIVE':
569
msg = 'Nova instance is not active'
570
amulet.raise_status(amulet.FAIL, msg=msg)
573
message = 'Nova instance does not exist'
574
amulet.raise_status(amulet.FAIL, msg=message)
576
# TODO: Confirm nova instance: TCP port knock
578
# This will require additional environment configuration
579
# and post-deployment operation such as network creation
580
# before it can be tested. The instance has no IP address.
585
# connected = u.port_knock_tcp(host, port, timeout)
587
# u.log.debug('Socket connect OK: {}:{}'.format(host, port))
589
# msg = 'Socket connect failed: {}:{}'.format(host, port)
590
# amulet.raise_status(amulet.FAIL, msg)
592
# TODO: ICMP instance ping
593
# TODO: SSH instance login
596
u.delete_resource(self.glance.images, image.id,
599
u.delete_resource(self.nova_demo.servers, instance.id,
601
# TODO: Delete nova keypair
605
def test_900_compute_restart_on_config_change(self):
606
"""Verify that the specified services are restarted when the config
608
u.log.debug('Checking service restart on charm config '
611
sentry = self.compute0_sentry
612
juju_service = 'nova-compute'
614
# Expected default and alternate values
615
set_default = {'verbose': 'False'}
616
set_alternate = {'verbose': 'True'}
618
# Services which are expected to restart upon config change,
619
# and corresponding config files affected by the change
620
conf_file = '/etc/nova/nova.conf'
622
'nova-compute': conf_file,
623
'nova-api': conf_file,
624
'nova-network': conf_file
627
# Make config change, check for service restarts
628
u.log.debug('Making config change on {}...'.format(juju_service))
629
mtime = u.get_sentry_time(sentry)
630
self.d.configure(juju_service, set_alternate)
633
for s, conf_file in services.iteritems():
634
u.log.debug("Checking that service restarted: {}".format(s))
635
if not u.validate_service_config_changed(sentry, mtime, s,
637
sleep_time=sleep_time):
639
self.d.configure(juju_service, set_default)
640
msg = "service {} didn't restart after config change".format(s)
641
amulet.raise_status(amulet.FAIL, msg=msg)
644
self.d.configure(juju_service, set_default)