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:
Swann Croiset 2015-03-12 17:59:02 +01:00
parent bac87dbb3c
commit 652ceff526
8 changed files with 503 additions and 1 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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":
}
}

View File

@ -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
^^^^^^^^^^^^^^^^^^