Skip to content

Commit 118db09

Browse files
committed
[kubernetes] Only use kubernetes annotations for service discovery for the first container in a pod.
Otherwise you could end up instantiating several instances of the same check when the pod contains more than one container. Also get rid of the trace_config madness
1 parent a33aa3f commit 118db09

File tree

4 files changed

+95
-104
lines changed

4 files changed

+95
-104
lines changed

config.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,16 +1038,11 @@ def load_check_directory(agentConfig, hostname):
10381038
if check_name in initialized_checks or check_name in init_failed_checks:
10391039
continue
10401040

1041-
# if TRACE_CONFIG is set, service_disco_check_config looks like:
1042-
# (config_src, (sd_init_config, sd_instances)) instead of
1043-
# (sd_init_config, sd_instances)
1041+
sd_init_config, sd_instances = service_disco_check_config[1]
10441042
if agentConfig.get(TRACE_CONFIG):
1045-
sd_init_config, sd_instances = service_disco_check_config[1]
10461043
configs_and_sources[check_name] = (
10471044
service_disco_check_config[0],
10481045
{'init_config': sd_init_config, 'instances': sd_instances})
1049-
else:
1050-
sd_init_config, sd_instances = service_disco_check_config
10511046

10521047
check_config = {'init_config': sd_init_config, 'instances': sd_instances}
10531048

@@ -1065,8 +1060,7 @@ def load_check_directory(agentConfig, hostname):
10651060
return configs_and_sources
10661061

10671062
return {'initialized_checks': initialized_checks.values(),
1068-
'init_failed_checks': init_failed_checks,
1069-
}
1063+
'init_failed_checks': init_failed_checks}
10701064

10711065

10721066
def load_check(agentConfig, hostname, checkname):
@@ -1089,7 +1083,7 @@ def load_check(agentConfig, hostname, checkname):
10891083
# the check was not found, try with service discovery
10901084
for check_name, service_disco_check_config in _service_disco_configs(agentConfig).iteritems():
10911085
if check_name == checkname:
1092-
sd_init_config, sd_instances = service_disco_check_config
1086+
sd_init_config, sd_instances = service_disco_check_config[1]
10931087
check_config = {'init_config': sd_init_config, 'instances': sd_instances}
10941088

10951089
# try to load the check and return the result

tests/core/test_service_discovery.py

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from utils.service_discovery.config_stores import get_config_store
1111
from utils.service_discovery.consul_config_store import ConsulStore
1212
from utils.service_discovery.etcd_config_store import EtcdStore
13-
from utils.service_discovery.abstract_config_store import AbstractConfigStore
13+
from utils.service_discovery.abstract_config_store import AbstractConfigStore, CONFIG_FROM_KUBE
1414
from utils.service_discovery.sd_backend import get_sd_backend
1515
from utils.service_discovery.sd_docker_backend import SDDockerBackend
1616

@@ -40,14 +40,16 @@ def _get_container_inspect(c_id):
4040
return None
4141

4242

43-
def _get_conf_tpls(image_name, trace_config=False, kube_annotations=None):
43+
def _get_conf_tpls(image_name, kube_annotations=None, kube_pod_name=None):
4444
"""Return a mocked configuration template from self.mock_templates."""
45-
return copy.deepcopy(TestServiceDiscovery.mock_templates.get(image_name)[0])
45+
return [(x, image_name + ':0', y) for x, y in
46+
copy.deepcopy(TestServiceDiscovery.mock_templates.get(image_name)[0])]
4647

4748

4849
def _get_check_tpls(image_name, **kwargs):
4950
if image_name in TestServiceDiscovery.mock_templates:
50-
return [copy.deepcopy(TestServiceDiscovery.mock_templates.get(image_name)[0][0][0:3])]
51+
result = copy.deepcopy(TestServiceDiscovery.mock_templates.get(image_name)[0][0])
52+
return [(result[0], image_name + ':0', result[1][0:3])]
5153
elif image_name in TestServiceDiscovery.bad_mock_templates:
5254
try:
5355
return [copy.deepcopy(TestServiceDiscovery.bad_mock_templates.get(image_name))]
@@ -107,30 +109,30 @@ class TestServiceDiscovery(unittest.TestCase):
107109

108110
# templates with variables already extracted
109111
mock_templates = {
110-
# image_name: ([(check_name, init_tpl, instance_tpl, variables)], (expected_config_template))
112+
# image_name: ([(source, (check_name, init_tpl, instance_tpl, variables))], (expected_config_template))
111113
'image_0': (
112-
[('check_0', {}, {'host': '%%host%%'}, ['host'])],
113-
('check_0', {}, {'host': '127.0.0.1'})),
114+
[('template', ('check_0', {}, {'host': '%%host%%'}, ['host']))],
115+
('template', 'image_0:0', ('check_0', {}, {'host': '127.0.0.1'}))),
114116
'image_1': (
115-
[('check_1', {}, {'port': '%%port%%'}, ['port'])],
116-
('check_1', {}, {'port': '1337'})),
117+
[('template', ('check_1', {}, {'port': '%%port%%'}, ['port']))],
118+
('template', 'image_1:0', ('check_1', {}, {'port': '1337'}))),
117119
'image_2': (
118-
[('check_2', {}, {'host': '%%host%%', 'port': '%%port%%'}, ['host', 'port'])],
119-
('check_2', {}, {'host': '127.0.0.1', 'port': '1337'})),
120+
[('template', ('check_2', {}, {'host': '%%host%%', 'port': '%%port%%'}, ['host', 'port']))],
121+
('template', 'image_2:0', ('check_2', {}, {'host': '127.0.0.1', 'port': '1337'}))),
120122
}
121123

122124
# raw templates coming straight from the config store
123125
mock_tpls = {
124126
# image_name: ('[check_name]', '[init_tpl]', '[instance_tpl]', expected_python_tpl_list)
125127
'image_0': (
126128
('["check_0"]', '[{}]', '[{"host": "%%host%%"}]'),
127-
[('check_0', {}, {"host": "%%host%%"})]),
129+
[('template', 'image_0:0', ('check_0', {}, {"host": "%%host%%"}))]),
128130
'image_1': (
129131
('["check_1"]', '[{}]', '[{"port": "%%port%%"}]'),
130-
[('check_1', {}, {"port": "%%port%%"})]),
132+
[('template', 'image_1:0', ('check_1', {}, {"port": "%%port%%"}))]),
131133
'image_2': (
132134
('["check_2"]', '[{}]', '[{"host": "%%host%%", "port": "%%port%%"}]'),
133-
[('check_2', {}, {"host": "%%host%%", "port": "%%port%%"})]),
135+
[('template', 'image_2:0', ('check_2', {}, {"host": "%%host%%", "port": "%%port%%"}))]),
134136
'bad_image_0': ((['invalid template']), []),
135137
'bad_image_1': (('invalid template'), []),
136138
'bad_image_2': (None, []),
@@ -249,38 +251,39 @@ def test_get_port(self):
249251
self.assertRaises(expected_ports, sd_backend._get_port, c_ins, var_tpl)
250252
clear_singletons(self.auto_conf_agentConfig)
251253

254+
@mock.patch('utils.dockerutil.DockerUtil.client', return_value=None)
255+
@mock.patch.object(SDDockerBackend, '_get_host_address', return_value='127.0.0.1')
256+
@mock.patch.object(SDDockerBackend, '_get_port', return_value='1337')
252257
@mock.patch('docker.Client.inspect_container', side_effect=_get_container_inspect)
253258
@mock.patch.object(SDDockerBackend, '_get_config_templates', side_effect=_get_conf_tpls)
254-
def test_get_check_configs(self, mock_inspect_container, mock_get_conf_tpls):
259+
def test_get_check_configs(self, *args):
255260
"""Test get_check_config with mocked container inspect and config template"""
256-
with mock.patch('utils.dockerutil.DockerUtil.client', return_value=None):
257-
with mock.patch.object(SDDockerBackend, '_get_host_address', return_value='127.0.0.1'):
258-
with mock.patch.object(SDDockerBackend, '_get_port', return_value='1337'):
259-
c_id = self.docker_container_inspect.get('Id')
260-
for image in self.mock_templates.keys():
261-
sd_backend = get_sd_backend(agentConfig=self.auto_conf_agentConfig)
262-
self.assertEquals(
263-
sd_backend._get_check_configs(c_id, image)[0],
264-
self.mock_templates[image][1])
265-
clear_singletons(self.auto_conf_agentConfig)
261+
c_id = self.docker_container_inspect.get('Id')
262+
for image in self.mock_templates.keys():
263+
sd_backend = get_sd_backend(agentConfig=self.auto_conf_agentConfig)
264+
self.assertEquals(
265+
sd_backend._get_check_configs(c_id, image)[0],
266+
self.mock_templates[image][1])
267+
clear_singletons(self.auto_conf_agentConfig)
266268

269+
@mock.patch('utils.dockerutil.DockerUtil.client', return_value=None)
270+
@mock.patch.object(ConsulStore, 'get_client', return_value=None)
271+
@mock.patch.object(EtcdStore, 'get_client', return_value=None)
267272
@mock.patch.object(AbstractConfigStore, 'get_check_tpls', side_effect=_get_check_tpls)
268-
def test_get_config_templates(self, mock_get_check_tpls):
273+
def test_get_config_templates(self, *args):
269274
"""Test _get_config_templates with mocked get_check_tpls"""
270-
with mock.patch('utils.dockerutil.DockerUtil.client', return_value=None):
271-
with mock.patch.object(EtcdStore, 'get_client', return_value=None):
272-
with mock.patch.object(ConsulStore, 'get_client', return_value=None):
273-
for agentConfig in self.agentConfigs:
274-
sd_backend = get_sd_backend(agentConfig=agentConfig)
275-
# normal cases
276-
for image in self.mock_templates.keys():
277-
template = sd_backend._get_config_templates(image)
278-
expected_template = self.mock_templates.get(image)[0]
279-
self.assertEquals(template, expected_template)
280-
# error cases
281-
for image in self.bad_mock_templates.keys():
282-
self.assertEquals(sd_backend._get_config_templates(image), None)
283-
clear_singletons(agentConfig)
275+
for agentConfig in self.agentConfigs:
276+
sd_backend = get_sd_backend(agentConfig=agentConfig)
277+
# normal cases
278+
for image in self.mock_templates.keys():
279+
template = sd_backend._get_config_templates(image)
280+
expected_template = self.mock_templates.get(image)[0]
281+
expected_template = [(t[0], image + ':0', t[1]) for t in expected_template]
282+
self.assertEquals(template, expected_template)
283+
# error cases
284+
for image in self.bad_mock_templates.keys():
285+
self.assertEquals(sd_backend._get_config_templates(image), None)
286+
clear_singletons(agentConfig)
284287

285288
def test_render_template(self):
286289
"""Test _render_template"""
@@ -526,12 +529,13 @@ def test_get_check_tpls(self, mock_client_read):
526529

527530
@mock.patch.object(AbstractConfigStore, 'client_read', side_effect=client_read)
528531
def test_get_check_tpls_kube(self, mock_client_read):
529-
"""Test get_check_tpls"""
532+
"""Test get_check_tpls for kubernetes annotations"""
530533
valid_config = ['image_0', 'image_1', 'image_2']
531534
invalid_config = ['bad_image_0']
532535
config_store = get_config_store(self.auto_conf_agentConfig)
533536
for image in valid_config + invalid_config:
534537
tpl = self.mock_tpls.get(image)[1]
538+
tpl = [(CONFIG_FROM_KUBE, t[1], t[2]) for t in tpl]
535539
if tpl:
536540
self.assertNotEquals(
537541
tpl,
@@ -540,6 +544,7 @@ def test_get_check_tpls_kube(self, mock_client_read):
540544
tpl,
541545
config_store.get_check_tpls(
542546
'k8s-' + image, auto_conf=True,
547+
kube_pod_name=image,
543548
kube_annotations=dict(zip(
544549
['com.datadoghq.sd/check_names',
545550
'com.datadoghq.sd/init_configs',

utils/service_discovery/abstract_config_store.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
INIT_CONFIGS = 'init_configs'
2727
INSTANCES = 'instances'
2828
KUBE_ANNOTATIONS = 'kube_annotations'
29+
KUBE_POD_NAME = 'kube_pod_name'
2930
KUBE_ANNOTATION_PREFIX = 'com.datadoghq.sd/'
3031

3132

@@ -141,29 +142,26 @@ def get_checks_to_refresh(self, identifier, **kwargs):
141142

142143
def get_check_tpls(self, identifier, **kwargs):
143144
"""Retrieve template configs for an identifier from the config_store or auto configuration."""
144-
trace_config = kwargs.get(TRACE_CONFIG, False)
145-
146145
# this flag is used when no valid configuration store was provided
147146
# it makes the method skip directly to the auto_conf
148147
if kwargs.get('auto_conf') is True:
149148
# When not using a configuration store on kubernetes, check the pod
150149
# annotations for configs before falling back to autoconf.
151150
kube_annotations = kwargs.get(KUBE_ANNOTATIONS)
151+
kube_pod_name = kwargs.get(KUBE_POD_NAME)
152152
if kube_annotations:
153153
kube_config = self._get_kube_config(identifier, kube_annotations)
154154
if kube_config is not None:
155155
check_names, init_config_tpls, instance_tpls = kube_config
156156
source = CONFIG_FROM_KUBE
157-
return [(source, vs) if trace_config else vs
158-
for vs in zip(check_names, init_config_tpls, instance_tpls)]
157+
return [(source, '{}:{}'.format(kube_pod_name, i), vs)
158+
for i, vs in enumerate(zip(check_names, init_config_tpls, instance_tpls))]
159159

160160
# in auto config mode, identifier is the image name
161161
auto_config = self._get_auto_config(identifier)
162162
if auto_config is not None:
163163
source = CONFIG_FROM_AUTOCONF
164-
if trace_config:
165-
return [(source, auto_config)]
166-
return [auto_config]
164+
return [(source, identifier, auto_config)]
167165
else:
168166
log.debug('No auto config was found for image %s, leaving it alone.' % identifier)
169167
return []
@@ -183,8 +181,8 @@ def get_check_tpls(self, identifier, **kwargs):
183181
# Try to update the identifier_to_checks cache
184182
self._update_identifier_to_checks(identifier, check_names)
185183

186-
return [(source, values) if trace_config else values
187-
for values in zip(check_names, init_config_tpls, instance_tpls)]
184+
return [(source, '{}:{}'.format(identifier, i), values)
185+
for i, values in enumerate(zip(check_names, init_config_tpls, instance_tpls))]
188186

189187
def read_config_from_store(self, identifier):
190188
"""Try to read from the config store, falls back to auto-config in case of failure."""

0 commit comments

Comments
 (0)