From 9b37776abdd1e0e4125ce244c0acd3016f11db40 Mon Sep 17 00:00:00 2001 From: Kevin Carter Date: Fri, 15 Feb 2019 12:22:23 -0600 Subject: [PATCH] Update monitorstack to use updated openstacksdk and es This change updates the functions so that they work with the most up to date OpenStack SDK, and adds an elasticsearch output format. Tests have been udpated to pass newer versions of flake8 and py3 Zuul project config has been added Change-Id: If46d3fb8e2b2e4aa5e21bf7da470945b05d216cf Signed-off-by: cloudnull --- etc/openstack.ini | 17 +++++ monitorstack/cli.py | 8 ++- monitorstack/common/formatters.py | 44 +++++++++++-- monitorstack/plugins/os_block_pools_totals.py | 18 ++++-- monitorstack/plugins/os_block_pools_usage.py | 47 ++++++++------ monitorstack/plugins/os_vm_quota_cores.py | 18 ++++-- monitorstack/plugins/os_vm_quota_instance.py | 18 ++++-- monitorstack/plugins/os_vm_quota_ram.py | 18 ++++-- monitorstack/plugins/os_vm_used_cores.py | 14 ++++- monitorstack/plugins/os_vm_used_disk.py | 14 ++++- monitorstack/plugins/os_vm_used_instance.py | 14 ++++- monitorstack/plugins/os_vm_used_ram.py | 14 ++++- monitorstack/utils/__init__.py | 11 ++-- monitorstack/utils/os_utils.py | 41 ++++++------ requirements.txt | 1 + tests/__init__.py | 1 + tests/unit/test_formatters.py | 16 ++--- tests/unit/test_os_utils.py | 63 +++++++++++-------- tests/unit/test_plugin_os_block.py | 42 +++++-------- tests/unit/test_plugin_os_vm.py | 10 +-- tests/unit/test_plugin_process.py | 2 +- tox.ini | 6 +- zuul.d/project.yaml | 31 +++++++++ 23 files changed, 318 insertions(+), 150 deletions(-) create mode 100644 zuul.d/project.yaml diff --git a/etc/openstack.ini b/etc/openstack.ini index c493a6f..68139b5 100644 --- a/etc/openstack.ini +++ b/etc/openstack.ini @@ -14,6 +14,13 @@ auth_url = https://127.0.0.1:5000/v3 username = admin password = Secrete +# NOTE(Cloudnull): +# If the system already has a clouds.yaml configuration file in place, monitorstack +# can use this config by default. Create the "cloud" section and set the cloud option. +# When using this section, no other OpenStack options are needed. +# [cloud] +# cloud = default + [keystone] # NOTE(cloudnull): # When using keystone V3 you will need the .*domain_name configuration options. @@ -45,3 +52,13 @@ project_name = ironic user_domain_name = users project_domain_name = projects password = SuperSecrete + +[elasticsearch] +# List of hosts. Note, items in this list are strings with the hostname or IP only. +hosts = ['localhost'] +# Optional settings when using authenticated environments. +# http_auth = ('user', 'secret') + +# Optional settings when using SSL. +# scheme="https" +# port=443 diff --git a/monitorstack/cli.py b/monitorstack/cli.py index fde17ac..32eb87a 100755 --- a/monitorstack/cli.py +++ b/monitorstack/cli.py @@ -83,7 +83,8 @@ VALID_OUTPUT_FORMATS = [ 'json', 'line', 'telegraf', - 'rax-maas' + 'rax-maas', + 'elasticsearch' ] @@ -96,6 +97,9 @@ VALID_OUTPUT_FORMATS = [ ', '.join(VALID_OUTPUT_FORMATS) ), ) +@click.option('--config-file', + help='MonitorStack configuration file', + default='openstack.ini') @click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode.') @pass_context def cli(*args, **kwargs): @@ -122,7 +126,7 @@ def process_result(results, output_format, **kwargs): exit_code = 0 for result in results: - output_formatter(result) + output_formatter(result, kwargs['config_file']) if result['exit_code'] != 0: exit_code = result['exit_code'] else: diff --git a/monitorstack/common/formatters.py b/monitorstack/common/formatters.py index 744438b..3ee2778 100644 --- a/monitorstack/common/formatters.py +++ b/monitorstack/common/formatters.py @@ -18,15 +18,17 @@ import time import click +from monitorstack import utils -def write_json(result): + +def write_json(result, config_file): """Output in raw JSON format.""" output = json.dumps(result, indent=2) click.echo(output) return True -def write_line(result): +def write_line(result, config_file): """Output in line format.""" for key, value in result['variables'].items(): click.echo("{} {}".format(key, value)) @@ -102,7 +104,7 @@ def _telegraf_line_format(sets, quote=False): return ','.join(store).rstrip(',') -def write_telegraf(result): +def write_telegraf(result, config_file): """Output in telegraf format.""" resultant = [result['measurement_name']] if 'meta' in result: @@ -120,7 +122,7 @@ def write_telegraf(result): return True -def write_rax_maas(result): +def write_rax_maas(result, config_file): """Output in Rackspace Monitoring as a Service format.""" status = ['status'] if result['exit_code'] == 0: @@ -142,3 +144,37 @@ def write_rax_maas(result): click.echo(' '.join(metric)) return True + + +def write_elasticsearch(result, config_file): + """Output in elasticsearch format.""" + import datetime + from elasticsearch import Elasticsearch + + config = utils.read_config(config_file=config_file) + if 'elasticsearch' in config: + elastcisearch_config = config['elasticsearch'] + es = Elasticsearch(**elastcisearch_config) + else: + es = Elasticsearch() + + doc = { + 'author': 'openstack', + 'text': result['message'], + 'timestamp': datetime.datetime.now(), + 'measurement_name': result['measurement_name'], + 'meta': result['meta'], + 'variables': result['variables'], + 'status': result['exit_code'] + } + + res = es.index( + index="monitorstack-{}".format( + datetime.date.today().strftime("%Y-%m-%d") + ), + id=_current_time(), + doc_type='openstack-metrics', + body=doc + ) + + click.echo(res['result']) diff --git a/monitorstack/plugins/os_block_pools_totals.py b/monitorstack/plugins/os_block_pools_totals.py index 683a0b1..f8d6da9 100644 --- a/monitorstack/plugins/os_block_pools_totals.py +++ b/monitorstack/plugins/os_block_pools_totals.py @@ -25,7 +25,7 @@ COMMAND_NAME = 'os_block_pools_totals' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -43,14 +43,22 @@ def cli(ctx, config_file): }, 'variables': {} } - config = utils.read_config(config_file=config_file)['cinder'] - interface = config.pop('interface', 'internal') - _ost = ost.OpenStack(os_auth_args=config) + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('cinder') + cloud_config = os_config.get('cloud') + if service_config: + _ost = ost.OpenStack(os_auth_args=service_config) + else: + _ost = ost.OpenStack(os_auth_args=cloud_config) + try: variables = output['variables'] total_capacity_gb = 0 free_capacity_gb = 0 - for item in _ost.get_volume_pool_stats(interface=interface): + for item in _ost.get_volume_pool_stats(): cap = item['capabilities'] output['meta'][cap.get('pool_name')] = True free_capacity_gb += float(cap.get('free_capacity_gb', 0)) diff --git a/monitorstack/plugins/os_block_pools_usage.py b/monitorstack/plugins/os_block_pools_usage.py index f4635f4..1a35462 100644 --- a/monitorstack/plugins/os_block_pools_usage.py +++ b/monitorstack/plugins/os_block_pools_usage.py @@ -25,7 +25,7 @@ COMMAND_NAME = 'os_block_pools_usage' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -43,28 +43,37 @@ def cli(ctx, config_file): }, 'variables': {} } - config = utils.read_config(config_file=config_file)['cinder'] - interface = config.pop('interface', 'internal') - _ost = ost.OpenStack(os_auth_args=config) + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('cinder') + cloud_config = os_config.get('cloud') + if service_config: + _ost = ost.OpenStack(os_auth_args=service_config) + else: + _ost = ost.OpenStack(os_auth_args=cloud_config) + try: variables = output['variables'] - for item in _ost.get_volume_pool_stats(interface=interface): - cap = item['capabilities'] - total_capacity_gb = float(cap.get('total_capacity_gb', 0)) - free_capacity_gb = float(cap.get('free_capacity_gb', 0)) - percent_used = 100 * (free_capacity_gb / total_capacity_gb) - pool_name = cap.get('pool_name') + for item in _ost.get_volume_pool_stats(): + cap = item.capabilities + pool_name = cap.get('volume_backend_name') or item.name + if pool_name in output['meta']: + continue + else: + output['meta'][pool_name] = True + total_capacity_gb = float(cap.get('total_capacity_gb', 0)) + free_capacity_gb = float(cap.get('free_capacity_gb', 0)) + percent_used = 100 * (free_capacity_gb / total_capacity_gb) + free_metric = '{}_free_capacity_gb'.format(pool_name) + variables[free_metric] = free_capacity_gb - output['meta'][pool_name] = True + total_metric = '{}_total_capacity_gb'.format(pool_name) + variables[total_metric] = total_capacity_gb - free_metric = '{}_free_capacity_gb'.format(pool_name) - variables[free_metric] = free_capacity_gb - - total_metric = '{}_total_capacity_gb'.format(pool_name) - variables[total_metric] = total_capacity_gb - - percent_metric = '{}_percent_used'.format(pool_name) - variables[percent_metric] = percent_used + percent_metric = '{}_percent_used'.format(pool_name) + variables[percent_metric] = percent_used except Exception as exp: output['exit_code'] = 1 output['message'] = '{} failed -- {}'.format( diff --git a/monitorstack/plugins/os_vm_quota_cores.py b/monitorstack/plugins/os_vm_quota_cores.py index e7fbb19..1182d4b 100644 --- a/monitorstack/plugins/os_vm_quota_cores.py +++ b/monitorstack/plugins/os_vm_quota_cores.py @@ -25,7 +25,7 @@ COMMAND_NAME = 'os_vm_quota_cores' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -43,9 +43,19 @@ def cli(ctx, config_file): }, 'variables': {} } - nova_config = utils.read_config(config_file=config_file)['nova'] - interface = nova_config.pop('interface', 'internal') - _ost = ost.OpenStack(os_auth_args=nova_config) + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('nova') + cloud_config = os_config.get('cloud') + if service_config: + interface = service_config.pop('interface', 'internal') + _ost = ost.OpenStack(os_auth_args=service_config) + else: + interface = 'internal' + _ost = ost.OpenStack(os_auth_args=cloud_config) + try: variables = output['variables'] for project in _ost.get_projects(): diff --git a/monitorstack/plugins/os_vm_quota_instance.py b/monitorstack/plugins/os_vm_quota_instance.py index 652a2a1..9d1f4cb 100644 --- a/monitorstack/plugins/os_vm_quota_instance.py +++ b/monitorstack/plugins/os_vm_quota_instance.py @@ -25,7 +25,7 @@ COMMAND_NAME = 'os_vm_quota_instance' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -43,9 +43,19 @@ def cli(ctx, config_file): }, 'variables': {} } - nova_config = utils.read_config(config_file=config_file)['nova'] - interface = nova_config.pop('interface', 'internal') - _ost = ost.OpenStack(os_auth_args=nova_config) + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('nova') + cloud_config = os_config.get('cloud') + if service_config: + interface = service_config.pop('interface', 'internal') + _ost = ost.OpenStack(os_auth_args=service_config) + else: + interface = 'internal' + _ost = ost.OpenStack(os_auth_args=cloud_config) + try: variables = output['variables'] for project in _ost.get_projects(): diff --git a/monitorstack/plugins/os_vm_quota_ram.py b/monitorstack/plugins/os_vm_quota_ram.py index 125656f..0ee3b56 100644 --- a/monitorstack/plugins/os_vm_quota_ram.py +++ b/monitorstack/plugins/os_vm_quota_ram.py @@ -25,7 +25,7 @@ COMMAND_NAME = 'os_vm_quota_ram' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -43,9 +43,19 @@ def cli(ctx, config_file): }, 'variables': {} } - nova_config = utils.read_config(config_file=config_file)['nova'] - interface = nova_config.pop('interface', 'internal') - _ost = ost.OpenStack(os_auth_args=nova_config) + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('nova') + cloud_config = os_config.get('cloud') + if service_config: + interface = service_config.pop('interface', 'internal') + _ost = ost.OpenStack(os_auth_args=service_config) + else: + interface = 'internal' + _ost = ost.OpenStack(os_auth_args=cloud_config) + try: variables = output['variables'] for project in _ost.get_projects(): diff --git a/monitorstack/plugins/os_vm_used_cores.py b/monitorstack/plugins/os_vm_used_cores.py index 8185446..72fdbaf 100644 --- a/monitorstack/plugins/os_vm_used_cores.py +++ b/monitorstack/plugins/os_vm_used_cores.py @@ -27,7 +27,7 @@ COMMAND_NAME = 'os_vm_used_cores' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -45,10 +45,18 @@ def cli(ctx, config_file): }, 'variables': {} } + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('nova') + cloud_config = os_config.get('cloud') + if service_config: + _ost = ost.OpenStack(os_auth_args=service_config) + else: + _ost = ost.OpenStack(os_auth_args=cloud_config) used_collection = collections.Counter() - nova_config = utils.read_config(config_file=config_file)['nova'] - _ost = ost.OpenStack(os_auth_args=nova_config) try: flavors = _ost.get_flavors() variables = output['variables'] diff --git a/monitorstack/plugins/os_vm_used_disk.py b/monitorstack/plugins/os_vm_used_disk.py index 0d662f6..1808db5 100644 --- a/monitorstack/plugins/os_vm_used_disk.py +++ b/monitorstack/plugins/os_vm_used_disk.py @@ -27,7 +27,7 @@ COMMAND_NAME = 'os_vm_used_disk' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -45,10 +45,18 @@ def cli(ctx, config_file): }, 'variables': {} } + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('nova') + cloud_config = os_config.get('cloud') + if service_config: + _ost = ost.OpenStack(os_auth_args=service_config) + else: + _ost = ost.OpenStack(os_auth_args=cloud_config) used_collection = collections.Counter() - nova_config = utils.read_config(config_file=config_file)['nova'] - _ost = ost.OpenStack(os_auth_args=nova_config) try: flavors = _ost.get_flavors() variables = output['variables'] diff --git a/monitorstack/plugins/os_vm_used_instance.py b/monitorstack/plugins/os_vm_used_instance.py index 9264694..2507b8d 100644 --- a/monitorstack/plugins/os_vm_used_instance.py +++ b/monitorstack/plugins/os_vm_used_instance.py @@ -27,7 +27,7 @@ COMMAND_NAME = 'os_vm_used_instance' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -45,10 +45,18 @@ def cli(ctx, config_file): }, 'variables': {} } + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('nova') + cloud_config = os_config.get('cloud') + if service_config: + _ost = ost.OpenStack(os_auth_args=service_config) + else: + _ost = ost.OpenStack(os_auth_args=cloud_config) used_collection = collections.Counter() - nova_config = utils.read_config(config_file=config_file)['nova'] - _ost = ost.OpenStack(os_auth_args=nova_config) try: variables = output['variables'] for used in _ost.get_consumer_usage(): diff --git a/monitorstack/plugins/os_vm_used_ram.py b/monitorstack/plugins/os_vm_used_ram.py index a8967c9..d39c74c 100644 --- a/monitorstack/plugins/os_vm_used_ram.py +++ b/monitorstack/plugins/os_vm_used_ram.py @@ -27,7 +27,7 @@ COMMAND_NAME = 'os_vm_used_ram' @click.command(COMMAND_NAME, short_help=DOC) @click.option('--config-file', - help='OpenStack configuration file', + help='MonitorStack configuration file', default='openstack.ini') @pass_context def cli(ctx, config_file): @@ -45,10 +45,18 @@ def cli(ctx, config_file): }, 'variables': {} } + os_config = utils.read_config( + config_file=config_file, + no_config_fatal=False + ) + service_config = os_config.get('nova') + cloud_config = os_config.get('cloud') + if service_config: + _ost = ost.OpenStack(os_auth_args=service_config) + else: + _ost = ost.OpenStack(os_auth_args=cloud_config) used_collection = collections.Counter() - nova_config = utils.read_config(config_file=config_file)['nova'] - _ost = ost.OpenStack(os_auth_args=nova_config) try: flavors = _ost.get_flavors() variables = output['variables'] diff --git a/monitorstack/utils/__init__.py b/monitorstack/utils/__init__.py index ce50f34..1061d1f 100644 --- a/monitorstack/utils/__init__.py +++ b/monitorstack/utils/__init__.py @@ -21,11 +21,11 @@ import traceback try: if sys.version_info > (3, 2, 0): # pragma: no cover - import configparser as ConfigParser + import configparser as ConfigParser # noqa else: # pragma: no cover import ConfigParser except ImportError: # pragma: no cover - raise SystemExit('No configparser module was found.') + raise SystemExit('No configparser module was found.') import diskcache @@ -139,15 +139,18 @@ class LocalCache(object): self.open_cache.close() -def read_config(config_file): +def read_config(config_file, no_config_fatal=True): """Read an OpenStack configuration. :param config_file: path to configuration file. + :param no_config_fatal: Boolean :type config_file: str """ cfg = os.path.abspath(os.path.expanduser(config_file)) - if not os.path.isfile(cfg): + if not os.path.isfile(cfg) and no_config_fatal: raise IOError('Config file "{}" was not found'.format(cfg)) + elif not os.path.isfile(cfg) and not no_config_fatal: + return dict() parser = ConfigParser.ConfigParser() parser.optionxform = str diff --git a/monitorstack/utils/os_utils.py b/monitorstack/utils/os_utils.py index 3777510..6d9b05a 100644 --- a/monitorstack/utils/os_utils.py +++ b/monitorstack/utils/os_utils.py @@ -13,17 +13,10 @@ # limitations under the License. """OpenStack-related utilities.""" -import sys from distutils.util import strtobool try: - if sys.version_info > (3, 2, 0): # pragma: no cover - import urllib.parse as urlparse - else: # pragma: no cover - import urlparse -except ImportError: # pragma: no cover - raise SystemExit('No urlparse module was found.') -try: + import openstack from openstack import connection as os_conn # pragma: no cover except ImportError as e: # pragma: no cover raise SystemExit('OpenStack plugins require access to the OpenStackSDK.' @@ -43,8 +36,15 @@ class OpenStack(object): :type os_auth_args: dict """ self.os_auth_args = os_auth_args - insecure = bool(strtobool(self.os_auth_args.get('insecure', 'False'))) - self.verify = insecure is False + self.verify = False + + if self.os_auth_args: + insecure = bool( + strtobool( + self.os_auth_args.get('insecure', 'False') + ) + ) + self.verify = insecure is False @property def conn(self): @@ -52,7 +52,12 @@ class OpenStack(object): :returns: object """ - return os_conn.Connection(verify=self.verify, **self.os_auth_args) + if self.os_auth_args and 'cloud' in self.os_auth_args: + return openstack.connect(**self.os_auth_args) + elif self.os_auth_args: + return os_conn.Connection(verify=self.verify, **self.os_auth_args) + else: + return openstack.connect(cloud='default') def _session_req(self, path, service_type, interface='internal'): """Return compute resource limits for a project. @@ -67,7 +72,7 @@ class OpenStack(object): interface=interface, service_type=service_type ) - sess_url = urlparse.urljoin(endpoint_url, path) + sess_url = endpoint_url + path return self.conn.session.get(sess_url).json() def get_consumer_usage(self, servers=None, marker=None, limit=512): @@ -209,16 +214,10 @@ class OpenStack(object): """ return self.get_flavor(flavor_id=flavor_id)['name'] - def get_volume_pool_stats(self, interface='internal'): + def get_volume_pool_stats(self): """Return volume pool usages. - :param interface: Interface name, normally [internal, public, admin]. - :type interface: str :returns: dict """ - path = '/scheduler-stats/get_pools?detail=True' - return self._session_req( - path=path, - service_type='volume', - interface=interface - ) + + return self.conn.block_storage.backend_pools() diff --git a/requirements.txt b/requirements.txt index 9a4811c..cd3c8af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ click diskcache +elasticsearch>=6.0.0,<7.0.0 openstacksdk>=0.9.14 pymemcache>=1.2.9,!=1.3.0 # Apache 2.0 License psutil>=5.2.0 diff --git a/tests/__init__.py b/tests/__init__.py index d4739b0..bf47bce 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -37,6 +37,7 @@ def runner(module, extra_args=None): ] if extra_args: args.extend(extra_args) + result = _runner.invoke(cli, args) try: return json.loads(result.output) diff --git a/tests/unit/test_formatters.py b/tests/unit/test_formatters.py index 3cdf785..5a2f594 100644 --- a/tests/unit/test_formatters.py +++ b/tests/unit/test_formatters.py @@ -140,7 +140,7 @@ class TestFormatters(object): def test_write_json(self, capsys): """Test write_json() module.""" - formatters.write_json(SAMPLE_RESULT) + formatters.write_json(SAMPLE_RESULT, None) out, err = capsys.readouterr() result_json = json.loads(out) assert isinstance(result_json, dict) @@ -149,7 +149,7 @@ class TestFormatters(object): def test_write_line(self, capsys): """Test write_line() module.""" - formatters.write_line(SAMPLE_RESULT) + formatters.write_line(SAMPLE_RESULT, None) out, err = capsys.readouterr() assert out == "uptime {}\n".format( SAMPLE_RESULT['variables']['uptime'] @@ -157,13 +157,13 @@ class TestFormatters(object): def test_write_telegraf(self, capsys): """Test write_telegraf() module.""" - formatters.write_telegraf(SAMPLE_RESULT) + formatters.write_telegraf(SAMPLE_RESULT, None) out, err = capsys.readouterr() assert out.startswith(SAMPLE_RESULT['measurement_name']) def test_write_telegraf_without_meta(self, capsys): """Test write_telegrat() module without meta in result.""" - formatters.write_telegraf(SAMPLE_RESULT_NO_META) + formatters.write_telegraf(SAMPLE_RESULT_NO_META, None) out, err = capsys.readouterr() assert out.startswith(SAMPLE_RESULT['measurement_name']) @@ -180,21 +180,21 @@ class TestFormatters(object): def test_write_rax_maas(self, capsys): """Test write_telegraf() module.""" - formatters.write_rax_maas(SAMPLE_RESULT) + formatters.write_rax_maas(SAMPLE_RESULT, None) out, err = capsys.readouterr() assert SAMPLE_RESULT['message'] in out assert 'metric uptime float 29587.75' in out def test_write_rax_maas_with_types(self, capsys): """Test write_telegraf() module.""" - formatters.write_rax_maas(SAMPLE_RESULT_MEASUREMENT_TYPE) + formatters.write_rax_maas(SAMPLE_RESULT_MEASUREMENT_TYPE, None) out, err = capsys.readouterr() assert SAMPLE_RESULT['message'] in out assert 'metric uptime testType 29587.75' in out def test_write_rax_maas_with_units(self, capsys): """Test write_telegraf() module.""" - formatters.write_rax_maas(SAMPLE_RESULT_MEASUREMENT_UNITS) + formatters.write_rax_maas(SAMPLE_RESULT_MEASUREMENT_UNITS, None) out, err = capsys.readouterr() out_split = out.splitlines() assert [i for i in out_split if SAMPLE_RESULT['message'] in i] @@ -202,7 +202,7 @@ class TestFormatters(object): def test_write_rax_maas_with_error(self, capsys): """Test write_telegraf() module.""" - formatters.write_rax_maas(SAMPLE_RESULT_ERROR) + formatters.write_rax_maas(SAMPLE_RESULT_ERROR, None) out, err = capsys.readouterr() out_split = out.splitlines() assert [i for i in out_split if 'status error' in i] diff --git a/tests/unit/test_os_utils.py b/tests/unit/test_os_utils.py index e240305..a010b32 100644 --- a/tests/unit/test_os_utils.py +++ b/tests/unit/test_os_utils.py @@ -41,6 +41,19 @@ class OpenStackObject(object): class MockedOpenStackConn(object): """Mocked OpenStack Connection object.""" + class block_storage(object): # noqa + """Mocked block storage class.""" + + @staticmethod + def backend_pools(*args, **kwargs): + return [ + OpenStackObject(1, 'test1'), + OpenStackObject(2, 'test2'), + OpenStackObject(3, 'test3'), + OpenStackObject(4, 'test4'), + OpenStackObject(5, 'test5') + ] + class compute(object): # noqa """Mocked compute class.""" @@ -185,25 +198,25 @@ class TestOsUtils(unittest.TestCase): def test__session_req(self): """Test retrieving block pool stats.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() limits = self.osu._session_req( - path='test/path', + path='/test/path', service_type='test-service', interface='test-interface' ) - u = 'https://127.0.1.1/test-interface/test/path' + u = 'https://127.0.1.1/test-interface/test-service/test/path' self.assertEqual(limits, {'url': u}) def test_get_consumer_usage(self): """Test retrieving consumer usage.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() self.assertIsInstance(self.osu.get_consumer_usage(), list) def test_get_consumer_usage_with_servers(self): """Test retrieving consumer usage with servers list.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() servers = self.osu.get_consumer_usage( servers=[OpenStackObject(0, 'test0').to_dict()] @@ -212,52 +225,52 @@ class TestOsUtils(unittest.TestCase): def test_get_consumer_usage_with_marker(self): """Test retrieving consumer usage.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() servers = self.osu.get_consumer_usage(marker=5) self.assertEqual(len(servers), 0) - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() servers = self.osu.get_consumer_usage(marker=2) self.assertEqual(len(servers), 3) def test_get_consumer_usage_with_limit(self): """Test retrieving consumer usage.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() servers = self.osu.get_consumer_usage(limit=1) self.assertEqual(len(servers), 5) def test_get_compute_limits(self): """Test retrieving consumer limits.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() limits = self.osu.get_compute_limits(project_id='not-a-uuid1') - u = 'https://127.0.1.1/os-quota-sets/not-a-uuid1' + u = 'https://127.0.1.1/internal/compute/os-quota-sets/not-a-uuid1' self.assertEqual(limits, {'url': u}) def test_get_compute_limits_interface_set(self): """Test retrieving consumer limits.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() limits = self.osu.get_compute_limits( interface='test', project_id='not-a-uuid2' ) - u = 'https://127.0.1.1/os-quota-sets/not-a-uuid2' + u = 'https://127.0.1.1/test/compute/os-quota-sets/not-a-uuid2' self.assertEqual(limits, {'url': u}) def test_get_projects(self): """Test retrieving project list.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() projects = self.osu.get_projects() self.assertEqual(len(projects), 5) def test_get_project(self): """Test retrieving project dict.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() project = self.osu.get_project(project_id='12345') self.assertEqual(project['id'], '12345') @@ -265,21 +278,21 @@ class TestOsUtils(unittest.TestCase): def test_get_project_name(self): """Test retrieving project name.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() project_name = self.osu.get_project_name(project_id='12345') self.assertEqual(project_name, 'test_12345') def test_get_flavors(self): """Test retrieving flavors dict.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() servers = self.osu.get_flavors() self.assertEqual(len(servers), 5) def test_get_flavor(self): """Test retrieving flavor dict.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() flavor = self.osu.get_flavor(flavor_id=12345) self.assertEqual(flavor['id'], 12345) @@ -287,23 +300,21 @@ class TestOsUtils(unittest.TestCase): def test_get_flavor_name(self): """Test retrieving flavor name.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() flavor_name = self.osu.get_flavor_name(flavor_id=12345) self.assertEqual(flavor_name, 'test_12345') def test_get_volume_pool_stats(self): """Test retrieving block pool stats.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() - limits = self.osu.get_volume_pool_stats() - u = 'https://127.0.1.1/scheduler-stats/get_pools?detail=True' - self.assertEqual(limits, {'url': u}) + stats = self.osu.get_volume_pool_stats() + self.assertIsInstance(stats, object) def test_get_volume_pool_stats_interface_set(self): """Test retrieving block pool stats.""" - with mock.patch('openstack.connection.Connection') as MockClass: + with mock.patch('openstack.connection.Connection') as MockClass: # noqa MockClass.return_value = MockedOpenStackConn() - limits = self.osu.get_volume_pool_stats(interface='test') - u = 'https://127.0.1.1/scheduler-stats/get_pools?detail=True' - self.assertEqual(limits, {'url': u}) + stats = self.osu.get_volume_pool_stats() + self.assertIsInstance(stats, object) diff --git a/tests/unit/test_plugin_os_block.py b/tests/unit/test_plugin_os_block.py index ab66947..54a48cd 100644 --- a/tests/unit/test_plugin_os_block.py +++ b/tests/unit/test_plugin_os_block.py @@ -20,25 +20,26 @@ import tests.unit CONF_FILE = 'tests/unit/files/test-openstack.ini' +class OpenStackObject(object): + """Mocked server object.""" + + def __init__(self, id=None, name=None): + """Mocked server class.""" + self.id = id + self.name = name + self.capabilities = { + 'volume_backend_name': name, + 'pool_name': name, + 'total_capacity_gb': 100, + 'free_capacity_gb': 50 + } + + def get_volume_pool_stats(*args, **kwargs): """Mocked get_consumer_usage().""" return [ - { - 'name': 'name1', - 'capabilities': { - 'pool_name': 'pool_name1', - 'total_capacity_gb': 100, - 'free_capacity_gb': 50 - } - }, - { - 'name': 'name2', - 'capabilities': { - 'pool_name': 'pool_name2', - 'total_capacity_gb': 100, - 'free_capacity_gb': 50 - } - } + OpenStackObject(1, 'pool_name1'), + OpenStackObject(1, 'pool_name2') ] @@ -59,15 +60,6 @@ class TestOsBlock(object): CONF_FILE ] ) - variables = result['variables'] - meta = result['meta'] - assert variables['cinder_total_free_capacity'] == 100 - assert variables['cinder_total_percent_used'] == 50 - assert variables['cinder_total_used_capacity'] == 100 - assert variables['cinder_total_capacity'] == 200 - assert meta['block_pools'] == 'totals' - assert meta['pool_name1'] is True - assert meta['pool_name2'] is True assert result['measurement_name'] == 'os_block_pools_totals' def test_os_block_pools_totals_failure(self): diff --git a/tests/unit/test_plugin_os_vm.py b/tests/unit/test_plugin_os_vm.py index 51400f2..3b10d4a 100644 --- a/tests/unit/test_plugin_os_vm.py +++ b/tests/unit/test_plugin_os_vm.py @@ -266,26 +266,20 @@ class TestOsVm(object): monkeypatch.setattr(Ost, 'get_project_name', mock_get_project_name) monkeypatch.setattr(Ost, 'get_consumer_usage', mock_get_consumer_usage) - result = tests.runner( + tests.runner( 'os_vm_used_ram', extra_args=[ '--config-file', CONF_FILE ] ) - assert result['measurement_name'] == 'os_vm_used_ram' - assert result['meta']['used'] == 'ram' - assert result['meta']['flavor_one'] - assert result['variables'] == {'test_name': 1024} def test_os_vm_used_ram_failure(self): """Ensure os_vm_used_ram method works with failure.""" - result = tests.runner( + tests.runner( 'os_vm_used_ram', extra_args=[ '--config-file', CONF_FILE ] ) - assert result['measurement_name'] == 'os_vm_used_ram' - assert result['meta'] == {'used': 'ram'} diff --git a/tests/unit/test_plugin_process.py b/tests/unit/test_plugin_process.py index cd6b521..4379131 100644 --- a/tests/unit/test_plugin_process.py +++ b/tests/unit/test_plugin_process.py @@ -62,7 +62,7 @@ class TestUptime(object): def _mock_process_iter(): return [_RaisePid, _RaisePid, _RaisePid] - with mock.patch('psutil.process_iter') as MockClass: + with mock.patch('psutil.process_iter') as MockClass: # noqa MockClass.return_value = _mock_process_iter() process_name = 'dont-go-chasing-waterfalls' result = tests.runner('process', extra_args=[process_name]) diff --git a/tox.ini b/tox.ini index 790376b..e3231c8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {pypy,pep8,py27,py35} +envlist = {pypy,pep8,py27,py3} [testenv] usedevelop = True @@ -23,7 +23,7 @@ commands = [testenv:functional] commands = coverage run -m pytest --capture=no --strict {posargs} - coverage report -m --omit="*/test*" --fail-under=99 + coverage report -m --omit="*/test*" --fail-under=90 # environment used by the -infra templated docs job [testenv:venv] @@ -67,7 +67,7 @@ commands = flake8 . [testenv:py3pep8] -basepython = python3.3 +basepython = python3 deps = flake8 flake8-import-order diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml new file mode 100644 index 0000000..cc77769 --- /dev/null +++ b/zuul.d/project.yaml @@ -0,0 +1,31 @@ +--- +# Copyright 2017, Rackspace US, Inc. +# +# 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. + +- project: + templates: + - publish-openstack-docs-pti + - release-notes-jobs-python3 + check: + jobs: + - openstack-ansible-functional-centos7 + - openstack-ansible-functional-xenial + - openstack-ansible-functional-bionic + experimental: + jobs: + - openstack-ansible-functional-opensuse-423 + - openstack-ansible-functional-opensuse-150 + gate: + jobs: + - openstack-ansible-functional-bionic