Skip to content

Commit 45f05f5

Browse files
authored
Sql server agent check (#33719)
Adds a SQL Server check to ensure the SQL Server Agent is running. Without this check, CREATE SOURCE will succeed, but the hydration never finishes because the CDC jobs, while they exist, are not being run. ### Motivation fixes MaterializeInc/database-issues#9731 ### Checklist - [ ] This PR has adequate test coverage / QA involvement has been duly considered. ([trigger-ci for additional test/nightly runs](https://trigger-ci.dev.materialize.com/)) - [ ] This PR has an associated up-to-date [design doc](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/README.md), is a design doc ([template](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/00000000_template.md)), or is sufficiently small to not require a design. <!-- Reference the design in the description. --> - [ ] If this PR evolves [an existing `$T ⇔ Proto$T` mapping](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/command-and-response-binary-encoding.md) (possibly in a backwards-incompatible way), then it is tagged with a `T-proto` label. - [ ] If this PR will require changes to cloud orchestration or tests, there is a companion cloud PR to account for those changes that is tagged with the release-blocker label ([example](MaterializeInc/cloud#5021)). <!-- Ask in #team-cloud on Slack if you need help preparing the cloud PR. --> - [ ] If this PR includes major [user-facing behavior changes](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/guide-changes.md#what-changes-require-a-release-note), I have pinged the relevant PM to schedule a changelog post.
1 parent b88696d commit 45f05f5

File tree

6 files changed

+83
-2
lines changed

6 files changed

+83
-2
lines changed

doc/user/layouts/shortcodes/sql-server-direct/before-you-begin.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,14 @@
55

66
- Ensure you have access to your SQL Server instance via the [`sqlcmd` client](https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility),
77
or your preferred SQL client.
8+
9+
- Ensure SQL Server Agent is running.
10+
```mzsql
11+
USE msdb;
12+
SELECT
13+
servicename,
14+
status_desc,
15+
startup_type_desc
16+
FROM sys.dm_server_services
17+
WHERE servicename LIKE 'SQL Server Agent%';
18+
```

misc/python/materialize/mzcompose/services/sql_server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(
2525
name: str = "sql-server",
2626
environment_extra: list[str] = [],
2727
volumes_extra: list[str] = [],
28+
enable_agent: bool = True,
2829
) -> None:
2930
super().__init__(
3031
name=name,
@@ -35,7 +36,7 @@ def __init__(
3536
"environment": [
3637
"ACCEPT_EULA=Y",
3738
"MSSQL_PID=Developer",
38-
"MSSQL_AGENT_ENABLED=True",
39+
f"MSSQL_AGENT_ENABLED={enable_agent}",
3940
f"SA_PASSWORD={sa_password}",
4041
*environment_extra,
4142
],

src/sql-server-util/src/inspect.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,17 @@ pub async fn ensure_snapshot_isolation_enabled(client: &mut Client) -> Result<()
642642
Ok(())
643643
}
644644

645+
/// Ensure the SQL Server Agent is running.
646+
///
647+
/// See: <https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-server-services-transact-sql?view=azuresqldb-current&viewFallbackFrom=sql-server-ver17>
648+
pub async fn ensure_sql_server_agent_running(client: &mut Client) -> Result<(), SqlServerError> {
649+
static AGENT_STATUS_QUERY: &str = "SELECT status_desc FROM sys.dm_server_services WHERE servicename LIKE 'SQL Server Agent%';";
650+
let result = client.simple_query(AGENT_STATUS_QUERY).await?;
651+
652+
check_system_result(&result, "SQL Server Agent status".to_string(), "Running")?;
653+
Ok(())
654+
}
655+
645656
pub async fn get_tables(client: &mut Client) -> Result<Vec<SqlServerTableRaw>, SqlServerError> {
646657
let result = client
647658
.simple_query(&format!("{GET_COLUMNS_FOR_TABLES_WITH_CDC_QUERY};"))

src/storage-types/src/connections.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2113,6 +2113,7 @@ impl SqlServerConnectionDetails<InlinedConnection> {
21132113
for error in [
21142114
mz_sql_server_util::inspect::ensure_database_cdc_enabled(&mut client).await,
21152115
mz_sql_server_util::inspect::ensure_snapshot_isolation_enabled(&mut client).await,
2116+
mz_sql_server_util::inspect::ensure_sql_server_agent_running(&mut client).await,
21162117
] {
21172118
match error {
21182119
Err(mz_sql_server_util::SqlServerError::InvalidSystemSetting {

test/sql-server-cdc/21-privileges.td

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ DENY SELECT ON OBJECT::cdc.dbo_t1_privileges_ct(__$start_lsn) TO authorization_u
3434
CREATE LOGIN authorization_user_table_perms WITH PASSWORD = '${arg.default-sql-server-password}';
3535
CREATE USER authorization_user_table_perms FOR LOGIN authorization_user_table_perms;
3636
ALTER ROLE db_datareader ADD MEMBER authorization_user_table_perms;
37-
3837
DENY SELECT ON OBJECT::dbo.t1_privileges TO authorization_user_table_perms;
3938

39+
USE master;
40+
GRANT VIEW SERVER STATE TO authorization_user_column_perms;
41+
GRANT VIEW SERVER STATE TO authorization_user_column_perms_capture_instance;
42+
GRANT VIEW SERVER STATE TO authorization_user_table_perms;
43+
4044
> CREATE SECRET IF NOT EXISTS sql_server_pass AS '${arg.default-sql-server-password}'
4145

4246
> CREATE CONNECTION sql_server_privileges_connection_table TO SQL SERVER (

test/sql-server-cdc/mzcompose.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
),
4949
]
5050

51+
VOLUMES = {"ms_scratch": {}}
52+
5153

5254
#
5355
# Test that SQL Server ingestion works
@@ -124,6 +126,57 @@ def run(file: pathlib.Path | str) -> None:
124126
c.test_parts(sharded_files, run)
125127

126128

129+
def workflow_no_agent(c: Composition, parser: WorkflowArgumentParser) -> None:
130+
"""
131+
Ensures that MZ detects that the SQL Server Agent is not running at purification and produces
132+
an error to the user.
133+
"""
134+
# Start with a fresh state
135+
c.kill("sql-server")
136+
c.rm("sql-server")
137+
c.kill("materialized")
138+
c.rm("materialized")
139+
140+
try:
141+
with c.override(
142+
SqlServer(volumes_extra=["ms_scratch:/var/opt/mssql"]),
143+
):
144+
c.up("materialized", "sql-server", Service("testdrive", idle=True))
145+
c.run_testdrive_files(
146+
"setup/setup.td",
147+
f"--var=default-sql-server-user={SqlServer.DEFAULT_USER}",
148+
f"--var=default-sql-server-password={SqlServer.DEFAULT_SA_PASSWORD}",
149+
)
150+
c.kill("sql-server")
151+
152+
with c.override(
153+
SqlServer(
154+
enable_agent=False, volumes_extra=["ms_scratch:/var/opt/mssql"]
155+
)
156+
):
157+
c.up("sql-server")
158+
159+
c.testdrive(
160+
dedent(
161+
f"""
162+
> CREATE SECRET IF NOT EXISTS sql_server_pass AS '{SqlServer.DEFAULT_SA_PASSWORD}'
163+
164+
! CREATE CONNECTION sql_server_conn TO SQL SERVER (
165+
HOST 'sql-server',
166+
PORT 1433,
167+
DATABASE test,
168+
USER '{SqlServer.DEFAULT_USER}',
169+
PASSWORD = SECRET sql_server_pass);
170+
contains:Invalid SQL Server system replication settings
171+
"""
172+
)
173+
)
174+
finally:
175+
c.kill("sql-server")
176+
c.rm("sql-server")
177+
c.rm_volumes("ms_scratch")
178+
179+
127180
def workflow_snapshot_consistency(
128181
c: Composition, parser: WorkflowArgumentParser
129182
) -> None:

0 commit comments

Comments
 (0)