Skip to content

Commit e50672b

Browse files
Prevent hostname evaluating to None in sqlserver check (#18237)
* Prevent hostname evaluating to None in sqlserver check * always reference resolved_hostname property * add changelog * fix tests * reload hostname when engine edition static info expires --------- Co-authored-by: Zhengda Lu <[email protected]>
1 parent 3135f32 commit e50672b

File tree

5 files changed

+42
-23
lines changed

5 files changed

+42
-23
lines changed

sqlserver/changelog.d/18237.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevent hostname evaluating to None in sqlserver check

sqlserver/datadog_checks/sqlserver/connection.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,7 @@ class Connection(object):
152152

153153
VALID_ADOPROVIDERS = ['SQLOLEDB', 'MSOLEDBSQL', 'MSOLEDBSQL19', 'SQLNCLI11']
154154

155-
def __init__(self, host, init_config, instance_config, service_check_handler):
156-
self.host = host
155+
def __init__(self, init_config, instance_config, service_check_handler):
157156
self.instance = instance_config
158157
self.service_check_handler = service_check_handler
159158
self.log = get_check_logger()

sqlserver/datadog_checks/sqlserver/sqlserver.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def __init__(self, name, init_config, instances):
154154
# go through the agent internal metrics submission processing those tags
155155
self.non_internal_tags = copy.deepcopy(self.tags)
156156
self.check_initializations.append(self.initialize_connection)
157-
self.check_initializations.append(self.set_resolved_hostname)
157+
self.check_initializations.append(self.load_static_information)
158158
self.check_initializations.append(self.set_resolved_hostname_metadata)
159159
self.check_initializations.append(self.config_checks)
160160
self.check_initializations.append(self.make_metric_list_to_collect)
@@ -244,7 +244,6 @@ def set_resource_tags(self):
244244

245245
def set_resolved_hostname(self):
246246
# load static information cache
247-
self.load_static_information()
248247
if self._resolved_hostname is None:
249248
if self._config.reported_hostname:
250249
self._resolved_hostname = self._config.reported_hostname
@@ -280,6 +279,7 @@ def resolved_hostname(self):
280279
return self._resolved_hostname
281280

282281
def load_static_information(self):
282+
engine_edition_reloaded = False
283283
expected_keys = {STATIC_INFO_VERSION, STATIC_INFO_MAJOR_VERSION, STATIC_INFO_ENGINE_EDITION, STATIC_INFO_RDS}
284284
missing_keys = expected_keys - set(self.static_info_cache.keys())
285285
if missing_keys:
@@ -309,6 +309,7 @@ def load_static_information(self):
309309
result = cursor.fetchone()
310310
if result:
311311
self.static_info_cache[STATIC_INFO_ENGINE_EDITION] = result[0]
312+
engine_edition_reloaded = True
312313
else:
313314
self.log.warning("failed to load version static information due to empty results")
314315
if STATIC_INFO_RDS not in self.static_info_cache:
@@ -318,9 +319,11 @@ def load_static_information(self):
318319
self.static_info_cache[STATIC_INFO_RDS] = True
319320
else:
320321
self.static_info_cache[STATIC_INFO_RDS] = False
321-
# re-initialize resolved_hostname to ensure we take into consideration the static information
322+
# re-initialize resolved_hostname to ensure we take into consideration the egine edition
322323
# after it's loaded
323-
self._resolved_hostname = None
324+
if engine_edition_reloaded:
325+
self._resolved_hostname = None
326+
self.set_resolved_hostname()
324327

325328
def debug_tags(self):
326329
return self.tags + ["agent_hostname:{}".format(self.agent_hostname)]
@@ -342,7 +345,6 @@ def agent_hostname(self):
342345

343346
def initialize_connection(self):
344347
self.connection = Connection(
345-
host=self.resolved_hostname,
346348
init_config=self.init_config,
347349
instance_config=self.instance,
348350
service_check_handler=self.handle_service_check,
@@ -709,6 +711,7 @@ def _check_database_conns(self):
709711

710712
def check(self, _):
711713
if self.do_check:
714+
self.load_static_information()
712715
# configure custom queries for the check
713716
if self._query_manager is None:
714717
# use QueryManager to process custom queries

sqlserver/tests/test_connection.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ def test_warn_trusted_connection_username_pass(instance_minimal_defaults, cs, us
7474
instance_minimal_defaults["connection_string"] = cs
7575
instance_minimal_defaults["username"] = username
7676
instance_minimal_defaults["password"] = password
77-
check = SQLServer(CHECK_NAME, {}, [instance_minimal_defaults])
78-
connection = Connection(check.resolved_hostname, {}, instance_minimal_defaults, None)
77+
connection = Connection({}, instance_minimal_defaults, None)
7978
connection.log = mock.MagicMock()
8079
connection._connection_options_validation('somekey', 'somedb')
8180
if expect_warning:
@@ -97,8 +96,7 @@ def test_warn_trusted_connection_username_pass(instance_minimal_defaults, cs, us
9796
)
9897
def test_will_warn_parameters_for_the_wrong_connection(instance_minimal_defaults, connector, param):
9998
instance_minimal_defaults.update({'connector': connector, param: 'foo'})
100-
check = SQLServer(CHECK_NAME, {}, [instance_minimal_defaults])
101-
connection = Connection(check.resolved_hostname, {}, instance_minimal_defaults, None)
99+
connection = Connection({}, instance_minimal_defaults, None)
102100
connection.log = mock.MagicMock()
103101
connection._connection_options_validation('somekey', 'somedb')
104102
connection.log.warning.assert_called_once_with(
@@ -130,8 +128,7 @@ def test_will_warn_parameters_for_the_wrong_connection(instance_minimal_defaults
130128
)
131129
def test_will_fail_for_duplicate_parameters(instance_minimal_defaults, connector, cs, param, should_fail):
132130
instance_minimal_defaults.update({'connector': connector, param: 'foo', 'connection_string': cs + "=foo"})
133-
check = SQLServer(CHECK_NAME, {}, [instance_minimal_defaults])
134-
connection = Connection(check.resolved_hostname, {}, instance_minimal_defaults, None)
131+
connection = Connection({}, instance_minimal_defaults, None)
135132
if should_fail:
136133
match = (
137134
"%s has been provided both in the connection string and as a configuration option (%s), "
@@ -162,8 +159,7 @@ def test_will_fail_for_duplicate_parameters(instance_minimal_defaults, connector
162159
def test_will_fail_for_wrong_parameters_in_the_connection_string(instance_minimal_defaults, connector, cs):
163160
instance_minimal_defaults.update({'connector': connector, 'connection_string': cs + '=foo'})
164161
other_connector = 'odbc' if connector != 'odbc' else 'adodbapi'
165-
check = SQLServer(CHECK_NAME, {}, [instance_minimal_defaults])
166-
connection = Connection(check.resolved_hostname, {}, instance_minimal_defaults, None)
162+
connection = Connection({}, instance_minimal_defaults, None)
167163
match = (
168164
"%s has been provided in the connection string. "
169165
"This option is only available for %s connections, however %s has been selected"
@@ -226,8 +222,7 @@ def test_managed_auth_config_valid(instance_minimal_defaults, name, managed_iden
226222
for k, v in managed_identity_config.items():
227223
instance_minimal_defaults[k] = v
228224
instance_minimal_defaults.update({'connector': 'odbc'})
229-
check = SQLServer(CHECK_NAME, {}, [instance_minimal_defaults])
230-
connection = Connection(check.resolved_hostname, {}, instance_minimal_defaults, None)
225+
connection = Connection({}, instance_minimal_defaults, None)
231226
if should_fail:
232227
with pytest.raises(ConfigurationError, match=re.escape(expected_err)):
233228
connection._connection_options_validation('somekey', 'somedb')
@@ -287,8 +282,7 @@ def test_managed_auth_config_valid(instance_minimal_defaults, name, managed_iden
287282
def test_config_with_and_without_port(instance_minimal_defaults, host, port, expected_host):
288283
instance_minimal_defaults["host"] = host
289284
instance_minimal_defaults["port"] = port
290-
check = SQLServer(CHECK_NAME, {}, [instance_minimal_defaults])
291-
connection = Connection(check.resolved_hostname, {}, instance_minimal_defaults, None)
285+
connection = Connection({}, instance_minimal_defaults, None)
292286
_, result_host, _, _, _, _ = connection._get_access_info('somekey', 'somedb')
293287
assert result_host == expected_host
294288

@@ -369,9 +363,7 @@ def test_connection_failure(aggregator, dd_run_check, instance_docker):
369363

370364
try:
371365
# Break the connection
372-
check.connection = Connection(
373-
check.resolved_hostname, {}, {'host': '', 'username': '', 'password': ''}, check.handle_service_check
374-
)
366+
check.connection = Connection({}, {'host': '', 'username': '', 'password': ''}, check.handle_service_check)
375367
dd_run_check(check)
376368
except Exception:
377369
aggregator.assert_service_check(
@@ -495,7 +487,7 @@ def test_connection_error_reporting(
495487
expected_error_pattern = matching_patterns[0]
496488

497489
check = SQLServer(CHECK_NAME, {}, [instance_docker])
498-
connection = Connection(check.resolved_hostname, check.init_config, check.instance, check.handle_service_check)
490+
connection = Connection(check.init_config, check.instance, check.handle_service_check)
499491
with pytest.raises(SQLConnectionError) as excinfo:
500492
with connection.open_managed_default_connection():
501493
pytest.fail("connection should not have succeeded")

sqlserver/tests/test_integration.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,3 +873,27 @@ def test_propagate_agent_tags(
873873
status=SQLServer.OK,
874874
tags=expected_tags,
875875
)
876+
877+
878+
@pytest.mark.integration
879+
@pytest.mark.usefixtures('dd_environment')
880+
def test_check_static_information_expire(aggregator, dd_run_check, init_config, instance_docker):
881+
sqlserver_check = SQLServer(CHECK_NAME, init_config, [instance_docker])
882+
dd_run_check(sqlserver_check)
883+
assert sqlserver_check.static_info_cache is not None
884+
assert len(sqlserver_check.static_info_cache.keys()) == 4
885+
assert sqlserver_check.resolved_hostname == 'stubbed.hostname'
886+
887+
# manually clear static information cache
888+
sqlserver_check.static_info_cache.clear()
889+
dd_run_check(sqlserver_check)
890+
assert sqlserver_check.static_info_cache is not None
891+
assert len(sqlserver_check.static_info_cache.keys()) == 4
892+
assert sqlserver_check.resolved_hostname == 'stubbed.hostname'
893+
894+
# manually pop STATIC_INFO_ENGINE_EDITION to make sure it is reloaded
895+
sqlserver_check.static_info_cache.pop(STATIC_INFO_ENGINE_EDITION)
896+
dd_run_check(sqlserver_check)
897+
assert sqlserver_check.static_info_cache is not None
898+
assert len(sqlserver_check.static_info_cache.keys()) == 4
899+
assert sqlserver_check.resolved_hostname == 'stubbed.hostname'

0 commit comments

Comments
 (0)