Add Amulet basic tests

This commit is contained in:
Corey Bryant 2014-07-11 16:41:12 +00:00
parent 74a648cfaa
commit a73af3824a
10 changed files with 585 additions and 3 deletions

View File

@ -3,13 +3,20 @@ PYTHON := /usr/bin/env python
lint: lint:
@flake8 --exclude hooks/charmhelpers hooks @flake8 --exclude hooks/charmhelpers hooks
@flake8 --exclude hooks/charmhelpers unit_tests @flake8 --exclude hooks/charmhelpers unit_tests tests
@charm proof @charm proof
test: unit_test:
@echo Starting tests... @echo Starting unit tests...
@$(PYTHON) /usr/bin/nosetests -v --nologcapture --with-coverage unit_tests @$(PYTHON) /usr/bin/nosetests -v --nologcapture --with-coverage unit_tests
test:
@echo Starting Amulet tests...
# coreycb note: The -v should only be temporary until Amulet sends
# raise_status() messages to stderr:
# https://bugs.launchpad.net/amulet/+bug/1320357
@juju test -v -p AMULET_HTTP_PROXY
sync: sync:
@charm-helper-sync -c charm-helpers-hooks.yaml @charm-helper-sync -c charm-helpers-hooks.yaml
@charm-helper-sync -c charm-helpers-tests.yaml @charm-helper-sync -c charm-helpers-tests.yaml

11
tests/00-setup Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
set -ex
sudo add-apt-repository --yes ppa:juju/stable
sudo apt-get update --yes
sudo apt-get install --yes python-amulet
sudo apt-get install --yes python-swiftclient
sudo apt-get install --yes python-glanceclient
sudo apt-get install --yes python-keystoneclient
sudo apt-get install --yes python-novaclient

9
tests/10-basic-precise-essex Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/python
"""Amulet tests on a basic swift-storage deployment on precise-essex."""
from basic_deployment import SwiftStorageBasicDeployment
if __name__ == '__main__':
deployment = SwiftStorageBasicDeployment(series='precise')
deployment.run_tests()

11
tests/11-basic-precise-folsom Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/python
"""Amulet tests on a basic swift-storage deployment on precise-folsom."""
from basic_deployment import SwiftStorageBasicDeployment
if __name__ == '__main__':
deployment = SwiftStorageBasicDeployment(series='precise',
openstack='cloud:precise-folsom',
source='cloud:precise-updates/folsom')
deployment.run_tests()

11
tests/12-basic-precise-grizzly Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/python
"""Amulet tests on a basic swift-storage deployment on precise-grizzly."""
from basic_deployment import SwiftStorageBasicDeployment
if __name__ == '__main__':
deployment = SwiftStorageBasicDeployment(series='precise',
openstack='cloud:precise-grizzly',
source='cloud:precise-updates/grizzly')
deployment.run_tests()

11
tests/13-basic-precise-havana Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/python
"""Amulet tests on a basic swift-storage deployment on precise-havana."""
from basic_deployment import SwiftStorageBasicDeployment
if __name__ == '__main__':
deployment = SwiftStorageBasicDeployment(series='precise',
openstack='cloud:precise-havana',
source='cloud:precise-updates/havana')
deployment.run_tests()

11
tests/14-basic-precise-icehouse Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/python
"""Amulet tests on a basic swift-storage deployment on precise-icehouse."""
from basic_deployment import SwiftStorageBasicDeployment
if __name__ == '__main__':
deployment = SwiftStorageBasicDeployment(series='precise',
openstack='cloud:precise-icehouse',
source='cloud:precise-updates/icehouse')
deployment.run_tests()

9
tests/15-basic-trusty-icehouse Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/python
"""Amulet tests on a basic swift-storage deployment on trusty-icehouse."""
from basic_deployment import SwiftStorageBasicDeployment
if __name__ == '__main__':
deployment = SwiftStorageBasicDeployment(series='trusty')
deployment.run_tests()

52
tests/README Normal file
View File

@ -0,0 +1,52 @@
This directory provides Amulet tests that focus on verification of swift-storage
deployments.
If you use a web proxy server to access the web, you'll need to set the
AMULET_HTTP_PROXY environment variable to the http URL of the proxy server.
The following examples demonstrate different ways that tests can be executed.
All examples are run from the charm's root directory.
* To run all tests (starting with 00-setup):
make test
* To run a specific test module (or modules):
juju test -v -p AMULET_HTTP_PROXY 15-basic-trusty-icehouse
* To run a specific test module (or modules), and keep the environment
deployed after a failure:
juju test --set-e -v -p AMULET_HTTP_PROXY 15-basic-trusty-icehouse
* To re-run a test module against an already deployed environment (one
that was deployed by a previous call to 'juju test --set-e'):
./tests/15-basic-trusty-icehouse
For debugging and test development purposes, all code should be idempotent.
In other words, the code should have the ability to be re-run without changing
the results beyond the initial run. This enables editing and re-running of a
test module against an already deployed environment, as described above.
Manual debugging tips:
* Set the following env vars before using the OpenStack CLI as admin:
export OS_AUTH_URL=http://`juju-deployer -f keystone 2>&1 | tail -n 1`:5000/v2.0
export OS_TENANT_NAME=admin
export OS_USERNAME=admin
export OS_PASSWORD=openstack
export OS_REGION_NAME=RegionOne
* Set the following env vars before using the OpenStack CLI as demoUser:
export OS_AUTH_URL=http://`juju-deployer -f keystone 2>&1 | tail -n 1`:5000/v2.0
export OS_TENANT_NAME=demoTenant
export OS_USERNAME=demoUser
export OS_PASSWORD=password
export OS_REGION_NAME=RegionOne
* Sample swift command:
swift -A $OS_AUTH_URL --os-tenant-name services --os-username swift \
--os-password password list
(where tenant/user names and password are in swift-proxy's nova.conf file)

450
tests/basic_deployment.py Normal file
View File

@ -0,0 +1,450 @@
#!/usr/bin/python
import amulet
import swiftclient
from charmhelpers.contrib.openstack.amulet.deployment import (
OpenStackAmuletDeployment
)
from charmhelpers.contrib.openstack.amulet.utils import (
OpenStackAmuletUtils,
DEBUG, # flake8: noqa
ERROR
)
# Use DEBUG to turn on debug logging
u = OpenStackAmuletUtils(ERROR)
class SwiftStorageBasicDeployment(OpenStackAmuletDeployment):
"""Amulet tests on a basic swift-storage deployment."""
def __init__(self, series, openstack=None, source=None):
"""Deploy the entire test environment."""
super(SwiftStorageBasicDeployment, self).__init__(series, openstack,
source)
self._add_services()
self._add_relations()
self._configure_services()
self._deploy()
self._initialize_tests()
def _add_services(self):
"""Add the service that we're testing, including the number of units,
where swift-storage is local, and the other charms are from
the charm store."""
this_service = ('swift-storage', 1)
other_services = [('mysql', 1),
('keystone', 1), ('glance', 1), ('swift-proxy', 1)]
super(SwiftStorageBasicDeployment, self)._add_services(this_service,
other_services)
def _add_relations(self):
"""Add all of the relations for the services."""
relations = {
'keystone:shared-db': 'mysql:shared-db',
'swift-proxy:identity-service': 'keystone:identity-service',
'swift-storage:swift-storage': 'swift-proxy:swift-storage',
'glance:identity-service': 'keystone:identity-service',
'glance:shared-db': 'mysql:shared-db',
'glance:object-store': 'swift-proxy:object-store'
}
super(SwiftStorageBasicDeployment, self)._add_relations(relations)
def _configure_services(self):
"""Configure all of the services."""
keystone_config = {'admin-password': 'openstack',
'admin-token': 'ubuntutesting'}
swift_proxy_config = {'zone-assignment': 'manual',
'replicas': '1',
'swift-hash': 'fdfef9d4-8b06-11e2-8ac0-531c923c8fae',
'use-https': 'no'}
swift_storage_config = {'zone': '1',
'block-device': 'vdb',
'overwrite': 'true'}
configs = {'keystone': keystone_config,
'swift-proxy': swift_proxy_config,
'swift-storage': swift_storage_config}
super(SwiftStorageBasicDeployment, self)._configure_services(configs)
def _initialize_tests(self):
"""Perform final initialization before tests get run."""
# Access the sentries for inspecting service units
self.mysql_sentry = self.d.sentry.unit['mysql/0']
self.keystone_sentry = self.d.sentry.unit['keystone/0']
self.glance_sentry = self.d.sentry.unit['glance/0']
self.swift_proxy_sentry = self.d.sentry.unit['swift-proxy/0']
self.swift_storage_sentry = self.d.sentry.unit['swift-storage/0']
# Authenticate admin with keystone
self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
user='admin',
password='openstack',
tenant='admin')
# Authenticate admin with glance endpoint
self.glance = u.authenticate_glance_admin(self.keystone)
# Authenticate swift user
keystone_relation = self.keystone_sentry.relation('identity-service',
'swift-proxy:identity-service')
ep = self.keystone.service_catalog.url_for(service_type='identity',
endpoint_type='publicURL')
self.swift = swiftclient.Connection(authurl=ep,
user=keystone_relation['service_username'],
key=keystone_relation['service_password'],
tenant_name=keystone_relation['service_tenant'],
auth_version='2.0')
# Create a demo tenant/role/user
self.demo_tenant = 'demoTenant'
self.demo_role = 'demoRole'
self.demo_user = 'demoUser'
if not u.tenant_exists(self.keystone, self.demo_tenant):
tenant = self.keystone.tenants.create(tenant_name=self.demo_tenant,
description='demo tenant',
enabled=True)
self.keystone.roles.create(name=self.demo_role)
self.keystone.users.create(name=self.demo_user,
password='password',
tenant_id=tenant.id,
email='demo@demo.com')
# Authenticate demo user with keystone
self.keystone_demo = \
u.authenticate_keystone_user(self.keystone, user=self.demo_user,
password='password',
tenant=self.demo_tenant)
def test_services(self):
"""Verify the expected services are running on the corresponding
service units."""
swift_storage_services = ['status swift-account',
'status swift-account-auditor',
'status swift-account-reaper',
'status swift-account-replicator',
'status swift-container',
'status swift-container-auditor',
'status swift-container-replicator',
'status swift-container-updater',
'status swift-object',
'status swift-object-auditor',
'status swift-object-replicator',
'status swift-object-updater']
if self._get_openstack_release() >= self.precise_icehouse:
swift_storage_services.append('status swift-container-sync')
commands = {
self.mysql_sentry: ['status mysql'],
self.keystone_sentry: ['status keystone'],
self.glance_sentry: ['status glance-registry', 'status glance-api'],
self.swift_proxy_sentry: ['status swift-proxy'],
self.swift_storage_sentry: swift_storage_services
}
ret = u.validate_services(commands)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_users(self):
"""Verify all existing roles."""
user1 = {'name': 'demoUser',
'enabled': True,
'tenantId': u.not_null,
'id': u.not_null,
'email': 'demo@demo.com'}
user2 = {'name': 'admin',
'enabled': True,
'tenantId': u.not_null,
'id': u.not_null,
'email': 'juju@localhost'}
user3 = {'name': 'glance',
'enabled': True,
'tenantId': u.not_null,
'id': u.not_null,
'email': u'juju@localhost'}
user4 = {'name': 'swift',
'enabled': True,
'tenantId': u.not_null,
'id': u.not_null,
'email': u'juju@localhost'}
expected = [user1, user2, user3, user4]
actual = self.keystone.users.list()
ret = u.validate_user_data(expected, actual)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_service_catalog(self):
"""Verify that the service catalog endpoint data is valid."""
endpoint_vol = {'adminURL': u.valid_url,
'region': 'RegionOne',
'publicURL': u.valid_url,
'internalURL': u.valid_url}
endpoint_id = {'adminURL': u.valid_url,
'region': 'RegionOne',
'publicURL': u.valid_url,
'internalURL': u.valid_url}
if self._get_openstack_release() >= self.precise_folsom:
endpoint_vol['id'] = u.not_null
endpoint_id['id'] = u.not_null
expected = {'image': [endpoint_id], 'object-store': [endpoint_id],
'identity': [endpoint_id]}
actual = self.keystone_demo.service_catalog.get_endpoints()
ret = u.validate_svc_catalog_endpoint_data(expected, actual)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
def test_openstack_object_store_endpoint(self):
"""Verify the swift object-store endpoint data."""
endpoints = self.keystone.endpoints.list()
admin_port = internal_port = public_port = '8080'
expected = {'id': u.not_null,
'region': 'RegionOne',
'adminurl': u.valid_url,
'internalurl': u.valid_url,
'publicurl': u.valid_url,
'service_id': u.not_null}
ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
public_port, expected)
if ret:
message = 'object-store endpoint: {}'.format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_swift_storage_swift_storage_relation(self):
"""Verify the swift-storage to swift-proxy swift-storage relation
data."""
unit = self.swift_storage_sentry
relation = ['swift-storage', 'swift-proxy:swift-storage']
expected = {
'account_port': '6002',
'zone': '1',
'object_port': '6000',
'container_port': '6001',
'private-address': u.valid_ip,
'device': 'vdb'
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('swift-storage swift-storage', ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_swift_proxy_swift_storage_relation(self):
"""Verify the swift-proxy to swift-storage swift-storage relation
data."""
unit = self.swift_proxy_sentry
relation = ['swift-storage', 'swift-storage:swift-storage']
expected = {
'private-address': u.valid_ip,
'trigger': u.not_null,
'rings_url': u.valid_url,
'swift_hash': u.not_null
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('swift-proxy swift-storage', ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_restart_on_config_change(self):
"""Verify that the specified services are restarted when the config
is changed."""
# NOTE(coreycb): Skipping failing test on until resolved. This test
# fails because the config file's last mod time is
# slightly after the process' last mod time.
if self._get_openstack_release() >= self.precise_essex:
u.log.error("Skipping failing test until resolved")
return
services = {'swift-account-server': 'account-server.conf',
'swift-account-auditor': 'account-server.conf',
'swift-account-reaper': 'account-server.conf',
'swift-account-replicator': 'account-server.conf',
'swift-container-server': 'container-server.conf',
'swift-container-auditor': 'container-server.conf',
'swift-container-replicator': 'container-server.conf',
'swift-container-updater': 'container-server.conf',
'swift-object-server': 'object-server.conf',
'swift-object-auditor': 'object-server.conf',
'swift-object-replicator': 'object-server.conf',
'swift-object-updater': 'object-server.conf'}
if self._get_openstack_release() >= self.precise_icehouse:
services['swift-container-sync'] = 'container-server.conf'
self.d.configure('swift-storage',
{'object-server-threads-per-disk': '2'})
time = 20
for s, conf in services.iteritems():
config = '/etc/swift/{}'.format(conf)
if not u.service_restarted(self.swift_storage_sentry, s, config,
pgrep_full=True, sleep_time=time):
msg = "service {} didn't restart after config change".format(s)
amulet.raise_status(amulet.FAIL, msg=msg)
time = 0
self.d.configure('swift-storage',
{'object-server-threads-per-disk': '4'})
def test_swift_config(self):
"""Verify the data in the swift-hash section of the swift config
file."""
unit = self.swift_storage_sentry
conf = '/etc/swift/swift.conf'
swift_proxy_relation = self.swift_proxy_sentry.relation('swift-storage',
'swift-storage:swift-storage')
expected = {
'swift_hash_path_suffix': swift_proxy_relation['swift_hash']
}
ret = u.validate_config_data(unit, conf, 'swift-hash', expected)
if ret:
message = "swift config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_account_server_config(self):
"""Verify the data in the account server config file."""
unit = self.swift_storage_sentry
conf = '/etc/swift/account-server.conf'
expected = {
'DEFAULT': {
'bind_ip': '0.0.0.0',
'bind_port': '6002',
'workers': '1'
},
'pipeline:main': {
'pipeline': 'recon account-server'
},
'filter:recon': {
'use': 'egg:swift#recon',
'recon_cache_path': '/var/cache/swift'
},
'app:account-server': {
'use': 'egg:swift#account'
}
}
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit, conf, section, pairs)
if ret:
message = "account server config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_container_server_config(self):
"""Verify the data in the container server config file."""
unit = self.swift_storage_sentry
conf = '/etc/swift/container-server.conf'
expected = {
'DEFAULT': {
'bind_ip': '0.0.0.0',
'bind_port': '6001',
'workers': '1'
},
'pipeline:main': {
'pipeline': 'recon container-server'
},
'filter:recon': {
'use': 'egg:swift#recon',
'recon_cache_path': '/var/cache/swift'
},
'app:container-server': {
'use': 'egg:swift#container',
'allow_versions': 'true'
}
}
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit, conf, section, pairs)
if ret:
message = "container server config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_object_server_config(self):
"""Verify the data in the object server config file."""
unit = self.swift_storage_sentry
conf = '/etc/swift/object-server.conf'
expected = {
'DEFAULT': {
'bind_ip': '0.0.0.0',
'bind_port': '6000',
'workers': '1'
},
'pipeline:main': {
'pipeline': 'recon object-server'
},
'filter:recon': {
'use': 'egg:swift#recon',
'recon_cache_path': '/var/cache/swift'
},
'app:object-server': {
'use': 'egg:swift#object',
'threads_per_disk': '4'
}
}
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit, conf, section, pairs)
if ret:
message = "object server config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_image_create(self):
"""Create an instance in glance, which is backed by swift, and validate
that some of the metadata for the image match in glance and swift."""
# NOTE(coreycb): Skipping failing test on folsom until resolved. On
# folsom only, uploading an image to glance gets 400 Bad
# Request - Error uploading image: (error): [Errno 111]
# ECONNREFUSED (HTTP 400)
if self._get_openstack_release() == self.precise_folsom:
u.log.error("Skipping failing test until resolved")
return
# Create glance image
image = u.create_cirros_image(self.glance, "cirros-image")
if not image:
amulet.raise_status(amulet.FAIL, msg="Image create failed")
# Validate that cirros image exists in glance and get its checksum/size
images = list(self.glance.images.list())
if len(images) != 1:
msg = "Expected 1 glance image, found {}".format(len(images))
amulet.raise_status(amulet.FAIL, msg=msg)
if images[0].name != 'cirros-image':
message = "cirros image does not exist"
amulet.raise_status(amulet.FAIL, msg=message)
glance_image_md5 = image.checksum
glance_image_size = image.size
# Validate that swift object's checksum/size match that from glance
headers, containers = self.swift.get_account()
if len(containers) != 1:
msg = "Expected 1 swift container, found {}".format(len(containers))
amulet.raise_status(amulet.FAIL, msg=msg)
container_name = containers[0].get('name')
headers, objects = self.swift.get_container(container_name)
if len(objects) != 1:
msg = "Expected 1 swift object, found {}".format(len(objects))
amulet.raise_status(amulet.FAIL, msg=msg)
swift_object_size = objects[0].get('bytes')
swift_object_md5 = objects[0].get('hash')
if glance_image_size != swift_object_size:
msg = "Glance image size {} != swift object size {}".format( \
glance_image_size, swift_object_size)
amulet.raise_status(amulet.FAIL, msg=msg)
if glance_image_md5 != swift_object_md5:
msg = "Glance image hash {} != swift object hash {}".format( \
glance_image_md5, swift_object_md5)
amulet.raise_status(amulet.FAIL, msg=msg)
# Cleanup
u.delete_image(self.glance, image)