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

Print deployment process for the command 'az spring app deploy' #6040

Merged
merged 35 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from 27 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
4 changes: 4 additions & 0 deletions src/spring/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Release History
===============
1.10.0
---
* Print more logs for app deployment

1.9.1
---
* Support subPath for bring your own persistent storage feature.
Expand Down
39 changes: 20 additions & 19 deletions src/spring/azext_spring/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,56 +606,55 @@ def validate_jar(namespace):
if values is None:
# ignore jar_file check
return
file_size, spring_boot_version, spring_cloud_version, has_actuator, has_manifest, has_jar, has_class, ms_sdk_version, jdk_version = values

tips = ", if you choose to ignore these errors, turn validation off with --disable-validation"
if not has_jar and not has_class:
if not values["has_jar"] and not values["has_class"]:
telemetry.set_user_fault("invalid_jar_no_class_jar")
raise InvalidArgumentValueError(
"Do not find any class or jar file, please check if your artifact is a valid fat jar" + tips)
if not has_manifest:
if not values["has_manifest"]:
telemetry.set_user_fault("invalid_jar_no_manifest")
raise InvalidArgumentValueError(
"Do not find MANIFEST.MF, please check if your artifact is a valid fat jar" + tips)
if file_size / 1024 / 1024 < 10:
if values["file_size"] / 1024 / 1024 < 10:
telemetry.set_user_fault("invalid_jar_thin_jar")
raise InvalidArgumentValueError("Thin jar detected, please check if your artifact is a valid fat jar" + tips)
version_number = int(runtime_version[len("Java_"):])
if jdk_version not in _java_runtime_in_number():
if values["jdk_version"] not in _java_runtime_in_number():
raise InvalidArgumentValueError("Your java application is compiled with {}, currently the supported "
"java version is Java_8, Java_11, Java_17, you can configure the java runtime "
"with --runtime-version".format("Java_" + str(jdk_version)) + tips)
if jdk_version > version_number:
"with --runtime-version".format("Java_" + str(values["jdk_version"])) + tips)
if values["jdk_version"] > version_number:
telemetry.set_user_fault("invalid_java_runtime")
raise InvalidArgumentValueError("Invalid java runtime, the runtime you configured is {}, the jar you use is "
"compiled with {}, you can configure the java runtime with --runtime-version".
format(runtime_version, "Java_" + str(jdk_version)) + tips)
format(runtime_version, "Java_" + str(values["jdk_version"])) + tips)
# validate spring boot version
if spring_boot_version and spring_boot_version.startswith('1'):
if values["spring_boot_version"] and values["spring_boot_version"].startswith('1'):
telemetry.set_user_fault("old_spring_boot_version")
raise InvalidArgumentValueError(
"The spring boot {} you are using is not supported. To get the latest supported "
"versions please refer to: https://aka.ms/ascspringversion".format(spring_boot_version) + tips)
"versions please refer to: https://aka.ms/ascspringversion".format(values["spring_boot_version"]) + tips)

# old spring cloud version, need to import ms sdk <= 2.2.1
if spring_cloud_version:
if spring_cloud_version < "2.2.5":
if not ms_sdk_version or ms_sdk_version > "2.2.1":
if values["spring_cloud_version"]:
if values["spring_cloud_version"] < "2.2.5":
if not values["ms_sdk_version"] or values["ms_sdk_version"] > "2.2.1":
telemetry.set_user_fault("old_spring_cloud_version")
raise InvalidArgumentValueError(
"The spring cloud {} you are using is not supported. To get the latest supported "
"versions please refer to: https://aka.ms/ascspringversion".format(spring_cloud_version) + tips)
"versions please refer to: https://aka.ms/ascspringversion".format(values["spring_cloud_version"]) + tips)
else:
if ms_sdk_version and ms_sdk_version <= "2.2.1":
if values["ms_sdk_version"] and values["ms_sdk_version"] <= "2.2.1":
telemetry.set_user_fault("old_ms_sdk_version")
raise InvalidArgumentValueError(
"The spring-cloud-starter-azure-spring-cloud-client version {} is no longer "
"supported, please remove it or upgrade to a higher version, to get the latest "
"supported versions please refer to: "
"https://mvnrepository.com/artifact/com.microsoft.azure/spring-cloud-starter-azure"
"-spring-cloud-client".format(ms_sdk_version) + tips)
"-spring-cloud-client".format(values["ms_sdk_version"]) + tips)

if not has_actuator:
if not values["has_actuator"]:
telemetry.set_user_fault("no_spring_actuator")
logger.warning(
"Seems you do not import spring actuator, thus metrics are not enabled, please refer to "
Expand Down Expand Up @@ -719,8 +718,10 @@ def _parse_jar_file(artifact_path):
if search(spring_cloud_eureka_pattern, file_name):
prefix = 'spring-cloud-netflix-eureka-client-'
spring_cloud_version = file_name[file_name.index(prefix) + len(prefix):file_name.index('.jar')]
return file_size, spring_boot_version, spring_cloud_version, has_actuator, has_manifest, has_jar, has_class, \
ms_sdk_version, jdk_version
return dict(file_size=file_size, spring_boot_version=spring_boot_version,
spring_cloud_version=spring_cloud_version, has_actuator=has_actuator,
has_manifest=has_manifest, has_jar=has_jar, has_class=has_class,
ms_sdk_version=ms_sdk_version, jdk_version=jdk_version)
except Exception as err: # pylint: disable=broad-except
telemetry.set_exception("parse user jar file failed, " + str(err))
return None
Expand Down
79 changes: 72 additions & 7 deletions src/spring/azext_spring/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from datetime import datetime

# pylint: disable=wrong-import-order
from knack.log import get_logger
Expand All @@ -17,6 +18,8 @@
from ._deployment_deployable_factory import deployable_selector
from ._app_validator import _get_active_deployment
from .custom import app_tail_log_internal
import datetime
from time import sleep

logger = get_logger(__name__)
DEFAULT_DEPLOYMENT_NAME = "default"
Expand Down Expand Up @@ -419,14 +422,20 @@ def app_deploy(cmd, client, resource_group, service, name,
return client.deployments.get(resource_group, service, name, deployment.name)


def _log_application(cmd, client, no_wait, poller, resource_group, service, app_name, deployment_name):
def _log_application(cmd, client, no_wait, poller, resource_group, service, app_name, deployment_name,
print_deploy_process=True):
if no_wait:
return
deployment_error = None
try:
poller.result()
except Exception as err:
deployment_error = err
# We will wait for the poller to be done to print the deploy process
if print_deploy_process:
zmssp marked this conversation as resolved.
Show resolved Hide resolved
deployment_error = _print_deploy_process(client, poller, resource_group, service, app_name, deployment_name)
# If we do not print deploy process, we should wait for the poller to be done here
else:
try:
poller.result()
except Exception as err:
deployment_error = err
try:
deployment_resource = client.deployments.get(resource_group, service, app_name, deployment_name)
instances = deployment_resource.properties.instances
Expand All @@ -447,11 +456,67 @@ def _log_application(cmd, client, no_wait, poller, resource_group, service, app_
since=300, timeout=10, get_app_log=_get_app_log_deploy_phase)
except Exception:
# ignore
return
pass
if deployment_error:
raise deployment_error


def _print_deploy_process(client, poller, resource_group, service, app_name, deployment_name):
try:
deployment_resource = _get_deployment_ignore_exception(client, resource_group, service, app_name,
deployment_name)
if deployment_resource is not None:
instance_count = deployment_resource.sku.capacity
rolling_number = max(1, instance_count // 4)
rounds = int(instance_count // rolling_number + 0 if instance_count % rolling_number == 0 else 1)
zmssp marked this conversation as resolved.
Show resolved Hide resolved

if instance_count > 1:
instance_desc = str(instance_count) + " instances"
rounds_desc = str(rounds) + " rounds"
else:
instance_desc = str(instance_count) + " instance"
rounds_desc = str(rounds) + " round"
logger.warning('ASA will use rolling upgrade to update your deployment, you have {}, '
zmssp marked this conversation as resolved.
Show resolved Hide resolved
'ASA will update the deployment in {}'.format(instance_desc, rounds_desc))
last_round = 0

deployment_time = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z")
zmssp marked this conversation as resolved.
Show resolved Hide resolved
while not poller.done():
zmssp marked this conversation as resolved.
Show resolved Hide resolved
deployment_resource = _get_deployment_ignore_exception(client, resource_group, service, app_name,
deployment_name)
if deployment_resource is not None:
instances = deployment_resource.properties.instances
new_instance_count = 0
for temp_instance in instances:
if temp_instance.start_time > deployment_time:
new_instance_count += 1
instance_round = instance_count // rounds
current_round = new_instance_count // instance_round + (0 if new_instance_count % instance_round == 0 else 1)
if current_round != last_round:
if int(current_round) > 1:
old_desc = "{} old instances are".format(int(new_instance_count))
else:
old_desc = "{} old instance is".format(int(new_instance_count))
if int(new_instance_count) > 1:
new_desc = "{} new instances are".format(int(new_instance_count))
else:
new_desc = "{} new instance is".format(int(new_instance_count))
logger.warning(
'The deployment is in round {}, {} deleted/deleting and {} '
'started/starting'.format(int(current_round), old_desc, new_desc))
last_round = current_round
sleep(5)
except Exception as err:
return err


def _get_deployment_ignore_exception(client, resource_group, service, app_name, deployment_name):
try:
return client.deployments.get(resource_group, service, app_name, deployment_name)
except Exception:
pass


def _get_app_log_deploy_phase(url, auth, format_json, exceptions):
try:
_get_app_log(url, auth, format_json, exceptions, chunk_size=10 * 1024, stderr=True)
Expand Down Expand Up @@ -578,7 +643,7 @@ def deployment_create(cmd, client, resource_group, service, app, name,
resource_group, service, app, name,
deployment_resource)
if not disable_app_log:
_log_application(cmd, client, no_wait, poller, resource_group, service, app, name)
_log_application(cmd, client, no_wait, poller, resource_group, service, app, name, print_deploy_process=False)
if "succeeded" != poller.status().lower():
return poller
return client.deployments.get(resource_group, service, app, name)
Expand Down
6 changes: 3 additions & 3 deletions src/spring/azext_spring/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,9 @@ def app_tail_log_internal(cmd, client, resource_group, service, name,

if timeout:
t.join(timeout=timeout)

while t.is_alive():
sleep(5) # so that ctrl+c can stop the command
else:
while t.is_alive():
sleep(5) # so that ctrl+c can stop the command

if exceptions:
raise exceptions[0]
Expand Down
Loading