#!/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' } 'object-replicator': { 'concurrency': '1' } } 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)