Skip to content

Commit 3d3b655

Browse files
feat: [SNOW-2155083] add auto_suspend_secs parameter support
Added entry for new auto_suspend_secs parameter functionality in SPCS service commands (deploy, set, unset) that allows configuring automatic service suspension after inactivity period.
1 parent 2a20bcc commit 3d3b655

File tree

8 files changed

+123
-21
lines changed

8 files changed

+123
-21
lines changed

RELEASE-NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
## Deprecations
2020

2121
## New additions
22+
* Added support for `auto_suspend_secs` parameter in SPCS service commands (`deploy`, `set`, `unset`) to configure automatic service suspension after inactivity period.
2223

2324
## Fixes and improvements
2425
* Fixed DBT deploy command to properly handle fully qualified names

src/snowflake/cli/_plugins/spcs/services/commands.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ def _service_name_callback(name: FQN) -> FQN:
151151
help=_AUTO_RESUME_HELP,
152152
)
153153

154+
_AUTO_SUSPEND_SECS_HELP = "Number of seconds of inactivity after which the service will be automatically suspended."
155+
AutoSuspendSecsOption = OverrideableOption(
156+
None,
157+
"--auto-suspend-secs",
158+
help=_AUTO_SUSPEND_SECS_HELP,
159+
min=0,
160+
)
161+
154162
_COMMENT_HELP = "Comment for the service."
155163

156164
add_object_command_aliases(
@@ -217,7 +225,7 @@ def deploy(
217225
upgrade: bool = typer.Option(
218226
False,
219227
"--upgrade",
220-
help="Updates the existing service. Can update min_instances, max_instances, query_warehouse, auto_resume, external_access_integrations and comment.",
228+
help="Updates the existing service. Can update min_instances, max_instances, query_warehouse, auto_resume, auto_suspend_secs, external_access_integrations and comment.",
221229
),
222230
**options,
223231
) -> CommandResult:
@@ -241,6 +249,7 @@ def deploy(
241249
min_instances=service.min_instances,
242250
max_instances=max_instances,
243251
auto_resume=service.auto_resume,
252+
auto_suspend_secs=service.auto_suspend_secs,
244253
external_access_integrations=service.external_access_integrations,
245254
query_warehouse=service.query_warehouse,
246255
tags=service.tags,
@@ -529,6 +538,7 @@ def set_property(
529538
max_instances: Optional[int] = MaxInstancesOption(show_default=False),
530539
query_warehouse: Optional[str] = QueryWarehouseOption(show_default=False),
531540
auto_resume: Optional[bool] = AutoResumeOption(default=None, show_default=False),
541+
auto_suspend_secs: Optional[int] = AutoSuspendSecsOption(show_default=False),
532542
external_access_integrations: Optional[List[str]] = typer.Option(
533543
None,
534544
"--eai-name",
@@ -546,6 +556,7 @@ def set_property(
546556
max_instances=max_instances,
547557
query_warehouse=query_warehouse,
548558
auto_resume=auto_resume,
559+
auto_suspend_secs=auto_suspend_secs,
549560
external_access_integrations=external_access_integrations,
550561
comment=comment,
551562
)
@@ -576,6 +587,12 @@ def unset_property(
576587
help=f"Reset the AUTO_RESUME property - {_AUTO_RESUME_HELP}",
577588
show_default=False,
578589
),
590+
auto_suspend_secs: bool = AutoSuspendSecsOption(
591+
default=False,
592+
param_decls=["--auto-suspend-secs"],
593+
help=f"Reset the AUTO_SUSPEND_SECS property - {_AUTO_SUSPEND_SECS_HELP}",
594+
show_default=False,
595+
),
579596
comment: bool = CommentOption(
580597
default=False,
581598
help=f"Reset the COMMENT property - {_COMMENT_HELP}",
@@ -593,6 +610,7 @@ def unset_property(
593610
max_instances=max_instances,
594611
query_warehouse=query_warehouse,
595612
auto_resume=auto_resume,
613+
auto_suspend_secs=auto_suspend_secs,
596614
comment=comment,
597615
)
598616
return SingleQueryResult(cursor)

src/snowflake/cli/_plugins/spcs/services/manager.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def deploy(
114114
min_instances: int,
115115
max_instances: int,
116116
auto_resume: bool,
117+
auto_suspend_secs: Optional[int],
117118
external_access_integrations: Optional[List[str]],
118119
query_warehouse: Optional[str],
119120
tags: Optional[List[Tag]],
@@ -139,6 +140,7 @@ def deploy(
139140
max_instances=max_instances,
140141
query_warehouse=query_warehouse,
141142
auto_resume=auto_resume,
143+
auto_suspend_secs=auto_suspend_secs,
142144
external_access_integrations=external_access_integrations,
143145
comment=comment,
144146
)
@@ -163,6 +165,9 @@ def deploy(
163165
if max_instances:
164166
query.append(f"MAX_INSTANCES = {max_instances}")
165167

168+
if auto_suspend_secs is not None:
169+
query.append(f"AUTO_SUSPEND_SECS = {auto_suspend_secs}")
170+
166171
if query_warehouse:
167172
query.append(f"QUERY_WAREHOUSE = {query_warehouse}")
168173

@@ -531,6 +536,7 @@ def set_property(
531536
max_instances: Optional[int],
532537
query_warehouse: Optional[str],
533538
auto_resume: Optional[bool],
539+
auto_suspend_secs: Optional[int],
534540
external_access_integrations: Optional[List[str]],
535541
comment: Optional[str],
536542
):
@@ -539,6 +545,7 @@ def set_property(
539545
("max_instances", max_instances),
540546
("query_warehouse", query_warehouse),
541547
("auto_resume", auto_resume),
548+
("auto_suspend_secs", auto_suspend_secs),
542549
("external_access_integrations", external_access_integrations),
543550
("comment", comment),
544551
]
@@ -562,6 +569,9 @@ def set_property(
562569
if auto_resume is not None:
563570
query.append(f" auto_resume = {auto_resume}")
564571

572+
if auto_suspend_secs is not None:
573+
query.append(f" auto_suspend_secs = {auto_suspend_secs}")
574+
565575
if external_access_integrations is not None:
566576
external_access_integration_list = ",".join(
567577
f"{e}" for e in external_access_integrations
@@ -582,13 +592,15 @@ def unset_property(
582592
max_instances: bool,
583593
query_warehouse: bool,
584594
auto_resume: bool,
595+
auto_suspend_secs: bool,
585596
comment: bool,
586597
):
587598
property_pairs = [
588599
("min_instances", min_instances),
589600
("max_instances", max_instances),
590601
("query_warehouse", query_warehouse),
591602
("auto_resume", auto_resume),
603+
("auto_suspend_secs", auto_suspend_secs),
592604
("comment", comment),
593605
]
594606

src/snowflake/cli/_plugins/spcs/services/service_entity_model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ class ServiceEntityModel(EntityModelBaseWithArtifacts, ExternalAccessBaseModel):
3030
title="The service will automatically resume when a service function or ingress is called.",
3131
default=True,
3232
)
33+
auto_suspend_secs: Optional[int] = Field(
34+
title="Number of seconds of inactivity after which the service is automatically suspended.",
35+
default=None,
36+
ge=0,
37+
)
3338
query_warehouse: Optional[str] = Field(
3439
title="Warehouse to use if a service container connects to Snowflake to execute a query without explicitly specifying a warehouse to use",
3540
default=None,

tests/__snapshots__/test_help_messages.ambr

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15604,8 +15604,8 @@
1560415604
+- Options --------------------------------------------------------------------+
1560515605
| --upgrade Updates the existing service. Can update |
1560615606
| min_instances, max_instances, query_warehouse, |
15607-
| auto_resume, external_access_integrations and |
15608-
| comment. |
15607+
| auto_resume, auto_suspend_secs, |
15608+
| external_access_integrations and comment. |
1560915609
| --project -p TEXT Path where the Snowflake project is stored. |
1561015610
| Defaults to the current working directory. |
1561115611
| --env TEXT String in the format key=value. Overrides variables |
@@ -17227,6 +17227,13 @@
1722717227
| service function |
1722817228
| or ingress is |
1722917229
| called. |
17230+
| --auto-suspend-s… INTEGER RANGE Number of |
17231+
| [x>=0] seconds of |
17232+
| inactivity after |
17233+
| which the |
17234+
| service will be |
17235+
| automatically |
17236+
| suspended. |
1723017237
| --eai-name TEXT Identifies |
1723117238
| external access |
1723217239
| integrations |
@@ -17634,20 +17641,24 @@
1763417641
| [required] |
1763517642
+------------------------------------------------------------------------------+
1763617643
+- Options --------------------------------------------------------------------+
17637-
| --min-instances Reset the MIN_INSTANCES property - Minimum |
17638-
| number of service instances to run. |
17639-
| --max-instances Reset the MAX_INSTANCES property - Maximum |
17640-
| number of service instances to run. |
17641-
| --query-warehouse Reset the QUERY_WAREHOUSE property - Warehouse |
17642-
| to use if a service container connects to |
17643-
| Snowflake to execute a query without explicitly |
17644-
| specifying a warehouse to use. |
17645-
| --auto-resume Reset the AUTO_RESUME property - The service |
17646-
| will automatically resume when a service |
17647-
| function or ingress is called. |
17648-
| --comment Reset the COMMENT property - Comment for the |
17649-
| service. |
17650-
| --help -h Show this message and exit. |
17644+
| --min-instances Reset the MIN_INSTANCES property - Minimum |
17645+
| number of service instances to run. |
17646+
| --max-instances Reset the MAX_INSTANCES property - Maximum |
17647+
| number of service instances to run. |
17648+
| --query-warehouse Reset the QUERY_WAREHOUSE property - |
17649+
| Warehouse to use if a service container |
17650+
| connects to Snowflake to execute a query |
17651+
| without explicitly specifying a warehouse to |
17652+
| use. |
17653+
| --auto-resume Reset the AUTO_RESUME property - The service |
17654+
| will automatically resume when a service |
17655+
| function or ingress is called. |
17656+
| --auto-suspend-secs Reset the AUTO_SUSPEND_SECS property - Number |
17657+
| of seconds of inactivity after which the |
17658+
| service will be automatically suspended. |
17659+
| --comment Reset the COMMENT property - Comment for the |
17660+
| service. |
17661+
| --help -h Show this message and exit. |
1765117662
+------------------------------------------------------------------------------+
1765217663
+- Connection configuration ---------------------------------------------------+
1765317664
| --connection,--environment -c TEXT Name of the connection, as |

tests/spcs/test_services.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,7 @@ def test_set_property(mock_execute_query):
15031503
max_instances = 3
15041504
query_warehouse = "test_warehouse"
15051505
auto_resume = False
1506+
auto_suspend_secs = 600
15061507
external_access_integrations = [
15071508
"google_apis_access_integration",
15081509
"salesforce_api_access_integration",
@@ -1516,6 +1517,7 @@ def test_set_property(mock_execute_query):
15161517
max_instances=max_instances,
15171518
query_warehouse=query_warehouse,
15181519
auto_resume=auto_resume,
1520+
auto_suspend_secs=auto_suspend_secs,
15191521
external_access_integrations=external_access_integrations,
15201522
comment=comment,
15211523
)
@@ -1527,6 +1529,7 @@ def test_set_property(mock_execute_query):
15271529
f"max_instances = {max_instances}",
15281530
f"query_warehouse = {query_warehouse}",
15291531
f"auto_resume = {auto_resume}",
1532+
f"auto_suspend_secs = {auto_suspend_secs}",
15301533
f"external_access_integrations = ({eai_list})",
15311534
f"comment = {comment}",
15321535
]
@@ -1538,7 +1541,9 @@ def test_set_property(mock_execute_query):
15381541
def test_set_property_no_properties():
15391542
service_name = "test_service"
15401543
with pytest.raises(NoPropertiesProvidedError) as e:
1541-
ServiceManager().set_property(service_name, None, None, None, None, None, None)
1544+
ServiceManager().set_property(
1545+
service_name, None, None, None, None, None, None, None
1546+
)
15421547
assert (
15431548
e.value.message
15441549
== f"No properties specified for service '{service_name}'. Please provide at least one property to set."
@@ -1554,6 +1559,7 @@ def test_set_property_cli(mock_set, mock_statement_success, runner):
15541559
max_instances = 3
15551560
query_warehouse = "test_warehouse"
15561561
auto_resume = False
1562+
auto_suspend_secs = 600
15571563
external_access_integrations = [
15581564
"google_apis_access_integration",
15591565
"salesforce_api_access_integration",
@@ -1572,6 +1578,8 @@ def test_set_property_cli(mock_set, mock_statement_success, runner):
15721578
"--query-warehouse",
15731579
query_warehouse,
15741580
"--no-auto-resume",
1581+
"--auto-suspend-secs",
1582+
str(auto_suspend_secs),
15751583
"--eai-name",
15761584
"google_apis_access_integration",
15771585
"--eai-name",
@@ -1586,6 +1594,7 @@ def test_set_property_cli(mock_set, mock_statement_success, runner):
15861594
max_instances=max_instances,
15871595
query_warehouse=query_warehouse,
15881596
auto_resume=auto_resume,
1597+
auto_suspend_secs=auto_suspend_secs,
15891598
external_access_integrations=external_access_integrations,
15901599
comment=to_string_literal(comment),
15911600
)
@@ -1608,6 +1617,7 @@ def test_set_property_no_properties_cli(mock_set, runner):
16081617
max_instances=None,
16091618
query_warehouse=None,
16101619
auto_resume=None,
1620+
auto_suspend_secs=None,
16111621
external_access_integrations=None,
16121622
comment=None,
16131623
)
@@ -1618,16 +1628,20 @@ def test_unset_property(mock_execute_query):
16181628
service_name = "test_service"
16191629
cursor = Mock(spec=SnowflakeCursor)
16201630
mock_execute_query.return_value = cursor
1621-
result = ServiceManager().unset_property(service_name, True, True, True, True, True)
1622-
expected_query = "alter service test_service unset min_instances,max_instances,query_warehouse,auto_resume,comment"
1631+
result = ServiceManager().unset_property(
1632+
service_name, True, True, True, True, True, True
1633+
)
1634+
expected_query = "alter service test_service unset min_instances,max_instances,query_warehouse,auto_resume,auto_suspend_secs,comment"
16231635
mock_execute_query.assert_called_once_with(expected_query)
16241636
assert result == cursor
16251637

16261638

16271639
def test_unset_property_no_properties():
16281640
service_name = "test_service"
16291641
with pytest.raises(NoPropertiesProvidedError) as e:
1630-
ServiceManager().unset_property(service_name, False, False, False, False, False)
1642+
ServiceManager().unset_property(
1643+
service_name, False, False, False, False, False, False
1644+
)
16311645
assert (
16321646
e.value.message
16331647
== f"No properties specified for service '{service_name}'. Please provide at least one property to reset to its default value."
@@ -1649,6 +1663,7 @@ def test_unset_property_cli(mock_unset, mock_statement_success, runner):
16491663
"--max-instances",
16501664
"--query-warehouse",
16511665
"--auto-resume",
1666+
"--auto-suspend-secs",
16521667
"--comment",
16531668
]
16541669
)
@@ -1658,6 +1673,7 @@ def test_unset_property_cli(mock_unset, mock_statement_success, runner):
16581673
max_instances=True,
16591674
query_warehouse=True,
16601675
auto_resume=True,
1676+
auto_suspend_secs=True,
16611677
comment=True,
16621678
)
16631679
assert result.exit_code == 0, result.output
@@ -1679,10 +1695,47 @@ def test_unset_property_no_properties_cli(mock_unset, runner):
16791695
max_instances=False,
16801696
query_warehouse=False,
16811697
auto_resume=False,
1698+
auto_suspend_secs=False,
16821699
comment=False,
16831700
)
16841701

16851702

1703+
@patch(EXECUTE_QUERY)
1704+
def test_set_property_auto_suspend_secs_only(mock_execute_query):
1705+
service_name = "test_service"
1706+
auto_suspend_secs = 300
1707+
cursor = Mock(spec=SnowflakeCursor)
1708+
mock_execute_query.return_value = cursor
1709+
result = ServiceManager().set_property(
1710+
service_name=service_name,
1711+
min_instances=None,
1712+
max_instances=None,
1713+
query_warehouse=None,
1714+
auto_resume=None,
1715+
auto_suspend_secs=auto_suspend_secs,
1716+
external_access_integrations=None,
1717+
comment=None,
1718+
)
1719+
expected_query = (
1720+
f"alter service {service_name} set\nauto_suspend_secs = {auto_suspend_secs}"
1721+
)
1722+
mock_execute_query.assert_called_once_with(expected_query)
1723+
assert result == cursor
1724+
1725+
1726+
@patch(EXECUTE_QUERY)
1727+
def test_unset_property_auto_suspend_secs_only(mock_execute_query):
1728+
service_name = "test_service"
1729+
cursor = Mock(spec=SnowflakeCursor)
1730+
mock_execute_query.return_value = cursor
1731+
result = ServiceManager().unset_property(
1732+
service_name, False, False, False, False, True, False
1733+
)
1734+
expected_query = f"alter service {service_name} unset auto_suspend_secs"
1735+
mock_execute_query.assert_called_once_with(expected_query)
1736+
assert result == cursor
1737+
1738+
16861739
def test_unset_property_with_args(runner):
16871740
service_name = "test_service"
16881741
result = runner.invoke(

tests_integration/test_data/projects/spcs_service/snowflake.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ entities:
1212
max_instances: 1
1313
query_warehouse: xsmall
1414
comment: "This is a test service"
15+
auto_suspend_secs: 1000
1516
artifacts:
1617
- spec.yml

tests_integration/tests_using_container_services/spcs/test_services.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def test_service_create_from_project_definition(
105105
"spec_file": "spec_upgrade.yml",
106106
"min_instances": 1,
107107
"max_instances": 2,
108+
"auto_suspend_secs": 1000,
108109
"query_warehouse": "xsmall",
109110
"comment": "Upgraded service",
110111
"artifacts": ["spec_upgrade.yml"],

0 commit comments

Comments
 (0)