Add OpenStack service metrics
* nova: instances and services * cinder: volumes and snapshots * glance: images and snapshots * keystone: tenants, users, roles Change-Id: I1bbc97b7d4119100abc149d796f9697d7f35548b
This commit is contained in:
parent
bac87dbb3c
commit
652ceff526
@ -90,6 +90,7 @@ class OSClient(object):
|
||||
'region': endpoint['region'],
|
||||
'service_type': item['type'],
|
||||
'url': endpoint['internalURL'],
|
||||
'admin_url': endpoint['adminURL'],
|
||||
})
|
||||
|
||||
self.logger.debug("Got token '%s'" % self.token)
|
||||
@ -129,6 +130,7 @@ class OSClient(object):
|
||||
|
||||
|
||||
class CollectdPlugin(object):
|
||||
|
||||
def __init__(self, logger):
|
||||
self.os_client = None
|
||||
self.logger = logger
|
||||
@ -136,7 +138,14 @@ class CollectdPlugin(object):
|
||||
self.extra_config = {}
|
||||
|
||||
def _build_url(self, service, resource):
|
||||
url = (self.get_service(service) or {}).get('url')
|
||||
s = (self.get_service(service) or {})
|
||||
# the adminURL must be used to access resources with Keystone API v2
|
||||
if service == 'keystone' and \
|
||||
(resource in ['tenants', 'users'] or 'OS-KS' in resource):
|
||||
url = s.get('admin_url')
|
||||
else:
|
||||
url = s.get('url')
|
||||
|
||||
if url:
|
||||
if url[-1] != '/':
|
||||
url += '/'
|
||||
@ -184,3 +193,41 @@ class CollectdPlugin(object):
|
||||
|
||||
def read_callback(self):
|
||||
raise "read_callback method needs to be overriden!"
|
||||
|
||||
def get_objects_details(self, project, object_name,
|
||||
api_version='',
|
||||
params='all_tenants=1'):
|
||||
""" Return object details list
|
||||
|
||||
the version is not always included in url endpoint (glance)
|
||||
use api_version param to include the version in resource url
|
||||
"""
|
||||
|
||||
if api_version:
|
||||
resource = '%s/%s/detail?%s' % (api_version, object_name, params)
|
||||
else:
|
||||
resource = '%s/detail?%s' % (object_name, params)
|
||||
|
||||
# TODO(scroiset): use pagination to handle large collection
|
||||
r = self.get(project, resource)
|
||||
if not r:
|
||||
self.logger.warning('Could not find %s %s' % (project,
|
||||
object_name))
|
||||
return []
|
||||
return r.json().get(object_name, [])
|
||||
|
||||
def count_objects_group_by(self,
|
||||
list_object,
|
||||
group_by_func,
|
||||
count_func=None):
|
||||
|
||||
""" Dispatch values of object number grouped by criteria."""
|
||||
|
||||
status = {}
|
||||
for obj in list_object:
|
||||
s = group_by_func(obj)
|
||||
if s in status:
|
||||
status[s] += count_func(obj) if count_func else 1
|
||||
else:
|
||||
status[s] = count_func(obj) if count_func else 1
|
||||
return status
|
||||
|
@ -0,0 +1,91 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2015 Mirantis, 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.
|
||||
#
|
||||
# Collectd plugin for getting statistics from Cinder
|
||||
import collectd
|
||||
import openstack
|
||||
|
||||
PLUGIN_NAME = 'cinder'
|
||||
INTERVAL = 60
|
||||
|
||||
|
||||
class CinderStatsPlugin(openstack.CollectdPlugin):
|
||||
""" Class to report the statistics on Cinder service.
|
||||
|
||||
number of volumes broken down by state
|
||||
total size of volumes usable and in error state
|
||||
"""
|
||||
|
||||
def config_callback(self, config):
|
||||
super(CinderStatsPlugin, self).config_callback(config)
|
||||
|
||||
def read_callback(self):
|
||||
volumes_details = self.get_objects_details('cinder', 'volumes')
|
||||
|
||||
def groupby(d):
|
||||
return d.get('status', 'unknown').lower()
|
||||
|
||||
def count_size_bytes(d):
|
||||
return d.get('size', 0) * 10**9
|
||||
|
||||
status = self.count_objects_group_by(volumes_details,
|
||||
group_by_func=groupby)
|
||||
for s, nb in status.iteritems():
|
||||
self.dispatch_value('volumes', s, nb)
|
||||
|
||||
sizes = self.count_objects_group_by(volumes_details,
|
||||
group_by_func=groupby,
|
||||
count_func=count_size_bytes)
|
||||
for n, size in sizes.iteritems():
|
||||
self.dispatch_value('volume_size', n, size)
|
||||
|
||||
snaps_details = self.get_objects_details('cinder', 'snapshots')
|
||||
status_snaps = self.count_objects_group_by(snaps_details,
|
||||
group_by_func=groupby)
|
||||
for s, nb in status_snaps.iteritems():
|
||||
self.dispatch_value('snapshots', s, nb)
|
||||
|
||||
sizes = self.count_objects_group_by(snaps_details,
|
||||
group_by_func=groupby,
|
||||
count_func=count_size_bytes)
|
||||
for n, size in sizes.iteritems():
|
||||
self.dispatch_value('snapshots_size', n, size)
|
||||
|
||||
def dispatch_value(self, plugin_instance, name, value):
|
||||
v = collectd.Values(
|
||||
plugin=PLUGIN_NAME, # metric source
|
||||
plugin_instance=plugin_instance,
|
||||
type='gauge',
|
||||
type_instance=name,
|
||||
interval=INTERVAL,
|
||||
# w/a for https://github.com/collectd/collectd/issues/716
|
||||
meta={'0': True},
|
||||
values=[value]
|
||||
)
|
||||
v.dispatch()
|
||||
|
||||
plugin = CinderStatsPlugin(collectd)
|
||||
|
||||
|
||||
def config_callback(conf):
|
||||
plugin.config_callback(conf)
|
||||
|
||||
|
||||
def read_callback():
|
||||
plugin.read_callback()
|
||||
|
||||
collectd.register_config(config_callback)
|
||||
collectd.register_read(read_callback, INTERVAL)
|
||||
|
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2015 Mirantis, 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.
|
||||
#
|
||||
# Collectd plugin for getting resource statistics from Glance
|
||||
import collectd
|
||||
import openstack
|
||||
|
||||
PLUGIN_NAME = 'glance'
|
||||
INTERVAL = 60
|
||||
|
||||
|
||||
class GlanceStatsPlugin(openstack.CollectdPlugin):
|
||||
""" Class to report the statistics on Glance service.
|
||||
|
||||
number of image broken down by state
|
||||
total size of images usable and in error state
|
||||
"""
|
||||
|
||||
def config_callback(self, config):
|
||||
super(GlanceStatsPlugin, self).config_callback(config)
|
||||
|
||||
def read_callback(self):
|
||||
|
||||
def is_snap(d):
|
||||
return d.get('properties', {}).get('image_type') == 'snapshot'
|
||||
|
||||
def groupby(d):
|
||||
p = 'public' if d.get('is_public', True) else 'private'
|
||||
status = d.get('status', 'unknown').lower()
|
||||
if is_snap(d):
|
||||
return 'snapshots.%s.%s' % (p, status)
|
||||
return 'images.%s.%s' % (p, status)
|
||||
|
||||
images_details = self.get_objects_details('glance', 'images',
|
||||
api_version='v1',
|
||||
params='is_public=None')
|
||||
status = self.count_objects_group_by(images_details,
|
||||
group_by_func=groupby)
|
||||
for s, nb in status.iteritems():
|
||||
self.dispatch_value(s, nb)
|
||||
|
||||
# sizes
|
||||
def count_size_bytes(d):
|
||||
return d.get('size', 0)
|
||||
|
||||
def groupby_size(d):
|
||||
p = 'public' if d.get('is_public', True) else 'private'
|
||||
status = d.get('status', 'unknown').lower()
|
||||
if is_snap(d):
|
||||
return 'snapshots_size.%s.%s' % (p, status)
|
||||
return 'images_size.%s.%s' % (p, status)
|
||||
|
||||
sizes = self.count_objects_group_by(images_details,
|
||||
group_by_func=groupby_size,
|
||||
count_func=count_size_bytes)
|
||||
for s, nb in sizes.iteritems():
|
||||
self.dispatch_value(s, nb)
|
||||
|
||||
def dispatch_value(self, name, value):
|
||||
v = collectd.Values(
|
||||
plugin=PLUGIN_NAME, # metric source
|
||||
type='gauge',
|
||||
type_instance=name,
|
||||
interval=INTERVAL,
|
||||
# w/a for https://github.com/collectd/collectd/issues/716
|
||||
meta={'0': True},
|
||||
values=[value]
|
||||
)
|
||||
v.dispatch()
|
||||
|
||||
plugin = GlanceStatsPlugin(collectd)
|
||||
|
||||
|
||||
def config_callback(conf):
|
||||
plugin.config_callback(conf)
|
||||
|
||||
|
||||
def read_callback():
|
||||
plugin.read_callback()
|
||||
|
||||
collectd.register_config(config_callback)
|
||||
collectd.register_read(read_callback, INTERVAL)
|
||||
|
||||
|
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2015 Mirantis, 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.
|
||||
#
|
||||
# Collectd plugin for getting statistics from Keystone
|
||||
import collectd
|
||||
import openstack
|
||||
|
||||
PLUGIN_NAME = 'keystone'
|
||||
INTERVAL = 60
|
||||
|
||||
|
||||
class KeystoneStatsPlugin(openstack.CollectdPlugin):
|
||||
""" Class to report the statistics on Keystone service.
|
||||
|
||||
number of tenants, users broken down by state
|
||||
number of roles
|
||||
"""
|
||||
|
||||
def config_callback(self, config):
|
||||
super(KeystoneStatsPlugin, self).config_callback(config)
|
||||
|
||||
def read_callback(self):
|
||||
|
||||
def groupby(d):
|
||||
return 'enabled' if d.get('enabled') else 'disabled'
|
||||
|
||||
# tenants
|
||||
r = self.get('keystone', 'tenants')
|
||||
if not r:
|
||||
self.logger.warning('Could not find Keystone tenants')
|
||||
return
|
||||
tenants_details = r.json().get('tenants', [])
|
||||
status = self.count_objects_group_by(tenants_details,
|
||||
group_by_func=groupby)
|
||||
for s, nb in status.iteritems():
|
||||
self.dispatch_value('tenants.' + s, nb)
|
||||
|
||||
# users
|
||||
r = self.get('keystone', 'users')
|
||||
if not r:
|
||||
self.logger.warning('Could not find Keystone users')
|
||||
return
|
||||
users_details = r.json().get('users', [])
|
||||
status = self.count_objects_group_by(users_details,
|
||||
group_by_func=groupby)
|
||||
for s, nb in status.iteritems():
|
||||
self.dispatch_value('users.' + s, nb)
|
||||
|
||||
# roles
|
||||
r = self.get('keystone', 'OS-KSADM/roles')
|
||||
if not r:
|
||||
self.logger.warning('Could not find Keystone roles')
|
||||
return
|
||||
roles = r.json().get('roles', [])
|
||||
self.dispatch_value('roles', len(roles))
|
||||
|
||||
def dispatch_value(self, name, value):
|
||||
v = collectd.Values(
|
||||
plugin=PLUGIN_NAME, # metric source
|
||||
type='gauge',
|
||||
type_instance=name,
|
||||
interval=INTERVAL,
|
||||
# w/a for https://github.com/collectd/collectd/issues/716
|
||||
meta={'0': True},
|
||||
values=[value]
|
||||
)
|
||||
v.dispatch()
|
||||
|
||||
plugin = KeystoneStatsPlugin(collectd)
|
||||
|
||||
|
||||
def config_callback(conf):
|
||||
plugin.config_callback(conf)
|
||||
|
||||
|
||||
def read_callback():
|
||||
plugin.read_callback()
|
||||
|
||||
collectd.register_config(config_callback)
|
||||
collectd.register_read(read_callback, INTERVAL)
|
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2015 Mirantis, 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.
|
||||
#
|
||||
# Collectd plugin for getting statistics from Nova
|
||||
import collectd
|
||||
import openstack
|
||||
|
||||
PLUGIN_NAME = 'nova'
|
||||
INTERVAL = 60
|
||||
|
||||
|
||||
class NovaStatsPlugin(openstack.CollectdPlugin):
|
||||
""" Class to report the statistics on Nova service.
|
||||
|
||||
number of instances broken down by state
|
||||
number of services by state enabled or disabled
|
||||
"""
|
||||
|
||||
def config_callback(self, config):
|
||||
super(NovaStatsPlugin, self).config_callback(config)
|
||||
|
||||
def _count_services_by_state(self):
|
||||
r = self.get('nova', 'os-services')
|
||||
if not r:
|
||||
self.logger.warning("Could not get services statistics")
|
||||
return {}
|
||||
|
||||
services = {}
|
||||
for s in r.json().get('services'):
|
||||
if s['binary'] not in services:
|
||||
services[s['binary']] = {'enabled': 0, 'disabled': 0}
|
||||
if s['status'] == 'enabled':
|
||||
services[s['binary']]['enabled'] += 1
|
||||
else:
|
||||
services[s['binary']]['disabled'] += 1
|
||||
return services
|
||||
|
||||
def read_callback(self):
|
||||
servers_details = self.get_objects_details('nova', 'servers')
|
||||
|
||||
def groupby(d):
|
||||
return d.get('status', 'unknown').lower()
|
||||
status = self.count_objects_group_by(servers_details,
|
||||
group_by_func=groupby)
|
||||
for s, nb in status.iteritems():
|
||||
self.dispatch_value('instances', s, nb)
|
||||
|
||||
services = self._count_services_by_state()
|
||||
for service_name, states in services.iteritems():
|
||||
for s in states.keys():
|
||||
self.dispatch_value('services.' + service_name,
|
||||
s, services[service_name][s])
|
||||
|
||||
def dispatch_value(self, plugin_instance, name, value):
|
||||
v = collectd.Values(
|
||||
plugin=PLUGIN_NAME, # metric source
|
||||
plugin_instance=plugin_instance,
|
||||
type='gauge',
|
||||
type_instance=name,
|
||||
interval=INTERVAL,
|
||||
# w/a for https://github.com/collectd/collectd/issues/716
|
||||
meta={'0': True},
|
||||
values=[value]
|
||||
)
|
||||
v.dispatch()
|
||||
|
||||
plugin = NovaStatsPlugin(collectd)
|
||||
|
||||
|
||||
def config_callback(conf):
|
||||
plugin.config_callback(conf)
|
||||
|
||||
|
||||
def read_callback():
|
||||
plugin.read_callback()
|
||||
|
||||
collectd.register_config(config_callback)
|
||||
collectd.register_read(read_callback, INTERVAL)
|
@ -108,6 +108,14 @@ function process_message ()
|
||||
end
|
||||
elseif metric_source == 'rabbitmq_info' then
|
||||
msg['Fields']['name'] = 'rabbitmq' .. sep .. sample['type_instance']
|
||||
elseif metric_source == 'nova' then
|
||||
msg['Fields']['name'] = 'openstack.nova' .. sep .. sample['plugin_instance'] .. sep .. sample['type_instance']
|
||||
elseif metric_source == 'cinder' then
|
||||
msg['Fields']['name'] = 'openstack.cinder' .. sep .. sample['plugin_instance'] .. sep .. sample['type_instance']
|
||||
elseif metric_source == 'glance' then
|
||||
msg['Fields']['name'] = 'openstack.glance' .. sep .. sample['type_instance']
|
||||
elseif metric_source == 'keystone' then
|
||||
msg['Fields']['name'] = 'openstack.keystone' .. sep .. sample['type_instance']
|
||||
else
|
||||
msg['Fields']['name'] = metric_name
|
||||
end
|
||||
|
@ -31,6 +31,34 @@ class lma_collector::collectd::controller (
|
||||
'Timeout' => $lma_collector::params::openstack_client_timeout,
|
||||
'CpuAllocationRatio' => $nova_cpu_allocation_ratio,
|
||||
},
|
||||
'openstack_nova' => {
|
||||
'Username' => $service_user,
|
||||
'Password' => $service_password,
|
||||
'Tenant' => $service_tenant,
|
||||
'KeystoneUrl' => $keystone_url,
|
||||
'Timeout' => $lma_collector::params::openstack_client_timeout,
|
||||
},
|
||||
'openstack_cinder' => {
|
||||
'Username' => $service_user,
|
||||
'Password' => $service_password,
|
||||
'Tenant' => $service_tenant,
|
||||
'KeystoneUrl' => $keystone_url,
|
||||
'Timeout' => $lma_collector::params::openstack_client_timeout,
|
||||
},
|
||||
'openstack_glance' => {
|
||||
'Username' => $service_user,
|
||||
'Password' => $service_password,
|
||||
'Tenant' => $service_tenant,
|
||||
'KeystoneUrl' => $keystone_url,
|
||||
'Timeout' => $lma_collector::params::openstack_client_timeout,
|
||||
},
|
||||
'openstack_keystone' => {
|
||||
'Username' => $service_user,
|
||||
'Password' => $service_password,
|
||||
'Tenant' => $service_tenant,
|
||||
'KeystoneUrl' => $keystone_url,
|
||||
'Timeout' => $lma_collector::params::openstack_client_timeout,
|
||||
},
|
||||
}
|
||||
|
||||
file {"${collectd::params::plugin_conf_dir}/openstack.conf":
|
||||
@ -52,4 +80,16 @@ class lma_collector::collectd::controller (
|
||||
|
||||
lma_collector::collectd::python_script { "openstack.py":
|
||||
}
|
||||
|
||||
lma_collector::collectd::python_script { "openstack_nova.py":
|
||||
}
|
||||
|
||||
lma_collector::collectd::python_script { "openstack_cinder.py":
|
||||
}
|
||||
|
||||
lma_collector::collectd::python_script { "openstack_glance.py":
|
||||
}
|
||||
|
||||
lma_collector::collectd::python_script { "openstack_keystone.py":
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,46 @@ These metrics are emitted per compute node.
|
||||
* ``openstack.nova.running_instances``, the number of running instances on the node.
|
||||
* ``openstack.nova.running_tasks``, the number of tasks currently executed by the node.
|
||||
|
||||
These metrics are retrieved from the Nova API.
|
||||
|
||||
* ``openstack.nova.instances.<state>``, the number of instances by state.
|
||||
* ``openstack.nova.services.<service>.enabled``, the number of enabled Nova
|
||||
services by service name.
|
||||
* ``openstack.nova.services.<service>.disabled``, the number of disabled Nova
|
||||
services by service name.
|
||||
|
||||
``<state>`` is one of 'active', 'deleted', 'error', 'paused', 'resumed', 'rescued', 'resized', 'shelved_offloaded' or 'suspended'.
|
||||
|
||||
``<service>`` is one of service is one of 'compute', 'conductor', 'scheduler', 'cert' or 'consoleauth'.
|
||||
|
||||
Volume
|
||||
^^^^^^
|
||||
|
||||
These metrics are retrieved from the Cinder API.
|
||||
|
||||
* ``openstack.cinder.volumes.<state>``, the number of volumes by state.
|
||||
* ``openstack.cinder.snapshots.<state>``, the number of snapshots by state.
|
||||
* ``openstack.cinder.volumes_size.<state>``, the total size (in bytes) of volumes by state.
|
||||
* ``openstack.cinder.snapshots_size.<state>``, the total size (in bytes) of snapshots by state.
|
||||
|
||||
``<state>`` is one of 'available', 'creating', 'attaching', 'in-use', 'deleting', 'backing-up', 'restoring-backup', 'error', 'error_deleting', 'error_restoring', 'error_extending'.
|
||||
|
||||
Image
|
||||
^^^^^
|
||||
|
||||
These metrics are retrieved from the Glance API.
|
||||
|
||||
* ``openstack.glance.images.public.<state>``, the number of public images by state.
|
||||
* ``openstack.glance.images.private.<state>``, the number of private images by state.
|
||||
* ``openstack.glance.snapshots.public.<state>``, the number of public snapshot images by state.
|
||||
* ``openstack.glance.snapshots.private.<state>``, the number of private snapshot images by state.
|
||||
* ``openstack.glance.images_size.public.<state>``, the total size (in bytes) of public images by state.
|
||||
* ``openstack.glance.images_size.private.<state>``, the total size (in bytes) of private images by state.
|
||||
* ``openstack.glance.snapshots_size.public.<state>``, the total size (in bytes) of public snapshots by state.
|
||||
* ``openstack.glance.snapshots_size.private.<state>``, the total size (in bytes) of private snapshots by state.
|
||||
|
||||
``<state>`` is one of 'queued', 'saving', 'active', 'killed', 'deleted', 'pending_delete'.
|
||||
|
||||
API response times
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user