Skip to content

Commit

Permalink
Introduce App Resiliency & Dapr Component Resiliency (Azure#6967)
Browse files Browse the repository at this point in the history
* Introduce App Resiliency & Dapr Component Resiliency

Signed-off-by: Yash Nisar <yashnisar@microsoft.com>

* Address comments and shorten cli flags

Signed-off-by: Yash Nisar <yashnisar@microsoft.com>

* Code cleanup and address review comments

Signed-off-by: Yash Nisar <yashnisar@microsoft.com>

* Fix param_name output errors

Signed-off-by: Yash Nisar <yashnisar@microsoft.com>

---------

Signed-off-by: Yash Nisar <yashnisar@microsoft.com>
  • Loading branch information
yash-nisar authored Nov 10, 2023
1 parent 7fc0f72 commit 9509e41
Show file tree
Hide file tree
Showing 14 changed files with 12,406 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ upcoming
++++++
* 'az containerapp env workload-profile set': deprecate command
* 'az containerapp add-on': support for az containerapp add-on commands; deprecation of az containerapp service commands
* 'az containerapp env dapr-component resiliency': Add Dapr Component Resiliency commands
* 'az containerapp resiliency': Add Container App Resiliency commands
* 'az containerapp env create': Support --enable-dedicated-gpu
* 'az containerapp job create': fix problem of parsing parameters minExecutions and maxExecutions from --yaml
* 'az containerapp env dapr-component init': support initializing Dapr components and dev services for an environment
Expand Down
229 changes: 229 additions & 0 deletions src/containerapp/azext_containerapp/_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,235 @@ class ContainerAppsJobPreviewClient(ContainerAppsJobClient):
api_version = PREVIEW_API_VERSION


class ContainerAppsResiliencyPreviewClient():
api_version = PREVIEW_API_VERSION

@classmethod
def create_or_update(cls, cmd, resource_group_name, name, container_app_name, container_app_resiliency_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/containerApps/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
container_app_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(container_app_resiliency_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)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
container_app_name,
name,
cls.api_version)

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

return r.json()

@classmethod
def update(cls, cmd, resource_group_name, name, container_app_name, container_app_resiliency_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/containerApps/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
container_app_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "PATCH", request_url, body=json.dumps(container_app_resiliency_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 app resiliency policy")
else:
return response

return r.json()

@classmethod
def delete(cls, cmd, resource_group_name, name, container_app_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/containerApps/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
container_app_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('App Resiliency Policy successfully deleted')

@classmethod
def show(cls, cmd, resource_group_name, name, container_app_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/containerApps/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
container_app_name,
name,
cls.api_version)

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

return r.json()

@classmethod
def list(cls, cmd, resource_group_name, container_app_name):
policy_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/containerApps/{}/resiliencyPolicies?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
container_app_name,
cls.api_version)

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

for policy in r["value"]:
policy_list.append(policy)

return policy_list


class DaprComponentResiliencyPreviewClient():
api_version = PREVIEW_API_VERSION

@classmethod
def create_or_update(cls, cmd, name, resource_group_name, dapr_component_name, environment_name, component_resiliency_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/managedEnvironments/{}/daprComponents/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
dapr_component_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(component_resiliency_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)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/managedEnvironments/{}/daprComponents/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
dapr_component_name,
name,
cls.api_version)

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

return r.json()

@classmethod
def delete(cls, cmd, name, resource_group_name, dapr_component_name, environment_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/managedEnvironments/{}/daprComponents/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
dapr_component_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('Dapr Component Resiliency Policy successfully deleted')

@classmethod
def show(cls, cmd, name, resource_group_name, dapr_component_name, environment_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/managedEnvironments/{}/daprComponents/{}/resiliencyPolicies/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
dapr_component_name,
name,
cls.api_version)

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

return r.json()

@classmethod
def list(cls, cmd, resource_group_name, dapr_component_name, environment_name):
policy_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/managedEnvironments/{}/daprComponents/{}/resiliencyPolicies?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
dapr_component_name,
cls.api_version)

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

for policy in r["value"]:
policy_list.append(policy)

return policy_list


class SubscriptionPreviewClient():
api_version = PREVIEW_API_VERSION

Expand Down
19 changes: 19 additions & 0 deletions src/containerapp/azext_containerapp/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@

MAXIMUM_SECRET_LENGTH = 20
MAXIMUM_CONTAINER_APP_NAME_LENGTH = 32
MAXIMUM_APP_RESILIENCY_NAME_LENGTH = 30
MAXIMUM_COMPONENT_RESILIENCY_NAME_LENGTH = 30

DEFAULT_HTTP_RETRY_MAX = 3
DEFAULT_HTTP_RETRY_DELAY_IN_MILLISECONDS = 1000
DEFAULT_HTTP_RETRY_INTERVAL_IN_MILLISECONDS = 10000
DEFAULT_HTTP_RETRY_ERRORS = ['5xx']

DEFAULT_RESPONSE_TIMEOUT = 60
DEFAULT_CONNECTION_TIMEOUT = 5
DEFAULT_CONSECUTIVE_ERRORS = 5
DEFAULT_INTERVAL = 10
DEFAULT_MAX_EJECTION = 100
DEFAULT_HTTP1_MAX_PENDING_REQ = 1024
DEFAULT_HTTP2_MAX_REQ = 1024

DEFAULT_COMPONENT_HTTP_RETRY_MAX = 3
DEFAULT_COMPONENT_HTTP_RETRY_BACKOFF_INITIAL_DELAY = 1000
DEFAULT_COMPONENT_HTTP_RETRY_BACKOFF_MAX_DELAY = 10000

SHORT_POLLING_INTERVAL_SECS = 3
LONG_POLLING_INTERVAL_SECS = 10
Expand Down
45 changes: 45 additions & 0 deletions src/containerapp/azext_containerapp/_decorator_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,48 @@ def process_loaded_yaml(yaml_containerapp):
del yaml_containerapp['properties']['managedEnvironmentId']

return yaml_containerapp


def process_containerapp_resiliency_yaml(containerapp_resiliency):

if type(containerapp_resiliency) != dict: # pylint: disable=unidiomatic-typecheck
raise ValidationError('Invalid YAML provided. Please provide a valid container app resiliency YAML spec.')
if 'additionalProperties' in containerapp_resiliency and not containerapp_resiliency['additionalProperties']:
raise ValidationError('Invalid YAML provided. Please provide a valid containerapp resiliency YAML spec.')
if not containerapp_resiliency.get('properties'):
containerapp_resiliency['properties'] = {}

nested_properties = ["timeoutPolicy",
"httpRetryPolicy",
"tcpRetryPolicy",
"circuitBreakerPolicy",
"tcpConnectionPool",
"httpConnectionPool"]
for nested_property in nested_properties:
# Fix this and remove additionalProperties after flattening is avoided
tmp = containerapp_resiliency['additionalProperties'].get(nested_property)
if nested_property in containerapp_resiliency:
containerapp_resiliency['properties'][nested_property] = tmp
del containerapp_resiliency[nested_property]

return containerapp_resiliency


def process_dapr_component_resiliency_yaml(dapr_component_resiliency):

if type(dapr_component_resiliency) != dict: # pylint: disable=unidiomatic-typecheck
raise ValidationError('Invalid YAML provided. Please provide a valid dapr component resiliency YAML spec.')
if 'additionalProperties' in dapr_component_resiliency and not dapr_component_resiliency['additionalProperties']:
raise ValidationError('Invalid YAML provided. Please provide a valid dapr component resiliency YAML spec.')
if not dapr_component_resiliency.get('properties'):
dapr_component_resiliency['properties'] = {}

nested_properties = ["inboundPolicy",
"outboundPolicy"]
for nested_property in nested_properties:
tmp = dapr_component_resiliency['additionalProperties'].get(nested_property)
if nested_property in dapr_component_resiliency:
dapr_component_resiliency['properties'][nested_property] = tmp
del dapr_component_resiliency[nested_property]

return dapr_component_resiliency
Loading

0 comments on commit 9509e41

Please sign in to comment.