Merge "Add support to host command dynamic pollster definitions"
This commit is contained in:
commit
c7b53e0afb
|
@ -20,6 +20,7 @@
|
|||
import copy
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
import xmltodict
|
||||
|
||||
|
@ -205,7 +206,7 @@ class PollsterSampleExtractor(object):
|
|||
metadata=metadata, pollster_definitions=pollster_definitions)
|
||||
|
||||
extra_metadata = self.definitions.retrieve_extra_metadata(
|
||||
kwargs['manager'], pollster_sample)
|
||||
kwargs['manager'], pollster_sample, kwargs['conf'])
|
||||
|
||||
for key in extra_metadata.keys():
|
||||
if key in metadata.keys():
|
||||
|
@ -518,7 +519,8 @@ class PollsterDefinitions(object):
|
|||
default=3600),
|
||||
PollsterDefinition(name='extra_metadata_fields'),
|
||||
PollsterDefinition(name='response_handlers', default=['json'],
|
||||
validator=validate_response_handler)
|
||||
validator=validate_response_handler),
|
||||
PollsterDefinition(name='base_metadata', default={})
|
||||
]
|
||||
|
||||
extra_definitions = []
|
||||
|
@ -572,114 +574,75 @@ class PollsterDefinitions(object):
|
|||
"Required fields %s not specified."
|
||||
% missing, self.configurations)
|
||||
|
||||
def retrieve_extra_metadata(self, manager, request_sample):
|
||||
def retrieve_extra_metadata(self, manager, request_sample, pollster_conf):
|
||||
extra_metadata_fields = self.configurations['extra_metadata_fields']
|
||||
if extra_metadata_fields:
|
||||
if isinstance(self, NonOpenStackApisPollsterDefinition):
|
||||
raise declarative.NonOpenStackApisDynamicPollsterException(
|
||||
"Not supported the use of extra metadata gathering for "
|
||||
"non-openstack pollsters [%s] (yet)."
|
||||
% self.configurations['name'])
|
||||
|
||||
return self._retrieve_extra_metadata(
|
||||
extra_metadata_fields, manager, request_sample)
|
||||
extra_metadata_samples = {}
|
||||
extra_metadata_by_name = {}
|
||||
if not isinstance(extra_metadata_fields, (list, tuple)):
|
||||
extra_metadata_fields = [extra_metadata_fields]
|
||||
for ext_metadata in extra_metadata_fields:
|
||||
ext_metadata.setdefault(
|
||||
'sample_type', self.configurations['sample_type'])
|
||||
ext_metadata.setdefault('unit', self.configurations['unit'])
|
||||
ext_metadata.setdefault(
|
||||
'value_attribute', ext_metadata.get(
|
||||
'value', self.configurations['value_attribute']))
|
||||
ext_metadata['base_metadata'] = {
|
||||
'extra_metadata_captured': extra_metadata_samples,
|
||||
'extra_metadata_by_name': extra_metadata_by_name,
|
||||
'sample': request_sample
|
||||
}
|
||||
parent_cache_ttl = self.configurations[
|
||||
'extra_metadata_fields_cache_seconds']
|
||||
cache_ttl = ext_metadata.get(
|
||||
'extra_metadata_fields_cache_seconds', parent_cache_ttl
|
||||
)
|
||||
response_cache = self.response_cache
|
||||
extra_metadata_pollster = DynamicPollster(
|
||||
ext_metadata, conf=pollster_conf, cache_ttl=cache_ttl,
|
||||
extra_metadata_responses_cache=response_cache,
|
||||
)
|
||||
resources = [None]
|
||||
if ext_metadata.get('endpoint_type'):
|
||||
resources = manager.discover([
|
||||
extra_metadata_pollster.default_discovery], {})
|
||||
samples = extra_metadata_pollster.get_samples(
|
||||
manager, None, resources)
|
||||
for sample in samples:
|
||||
self.fill_extra_metadata_samples(
|
||||
extra_metadata_by_name,
|
||||
extra_metadata_samples,
|
||||
sample)
|
||||
return extra_metadata_samples
|
||||
|
||||
LOG.debug("No extra metadata to be captured for pollsters [%s] and "
|
||||
"request sample [%s].", self.definitions, request_sample)
|
||||
return {}
|
||||
|
||||
def _retrieve_extra_metadata(
|
||||
self, extra_metadata_fields, manager, request_sample):
|
||||
LOG.debug("Processing extra metadata fields [%s] for "
|
||||
"sample [%s].", extra_metadata_fields,
|
||||
request_sample)
|
||||
|
||||
extra_metadata_captured = {}
|
||||
for extra_metadata in extra_metadata_fields:
|
||||
extra_metadata_name = extra_metadata['name']
|
||||
|
||||
if extra_metadata_name in extra_metadata_captured.keys():
|
||||
LOG.warning("Duplicated extra metadata name [%s]. Therefore, "
|
||||
"we do not process this iteration [%s].",
|
||||
extra_metadata_name, extra_metadata)
|
||||
def fill_extra_metadata_samples(self, extra_metadata_by_name,
|
||||
extra_metadata_samples, sample):
|
||||
extra_metadata_samples[sample.name] = sample.volume
|
||||
LOG.debug("Merging the sample metadata [%s] of the "
|
||||
"extra_metadata_field [%s], with the "
|
||||
"extra_metadata_samples [%s].",
|
||||
sample.resource_metadata,
|
||||
sample.name,
|
||||
extra_metadata_samples)
|
||||
for key, value in sample.resource_metadata.items():
|
||||
if value is None and key in extra_metadata_samples:
|
||||
LOG.debug("Metadata [%s] for extra_metadata_field [%s] "
|
||||
"is None, skipping metadata override by None "
|
||||
"value", key, sample.name)
|
||||
continue
|
||||
extra_metadata_samples[key] = value
|
||||
extra_metadata_by_name[sample.name] = {
|
||||
'value': sample.volume,
|
||||
'metadata': sample.resource_metadata
|
||||
}
|
||||
|
||||
LOG.debug("Processing extra metadata [%s] for sample [%s].",
|
||||
extra_metadata_name, request_sample)
|
||||
|
||||
endpoint_type = 'endpoint:' + extra_metadata['endpoint_type']
|
||||
if not endpoint_type.endswith(
|
||||
PollsterDefinitions.EXTERNAL_ENDPOINT_TYPE):
|
||||
response = self.execute_openstack_extra_metadata_gathering(
|
||||
endpoint_type, extra_metadata, manager, request_sample,
|
||||
extra_metadata_captured)
|
||||
else:
|
||||
raise declarative.NonOpenStackApisDynamicPollsterException(
|
||||
"Not supported the use of extra metadata gathering for "
|
||||
"non-openstack endpoints [%s] (yet)." % extra_metadata)
|
||||
|
||||
extra_metadata_extractor_kwargs = {
|
||||
'value_attribute': extra_metadata['value'],
|
||||
'sample': request_sample}
|
||||
|
||||
extra_metadata_value = \
|
||||
self.sample_extractor.retrieve_attribute_nested_value(
|
||||
response, **extra_metadata_extractor_kwargs)
|
||||
|
||||
LOG.debug("Generated extra metadata [%s] with value [%s].",
|
||||
extra_metadata_name, extra_metadata_value)
|
||||
extra_metadata_captured[extra_metadata_name] = extra_metadata_value
|
||||
|
||||
return extra_metadata_captured
|
||||
|
||||
def execute_openstack_extra_metadata_gathering(self, endpoint_type,
|
||||
extra_metadata, manager,
|
||||
request_sample,
|
||||
extra_metadata_captured):
|
||||
url_for_endpoint_type = manager.discover(
|
||||
[endpoint_type], self.response_cache)
|
||||
|
||||
LOG.debug("URL [%s] found for endpoint type [%s].",
|
||||
url_for_endpoint_type, endpoint_type)
|
||||
|
||||
if url_for_endpoint_type:
|
||||
url_for_endpoint_type = url_for_endpoint_type[0]
|
||||
|
||||
self.sample_gatherer.generate_url_path(
|
||||
extra_metadata, request_sample, extra_metadata_captured)
|
||||
|
||||
cached_response, max_ttl_for_cache = self.response_cache.get(
|
||||
extra_metadata['url_path'], (None, None))
|
||||
|
||||
extra_metadata_fields_cache_seconds = extra_metadata.get(
|
||||
'extra_metadata_fields_cache_seconds',
|
||||
self.configurations['extra_metadata_fields_cache_seconds'])
|
||||
|
||||
current_time = time.time()
|
||||
if cached_response and max_ttl_for_cache >= current_time:
|
||||
LOG.debug("Returning response [%s] for request [%s] as the TTL "
|
||||
"[max=%s, current_time=%s] has not expired yet.",
|
||||
cached_response, extra_metadata['url_path'],
|
||||
max_ttl_for_cache, current_time)
|
||||
return cached_response
|
||||
|
||||
if cached_response:
|
||||
LOG.debug("Cleaning cached response [%s] for request [%s] "
|
||||
"as the TTL [max=%s, current_time=%s] has expired.",
|
||||
cached_response, extra_metadata['url_path'],
|
||||
max_ttl_for_cache, current_time)
|
||||
|
||||
response = self.sample_gatherer.execute_request_for_definitions(
|
||||
extra_metadata, **{'manager': manager,
|
||||
'keystone_client': manager._keystone,
|
||||
'resource': url_for_endpoint_type,
|
||||
'execute_id_overrides': False})
|
||||
|
||||
max_ttl_for_cache = time.time() + extra_metadata_fields_cache_seconds
|
||||
|
||||
cache_tuple = (response, max_ttl_for_cache)
|
||||
self.response_cache[extra_metadata['url_path']] = cache_tuple
|
||||
return response
|
||||
LOG.debug("extra_metadata_samples after merging: [%s].",
|
||||
extra_metadata_samples)
|
||||
|
||||
|
||||
class MultiMetricPollsterDefinitions(PollsterDefinitions):
|
||||
|
@ -739,6 +702,42 @@ class PollsterSampleGatherer(object):
|
|||
url_path=definitions.configurations['url_path']
|
||||
)
|
||||
|
||||
def get_cache_key(self, definitions, **kwargs):
|
||||
return self.get_request_linked_samples_url(kwargs, definitions)
|
||||
|
||||
def get_cached_response(self, definitions, **kwargs):
|
||||
if self.definitions.cache_ttl == 0:
|
||||
return
|
||||
cache_key = self.get_cache_key(definitions, **kwargs)
|
||||
response_cache = self.definitions.response_cache
|
||||
cached_response, max_ttl_for_cache = response_cache.get(
|
||||
cache_key, (None, None))
|
||||
|
||||
current_time = time.time()
|
||||
if cached_response and max_ttl_for_cache >= current_time:
|
||||
LOG.debug("Returning response [%s] for request [%s] as the TTL "
|
||||
"[max=%s, current_time=%s] has not expired yet.",
|
||||
cached_response, definitions,
|
||||
max_ttl_for_cache, current_time)
|
||||
return cached_response
|
||||
|
||||
if cached_response and max_ttl_for_cache < current_time:
|
||||
LOG.debug("Cleaning cached response [%s] for request [%s] "
|
||||
"as the TTL [max=%s, current_time=%s] has expired.",
|
||||
cached_response, definitions,
|
||||
max_ttl_for_cache, current_time)
|
||||
response_cache.pop(cache_key, None)
|
||||
|
||||
def store_cached_response(self, definitions, resp, **kwargs):
|
||||
if self.definitions.cache_ttl == 0:
|
||||
return
|
||||
cache_key = self.get_cache_key(definitions, **kwargs)
|
||||
extra_metadata_fields_cache_seconds = self.definitions.cache_ttl
|
||||
max_ttl_for_cache = time.time() + extra_metadata_fields_cache_seconds
|
||||
|
||||
cache_tuple = (resp, max_ttl_for_cache)
|
||||
self.definitions.response_cache[cache_key] = cache_tuple
|
||||
|
||||
@property
|
||||
def default_discovery(self):
|
||||
return 'endpoint:' + self.definitions.configurations['endpoint_type']
|
||||
|
@ -748,10 +747,14 @@ class PollsterSampleGatherer(object):
|
|||
self.definitions.configurations, **kwargs)
|
||||
|
||||
def execute_request_for_definitions(self, definitions, **kwargs):
|
||||
resp, url = self._internal_execute_request_get_samples(
|
||||
definitions=definitions, **kwargs)
|
||||
if response_dict := self.get_cached_response(definitions, **kwargs):
|
||||
url = 'cached'
|
||||
else:
|
||||
resp, url = self._internal_execute_request_get_samples(
|
||||
definitions=definitions, **kwargs)
|
||||
response_dict = self.response_handler_chain.handle(resp.text)
|
||||
self.store_cached_response(definitions, response_dict, **kwargs)
|
||||
|
||||
response_dict = self.response_handler_chain.handle(resp.text)
|
||||
entry_size = len(response_dict)
|
||||
LOG.debug("Entries [%s] in the DICT for request [%s] "
|
||||
"for dynamic pollster [%s].",
|
||||
|
@ -790,21 +793,6 @@ class PollsterSampleGatherer(object):
|
|||
self.generate_new_attributes_in_sample(
|
||||
request_sample, resource_id_attribute, 'id')
|
||||
|
||||
def generate_url_path(self, extra_metadata, sample,
|
||||
extra_metadata_captured):
|
||||
if not extra_metadata.get('url_path_original'):
|
||||
extra_metadata[
|
||||
'url_path_original'] = extra_metadata['url_path']
|
||||
|
||||
extra_metadata['url_path'] = eval(
|
||||
extra_metadata['url_path_original'])
|
||||
|
||||
LOG.debug("URL [%s] generated for pattern [%s] for sample [%s] and "
|
||||
"extra metadata captured [%s].",
|
||||
extra_metadata['url_path'],
|
||||
extra_metadata['url_path_original'], sample,
|
||||
extra_metadata_captured)
|
||||
|
||||
def generate_new_attributes_in_sample(
|
||||
self, sample, attribute_key, new_attribute_key):
|
||||
|
||||
|
@ -881,6 +869,15 @@ class PollsterSampleGatherer(object):
|
|||
|
||||
def get_request_url(self, kwargs, url_path):
|
||||
endpoint = kwargs['resource']
|
||||
params = copy.deepcopy(
|
||||
self.definitions.configurations.get(
|
||||
'base_metadata', {}))
|
||||
try:
|
||||
url_path = eval(url_path, params)
|
||||
except Exception:
|
||||
LOG.debug("Cannot eval path [%s] with params [%s],"
|
||||
" using [%s] instead.",
|
||||
url_path, params, url_path)
|
||||
return urlparse.urljoin(endpoint, url_path)
|
||||
|
||||
def retrieve_entries_from_response(self, response_json, definitions):
|
||||
|
@ -919,6 +916,57 @@ class NonOpenStackApisPollsterDefinition(PollsterDefinitions):
|
|||
return configurations.get('module')
|
||||
|
||||
|
||||
class HostCommandPollsterDefinition(PollsterDefinitions):
|
||||
|
||||
extra_definitions = [
|
||||
PollsterDefinition(name='endpoint_type', required=False),
|
||||
PollsterDefinition(name='url_path', required=False),
|
||||
PollsterDefinition(name='host_command', required=True)]
|
||||
|
||||
def __init__(self, configurations):
|
||||
super(HostCommandPollsterDefinition, self).__init__(
|
||||
configurations)
|
||||
self.sample_gatherer = HostCommandSamplesGatherer(self)
|
||||
|
||||
@staticmethod
|
||||
def is_field_applicable_to_definition(configurations):
|
||||
return configurations.get('host_command')
|
||||
|
||||
|
||||
class HostCommandSamplesGatherer(PollsterSampleGatherer):
|
||||
|
||||
class Response(object):
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def get_cache_key(self, definitions, **kwargs):
|
||||
return self.get_command(definitions)
|
||||
|
||||
def _internal_execute_request_get_samples(self, definitions, **kwargs):
|
||||
command = self.get_command(definitions, **kwargs)
|
||||
LOG.debug('Running Host command: [%s]', command)
|
||||
result = subprocess.getoutput(command)
|
||||
LOG.debug('Host command [%s] result: [%s]', command, result)
|
||||
return self.Response(result), command
|
||||
|
||||
def get_command(self, definitions, next_sample_url=None, **kwargs):
|
||||
command = next_sample_url or definitions['host_command']
|
||||
params = copy.deepcopy(
|
||||
self.definitions.configurations.get(
|
||||
'base_metadata', {}))
|
||||
try:
|
||||
command = eval(command, params)
|
||||
except Exception:
|
||||
LOG.debug("Cannot eval command [%s] with params [%s],"
|
||||
" using [%s] instead.",
|
||||
command, params, command)
|
||||
return command
|
||||
|
||||
@property
|
||||
def default_discovery(self):
|
||||
return 'local_node'
|
||||
|
||||
|
||||
class NonOpenStackApisSamplesGatherer(PollsterSampleGatherer):
|
||||
|
||||
@property
|
||||
|
@ -1010,8 +1058,10 @@ class DynamicPollster(plugin_base.PollsterBase):
|
|||
# Mandatory name field
|
||||
name = ""
|
||||
|
||||
def __init__(self, pollster_definitions={}, conf=None,
|
||||
supported_definitions=[NonOpenStackApisPollsterDefinition,
|
||||
def __init__(self, pollster_definitions={}, conf=None, cache_ttl=0,
|
||||
extra_metadata_responses_cache=None,
|
||||
supported_definitions=[HostCommandPollsterDefinition,
|
||||
NonOpenStackApisPollsterDefinition,
|
||||
MultiMetricPollsterDefinitions,
|
||||
SingleMetricPollsterDefinitions]):
|
||||
super(DynamicPollster, self).__init__(conf)
|
||||
|
@ -1021,6 +1071,10 @@ class DynamicPollster(plugin_base.PollsterBase):
|
|||
|
||||
self.definitions = PollsterDefinitionBuilder(
|
||||
self.supported_definitions).build_definitions(pollster_definitions)
|
||||
self.definitions.cache_ttl = cache_ttl
|
||||
self.definitions.response_cache = extra_metadata_responses_cache
|
||||
if extra_metadata_responses_cache is None:
|
||||
self.definitions.response_cache = {}
|
||||
self.pollster_definitions = self.definitions.configurations
|
||||
if 'metadata_fields' in self.pollster_definitions:
|
||||
LOG.debug("Metadata fields configured to [%s].",
|
||||
|
@ -1054,9 +1108,12 @@ class DynamicPollster(plugin_base.PollsterBase):
|
|||
for r in resources:
|
||||
LOG.debug("Executing get sample for resource [%s].", r)
|
||||
samples = self.load_samples(r, manager)
|
||||
if not isinstance(samples, (list, tuple)):
|
||||
samples = [samples]
|
||||
for pollster_sample in samples:
|
||||
kwargs = {'manager': manager, 'resource': r}
|
||||
sample = self.extract_sample(pollster_sample, **kwargs)
|
||||
sample = self.extract_sample(
|
||||
pollster_sample, manager=manager,
|
||||
resource=r, conf=self.conf)
|
||||
if isinstance(sample, SkippedSample):
|
||||
continue
|
||||
yield from sample
|
||||
|
|
|
@ -388,6 +388,424 @@ class TestDynamicPollster(base.BaseTestCase):
|
|||
|
||||
self.assertEqual(4, len(samples))
|
||||
|
||||
@mock.patch('keystoneclient.v2_0.client.Client')
|
||||
def test_execute_request_extra_metadata_fields_cache_disabled(
|
||||
self, client_mock):
|
||||
definitions = copy.deepcopy(
|
||||
self.pollster_definition_only_required_fields)
|
||||
extra_metadata_fields = {
|
||||
'extra_metadata_fields_cache_seconds': 0,
|
||||
'name': "project_name",
|
||||
'endpoint_type': "identity",
|
||||
'url_path': "'/v3/projects/' + str(sample['project_id'])",
|
||||
'value': "name",
|
||||
}
|
||||
definitions['value_attribute'] = 'project_id'
|
||||
definitions['extra_metadata_fields'] = extra_metadata_fields
|
||||
pollster = dynamic_pollster.DynamicPollster(definitions)
|
||||
|
||||
return_value = self.FakeResponse()
|
||||
return_value.status_code = requests.codes.ok
|
||||
return_value._text = '''
|
||||
{"projects": [
|
||||
{"project_id": 9999, "name": "project1"},
|
||||
{"project_id": 8888, "name": "project2"},
|
||||
{"project_id": 7777, "name": "project3"},
|
||||
{"project_id": 9999, "name": "project1"},
|
||||
{"project_id": 8888, "name": "project2"},
|
||||
{"project_id": 7777, "name": "project3"},
|
||||
{"project_id": 9999, "name": "project1"},
|
||||
{"project_id": 8888, "name": "project2"},
|
||||
{"project_id": 7777, "name": "project3"}]
|
||||
}
|
||||
'''
|
||||
|
||||
return_value9999 = self.FakeResponse()
|
||||
return_value9999.status_code = requests.codes.ok
|
||||
return_value9999._text = '''
|
||||
{"project":
|
||||
{"project_id": 9999, "name": "project1"}
|
||||
}
|
||||
'''
|
||||
|
||||
return_value8888 = self.FakeResponse()
|
||||
return_value8888.status_code = requests.codes.ok
|
||||
return_value8888._text = '''
|
||||
{"project":
|
||||
{"project_id": 8888, "name": "project2"}
|
||||
}
|
||||
'''
|
||||
|
||||
return_value7777 = self.FakeResponse()
|
||||
return_value7777.status_code = requests.codes.ok
|
||||
return_value7777._text = '''
|
||||
{"project":
|
||||
{"project_id": 7777, "name": "project3"}
|
||||
}
|
||||
'''
|
||||
|
||||
def get(url, *args, **kwargs):
|
||||
if '9999' in url:
|
||||
return return_value9999
|
||||
if '8888' in url:
|
||||
return return_value8888
|
||||
if '7777' in url:
|
||||
return return_value7777
|
||||
return return_value
|
||||
|
||||
client_mock.session.get.side_effect = get
|
||||
manager = mock.Mock
|
||||
manager._keystone = client_mock
|
||||
|
||||
def discover(*args, **kwargs):
|
||||
return ["https://endpoint.server.name/"]
|
||||
|
||||
manager.discover = discover
|
||||
samples = pollster.get_samples(
|
||||
manager=manager, cache=None,
|
||||
resources=["https://endpoint.server.name/"])
|
||||
|
||||
samples = list(samples)
|
||||
|
||||
n_calls = client_mock.session.get.call_count
|
||||
self.assertEqual(9, len(samples))
|
||||
self.assertEqual(10, n_calls)
|
||||
|
||||
@mock.patch('keystoneclient.v2_0.client.Client')
|
||||
def test_execute_request_extra_metadata_fields_cache_enabled(
|
||||
self, client_mock):
|
||||
definitions = copy.deepcopy(
|
||||
self.pollster_definition_only_required_fields)
|
||||
extra_metadata_fields = {
|
||||
'extra_metadata_fields_cache_seconds': 3600,
|
||||
'name': "project_name",
|
||||
'endpoint_type': "identity",
|
||||
'url_path': "'/v3/projects/' + str(sample['project_id'])",
|
||||
'value': "name",
|
||||
}
|
||||
definitions['value_attribute'] = 'project_id'
|
||||
definitions['extra_metadata_fields'] = extra_metadata_fields
|
||||
pollster = dynamic_pollster.DynamicPollster(definitions)
|
||||
|
||||
return_value = self.FakeResponse()
|
||||
return_value.status_code = requests.codes.ok
|
||||
return_value._text = '''
|
||||
{"projects": [
|
||||
{"project_id": 9999, "name": "project1"},
|
||||
{"project_id": 8888, "name": "project2"},
|
||||
{"project_id": 7777, "name": "project3"},
|
||||
{"project_id": 9999, "name": "project4"},
|
||||
{"project_id": 8888, "name": "project5"},
|
||||
{"project_id": 7777, "name": "project6"},
|
||||
{"project_id": 9999, "name": "project7"},
|
||||
{"project_id": 8888, "name": "project8"},
|
||||
{"project_id": 7777, "name": "project9"}]
|
||||
}
|
||||
'''
|
||||
|
||||
return_value9999 = self.FakeResponse()
|
||||
return_value9999.status_code = requests.codes.ok
|
||||
return_value9999._text = '''
|
||||
{"project":
|
||||
{"project_id": 9999, "name": "project1"}
|
||||
}
|
||||
'''
|
||||
|
||||
return_value8888 = self.FakeResponse()
|
||||
return_value8888.status_code = requests.codes.ok
|
||||
return_value8888._text = '''
|
||||
{"project":
|
||||
{"project_id": 8888, "name": "project2"}
|
||||
}
|
||||
'''
|
||||
|
||||
return_value7777 = self.FakeResponse()
|
||||
return_value7777.status_code = requests.codes.ok
|
||||
return_value7777._text = '''
|
||||
{"project":
|
||||
{"project_id": 7777, "name": "project3"}
|
||||
}
|
||||
'''
|
||||
|
||||
def get(url, *args, **kwargs):
|
||||
if '9999' in url:
|
||||
return return_value9999
|
||||
if '8888' in url:
|
||||
return return_value8888
|
||||
if '7777' in url:
|
||||
return return_value7777
|
||||
return return_value
|
||||
|
||||
client_mock.session.get.side_effect = get
|
||||
manager = mock.Mock
|
||||
manager._keystone = client_mock
|
||||
|
||||
def discover(*args, **kwargs):
|
||||
return ["https://endpoint.server.name/"]
|
||||
|
||||
manager.discover = discover
|
||||
samples = pollster.get_samples(
|
||||
manager=manager, cache=None,
|
||||
resources=["https://endpoint.server.name/"])
|
||||
|
||||
samples = list(samples)
|
||||
|
||||
n_calls = client_mock.session.get.call_count
|
||||
self.assertEqual(9, len(samples))
|
||||
self.assertEqual(4, n_calls)
|
||||
|
||||
@mock.patch('keystoneclient.v2_0.client.Client')
|
||||
def test_execute_request_extra_metadata_fields(
|
||||
self, client_mock):
|
||||
definitions = copy.deepcopy(
|
||||
self.pollster_definition_only_required_fields)
|
||||
extra_metadata_fields = [{
|
||||
'name': "project_name",
|
||||
'endpoint_type': "identity",
|
||||
'url_path': "'/v3/projects/' + str(sample['project_id'])",
|
||||
'value': "name",
|
||||
'metadata_fields': ['meta']
|
||||
}, {
|
||||
'name': "project_alias",
|
||||
'endpoint_type': "identity",
|
||||
'url_path': "'/v3/projects/' + "
|
||||
"str(extra_metadata_captured['project_name'])",
|
||||
'value': "name",
|
||||
'metadata_fields': ['meta']
|
||||
}, {
|
||||
'name': "project_meta",
|
||||
'endpoint_type': "identity",
|
||||
'url_path': "'/v3/projects/' + "
|
||||
"str(extra_metadata_by_name['project_name']"
|
||||
"['metadata']['meta'])",
|
||||
'value': "project_id",
|
||||
'metadata_fields': ['meta']
|
||||
}]
|
||||
definitions['value_attribute'] = 'project_id'
|
||||
definitions['extra_metadata_fields'] = extra_metadata_fields
|
||||
pollster = dynamic_pollster.DynamicPollster(definitions)
|
||||
|
||||
return_value = self.FakeResponse()
|
||||
return_value.status_code = requests.codes.ok
|
||||
return_value._text = '''
|
||||
{"projects": [
|
||||
{"project_id": 9999, "name": "project1"},
|
||||
{"project_id": 8888, "name": "project2"},
|
||||
{"project_id": 7777, "name": "project3"}]
|
||||
}
|
||||
'''
|
||||
|
||||
return_value9999 = self.FakeResponse()
|
||||
return_value9999.status_code = requests.codes.ok
|
||||
return_value9999._text = '''
|
||||
{"project":
|
||||
{"project_id": 9999, "name": "project1",
|
||||
"meta": "m1"}
|
||||
}
|
||||
'''
|
||||
|
||||
return_value8888 = self.FakeResponse()
|
||||
return_value8888.status_code = requests.codes.ok
|
||||
return_value8888._text = '''
|
||||
{"project":
|
||||
{"project_id": 8888, "name": "project2",
|
||||
"meta": "m2"}
|
||||
}
|
||||
'''
|
||||
|
||||
return_value7777 = self.FakeResponse()
|
||||
return_value7777.status_code = requests.codes.ok
|
||||
return_value7777._text = '''
|
||||
{"project":
|
||||
{"project_id": 7777, "name": "project3",
|
||||
"meta": "m3"}
|
||||
}
|
||||
'''
|
||||
|
||||
return_valueP1 = self.FakeResponse()
|
||||
return_valueP1.status_code = requests.codes.ok
|
||||
return_valueP1._text = '''
|
||||
{"project":
|
||||
{"project_id": 7777, "name": "p1",
|
||||
"meta": null}
|
||||
}
|
||||
'''
|
||||
|
||||
return_valueP2 = self.FakeResponse()
|
||||
return_valueP2.status_code = requests.codes.ok
|
||||
return_valueP2._text = '''
|
||||
{"project":
|
||||
{"project_id": 7777, "name": "p2",
|
||||
"meta": null}
|
||||
}
|
||||
'''
|
||||
|
||||
return_valueP3 = self.FakeResponse()
|
||||
return_valueP3.status_code = requests.codes.ok
|
||||
return_valueP3._text = '''
|
||||
{"project":
|
||||
{"project_id": 7777, "name": "p3",
|
||||
"meta": null}
|
||||
}
|
||||
'''
|
||||
|
||||
return_valueM1 = self.FakeResponse()
|
||||
return_valueM1.status_code = requests.codes.ok
|
||||
return_valueM1._text = '''
|
||||
{"project":
|
||||
{"project_id": "META1", "name": "p3",
|
||||
"meta": null}
|
||||
}
|
||||
'''
|
||||
|
||||
return_valueM2 = self.FakeResponse()
|
||||
return_valueM2.status_code = requests.codes.ok
|
||||
return_valueM2._text = '''
|
||||
{"project":
|
||||
{"project_id": "META2", "name": "p3",
|
||||
"meta": null}
|
||||
}
|
||||
'''
|
||||
|
||||
return_valueM3 = self.FakeResponse()
|
||||
return_valueM3.status_code = requests.codes.ok
|
||||
return_valueM3._text = '''
|
||||
{"project":
|
||||
{"project_id": "META3", "name": "p3",
|
||||
"meta": null}
|
||||
}
|
||||
'''
|
||||
|
||||
def get(url, *args, **kwargs):
|
||||
if '9999' in url:
|
||||
return return_value9999
|
||||
if '8888' in url:
|
||||
return return_value8888
|
||||
if '7777' in url:
|
||||
return return_value7777
|
||||
if 'project1' in url:
|
||||
return return_valueP1
|
||||
if 'project2' in url:
|
||||
return return_valueP2
|
||||
if 'project3' in url:
|
||||
return return_valueP3
|
||||
if 'm1' in url:
|
||||
return return_valueM1
|
||||
if 'm2' in url:
|
||||
return return_valueM2
|
||||
if 'm3' in url:
|
||||
return return_valueM3
|
||||
|
||||
return return_value
|
||||
|
||||
client_mock.session.get = get
|
||||
manager = mock.Mock
|
||||
manager._keystone = client_mock
|
||||
|
||||
def discover(*args, **kwargs):
|
||||
return ["https://endpoint.server.name/"]
|
||||
|
||||
manager.discover = discover
|
||||
samples = pollster.get_samples(
|
||||
manager=manager, cache=None,
|
||||
resources=["https://endpoint.server.name/"])
|
||||
|
||||
samples = list(samples)
|
||||
self.assertEqual(3, len(samples))
|
||||
|
||||
self.assertEqual(samples[0].volume, 9999)
|
||||
self.assertEqual(samples[1].volume, 8888)
|
||||
self.assertEqual(samples[2].volume, 7777)
|
||||
|
||||
self.assertEqual(samples[0].resource_metadata,
|
||||
{'project_name': 'project1',
|
||||
'project_alias': 'p1',
|
||||
'meta': 'm1',
|
||||
'project_meta': 'META1'})
|
||||
self.assertEqual(samples[1].resource_metadata,
|
||||
{'project_name': 'project2',
|
||||
'project_alias': 'p2',
|
||||
'meta': 'm2',
|
||||
'project_meta': 'META2'})
|
||||
self.assertEqual(samples[2].resource_metadata,
|
||||
{'project_name': 'project3',
|
||||
'project_alias': 'p3',
|
||||
'meta': 'm3',
|
||||
'project_meta': 'META3'})
|
||||
|
||||
@mock.patch('keystoneclient.v2_0.client.Client')
|
||||
def test_execute_request_extra_metadata_fields_different_requests(
|
||||
self, client_mock):
|
||||
definitions = copy.deepcopy(
|
||||
self.pollster_definition_only_required_fields)
|
||||
|
||||
command = ''' \'\'\'echo '{"project":
|
||||
{"project_id": \'\'\'+ str(sample['project_id'])
|
||||
+\'\'\' , "name": "project1"}}' \'\'\' '''.replace('\n', '')
|
||||
|
||||
command2 = ''' \'\'\'echo '{"project":
|
||||
{"project_id": \'\'\'+ str(sample['project_id'])
|
||||
+\'\'\' , "name": "project2"}}' \'\'\' '''.replace('\n', '')
|
||||
|
||||
extra_metadata_fields_embedded = {
|
||||
'name': "project_name2",
|
||||
'host_command': command2,
|
||||
'value': "name",
|
||||
}
|
||||
|
||||
extra_metadata_fields = {
|
||||
'name': "project_id2",
|
||||
'host_command': command,
|
||||
'value': "project_id",
|
||||
'extra_metadata_fields': extra_metadata_fields_embedded
|
||||
}
|
||||
|
||||
definitions['value_attribute'] = 'project_id'
|
||||
definitions['extra_metadata_fields'] = extra_metadata_fields
|
||||
pollster = dynamic_pollster.DynamicPollster(definitions)
|
||||
|
||||
return_value = self.FakeResponse()
|
||||
return_value.status_code = requests.codes.ok
|
||||
return_value._text = '''
|
||||
{"projects": [
|
||||
{"project_id": 9999, "name": "project1"},
|
||||
{"project_id": 8888, "name": "project2"},
|
||||
{"project_id": 7777, "name": "project3"}]
|
||||
}
|
||||
'''
|
||||
|
||||
def get(url, *args, **kwargs):
|
||||
return return_value
|
||||
|
||||
client_mock.session.get = get
|
||||
manager = mock.Mock
|
||||
manager._keystone = client_mock
|
||||
|
||||
def discover(*args, **kwargs):
|
||||
return ["https://endpoint.server.name/"]
|
||||
|
||||
manager.discover = discover
|
||||
samples = pollster.get_samples(
|
||||
manager=manager, cache=None,
|
||||
resources=["https://endpoint.server.name/"])
|
||||
|
||||
samples = list(samples)
|
||||
self.assertEqual(3, len(samples))
|
||||
|
||||
self.assertEqual(samples[0].volume, 9999)
|
||||
self.assertEqual(samples[1].volume, 8888)
|
||||
self.assertEqual(samples[2].volume, 7777)
|
||||
|
||||
self.assertEqual(samples[0].resource_metadata,
|
||||
{'project_id2': 9999,
|
||||
'project_name2': 'project2'})
|
||||
self.assertEqual(samples[1].resource_metadata,
|
||||
{'project_id2': 8888,
|
||||
'project_name2': 'project2'})
|
||||
self.assertEqual(samples[2].resource_metadata,
|
||||
{'project_id2': 7777,
|
||||
'project_name2': 'project2'})
|
||||
|
||||
@mock.patch('keystoneclient.v2_0.client.Client')
|
||||
def test_execute_request_xml_json_response_handler_invalid_response(
|
||||
self, client_mock):
|
||||
|
@ -410,8 +828,8 @@ class TestDynamicPollster(base.BaseTestCase):
|
|||
keystone_client=client_mock,
|
||||
resource="https://endpoint.server.name/")
|
||||
|
||||
xml_handling_error = logs.output[2]
|
||||
json_handling_error = logs.output[3]
|
||||
xml_handling_error = logs.output[3]
|
||||
json_handling_error = logs.output[4]
|
||||
|
||||
self.assertIn(
|
||||
'DEBUG:ceilometer.polling.dynamic_pollster:'
|
||||
|
@ -479,6 +897,57 @@ class TestDynamicPollster(base.BaseTestCase):
|
|||
resource="https://endpoint.server.name/")
|
||||
self.assertEqual("Mock HTTP error.", str(exception))
|
||||
|
||||
def test_execute_host_command_paged_responses(self):
|
||||
definitions = copy.deepcopy(
|
||||
self.pollster_definition_only_required_fields)
|
||||
definitions['host_command'] = '''
|
||||
echo '{"server": [{"status": "ACTIVE"}], "next": ""}'
|
||||
'''
|
||||
str_json = "'{\\\"server\\\": [{\\\"status\\\": \\\"INACTIVE\\\"}]}'"
|
||||
definitions['next_sample_url_attribute'] = \
|
||||
"next|\"echo \"+value+\"" + str_json + '"'
|
||||
pollster = dynamic_pollster.DynamicPollster(definitions)
|
||||
samples = pollster.definitions.sample_gatherer. \
|
||||
execute_request_get_samples()
|
||||
resp_json = [{'status': 'ACTIVE'}, {'status': 'INACTIVE'}]
|
||||
self.assertEqual(resp_json, samples)
|
||||
|
||||
def test_execute_host_command_response_handler(self):
|
||||
definitions = copy.deepcopy(
|
||||
self.pollster_definition_only_required_fields)
|
||||
definitions['response_handlers'] = ['xml', 'json']
|
||||
definitions['host_command'] = 'echo "<a><y>xml\n</y><s>xml</s></a>"'
|
||||
entry = 'a'
|
||||
definitions['response_entries_key'] = entry
|
||||
definitions.pop('url_path')
|
||||
definitions.pop('endpoint_type')
|
||||
pollster = dynamic_pollster.DynamicPollster(definitions)
|
||||
|
||||
samples_xml = pollster.definitions.sample_gatherer. \
|
||||
execute_request_get_samples()
|
||||
|
||||
definitions['host_command'] = 'echo \'{"a": {"y":"json",' \
|
||||
'\n"s":"json"}}\''
|
||||
samples_json = pollster.definitions.sample_gatherer. \
|
||||
execute_request_get_samples()
|
||||
|
||||
resp_xml = {'a': {'y': 'xml', 's': 'xml'}}
|
||||
resp_json = {'a': {'y': 'json', 's': 'json'}}
|
||||
self.assertEqual(resp_xml[entry], samples_xml)
|
||||
self.assertEqual(resp_json[entry], samples_json)
|
||||
|
||||
def test_execute_host_command_invalid_command(self):
|
||||
definitions = copy.deepcopy(
|
||||
self.pollster_definition_only_required_fields)
|
||||
definitions['host_command'] = 'invalid-command'
|
||||
definitions.pop('url_path')
|
||||
definitions.pop('endpoint_type')
|
||||
pollster = dynamic_pollster.DynamicPollster(definitions)
|
||||
|
||||
self.assertRaises(
|
||||
declarative.InvalidResponseTypeException,
|
||||
pollster.definitions.sample_gatherer.execute_request_get_samples)
|
||||
|
||||
def test_generate_new_metadata_fields_no_metadata_mapping(self):
|
||||
metadata = {'name': 'someName',
|
||||
'value': 1}
|
||||
|
@ -1105,7 +1574,7 @@ class TestDynamicPollster(base.BaseTestCase):
|
|||
|
||||
sample = pollster.definitions.sample_extractor.generate_sample(
|
||||
pollster_sample, pollster.definitions.configurations,
|
||||
manager=mock.Mock())
|
||||
manager=mock.Mock(), conf={})
|
||||
|
||||
self.assertEqual(1, sample.volume)
|
||||
self.assertEqual(2, len(sample.resource_metadata))
|
||||
|
@ -1127,7 +1596,7 @@ class TestDynamicPollster(base.BaseTestCase):
|
|||
|
||||
sample = pollster.definitions.sample_extractor.generate_sample(
|
||||
pollster_sample, pollster.definitions.configurations,
|
||||
manager=mock.Mock())
|
||||
manager=mock.Mock(), conf={})
|
||||
|
||||
self.assertEqual(1, sample.volume)
|
||||
self.assertEqual(3, len(sample.resource_metadata))
|
||||
|
@ -1150,7 +1619,7 @@ class TestDynamicPollster(base.BaseTestCase):
|
|||
|
||||
sample = pollster.definitions.sample_extractor.generate_sample(
|
||||
pollster_sample, pollster.definitions.configurations,
|
||||
manager=mock.Mock())
|
||||
manager=mock.Mock(), conf={})
|
||||
|
||||
self.assertEqual(1, sample.volume)
|
||||
self.assertEqual(3, len(sample.resource_metadata))
|
||||
|
|
|
@ -471,6 +471,62 @@ ones), we can use the `successful_ops`.
|
|||
resource_id_attribute: "user"
|
||||
response_entries_key: "summary"
|
||||
|
||||
The dynamic pollsters system configuration (for local host commands)
|
||||
--------------------------------------------------------------------
|
||||
|
||||
The dynamic pollster system can also be used for local host commands,
|
||||
these commands must be installed in the system that is running the
|
||||
Ceilometer compute agent.
|
||||
To configure local hosts commands, one can use all but two attributes of
|
||||
the Dynamic pollster system. The attributes that are not supported are
|
||||
the ``endpoint_type`` and ``url_path``. The dynamic pollster system for
|
||||
local host commands is activated automatically when one uses the
|
||||
configuration ``host_command``.
|
||||
|
||||
The extra parameter (in addition to the original ones) that is available
|
||||
when using the local host commands dynamic pollster sub-subsystem is the
|
||||
following:
|
||||
|
||||
* ``host_command``: required parameter. It is the host command that will
|
||||
be executed in the same host the Ceilometer dynamic pollster agent is
|
||||
running. The output of the command will be processed by the pollster and
|
||||
stored in the configured backend.
|
||||
|
||||
As follows we present an example on how to use the local host command:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
|
||||
- name: "dynamic.host.command"
|
||||
sample_type: "gauge"
|
||||
unit: "request"
|
||||
value_attribute: "value"
|
||||
response_entries_key: "test"
|
||||
host_command: "echo '<test><user_id>id1_u</user_id><project_id>id1_p</project_id><id>id1</id><meta>meta-data-to-store</meta><value>1</value></test>'"
|
||||
metadata_fields:
|
||||
- "meta"
|
||||
response_handlers:
|
||||
- xml
|
||||
|
||||
To execute multi page host commands, the `next_sample_url_attribute`
|
||||
must generate the next sample command, like the following example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
|
||||
- name: "dynamic.s3.objects.size"
|
||||
sample_type: "gauge"
|
||||
unit: "request"
|
||||
value_attribute: "Size"
|
||||
project_id_attribute: "Owner.ID"
|
||||
user_id_attribute: "Owner.ID"
|
||||
resource_id_attribute: "Key"
|
||||
response_entries_key: "Contents"
|
||||
host_command: "aws s3api list-objects"
|
||||
next_sample_url_attribute: NextToken | 'aws s3api list-objects --starting-token "' + value + '"'
|
||||
|
||||
Operations on extracted attributes
|
||||
----------------------------------
|
||||
|
||||
|
@ -876,12 +932,10 @@ we only have the `tenant_id`, which must be used as the `project_id`. However,
|
|||
for billing and later invoicing one might need/want the project name, domain
|
||||
id, and other metadata that are available in Keystone (and maybe some others
|
||||
that are scattered over other components). To achieve that, one can use the
|
||||
OpenStack metadata enrichment option. This feature is only available
|
||||
to *OpenStack pollsters*, and can only gather extra metadata from OpenStack
|
||||
APIs. As follows we present an example that shows a dynamic pollster
|
||||
configuration to gather virtual machine (VM) status, and to enrich the data
|
||||
pushed to the storage backend (e.g. Gnocchi) with project name, domain ID,
|
||||
and domain name.
|
||||
OpenStack metadata enrichment option. As follows we present an example that
|
||||
shows a dynamic pollster configuration to gather virtual machine (VM) status,
|
||||
and to enrich the data pushed to the storage backend (e.g. Gnocchi) with
|
||||
project name, domain ID, and domain name.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
@ -937,26 +991,59 @@ and domain name.
|
|||
"Openstack-API-Version": "identity latest"
|
||||
value: "name"
|
||||
extra_metadata_fields_cache_seconds: 1800 # overriding the default cache policy
|
||||
metadata_fields:
|
||||
- id
|
||||
- name: "domain_id"
|
||||
endpoint_type: "identity"
|
||||
url_path: "'/v3/projects/' + str(sample['project_id'])"
|
||||
headers:
|
||||
"Openstack-API-Version": "identity latest"
|
||||
value: "domain_id"
|
||||
metadata_fields:
|
||||
- id
|
||||
- name: "domain_name"
|
||||
endpoint_type: "identity"
|
||||
url_path: "'/v3/domains/' + str(extra_metadata_captured['domain_id'])"
|
||||
headers:
|
||||
"Openstack-API-Version": "identity latest"
|
||||
value: "name"
|
||||
metadata_fields:
|
||||
- id
|
||||
- name: "operating-system"
|
||||
host_command: "'get-vm --vm-name ' + str(extra_metadata_by_name['project_name']['metadata']['id'])"
|
||||
value: "os"
|
||||
|
||||
|
||||
The above example can be used to gather and persist in the backend the
|
||||
status of VMs. It will persist `1` in the backend as a measure for every
|
||||
collecting period if the VM's status is `ACTIVE`, and `0` otherwise. This is
|
||||
quite useful to create hashmap rating rules for running VMs in CloudKitty.
|
||||
Then, to enrich the resource in the storage backend, we are adding extra
|
||||
metadata that are collected in Keystone via the `extra_metadata_fields`
|
||||
options.
|
||||
metadata that are collected in Keystone and in the local host via the
|
||||
`extra_metadata_fields` options. If you have multiples `extra_metadata_fields`
|
||||
defining the same `metadata_field`, the last not `None` metadata value will
|
||||
be used.
|
||||
|
||||
To operate values in the `extra_metadata_fields`, you can access 3 local
|
||||
variables:
|
||||
|
||||
* ``sample``: it is a dictionary which holds the current data of the root
|
||||
sample. The root sample is the final sample that will be persisted in the
|
||||
configured storage backend.
|
||||
|
||||
* ``extra_metadata_captured``: it is a dictionary which holds the current
|
||||
data of all `extra_metadata_fields` processed before this one.
|
||||
If you have multiples `extra_metadata_fields` defining the same
|
||||
`metadata_field`, the last not `None` metadata value will be used.
|
||||
|
||||
* ``extra_metadata_by_name``: it is a dictionary which holds the data of
|
||||
all `extra_metadata_fields` processed before this one. No data is
|
||||
overwritten in this variable. To access an specific `extra_metadata_field`
|
||||
using this variable, you can do
|
||||
`extra_metadata_by_name['<extra_metadata_field_name>']['value']` to get
|
||||
its value, or
|
||||
`extra_metadata_by_name['<extra_metadata_field_name>']['metadata']['<metadata>']`
|
||||
to get its metadata.
|
||||
|
||||
The metadata enrichment feature has the following options:
|
||||
|
||||
|
@ -969,41 +1056,13 @@ The metadata enrichment feature has the following options:
|
|||
value can be increased of decreased.
|
||||
|
||||
* ``extra_metadata_fields``: optional parameter. This option is a list of
|
||||
objects, where each one of its elements is an extra metadata definition.
|
||||
Each one of the extra metadata definition can have the options defined in
|
||||
the dynamic pollsters such as to handle paged responses, operations on the
|
||||
extracted values, headers and so on. The basic options that must be
|
||||
defined for an extra metadata definitions are the following:
|
||||
objects or a single one, where each one of its elements is an
|
||||
dynamic pollster configuration set. Each one of the extra metadata
|
||||
definition can have the same options defined in the dynamic pollsters,
|
||||
including the `extra_metadata_fields` option, so this option is a
|
||||
multi-level option. When defined, the result of the collected data will
|
||||
be merged in the final sample resource metadata. If some of the required
|
||||
dynamic pollster configuration is not set in the `extra_metadata_fields`,
|
||||
will be used the parent pollster configuration, except the `name`.
|
||||
|
||||
* ``name``: This option is mandatory. The name of the extra metadata.
|
||||
This is the name that is going to be used by the metadata. If there is
|
||||
already any other metadata gathered via `metadata_fields` option or
|
||||
transformed via `metadata_mapping` configuration, this metadata is
|
||||
going to be discarded.
|
||||
|
||||
* ``endpoint_type``: The endpoint type that we want to execute the
|
||||
call against. This option is mandatory. It works similarly to the
|
||||
`endpoint_type` option in the dynamic pollster definition.
|
||||
|
||||
* ``url_path``: This option is mandatory. It works similarly to the
|
||||
`url_path` option in the dynamic pollster definition. However, this
|
||||
`one enables operators to execute/evaluate expressions in runtime, which
|
||||
`allows one to retrieve the information from previously gathered
|
||||
metadata via ``extra_metadata_captured` dictionary, or via the
|
||||
`sample` itself.
|
||||
|
||||
* ``value``: This configuration is mandatory. It works similarly to the
|
||||
`value_attribute` option in the dynamic pollster definition. It is
|
||||
the value we want to extract from the response, and assign in the
|
||||
metadata being generated.
|
||||
|
||||
* ``headers``: This option is optional. It works similarly to the
|
||||
`headers` option in the dynamic pollster definition.
|
||||
|
||||
* ``next_sample_url_attribute``: This option is optional. It works
|
||||
similarly to the `next_sample_url_attribute` option in the dynamic
|
||||
pollster definition.
|
||||
|
||||
* ``response_entries_key``: This option is optional. It works
|
||||
similarly to the `response_entries_key` option in the dynamic
|
||||
pollster definition.
|
||||
|
|
|
@ -45,6 +45,7 @@ ceilometer.sample.endpoint =
|
|||
|
||||
ceilometer.discover.compute =
|
||||
local_instances = ceilometer.compute.discovery:InstanceDiscovery
|
||||
local_node = ceilometer.polling.discovery.localnode:LocalNodeDiscovery
|
||||
|
||||
ceilometer.discover.central =
|
||||
barbican = ceilometer.polling.discovery.non_openstack_credentials_discovery:NonOpenStackCredentialsDiscovery
|
||||
|
|
Loading…
Reference in New Issue