From e2ee6d824e802bd03fb4193209f1d3d0f3378a90 Mon Sep 17 00:00:00 2001 From: Victoria Litvinova <73560279+vilit1@users.noreply.github.com> Date: Tue, 18 Jun 2024 13:54:21 -0700 Subject: [PATCH 1/4] test: `az iot ops check --pre` updates (#239) * current pre * updates * pylint --- azext_edge/tests/edge/checks/int/helpers.py | 28 +++++- .../edge/checks/int/test_pre_post_int.py | 94 ++++++++++++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/azext_edge/tests/edge/checks/int/helpers.py b/azext_edge/tests/edge/checks/int/helpers.py index fcf6b60a3..631e1e535 100644 --- a/azext_edge/tests/edge/checks/int/helpers.py +++ b/azext_edge/tests/edge/checks/int/helpers.py @@ -4,7 +4,7 @@ # Licensed under the MIT License. See License file in the project root for license information. # ---------------------------------------------------------------------------------------------- -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple from azure.cli.core.azclierror import CLIInternalError from azext_edge.edge.providers.edge_api.base import EdgeResourceApi from ....helpers import ( @@ -133,6 +133,32 @@ def assert_general_eval_custom_resources( assert name in kubectl_items +def combine_statuses( + status_list: List[str] +): + final_status = "success" + for status in status_list: + if final_status == "success" and status in ["warning", "error", "skipped"]: + final_status = status + elif final_status in ["warning", "skipped"] and status == "error": + final_status = status + return final_status + + +def expected_status( + success_or_fail: bool, + success_or_warning: Optional[bool] = None, + warning_or_fail: Optional[bool] = None +): + status = "success" if success_or_fail else "error" + if any([ + status == "success" and success_or_warning is False, + status == "error" and warning_or_fail is True + ]): + status = "warning" + return status + + def run_check_command( detail_level: str, ops_service: str, diff --git a/azext_edge/tests/edge/checks/int/test_pre_post_int.py b/azext_edge/tests/edge/checks/int/test_pre_post_int.py index 22910189b..0941d19dc 100644 --- a/azext_edge/tests/edge/checks/int/test_pre_post_int.py +++ b/azext_edge/tests/edge/checks/int/test_pre_post_int.py @@ -6,8 +6,16 @@ import pytest from knack.log import get_logger +from kubernetes.utils import parse_quantity from azure.cli.core.azclierror import CLIInternalError from ....helpers import run +from .helpers import combine_statuses, expected_status +from azext_edge.edge.providers.check.common import ( + AIO_SUPPORTED_ARCHITECTURES, + MIN_NODE_MEMORY, + MIN_NODE_STORAGE, + MIN_NODE_VCPU +) logger = get_logger(__name__) @@ -40,4 +48,88 @@ def test_check_pre_post(init_setup, post, pre): assert bool(result.get("preDeployment")) == expected_pre assert bool(result.get("postDeployment")) == expected_post - # TODO: add in pre deployment asserts + if not expected_pre: + # only check the pre results since the post results will be checked per service + return + + assert len(result["preDeployment"]) == 2 + k8s_result, node_result = result["preDeployment"] + + # k8s evaluation + assert k8s_result["description"] == "Evaluate Kubernetes server" + assert k8s_result["name"] == "evalK8sVers" + assert "k8s" in k8s_result["targets"] + assert "_all_" in k8s_result["targets"]["k8s"] + k8s_target_result = k8s_result["targets"]["k8s"]["_all_"] + assert k8s_target_result["conditions"] == ["(k8s version)>=1.20"] + k8s_version = run("kubectl version -o json").get("serverVersion") + + k8s_status = k8s_target_result["evaluations"][0]["status"] + if k8s_version: + expected_version = f"{k8s_version.get('major')}.{k8s_version.get('minor')}" + assert k8s_target_result["evaluations"][0]["value"] == expected_version + assert k8s_status == expected_status(expected_version >= "1.20") + assert k8s_result["status"] == k8s_target_result["status"] == k8s_status + + # cluster node evaluation + assert node_result["description"] == "Evaluate cluster nodes" + assert node_result["name"] == "evalClusterNodes" + kubectl_nodes = run("kubectl get nodes -o json")["items"] + + # num nodes + assert "cluster/nodes" in node_result["targets"] + assert "_all_" in node_result["targets"]["cluster/nodes"] + node_count_target = node_result["targets"]["cluster/nodes"]["_all_"] + assert node_count_target["conditions"] == ["len(cluster/nodes)>=1"] + assert node_count_target["evaluations"][0]["value"] == len(kubectl_nodes) + final_status = expected_status( + success_or_fail=len(kubectl_nodes) >= 1, + success_or_warning=len(kubectl_nodes) == 1 + ) + assert node_count_target["evaluations"][0]["status"] == node_count_target["status"] == final_status + + # node eval + for node in kubectl_nodes: + node_name = node["metadata"]["name"] + node_key = f"cluster/nodes/{node_name}" + assert node_key in node_result["targets"] + assert "_all_" in node_result["targets"][node_key] + node_target = node_result["targets"][node_key]["_all_"] + + assert node_target["conditions"] == [ + f"info.architecture in ({','.join(AIO_SUPPORTED_ARCHITECTURES)})", + f"condition.cpu>={MIN_NODE_VCPU}", + f"condition.memory>={MIN_NODE_MEMORY}", + f"condition.ephemeral-storage>={MIN_NODE_STORAGE}" + ] + + node_arch = node_target["evaluations"][0]["value"]["info.architecture"] + assert node_arch == node["status"]["nodeInfo"]["architecture"] + assert node_target["evaluations"][0]["status"] == expected_status( + node_arch in AIO_SUPPORTED_ARCHITECTURES + ) + + node_capacity = node["status"]["capacity"] + node_cpu = node_target["evaluations"][1]["value"]["condition.cpu"] + assert node_cpu == int(node_capacity["cpu"]) + assert node_target["evaluations"][1]["status"] == expected_status( + node_cpu >= int(MIN_NODE_VCPU) + ) + + node_memory = node_target["evaluations"][2]["value"]["condition.memory"] + assert node_memory == int(parse_quantity(node_capacity["memory"])) + assert node_target["evaluations"][2]["status"] == expected_status( + node_memory >= parse_quantity(MIN_NODE_MEMORY) + ) + + node_storage = node_target["evaluations"][3]["value"]["condition.ephemeral-storage"] + assert node_storage == int(parse_quantity(node_capacity["ephemeral-storage"])) + assert node_target["evaluations"][3]["status"] == expected_status( + node_storage >= parse_quantity(MIN_NODE_STORAGE) + ) + + node_status = combine_statuses([cond["status"] for cond in node_target["evaluations"]]) + assert node_status == node_target["status"] + final_status = combine_statuses([final_status, node_status]) + + assert final_status == node_result["status"] From b4615bff6a95c4ad81905bcd645872a7e927aa2e Mon Sep 17 00:00:00 2001 From: Elsie4ever <3467996@gmail.com> Date: Thu, 20 Jun 2024 11:26:12 -0700 Subject: [PATCH 2/4] fix: error when expected CRD is not deployed instead of skip (#241) --- azext_edge/edge/providers/check/base/resource.py | 3 +-- azext_edge/edge/providers/checks.py | 7 ++++--- azext_edge/tests/edge/checks/int/test_pre_post_int.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/azext_edge/edge/providers/check/base/resource.py b/azext_edge/edge/providers/check/base/resource.py index 0a1df5d48..ad4ffe3e3 100644 --- a/azext_edge/edge/providers/check/base/resource.py +++ b/azext_edge/edge/providers/check/base/resource.py @@ -49,10 +49,9 @@ def enumerate_ops_service_resources( ) if not api_resources: - check_manager.add_target_eval(target_name=target_api, status=CheckTaskStatus.skipped.value) + check_manager.add_target_eval(target_name=target_api, status=CheckTaskStatus.error.value) missing_api_text = ( f"[bright_blue]{target_api}[/bright_blue] API resources [red]not[/red] detected." - "\n\n[bright_white]Skipping deployment evaluation[/bright_white]." ) check_manager.add_display(target_name=target_api, display=Padding(missing_api_text, (0, 0, 0, 8))) return check_manager.as_dict(as_list), resource_kind_map diff --git a/azext_edge/edge/providers/checks.py b/azext_edge/edge/providers/checks.py index 2ab0db0eb..d6015b4f0 100644 --- a/azext_edge/edge/providers/checks.py +++ b/azext_edge/edge/providers/checks.py @@ -11,7 +11,7 @@ from ..common import ListableEnum, OpsServiceType from .check.base import check_pre_deployment, display_as_list -from .check.common import ResourceOutputDetailLevel +from .check.common import COLOR_STR_FORMAT, ResourceOutputDetailLevel from .check.dataprocessor import check_dataprocessor_deployment from .check.deviceregistry import check_deviceregistry_deployment from .check.lnm import check_lnm_deployment @@ -48,10 +48,11 @@ def run_checks( sleep(0.5) + color = COLOR_STR_FORMAT.format(color="bright_blue", value="{text}") if as_list else "{text}" title_subject = ( - f"{{[bright_blue]{ops_service}[/bright_blue]}} service deployment" + f"{{{color.format(text=ops_service)}}} service deployment" if post_deployment - else "[bright_blue]IoT Operations readiness[/bright_blue]" + else color.format(text="IoT Operations readiness") ) result["title"] = f"Evaluation for {title_subject}" diff --git a/azext_edge/tests/edge/checks/int/test_pre_post_int.py b/azext_edge/tests/edge/checks/int/test_pre_post_int.py index 0941d19dc..7b7700687 100644 --- a/azext_edge/tests/edge/checks/int/test_pre_post_int.py +++ b/azext_edge/tests/edge/checks/int/test_pre_post_int.py @@ -31,8 +31,8 @@ def test_check_pre_post(init_setup, post, pre): result = run(command) # default service title - expected_title = "Evaluation for {[bright_blue]mq[/bright_blue]} service deployment" - expected_precheck_title = "[bright_blue]IoT Operations readiness[/bright_blue]" + expected_title = "Evaluation for {mq} service deployment" + expected_precheck_title = "IoT Operations readiness" expected_pre = not post if pre is None else pre expected_post = not pre if post is None else post assert result["title"] == expected_title if expected_post else expected_precheck_title From d4d0babcafec151868dda8f79a4b2e4293d1aa6b Mon Sep 17 00:00:00 2001 From: Elsie4ever <3467996@gmail.com> Date: Mon, 24 Jun 2024 14:14:35 -0700 Subject: [PATCH 3/4] revert: error when expected CRD is not deployed instead of skip (#243) Revert "fix: error when expected CRD is not deployed instead of skip (#241)" This reverts commit b4615bff6a95c4ad81905bcd645872a7e927aa2e. --- azext_edge/edge/providers/check/base/resource.py | 3 ++- azext_edge/edge/providers/checks.py | 7 +++---- azext_edge/tests/edge/checks/int/test_pre_post_int.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/azext_edge/edge/providers/check/base/resource.py b/azext_edge/edge/providers/check/base/resource.py index ad4ffe3e3..0a1df5d48 100644 --- a/azext_edge/edge/providers/check/base/resource.py +++ b/azext_edge/edge/providers/check/base/resource.py @@ -49,9 +49,10 @@ def enumerate_ops_service_resources( ) if not api_resources: - check_manager.add_target_eval(target_name=target_api, status=CheckTaskStatus.error.value) + check_manager.add_target_eval(target_name=target_api, status=CheckTaskStatus.skipped.value) missing_api_text = ( f"[bright_blue]{target_api}[/bright_blue] API resources [red]not[/red] detected." + "\n\n[bright_white]Skipping deployment evaluation[/bright_white]." ) check_manager.add_display(target_name=target_api, display=Padding(missing_api_text, (0, 0, 0, 8))) return check_manager.as_dict(as_list), resource_kind_map diff --git a/azext_edge/edge/providers/checks.py b/azext_edge/edge/providers/checks.py index d6015b4f0..2ab0db0eb 100644 --- a/azext_edge/edge/providers/checks.py +++ b/azext_edge/edge/providers/checks.py @@ -11,7 +11,7 @@ from ..common import ListableEnum, OpsServiceType from .check.base import check_pre_deployment, display_as_list -from .check.common import COLOR_STR_FORMAT, ResourceOutputDetailLevel +from .check.common import ResourceOutputDetailLevel from .check.dataprocessor import check_dataprocessor_deployment from .check.deviceregistry import check_deviceregistry_deployment from .check.lnm import check_lnm_deployment @@ -48,11 +48,10 @@ def run_checks( sleep(0.5) - color = COLOR_STR_FORMAT.format(color="bright_blue", value="{text}") if as_list else "{text}" title_subject = ( - f"{{{color.format(text=ops_service)}}} service deployment" + f"{{[bright_blue]{ops_service}[/bright_blue]}} service deployment" if post_deployment - else color.format(text="IoT Operations readiness") + else "[bright_blue]IoT Operations readiness[/bright_blue]" ) result["title"] = f"Evaluation for {title_subject}" diff --git a/azext_edge/tests/edge/checks/int/test_pre_post_int.py b/azext_edge/tests/edge/checks/int/test_pre_post_int.py index 7b7700687..0941d19dc 100644 --- a/azext_edge/tests/edge/checks/int/test_pre_post_int.py +++ b/azext_edge/tests/edge/checks/int/test_pre_post_int.py @@ -31,8 +31,8 @@ def test_check_pre_post(init_setup, post, pre): result = run(command) # default service title - expected_title = "Evaluation for {mq} service deployment" - expected_precheck_title = "IoT Operations readiness" + expected_title = "Evaluation for {[bright_blue]mq[/bright_blue]} service deployment" + expected_precheck_title = "[bright_blue]IoT Operations readiness[/bright_blue]" expected_pre = not post if pre is None else pre expected_post = not pre if post is None else post assert result["title"] == expected_title if expected_post else expected_precheck_title From 63b408507965916822703dc61ac8c88cc9c26b5e Mon Sep 17 00:00:00 2001 From: Ryan K Date: Thu, 27 Jun 2024 11:27:52 -0700 Subject: [PATCH 4/4] ci: enable ci workflow on all branches (#249) Co-authored-by: Ryan Kelly --- .github/workflows/ci_workflow.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index 61394fb0f..4fb0428f1 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -3,11 +3,7 @@ permissions: contents: read on: pull_request: - branches: - - dev push: - branches: - - dev workflow_dispatch: jobs: build: