Skip to content

Commit 8f6f15d

Browse files
authored
[Container App] Support session pool OnContainerExit lifecycle (#9015)
1 parent 5c8fec8 commit 8f6f15d

10 files changed

+32882
-22904
lines changed

src/containerapp/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ upcoming
66
++++++
77
* 'az containerapp update/up': Disallow changing `--revisions-mode` to Labels.
88
* 'az containerapp session code-interpreter': Fix `--path` in examples
9+
* 'az containerapp sessionpool create/update': Support `--lifecycle-type` and `--max-alive-period`
910
* 'az containerapp env premium-ingress': Deprecate `--min-replicas` and `--max-replicas` parameters, use workload profile scale instead.
1011

1112
1.2.0b3

src/containerapp/azext_containerapp/_params.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,11 +443,18 @@ def load_arguments(self, _):
443443
c.argument('mi_user_assigned', nargs='+', help='Space-separated user identities to be assigned.')
444444

445445
with self.argument_context('containerapp sessionpool', arg_group='Configuration') as c:
446-
c.argument('container_type', arg_type=get_enum_type(["CustomContainer", "PythonLTS", "NodeLTS"]), help="The pool type of the Session Pool, default='PythonLTS'")
447-
c.argument('cooldown_period', help="Period (in seconds), after which the session will be deleted, default=300")
446+
c.argument('cooldown_period', help="Period (in seconds), after which the session will be deleted, this is only applicable for lifecycle type 'Timed', default=300")
447+
c.argument('max_alive_period', help="Period (in seconds), before the session be deleted if its container was not exited earlier, this is only applicable for lifecycle type 'OnContainerExit', default=3600")
448448
c.argument('secrets', nargs='*', options_list=['--secrets', '-s'], help="A list of secret(s) for the session pool. Space-separated values in 'key=value' format. Empty string to clear existing values.")
449449
c.argument('network_status', arg_type=get_enum_type(["EgressEnabled", "EgressDisabled"]), help="Egress is enabled for the Sessions or not.")
450450

451+
with self.argument_context('containerapp sessionpool create', arg_group='Configuration') as c:
452+
c.argument('container_type', arg_type=get_enum_type(["CustomContainer", "PythonLTS", "NodeLTS"]), help="The pool type of the Session Pool, default='PythonLTS'")
453+
c.argument('lifecycle_type', arg_type=get_enum_type(["Timed", "OnContainerExit"]), help="The lifecycle type of the Session Pool", default='Timed')
454+
455+
with self.argument_context('containerapp sessionpool update', arg_group='Configuration') as c:
456+
c.argument('lifecycle_type', arg_type=get_enum_type(["Timed", "OnContainerExit"]), help="The lifecycle type of the Session Pool")
457+
451458
with self.argument_context('containerapp sessionpool', arg_group='Scale') as c:
452459
c.argument('max_concurrent_sessions', options_list=['--max-sessions'], help="Max count of sessions can be run at the same time.", type=int)
453460
c.argument('ready_session_instances', options_list=['--ready-sessions'], help="The number of sessions that will be ready in the session pool all the time.", type=int)

src/containerapp/azext_containerapp/containerapp_sessionpool_decorator.py

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ class ContainerType(Enum):
4646
NodeLTS = 3
4747

4848

49+
class LifecycleType(Enum):
50+
Timed = 0
51+
OnContainerExit = 1
52+
53+
4954
class SessionPoolPreviewDecorator(BaseResource):
5055
def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str):
5156
super().__init__(cmd, client, raw_parameters, models)
@@ -70,6 +75,18 @@ def get_argument_container_type(self):
7075
def set_argument_container_type(self, container_type):
7176
return self.set_param('container_type', container_type)
7277

78+
def get_argument_lifecycle_type(self):
79+
return self.get_param('lifecycle_type')
80+
81+
def set_argument_lifecycle_type(self, lifecycle_type):
82+
return self.set_param('lifecycle_type', lifecycle_type)
83+
84+
def get_argument_max_alive_period(self):
85+
return self.get_param('max_alive_period')
86+
87+
def set_argument_max_alive_period(self, max_alive_period):
88+
return self.set_param('max_alive_period', max_alive_period)
89+
7390
def get_argument_cooldown_period_in_seconds(self):
7491
return self.get_param('cooldown_period')
7592

@@ -160,6 +177,22 @@ def validate_arguments(self):
160177
else:
161178
if environment_name is not None:
162179
raise ValidationError(f"Do not pass environment name when using container type {container_type}")
180+
if self.get_argument_lifecycle_type().lower() != LifecycleType.Timed.name.lower():
181+
raise ValidationError(f"The container type {container_type} only supports lifecycle type '{LifecycleType.Timed.name}'.")
182+
if self.get_argument_max_alive_period() is not None:
183+
raise ValidationError(f"The container type {container_type} does not support --max-alive-period.")
184+
185+
if self.get_argument_max_alive_period() is not None and \
186+
self.get_argument_cooldown_period_in_seconds() is not None:
187+
raise ValidationError("--max-alive-period and --cooldown-period cannot be set at the same time.")
188+
189+
if self.get_argument_max_alive_period() is not None and \
190+
self.get_argument_lifecycle_type().lower() != LifecycleType.OnContainerExit.name.lower():
191+
raise ValidationError(f"--max-alive-period can only be set when --lifecycle-type is '{LifecycleType.OnContainerExit.name}'.")
192+
193+
if self.get_argument_cooldown_period_in_seconds() is not None and \
194+
self.get_argument_lifecycle_type().lower() != LifecycleType.Timed.name.lower():
195+
raise ValidationError(f"--cooldown-period can only be set when --lifecycle-type is '{LifecycleType.Timed.name}'.")
163196

164197
def construct_payload(self):
165198
self.session_pool_def["location"] = self.get_argument_location()
@@ -253,12 +286,18 @@ def set_up_managed_identity(self):
253286
self.session_pool_def["identity"] = identity_def
254287

255288
def set_up_dynamic_configuration(self):
256-
if self.get_argument_cooldown_period_in_seconds() is None:
289+
if self.get_argument_lifecycle_type().lower() == LifecycleType.Timed.name.lower() and \
290+
self.get_argument_cooldown_period_in_seconds() is None:
257291
self.set_argument_cooldown_period_in_seconds(300)
258292

293+
if self.get_argument_lifecycle_type().lower() == LifecycleType.OnContainerExit.name.lower() and \
294+
self.get_argument_max_alive_period() is None:
295+
self.set_argument_max_alive_period(3600)
296+
259297
dynamic_pool_def = {}
260298
lifecycle_config_def = {}
261-
lifecycle_config_def["lifecycleType"] = "Timed"
299+
lifecycle_config_def["lifecycleType"] = self.get_argument_lifecycle_type()
300+
lifecycle_config_def["maxAlivePeriodInSeconds"] = self.get_argument_max_alive_period()
262301
lifecycle_config_def["cooldownPeriodInSeconds"] = self.get_argument_cooldown_period_in_seconds()
263302
dynamic_pool_def["lifecycleConfiguration"] = lifecycle_config_def
264303

@@ -401,6 +440,41 @@ def create(self):
401440

402441

403442
class SessionPoolUpdateDecorator(SessionPoolPreviewDecorator):
443+
def validate_arguments(self):
444+
self.existing_pool_def = self.client.show(cmd=self.cmd,
445+
resource_group_name=self.get_argument_resource_group_name(),
446+
name=self.get_argument_name())
447+
448+
if ((self.get_argument_container_type() is not None and safe_get(self.existing_pool_def, "properties", "containerType").lower() == self.get_argument_container_type().lower()) or
449+
(self.get_argument_managed_env() is not None and safe_get(self.existing_pool_def, "properties", "environmentId").lower() == self.get_argument_managed_env().lower())):
450+
raise ValidationError("containerType and environmentId cannot be updated.")
451+
452+
# Validate unsupported arguments with a certain lifecycle type
453+
if self.get_argument_max_alive_period() is not None and \
454+
self.get_argument_lifecycle_type() is not None and \
455+
self.get_argument_lifecycle_type().lower() != LifecycleType.OnContainerExit.name.lower():
456+
raise ValidationError(f"--max-alive-period can only be set when --lifecycle-type is '{LifecycleType.OnContainerExit.name}'.")
457+
if self.get_argument_cooldown_period_in_seconds() is not None and \
458+
self.get_argument_lifecycle_type() is not None and \
459+
self.get_argument_lifecycle_type().lower() != LifecycleType.Timed.name.lower():
460+
raise ValidationError(f"--cooldown-period can only be set when --lifecycle-type is '{LifecycleType.Timed.name}'.")
461+
462+
# Validate that max_alive_period and cooldown_period are not set at the same time
463+
if self.get_argument_max_alive_period() is not None and \
464+
self.get_argument_cooldown_period_in_seconds() is not None:
465+
raise ValidationError("--max-alive-period and --cooldown-period cannot be set at the same time.")
466+
467+
# Validate unsupported arguments with existing lifecycle type
468+
current_lifecycle_type = safe_get(self.existing_pool_def, "properties", "dynamicPoolConfiguration", "lifecycleConfiguration", "lifecycleType")
469+
if self.get_argument_max_alive_period() is not None and \
470+
self.get_argument_lifecycle_type() is None and \
471+
current_lifecycle_type.lower() != LifecycleType.OnContainerExit.name.lower():
472+
raise ValidationError(f"--max-alive-period is not supported for the current --lifecycle-type '{current_lifecycle_type}'.")
473+
if self.get_argument_cooldown_period_in_seconds() is not None and \
474+
self.get_argument_lifecycle_type() is None and \
475+
current_lifecycle_type.lower() != LifecycleType.Timed.name.lower():
476+
raise ValidationError(f"--cooldown-period is not supported for the current --lifecycle-type '{current_lifecycle_type}'.")
477+
404478
def update(self):
405479
try:
406480
return self.client.update(
@@ -412,13 +486,6 @@ def update(self):
412486

413487
def construct_payload(self):
414488
self.session_pool_def = {}
415-
self.existing_pool_def = self.client.show(cmd=self.cmd,
416-
resource_group_name=self.get_argument_resource_group_name(),
417-
name=self.get_argument_name())
418-
419-
if ((self.get_argument_container_type() is not None and safe_get(self.existing_pool_def, "properties", "containerType").lower() == self.get_argument_container_type().lower()) or
420-
(self.get_argument_managed_env() is not None and safe_get(self.existing_pool_def, "properties", "environmentId").lower() == self.get_argument_managed_env().lower())):
421-
raise ValidationError("containerType and environmentId cannot be updated.")
422489

423490
self.set_up_managed_identity()
424491
self.set_up_dynamic_configuration()
@@ -483,11 +550,19 @@ def set_up_managed_identity_settings(self):
483550
safe_set(self.session_pool_def, "properties", "managedIdentitySettings", value=managed_identity_settings)
484551

485552
def set_up_dynamic_configuration(self):
553+
lifecycle_config_def = {}
554+
555+
if self.get_argument_lifecycle_type() is not None:
556+
lifecycle_config_def["lifecycleType"] = self.get_argument_lifecycle_type()
557+
486558
if self.get_argument_cooldown_period_in_seconds() is not None:
487-
dynamic_pool_def = {}
488-
lifecycle_config_def = {}
489-
lifecycle_config_def["lifecycleType"] = "Timed"
490559
lifecycle_config_def["cooldownPeriodInSeconds"] = self.get_argument_cooldown_period_in_seconds()
560+
561+
if self.get_argument_max_alive_period() is not None:
562+
lifecycle_config_def["maxAlivePeriodInSeconds"] = self.get_argument_max_alive_period()
563+
564+
if lifecycle_config_def:
565+
dynamic_pool_def = {}
491566
dynamic_pool_def["lifecycleConfiguration"] = lifecycle_config_def
492567
safe_set(self.session_pool_def, "properties", "dynamicPoolConfiguration", value=dynamic_pool_def)
493568

src/containerapp/azext_containerapp/custom.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3096,7 +3096,9 @@ def create_session_pool(cmd,
30963096
location=None,
30973097
managed_env=None,
30983098
container_type=None,
3099+
lifecycle_type=None,
30993100
cooldown_period=None,
3101+
max_alive_period=None,
31003102
secrets=None,
31013103
network_status=None,
31023104
max_concurrent_sessions=None,
@@ -3135,7 +3137,9 @@ def update_session_pool(cmd,
31353137
name,
31363138
resource_group_name,
31373139
location=None,
3140+
lifecycle_type=None,
31383141
cooldown_period=None,
3142+
max_alive_period=None,
31393143
secrets=None,
31403144
network_status=None,
31413145
max_concurrent_sessions=None,
@@ -3161,6 +3165,7 @@ def update_session_pool(cmd,
31613165
raw_parameters=raw_parameters,
31623166
models=CONTAINER_APPS_SDK_MODELS
31633167
)
3168+
session_pool_decorator.validate_arguments()
31643169
session_pool_decorator.construct_payload()
31653170
r = session_pool_decorator.update()
31663171

0 commit comments

Comments
 (0)