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

[App Service] az functionapp create: Add --zone-redundant parameter to support zone redundant for Functions Flex SKU #29984

Merged
merged 15 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ def load_arguments(self, _):
c.argument('location', arg_type=get_location_type(self.cli_ctx), help="limit the output to just the runtimes available in the specified location")
c.argument('runtime', help="limit the output to just the specified runtime")

with self.argument_context('functionapp list-flexconsumption-locations') as c:
c.argument('zone_redundant', arg_type=get_three_state_flag(),
help='Filter the list to return only locations which support zone redundancy.', is_preview=True)

with self.argument_context('webapp deleted list') as c:
c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
c.argument('slot', options_list=['--slot', '-s'], help='Name of the deleted web app slot.')
Expand Down Expand Up @@ -822,6 +826,8 @@ def load_arguments(self, _):
c.argument('deployment_storage_auth_value', options_list=['--deployment-storage-auth-value', '--dsav'], help="The deployment storage account authentication value. For the user-assigned managed identity authentication type, "
"this should be the user assigned identity resource id. For the storage account connection string authentication type, this should be the name of the app setting that will contain the storage account connection "
"string. For the system assigned managed-identity authentication type, this parameter is not applicable and should be left empty.", is_preview=True)
c.argument('zone_redundant', arg_type=get_three_state_flag(),
help='Enable zone redundancy for high availability. Applies to Flex Consumption SKU only.', is_preview=True)

with self.argument_context('functionapp deployment config set') as c:
c.argument('deployment_storage_name', options_list=['--deployment-storage-name', '--dsn'], help="The deployment storage account name.", is_preview=True)
Expand Down
40 changes: 36 additions & 4 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -4576,7 +4576,7 @@ def get_app_insights_connection_string(cli_ctx, resource_group, name):
return appinsights.connection_string


def create_flex_app_service_plan(cmd, resource_group_name, name, location):
def create_flex_app_service_plan(cmd, resource_group_name, name, location, zone_redundant):
SkuDescription, AppServicePlan = cmd.get_models('SkuDescription', 'AppServicePlan')
client = web_client_factory(cmd.cli_ctx)
sku_def = SkuDescription(tier="FlexConsumption", name="FC1", size="FC", family="FC")
Expand All @@ -4587,6 +4587,10 @@ def create_flex_app_service_plan(cmd, resource_group_name, name, location):
kind="functionapp",
name=name
)

if zone_redundant:
_enable_zone_redundant(plan_def, sku_def, None)

poller = client.app_service_plans.begin_create_or_update(resource_group_name, name, plan_def)
return LongRunningOperation(cmd.cli_ctx)(poller)

Expand Down Expand Up @@ -4752,6 +4756,13 @@ def is_exactly_one_true(*args):
return found


def list_flexconsumption_zone_redundant_locations(cmd):
client = web_client_factory(cmd.cli_ctx)
regions = client.list_geo_regions(sku="FlexConsumption")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when this API call fails? Does the operation fail or the method return no locations?

Copy link
Contributor Author

@khkh-ms khkh-ms Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API errors are surfaced to users in cli. The user will be able to see the actual error message from the backend. User can use --debug flag for further details for the API requests/responses.

regions = [x for x in regions if "FCZONEREDUNDANCY" in x.org_domain]
return [{'name': x.name.lower().replace(' ', '')} for x in regions]


def create_functionapp(cmd, resource_group_name, name, storage_account, plan=None,
os_type=None, functions_version=None, runtime=None, runtime_version=None,
consumption_plan_location=None, app_insights=None, app_insights_key=None,
Expand All @@ -4767,8 +4778,9 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
always_ready_instances=None, maximum_instance_count=None, instance_memory=None,
flexconsumption_location=None, deployment_storage_name=None,
deployment_storage_container_name=None, deployment_storage_auth_type=None,
deployment_storage_auth_value=None):
deployment_storage_auth_value=None, zone_redundant=False):
# pylint: disable=too-many-statements, too-many-branches

if functions_version is None and flexconsumption_location is None:
logger.warning("No functions version specified so defaulting to 4.")
functions_version = '4'
Expand Down Expand Up @@ -4807,6 +4819,12 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
SiteConfig, NameValuePair, DaprConfig, ResourceConfig = cmd.get_models('SiteConfig', 'NameValuePair',
'DaprConfig', 'ResourceConfig')

if flexconsumption_location is None:
if zone_redundant:
raise ArgumentUsageError(
'--zone-redundant is only valid for the Flex Consumption plan. '
'Please try again without the --zone-redundant parameter.')

if flexconsumption_location is not None:
if image is not None:
raise ArgumentUsageError(
Expand Down Expand Up @@ -5133,14 +5151,25 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
create_app_insights = True

if flexconsumption_location is not None:
if zone_redundant:
zone_redundant_locations = list_flexconsumption_zone_redundant_locations(cmd)
zone_redundant_location = next((loc for loc in zone_redundant_locations
if loc['name'].lower() == flexconsumption_location.lower()), None)
if zone_redundant_location is None:
raise ValidationError("The specified location '{0}' "
"doesn't support zone redundancy in Flex Consumption. "
"Use: az functionapp list-flexconsumption-locations --zone-redundant "
"for the list of locations that support zone redundancy."
.format(flexconsumption_location))

site_config.net_framework_version = None
functionapp_def.reserved = None
functionapp_def.is_xenon = None

try:
plan_name = generatePlanName(resource_group_name)
plan_info = create_flex_app_service_plan(
cmd, resource_group_name, plan_name, flexconsumption_location)
cmd, resource_group_name, plan_name, flexconsumption_location, zone_redundant)
functionapp_def.server_farm_id = plan_info.id
functionapp_def.location = flexconsumption_location

Expand Down Expand Up @@ -5685,7 +5714,10 @@ def list_consumption_locations(cmd):
return [{'name': x.name.lower().replace(' ', '')} for x in regions]


def list_flexconsumption_locations(cmd):
def list_flexconsumption_locations(cmd, zone_redundant=False):
if zone_redundant:
return list_flexconsumption_zone_redundant_locations(cmd)

from azure.cli.core.commands.client_factory import get_subscription_id
sub_id = get_subscription_id(cmd.cli_ctx)
geo_regions_api = 'subscriptions/{}/providers/Microsoft.Web/geoRegions?sku=FlexConsumption&api-version=2023-01-01'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -779,10 +779,42 @@ def test_functionapp_list_flexconsumption_locations(self):
locations = self.cmd('functionapp list-flexconsumption-locations').get_output_in_json()
self.assertTrue(len(locations) == 13)

def test_functionapp_list_flexconsumption_locations_zone_redundant(self):
locations = self.cmd('functionapp list-flexconsumption-locations --zone-redundant').get_output_in_json()
self.assertTrue(len(locations) > 0)

def test_functionapp_list_flexconsumption_runtimes(self):
runtimes = self.cmd('functionapp list-flexconsumption-runtimes -l eastasia --runtime python').get_output_in_json()
self.assertTrue(len(runtimes) == 2)

@ResourceGroupPreparer(location=FLEX_ASP_LOCATION_FUNCTIONAPP)
@StorageAccountPreparer()
def test_functionapp_flex_zone_redundant_active(self, resource_group, storage_account):
functionapp_name = self.create_random_name(
'functionapp', 40)

functionapp = self.cmd('functionapp create -g {} -n {} -f {} -s {} --runtime python --runtime-version 3.11 --zone-redundant'
.format(resource_group, functionapp_name, FLEX_ASP_LOCATION_FUNCTIONAPP, storage_account)).get_output_in_json()

server_farm_id =functionapp['properties']['serverFarmId']
function_plan = self.cmd('az functionapp plan show --ids {}'
.format(server_farm_id)).get_output_in_json()
self.assertTrue(function_plan['zoneRedundant'] == True)

@ResourceGroupPreparer(location=FLEX_ASP_LOCATION_FUNCTIONAPP)
@StorageAccountPreparer()
def test_functionapp_flex_zone_redundant_not_active(self, resource_group, storage_account):
functionapp_name = self.create_random_name(
'functionapp', 40)

functionapp = self.cmd('functionapp create -g {} -n {} -f {} -s {} --runtime python --runtime-version 3.11'
.format(resource_group, functionapp_name, FLEX_ASP_LOCATION_FUNCTIONAPP, storage_account)).get_output_in_json()

server_farm_id =functionapp['properties']['serverFarmId']
function_plan = self.cmd('az functionapp plan show --ids {}'
.format(server_farm_id)).get_output_in_json()
self.assertTrue(function_plan['zoneRedundant'] == False)

@ResourceGroupPreparer(location=FLEX_ASP_LOCATION_FUNCTIONAPP)
@StorageAccountPreparer()
def test_functionapp_flex_scale_config(self, resource_group, storage_account):
Expand Down
Loading