Skip to content

Commit

Permalink
Propagate agent host tags for mysql (#18400)
Browse files Browse the repository at this point in the history
  • Loading branch information
natashadada authored Sep 13, 2024
1 parent 9eb8f2d commit ef41af5
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 8 deletions.
17 changes: 16 additions & 1 deletion mysql/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ files:
options:
- template: init_config
options:
- name: propagate_agent_tags
description: |
Set to `true` to propagate the tags from `datadog.yaml` to the check.
When set to `true`, the tags from `datadog.yaml` are added to the check's tags for all instances.
value:
example: false
type: boolean
- template: init_config/db
overrides:
global_custom_queries.value.example:
Expand Down Expand Up @@ -49,6 +56,14 @@ files:
type: number
example: 3306

- name: propagate_agent_tags
description: |
Set to `true` to propagate the tags from `datadog.yaml` to the check.
When set to `true`, the tags from `datadog.yaml` are added to the check's tags for all instances.
value:
example: false
type: boolean

- name: reported_hostname
description: |
Set the reported hostname for this instance. This value overrides the hostname detected by the Agent
Expand Down Expand Up @@ -390,7 +405,7 @@ files:
Capped by `schemas_collection.collection_interval`
value:
type: number
example: 60
example: 60
- name: query_metrics
description: Configure collection of query metrics
options:
Expand Down
1 change: 1 addition & 0 deletions mysql/changelog.d/18400.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update the propagate_agent_tags setting. When set to `true`, the tags from the agent host are now added to the check's tags for all instances.
43 changes: 39 additions & 4 deletions mysql/datadog_checks/mysql/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from datadog_checks.base import ConfigurationError, is_affirmative
from datadog_checks.base.log import get_check_logger
from datadog_checks.base.utils.aws import rds_parse_tags_from_endpoint
from datadog_checks.base.utils.db.utils import get_agent_host_tags

DEFAULT_MAX_CUSTOM_QUERIES = 20


class MySQLConfig(object):
def __init__(self, instance):
def __init__(self, instance, init_config):
self.log = get_check_logger()
self.host = instance.get('host', instance.get('server', ''))
self.port = int(instance.get('port', 0))
Expand All @@ -18,7 +19,10 @@ def __init__(self, instance):
self.defaults_file = instance.get('defaults_file', '')
self.user = instance.get('username', instance.get('user', ''))
self.password = str(instance.get('password', instance.get('pass', '')))
self.tags = self._build_tags(instance.get('tags', []))
self.tags = self._build_tags(
custom_tags=instance.get('tags', []),
propagate_agent_tags=self._should_propagate_agent_tags(instance, init_config),
)
self.options = instance.get('options', {}) or {} # options could be None if empty in the YAML
replication_channel = self.options.get('replication_channel')
if replication_channel:
Expand Down Expand Up @@ -94,12 +98,26 @@ def __init__(self, instance):
self.database_instance_collection_interval = instance.get('database_instance_collection_interval', 300)
self.configuration_checks()

def _build_tags(self, custom_tags):
tags = list(set(custom_tags)) or []
def _build_tags(self, custom_tags, propagate_agent_tags):
# Clean up tags in case there was a None entry in the instance
# e.g. if the yaml contains tags: but no actual tags
if custom_tags is None:
tags = []
else:
tags = list(set(custom_tags))

rds_tags = rds_parse_tags_from_endpoint(self.host)
if rds_tags:
tags.extend(rds_tags)

if propagate_agent_tags:
try:
agent_tags = get_agent_host_tags()
tags.extend(agent_tags)
except Exception as e:
raise ConfigurationError(
'propagate_agent_tags enabled but there was an error fetching agent tags {}'.format(e)
)
return tags

def configuration_checks(self):
Expand All @@ -119,3 +137,20 @@ def configuration_checks(self):

if self.mysql_sock and self.host:
self.log.warning("Both socket and host have been specified, socket will be used")

@staticmethod
def _should_propagate_agent_tags(instance, init_config) -> bool:
'''
return True if the agent tags should be propagated to the check
'''
instance_propagate_agent_tags = instance.get('propagate_agent_tags')
init_config_propagate_agent_tags = init_config.get('propagate_agent_tags')

if instance_propagate_agent_tags is not None:
# if the instance has explicitly set the value, return the boolean
return instance_propagate_agent_tags
if init_config_propagate_agent_tags is not None:
# if the init_config has explicitly set the value, return the boolean
return init_config_propagate_agent_tags
# if neither the instance nor the init_config has set the value, return False
return False
8 changes: 8 additions & 0 deletions mysql/datadog_checks/mysql/config_models/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
# ddev -x validate models -s <INTEGRATION_NAME>


def shared_propagate_agent_tags():
return False


def instance_connect_timeout():
return 10

Expand Down Expand Up @@ -52,5 +56,9 @@ def instance_port():
return 3306


def instance_propagate_agent_tags():
return False


def instance_use_global_custom_queries():
return 'true'
1 change: 1 addition & 0 deletions mysql/datadog_checks/mysql/config_models/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ class InstanceConfig(BaseModel):
options: Optional[Options] = None
password: Optional[str] = None
port: Optional[float] = None
propagate_agent_tags: Optional[bool] = None
queries: Optional[tuple[MappingProxyType[str, Any], ...]] = None
query_activity: Optional[QueryActivity] = None
query_metrics: Optional[QueryMetrics] = None
Expand Down
5 changes: 4 additions & 1 deletion mysql/datadog_checks/mysql/config_models/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from datadog_checks.base.utils.functions import identity
from datadog_checks.base.utils.models import validation

from . import validators
from . import defaults, validators


class SharedConfig(BaseModel):
Expand All @@ -27,6 +27,7 @@ class SharedConfig(BaseModel):
frozen=True,
)
global_custom_queries: Optional[tuple[MappingProxyType[str, Any], ...]] = None
propagate_agent_tags: Optional[bool] = None
service: Optional[str] = None

@model_validator(mode='before')
Expand All @@ -39,6 +40,8 @@ def _validate(cls, value, info):
field_name = field.alias or info.field_name
if field_name in info.context['configured_fields']:
value = getattr(validators, f'shared_{info.field_name}', identity)(value, field=field)
else:
value = getattr(defaults, f'shared_{info.field_name}', lambda: value)()

return validation.utils.make_immutable(value)

Expand Down
12 changes: 12 additions & 0 deletions mysql/datadog_checks/mysql/data/conf.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
#
init_config:

## @param propagate_agent_tags - boolean - optional - default: false
## Set to `true` to propagate the tags from `datadog.yaml` to the check.
## When set to `true`, the tags from `datadog.yaml` are added to the check's tags for all instances.
#
# propagate_agent_tags: false

## @param global_custom_queries - list of mappings - optional
## See `custom_queries` defined below.
##
Expand Down Expand Up @@ -46,6 +52,12 @@ instances:
#
port: 3306

## @param propagate_agent_tags - boolean - optional - default: false
## Set to `true` to propagate the tags from `datadog.yaml` to the check.
## When set to `true`, the tags from `datadog.yaml` are added to the check's tags for all instances.
#
# propagate_agent_tags: false

## @param reported_hostname - string - optional
## Set the reported hostname for this instance. This value overrides the hostname detected by the Agent
## and can be useful to set a custom hostname when connecting to a remote database through a proxy.
Expand Down
2 changes: 1 addition & 1 deletion mysql/datadog_checks/mysql/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def __init__(self, name, init_config, instances):
self._resolved_hostname = None
self._agent_hostname = None
self._is_aurora = None
self._config = MySQLConfig(self.instance)
self._config = MySQLConfig(self.instance, init_config)
self.tags = self._config.tags
self.cloud_metadata = self._config.cloud_metadata

Expand Down
2 changes: 1 addition & 1 deletion mysql/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ classifiers = [
"Private :: Do Not Upload",
]
dependencies = [
"datadog-checks-base>=36.5.0",
"datadog-checks-base>=36.14.0",
]
dynamic = [
"version",
Expand Down
56 changes: 56 additions & 0 deletions mysql/tests/test_mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,3 +759,59 @@ def test_database_instance_metadata(aggregator, dd_run_check, instance_complex,
dbm_metadata = aggregator.get_event_platform_events("dbm-metadata")
event = next((e for e in dbm_metadata if e['kind'] == 'database_instance'), None)
assert event is None


@pytest.mark.parametrize(
'instance_propagate_agent_tags,init_config_propagate_agent_tags,should_propagate_agent_tags',
[
pytest.param(True, True, True, id="both true"),
pytest.param(True, False, True, id="instance config true prevails"),
pytest.param(False, True, False, id="instance config false prevails"),
pytest.param(False, False, False, id="both false"),
pytest.param(None, True, True, id="init_config true applies to all instances"),
pytest.param(None, False, False, id="init_config false applies to all instances"),
pytest.param(None, None, False, id="default to false"),
pytest.param(True, None, True, id="instance config true prevails, init_config is None"),
pytest.param(False, None, False, id="instance config false prevails, init_config is None"),
],
)
@pytest.mark.integration
def test_propagate_agent_tags(
aggregator,
dd_run_check,
instance_basic,
instance_propagate_agent_tags,
init_config_propagate_agent_tags,
should_propagate_agent_tags,
):
instance_basic['propagate_agent_tags'] = instance_propagate_agent_tags
init_config = {}
if init_config_propagate_agent_tags is not None:
init_config['propagate_agent_tags'] = init_config_propagate_agent_tags

agent_tags = ['my-env:test-env', 'random:tag', 'bar:foo']
expected_tags = (
instance_basic.get('tags', [])
+ [
'server:{}'.format(HOST),
'port:{}'.format(PORT),
'dd.internal.resource:database_instance:forced_hostname',
"dd.internal.resource:aws_rds_instance:foo.aws.com",
"dd.internal.resource:azure_mysql_server:my-instance",
'dd.internal.resource:gcp_sql_database_instance:foo-project:bar',
]
+ agent_tags
)

with mock.patch('datadog_checks.mysql.config.get_agent_host_tags', return_value=agent_tags):
check = MySql(common.CHECK_NAME, init_config, [instance_basic])
assert check._config._should_propagate_agent_tags(instance_basic, init_config) == should_propagate_agent_tags
if should_propagate_agent_tags:
assert all(tag in check.tags for tag in agent_tags)
dd_run_check(check)
aggregator.assert_service_check(
'mysql.can_connect',
count=1,
status=MySql.OK,
tags=expected_tags,
)

0 comments on commit ef41af5

Please sign in to comment.