Skip to content

Commit df1a506

Browse files
authored
Merge pull request #3060 from DataDog/haissam/sd-cache-kv-tpls
[Service Discovery] create a template cache to reduce calls to the KV store
2 parents 7fe1b3d + 13d8a24 commit df1a506

File tree

10 files changed

+326
-182
lines changed

10 files changed

+326
-182
lines changed

agent.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def __init__(self, pidfile, autorestart, start_event=True, in_developer_mode=Fal
9191
self._checksd = []
9292
self.collector_profile_interval = DEFAULT_COLLECTOR_PROFILE_INTERVAL
9393
self.check_frequency = None
94-
# this flag can be set to True, False, or a list of checks (for partial reload)
94+
# this flag can be set to True, False, or a set of checks (for partial reload)
9595
self.reload_configs_flag = False
9696
self.sd_backend = None
9797
self.supervisor_proxy = None
@@ -161,7 +161,7 @@ def reload_configs(self, checks_to_reload=set()):
161161
log.info("No checksd configs found")
162162

163163
def refresh_specific_checks(self, hostname, checksd, checks):
164-
"""take a list of checks and for each of them:
164+
"""take a set of checks and for each of them:
165165
- remove it from the init_failed_checks if it was there
166166
- load a fresh config for it
167167
- replace its old config with the new one in initialized_checks if there was one
@@ -245,7 +245,7 @@ def run(self, config=None):
245245
if self._agentConfig.get('service_discovery'):
246246
self.sd_backend = get_sd_backend(self._agentConfig)
247247

248-
if _is_affirmative(self._agentConfig.get('sd_jmx_enable')):
248+
if _is_affirmative(self._agentConfig.get('sd_jmx_enable', False)):
249249
pipe_path = get_jmx_pipe_path()
250250
if Platform.is_windows():
251251
pipe_name = pipe_path.format(pipename=SD_PIPE_NAME)

checks.d/docker_daemon.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ def _parse_cgroup_file(self, stat_file):
851851
except IOError:
852852
# It is possible that the container got stopped between the API call and now.
853853
# Some files can also be missing (like cpu.stat) and that's fine.
854-
self.log.info("Can't open %s. Some metrics for this container may be missing." % stat_file)
854+
self.log.debug("Can't open %s. Its metrics will be missing." % stat_file)
855855

856856
def _parse_blkio_metrics(self, stats):
857857
"""Parse the blkio metrics."""

config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1123,7 +1123,7 @@ def load_check(agentConfig, hostname, checkname):
11231123

11241124
# try to load the check and return the result
11251125
load_success, load_failure = load_check_from_places(check_config, check_name, checks_places, agentConfig)
1126-
return load_success.values()[0] or load_failure
1126+
return load_success.values()[0] if load_success else load_failure
11271127

11281128
return None
11291129

tests/core/test_service_discovery.py

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import copy
33
import mock
44
import unittest
5+
from collections import defaultdict
56

67
# 3p
78
from nose.plugins.attrib import attr
@@ -11,7 +12,8 @@
1112
from utils.service_discovery.config_stores import get_config_store
1213
from utils.service_discovery.consul_config_store import ConsulStore
1314
from utils.service_discovery.etcd_config_store import EtcdStore
14-
from utils.service_discovery.abstract_config_store import AbstractConfigStore, CONFIG_FROM_KUBE
15+
from utils.service_discovery.abstract_config_store import AbstractConfigStore, \
16+
_TemplateCache, CONFIG_FROM_KUBE, CONFIG_FROM_TEMPLATE, CONFIG_FROM_AUTOCONF
1517
from utils.service_discovery.sd_backend import get_sd_backend
1618
from utils.service_discovery.sd_docker_backend import SDDockerBackend, _SDDockerBackendConfigFetchState
1719

@@ -66,11 +68,11 @@ def client_read(path, **kwargs):
6668
if 'all' in kwargs:
6769
return {}
6870
else:
69-
return TestServiceDiscovery.mock_tpls.get(image)[0][config_parts.index(config_part)]
71+
return TestServiceDiscovery.mock_raw_templates.get(image)[0][config_parts.index(config_part)]
7072

7173

7274
def issue_read(identifier):
73-
return TestServiceDiscovery.mock_tpls.get(identifier)
75+
return TestServiceDiscovery.mock_raw_templates.get(identifier)
7476

7577
@attr('unix')
7678
class TestServiceDiscovery(unittest.TestCase):
@@ -122,7 +124,7 @@ class TestServiceDiscovery(unittest.TestCase):
122124
}
123125

124126
# raw templates coming straight from the config store
125-
mock_tpls = {
127+
mock_raw_templates = {
126128
# image_name: ('[check_name]', '[init_tpl]', '[instance_tpl]', expected_python_tpl_list)
127129
'image_0': (
128130
('["check_0"]', '[{}]', '[{"host": "%%host%%"}]'),
@@ -509,12 +511,12 @@ def test_fill_tpl(self, *args):
509511
def test_get_auto_config(self):
510512
"""Test _get_auto_config"""
511513
expected_tpl = {
512-
'redis': ('redisdb', None, {"host": "%%host%%", "port": "%%port%%"}),
513-
'consul': ('consul', None, {
514-
"url": "http://%%host%%:%%port%%", "catalog_checks": True, "new_leader_checks": True
515-
}),
516-
'redis:v1': ('redisdb', None, {"host": "%%host%%", "port": "%%port%%"}),
517-
'foobar': None
514+
'redis': [('redisdb', None, {"host": "%%host%%", "port": "%%port%%"})],
515+
'consul': [('consul', None, {
516+
"url": "http://%%host%%:%%port%%", "catalog_checks": True, "new_leader_checks": True
517+
})],
518+
'redis:v1': [('redisdb', None, {"host": "%%host%%", "port": "%%port%%"})],
519+
'foobar': []
518520
}
519521

520522
config_store = get_config_store(self.auto_conf_agentConfig)
@@ -529,10 +531,10 @@ def test_get_check_tpls(self, mock_client_read):
529531
invalid_config = ['bad_image_0', 'bad_image_1']
530532
config_store = get_config_store(self.auto_conf_agentConfig)
531533
for image in valid_config:
532-
tpl = self.mock_tpls.get(image)[1]
534+
tpl = self.mock_raw_templates.get(image)[1]
533535
self.assertEquals(tpl, config_store.get_check_tpls(image))
534536
for image in invalid_config:
535-
tpl = self.mock_tpls.get(image)[1]
537+
tpl = self.mock_raw_templates.get(image)[1]
536538
self.assertEquals(tpl, config_store.get_check_tpls(image))
537539

538540
@mock.patch.object(AbstractConfigStore, 'client_read', side_effect=client_read)
@@ -542,7 +544,7 @@ def test_get_check_tpls_kube(self, mock_client_read):
542544
invalid_config = ['bad_image_0']
543545
config_store = get_config_store(self.auto_conf_agentConfig)
544546
for image in valid_config + invalid_config:
545-
tpl = self.mock_tpls.get(image)[1]
547+
tpl = self.mock_raw_templates.get(image)[1]
546548
tpl = [(CONFIG_FROM_KUBE, t[1]) for t in tpl]
547549
if tpl:
548550
self.assertNotEquals(
@@ -558,7 +560,7 @@ def test_get_check_tpls_kube(self, mock_client_read):
558560
['service-discovery.datadoghq.com/foo.check_names',
559561
'service-discovery.datadoghq.com/foo.init_configs',
560562
'service-discovery.datadoghq.com/foo.instances'],
561-
self.mock_tpls[image][0]))))
563+
self.mock_raw_templates[image][0]))))
562564

563565
def test_get_config_id(self):
564566
"""Test get_config_id"""
@@ -570,8 +572,8 @@ def test_get_config_id(self):
570572
expected_ident)
571573
clear_singletons(self.auto_conf_agentConfig)
572574

573-
@mock.patch.object(AbstractConfigStore, '_issue_read', side_effect=issue_read)
574-
def test_read_config_from_store(self, issue_read):
575+
@mock.patch.object(_TemplateCache, '_issue_read', side_effect=issue_read)
576+
def test_read_config_from_store(self, args):
575577
"""Test read_config_from_store"""
576578
valid_idents = [('nginx', 'nginx'), ('nginx:latest', 'nginx:latest'),
577579
('custom-nginx', 'custom-nginx'), ('custom-nginx:latest', 'custom-nginx'),
@@ -582,7 +584,13 @@ def test_read_config_from_store(self, issue_read):
582584
for ident, expected_key in valid_idents:
583585
tpl = config_store.read_config_from_store(ident)
584586
# source is added after reading from the store
585-
self.assertEquals(tpl, ('template',) + self.mock_tpls.get(expected_key))
587+
self.assertEquals(
588+
tpl,
589+
{
590+
CONFIG_FROM_AUTOCONF: None,
591+
CONFIG_FROM_TEMPLATE: self.mock_raw_templates.get(expected_key)
592+
}
593+
)
586594
for ident in invalid_idents:
587595
self.assertEquals(config_store.read_config_from_store(ident), [])
588596

@@ -600,3 +608,72 @@ def test_read_jmx_config_from_store(self, *args):
600608
for check in self.jmx_sd_configs:
601609
key = '{}_0'.format(check)
602610
self.assertEquals(jmx_configs[key], valid_configs[key])
611+
612+
# Template cache
613+
@mock.patch('utils.service_discovery.abstract_config_store.get_auto_conf_images')
614+
def test_populate_auto_conf(self, mock_get_auto_conf_images):
615+
"""test _populate_auto_conf"""
616+
auto_tpls = {
617+
'foo': [['check0', 'check1'], [{}, {}], [{}, {}]],
618+
'bar': [['check2', 'check3', 'check3'], [{}, {}, {}], [{}, {'foo': 'bar'}, {'bar': 'foo'}]],
619+
}
620+
cache = _TemplateCache(issue_read, '')
621+
cache.auto_conf_templates = defaultdict(lambda: [[]] * 3)
622+
mock_get_auto_conf_images.return_value = auto_tpls
623+
624+
cache._populate_auto_conf()
625+
self.assertEquals(cache.auto_conf_templates['foo'], auto_tpls['foo'])
626+
self.assertEquals(cache.auto_conf_templates['bar'],
627+
[['check2', 'check3'], [{}, {}], [{}, {'foo': 'bar'}]])
628+
629+
@mock.patch.object(_TemplateCache, '_issue_read', return_value=None)
630+
def test_get_templates(self, args):
631+
"""test get_templates"""
632+
kv_tpls = {
633+
'foo': [['check0', 'check1'], [{}, {}], [{}, {}]],
634+
'bar': [['check2', 'check3'], [{}, {}], [{}, {}]],
635+
}
636+
auto_tpls = {
637+
'foo': [['check3', 'check5'], [{}, {}], [{}, {}]],
638+
'bar': [['check2', 'check6'], [{}, {}], [{}, {}]],
639+
'foobar': [['check4'], [{}], [{}]],
640+
}
641+
cache = _TemplateCache(issue_read, '')
642+
cache.kv_templates = kv_tpls
643+
cache.auto_conf_templates = auto_tpls
644+
self.assertEquals(cache.get_templates('foo'),
645+
{CONFIG_FROM_TEMPLATE: [['check0', 'check1'], [{}, {}], [{}, {}]],
646+
CONFIG_FROM_AUTOCONF: [['check3', 'check5'], [{}, {}], [{}, {}]]}
647+
)
648+
649+
self.assertEquals(cache.get_templates('bar'),
650+
# check3 must come from template not autoconf
651+
{CONFIG_FROM_TEMPLATE: [['check2', 'check3'], [{}, {}], [{}, {}]],
652+
CONFIG_FROM_AUTOCONF: [['check6'], [{}], [{}]]}
653+
)
654+
655+
self.assertEquals(cache.get_templates('foobar'),
656+
{CONFIG_FROM_TEMPLATE: None,
657+
CONFIG_FROM_AUTOCONF: [['check4'], [{}], [{}]]}
658+
)
659+
660+
self.assertEquals(cache.get_templates('baz'), None)
661+
662+
def test_get_check_names(self):
663+
"""Test get_check_names"""
664+
kv_tpls = {
665+
'foo': [['check0', 'check1'], [{}, {}], [{}, {}]],
666+
'bar': [['check2', 'check3'], [{}, {}], [{}, {}]],
667+
}
668+
auto_tpls = {
669+
'foo': [['check4', 'check5'], [{}, {}], [{}, {}]],
670+
'bar': [['check2', 'check6'], [{}, {}], [{}, {}]],
671+
'foobar': None,
672+
}
673+
cache = _TemplateCache(issue_read, '')
674+
cache.kv_templates = kv_tpls
675+
cache.auto_conf_templates = auto_tpls
676+
self.assertEquals(cache.get_check_names('foo'), set(['check0', 'check1', 'check4', 'check5']))
677+
self.assertEquals(cache.get_check_names('bar'), set(['check2', 'check3', 'check6']))
678+
self.assertEquals(cache.get_check_names('foobar'), set())
679+
self.assertEquals(cache.get_check_names('baz'), set())

utils/checkfiles.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def get_check_class(agentConfig, check_name):
5050
return None
5151

5252

53-
def get_auto_conf(agentConfig, check_name):
53+
def get_auto_conf(check_name):
5454
"""Return the yaml auto_config dict for a check name (None if it doesn't exist)."""
5555
from config import PathNotFound, get_auto_confd_path
5656

@@ -75,11 +75,11 @@ def get_auto_conf(agentConfig, check_name):
7575
return auto_conf
7676

7777

78-
def get_auto_conf_images(agentConfig):
78+
def get_auto_conf_images(full_tpl=False):
7979
"""Walk through the auto_config folder and build a dict of auto-configurable images."""
8080
from config import PathNotFound, get_auto_confd_path
8181
auto_conf_images = {
82-
# image_name: check_name
82+
# image_name: [check_names] or [[check_names], [init_tpls], [instance_tpls]]
8383
}
8484

8585
try:
@@ -100,5 +100,18 @@ def get_auto_conf_images(agentConfig):
100100
# extract the supported image list
101101
images = auto_conf.get('docker_images', [])
102102
for image in images:
103-
auto_conf_images[image] = check_name
103+
if full_tpl:
104+
init_tpl = auto_conf.get('init_config') or {}
105+
instance_tpl = auto_conf.get('instances', [])
106+
if image not in auto_conf_images:
107+
auto_conf_images[image] = [[check_name], [init_tpl], [instance_tpl]]
108+
else:
109+
for idx, item in enumerate([check_name, init_tpl, instance_tpl]):
110+
auto_conf_images[image][idx].append(item)
111+
else:
112+
if image in auto_conf_images:
113+
auto_conf_images[image].append(check_name)
114+
else:
115+
auto_conf_images[image] = [check_name]
116+
104117
return auto_conf_images

utils/configcheck.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ def print_templates(agentConfig):
9494
except Exception as ex:
9595
print("Failed to extract configuration templates from the backend:\n%s" % str(ex))
9696

97-
for img, tpl in templates.iteritems():
97+
for ident, tpl in templates.iteritems():
9898
print(
99-
"- Image %s:\n\tcheck names: %s\n\tinit_configs: %s\n\tinstances: %s" % (
100-
img,
99+
"- Identifier %s:\n\tcheck names: %s\n\tinit_configs: %s\n\tinstances: %s" % (
100+
ident,
101101
tpl.get('check_names'),
102102
tpl.get('init_configs'),
103103
tpl.get('instances'),

0 commit comments

Comments
 (0)