Merge "gnocchi: reduce the number of patch to gnocchi API"
This commit is contained in:
commit
6e969335d1
@ -320,89 +320,79 @@ class GnocchiDispatcher(dispatcher.Base):
|
|||||||
data, key=operator.itemgetter('resource_id'))
|
data, key=operator.itemgetter('resource_id'))
|
||||||
|
|
||||||
for resource_id, samples_of_resource in resource_grouped_samples:
|
for resource_id, samples_of_resource in resource_grouped_samples:
|
||||||
resource_need_to_be_updated = True
|
|
||||||
|
|
||||||
metric_grouped_samples = itertools.groupby(
|
metric_grouped_samples = itertools.groupby(
|
||||||
list(samples_of_resource),
|
list(samples_of_resource),
|
||||||
key=operator.itemgetter('counter_name'))
|
key=operator.itemgetter('counter_name'))
|
||||||
for metric_name, samples in metric_grouped_samples:
|
|
||||||
samples = list(samples)
|
|
||||||
rd = self._get_resource_definition(metric_name)
|
|
||||||
if rd:
|
|
||||||
self._process_samples(rd, resource_id, metric_name,
|
|
||||||
samples,
|
|
||||||
resource_need_to_be_updated)
|
|
||||||
else:
|
|
||||||
LOG.warn("metric %s is not handled by gnocchi" %
|
|
||||||
metric_name)
|
|
||||||
|
|
||||||
# FIXME(sileht): Does it reasonable to skip the resource
|
self._process_resource(resource_id, metric_grouped_samples)
|
||||||
# update here ? Does differents kind of counter_name
|
|
||||||
# can have different metadata set ?
|
|
||||||
# (ie: one have only flavor_id, and an other one have only
|
|
||||||
# image_ref ?)
|
|
||||||
#
|
|
||||||
# resource_need_to_be_updated = False
|
|
||||||
|
|
||||||
@log_and_ignore_unexpected_workflow_error
|
@log_and_ignore_unexpected_workflow_error
|
||||||
def _process_samples(self, resource_def, resource_id, metric_name, samples,
|
def _process_resource(self, resource_id, metric_grouped_samples):
|
||||||
resource_need_to_be_updated):
|
# TODO(sileht): Any HTTP 50X/401 error is just logged and this method
|
||||||
resource_type = resource_def.cfg['resource_type']
|
# stop, perhaps we can be smarter and retry later in case of 50X and
|
||||||
measure_attributes = [{'timestamp': sample['timestamp'],
|
# directly in case of 401. A gnocchiclient would help a lot for the
|
||||||
'value': sample['counter_volume']}
|
# latest.
|
||||||
for sample in samples]
|
|
||||||
|
resource_extra = {}
|
||||||
|
for metric_name, samples in metric_grouped_samples:
|
||||||
|
samples = list(samples)
|
||||||
|
rd = self._get_resource_definition(metric_name)
|
||||||
|
if rd is None:
|
||||||
|
LOG.warn("metric %s is not handled by gnocchi" %
|
||||||
|
metric_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
resource_type = rd.cfg['resource_type']
|
||||||
|
resource = {
|
||||||
|
"id": resource_id,
|
||||||
|
"user_id": samples[0]['user_id'],
|
||||||
|
"project_id": samples[0]['project_id'],
|
||||||
|
"metrics": rd.metrics(),
|
||||||
|
}
|
||||||
|
measures = []
|
||||||
|
|
||||||
|
for sample in samples:
|
||||||
|
resource_extra.update(rd.attributes(sample))
|
||||||
|
measures.append({'timestamp': sample['timestamp'],
|
||||||
|
'value': sample['counter_volume']})
|
||||||
|
|
||||||
|
resource.update(resource_extra)
|
||||||
|
|
||||||
try:
|
|
||||||
self._post_measure(resource_type, resource_id, metric_name,
|
|
||||||
measure_attributes)
|
|
||||||
except NoSuchMetric:
|
|
||||||
# NOTE(sileht): we try first to create the resource, because
|
|
||||||
# they more chance that the resource doesn't exists than the metric
|
|
||||||
# is missing, the should be reduce the number of resource API call
|
|
||||||
resource_attributes = self._get_resource_attributes(
|
|
||||||
resource_def, resource_id, metric_name, samples)
|
|
||||||
try:
|
try:
|
||||||
self._create_resource(resource_type, resource_id,
|
self._post_measure(resource_type, resource_id, metric_name,
|
||||||
resource_attributes)
|
measures)
|
||||||
except ResourceAlreadyExists:
|
except NoSuchMetric:
|
||||||
|
# TODO(sileht): Make gnocchi smarter to be able to detect 404
|
||||||
|
# for 'resource doesn't exist' and for 'metric doesn't exist'
|
||||||
|
# https://bugs.launchpad.net/gnocchi/+bug/1476186
|
||||||
|
self._ensure_resource_and_metric(resource_type, resource,
|
||||||
|
metric_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
archive_policy = (resource_def.metrics()[metric_name])
|
self._post_measure(resource_type, resource_id,
|
||||||
self._create_metric(resource_type, resource_id,
|
metric_name, measures)
|
||||||
metric_name, archive_policy)
|
except NoSuchMetric:
|
||||||
except MetricAlreadyExists:
|
LOG.error(_LE("Fail to post measures for "
|
||||||
# NOTE(sileht): Just ignore the metric have been created in
|
"%(resource_id)s/%(metric_name)s") %
|
||||||
# the meantime.
|
dict(resource_id=resource_id,
|
||||||
pass
|
metric_name=metric_name))
|
||||||
else:
|
|
||||||
# No need to update it we just created it
|
|
||||||
# with everything we need
|
|
||||||
resource_need_to_be_updated = False
|
|
||||||
|
|
||||||
# NOTE(sileht): we retry to post the measure but if it fail we
|
if resource_extra:
|
||||||
# don't catch the exception to just log it and continue to process
|
self._update_resource(resource_type, resource_id, resource_extra)
|
||||||
# other samples
|
|
||||||
self._post_measure(resource_type, resource_id, metric_name,
|
|
||||||
measure_attributes)
|
|
||||||
|
|
||||||
if resource_need_to_be_updated:
|
def _ensure_resource_and_metric(self, resource_type, resource,
|
||||||
resource_attributes = self._get_resource_attributes(
|
metric_name):
|
||||||
resource_def, resource_id, metric_name, samples,
|
try:
|
||||||
for_update=True)
|
self._create_resource(resource_type, resource)
|
||||||
if resource_attributes:
|
except ResourceAlreadyExists:
|
||||||
self._update_resource(resource_type, resource_id,
|
try:
|
||||||
resource_attributes)
|
archive_policy = resource['metrics'][metric_name]
|
||||||
|
self._create_metric(resource_type, resource['id'],
|
||||||
def _get_resource_attributes(self, resource_def, resource_id, metric_name,
|
metric_name, archive_policy)
|
||||||
samples, for_update=False):
|
except MetricAlreadyExists:
|
||||||
# FIXME(sileht): Should I merge attibutes of all samples ?
|
# NOTE(sileht): Just ignore the metric have been
|
||||||
# Or keep only the last one is sufficient ?
|
# created in the meantime.
|
||||||
attributes = resource_def.attributes(samples[-1])
|
pass
|
||||||
if not for_update:
|
|
||||||
attributes["id"] = resource_id
|
|
||||||
attributes["user_id"] = samples[-1]['user_id']
|
|
||||||
attributes["project_id"] = samples[-1]['project_id']
|
|
||||||
attributes["metrics"] = resource_def.metrics()
|
|
||||||
return attributes
|
|
||||||
|
|
||||||
def _post_measure(self, resource_type, resource_id, metric_name,
|
def _post_measure(self, resource_type, resource_id, metric_name,
|
||||||
measure_attributes):
|
measure_attributes):
|
||||||
@ -432,33 +422,32 @@ class GnocchiDispatcher(dispatcher.Base):
|
|||||||
LOG.debug("Measure posted on metric %s of resource %s",
|
LOG.debug("Measure posted on metric %s of resource %s",
|
||||||
metric_name, resource_id)
|
metric_name, resource_id)
|
||||||
|
|
||||||
def _create_resource(self, resource_type, resource_id,
|
def _create_resource(self, resource_type, resource):
|
||||||
resource_attributes):
|
|
||||||
r = self.gnocchi_api.post("%s/v1/resource/%s"
|
r = self.gnocchi_api.post("%s/v1/resource/%s"
|
||||||
% (self.gnocchi_url, resource_type),
|
% (self.gnocchi_url, resource_type),
|
||||||
headers=self._get_headers(),
|
headers=self._get_headers(),
|
||||||
data=json.dumps(resource_attributes))
|
data=json.dumps(resource))
|
||||||
if r.status_code == 409:
|
if r.status_code == 409:
|
||||||
LOG.debug("Resource %s already exists", resource_id)
|
LOG.debug("Resource %s already exists", resource['id'])
|
||||||
raise ResourceAlreadyExists
|
raise ResourceAlreadyExists
|
||||||
|
|
||||||
elif int(r.status_code / 100) != 2:
|
elif int(r.status_code / 100) != 2:
|
||||||
raise UnexpectedWorkflowError(
|
raise UnexpectedWorkflowError(
|
||||||
_("Resource %(resource_id)s creation failed with "
|
_("Resource %(resource_id)s creation failed with "
|
||||||
"status: %(status_code)d: %(msg)s") %
|
"status: %(status_code)d: %(msg)s") %
|
||||||
{'resource_id': resource_id,
|
{'resource_id': resource['id'],
|
||||||
'status_code': r.status_code,
|
'status_code': r.status_code,
|
||||||
'msg': r.text})
|
'msg': r.text})
|
||||||
else:
|
else:
|
||||||
LOG.debug("Resource %s created", resource_id)
|
LOG.debug("Resource %s created", resource['id'])
|
||||||
|
|
||||||
def _update_resource(self, resource_type, resource_id,
|
def _update_resource(self, resource_type, resource_id,
|
||||||
resource_attributes):
|
resource_extra):
|
||||||
r = self.gnocchi_api.patch(
|
r = self.gnocchi_api.patch(
|
||||||
"%s/v1/resource/%s/%s"
|
"%s/v1/resource/%s/%s"
|
||||||
% (self.gnocchi_url, resource_type, resource_id),
|
% (self.gnocchi_url, resource_type, resource_id),
|
||||||
headers=self._get_headers(),
|
headers=self._get_headers(),
|
||||||
data=json.dumps(resource_attributes))
|
data=json.dumps(resource_extra))
|
||||||
|
|
||||||
if int(r.status_code / 100) != 2:
|
if int(r.status_code / 100) != 2:
|
||||||
raise UnexpectedWorkflowError(
|
raise UnexpectedWorkflowError(
|
||||||
|
@ -139,15 +139,23 @@ class DispatcherTest(base.BaseTestCase):
|
|||||||
gnocchi.GnocchiDispatcher, self.conf.conf)
|
gnocchi.GnocchiDispatcher, self.conf.conf)
|
||||||
|
|
||||||
@mock.patch('ceilometer.dispatcher.gnocchi.GnocchiDispatcher'
|
@mock.patch('ceilometer.dispatcher.gnocchi.GnocchiDispatcher'
|
||||||
'._process_samples')
|
'._process_resource')
|
||||||
def _do_test_activity_filter(self, expected_samples, fake_process_samples):
|
def _do_test_activity_filter(self, expected_samples,
|
||||||
|
fake_process_resource):
|
||||||
|
|
||||||
|
def assert_samples(resource_id, metric_grouped_samples):
|
||||||
|
samples = []
|
||||||
|
for metric_name, s in metric_grouped_samples:
|
||||||
|
samples.extend(list(s))
|
||||||
|
self.assertEqual(expected_samples, samples)
|
||||||
|
|
||||||
|
fake_process_resource.side_effect = assert_samples
|
||||||
|
|
||||||
d = gnocchi.GnocchiDispatcher(self.conf.conf)
|
d = gnocchi.GnocchiDispatcher(self.conf.conf)
|
||||||
d.record_metering_data(self.samples)
|
d.record_metering_data(self.samples)
|
||||||
|
|
||||||
fake_process_samples.assert_called_with(
|
fake_process_resource.assert_called_with(self.resource_id,
|
||||||
mock.ANY, self.resource_id, 'disk.root.size',
|
mock.ANY)
|
||||||
expected_samples, True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_archive_policy_map_config(self):
|
def test_archive_policy_map_config(self):
|
||||||
archive_policy_map = yaml.dump({
|
archive_policy_map = yaml.dump({
|
||||||
@ -284,7 +292,7 @@ class DispatcherWorkflowTest(base.BaseTestCase,
|
|||||||
('normal_workflow', dict(measure=204, post_resource=None, metric=None,
|
('normal_workflow', dict(measure=204, post_resource=None, metric=None,
|
||||||
measure_retry=None, patch_resource=204)),
|
measure_retry=None, patch_resource=204)),
|
||||||
('new_resource', dict(measure=404, post_resource=204, metric=None,
|
('new_resource', dict(measure=404, post_resource=204, metric=None,
|
||||||
measure_retry=204, patch_resource=None)),
|
measure_retry=204, patch_resource=204)),
|
||||||
('new_resource_fail', dict(measure=404, post_resource=500, metric=None,
|
('new_resource_fail', dict(measure=404, post_resource=500, metric=None,
|
||||||
measure_retry=None, patch_resource=None)),
|
measure_retry=None, patch_resource=None)),
|
||||||
('resource_update_fail', dict(measure=204, post_resource=None,
|
('resource_update_fail', dict(measure=204, post_resource=None,
|
||||||
|
Loading…
Reference in New Issue
Block a user