Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Container app] az containerapp sessionpool: Support sessionpool command. #7559

Merged
merged 20 commits into from
May 14, 2024
Merged
1 change: 1 addition & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ upcoming
* 'az containerapp update': Fix --scale-rule-tcp-concurrency for TCP scale rule
* 'az containerapp compose create': Fix an issue where the environment's location is not resolved from --location
* 'az containerapp up': Fix an issue about creating resource group automatically
* 'az containerapp sessionpool': Support create/update/show/delete/list session pools

0.3.50
++++++
Expand Down
140 changes: 140 additions & 0 deletions src/containerapp/azext_containerapp/_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,3 +990,143 @@ def list(cls, cmd, resource_group_name, environment_name):
java_component_list.append(component)

return java_component_list


class SessionPoolPreviewClient():
api_version = PREVIEW_API_VERSION

@classmethod
def create(cls, cmd, resource_group_name, name, session_pool_envelope, no_wait=False):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/sessionPools/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(session_pool_envelope))

if no_wait:
return r.json()
elif r.status_code == 201:
operation_url = r.headers.get(HEADER_AZURE_ASYNC_OPERATION)
poll_status(cmd, operation_url)
r = send_raw_request(cmd.cli_ctx, "GET", request_url)

return r.json()

@classmethod
def update(cls, cmd, resource_group_name, name, session_pool_envelope, no_wait=False):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/sessionPools/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "PATCH", request_url, body=json.dumps(session_pool_envelope))

if no_wait:
return
elif r.status_code == 202:
operation_url = r.headers.get(HEADER_LOCATION)
response = poll_results(cmd, operation_url)
if response is None:
raise ResourceNotFoundError("Could not find the Session Pool")
else:
return response

return r.json()

@classmethod
def delete(cls, cmd, resource_group_name, name, no_wait=False):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/sessionPools/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "DELETE", request_url)

if no_wait:
return # API doesn't return JSON (it returns no content)
elif r.status_code in [200, 201, 202, 204]:
if r.status_code == 202:
operation_url = r.headers.get(HEADER_LOCATION)
poll_results(cmd, operation_url)
logger.warning('Session pool successfully deleted')

@classmethod
def show(cls, cmd, resource_group_name, name):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/sessionPools/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "GET", request_url)

return r.json()

@classmethod
def list_by_resource_group(cls, cmd, resource_group_name):
session_pool_list = []

management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/sessionPools?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "GET", request_url)
r = r.json()

for session_pool in r["value"]:
session_pool_list.append(session_pool)
yalixiang marked this conversation as resolved.
Show resolved Hide resolved

return session_pool_list

@classmethod
def list_by_subscription(cls, cmd, formatter=lambda x: x):
sessionpools = []

management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
request_url = "{}/subscriptions/{}/providers/Microsoft.App/sessionPools?api-version={}".format(
management_hostname.strip('/'),
sub_id,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "GET", request_url)
j = r.json()
for env in j["value"]:
formatted = formatter(env)
sessionpools.append(formatted)

while j.get("nextLink") is not None:
request_url = j["nextLink"]
r = send_raw_request(cmd.cli_ctx, "GET", request_url)
j = r.json()
for env in j["value"]:
formatted = formatter(env)
sessionpools.append(formatted)

return sessionpools

83 changes: 83 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,3 +1601,86 @@
az containerapp env telemetry otlp list -n MyContainerappEnvironment -g MyResourceGroup
"""

# SessionPool Commands
helps['containerapp sessionpool'] = """
type: group
short-summary: Commands to manage session pools.
"""

helps['containerapp sessionpool create'] = """
type: command
short-summary: Create or update a Session pool.
examples:
- name: Create or update a Session Pool with container type PythonLTS default settings.
text: |
az containerapp sessionpool create -n mysessionpool -g MyResourceGroup \\
--location eastasia
- name: Create or update a Session Pool with container type PythonLTS, with max concurrent sessions is 30, ready session instances 20.
text: |
az containerapp sessionpool create -n mysessionpool -g MyResourceGroup \\
--container-type PythonLTS --max-sessions 30 --ready-sessions 20 \\
--location eastasia
- name: Create or update a Session Pool with container type CustomContainer with default quickstart image.
text: |
az containerapp sessionpool create -n mysessionpool -g MyResourceGroup \\
--container-type CustomContainer --environment MyEnvironment \\
--cpu 0.5 --memory 1Gi --target-port 80 --location eastasia --image mcr.microsoft.com/k8se/quickstart:latest
- name: Create or update a Session Pool with container type CustomContainer that has secrets and environment variables.
text: |
az containerapp sessionpool create -n mysessionpool -g MyResourceGroup \\
--container-type CustomContainer --environment MyEnvironment \\
--cpu 0.5 --memory 1Gi --target-port 80 --image MyImage \\
--env-vars GREETING="Hello, world" SECRETENV=secretref:anothersecret \\
--secrets mysecret=secretvalue1 anothersecret="secret value 2" --location eastasia
- name: Create or update a Session Pool with container type CustomContainer that from private registry
text: |
az containerapp sessionpool create -n mysessionpool -g MyResourceGroup \\
--container-type CustomContainer --environment MyEnvironment --image MyImage \\
--cpu 0.5 --memory 1Gi --target-port 80 --registry-server myregistry.azurecr.io \\
--registry-username myregistry --registry-password $REGISTRY_PASSWORD \\
--location eastasia
- name: Create or update a Session Pool with container type CustomContainer with cooldown period 360s
text: |
az containerapp sessionpool create -n mysessionpool -g MyResourceGroup \\
--environment MyEnvironment --cpu 0.5 --memory 1Gi --target-port 80 --container-type CustomContainer \\
--cooldown-period 360 --location eastasia
"""

helps['containerapp sessionpool update'] = """
type: command
short-summary: Update a Session pool.
examples:
- name: Update a session pool's max concurrent sessions configuration and image.
text: |
az containerapp sessionpool update -n mysessionpool -g MyResourceGroup --max-sessions 20 --image MyNewImage
"""

helps['containerapp sessionpool delete'] = """
type: command
short-summary: Delete a session pool.
examples:
- name: Delete a session pool.
text: az containerapp sessionpool delete -n mysessionpool -g MyResourceGroup
"""

helps['containerapp sessionpool show'] = """
type: command
short-summary: Show details of a Session Pool.
examples:
- name: Show the details of a Session Pool.
text: |
az containerapp sessionpool show -n mysessionpool -g MyResourceGroup
"""

helps['containerapp sessionpool list'] = """
yalixiang marked this conversation as resolved.
Show resolved Hide resolved
type: command
short-summary: List Session Pools by subscription or resource group.
examples:
- name: List Session Pools in the current subscription.
text: |
az containerapp sessionpool list
- name: List Session Pools by resource group.
text: |
az containerapp sessionpool list -g MyResourceGroup
"""

14 changes: 14 additions & 0 deletions src/containerapp/azext_containerapp/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,20 @@
"tags": None
}

SessionPool = {
"location": None,
"properties": {
"environmentId": None,
"poolManagementType": None,
"containerType": None,
"customContainerTemplate": None,
"secrets": None,
"dynamicPoolConfiguration": None,
"scaleConfiguration": None,
"sessionNetworkConfiguration": None
}
}

DaprComponentResiliency = {
"properties": {
"inboundPolicy": {
Expand Down
38 changes: 37 additions & 1 deletion src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
from azure.cli.core.commands.parameters import (resource_group_name_type, get_location_type,
file_type,
get_three_state_flag, get_enum_type, tags_type)
from azure.cli.command_modules.containerapp._validators import (validate_memory, validate_cpu,
validate_managed_env_name_or_id,
validate_registry_server,
validate_registry_user, validate_registry_pass
)

from .action import AddCustomizedKeys
from ._validators import (validate_env_name_or_id, validate_build_env_vars,
validate_custom_location_name_or_id, validate_env_name_or_id_for_up,
validate_otlp_headers)
validate_otlp_headers, validate_target_port_range)
from ._constants import MAXIMUM_CONTAINER_APP_NAME_LENGTH, MAXIMUM_APP_RESILIENCY_NAME_LENGTH, MAXIMUM_COMPONENT_RESILIENCY_NAME_LENGTH


Expand Down Expand Up @@ -347,3 +352,34 @@ def load_arguments(self, _):

with self.argument_context('containerapp env', arg_group='Peer Traffic Configuration') as c:
c.argument('p2p_encryption_enabled', arg_type=get_three_state_flag(), options_list=['--enable-peer-to-peer-encryption'], is_preview=True, help='Boolean indicating whether the peer-to-peer traffic encryption is enabled for the environment.')

with self.argument_context('containerapp sessionpool') as c:
c.argument('name', options_list=['--name', '-n'], help="The Session Pool name.")
c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None)

with self.argument_context('containerapp sessionpool', arg_group='Configuration') as c:
c.argument('container_type', arg_type=get_enum_type(["CustomContainer", "PythonLTS"]), help="The pool type of the Session Pool, default='PythonLTS'")
c.argument('cooldown_period', help="Period (in seconds), after which the session will be deleted, default=300")
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.")
c.argument('network_status', arg_type=get_enum_type(["EgressEnabled", "EgressDisabled"]), help="Egress is enabled for the Sessions or not.")
yalixiang marked this conversation as resolved.
Show resolved Hide resolved

with self.argument_context('containerapp sessionpool', arg_group='Scale') as c:
c.argument('max_concurrent_sessions', options_list=['--max-sessions'], help="Max count of sessions can be run at the same time.", type=int)
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)

with self.argument_context('containerapp sessionpool', arg_group='Container') as c:
c.argument('managed_env', validator=validate_managed_env_name_or_id, options_list=['--environment'], help="Name or resource ID of the container app's environment.")
yalixiang marked this conversation as resolved.
Show resolved Hide resolved
c.argument('image', options_list=['--image', '-i'], help="Container image, e.g. publisher/image-name:tag.")
c.argument('container_name', help="Name of the container.")
yalixiang marked this conversation as resolved.
Show resolved Hide resolved
c.argument('cpu', type=float, validator=validate_cpu, help="Required CPU in cores from 0.25 - 2.0, e.g. 0.5")
yalixiang marked this conversation as resolved.
Show resolved Hide resolved
c.argument('memory', validator=validate_memory, help="Required memory from 0.5 - 4.0 ending with \"Gi\", e.g. 1.0Gi")
yalixiang marked this conversation as resolved.
Show resolved Hide resolved
c.argument('env_vars', nargs='*', help="A list of environment variable(s) for the container. Space-separated values in 'key=value' format. Empty string to clear existing values. Prefix value with 'secretref:' to reference a secret.")
c.argument('startup_command', nargs='*', options_list=['--command'], help="A list of supported commands on the container that will executed during startup. Space-separated values e.g. \"/bin/queue\" \"mycommand\". Empty string to clear existing values")
c.argument('args', nargs='*', help="A list of container startup command argument(s). Space-separated values e.g. \"-c\" \"mycommand\". Empty string to clear existing values")
c.argument('target_port', type=int, validator=validate_target_port_range, help="The session port used for ingress traffic.")

with self.argument_context('containerapp sessionpool', arg_group='Registry') as c:
c.argument('registry_server', validator=validate_registry_server, help="The container registry server hostname, e.g. myregistry.azurecr.io.")
c.argument('registry_pass', validator=validate_registry_pass, options_list=['--registry-password'], help="The password to log in to container registry. If stored as a secret, value must start with \'secretref:\' followed by the secret name.")
c.argument('registry_user', validator=validate_registry_user, options_list=['--registry-username'], help="The username to log in to container registry.")

8 changes: 8 additions & 0 deletions src/containerapp/azext_containerapp/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import requests
import packaging.version as SemVer

from enum import Enum
from urllib.request import urlopen

from azure.cli.command_modules.containerapp._utils import safe_get, _ensure_location_allowed, \
Expand Down Expand Up @@ -732,3 +733,10 @@ def is_cloud_supported_by_connected_env(cli_ctx):
if cli_ctx.cloud.name == 'AzureCloud':
return True
return False


class AppType(Enum):
yalixiang marked this conversation as resolved.
Show resolved Hide resolved
ContainerApp = 1
ContainerAppJob = 2
SessionPool = 3

7 changes: 7 additions & 0 deletions src/containerapp/azext_containerapp/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def validate_build_env_vars(cmd, namespace):
env=key_val[0]))
env_pairs[key_val[0]] = key_val[1]


def validate_otlp_headers(cmd, namespace):
headers = namespace.headers

Expand All @@ -194,3 +195,9 @@ def validate_otlp_headers(cmd, namespace):
header=key_val[0]))
header_pairs[key_val[0]] = key_val[1]


def validate_target_port_range(cmd, namespace):
target_port = namespace.target_port
if target_port is not None:
if target_port < 1 or target_port > 65535:
raise ValidationError("Port must be in range [1, 65535].")
7 changes: 7 additions & 0 deletions src/containerapp/azext_containerapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,10 @@ def load_command_table(self, args):
g.custom_command('update', 'update_eureka_server_for_spring', supports_no_wait=True)
g.custom_show_command('show', 'show_eureka_server_for_spring')
g.custom_command('delete', 'delete_eureka_server_for_spring', confirmation=True, supports_no_wait=True)

with self.command_group('containerapp sessionpool', is_preview=True) as g:
g.custom_show_command('show', 'show_session_pool')
g.custom_show_command('list', 'list_session_pool')
g.custom_command('create', 'create_session_pool', supports_no_wait=True)
g.custom_command('update', 'update_session_pool', supports_no_wait=True)
g.custom_command('delete', 'delete_session_pool', confirmation=True, supports_no_wait=True)
Loading
Loading