openldap-opencensus-stats 0.0.21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openldap_opencensus_stats/__init__.py +3 -0
- openldap_opencensus_stats/config_transformers/__init__.py +20 -0
- openldap_opencensus_stats/config_transformers/base.py +47 -0
- openldap_opencensus_stats/config_transformers/child_object.py +75 -0
- openldap_opencensus_stats/config_transformers/copy_for_each_database.py +13 -0
- openldap_opencensus_stats/config_transformers/metric_dn.py +46 -0
- openldap_opencensus_stats/config_transformers/metric_name.py +37 -0
- openldap_opencensus_stats/config_transformers/metric_name_interpolation.py +84 -0
- openldap_opencensus_stats/config_transformers/metric_query_dn.py +37 -0
- openldap_opencensus_stats/config_transformers/snake_case.py +22 -0
- openldap_opencensus_stats/configuration.py +172 -0
- openldap_opencensus_stats/ldap_metric_set.py +45 -0
- openldap_opencensus_stats/ldap_server.py +111 -0
- openldap_opencensus_stats/ldap_statistic.py +91 -0
- openldap_opencensus_stats/ldap_stats_log_pipe.py +509 -0
- openldap_opencensus_stats/ldap_sync_statistic.py +78 -0
- openldap_opencensus_stats/main.py +41 -0
- openldap_opencensus_stats/sync_metric_set.py +99 -0
- openldap_opencensus_stats-0.0.21.data/data/etc/openldap-opencensus-stats.template.yml +125 -0
- openldap_opencensus_stats-0.0.21.dist-info/METADATA +431 -0
- openldap_opencensus_stats-0.0.21.dist-info/RECORD +24 -0
- openldap_opencensus_stats-0.0.21.dist-info/WHEEL +4 -0
- openldap_opencensus_stats-0.0.21.dist-info/entry_points.txt +2 -0
- openldap_opencensus_stats-0.0.21.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
# The transformations process in the order listed here.
|
|
3
|
+
from openldap_opencensus_stats.config_transformers.base \
|
|
4
|
+
import ConfigurationTransformationChainSingleton
|
|
5
|
+
from openldap_opencensus_stats.config_transformers.snake_case \
|
|
6
|
+
import SnakeCaseConfigurationTransformer # noqa: F401
|
|
7
|
+
from openldap_opencensus_stats.config_transformers.metric_dn \
|
|
8
|
+
import MetricDnConfigurationTransformer # noqa: F401
|
|
9
|
+
from openldap_opencensus_stats.config_transformers.copy_for_each_database \
|
|
10
|
+
import CopyForEachDatabaseConfigurationTransformer # noqa: F401
|
|
11
|
+
from openldap_opencensus_stats.config_transformers.child_object \
|
|
12
|
+
import ChildObjectConfigurationTransformer # noqa: F401
|
|
13
|
+
from openldap_opencensus_stats.config_transformers.metric_name_interpolation \
|
|
14
|
+
import MetricNameInterpolationConfigurationTransformer # noqa: F401
|
|
15
|
+
from openldap_opencensus_stats.config_transformers.metric_name \
|
|
16
|
+
import MetricNameConfigurationTransformer # noqa: F401
|
|
17
|
+
from openldap_opencensus_stats.config_transformers.metric_query_dn \
|
|
18
|
+
import MetricQueryDnConfigurationTransformer # noqa: F401
|
|
19
|
+
|
|
20
|
+
ConfigurationTransformationChainSingleton().register(MetricDnConfigurationTransformer)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from openldap_opencensus_stats.ldap_server import LdapServerPool
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConfigurationTransformer:
|
|
8
|
+
transformers = []
|
|
9
|
+
|
|
10
|
+
def __init_subclass__(cls, **kwargs):
|
|
11
|
+
ConfigurationTransformationChainSingleton().register(cls)
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def process(configuration):
|
|
15
|
+
return configuration
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_ldap_server(server_config):
|
|
19
|
+
args = dict([
|
|
20
|
+
(re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower(), value)
|
|
21
|
+
for name, value in server_config.get('connection', {}).items()
|
|
22
|
+
])
|
|
23
|
+
args['database'] = server_config.get('database')
|
|
24
|
+
ldap_server = LdapServerPool().get_ldap_server(**args)
|
|
25
|
+
return ldap_server
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConfigurationTransformationChainSingleton:
|
|
29
|
+
transformation_chain = []
|
|
30
|
+
|
|
31
|
+
def __new__(cls, *args, **kwargs):
|
|
32
|
+
if not hasattr(cls, 'instance'):
|
|
33
|
+
cls.instance = super(ConfigurationTransformationChainSingleton, cls).__new__(cls)
|
|
34
|
+
return cls.instance
|
|
35
|
+
|
|
36
|
+
def register(self, cls):
|
|
37
|
+
self.transformation_chain.append(cls)
|
|
38
|
+
logging.info(f"Registered transformer: {cls.__name__}")
|
|
39
|
+
|
|
40
|
+
def transform_configuration(self, configuration):
|
|
41
|
+
configuration_result = {}
|
|
42
|
+
configuration_input = configuration
|
|
43
|
+
for link in self.transformation_chain:
|
|
44
|
+
print(f"Calling configuration chain link {link.__name__}")
|
|
45
|
+
configuration_result = link.process(configuration_input)
|
|
46
|
+
configuration_input = configuration_result
|
|
47
|
+
return configuration_result
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
import ldap
|
|
6
|
+
|
|
7
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ChildObjectConfigurationTransformer(ConfigurationTransformer):
|
|
11
|
+
@staticmethod
|
|
12
|
+
def process(configuration):
|
|
13
|
+
if not isinstance(configuration, dict):
|
|
14
|
+
raise ValueError('The top level of the configuration must be a mapping.')
|
|
15
|
+
|
|
16
|
+
config = copy.deepcopy(configuration)
|
|
17
|
+
for server_config in config.get('ldap_servers'):
|
|
18
|
+
ldap_server = ChildObjectConfigurationTransformer.get_ldap_server(server_config)
|
|
19
|
+
# A server entry with no object tree defines no cn=Monitor
|
|
20
|
+
# metrics (e.g. statsLogPipe only) and has nothing to transform
|
|
21
|
+
if not server_config.get('sync_only', False) and server_config.get('object'):
|
|
22
|
+
server_config['object'] = ChildObjectConfigurationTransformer.process_objects_for_ldap_server(
|
|
23
|
+
configuration=server_config.get('object'),
|
|
24
|
+
dn='',
|
|
25
|
+
ldap_server=ldap_server
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return config
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def process_objects_for_ldap_server(configuration, dn, ldap_server):
|
|
32
|
+
if not configuration or not ldap_server:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
'ChildObjectConfigurationTransformer.process_objects_for_ldap_server: All arguments must exist'
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if isinstance(configuration, dict):
|
|
38
|
+
config = {}
|
|
39
|
+
for key, value in configuration.items():
|
|
40
|
+
proper_value = ChildObjectConfigurationTransformer.process_objects_for_ldap_server(
|
|
41
|
+
value,
|
|
42
|
+
configuration.get('computed_dn', dn),
|
|
43
|
+
ldap_server
|
|
44
|
+
)
|
|
45
|
+
if key == 'children':
|
|
46
|
+
if not isinstance(proper_value, dict):
|
|
47
|
+
raise TypeError("A configuration entry of 'children' must contain a mapping.")
|
|
48
|
+
|
|
49
|
+
for child_dn in ChildObjectConfigurationTransformer.get_child_keys(dn, ldap_server):
|
|
50
|
+
child_value = copy.deepcopy(proper_value)
|
|
51
|
+
child_value['computed_dn'] = child_dn
|
|
52
|
+
child_value['computed_rdn'] = re.sub(r',.*', '', child_dn)
|
|
53
|
+
new_key = re.sub(r',.*', '', re.sub(r'^[^=]*=', '', child_dn))
|
|
54
|
+
if re.match(child_value.get('rdn'), child_value.get('computed_rdn')):
|
|
55
|
+
config[new_key] = child_value
|
|
56
|
+
else:
|
|
57
|
+
config[key] = proper_value
|
|
58
|
+
return config
|
|
59
|
+
elif isinstance(configuration, list):
|
|
60
|
+
return [
|
|
61
|
+
ChildObjectConfigurationTransformer.process_objects_for_ldap_server(item, dn, ldap_server)
|
|
62
|
+
for item in configuration
|
|
63
|
+
]
|
|
64
|
+
else:
|
|
65
|
+
return configuration
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def get_child_keys(dn, ldap_server):
|
|
69
|
+
ldap_result = ldap_server.query(dn=dn, scope=ldap.SCOPE_ONELEVEL)
|
|
70
|
+
if not ldap_result:
|
|
71
|
+
logging.warning(f"Found no child objects for {dn} on LDAP database {ldap_server.database}!")
|
|
72
|
+
return [
|
|
73
|
+
result_item[0]
|
|
74
|
+
for result_item in ldap_result
|
|
75
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CopyForEachDatabaseConfigurationTransformer(ConfigurationTransformer):
|
|
7
|
+
@staticmethod
|
|
8
|
+
def process(configuration):
|
|
9
|
+
conf = copy.deepcopy(configuration)
|
|
10
|
+
ldap_servers = conf.get('ldap_servers', [])
|
|
11
|
+
for server_conf in ldap_servers:
|
|
12
|
+
server_conf['object'] = copy.deepcopy(conf.get('object'))
|
|
13
|
+
return conf
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MetricDnConfigurationTransformer(ConfigurationTransformer):
|
|
5
|
+
@staticmethod
|
|
6
|
+
def process(configuration):
|
|
7
|
+
return MetricDnConfigurationTransformer.process_object_config(configuration)
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def process_object_config(configuration, suffix_dn='', separator=','):
|
|
11
|
+
if isinstance(configuration, dict):
|
|
12
|
+
config = {}
|
|
13
|
+
if suffix_dn:
|
|
14
|
+
full_suffix = separator + suffix_dn
|
|
15
|
+
else:
|
|
16
|
+
full_suffix = suffix_dn
|
|
17
|
+
computed_dn = suffix_dn
|
|
18
|
+
rdn = configuration.get('computed_rdn', configuration.get('rdn'))
|
|
19
|
+
if rdn:
|
|
20
|
+
computed_dn = rdn + full_suffix
|
|
21
|
+
|
|
22
|
+
for key, value in configuration.items():
|
|
23
|
+
config[key] = MetricDnConfigurationTransformer.process_object_config(
|
|
24
|
+
configuration=value,
|
|
25
|
+
suffix_dn=computed_dn,
|
|
26
|
+
separator=separator
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# True if either 'rdn' or 'attribute' is a key
|
|
30
|
+
if configuration.get('rdn', configuration.get('attribute')):
|
|
31
|
+
config['computed_dn'] = computed_dn
|
|
32
|
+
else:
|
|
33
|
+
computed_dn = suffix_dn
|
|
34
|
+
|
|
35
|
+
return config
|
|
36
|
+
elif isinstance(configuration, list):
|
|
37
|
+
return [
|
|
38
|
+
MetricDnConfigurationTransformer.process_object_config(
|
|
39
|
+
configuration=item,
|
|
40
|
+
suffix_dn=suffix_dn,
|
|
41
|
+
separator=separator
|
|
42
|
+
)
|
|
43
|
+
for item in configuration
|
|
44
|
+
]
|
|
45
|
+
else:
|
|
46
|
+
return configuration
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MetricNameConfigurationTransformer(ConfigurationTransformer):
|
|
5
|
+
@staticmethod
|
|
6
|
+
def process(configuration):
|
|
7
|
+
return MetricNameConfigurationTransformer.process_object_config(configuration)
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def process_object_config(configuration, prefix='', default_name='', separator='/'):
|
|
11
|
+
full_prefix = prefix + separator if prefix else ''
|
|
12
|
+
if isinstance(configuration, dict):
|
|
13
|
+
config = {}
|
|
14
|
+
metric_name = prefix
|
|
15
|
+
if configuration.get('rdn') or configuration.get('attribute'):
|
|
16
|
+
metric_name = full_prefix + configuration.get('computed_name', configuration.get('name', default_name))
|
|
17
|
+
config['metric_name'] = metric_name
|
|
18
|
+
for key, value in configuration.items():
|
|
19
|
+
config[key] = MetricNameConfigurationTransformer.process_object_config(
|
|
20
|
+
configuration=value,
|
|
21
|
+
prefix=metric_name,
|
|
22
|
+
default_name=key,
|
|
23
|
+
separator=separator
|
|
24
|
+
)
|
|
25
|
+
return config
|
|
26
|
+
elif isinstance(configuration, list):
|
|
27
|
+
return [
|
|
28
|
+
MetricNameConfigurationTransformer.process_object_config(
|
|
29
|
+
configuration=item,
|
|
30
|
+
prefix=prefix,
|
|
31
|
+
default_name=default_name,
|
|
32
|
+
separator=separator
|
|
33
|
+
)
|
|
34
|
+
for item in configuration
|
|
35
|
+
]
|
|
36
|
+
else:
|
|
37
|
+
return configuration
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MetricNameInterpolationConfigurationTransformer(ConfigurationTransformer):
|
|
9
|
+
@staticmethod
|
|
10
|
+
def process(configuration):
|
|
11
|
+
if not isinstance(configuration, dict):
|
|
12
|
+
raise ValueError(
|
|
13
|
+
'NameInterpolationConfigurationTransformer expect a configuration that is a dict '
|
|
14
|
+
'but received one that isn\'t.')
|
|
15
|
+
|
|
16
|
+
config = copy.deepcopy(configuration)
|
|
17
|
+
for server_config in config.get('ldap_servers'):
|
|
18
|
+
if not server_config.get('sync_only', False):
|
|
19
|
+
ldap_server = ConfigurationTransformer.get_ldap_server(server_config=server_config)
|
|
20
|
+
server_config['object'] = MetricNameInterpolationConfigurationTransformer.process_objects_for_ldap_server(
|
|
21
|
+
configuration=server_config.get('object', {}),
|
|
22
|
+
dn='',
|
|
23
|
+
ldap_server=ldap_server
|
|
24
|
+
)
|
|
25
|
+
return config
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def process_objects_for_ldap_server(configuration, dn, ldap_server):
|
|
29
|
+
|
|
30
|
+
if isinstance(configuration, list):
|
|
31
|
+
return [
|
|
32
|
+
MetricNameInterpolationConfigurationTransformer.process_objects_for_ldap_server(item, dn, ldap_server)
|
|
33
|
+
for item in configuration
|
|
34
|
+
]
|
|
35
|
+
elif isinstance(configuration, dict):
|
|
36
|
+
config = {}
|
|
37
|
+
|
|
38
|
+
for key, value in configuration.items():
|
|
39
|
+
proper_value = MetricNameInterpolationConfigurationTransformer.process_objects_for_ldap_server(
|
|
40
|
+
value, dn, ldap_server)
|
|
41
|
+
if key in ['name']:
|
|
42
|
+
config['computed_name'] = MetricNameInterpolationConfigurationTransformer.create_metric_name(
|
|
43
|
+
configuration,
|
|
44
|
+
ldap_server
|
|
45
|
+
)
|
|
46
|
+
config[key] = proper_value
|
|
47
|
+
|
|
48
|
+
return config
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
return configuration
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def create_metric_name(config, ldap_server):
|
|
55
|
+
def repl_func(matches):
|
|
56
|
+
groups = matches.groups()
|
|
57
|
+
if 'rdn' == groups[0]:
|
|
58
|
+
index = int(groups[1])
|
|
59
|
+
rdn_matches = re.match(
|
|
60
|
+
config.get('rdn'), # Pattern to match - the 'rdn' entry
|
|
61
|
+
config.get('computed_rdn') # String for matching - the 'computed_rdn' entry
|
|
62
|
+
)
|
|
63
|
+
if rdn_matches:
|
|
64
|
+
return rdn_matches.group(index)
|
|
65
|
+
elif 'attr' == groups[0]:
|
|
66
|
+
attribute_name = groups[1]
|
|
67
|
+
ldap_result = ldap_server.query_dn_and_attribute(
|
|
68
|
+
config.get('computed_dn', ''),
|
|
69
|
+
attribute_name
|
|
70
|
+
)
|
|
71
|
+
value = ldap_result[0]
|
|
72
|
+
if isinstance(value, bytes):
|
|
73
|
+
value = value.decode()
|
|
74
|
+
return str(value)
|
|
75
|
+
else:
|
|
76
|
+
logging.error(f"Unknown interpolation requested in name: {groups[0]}.{groups[1]}")
|
|
77
|
+
|
|
78
|
+
name = config.get('computed_name', config.get('name', ''))
|
|
79
|
+
name = re.sub(
|
|
80
|
+
'{([^\\.}]+)\\.([^}]+)}',
|
|
81
|
+
repl_func,
|
|
82
|
+
name
|
|
83
|
+
).lower()
|
|
84
|
+
return name
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MetricQueryDnConfigurationTransformer(ConfigurationTransformer):
|
|
5
|
+
@staticmethod
|
|
6
|
+
def process(configuration):
|
|
7
|
+
return MetricQueryDnConfigurationTransformer.process_object_config(configuration)
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def process_object_config(configuration, query_dn=None):
|
|
11
|
+
if isinstance(configuration, dict):
|
|
12
|
+
config = {}
|
|
13
|
+
configuration_query_dn = configuration.get('query_dn', configuration.get('rdn'))
|
|
14
|
+
if not query_dn and configuration_query_dn:
|
|
15
|
+
query_dn = configuration_query_dn
|
|
16
|
+
|
|
17
|
+
for key, value in configuration.items():
|
|
18
|
+
config[key] = MetricQueryDnConfigurationTransformer.process_object_config(
|
|
19
|
+
configuration=value,
|
|
20
|
+
query_dn=query_dn,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# True if either 'rdn' or 'attribute' is a key
|
|
24
|
+
if query_dn:
|
|
25
|
+
config['query_dn'] = query_dn
|
|
26
|
+
|
|
27
|
+
return config
|
|
28
|
+
elif isinstance(configuration, list):
|
|
29
|
+
return [
|
|
30
|
+
MetricQueryDnConfigurationTransformer.process_object_config(
|
|
31
|
+
configuration=item,
|
|
32
|
+
query_dn=query_dn,
|
|
33
|
+
)
|
|
34
|
+
for item in configuration
|
|
35
|
+
]
|
|
36
|
+
else:
|
|
37
|
+
return configuration
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SnakeCaseConfigurationTransformer(ConfigurationTransformer):
|
|
7
|
+
@staticmethod
|
|
8
|
+
def process(configuration):
|
|
9
|
+
config = {}
|
|
10
|
+
for key, value in configuration.items():
|
|
11
|
+
snake_case_key = re.sub(r'(?<!^)(?=[A-Z])', '_', key).lower()
|
|
12
|
+
if isinstance(value, dict):
|
|
13
|
+
snake_cased_value = SnakeCaseConfigurationTransformer.process(value)
|
|
14
|
+
elif isinstance(value, list):
|
|
15
|
+
snake_cased_value = [
|
|
16
|
+
SnakeCaseConfigurationTransformer.process(x) if isinstance(x, dict) else x
|
|
17
|
+
for x in value
|
|
18
|
+
]
|
|
19
|
+
else:
|
|
20
|
+
snake_cased_value = value
|
|
21
|
+
config[snake_case_key] = snake_cased_value
|
|
22
|
+
return config
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
import logging.config
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from time import sleep
|
|
7
|
+
|
|
8
|
+
from openldap_opencensus_stats.config_transformers.base import ConfigurationTransformationChainSingleton
|
|
9
|
+
from openldap_opencensus_stats.ldap_metric_set import MetricSet
|
|
10
|
+
from openldap_opencensus_stats.sync_metric_set import SyncMetricSet
|
|
11
|
+
from openldap_opencensus_stats.ldap_server import LdapServerPool
|
|
12
|
+
from openldap_opencensus_stats.ldap_statistic import LdapStatistic
|
|
13
|
+
from openldap_opencensus_stats.ldap_stats_log_pipe import LdapStatsLogPipeReader, METRICS_NAMESPACE_DEFAULT
|
|
14
|
+
|
|
15
|
+
from opencensus.stats import stats
|
|
16
|
+
from opencensus.ext.prometheus import stats_exporter
|
|
17
|
+
|
|
18
|
+
# Make up for broken code in the Prometheus exporter
|
|
19
|
+
import opencensus.stats.aggregation_data
|
|
20
|
+
opencensus.stats.aggregation_data.SumAggregationDataFloat = opencensus.stats.aggregation_data.SumAggregationData
|
|
21
|
+
|
|
22
|
+
SUPPORTED_EXPORTERS = ['Prometheus', 'Stackdriver']
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Configuration:
|
|
26
|
+
def __init__(self, config_file_name):
|
|
27
|
+
if config_file_name is None:
|
|
28
|
+
raise ValueError("Config file name must be supplied")
|
|
29
|
+
self._config_file_name = config_file_name
|
|
30
|
+
self._configuration_dict = {}
|
|
31
|
+
self._sleep_time = 5
|
|
32
|
+
self._metric_sets = []
|
|
33
|
+
self._ldap_metrics = {}
|
|
34
|
+
self._stats_log_pipe_readers = []
|
|
35
|
+
|
|
36
|
+
self.reconfigure()
|
|
37
|
+
|
|
38
|
+
def reconfigure(self):
|
|
39
|
+
self._configuration_dict = read_yaml_file(self._config_file_name)
|
|
40
|
+
normalized_configuration = ConfigurationTransformationChainSingleton().transform_configuration(
|
|
41
|
+
self._configuration_dict
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self._sleep_time = normalized_configuration.get('period', 5)
|
|
45
|
+
log_config = normalized_configuration.get('log_config')
|
|
46
|
+
if log_config and isinstance(log_config, dict):
|
|
47
|
+
log_config['version'] = log_config.get('version', 1)
|
|
48
|
+
logging.config.dictConfig(log_config)
|
|
49
|
+
prometheus_namespace = METRICS_NAMESPACE_DEFAULT
|
|
50
|
+
for exporter_config in normalized_configuration.get('exporters', []):
|
|
51
|
+
exporter = create_exporter(exporter_config)
|
|
52
|
+
stats.stats.view_manager.register_exporter(exporter)
|
|
53
|
+
if exporter_config.get('name') == 'Prometheus':
|
|
54
|
+
prometheus_namespace = exporter.options.namespace
|
|
55
|
+
|
|
56
|
+
for ldap_server_config in normalized_configuration.get('ldap_servers', []):
|
|
57
|
+
if not ldap_server_config.get('sync_only', False):
|
|
58
|
+
metric_set = self.generate_metric_set(ldap_server_config)
|
|
59
|
+
self._metric_sets.append(metric_set)
|
|
60
|
+
stats_log_pipe = ldap_server_config.get('stats_log_pipe')
|
|
61
|
+
if stats_log_pipe:
|
|
62
|
+
if isinstance(stats_log_pipe, str):
|
|
63
|
+
stats_log_pipe = {'pipe': stats_log_pipe}
|
|
64
|
+
stats_log_pipe_reader = LdapStatsLogPipeReader(
|
|
65
|
+
database=ldap_server_config.get('database'),
|
|
66
|
+
pipe_path=stats_log_pipe.get('pipe'),
|
|
67
|
+
suffixes=stats_log_pipe.get('suffixes'),
|
|
68
|
+
namespace=prometheus_namespace
|
|
69
|
+
)
|
|
70
|
+
stats_log_pipe_reader.start()
|
|
71
|
+
self._stats_log_pipe_readers.append(stats_log_pipe_reader)
|
|
72
|
+
|
|
73
|
+
for base_dn, sync_config in normalized_configuration.get('sync', {}).items():
|
|
74
|
+
ldap_server_names = sync_config.get('cluster_servers', [])
|
|
75
|
+
ldap_servers = [
|
|
76
|
+
LdapServerPool().get_ldap_server(**server)
|
|
77
|
+
for server in normalized_configuration.get('ldap_servers', [])
|
|
78
|
+
if server['database'] in ldap_server_names
|
|
79
|
+
]
|
|
80
|
+
report_servers = sync_config.get('report_servers', [])
|
|
81
|
+
sync_metric_set = SyncMetricSet(
|
|
82
|
+
base_dn=base_dn,
|
|
83
|
+
ldap_servers=ldap_servers,
|
|
84
|
+
report_servers=report_servers
|
|
85
|
+
)
|
|
86
|
+
self._metric_sets.append(sync_metric_set)
|
|
87
|
+
|
|
88
|
+
def generate_metric_set(self, ldap_server_config):
|
|
89
|
+
args = copy.deepcopy(ldap_server_config.get('connection', {}))
|
|
90
|
+
args['database'] = ldap_server_config.get('database')
|
|
91
|
+
ldap_server = LdapServerPool().get_ldap_server(**args)
|
|
92
|
+
metric_set = MetricSet(ldap_server=ldap_server)
|
|
93
|
+
configs = [ldap_server_config.get('object')]
|
|
94
|
+
while configs:
|
|
95
|
+
config = configs.pop()
|
|
96
|
+
if isinstance(config, list):
|
|
97
|
+
for item in config:
|
|
98
|
+
configs.insert(0, item)
|
|
99
|
+
elif isinstance(config, dict):
|
|
100
|
+
dn = config.get('computed_dn')
|
|
101
|
+
name = config.get('metric_name')
|
|
102
|
+
attribute = config.get('attribute')
|
|
103
|
+
unit = config.get('unit')
|
|
104
|
+
description = config.get('description', '')
|
|
105
|
+
value_function = config.get('func', 'value')
|
|
106
|
+
query_dn = config.get('query_dn')
|
|
107
|
+
if dn and name and attribute and unit and query_dn:
|
|
108
|
+
stat = self._ldap_metrics.get(name)
|
|
109
|
+
if not stat:
|
|
110
|
+
stat = LdapStatistic(
|
|
111
|
+
dn=dn,
|
|
112
|
+
name=name,
|
|
113
|
+
attribute=attribute,
|
|
114
|
+
description=description,
|
|
115
|
+
unit=unit,
|
|
116
|
+
value_function=value_function,
|
|
117
|
+
query_dn=query_dn,
|
|
118
|
+
tag_keys=['database']
|
|
119
|
+
)
|
|
120
|
+
self._ldap_metrics[name] = stat
|
|
121
|
+
metric_set.add_statistic(stat)
|
|
122
|
+
for key, value in config.items():
|
|
123
|
+
configs.insert(0, value)
|
|
124
|
+
return metric_set
|
|
125
|
+
|
|
126
|
+
def metric_sets(self):
|
|
127
|
+
return self._metric_sets
|
|
128
|
+
|
|
129
|
+
def sleep(self):
|
|
130
|
+
sleep(self._sleep_time)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def read_yaml_file(file_name):
|
|
134
|
+
with open(file_name, 'r') as file:
|
|
135
|
+
ret_val = yaml.safe_load(file)
|
|
136
|
+
return ret_val
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def create_exporter(exporter_configuration=None):
|
|
140
|
+
if exporter_configuration is None:
|
|
141
|
+
raise ValueError("Cannot create an exporter with no configuration!")
|
|
142
|
+
|
|
143
|
+
name = exporter_configuration.get('name')
|
|
144
|
+
if name not in SUPPORTED_EXPORTERS:
|
|
145
|
+
logging.error(
|
|
146
|
+
f"Requested exporter named {name}, which is not supported. Choose from:{', '.join(SUPPORTED_EXPORTERS)}"
|
|
147
|
+
)
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"Requested exporter named {name}, which is not supported. Choose from:{', '.join(SUPPORTED_EXPORTERS)}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
options = exporter_configuration.get('options', {})
|
|
153
|
+
exporter = None
|
|
154
|
+
if "Prometheus" == name:
|
|
155
|
+
if 'options' not in exporter_configuration:
|
|
156
|
+
logging.error("The Prometheus exporter requires options configuration.")
|
|
157
|
+
raise ValueError("The Prometheus exporter requires options configuration.")
|
|
158
|
+
final_options = {'namespace': METRICS_NAMESPACE_DEFAULT, 'port': 8000, 'address': '0.0.0.0'}
|
|
159
|
+
final_options.update(options)
|
|
160
|
+
exporter = stats_exporter.new_stats_exporter(
|
|
161
|
+
stats_exporter.Options(**final_options)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
elif "Stackdriver" == name:
|
|
165
|
+
# Imported here, not at module level: opencensus-ext-stackdriver and its
|
|
166
|
+
# grpcio dependency are optional (see pyproject.toml's "stackdriver" extra),
|
|
167
|
+
# so this module must not import them unconditionally.
|
|
168
|
+
import opencensus.ext.stackdriver.stats_exporter
|
|
169
|
+
exporter = opencensus.ext.stackdriver.stats_exporter.new_stats_exporter(interval=5)
|
|
170
|
+
print(f"Exporting stats to this project {exporter.options.project_id}")
|
|
171
|
+
|
|
172
|
+
return exporter
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
from opencensus.stats import stats
|
|
4
|
+
from opencensus.tags import tag_map, tag_value, tag_key
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MetricSet:
|
|
8
|
+
def __init__(self, ldap_server=None, ldap_statistics=None):
|
|
9
|
+
self._ldap_server = ldap_server
|
|
10
|
+
if not ldap_statistics or not isinstance(ldap_statistics, list):
|
|
11
|
+
ldap_statistics = []
|
|
12
|
+
self._ldap_statistics = copy.deepcopy(ldap_statistics)
|
|
13
|
+
self._query_dns = set()
|
|
14
|
+
|
|
15
|
+
def set_ldap_server(self, ldap_server):
|
|
16
|
+
self._ldap_server = ldap_server
|
|
17
|
+
|
|
18
|
+
def add_statistic(self, ldap_statistic):
|
|
19
|
+
self._ldap_statistics.append(ldap_statistic)
|
|
20
|
+
self._query_dns.add(ldap_statistic.query_dn)
|
|
21
|
+
|
|
22
|
+
def collect(self):
|
|
23
|
+
tag_keys = [tag_key.TagKey('database')]
|
|
24
|
+
results = {}
|
|
25
|
+
for query_dn in self._query_dns:
|
|
26
|
+
results[query_dn] = dict([
|
|
27
|
+
(result_dn, result_attributes)
|
|
28
|
+
for result_dn, result_attributes in self._ldap_server.query(dn=query_dn)
|
|
29
|
+
])
|
|
30
|
+
mmap = stats.stats.stats_recorder.new_measurement_map()
|
|
31
|
+
for server_statistic in self._ldap_statistics:
|
|
32
|
+
ldap_value = results.get(
|
|
33
|
+
server_statistic.query_dn, {}
|
|
34
|
+
).get(
|
|
35
|
+
server_statistic.dn, {}
|
|
36
|
+
).get(
|
|
37
|
+
server_statistic.attribute
|
|
38
|
+
)
|
|
39
|
+
server_statistic.collect(ldap_server=self._ldap_server, measurement_map=mmap, ldap_value=ldap_value)
|
|
40
|
+
tmap = tag_map.TagMap()
|
|
41
|
+
tmap.insert(
|
|
42
|
+
tag_keys[0],
|
|
43
|
+
tag_value.TagValue(self._ldap_server.database)
|
|
44
|
+
)
|
|
45
|
+
mmap.record(tmap)
|