From 4faa7f927149a5c4ef7a03523f7bc14523cb9baa Mon Sep 17 00:00:00 2001 From: Stuart Mackie Date: Fri, 7 Oct 2016 12:24:58 -0700 Subject: Charms for Contrail 3.1 with Mitaka Change-Id: Id37f3b9743d1974e31fcd7cd9c54be41bb0c47fb Signed-off-by: Stuart Mackie --- .../ceilometer-agent/tests/basic_deployment.py | 678 +++++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 charms/trusty/ceilometer-agent/tests/basic_deployment.py (limited to 'charms/trusty/ceilometer-agent/tests/basic_deployment.py') diff --git a/charms/trusty/ceilometer-agent/tests/basic_deployment.py b/charms/trusty/ceilometer-agent/tests/basic_deployment.py new file mode 100644 index 0000000..4be7285 --- /dev/null +++ b/charms/trusty/ceilometer-agent/tests/basic_deployment.py @@ -0,0 +1,678 @@ +# Copyright 2016 Canonical Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess +import amulet +import json +import time +import ceilometerclient.v2.client as ceilo_client + +from charmhelpers.contrib.openstack.amulet.deployment import ( + OpenStackAmuletDeployment +) + +from charmhelpers.contrib.openstack.amulet.utils import ( + OpenStackAmuletUtils, + DEBUG, + # ERROR +) + +# Use DEBUG to turn on debug logging +u = OpenStackAmuletUtils(DEBUG) + + +class CeiloAgentBasicDeployment(OpenStackAmuletDeployment): + """Amulet tests on a basic ceilometer-agent deployment.""" + + def __init__(self, series, openstack=None, source=None, stable=True): + """Deploy the entire test environment.""" + super(CeiloAgentBasicDeployment, self).__init__(series, openstack, + source, stable) + self._add_services() + self._add_relations() + self._configure_services() + self._deploy() + + u.log.info('Waiting on extended status checks...') + exclude_services = ['mysql', 'mongodb'] + self._auto_wait_for_status(exclude_services=exclude_services) + + self._initialize_tests() + + def _add_services(self): + """Add services + + Add the services that we're testing, where ceilometer is local, + and the rest of the service are from lp branches that are + compatible with the local charm (e.g. stable or next). + """ + # Note: ceilometer-agent becomes a subordinate of nova-compute + this_service = {'name': 'ceilometer-agent'} + other_services = [{'name': 'mysql'}, + {'name': 'rabbitmq-server'}, + {'name': 'keystone'}, + {'name': 'mongodb'}, + {'name': 'glance'}, # to satisfy workload status + {'name': 'ceilometer'}, + {'name': 'nova-compute'}] + super(CeiloAgentBasicDeployment, self)._add_services(this_service, + other_services) + + def _add_relations(self): + """Add all of the relations for the services.""" + relations = { + 'ceilometer:shared-db': 'mongodb:database', + 'ceilometer:amqp': 'rabbitmq-server:amqp', + 'ceilometer:identity-service': 'keystone:identity-service', + 'ceilometer:identity-notifications': 'keystone:' + 'identity-notifications', + 'keystone:shared-db': 'mysql:shared-db', + 'ceilometer:ceilometer-service': 'ceilometer-agent:' + 'ceilometer-service', + 'nova-compute:nova-ceilometer': 'ceilometer-agent:nova-ceilometer', + 'nova-compute:shared-db': 'mysql:shared-db', + 'nova-compute:amqp': 'rabbitmq-server:amqp', + 'glance:identity-service': 'keystone:identity-service', + 'glance:shared-db': 'mysql:shared-db', + 'glance:amqp': 'rabbitmq-server:amqp', + 'nova-compute:image-service': 'glance:image-service' + } + super(CeiloAgentBasicDeployment, self)._add_relations(relations) + + def _configure_services(self): + """Configure all of the services.""" + keystone_config = {'admin-password': 'openstack', + 'admin-token': 'ubuntutesting'} + configs = {'keystone': keystone_config} + super(CeiloAgentBasicDeployment, self)._configure_services(configs) + + def _get_token(self): + return self.keystone.service_catalog.catalog['token']['id'] + + def _initialize_tests(self): + """Perform final initialization before tests get run.""" + # Access the sentries for inspecting service units + self.ceil_sentry = self.d.sentry['ceilometer'][0] + self.ceil_agent_sentry = self.d.sentry['ceilometer-agent'][0] + self.mysql_sentry = self.d.sentry['mysql'][0] + self.keystone_sentry = self.d.sentry['keystone'][0] + self.rabbitmq_sentry = self.d.sentry['rabbitmq-server'][0] + self.mongodb_sentry = self.d.sentry['mongodb'][0] + self.nova_sentry = self.d.sentry['nova-compute'][0] + u.log.debug('openstack release val: {}'.format( + self._get_openstack_release())) + u.log.debug('openstack release str: {}'.format( + self._get_openstack_release_string())) + + # Authenticate admin with keystone endpoint + self.keystone = u.authenticate_keystone_admin(self.keystone_sentry, + user='admin', + password='openstack', + tenant='admin') + + # Authenticate admin with ceilometer endpoint + ep = self.keystone.service_catalog.url_for(service_type='metering', + endpoint_type='publicURL') + os_token = self.keystone.auth_token + self.log.debug('Instantiating ceilometer client...') + self.ceil = ceilo_client.Client(endpoint=ep, token=os_token) + + def _run_action(self, unit_id, action, *args): + command = ["juju", "action", "do", "--format=json", unit_id, action] + command.extend(args) + print("Running command: %s\n" % " ".join(command)) + output = subprocess.check_output(command) + output_json = output.decode(encoding="UTF-8") + data = json.loads(output_json) + action_id = data[u'Action queued with id'] + return action_id + + def _wait_on_action(self, action_id): + command = ["juju", "action", "fetch", "--format=json", action_id] + while True: + try: + output = subprocess.check_output(command) + except Exception as e: + print(e) + return False + output_json = output.decode(encoding="UTF-8") + data = json.loads(output_json) + if data[u"status"] == "completed": + return True + elif data[u"status"] == "failed": + return False + time.sleep(2) + + def test_100_services(self): + """Verify the expected services are running on the corresponding + service units.""" + u.log.debug('Checking system services on units...') + + ceilometer_svcs = [ + 'ceilometer-agent-central', + 'ceilometer-collector', + 'ceilometer-api', + 'ceilometer-agent-notification', + ] + + if self._get_openstack_release() < self.trusty_mitaka: + ceilometer_svcs.append('ceilometer-alarm-evaluator') + ceilometer_svcs.append('ceilometer-alarm-notifier') + + service_names = { + self.ceil_sentry: ceilometer_svcs, + } + + ret = u.validate_services_by_name(service_names) + if ret: + amulet.raise_status(amulet.FAIL, msg=ret) + + u.log.debug('OK') + + def test_110_service_catalog(self): + """Verify that the service catalog endpoint data is valid.""" + u.log.debug('Checking keystone service catalog data...') + endpoint_check = { + 'adminURL': u.valid_url, + 'id': u.not_null, + 'region': 'RegionOne', + 'publicURL': u.valid_url, + 'internalURL': u.valid_url + } + expected = { + 'metering': [endpoint_check], + 'identity': [endpoint_check] + } + actual = self.keystone.service_catalog.get_endpoints() + + ret = u.validate_svc_catalog_endpoint_data(expected, actual) + if ret: + amulet.raise_status(amulet.FAIL, msg=ret) + + u.log.debug('OK') + + def test_112_keystone_api_endpoint(self): + """Verify the ceilometer api endpoint data.""" + u.log.debug('Checking keystone api endpoint data...') + endpoints = self.keystone.endpoints.list() + u.log.debug(endpoints) + internal_port = public_port = '5000' + admin_port = '35357' + 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 = 'Keystone endpoint: {}'.format(ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_114_ceilometer_api_endpoint(self): + """Verify the ceilometer api endpoint data.""" + u.log.debug('Checking ceilometer api endpoint data...') + endpoints = self.keystone.endpoints.list() + u.log.debug(endpoints) + admin_port = internal_port = public_port = '8777' + 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 = 'Ceilometer endpoint: {}'.format(ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_200_ceilometer_identity_relation(self): + """Verify the ceilometer to keystone identity-service relation data""" + u.log.debug('Checking ceilometer to keystone identity-service ' + 'relation data...') + unit = self.ceil_sentry + relation = ['identity-service', 'keystone:identity-service'] + ceil_ip = unit.relation('identity-service', + 'keystone:identity-service')['private-address'] + ceil_endpoint = "http://%s:8777" % (ceil_ip) + + expected = { + 'admin_url': ceil_endpoint, + 'internal_url': ceil_endpoint, + 'private-address': ceil_ip, + 'public_url': ceil_endpoint, + 'region': 'RegionOne', + 'requested_roles': 'ResellerAdmin', + 'service': 'ceilometer', + } + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('ceilometer identity-service', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_201_keystone_ceilometer_identity_relation(self): + """Verify the keystone to ceilometer identity-service relation data""" + u.log.debug('Checking keystone:ceilometer identity relation data...') + unit = self.keystone_sentry + relation = ['identity-service', 'ceilometer:identity-service'] + id_relation = unit.relation('identity-service', + 'ceilometer:identity-service') + id_ip = id_relation['private-address'] + expected = { + 'admin_token': 'ubuntutesting', + 'auth_host': id_ip, + 'auth_port': "35357", + 'auth_protocol': 'http', + 'private-address': id_ip, + 'service_host': id_ip, + 'service_password': u.not_null, + 'service_port': "5000", + 'service_protocol': 'http', + 'service_tenant': 'services', + 'service_tenant_id': u.not_null, + 'service_username': 'ceilometer', + } + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('keystone identity-service', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_202_keystone_ceilometer_identity_notes_relation(self): + """Verify ceilometer to keystone identity-notifications relation""" + u.log.debug('Checking keystone:ceilometer ' + 'identity-notifications relation data...') + + # Relation data may vary depending on timing of hooks and relations. + # May be glance- or keystone- or another endpoint-changed value, so + # check that at least one ???-endpoint-changed value exists. + unit = self.keystone_sentry + relation_data = unit.relation('identity-service', + 'ceilometer:identity-notifications') + + expected = '-endpoint-changed' + found = 0 + for key in relation_data.keys(): + if expected in key and relation_data[key]: + found += 1 + u.log.debug('{}: {}'.format(key, relation_data[key])) + + if not found: + message = ('keystone:ceilometer identity-notification relation ' + 'error\n expected something like: {}\n actual: ' + '{}'.format(expected, relation_data)) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_203_ceilometer_amqp_relation(self): + """Verify the ceilometer to rabbitmq-server amqp relation data""" + u.log.debug('Checking ceilometer:rabbitmq amqp relation data...') + unit = self.ceil_sentry + relation = ['amqp', 'rabbitmq-server:amqp'] + expected = { + 'username': 'ceilometer', + 'private-address': u.valid_ip, + 'vhost': 'openstack' + } + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('ceilometer amqp', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_204_amqp_ceilometer_relation(self): + """Verify the rabbitmq-server to ceilometer amqp relation data""" + u.log.debug('Checking rabbitmq:ceilometer amqp relation data...') + unit = self.rabbitmq_sentry + relation = ['amqp', 'ceilometer:amqp'] + expected = { + 'hostname': u.valid_ip, + 'private-address': u.valid_ip, + 'password': u.not_null, + } + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('rabbitmq amqp', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_205_ceilometer_to_mongodb_relation(self): + """Verify the ceilometer to mongodb relation data""" + u.log.debug('Checking ceilometer:mongodb relation data...') + unit = self.ceil_sentry + relation = ['shared-db', 'mongodb:database'] + expected = { + 'ceilometer_database': 'ceilometer', + 'private-address': u.valid_ip, + } + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('ceilometer shared-db', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_206_mongodb_to_ceilometer_relation(self): + """Verify the mongodb to ceilometer relation data""" + u.log.debug('Checking mongodb:ceilometer relation data...') + unit = self.mongodb_sentry + relation = ['database', 'ceilometer:shared-db'] + expected = { + 'hostname': u.valid_ip, + 'port': '27017', + 'private-address': u.valid_ip, + 'type': 'database', + } + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('mongodb database', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_207_ceilometer_ceilometer_agent_relation(self): + """Verify the ceilometer to ceilometer-agent relation data""" + u.log.debug('Checking ceilometer:ceilometer-agent relation data...') + unit = self.ceil_sentry + relation = ['ceilometer-service', + 'ceilometer-agent:ceilometer-service'] + expected = { + 'rabbitmq_user': 'ceilometer', + 'verbose': 'False', + 'rabbitmq_host': u.valid_ip, + 'service_ports': "{'ceilometer_api': [8777, 8767]}", + 'use_syslog': 'False', + 'metering_secret': u.not_null, + 'rabbitmq_virtual_host': 'openstack', + 'db_port': '27017', + 'private-address': u.valid_ip, + 'db_name': 'ceilometer', + 'db_host': u.valid_ip, + 'debug': 'False', + 'rabbitmq_password': u.not_null, + 'port': '8767' + } + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('ceilometer-service', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_208_ceilometer_agent_ceilometer_relation(self): + """Verify the ceilometer-agent to ceilometer relation data""" + u.log.debug('Checking ceilometer-agent:ceilometer relation data...') + unit = self.ceil_agent_sentry + relation = ['ceilometer-service', 'ceilometer:ceilometer-service'] + expected = {'private-address': u.valid_ip} + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('ceilometer-service', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_209_nova_compute_ceilometer_agent_relation(self): + """Verify the nova-compute to ceilometer relation data""" + u.log.debug('Checking nova-compute:ceilometer relation data...') + unit = self.nova_sentry + relation = ['nova-ceilometer', 'ceilometer-agent:nova-ceilometer'] + expected = {'private-address': u.valid_ip} + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('ceilometer-service', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_210_ceilometer_agent_nova_compute_relation(self): + """Verify the ceilometer to nova-compute relation data""" + u.log.debug('Checking ceilometer:nova-compute relation data...') + unit = self.ceil_agent_sentry + relation = ['nova-ceilometer', 'nova-compute:nova-ceilometer'] + sub = ('{"nova": {"/etc/nova/nova.conf": {"sections": {"DEFAULT": ' + '[["instance_usage_audit", "True"], ' + '["instance_usage_audit_period", "hour"], ' + '["notify_on_state_change", "vm_and_task_state"], ' + '["notification_driver", "ceilometer.compute.nova_notifier"], ' + '["notification_driver", ' + '"nova.openstack.common.notifier.rpc_notifier"]]}}}}') + expected = { + 'subordinate_configuration': sub, + 'private-address': u.valid_ip + } + + ret = u.validate_relation_data(unit, relation, expected) + if ret: + message = u.relation_error('ceilometer-service', ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_300_ceilometer_config(self): + """Verify the data in the ceilometer config file.""" + u.log.debug('Checking ceilometer config file data...') + unit = self.ceil_sentry + ks_rel = self.keystone_sentry.relation('identity-service', + 'ceilometer:identity-service') + auth_uri = '%s://%s:%s/' % (ks_rel['service_protocol'], + ks_rel['service_host'], + ks_rel['service_port']) + db_relation = self.mongodb_sentry.relation('database', + 'ceilometer:shared-db') + db_conn = 'mongodb://%s:%s/ceilometer' % (db_relation['hostname'], + db_relation['port']) + conf = '/etc/ceilometer/ceilometer.conf' + expected = { + 'DEFAULT': { + 'verbose': 'False', + 'debug': 'False', + 'use_syslog': 'False', + }, + 'api': { + 'port': '8767', + }, + 'service_credentials': { + 'os_auth_url': auth_uri + 'v2.0', + 'os_tenant_name': 'services', + 'os_username': 'ceilometer', + 'os_password': ks_rel['service_password'], + }, + 'database': { + 'connection': db_conn, + }, + } + + for section, pairs in expected.iteritems(): + ret = u.validate_config_data(unit, conf, section, pairs) + if ret: + message = "ceilometer config error: {}".format(ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_301_nova_config(self): + """Verify data in the nova compute nova config file""" + u.log.debug('Checking nova compute config file...') + unit = self.nova_sentry + conf = '/etc/nova/nova.conf' + expected = { + 'DEFAULT': { + 'verbose': 'False', + 'debug': 'False', + 'use_syslog': 'False', + 'my_ip': u.valid_ip, + } + } + + # NOTE(beisner): notification_driver is not checked like the + # others, as configparser does not support duplicate config + # options, and dicts cant have duplicate keys. + # Ex. from conf file: + # notification_driver = ceilometer.compute.nova_notifier + # notification_driver = nova.openstack.common.notifier.rpc_notifier + for section, pairs in expected.iteritems(): + ret = u.validate_config_data(unit, conf, section, pairs) + if ret: + message = "ceilometer config error: {}".format(ret) + amulet.raise_status(amulet.FAIL, msg=message) + + # Check notification_driver existence via simple grep cmd + lines = [('notification_driver = ' + 'ceilometer.compute.nova_notifier'), + ('notification_driver = ' + 'nova.openstack.common.notifier.rpc_notifier')] + + sentry_units = [unit] + cmds = [] + for line in lines: + cmds.append('grep "{}" {}'.format(line, conf)) + + ret = u.check_commands_on_units(cmds, sentry_units) + if ret: + amulet.raise_status(amulet.FAIL, msg=ret) + + u.log.debug('OK') + + def test_302_nova_ceilometer_config(self): + """Verify data in the ceilometer config file on the + nova-compute (ceilometer-agent) unit.""" + u.log.debug('Checking nova ceilometer config file...') + unit = self.nova_sentry + conf = '/etc/ceilometer/ceilometer.conf' + expected = { + 'DEFAULT': { + 'logdir': '/var/log/ceilometer' + }, + 'database': { + 'backend': 'sqlalchemy', + 'connection': 'sqlite:////var/lib/ceilometer/$sqlite_db' + } + } + + for section, pairs in expected.iteritems(): + ret = u.validate_config_data(unit, conf, section, pairs) + if ret: + message = "ceilometer config error: {}".format(ret) + amulet.raise_status(amulet.FAIL, msg=message) + + u.log.debug('OK') + + def test_400_api_connection(self): + """Simple api calls to check service is up and responding""" + u.log.debug('Checking api functionality...') + assert(self.ceil.samples.list() == []) + assert(self.ceil.meters.list() == []) + u.log.debug('OK') + + # NOTE(beisner): need to add more functional tests + + def test_900_restart_on_config_change(self): + """Verify that the specified services are restarted when the config + is changed. + """ + sentry = self.ceil_sentry + juju_service = 'ceilometer' + + # Expected default and alternate values + set_default = {'debug': 'False'} + set_alternate = {'debug': 'True'} + + # Services which are expected to restart upon config change, + # and corresponding config files affected by the change + conf_file = '/etc/ceilometer/ceilometer.conf' + services = { + 'ceilometer-collector': conf_file, + 'ceilometer-api': conf_file, + 'ceilometer-agent-notification': conf_file, + } + + if self._get_openstack_release() < self.trusty_mitaka: + services['ceilometer-alarm-notifier'] = conf_file + services['ceilometer-alarm-evaluator'] = conf_file + + if self._get_openstack_release() == self.trusty_liberty or \ + self._get_openstack_release() >= self.wily_liberty: + # Liberty and later + services['ceilometer-polling'] = conf_file + else: + # Juno and earlier + services['ceilometer-agent-central'] = conf_file + + # Make config change, check for service restarts + u.log.debug('Making config change on {}...'.format(juju_service)) + mtime = u.get_sentry_time(sentry) + self.d.configure(juju_service, set_alternate) + + sleep_time = 40 + for s, conf_file in services.iteritems(): + u.log.debug("Checking that service restarted: {}".format(s)) + if not u.validate_service_config_changed(sentry, mtime, s, + conf_file, + retry_count=4, + retry_sleep_time=20, + sleep_time=sleep_time): + self.d.configure(juju_service, set_default) + msg = "service {} didn't restart after config change".format(s) + amulet.raise_status(amulet.FAIL, msg=msg) + sleep_time = 0 + + self.d.configure(juju_service, set_default) + u.log.debug('OK') + + def test_910_pause_and_resume(self): + """The services can be paused and resumed. """ + u.log.debug('Checking pause and resume actions...') + unit = self.d.sentry['ceilometer-agent'][0] + unit_name = unit.info['unit_name'] + + u.log.debug('Checking for active status on {}'.format(unit_name)) + assert u.status_get(unit)[0] == "active" + + u.log.debug('Running pause action on {}'.format(unit_name)) + action_id = self._run_action(unit_name, "pause") + u.log.debug('Waiting on action {}'.format(action_id)) + assert self._wait_on_action(action_id), "Pause action failed." + u.log.debug('Checking for maintenance status on {}'.format(unit_name)) + assert u.status_get(unit)[0] == "maintenance" + + u.log.debug('Running resume action on {}'.format(unit_name)) + action_id = self._run_action(unit_name, "resume") + u.log.debug('Waiting on action {}'.format(action_id)) + assert self._wait_on_action(action_id), "Resume action failed." + u.log.debug('Checking for active status on {}'.format(unit_name)) + assert u.status_get(unit)[0] == "active" + u.log.debug('OK') -- cgit 1.2.3-korg