Skip to content

Commit 00eabc1

Browse files
authored
Require key_prefix for SQLServer connections (DataDog#22557)
* Require key_prefix for SQLServer connections * Changelog * Rename * Lint * WIP * Lint * Fix
1 parent d04193c commit 00eabc1

17 files changed

Lines changed: 96 additions & 87 deletions

sqlserver/changelog.d/22557.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Require key_prefix for SQLServer connections to avoid unsafely sharing connections across threads

sqlserver/datadog_checks/sqlserver/activity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,8 @@ def collect_activity(self):
481481

482482
# re-use the check's conn module, but set extra_key=dbm-activity- to ensure we get our own
483483
# raw connection. adodbapi and pyodbc modules are thread safe, but connections are not.
484-
with self._check.connection.open_managed_default_connection(key_prefix=self._conn_key_prefix):
485-
with self._check.connection.get_managed_cursor(key_prefix=self._conn_key_prefix) as cursor:
484+
with self._check.connection.open_managed_default_connection(self._conn_key_prefix):
485+
with self._check.connection.get_managed_cursor(self._conn_key_prefix) as cursor:
486486
connections = self._get_active_connections(cursor)
487487
request_cols = self._get_exec_requests_cols_cached(cursor, DM_EXEC_REQUESTS_COLS)
488488
input_buffer_columns, input_buffer_join = self._get_input_buffer_columns_and_join()

sqlserver/datadog_checks/sqlserver/agent_history.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ def collect_agent_history(self):
178178
Collects all current agent activity for the SQLServer intance.
179179
:return:
180180
"""
181-
with self._check.connection.open_managed_default_connection(key_prefix=self._conn_key_prefix):
182-
with self._check.connection.get_managed_cursor(key_prefix=self._conn_key_prefix) as cursor:
181+
with self._check.connection.open_managed_default_connection(self._conn_key_prefix):
182+
with self._check.connection.get_managed_cursor(self._conn_key_prefix) as cursor:
183183
history_rows = self._get_new_agent_job_history(cursor)
184184
history_event = self._create_agent_jobs_history_event(history_rows)
185185
payload = json.dumps(history_event, default=default_json_event_encoding)

sqlserver/datadog_checks/sqlserver/connection.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def __init__(self, init_config, instance_config, service_check_handler):
177177
self.log.debug('Connection initialized.')
178178

179179
@contextmanager
180-
def get_managed_cursor(self, key_prefix=None):
180+
def get_managed_cursor(self, key_prefix):
181181
cursor = self.get_cursor(self.DEFAULT_DB_KEY, key_prefix=key_prefix)
182182
try:
183183
yield cursor
@@ -226,7 +226,7 @@ def open_managed_default_database(self):
226226
yield
227227

228228
@contextmanager
229-
def open_managed_default_connection(self, key_prefix=None):
229+
def open_managed_default_connection(self, key_prefix):
230230
with self._open_managed_db_connections(self.DEFAULT_DB_KEY, key_prefix=key_prefix):
231231
yield
232232

@@ -692,28 +692,28 @@ def test_network_connectivity(self):
692692

693693
return None
694694

695-
def _get_current_database_context(self):
695+
def _get_current_database_context(self, key_prefix):
696696
"""
697697
Get the current database name.
698698
"""
699-
with self.get_managed_cursor() as cursor:
699+
with self.get_managed_cursor(key_prefix) as cursor:
700700
cursor.execute('select DB_NAME()')
701701
data = cursor.fetchall()
702702
return data[0][0]
703703

704704
@contextmanager
705-
def restore_current_database_context(self):
705+
def restore_current_database_context(self, key_prefix):
706706
"""
707707
Restores the default database after executing use statements.
708708
"""
709-
current_db = self._get_current_database_context()
709+
current_db = self._get_current_database_context(key_prefix)
710710
try:
711711
yield
712712
finally:
713713
if current_db:
714714
try:
715715
self.log.debug("Restoring the original database context %s", current_db)
716-
with self.get_managed_cursor() as cursor:
716+
with self.get_managed_cursor(key_prefix) as cursor:
717717
cursor.execute(construct_use_statement(current_db))
718718
except Exception as e:
719719
self.log.error("Failed to switch back to the original database context %s: %s", current_db, e)

sqlserver/datadog_checks/sqlserver/deadlocks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ def _get_connector(self):
133133
return self._check.connection.connector
134134

135135
def _set_xe_session_name(self):
136-
with self._check.connection.open_managed_default_connection(key_prefix=self._conn_key_prefix):
137-
with self._check.connection.get_managed_cursor(key_prefix=self._conn_key_prefix) as cursor:
136+
with self._check.connection.open_managed_default_connection(self._conn_key_prefix):
137+
with self._check.connection.get_managed_cursor(self._conn_key_prefix) as cursor:
138138
if self._xe_session_name is None:
139139
cursor.execute(
140140
get_xe_sessions_query(dm_xe_targets=self._dm_xe_targets, dm_xe_sessions=self._dm_xe_sessions)
@@ -180,8 +180,8 @@ def _query_deadlocks(self):
180180
f'Using XE session [{self._xe_session_name}], target [{self._xe_session_target}] to collect deadlocks'
181181
)
182182

183-
with self._check.connection.open_managed_default_connection(key_prefix=self._conn_key_prefix):
184-
with self._check.connection.get_managed_cursor(key_prefix=self._conn_key_prefix) as cursor:
183+
with self._check.connection.open_managed_default_connection(self._conn_key_prefix):
184+
with self._check.connection.get_managed_cursor(self._conn_key_prefix) as cursor:
185185
convert_xml_to_str = False
186186
if self._force_convert_xml_to_str or self._get_connector() == "adodbapi":
187187
convert_xml_to_str = True

sqlserver/datadog_checks/sqlserver/metadata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ def _load_settings_rows(self, cursor):
139139

140140
@tracked_method(agent_check_getter=agent_check_getter)
141141
def report_sqlserver_metadata(self):
142-
with self._check.connection.open_managed_default_connection(key_prefix=self._conn_key_prefix):
143-
with self._check.connection.get_managed_cursor(key_prefix=self._conn_key_prefix) as cursor:
142+
with self._check.connection.open_managed_default_connection(self._conn_key_prefix):
143+
with self._check.connection.get_managed_cursor(self._conn_key_prefix) as cursor:
144144
settings_rows = self._load_settings_rows(cursor)
145145
event = {
146146
"host": self._check.reported_hostname,

sqlserver/datadog_checks/sqlserver/schemas.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ def kind(self):
9898

9999
def _get_databases(self):
100100
database_names = self._check.get_databases()
101-
with self._check.connection.open_managed_default_connection(key_prefix=KEY_PREFIX):
102-
with self._check.connection.get_managed_cursor(key_prefix=KEY_PREFIX) as cursor:
101+
with self._check.connection.open_managed_default_connection(KEY_PREFIX):
102+
with self._check.connection.get_managed_cursor(KEY_PREFIX) as cursor:
103103
db_names_formatted = ",".join(["'{}'".format(t) for t in database_names])
104104
return execute_query(DB_QUERY.format(db_names_formatted), cursor, convert_results_to_str=True)
105105

106106
@contextlib.contextmanager
107107
def _get_cursor(self, database_name):
108-
with self._check.connection.open_managed_default_connection(key_prefix=KEY_PREFIX):
109-
with self._check.connection.get_managed_cursor(key_prefix=KEY_PREFIX) as cursor:
108+
with self._check.connection.open_managed_default_connection(KEY_PREFIX):
109+
with self._check.connection.get_managed_cursor(KEY_PREFIX) as cursor:
110110
switch_db_statement = construct_use_statement(database_name)
111111
cursor.execute(switch_db_statement)
112112
query = self._get_tables_query()
@@ -167,8 +167,8 @@ def _map_row(self, database: DatabaseInfo, cursor_row) -> DatabaseObject:
167167
if self._is_2016_or_earlier:
168168
# We need to fetch the related data for each table
169169
# Use a key_prefix to get a separate connection to avoid conflicts with the main connection
170-
with self._check.connection.open_managed_default_connection(key_prefix=KEY_PREFIX_PRE_2017):
171-
with self._check.connection.get_managed_cursor(key_prefix=KEY_PREFIX_PRE_2017) as cursor:
170+
with self._check.connection.open_managed_default_connection(KEY_PREFIX_PRE_2017):
171+
with self._check.connection.get_managed_cursor(KEY_PREFIX_PRE_2017) as cursor:
172172
switch_db_statement = construct_use_statement(database.get("name"))
173173
cursor.execute(switch_db_statement)
174174
table_id = str(cursor_row.get("table_id"))

sqlserver/datadog_checks/sqlserver/sqlserver.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@
124124

125125
set_default_driver_conf()
126126

127+
KEY_PREFIX = "dbm-sqlserver-"
128+
127129

128130
class SQLServer(DatabaseCheck):
129131
__NAMESPACE__ = "sqlserver"
@@ -411,8 +413,8 @@ def load_static_information(self):
411413
}
412414
missing_keys = expected_keys - set(self.static_info_cache.keys())
413415
if missing_keys:
414-
with self.connection.open_managed_default_connection():
415-
with self.connection.get_managed_cursor() as cursor:
416+
with self.connection.open_managed_default_connection(KEY_PREFIX):
417+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
416418
if STATIC_INFO_VERSION not in self.static_info_cache:
417419
cursor.execute("select @@version")
418420
results = cursor.fetchall()
@@ -533,8 +535,8 @@ def make_metric_list_to_collect(self):
533535
self.log.warning("Database %s does not exist. Disabling checks for this instance.", context)
534536
return
535537
if self.instance.get("stored_procedure") is None:
536-
with self.connection.open_managed_default_connection():
537-
with self.connection.get_managed_cursor() as cursor:
538+
with self.connection.open_managed_default_connection(KEY_PREFIX):
539+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
538540
self.autodiscover_databases(cursor)
539541
self._make_metric_list_to_collect(self._config.custom_metrics)
540542
except SQLConnectionError:
@@ -754,7 +756,7 @@ def get_sql_counter_type(self, counter_name):
754756
cached = self._sql_counter_types.get(counter_name)
755757
if cached:
756758
return cached
757-
with self.connection.get_managed_cursor() as cursor:
759+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
758760
cursor.execute(COUNTER_TYPE_QUERY, (counter_name,))
759761
(sql_counter_type,) = cursor.fetchone()
760762
if sql_counter_type == PERF_LARGE_RAW_BASE:
@@ -832,8 +834,8 @@ def _check_connections_by_connecting_to_db(self):
832834
self.log.warning("failed service check for auto discovered database: %s", e)
833835

834836
def _check_connections_by_use_db(self):
835-
with self.connection.open_managed_default_connection():
836-
with self.connection.get_managed_cursor() as cursor:
837+
with self.connection.open_managed_default_connection(KEY_PREFIX):
838+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
837839
for db in self.databases:
838840
check_err_message = "Database {} connection service check failed: {}"
839841
try:
@@ -1030,34 +1032,34 @@ def load_basic_metrics(self, cursor):
10301032

10311033
def collect_metrics(self):
10321034
"""Fetch the metrics from all the associated database tables."""
1033-
with self.connection.open_managed_default_connection():
1035+
with self.connection.open_managed_default_connection(KEY_PREFIX):
10341036
if not self._config.only_custom_queries:
1035-
with self.connection.get_managed_cursor() as cursor:
1037+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
10361038
self.load_basic_metrics(cursor)
10371039

10381040
# Neither pyodbc nor adodbapi are able to read results of a query if the number of rows affected
10391041
# statement are returned as part of the result set, so we disable for the entire connection
10401042
# this is important mostly for custom_queries or the stored_procedure feature
10411043
# https://docs.microsoft.com/en-us/sql/t-sql/statements/set-nocount-transact-sql
1042-
with self.connection.get_managed_cursor() as cursor:
1044+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
10431045
cursor.execute("SET NOCOUNT ON")
10441046
try:
10451047
if not self._config.only_custom_queries:
10461048
# restore the current database after executing dynamic queries
10471049
# this is to ensure the current database context is not changed
1048-
with self.connection.restore_current_database_context():
1050+
with self.connection.restore_current_database_context(KEY_PREFIX):
10491051
if self.database_metrics:
10501052
for database_metric in self.database_metrics:
10511053
database_metric.execute()
10521054

10531055
# reuse the connection for custom queries
10541056
self._query_manager.execute()
10551057
finally:
1056-
with self.connection.get_managed_cursor() as cursor:
1058+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
10571059
cursor.execute("SET NOCOUNT OFF")
10581060

10591061
def execute_query_raw(self, query, db=None):
1060-
with self.connection.get_managed_cursor() as cursor:
1062+
with self.connection.get_managed_cursor(KEY_PREFIX) as cursor:
10611063
if db:
10621064
ctx = construct_use_statement(db)
10631065
self.log.debug("changing cursor context via use statement: %s", ctx)

sqlserver/datadog_checks/sqlserver/statements.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,8 +531,8 @@ def collect_statement_metrics_and_plans(self):
531531

532532
# re-use the check's conn module, but set extra_key=dbm- to ensure we get our own
533533
# raw connection. adodbapi and pyodbc modules are thread safe, but connections are not.
534-
with self._check.connection.open_managed_default_connection(key_prefix=self._conn_key_prefix):
535-
with self._check.connection.get_managed_cursor(key_prefix=self._conn_key_prefix) as cursor:
534+
with self._check.connection.open_managed_default_connection(self._conn_key_prefix):
535+
with self._check.connection.get_managed_cursor(self._conn_key_prefix) as cursor:
536536
rows = self._collect_metrics_rows(cursor)
537537
if not rows:
538538
return

sqlserver/datadog_checks/sqlserver/stored_procedures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ def collect_procedure_metrics(self):
169169
"""
170170
# re-use the check's conn module, but set extra_key=dbm- to ensure we get our own
171171
# raw connection. adodbapi and pyodbc modules are thread safe, but connections are not.
172-
with self._check.connection.open_managed_default_connection(key_prefix=self._conn_key_prefix):
173-
with self._check.connection.get_managed_cursor(key_prefix=self._conn_key_prefix) as cursor:
172+
with self._check.connection.open_managed_default_connection(self._conn_key_prefix):
173+
with self._check.connection.get_managed_cursor(self._conn_key_prefix) as cursor:
174174
rows = self._collect_metrics_rows(cursor)
175175
if not rows:
176176
self.log.debug("collect_procedure_metrics: no rows returned")

0 commit comments

Comments
 (0)