204 lines
7.7 KiB
Python
204 lines
7.7 KiB
Python
# -*- encoding: utf-8 -*-
|
|
#
|
|
# Copyright © 2014-2015 eNovance
|
|
#
|
|
# 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.
|
|
|
|
from oslo_config import cfg
|
|
|
|
from gnocchi import storage
|
|
from gnocchi.storage import _carbonara
|
|
from gnocchi.storage.common import ceph
|
|
|
|
|
|
OPTS = [
|
|
cfg.StrOpt('ceph_pool',
|
|
default='gnocchi',
|
|
help='Ceph pool name to use.'),
|
|
cfg.StrOpt('ceph_username',
|
|
help='Ceph username (ie: admin without "client." prefix).'),
|
|
cfg.StrOpt('ceph_secret', help='Ceph key', secret=True),
|
|
cfg.StrOpt('ceph_keyring', help='Ceph keyring path.'),
|
|
cfg.IntOpt('ceph_timeout', help='Ceph connection timeout'),
|
|
cfg.StrOpt('ceph_conffile',
|
|
default='/etc/ceph/ceph.conf',
|
|
help='Ceph configuration file.'),
|
|
]
|
|
|
|
rados = ceph.rados
|
|
|
|
|
|
class CephStorage(_carbonara.CarbonaraBasedStorage):
|
|
WRITE_FULL = False
|
|
|
|
def __init__(self, conf, incoming):
|
|
super(CephStorage, self).__init__(conf, incoming)
|
|
self.rados, self.ioctx = ceph.create_rados_connection(conf)
|
|
|
|
def stop(self):
|
|
ceph.close_rados_connection(self.rados, self.ioctx)
|
|
super(CephStorage, self).stop()
|
|
|
|
@staticmethod
|
|
def _get_object_name(metric, timestamp_key, aggregation, granularity,
|
|
version=3):
|
|
name = str("gnocchi_%s_%s_%s_%s" % (
|
|
metric.id, timestamp_key, aggregation, granularity))
|
|
return name + '_v%s' % version if version else name
|
|
|
|
def _object_exists(self, name):
|
|
try:
|
|
self.ioctx.stat(name)
|
|
return True
|
|
except rados.ObjectNotFound:
|
|
return False
|
|
|
|
def _create_metric(self, metric):
|
|
name = self._build_unaggregated_timeserie_path(metric, 3)
|
|
if self._object_exists(name):
|
|
raise storage.MetricAlreadyExists(metric)
|
|
else:
|
|
self.ioctx.write_full(name, b"")
|
|
|
|
def _store_metric_measures(self, metric, timestamp_key, aggregation,
|
|
granularity, data, offset=None, version=3):
|
|
name = self._get_object_name(metric, timestamp_key,
|
|
aggregation, granularity, version)
|
|
if offset is None:
|
|
self.ioctx.write_full(name, data)
|
|
else:
|
|
self.ioctx.write(name, data, offset=offset)
|
|
with rados.WriteOpCtx() as op:
|
|
self.ioctx.set_omap(op, (name,), (b"",))
|
|
self.ioctx.operate_write_op(
|
|
op, self._build_unaggregated_timeserie_path(metric, 3))
|
|
|
|
def _delete_metric_measures(self, metric, timestamp_key, aggregation,
|
|
granularity, version=3):
|
|
name = self._get_object_name(metric, timestamp_key,
|
|
aggregation, granularity, version)
|
|
|
|
try:
|
|
self.ioctx.remove_object(name)
|
|
except rados.ObjectNotFound:
|
|
# It's possible that we already remove that object and then crashed
|
|
# before removing it from the OMAP key list; then no big deal
|
|
# anyway.
|
|
pass
|
|
|
|
with rados.WriteOpCtx() as op:
|
|
self.ioctx.remove_omap_keys(op, (name,))
|
|
self.ioctx.operate_write_op(
|
|
op, self._build_unaggregated_timeserie_path(metric, 3))
|
|
|
|
def _delete_metric(self, metric):
|
|
with rados.ReadOpCtx() as op:
|
|
omaps, ret = self.ioctx.get_omap_vals(op, "", "", -1)
|
|
try:
|
|
self.ioctx.operate_read_op(
|
|
op, self._build_unaggregated_timeserie_path(metric, 3))
|
|
except rados.ObjectNotFound:
|
|
return
|
|
|
|
# NOTE(sileht): after reading the libradospy, I'm
|
|
# not sure that ret will have the correct value
|
|
# get_omap_vals transforms the C int to python int
|
|
# before operate_read_op is called, I dunno if the int
|
|
# content is copied during this transformation or if
|
|
# this is a pointer to the C int, I think it's copied...
|
|
try:
|
|
ceph.errno_to_exception(ret)
|
|
except rados.ObjectNotFound:
|
|
return
|
|
|
|
ops = [self.ioctx.aio_remove(name) for name, _ in omaps]
|
|
|
|
for op in ops:
|
|
op.wait_for_complete_and_cb()
|
|
|
|
try:
|
|
self.ioctx.remove_object(
|
|
self._build_unaggregated_timeserie_path(metric, 3))
|
|
except rados.ObjectNotFound:
|
|
# It's possible that the object does not exists
|
|
pass
|
|
|
|
def _get_measures(self, metric, timestamp_key, aggregation, granularity,
|
|
version=3):
|
|
try:
|
|
name = self._get_object_name(metric, timestamp_key,
|
|
aggregation, granularity, version)
|
|
return self._get_object_content(name)
|
|
except rados.ObjectNotFound:
|
|
if self._object_exists(
|
|
self._build_unaggregated_timeserie_path(metric, 3)):
|
|
raise storage.AggregationDoesNotExist(metric, aggregation)
|
|
else:
|
|
raise storage.MetricDoesNotExist(metric)
|
|
|
|
def _list_split_keys_for_metric(self, metric, aggregation, granularity,
|
|
version=3):
|
|
with rados.ReadOpCtx() as op:
|
|
omaps, ret = self.ioctx.get_omap_vals(op, "", "", -1)
|
|
try:
|
|
self.ioctx.operate_read_op(
|
|
op, self._build_unaggregated_timeserie_path(metric, 3))
|
|
except rados.ObjectNotFound:
|
|
raise storage.MetricDoesNotExist(metric)
|
|
|
|
# NOTE(sileht): after reading the libradospy, I'm
|
|
# not sure that ret will have the correct value
|
|
# get_omap_vals transforms the C int to python int
|
|
# before operate_read_op is called, I dunno if the int
|
|
# content is copied during this transformation or if
|
|
# this is a pointer to the C int, I think it's copied...
|
|
try:
|
|
ceph.errno_to_exception(ret)
|
|
except rados.ObjectNotFound:
|
|
raise storage.MetricDoesNotExist(metric)
|
|
|
|
keys = set()
|
|
for name, value in omaps:
|
|
meta = name.split('_')
|
|
if (aggregation == meta[3] and granularity == float(meta[4])
|
|
and self._version_check(name, version)):
|
|
keys.add(meta[2])
|
|
return keys
|
|
|
|
@staticmethod
|
|
def _build_unaggregated_timeserie_path(metric, version):
|
|
return (('gnocchi_%s_none' % metric.id)
|
|
+ ("_v%s" % version if version else ""))
|
|
|
|
def _get_unaggregated_timeserie(self, metric, version=3):
|
|
try:
|
|
return self._get_object_content(
|
|
self._build_unaggregated_timeserie_path(metric, version))
|
|
except rados.ObjectNotFound:
|
|
raise storage.MetricDoesNotExist(metric)
|
|
|
|
def _store_unaggregated_timeserie(self, metric, data, version=3):
|
|
self.ioctx.write_full(
|
|
self._build_unaggregated_timeserie_path(metric, version), data)
|
|
|
|
def _get_object_content(self, name):
|
|
offset = 0
|
|
content = b''
|
|
while True:
|
|
data = self.ioctx.read(name, offset=offset)
|
|
if not data:
|
|
break
|
|
content += data
|
|
offset += len(data)
|
|
return content
|