Added VM instances used and quotas (#6)
The following plugins add a basic OpenStack API framework which is used to pull metrics for nova vm quotas and usage for RAM, vCPUs, disk, and instance count. added codecov.yml Signed-off-by: Kevin Carter <kevin.carter@rackspace.com>
This commit is contained in:

committed by
Major Hayden

parent
f943e47f70
commit
37a9950ab2
1
AUTHORS
1
AUTHORS
@@ -1,2 +1,3 @@
|
|||||||
Kevin Carter <kcarter@linux.com>
|
Kevin Carter <kcarter@linux.com>
|
||||||
|
Kevin Carter <kevin.carter@rackspace.com>
|
||||||
Major Hayden <major@mhtx.net>
|
Major Hayden <major@mhtx.net>
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
CHANGES
|
CHANGES
|
||||||
=======
|
=======
|
||||||
|
|
||||||
* Improve test coverage
|
* Added VM instances used and quotas
|
||||||
|
* Improve test coverage (#4)
|
||||||
* Remove sys from uptime test (#3)
|
* Remove sys from uptime test (#3)
|
||||||
* Increase test coverage for uptime
|
* Increase test coverage for uptime
|
||||||
* Added KVM Metric plugin (#2)
|
* Added KVM Metric plugin (#2)
|
||||||
|
4
codecov.yml
Normal file
4
codecov.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
coverage:
|
||||||
|
round: down
|
||||||
|
range: 70..80
|
||||||
|
precision: 5
|
47
etc/openstack.ini
Normal file
47
etc/openstack.ini
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Store the authentication credentials needed to query a given OpenStack Service.
|
||||||
|
# All sections are overrides for the defaults. If you only need to connect to a
|
||||||
|
# single cloud simply store the credentials needd in the DEFAULT section and
|
||||||
|
# override whatever is needed within the local sections.
|
||||||
|
|
||||||
|
[DEFAULT]
|
||||||
|
insecure = false
|
||||||
|
auth_url = https://example.com:5000/v3
|
||||||
|
|
||||||
|
# NOTE(cloudnull):
|
||||||
|
# When using keystone V3 you will need the .*domain_name configuration options.
|
||||||
|
user_domain_name = default # This is required when Keystone V3 is being used
|
||||||
|
project_domain_name = default # This is required when Keystone V3 is being used
|
||||||
|
|
||||||
|
# If you're using keystone V2 you will need the tenant_name option.
|
||||||
|
tenant_name = admin # This is required when Keystone V2 is being used
|
||||||
|
|
||||||
|
# NEVER Mix and match the options tenant name and domain_name options.
|
||||||
|
# You are be required to run either V2 or V3 as it pertains to this config.
|
||||||
|
# If you provide both tenant_name and .*domain_name options at the same time
|
||||||
|
# the plugins will fail API version negotiation.
|
||||||
|
|
||||||
|
username = admin
|
||||||
|
password = Secrete
|
||||||
|
# The verify option is for SSL. If your SSL certificate is not
|
||||||
|
# valid set this option to false else omit it or set it true.
|
||||||
|
verify = false
|
||||||
|
|
||||||
|
[keystone]
|
||||||
|
|
||||||
|
[glance]
|
||||||
|
|
||||||
|
[nova]
|
||||||
|
project_name = nova
|
||||||
|
|
||||||
|
[neutron]
|
||||||
|
|
||||||
|
[heat]
|
||||||
|
|
||||||
|
[cinder]
|
||||||
|
|
||||||
|
[ironic]
|
||||||
|
auth_url = https://example2.com:5000/v3
|
||||||
|
project_name = ironic
|
||||||
|
user_domain_name = users
|
||||||
|
project_domain_name = projects
|
||||||
|
password = SuperSecrete
|
@@ -51,6 +51,7 @@ class MonitorStackCLI(click.MultiCommand):
|
|||||||
@property
|
@property
|
||||||
def cmd_folder(self):
|
def cmd_folder(self):
|
||||||
"""Get the path to the plugin directory."""
|
"""Get the path to the plugin directory."""
|
||||||
|
|
||||||
return os.path.abspath(
|
return os.path.abspath(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
@@ -60,6 +61,7 @@ class MonitorStackCLI(click.MultiCommand):
|
|||||||
|
|
||||||
def list_commands(self, ctx):
|
def list_commands(self, ctx):
|
||||||
"""Get a list of all available commands."""
|
"""Get a list of all available commands."""
|
||||||
|
|
||||||
rv = list()
|
rv = list()
|
||||||
for _, pkg_name, _ in pkgutil.iter_modules([self.cmd_folder]):
|
for _, pkg_name, _ in pkgutil.iter_modules([self.cmd_folder]):
|
||||||
rv.append(pkg_name)
|
rv.append(pkg_name)
|
||||||
@@ -68,6 +70,7 @@ class MonitorStackCLI(click.MultiCommand):
|
|||||||
|
|
||||||
def get_command(self, ctx, name):
|
def get_command(self, ctx, name):
|
||||||
"""Load a command and run it."""
|
"""Load a command and run it."""
|
||||||
|
|
||||||
for _, pkg_name, _ in pkgutil.iter_modules([self.cmd_folder]):
|
for _, pkg_name, _ in pkgutil.iter_modules([self.cmd_folder]):
|
||||||
if pkg_name == name:
|
if pkg_name == name:
|
||||||
mod = importlib.import_module(
|
mod = importlib.import_module(
|
||||||
@@ -99,6 +102,7 @@ VALID_OUTPUT_FORMATS = [
|
|||||||
@pass_context
|
@pass_context
|
||||||
def cli(ctx, output_format, verbose):
|
def cli(ctx, output_format, verbose):
|
||||||
"""A complex command line interface."""
|
"""A complex command line interface."""
|
||||||
|
|
||||||
ctx.verbose = verbose
|
ctx.verbose = verbose
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -106,6 +110,7 @@ def cli(ctx, output_format, verbose):
|
|||||||
@cli.resultcallback(replace=True)
|
@cli.resultcallback(replace=True)
|
||||||
def process_result(result, output_format, verbose):
|
def process_result(result, output_format, verbose):
|
||||||
"""Render the output into the proper format."""
|
"""Render the output into the proper format."""
|
||||||
|
|
||||||
module_name = 'monitorstack.common.formatters'
|
module_name = 'monitorstack.common.formatters'
|
||||||
method_name = 'write_{}'.format(output_format)
|
method_name = 'write_{}'.format(output_format)
|
||||||
output_formatter = getattr(
|
output_formatter = getattr(
|
||||||
|
@@ -18,13 +18,9 @@ import time
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
|
|
||||||
def current_time():
|
|
||||||
"""Return the current time in nanoseconds."""
|
|
||||||
return int(time.time() * 1000000000)
|
|
||||||
|
|
||||||
|
|
||||||
def write_json(result):
|
def write_json(result):
|
||||||
"""Output in raw JSON format."""
|
"""Output in raw JSON format."""
|
||||||
|
|
||||||
output = json.dumps(result, indent=2)
|
output = json.dumps(result, indent=2)
|
||||||
click.echo(output)
|
click.echo(output)
|
||||||
return True
|
return True
|
||||||
@@ -32,36 +28,52 @@ def write_json(result):
|
|||||||
|
|
||||||
def write_line(result):
|
def write_line(result):
|
||||||
"""Output in line format."""
|
"""Output in line format."""
|
||||||
|
|
||||||
for key, value in result['variables'].items():
|
for key, value in result['variables'].items():
|
||||||
click.echo("{} {}".format(key, value))
|
click.echo("{} {}".format(key, value))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _current_time():
|
||||||
|
"""Return the current time in nanoseconds."""
|
||||||
|
|
||||||
|
return int(time.time() * 1000000000)
|
||||||
|
|
||||||
|
|
||||||
|
def _telegraf_line_format(sets, quote=False):
|
||||||
|
"""Return a comma separated string."""
|
||||||
|
|
||||||
|
store = list()
|
||||||
|
for k, v in sets.items():
|
||||||
|
k = k.replace(' ', '_')
|
||||||
|
for v_type in [int, float]:
|
||||||
|
try:
|
||||||
|
v = v_type(v)
|
||||||
|
except ValueError:
|
||||||
|
pass # v was not a int, float, or long
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if not isinstance(v, (int, float, bool)) and quote:
|
||||||
|
store.append('{}="{}"'.format(k, v))
|
||||||
|
else:
|
||||||
|
store.append('{}={}'.format(k, v))
|
||||||
|
return ','.join(store).rstrip(',')
|
||||||
|
|
||||||
|
|
||||||
def write_telegraf(result):
|
def write_telegraf(result):
|
||||||
"""Output in telegraf format."""
|
"""Output in telegraf format."""
|
||||||
def line_format(sets, quote=False):
|
|
||||||
store = list()
|
|
||||||
for k, v in sets.items():
|
|
||||||
k = k.replace(' ', '_')
|
|
||||||
for v_type in [int, float]:
|
|
||||||
try:
|
|
||||||
v = v_type(v)
|
|
||||||
except ValueError:
|
|
||||||
pass # v was not a int, float, or long
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if not isinstance(v, (int, float, bool)) and quote:
|
|
||||||
store.append('{}="{}"'.format(k, v))
|
|
||||||
else:
|
|
||||||
store.append('{}={}'.format(k, v))
|
|
||||||
return ','.join(store).rstrip(',')
|
|
||||||
|
|
||||||
resultant = [result['measurement_name']]
|
resultant = [result['measurement_name']]
|
||||||
if 'meta' in result:
|
if 'meta' in result:
|
||||||
resultant.append(line_format(sets=result['meta']))
|
resultant.append(_telegraf_line_format(sets=result['meta']))
|
||||||
resultant.append(line_format(sets=result['variables'], quote=True))
|
resultant.append(
|
||||||
resultant.append(str(current_time()))
|
_telegraf_line_format(
|
||||||
|
sets=result['variables'],
|
||||||
|
quote=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
resultant.append(str(_current_time()))
|
||||||
click.echo(' '.join(resultant))
|
click.echo(' '.join(resultant))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
59
monitorstack/plugins/os_vm_quota_cores.py
Normal file
59
monitorstack/plugins/os_vm_quota_cores.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 click
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
from monitorstack.cli import pass_context
|
||||||
|
from monitorstack.utils import os_utils as ost
|
||||||
|
|
||||||
|
|
||||||
|
DOC = """Get nova cores quotas."""
|
||||||
|
COMMAND_NAME = 'os_vm_quota_cores'
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(COMMAND_NAME, short_help=DOC)
|
||||||
|
@click.option('--config-file',
|
||||||
|
help='OpenStack configuration file',
|
||||||
|
default='openstack.ini')
|
||||||
|
@pass_context
|
||||||
|
def cli(ctx, config_file):
|
||||||
|
setattr(cli, '__doc__', DOC)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'measurement_name': COMMAND_NAME,
|
||||||
|
'meta': {
|
||||||
|
'quotas': 'cores'
|
||||||
|
},
|
||||||
|
'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)
|
||||||
|
try:
|
||||||
|
variables = output['variables']
|
||||||
|
for project in _ost.get_projects():
|
||||||
|
limits = _ost.get_compute_limits(
|
||||||
|
project_id=project.id,
|
||||||
|
interface=interface
|
||||||
|
)
|
||||||
|
variables[project.name] = int(limits['quota_set']['cores'])
|
||||||
|
except Exception as exp:
|
||||||
|
output['exit_code'] = 1
|
||||||
|
output['message'] = '{} failed -- Error: {}'.format(COMMAND_NAME, exp)
|
||||||
|
else:
|
||||||
|
output['exit_code'] = 0
|
||||||
|
output['message'] = '{} is ok'.format(COMMAND_NAME)
|
||||||
|
finally:
|
||||||
|
return output
|
59
monitorstack/plugins/os_vm_quota_instance.py
Normal file
59
monitorstack/plugins/os_vm_quota_instance.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 click
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
from monitorstack.cli import pass_context
|
||||||
|
from monitorstack.utils import os_utils as ost
|
||||||
|
|
||||||
|
|
||||||
|
DOC = """Get nova instance quotas."""
|
||||||
|
COMMAND_NAME = 'os_vm_quota_instance'
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(COMMAND_NAME, short_help=DOC)
|
||||||
|
@click.option('--config-file',
|
||||||
|
help='OpenStack configuration file',
|
||||||
|
default='openstack.ini')
|
||||||
|
@pass_context
|
||||||
|
def cli(ctx, config_file):
|
||||||
|
setattr(cli, '__doc__', DOC)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'measurement_name': COMMAND_NAME,
|
||||||
|
'meta': {
|
||||||
|
'quotas': 'instances'
|
||||||
|
},
|
||||||
|
'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)
|
||||||
|
try:
|
||||||
|
variables = output['variables']
|
||||||
|
for project in _ost.get_projects():
|
||||||
|
limits = _ost.get_compute_limits(
|
||||||
|
project_id=project.id,
|
||||||
|
interface=interface
|
||||||
|
)
|
||||||
|
variables[project.name] = int(limits['quota_set']['instances'])
|
||||||
|
except Exception as exp:
|
||||||
|
output['exit_code'] = 1
|
||||||
|
output['message'] = '{} failed -- Error: {}'.format(COMMAND_NAME, exp)
|
||||||
|
else:
|
||||||
|
output['exit_code'] = 0
|
||||||
|
output['message'] = '{} is ok'.format(COMMAND_NAME)
|
||||||
|
finally:
|
||||||
|
return output
|
59
monitorstack/plugins/os_vm_quota_ram.py
Normal file
59
monitorstack/plugins/os_vm_quota_ram.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 click
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
from monitorstack.cli import pass_context
|
||||||
|
from monitorstack.utils import os_utils as ost
|
||||||
|
|
||||||
|
|
||||||
|
DOC = """Get nova ram quotas."""
|
||||||
|
COMMAND_NAME = 'os_vm_quota_ram'
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(COMMAND_NAME, short_help=DOC)
|
||||||
|
@click.option('--config-file',
|
||||||
|
help='OpenStack configuration file',
|
||||||
|
default='openstack.ini')
|
||||||
|
@pass_context
|
||||||
|
def cli(ctx, config_file):
|
||||||
|
setattr(cli, '__doc__', DOC)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'measurement_name': COMMAND_NAME,
|
||||||
|
'meta': {
|
||||||
|
'quotas': 'ram'
|
||||||
|
},
|
||||||
|
'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)
|
||||||
|
try:
|
||||||
|
variables = output['variables']
|
||||||
|
for project in _ost.get_projects():
|
||||||
|
limits = _ost.get_compute_limits(
|
||||||
|
project_id=project.id,
|
||||||
|
interface=interface
|
||||||
|
)
|
||||||
|
variables[project.name] = int(limits['quota_set']['ram'])
|
||||||
|
except Exception as exp:
|
||||||
|
output['exit_code'] = 1
|
||||||
|
output['message'] = '{} failed -- Error: {}'.format(COMMAND_NAME, exp)
|
||||||
|
else:
|
||||||
|
output['exit_code'] = 0
|
||||||
|
output['message'] = '{} is ok'.format(COMMAND_NAME)
|
||||||
|
finally:
|
||||||
|
return output
|
63
monitorstack/plugins/os_vm_used_cores.py
Normal file
63
monitorstack/plugins/os_vm_used_cores.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 collections
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
from monitorstack.cli import pass_context
|
||||||
|
from monitorstack.utils import os_utils as ost
|
||||||
|
|
||||||
|
|
||||||
|
DOC = """Get nova used cores."""
|
||||||
|
COMMAND_NAME = 'os_vm_used_cores'
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(COMMAND_NAME, short_help=DOC)
|
||||||
|
@click.option('--config-file',
|
||||||
|
help='OpenStack configuration file',
|
||||||
|
default='openstack.ini')
|
||||||
|
@pass_context
|
||||||
|
def cli(ctx, config_file):
|
||||||
|
setattr(cli, '__doc__', DOC)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'measurement_name': COMMAND_NAME,
|
||||||
|
'meta': {
|
||||||
|
'used': 'cores'
|
||||||
|
},
|
||||||
|
'variables': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
used_collection = collections.Counter()
|
||||||
|
nova_config = utils.read_config(config_file=config_file)['nova']
|
||||||
|
_ost = ost.OpenStack(os_auth_args=nova_config)
|
||||||
|
flavors = _ost.get_flavors()
|
||||||
|
try:
|
||||||
|
variables = output['variables']
|
||||||
|
for used in _ost.get_consumer_usage():
|
||||||
|
flavor = flavors[used['flavor']['id']]
|
||||||
|
used_collection[used['name']] += int(flavor['vcpus'])
|
||||||
|
output['meta'][used['flavor']['id']] = True
|
||||||
|
output['meta'][used['flavor']['name']] = True
|
||||||
|
variables.update(used_collection)
|
||||||
|
except Exception as exp:
|
||||||
|
output['exit_code'] = 1
|
||||||
|
output['message'] = '{} failed -- Error: {}'.format(COMMAND_NAME, exp)
|
||||||
|
else:
|
||||||
|
output['exit_code'] = 0
|
||||||
|
output['message'] = '{} is ok'.format(COMMAND_NAME)
|
||||||
|
finally:
|
||||||
|
return output
|
63
monitorstack/plugins/os_vm_used_disk.py
Normal file
63
monitorstack/plugins/os_vm_used_disk.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 collections
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
from monitorstack.cli import pass_context
|
||||||
|
from monitorstack.utils import os_utils as ost
|
||||||
|
|
||||||
|
|
||||||
|
DOC = """Get nova used disk."""
|
||||||
|
COMMAND_NAME = 'os_vm_used_disk'
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(COMMAND_NAME, short_help=DOC)
|
||||||
|
@click.option('--config-file',
|
||||||
|
help='OpenStack configuration file',
|
||||||
|
default='openstack.ini')
|
||||||
|
@pass_context
|
||||||
|
def cli(ctx, config_file):
|
||||||
|
setattr(cli, '__doc__', DOC)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'measurement_name': COMMAND_NAME,
|
||||||
|
'meta': {
|
||||||
|
'used': 'disk'
|
||||||
|
},
|
||||||
|
'variables': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
used_collection = collections.Counter()
|
||||||
|
nova_config = utils.read_config(config_file=config_file)['nova']
|
||||||
|
_ost = ost.OpenStack(os_auth_args=nova_config)
|
||||||
|
flavors = _ost.get_flavors()
|
||||||
|
try:
|
||||||
|
variables = output['variables']
|
||||||
|
for used in _ost.get_consumer_usage():
|
||||||
|
flavor = flavors[used['flavor']['id']]
|
||||||
|
used_collection[used['name']] += int(flavor['disk'])
|
||||||
|
output['meta'][used['flavor']['id']] = True
|
||||||
|
output['meta'][used['flavor']['name']] = True
|
||||||
|
variables.update(used_collection)
|
||||||
|
except Exception as exp:
|
||||||
|
output['exit_code'] = 1
|
||||||
|
output['message'] = '{} failed -- Error: {}'.format(COMMAND_NAME, exp)
|
||||||
|
else:
|
||||||
|
output['exit_code'] = 0
|
||||||
|
output['message'] = '{} is ok'.format(COMMAND_NAME)
|
||||||
|
finally:
|
||||||
|
return output
|
59
monitorstack/plugins/os_vm_used_instance.py
Normal file
59
monitorstack/plugins/os_vm_used_instance.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 collections
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
from monitorstack.cli import pass_context
|
||||||
|
from monitorstack.utils import os_utils as ost
|
||||||
|
|
||||||
|
|
||||||
|
DOC = """Get nova used instances."""
|
||||||
|
COMMAND_NAME = 'os_vm_used_instance'
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(COMMAND_NAME, short_help=DOC)
|
||||||
|
@click.option('--config-file',
|
||||||
|
help='OpenStack configuration file',
|
||||||
|
default='openstack.ini')
|
||||||
|
@pass_context
|
||||||
|
def cli(ctx, config_file):
|
||||||
|
setattr(cli, '__doc__', DOC)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'measurement_name': COMMAND_NAME,
|
||||||
|
'meta': {
|
||||||
|
'used': 'instances'
|
||||||
|
},
|
||||||
|
'variables': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
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():
|
||||||
|
used_collection[used['name']] += 1
|
||||||
|
variables.update(used_collection)
|
||||||
|
except Exception as exp:
|
||||||
|
output['exit_code'] = 1
|
||||||
|
output['message'] = '{} failed -- Error: {}'.format(COMMAND_NAME, exp)
|
||||||
|
else:
|
||||||
|
output['exit_code'] = 0
|
||||||
|
output['message'] = '{} is ok'.format(COMMAND_NAME)
|
||||||
|
finally:
|
||||||
|
return output
|
63
monitorstack/plugins/os_vm_used_ram.py
Normal file
63
monitorstack/plugins/os_vm_used_ram.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 collections
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
from monitorstack.cli import pass_context
|
||||||
|
from monitorstack.utils import os_utils as ost
|
||||||
|
|
||||||
|
|
||||||
|
DOC = """Get nova used ram."""
|
||||||
|
COMMAND_NAME = 'os_vm_used_ram'
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(COMMAND_NAME, short_help=DOC)
|
||||||
|
@click.option('--config-file',
|
||||||
|
help='OpenStack configuration file',
|
||||||
|
default='openstack.ini')
|
||||||
|
@pass_context
|
||||||
|
def cli(ctx, config_file):
|
||||||
|
setattr(cli, '__doc__', DOC)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'measurement_name': COMMAND_NAME,
|
||||||
|
'meta': {
|
||||||
|
'used': 'ram'
|
||||||
|
},
|
||||||
|
'variables': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
used_collection = collections.Counter()
|
||||||
|
nova_config = utils.read_config(config_file=config_file)['nova']
|
||||||
|
_ost = ost.OpenStack(os_auth_args=nova_config)
|
||||||
|
flavors = _ost.get_flavors()
|
||||||
|
try:
|
||||||
|
variables = output['variables']
|
||||||
|
for used in _ost.get_consumer_usage():
|
||||||
|
flavor = flavors[used['flavor']['id']]
|
||||||
|
used_collection[used['name']] += int(flavor['ram'])
|
||||||
|
output['meta'][used['flavor']['id']] = True
|
||||||
|
output['meta'][used['flavor']['name']] = True
|
||||||
|
variables.update(used_collection)
|
||||||
|
except Exception as exp:
|
||||||
|
output['exit_code'] = 1
|
||||||
|
output['message'] = '{} failed -- Error: {}'.format(COMMAND_NAME, exp)
|
||||||
|
else:
|
||||||
|
output['exit_code'] = 0
|
||||||
|
output['message'] = '{} is ok'.format(COMMAND_NAME)
|
||||||
|
finally:
|
||||||
|
return output
|
@@ -24,6 +24,7 @@ from monitorstack.cli import pass_context
|
|||||||
@pass_context
|
@pass_context
|
||||||
def cli(ctx):
|
def cli(ctx):
|
||||||
"""Get system uptime."""
|
"""Get system uptime."""
|
||||||
|
|
||||||
uptime = get_uptime()
|
uptime = get_uptime()
|
||||||
output = {
|
output = {
|
||||||
'exit_code': 0,
|
'exit_code': 0,
|
||||||
@@ -41,6 +42,7 @@ def cli(ctx):
|
|||||||
|
|
||||||
def get_uptime():
|
def get_uptime():
|
||||||
"""Read the uptime from the proc filesystem."""
|
"""Read the uptime from the proc filesystem."""
|
||||||
|
|
||||||
with open('/proc/uptime', 'r') as f:
|
with open('/proc/uptime', 'r') as f:
|
||||||
output = f.read()
|
output = f.read()
|
||||||
|
|
||||||
|
95
monitorstack/utils/__init__.py
Normal file
95
monitorstack/utils/__init__.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
import shelve
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
# Lower import to support conditional configuration parser
|
||||||
|
try:
|
||||||
|
if sys.version_info > (3, 2, 0):
|
||||||
|
import configparser as ConfigParser
|
||||||
|
else:
|
||||||
|
import ConfigParser
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit('No configparser module was found.')
|
||||||
|
|
||||||
|
|
||||||
|
def is_int(value):
|
||||||
|
for v_type in [int, float]:
|
||||||
|
try:
|
||||||
|
value = v_type(value)
|
||||||
|
except ValueError:
|
||||||
|
pass # v was not a int, float, or long
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class LocalCache(object):
|
||||||
|
"""Context Manager for opening and closing access to the DBM."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Set the Path to the DBM to create/Open."""
|
||||||
|
|
||||||
|
self.db_cache = os.path.join(
|
||||||
|
tempfile.gettempdir(),
|
||||||
|
'monitorstack.openstack.dbm'
|
||||||
|
)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""Open the DBM in r/w mode.
|
||||||
|
|
||||||
|
:return: Open DBM
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.open_shelve
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
"""Close DBM Connection."""
|
||||||
|
|
||||||
|
self.close_shelve()
|
||||||
|
|
||||||
|
def _open_shelve(self):
|
||||||
|
return shelve.open(self.db_cache)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open_shelve(self):
|
||||||
|
return self._open_shelve()
|
||||||
|
|
||||||
|
def close_shelve(self):
|
||||||
|
self.open_shelve.close()
|
||||||
|
|
||||||
|
|
||||||
|
def read_config(config_file):
|
||||||
|
cfg = os.path.abspath(os.path.expanduser(config_file))
|
||||||
|
if not os.path.isfile(cfg):
|
||||||
|
raise IOError('Config file "{}" was not found'.format(cfg))
|
||||||
|
|
||||||
|
parser = ConfigParser.ConfigParser()
|
||||||
|
parser.optionxform = str
|
||||||
|
parser.read([cfg])
|
||||||
|
args = dict()
|
||||||
|
defaults = dict([(k, v) for k, v in parser.items(section='DEFAULT')])
|
||||||
|
for section in parser.sections():
|
||||||
|
if section == 'DEFAULT':
|
||||||
|
continue
|
||||||
|
|
||||||
|
sec = args[section] = defaults
|
||||||
|
for key, value in parser.items(section):
|
||||||
|
sec[key] = is_int(value=value)
|
||||||
|
|
||||||
|
return args
|
90
monitorstack/utils/os_utils.py
Normal file
90
monitorstack/utils/os_utils.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
try:
|
||||||
|
from openstack import connection as os_conn
|
||||||
|
from openstack import exceptions as os_exp
|
||||||
|
except ImportError as e:
|
||||||
|
raise SystemExit('OpenStack plugins require access to the OpenStackSDK.'
|
||||||
|
' Please install "python-openstacksdk".'
|
||||||
|
' ERROR: %s' % str(e))
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
|
||||||
|
|
||||||
|
class OpenStack(object):
|
||||||
|
def __init__(self, os_auth_args):
|
||||||
|
self.os_auth_args = os_auth_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conn(self):
|
||||||
|
return os_conn.Connection(**self.os_auth_args)
|
||||||
|
|
||||||
|
def get_consumer_usage(self, servers=None, marker=None, limit=512):
|
||||||
|
tenant_kwargs = {'details': True, 'all_tenants': True, 'limit': limit}
|
||||||
|
if not servers:
|
||||||
|
servers = list()
|
||||||
|
|
||||||
|
if marker:
|
||||||
|
tenant_kwargs['marker'] = marker
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for server in self.conn.compute.servers(**tenant_kwargs):
|
||||||
|
servers.append(server)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if count == limit:
|
||||||
|
return self.get_consumer_usage(
|
||||||
|
servers=servers,
|
||||||
|
marker=servers[-1].id
|
||||||
|
)
|
||||||
|
|
||||||
|
return servers
|
||||||
|
|
||||||
|
def get_compute_limits(self, project_id, interface='internal'):
|
||||||
|
url = self.conn.compute.session.get_endpoint(
|
||||||
|
interface=interface,
|
||||||
|
service_type='compute'
|
||||||
|
)
|
||||||
|
quota_data = self.conn.compute.session.get(
|
||||||
|
url + '/os-quota-sets/' + project_id
|
||||||
|
)
|
||||||
|
return quota_data.json()
|
||||||
|
|
||||||
|
def get_project_name(self, project_id):
|
||||||
|
with utils.LocalCache() as c:
|
||||||
|
try:
|
||||||
|
project_name = c.get(project_id)
|
||||||
|
if not project_name:
|
||||||
|
project_info = self.conn.identity.get_project(project_id)
|
||||||
|
project_name = c[project_info.id] = project_info.name
|
||||||
|
except os_exp.ResourceNotFound:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return project_name
|
||||||
|
|
||||||
|
def get_projects(self):
|
||||||
|
_consumers = list()
|
||||||
|
with utils.LocalCache() as c:
|
||||||
|
for project in self.conn.identity.projects():
|
||||||
|
_consumers.append(project)
|
||||||
|
c[project.id] = project.name
|
||||||
|
return _consumers
|
||||||
|
|
||||||
|
def get_flavors(self):
|
||||||
|
flavor_cache = dict()
|
||||||
|
for flavor in self.conn.compute.flavors():
|
||||||
|
entry = flavor_cache[flavor['id']] = dict()
|
||||||
|
entry.update(flavor)
|
||||||
|
return flavor_cache
|
@@ -1,2 +1,3 @@
|
|||||||
pytest
|
pytest
|
||||||
tox
|
tox
|
||||||
|
mock>=2.0.0
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
@@ -35,7 +35,7 @@ class TestFormatters(object):
|
|||||||
|
|
||||||
def test_current_time(self):
|
def test_current_time(self):
|
||||||
"""Test current_time()."""
|
"""Test current_time()."""
|
||||||
result = formatters.current_time()
|
result = formatters._current_time()
|
||||||
assert isinstance(result, int)
|
assert isinstance(result, int)
|
||||||
assert result > 0
|
assert result > 0
|
||||||
|
|
||||||
|
62
tests/test_os_vm.py
Normal file
62
tests/test_os_vm.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Copyright 2017, Kevin Carter <kevin@cloudnull.com>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""Tests for the KVM plugin."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from monitorstack.cli import cli
|
||||||
|
|
||||||
|
|
||||||
|
def _runner(module):
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(cli, ['-f', 'json', module])
|
||||||
|
return json.loads(result.output)
|
||||||
|
|
||||||
|
|
||||||
|
class TestOs(object):
|
||||||
|
"""Tests for the os_vm.* monitors."""
|
||||||
|
|
||||||
|
def test_os_vm_quota_cores(self):
|
||||||
|
"""Ensure the run() method works."""
|
||||||
|
|
||||||
|
# result_json = _runner(module='os_vm_quota_cores')
|
||||||
|
# variables = result_json['variables']
|
||||||
|
# meta = result_json['meta']
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_os_vm_quota_instances(self):
|
||||||
|
"""Ensure the run() method works."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_os_vm_quota_ram(self):
|
||||||
|
"""Ensure the run() method works."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_os_vm_used_cores(self):
|
||||||
|
"""Ensure the run() method works."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_os_vm_used_disk(self):
|
||||||
|
"""Ensure the run() method works."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_os_vm_used_instances(self):
|
||||||
|
"""Ensure the run() method works."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_os_vm_used_ram(self):
|
||||||
|
"""Ensure the run() method works."""
|
||||||
|
pass
|
@@ -46,7 +46,7 @@ class LibvirtStub(object):
|
|||||||
|
|
||||||
|
|
||||||
class TestKvm(object):
|
class TestKvm(object):
|
||||||
"""Tests for the uptime monitor class."""
|
"""Tests for the kvm monitor."""
|
||||||
|
|
||||||
def test_run(self):
|
def test_run(self):
|
||||||
"""Ensure the run() method works."""
|
"""Ensure the run() method works."""
|
||||||
|
70
tests/test_utils.py
Normal file
70
tests/test_utils.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Copyright 2017, Major Hayden <major@mhtx.net>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""Tests for the uptime plugin."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from monitorstack import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestUptime(unittest.TestCase):
|
||||||
|
"""Tests for the utilities."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
os_config_file = os.path.expanduser(
|
||||||
|
os.path.abspath(__file__ + '/../../etc/openstack.ini')
|
||||||
|
)
|
||||||
|
self.config = utils.read_config(os_config_file)
|
||||||
|
conf = utils.ConfigParser.RawConfigParser()
|
||||||
|
conf.read([os_config_file])
|
||||||
|
self.config_defaults = conf.defaults()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
local_cache = os.path.join(
|
||||||
|
tempfile.gettempdir(),
|
||||||
|
'monitorstack.openstack.dbm'
|
||||||
|
)
|
||||||
|
if os.path.exists(local_cache):
|
||||||
|
os.remove(local_cache)
|
||||||
|
|
||||||
|
def test_is_int_is_int(self):
|
||||||
|
self.assertTrue(isinstance(utils.is_int(value=1), int))
|
||||||
|
|
||||||
|
def test_is_int_is_int_str(self):
|
||||||
|
self.assertTrue(isinstance(utils.is_int(value='1'), int))
|
||||||
|
|
||||||
|
def test_is_int_is_not_int(self):
|
||||||
|
self.assertTrue(isinstance(utils.is_int(value='a'), str))
|
||||||
|
|
||||||
|
def test_read_config_not_found(self):
|
||||||
|
self.assertRaises(
|
||||||
|
IOError,
|
||||||
|
utils.read_config,
|
||||||
|
'not-a-file'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_read_config_found_dict_return(self):
|
||||||
|
self.assertTrue(isinstance(self.config, dict))
|
||||||
|
|
||||||
|
def test_read_config_found_defaults_in_sections(self):
|
||||||
|
for k, v in self.config.items():
|
||||||
|
for key in self.config_defaults.keys():
|
||||||
|
self.assertTrue(key in v.keys())
|
||||||
|
|
||||||
|
def test_local_cache(self):
|
||||||
|
with utils.LocalCache() as c:
|
||||||
|
c['test_key'] = True
|
||||||
|
self.assertTrue('test_key' in c)
|
Reference in New Issue
Block a user