From f2fba05cf30c4f04db469e6b6bfff2e315c23eec Mon Sep 17 00:00:00 2001 From: Xavier Vello Date: Wed, 26 Jul 2017 16:31:49 +0200 Subject: [PATCH 1/4] allow autoconf templates in docker labels --- tests/core/test_service_discovery.py | 27 +++++++++- .../abstract_config_store.py | 51 +++++++++++++++---- utils/service_discovery/sd_docker_backend.py | 13 +++-- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/tests/core/test_service_discovery.py b/tests/core/test_service_discovery.py index 401c884008..49f4a9d1e5 100644 --- a/tests/core/test_service_discovery.py +++ b/tests/core/test_service_discovery.py @@ -14,7 +14,7 @@ from utils.service_discovery.consul_config_store import ConsulStore from utils.service_discovery.etcd_config_store import EtcdStore from utils.service_discovery.abstract_config_store import AbstractConfigStore, \ - _TemplateCache, CONFIG_FROM_KUBE, CONFIG_FROM_TEMPLATE, CONFIG_FROM_AUTOCONF + _TemplateCache, CONFIG_FROM_KUBE, CONFIG_FROM_TEMPLATE, CONFIG_FROM_AUTOCONF, CONFIG_FROM_LABELS from utils.service_discovery.sd_backend import get_sd_backend from utils.service_discovery.sd_docker_backend import SDDockerBackend, _SDDockerBackendConfigFetchState from utils.dockerutil import DockerUtil @@ -611,6 +611,31 @@ def test_get_check_tpls_kube(self, *args): 'service-discovery.datadoghq.com/foo.instances'], self.mock_raw_templates[image][0])))) + @mock.patch('config.get_auto_confd_path', return_value=os.path.join( + os.path.dirname(__file__), 'fixtures/auto_conf/')) + @mock.patch.object(AbstractConfigStore, 'client_read', side_effect=client_read) + def test_get_check_tpls_labels(self, *args): + """Test get_check_tpls from docker labesl""" + valid_config = ['image_0', 'image_1', 'image_2', 'image_3', 'image_4'] + invalid_config = ['bad_image_0'] + config_store = get_config_store(self.auto_conf_agentConfig) + for image in valid_config + invalid_config: + tpl = self.mock_raw_templates.get(image)[1] + tpl = [(CONFIG_FROM_LABELS, t[1]) for t in tpl] + if tpl: + self.assertNotEquals( + tpl, + config_store.get_check_tpls(image, auto_conf=True)) + self.assertEquals( + tpl, + config_store.get_check_tpls( + image, auto_conf=True, + docker_labels=dict(zip( + ['service-discovery.datadoghq.com/check_names', + 'service-discovery.datadoghq.com/init_configs', + 'service-discovery.datadoghq.com/instances'], + self.mock_raw_templates[image][0])))) + @mock.patch('config.get_auto_confd_path', return_value=os.path.join( os.path.dirname(__file__), 'fixtures/auto_conf/')) def test_get_config_id(self, mock_get_auto_confd_path): diff --git a/utils/service_discovery/abstract_config_store.py b/utils/service_discovery/abstract_config_store.py index 436f508093..8196be5f87 100644 --- a/utils/service_discovery/abstract_config_store.py +++ b/utils/service_discovery/abstract_config_store.py @@ -23,12 +23,15 @@ CONFIG_FROM_FILE = 'YAML file' CONFIG_FROM_TEMPLATE = 'template' CONFIG_FROM_KUBE = 'Kubernetes Pod Annotation' +CONFIG_FROM_LABELS = 'Docker container labels' + TRACE_CONFIG = 'trace_config' # used for tracing config load by service discovery CHECK_NAMES = 'check_names' INIT_CONFIGS = 'init_configs' INSTANCES = 'instances' KUBE_ANNOTATIONS = 'kube_annotations' KUBE_CONTAINER_NAME = 'kube_container_name' +DOCKER_LABELS = 'docker_labels' KUBE_ANNOTATION_PREFIX = 'service-discovery.datadoghq.com' @@ -210,17 +213,28 @@ def dump_directory(self, path, **kwargs): raise NotImplementedError() def _get_kube_config(self, identifier, kube_annotations, kube_container_name): + prefix = '{}/{}.'.format(KUBE_ANNOTATION_PREFIX, kube_container_name) + return self._extract_template(identifier, prefix, kube_annotations) + + def _get_docker_config(self, identifier, docker_labels): + prefix = '{}/'.format(KUBE_ANNOTATION_PREFIX) + return self._extract_template(identifier, prefix, docker_labels) + + def _extract_template(self, identifier, key_prefix, source_dict): + """ + Looks for autodiscovery configuration in a given source_dict (either docker labels + or kubernetes annotations) and returns it if found. + """ try: - prefix = '{}/{}.'.format(KUBE_ANNOTATION_PREFIX, kube_container_name) - check_names = json.loads(kube_annotations[prefix + CHECK_NAMES]) - init_config_tpls = json.loads(kube_annotations[prefix + INIT_CONFIGS]) - instance_tpls = json.loads(kube_annotations[prefix + INSTANCES]) + check_names = json.loads(source_dict[key_prefix + CHECK_NAMES]) + init_config_tpls = json.loads(source_dict[key_prefix + INIT_CONFIGS]) + instance_tpls = json.loads(source_dict[key_prefix + INSTANCES]) return [check_names, init_config_tpls, instance_tpls] except KeyError: return None except json.JSONDecodeError: log.exception('Could not decode the JSON configuration template ' - 'for the kubernetes pod with ident %s...' % identifier) + 'for the entity %s...' % identifier) return None def _get_auto_config(self, image_name): @@ -254,6 +268,7 @@ def get_checks_to_refresh(self, identifier, **kwargs): kube_annotations = kwargs.get(KUBE_ANNOTATIONS) kube_container_name = kwargs.get(KUBE_CONTAINER_NAME) + docker_labels = kwargs.get(DOCKER_LABELS) # then from annotations if kube_annotations: @@ -261,6 +276,11 @@ def get_checks_to_refresh(self, identifier, **kwargs): if kube_config is not None: to_check.update(kube_config[0]) + # then from docker labels + if docker_labels: + kube_config = self._get_docker_config(identifier, docker_labels) + if kube_config is not None: + to_check.update(kube_config[0]) # lastly, try with legacy name for auto-conf to_check.update(self.template_cache.get_check_names(self._get_image_ident(identifier))) @@ -276,13 +296,21 @@ def get_check_tpls(self, identifier, **kwargs): # annotations for configs before falling back to autoconf. kube_annotations = kwargs.get(KUBE_ANNOTATIONS) kube_container_name = kwargs.get(KUBE_CONTAINER_NAME) + docker_labels = kwargs.get(DOCKER_LABELS) + source = "" + + kube_config = None if kube_annotations: kube_config = self._get_kube_config(identifier, kube_annotations, kube_container_name) - if kube_config is not None: - check_names, init_config_tpls, instance_tpls = kube_config - source = CONFIG_FROM_KUBE - return [(source, vs) - for vs in zip(check_names, init_config_tpls, instance_tpls)] + source = CONFIG_FROM_KUBE + if kube_config is None and docker_labels is not None: + kube_config = self._get_docker_config(identifier, docker_labels) + source = CONFIG_FROM_LABELS + + if kube_config is not None: + check_names, init_config_tpls, instance_tpls = kube_config + return [(source, vs) + for vs in zip(check_names, init_config_tpls, instance_tpls)] # in auto config mode, identifier is the image name auto_config = self._get_auto_config(identifier) @@ -339,6 +367,9 @@ def read_config_from_store(self, identifier): def _get_image_ident(self, ident): """Extract an identifier from the image""" + # handle exceptionnal empty ident case (docker bug) + if not ident: + return "" # handle the 'redis@sha256:...' format if '@' in ident: return ident.split('@')[0].split('/')[-1] diff --git a/utils/service_discovery/sd_docker_backend.py b/utils/service_discovery/sd_docker_backend.py index 7093678c5c..2479aa8a98 100644 --- a/utils/service_discovery/sd_docker_backend.py +++ b/utils/service_discovery/sd_docker_backend.py @@ -149,7 +149,8 @@ def _get_checks_to_refresh(self, state, c_id): self.reload_check_configs = True return - identifier = inspect.get('Config', {}).get('Labels', {}).get(DATADOG_ID) or \ + labels = inspect.get('Config', {}).get('Labels', {}) + identifier = labels.get(DATADOG_ID) or \ self.dockerutil.image_name_extractor(inspect) platform_kwargs = {} @@ -159,7 +160,8 @@ def _get_checks_to_refresh(self, state, c_id): 'kube_annotations': kube_metadata.get('annotations'), 'kube_container_name': state.get_kube_container_name(c_id), } - + if labels: + platform_kwargs['docker_labels'] = labels return self.config_store.get_checks_to_refresh(identifier, **platform_kwargs) def _get_container_pid(self, state, cid, tpl_var): @@ -374,7 +376,7 @@ def get_configs(self): try: # value of the DATADOG_ID tag or the image name if the label is missing identifier = self.get_config_id(image, labels) - check_configs = self._get_check_configs(state, cid, identifier) or [] + check_configs = self._get_check_configs(state, cid, identifier, labels) or [] for conf in check_configs: source, (check_name, init_config, instance) = conf @@ -403,7 +405,7 @@ def get_config_id(self, image, labels): """Look for a DATADOG_ID label, return its value or the image name if missing""" return labels.get(DATADOG_ID) or image - def _get_check_configs(self, state, c_id, identifier): + def _get_check_configs(self, state, c_id, identifier, labels=None): """Retrieve configuration templates and fill them with data pulled from docker and tags.""" platform_kwargs = {} if Platform.is_k8s(): @@ -412,6 +414,9 @@ def _get_check_configs(self, state, c_id, identifier): 'kube_container_name': state.get_kube_container_name(c_id), 'kube_annotations': kube_metadata.get('annotations'), } + if labels: + platform_kwargs['docker_labels'] = labels + config_templates = self._get_config_templates(identifier, **platform_kwargs) if not config_templates: return None From 343e8d8b96a73e0e5b664ef1bfafd57b634bcf4f Mon Sep 17 00:00:00 2001 From: Xavier Vello Date: Wed, 2 Aug 2017 14:05:42 +0200 Subject: [PATCH 2/4] rename auto_conf docker label names --- tests/core/test_service_discovery.py | 6 +++--- utils/service_discovery/abstract_config_store.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/core/test_service_discovery.py b/tests/core/test_service_discovery.py index 49f4a9d1e5..1a8bf63dc8 100644 --- a/tests/core/test_service_discovery.py +++ b/tests/core/test_service_discovery.py @@ -631,9 +631,9 @@ def test_get_check_tpls_labels(self, *args): config_store.get_check_tpls( image, auto_conf=True, docker_labels=dict(zip( - ['service-discovery.datadoghq.com/check_names', - 'service-discovery.datadoghq.com/init_configs', - 'service-discovery.datadoghq.com/instances'], + ['com.datadoghq.sd.check_names', + 'com.datadoghq.sd.init_configs', + 'com.datadoghq.sd.instances'], self.mock_raw_templates[image][0])))) @mock.patch('config.get_auto_confd_path', return_value=os.path.join( diff --git a/utils/service_discovery/abstract_config_store.py b/utils/service_discovery/abstract_config_store.py index 8196be5f87..5b512961fb 100644 --- a/utils/service_discovery/abstract_config_store.py +++ b/utils/service_discovery/abstract_config_store.py @@ -33,6 +33,7 @@ KUBE_CONTAINER_NAME = 'kube_container_name' DOCKER_LABELS = 'docker_labels' KUBE_ANNOTATION_PREFIX = 'service-discovery.datadoghq.com' +DOCKER_LABEL_PREFIX = 'com.datadoghq.sd' class KeyNotFound(Exception): @@ -217,7 +218,7 @@ def _get_kube_config(self, identifier, kube_annotations, kube_container_name): return self._extract_template(identifier, prefix, kube_annotations) def _get_docker_config(self, identifier, docker_labels): - prefix = '{}/'.format(KUBE_ANNOTATION_PREFIX) + prefix = '{}.'.format(DOCKER_LABEL_PREFIX) return self._extract_template(identifier, prefix, docker_labels) def _extract_template(self, identifier, key_prefix, source_dict): From 3f99823593ca4e203218dd92c21440fa5e534f26 Mon Sep 17 00:00:00 2001 From: Xavier Vello Date: Thu, 10 Aug 2017 15:51:33 +0200 Subject: [PATCH 3/4] rename config variables --- .../service_discovery/abstract_config_store.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/utils/service_discovery/abstract_config_store.py b/utils/service_discovery/abstract_config_store.py index 5b512961fb..c339c1731a 100644 --- a/utils/service_discovery/abstract_config_store.py +++ b/utils/service_discovery/abstract_config_store.py @@ -279,9 +279,9 @@ def get_checks_to_refresh(self, identifier, **kwargs): # then from docker labels if docker_labels: - kube_config = self._get_docker_config(identifier, docker_labels) - if kube_config is not None: - to_check.update(kube_config[0]) + docker_config = self._get_docker_config(identifier, docker_labels) + if docker_config is not None: + to_check.update(docker_config[0]) # lastly, try with legacy name for auto-conf to_check.update(self.template_cache.get_check_names(self._get_image_ident(identifier))) @@ -300,16 +300,16 @@ def get_check_tpls(self, identifier, **kwargs): docker_labels = kwargs.get(DOCKER_LABELS) source = "" - kube_config = None + config = None if kube_annotations: - kube_config = self._get_kube_config(identifier, kube_annotations, kube_container_name) + config = self._get_kube_config(identifier, kube_annotations, kube_container_name) source = CONFIG_FROM_KUBE - if kube_config is None and docker_labels is not None: - kube_config = self._get_docker_config(identifier, docker_labels) + if config is None and docker_labels is not None: + config = self._get_docker_config(identifier, docker_labels) source = CONFIG_FROM_LABELS - if kube_config is not None: - check_names, init_config_tpls, instance_tpls = kube_config + if config is not None: + check_names, init_config_tpls, instance_tpls = config return [(source, vs) for vs in zip(check_names, init_config_tpls, instance_tpls)] From 1fea0730095ce55304b52581552a6a933f331b6f Mon Sep 17 00:00:00 2001 From: Xavier Vello Date: Thu, 10 Aug 2017 16:20:37 +0200 Subject: [PATCH 4/4] move to com.datadoghq.ad. prefix --- tests/core/test_service_discovery.py | 6 +++--- utils/service_discovery/abstract_config_store.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/core/test_service_discovery.py b/tests/core/test_service_discovery.py index 1a8bf63dc8..aa61369b64 100644 --- a/tests/core/test_service_discovery.py +++ b/tests/core/test_service_discovery.py @@ -631,9 +631,9 @@ def test_get_check_tpls_labels(self, *args): config_store.get_check_tpls( image, auto_conf=True, docker_labels=dict(zip( - ['com.datadoghq.sd.check_names', - 'com.datadoghq.sd.init_configs', - 'com.datadoghq.sd.instances'], + ['com.datadoghq.ad.check_names', + 'com.datadoghq.ad.init_configs', + 'com.datadoghq.ad.instances'], self.mock_raw_templates[image][0])))) @mock.patch('config.get_auto_confd_path', return_value=os.path.join( diff --git a/utils/service_discovery/abstract_config_store.py b/utils/service_discovery/abstract_config_store.py index c339c1731a..f66dc3690f 100644 --- a/utils/service_discovery/abstract_config_store.py +++ b/utils/service_discovery/abstract_config_store.py @@ -33,7 +33,7 @@ KUBE_CONTAINER_NAME = 'kube_container_name' DOCKER_LABELS = 'docker_labels' KUBE_ANNOTATION_PREFIX = 'service-discovery.datadoghq.com' -DOCKER_LABEL_PREFIX = 'com.datadoghq.sd' +DOCKER_LABEL_PREFIX = 'com.datadoghq.ad' class KeyNotFound(Exception):