Skip to content

Commit

Permalink
[App Service] az functionapp create: Add --zone-redundant paramet…
Browse files Browse the repository at this point in the history
…er to support zone redundant for Functions Flex SKU (Azure#29984)
  • Loading branch information
khkh-ms authored Oct 15, 2024
1 parent a9bec7b commit a54dce2
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 4 deletions.
6 changes: 6 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/_params.py
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 @@ -4581,7 +4581,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 @@ -4592,6 +4592,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 @@ -4757,6 +4761,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")
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 @@ -4772,8 +4783,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 @@ -4812,6 +4824,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 @@ -5138,14 +5156,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 @@ -5690,7 +5719,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

0 comments on commit a54dce2

Please sign in to comment.