Skip to content

Commit

Permalink
[spring-cloud] Add validator for app deploy and app deployment create (
Browse files Browse the repository at this point in the history
…#4258)

* Add validator for app deploy/create

* fix lint

* fix error message
  • Loading branch information
yuwzho authored Dec 28, 2021
1 parent f913cbc commit 5fa9c47
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 21 deletions.
27 changes: 27 additions & 0 deletions src/spring-cloud/azext_spring_cloud/_app_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from msrestazure.azure_exceptions import CloudError
from azure.core.exceptions import (ResourceNotFoundError)
from ._client_factory import cf_spring_cloud
from ._resource_quantity import (validate_cpu as validate_cpu_value, validate_memory as validate_memory_value)


# pylint: disable=line-too-long,raise-missing-from
Expand Down Expand Up @@ -76,3 +77,29 @@ def _get_active_deployment(client, resource_group, service, name):
return next(iter(x for x in deployments if x.properties.active), None)
except ResourceNotFoundError:
raise InvalidArgumentValueError('Deployments not found under App {}'.format(name))


def validate_deloy_path(namespace):
arguments = [namespace.artifact_path, namespace.source_path, namespace.container_image]
if all(not x for x in arguments):
raise InvalidArgumentValueError('One of --artifact-path, --source-path, --container-image must be provided.')
_deploy_path_mutual_exclusive(arguments)


def validate_deloyment_create_path(namespace):
arguments = [namespace.artifact_path, namespace.source_path, namespace.container_image]
_deploy_path_mutual_exclusive(arguments)


def _deploy_path_mutual_exclusive(args):
valued_args = [x for x in args if x]
if len(valued_args) > 1:
raise InvalidArgumentValueError('At most one of --artifact-path, --source-path, --container-image must be provided.')


def validate_cpu(namespace):
namespace.cpu = validate_cpu_value(namespace.cpu)


def validate_memory(namespace):
namespace.memory = validate_memory_value(namespace.memory)
46 changes: 27 additions & 19 deletions src/spring-cloud/azext_spring_cloud/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
validate_app_insights_parameters, validate_instance_count, validate_java_agent_parameters,
validate_jar)
from ._app_validator import (fulfill_deployment_param, active_deployment_exist, active_deployment_exist_under_app,
ensure_not_active_deployment)
ensure_not_active_deployment, validate_deloy_path, validate_deloyment_create_path,
validate_cpu, validate_memory)
from ._utils import ApiType

from .vendored_sdks.appplatform.v2020_07_01.models import RuntimeVersion, TestKeyType
Expand All @@ -27,6 +28,12 @@
service_name_type = CLIArgumentType(options_list=['--service', '-s'], help='Name of Azure Spring Cloud, you can configure the default service using az configure --defaults spring-cloud=<name>.', configured_default='spring-cloud')
app_name_type = CLIArgumentType(help='App name, you can configure the default app using az configure --defaults spring-cloud-app=<name>.', validator=validate_app_name, configured_default='spring-cloud-app')
sku_type = CLIArgumentType(arg_type=get_enum_type(['Basic', 'Standard', 'Enterprise']), validator=validate_sku, help='Name of SKU. Enterprise is still in Preview.')
source_path_type = CLIArgumentType(nargs='?', const='.',
help="Deploy the specified source folder. The folder will be packed into tar, uploaded, and built using kpack. Default to the current folder if no value provided.",
arg_group='Source Code deploy')
# app cpu and memory
cpu_type = CLIArgumentType(type=str, help='CPU resource quantity. Should be 500m or number of CPU cores.', validator=validate_cpu)
memort_type = CLIArgumentType(type=str, help='Memory resource quantity. Should be 512Mi or #Gi, e.g., 1Gi, 3Gi.', validator=validate_memory)


# pylint: disable=too-many-statements
Expand Down Expand Up @@ -122,10 +129,8 @@ def load_arguments(self, _):
options_list=['--assign-endpoint', c.deprecate(target='--is-public', redirect='--assign-endpoint', hide=True)])
c.argument('assign_identity', arg_type=get_three_state_flag(),
help='If true, assign managed service identity.')
c.argument('cpu', type=str, default="1",
help='CPU resource quantity. Should be 500m or number of CPU cores.')
c.argument('memory', type=str, default="1Gi",
help='Memory resource quantity. Should be 512Mi or #Gi, e.g., 1Gi, 3Gi.')
c.argument('cpu', arg_type=cpu_type, default="1")
c.argument('memory', arg_type=memort_type, default="1Gi")
c.argument('instance_count', type=int,
default=1, help='Number of instance.', validator=validate_instance_count)
c.argument('persistent_storage', type=str,
Expand Down Expand Up @@ -207,8 +212,8 @@ def prepare_logs_argument(c):
c.argument('disable_probe', arg_type=get_three_state_flag(), help='If true, disable the liveness and readiness probe.')

with self.argument_context('spring-cloud app scale') as c:
c.argument('cpu', type=str, help='CPU resource quantity. Should be 500m or number of CPU cores.')
c.argument('memory', type=str, help='Memory resource quantity. Should be 512Mi or #Gi, e.g., 1Gi, 3Gi.')
c.argument('cpu', arg_type=cpu_type)
c.argument('memory', arg_type=memort_type)
c.argument('instance_count', type=int, help='Number of instance.', validator=validate_instance_count)

for scope in ['spring-cloud app deploy', 'spring-cloud app deployment create']:
Expand All @@ -218,37 +223,40 @@ def prepare_logs_argument(c):
c.deprecate(target='--jar-path', redirect='--artifact-path', hide=True),
c.deprecate(target='-p', redirect='--artifact-path', hide=True)],
help='Deploy the specified pre-built artifact (jar or netcore zip).', validator=validate_jar)
c.argument(
'source_path', nargs='?', const='.',
help="Deploy the specified source folder. The folder will be packed into tar, uploaded, and built using kpack. Default to the current folder if no value provided.")
c.argument(
'disable_validation', arg_type=get_three_state_flag(),
help='If true, disable jar validation.')
c.argument(
'main_entry', options_list=[
'--main-entry', '-m'], help="A string containing the path to the .NET executable relative to zip root.")
c.argument(
'target_module', help='Child module to be deployed, required for multiple jar packages built from source code.')
'target_module', help='Child module to be deployed, required for multiple jar packages built from source code.', arg_group='Source Code deploy')
c.argument(
'version', help='Deployment version, keep unchanged if not set.')
c.argument(
'container_image', help='The container image tag.')
'container_image', help='The container image tag.', arg_group='Custom Container')
c.argument(
'container_registry', default='docker.io', help='The registry of the container image.')
'container_registry', default='docker.io', help='The registry of the container image.', arg_group='Custom Container')
c.argument(
'registry_username', help='The username of the container registry.')
'registry_username', help='The username of the container registry.', arg_group='Custom Container')
c.argument(
'registry_password', help='The password of the container registry.')
'registry_password', help='The password of the container registry.', arg_group='Custom Container')
c.argument(
'container_command', help='The command of the container image.')
'container_command', help='The command of the container image.', arg_group='Custom Container')
c.argument(
'container_args', help='The arguments of the container image.')
'container_args', help='The arguments of the container image.', arg_group='Custom Container')

with self.argument_context('spring-cloud app deploy') as c:
c.argument('source_path', arg_type=source_path_type, validator=validate_deloy_path)

with self.argument_context('spring-cloud app deployment create') as c:
c.argument('source_path', arg_type=source_path_type, validator=validate_deloyment_create_path)

with self.argument_context('spring-cloud app deployment create') as c:
c.argument('skip_clone_settings', help='Create staging deployment will automatically copy settings from production deployment.',
action='store_true')
c.argument('cpu', type=str, help='CPU resource quantity. Should be 500m or number of CPU cores.')
c.argument('memory', type=str, help='Memory resource quantity. Should be 512Mi or #Gi, e.g., 1Gi, 3Gi.')
c.argument('cpu', arg_type=cpu_type)
c.argument('memory', arg_type=memort_type)
c.argument('instance_count', type=int, help='Number of instance.', validator=validate_instance_count)

with self.argument_context('spring-cloud app deployment') as c:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
import unittest
import copy
from argparse import Namespace
from azure.cli.core.azclierror import InvalidArgumentValueError
from msrestazure.azure_exceptions import CloudError
from azure.core.exceptions import ResourceNotFoundError
from ..._app_validator import (fulfill_deployment_param, active_deployment_exist, active_deployment_exist_under_app)
from ..._app_validator import (fulfill_deployment_param, active_deployment_exist, active_deployment_exist_under_app,
validate_cpu, validate_memory, validate_deloyment_create_path, validate_deloy_path)


try:
Expand Down Expand Up @@ -39,6 +39,73 @@ def _get_deployment(resource_group, service, app, deployment, active):
return resource


class TestCpuAndMemoryValidator(unittest.TestCase):
def test_none_input(self):
ns = Namespace(cpu=None, memory=None)
validate_memory(ns)
validate_cpu(ns)
self.assertIsNone(ns.cpu)
self.assertIsNone(ns.memory)

def test_int_input(self):
ns = Namespace(cpu='1', memory='1')
validate_memory(ns)
validate_cpu(ns)
self.assertEqual('1', ns.cpu)
self.assertEqual('1Gi', ns.memory)

def test_str_input(self):
ns = Namespace(cpu='1', memory='1Gi')
validate_memory(ns)
validate_cpu(ns)
self.assertEqual('1', ns.cpu)
self.assertEqual('1Gi', ns.memory)

def test_invalid_memory(self):
ns = Namespace(memory='invalid')
with self.assertRaises(InvalidArgumentValueError) as context:
validate_memory(ns)
self.assertEqual('Memory quantity should be integer followed by unit (Mi/Gi)', str(context.exception))

def test_invalid_cpu(self):
ns = Namespace(cpu='invalid')
with self.assertRaises(InvalidArgumentValueError) as context:
validate_cpu(ns)
self.assertEqual('CPU quantity should be millis (500m) or integer (1, 2, ...)', str(context.exception))


class TestDeployPath(unittest.TestCase):
def test_no_deploy_path_provided_when_create(self):
ns = Namespace(source_path=None, artifact_path=None, container_image=None)
validate_deloyment_create_path(ns)

def test_no_deploy_path_when_deploy(self):
ns = Namespace(source_path=None, artifact_path=None, container_image=None)
with self.assertRaises(InvalidArgumentValueError):
validate_deloy_path(ns)

def test_more_than_one_path(self):
ns = Namespace(source_path='test', artifact_path='test', container_image=None)
with self.assertRaises(InvalidArgumentValueError):
validate_deloy_path(ns)
with self.assertRaises(InvalidArgumentValueError):
validate_deloyment_create_path(ns)

def test_more_than_one_path_1(self):
ns = Namespace(source_path='test', artifact_path='test', container_image='test')
with self.assertRaises(InvalidArgumentValueError):
validate_deloy_path(ns)
with self.assertRaises(InvalidArgumentValueError):
validate_deloyment_create_path(ns)

def test_more_than_one_path_2(self):
ns = Namespace(source_path='test', artifact_path=None, container_image='test')
with self.assertRaises(InvalidArgumentValueError):
validate_deloy_path(ns)
with self.assertRaises(InvalidArgumentValueError):
validate_deloyment_create_path(ns)


class TestActiveDeploymentExist(unittest.TestCase):
@mock.patch('azext_spring_cloud._app_validator.cf_spring_cloud', autospec=True)
def test_deployment_found(self, client_factory_mock):
Expand Down

0 comments on commit 5fa9c47

Please sign in to comment.