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