blob: 92c4602f66ce1f483d5bc10a7f4df020941da359 [file] [log] [blame]
garciadeblas96b94f52024-07-08 16:18:21 +02001#######################################################################################
2# Copyright ETSI Contributors and Others.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#######################################################################################
17
18
19import asyncio
garciadeblas40811852024-10-22 11:35:17 +020020from math import ceil
garciadeblas7cf480d2025-01-27 16:53:45 +010021from jsonpath_ng.ext import parse
garciadeblas96b94f52024-07-08 16:18:21 +020022
23
garciadeblasc89134b2025-02-05 16:36:17 +010024async def check_workflow_status(self, op_id, workflow_name):
25 self.logger.info(f"Op {op_id}, check_workflow_status Enter: {workflow_name}")
garciadeblasadb81e82024-11-08 01:11:46 +010026 if not workflow_name:
27 return False, "Workflow was not launched"
garciadeblasb33813b2024-10-22 11:56:37 +020028 try:
garciadeblas26d733c2025-02-03 16:12:43 +010029 # First check if the workflow ends successfully
30 completed, message = await self.readiness_loop(
garciadeblasc89134b2025-02-05 16:36:17 +010031 op_id,
garciadeblasb33813b2024-10-22 11:56:37 +020032 item="workflow",
garciadeblas96b94f52024-07-08 16:18:21 +020033 name=workflow_name,
garciadeblasb33813b2024-10-22 11:56:37 +020034 namespace="osm-workflows",
garciadeblas7cf480d2025-01-27 16:53:45 +010035 condition={
36 "jsonpath_filter": "status.conditions[?(@.type=='Completed')].status",
37 "value": "True",
38 },
garciadeblasad6d1ba2025-01-22 16:02:18 +010039 deleted=False,
garciadeblasb33813b2024-10-22 11:56:37 +020040 timeout=300,
garciadeblas96b94f52024-07-08 16:18:21 +020041 )
garciadeblas26d733c2025-02-03 16:12:43 +010042 if completed:
43 # Then check if the workflow has a failed task
44 return await self.readiness_loop(
garciadeblasc89134b2025-02-05 16:36:17 +010045 op_id,
garciadeblas26d733c2025-02-03 16:12:43 +010046 item="workflow",
47 name=workflow_name,
48 namespace="osm-workflows",
49 condition={
50 "jsonpath_filter": "status.phase",
51 "value": "Succeeded",
52 },
53 deleted=False,
54 timeout=0,
55 )
56 else:
57 return False, f"Workflow was not completed: {message}"
garciadeblasb33813b2024-10-22 11:56:37 +020058 except Exception as e:
garciadeblas26d733c2025-02-03 16:12:43 +010059 return False, f"Workflow could not be completed. Unexpected exception: {e}"
garciadeblas40811852024-10-22 11:35:17 +020060
61
garciadeblasad6d1ba2025-01-22 16:02:18 +010062async def readiness_loop(
garciadeblas6d8acf32025-02-06 13:34:37 +010063 self, op_id, item, name, namespace, condition, deleted, timeout, kubectl_obj=None
garciadeblasad6d1ba2025-01-22 16:02:18 +010064):
garciadeblas6d8acf32025-02-06 13:34:37 +010065 if kubectl_obj is None:
66 kubectl_obj = self._kubectl
67 self.logger.info("readiness_loop Enter")
garciadeblas40811852024-10-22 11:35:17 +020068 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +010069 f"Op {op_id}. {item} {name}. Namespace: '{namespace}'. Condition: {condition}. Deleted: {deleted}. Timeout: {timeout}"
garciadeblas40811852024-10-22 11:35:17 +020070 )
71 item_api_map = {
garciadeblasb33813b2024-10-22 11:56:37 +020072 "workflow": {
73 "api_group": "argoproj.io",
74 "api_plural": "workflows",
75 "api_version": "v1alpha1",
76 },
garciadeblas40811852024-10-22 11:35:17 +020077 "kustomization": {
78 "api_group": "kustomize.toolkit.fluxcd.io",
79 "api_plural": "kustomizations",
80 "api_version": "v1",
81 },
82 "cluster_aws": {
83 "api_group": "eks.aws.upbound.io",
84 "api_plural": "clusters",
85 "api_version": "v1beta1",
86 },
87 "cluster_azure": {
88 "api_group": "containerservice.azure.upbound.io",
89 "api_plural": "kubernetesclusters",
90 "api_version": "v1beta1",
91 },
92 "cluster_gcp": {
93 "api_group": "container.gcp.upbound.io",
94 "api_plural": "clusters",
95 "api_version": "v1beta2",
96 },
garciadeblas1ca09852025-05-30 11:19:06 +020097 "nodegroup_aws": {
garciadeblasceaa19d2024-10-24 12:52:11 +020098 "api_group": "eks.aws.upbound.io",
99 "api_plural": "nodegroups",
100 "api_version": "v1beta1",
101 },
garciadeblas1ca09852025-05-30 11:19:06 +0200102 "nodegroup_gcp": {
garciadeblasceaa19d2024-10-24 12:52:11 +0200103 "api_group": "container.gcp.upbound.io",
104 "api_plural": "nodepools",
105 "api_version": "v1beta2",
106 },
garciadeblas40811852024-10-22 11:35:17 +0200107 }
108 counter = 1
109 retry_time = self._odu_checkloop_retry_time
110 max_iterations = ceil(timeout / retry_time)
garciadeblas26d733c2025-02-03 16:12:43 +0100111 if max_iterations < 1:
112 max_iterations = 1
garciadeblasb33813b2024-10-22 11:56:37 +0200113 api_group = item_api_map[item]["api_group"]
114 api_plural = item_api_map[item]["api_plural"]
115 api_version = item_api_map[item]["api_version"]
garciadeblas40811852024-10-22 11:35:17 +0200116
117 while counter <= max_iterations:
garciadeblasc89134b2025-02-05 16:36:17 +0100118 iteration_prefix = f"Op {op_id}. Iteration {counter}/{max_iterations}"
garciadeblasad6d1ba2025-01-22 16:02:18 +0100119 try:
garciadeblasc89134b2025-02-05 16:36:17 +0100120 self.logger.info(f"Op {op_id}. Iteration {counter}/{max_iterations}")
garciadeblas6d8acf32025-02-06 13:34:37 +0100121 generic_object = await kubectl_obj.get_generic_object(
garciadeblasad6d1ba2025-01-22 16:02:18 +0100122 api_group=api_group,
123 api_plural=api_plural,
124 api_version=api_version,
125 namespace=namespace,
126 name=name,
garciadeblasceaa19d2024-10-24 12:52:11 +0200127 )
garciadeblasad6d1ba2025-01-22 16:02:18 +0100128 if deleted:
129 if generic_object:
130 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +0100131 f"{iteration_prefix}. Found {api_plural}. Name: {name}. Namespace: '{namespace}'. API: {api_group}/{api_version}"
garciadeblasad6d1ba2025-01-22 16:02:18 +0100132 )
133 else:
134 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +0100135 f"{iteration_prefix}. {item} {name} deleted after {counter} iterations (aprox {counter*retry_time} seconds)"
garciadeblasad6d1ba2025-01-22 16:02:18 +0100136 )
137 return True, "COMPLETED"
138 else:
garciadeblas7cf480d2025-01-27 16:53:45 +0100139 if not condition:
garciadeblasad6d1ba2025-01-22 16:02:18 +0100140 return True, "Nothing to check"
141 if generic_object:
garciadeblasae238482025-02-03 08:44:19 +0100142 # If there is object, conditions must be checked
garciadeblasad6d1ba2025-01-22 16:02:18 +0100143 # self.logger.debug(f"{yaml.safe_dump(generic_object)}")
144 conditions = generic_object.get("status", {}).get("conditions", [])
garciadeblasae238482025-02-03 08:44:19 +0100145 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +0100146 f"{iteration_prefix}. Object found: {item} status conditions: {conditions}"
garciadeblasae238482025-02-03 08:44:19 +0100147 )
148 jsonpath_expr = parse(condition["jsonpath_filter"])
149 match = jsonpath_expr.find(generic_object)
150 if match:
garciadeblas65cd9892025-02-08 10:42:08 +0100151 value = str(match[0].value)
garciadeblasae238482025-02-03 08:44:19 +0100152 condition_function = condition.get(
153 "function", lambda x, y: x == y
154 )
155 if condition_function(condition["value"], value):
156 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +0100157 f"{iteration_prefix}. {item} {name} met the condition {condition} with {value} in {counter} iterations (aprox {counter*retry_time} seconds)"
garciadeblasae238482025-02-03 08:44:19 +0100158 )
159 return True, "COMPLETED"
160 else:
161 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +0100162 f"{iteration_prefix}. {item} {name} did not meet the condition {condition} with value {value}"
garciadeblasae238482025-02-03 08:44:19 +0100163 )
164 else:
165 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +0100166 f"{iteration_prefix}. No match for filter {condition.get('jsonpath_filter', '-')} in {item} {name}"
garciadeblasae238482025-02-03 08:44:19 +0100167 )
garciadeblasad6d1ba2025-01-22 16:02:18 +0100168 else:
169 self.logger.info(
garciadeblasc89134b2025-02-05 16:36:17 +0100170 f"{iteration_prefix}. Could not find {api_plural}. Name: {name}. Namespace: '{namespace}'. API: {api_group}/{api_version}"
garciadeblasad6d1ba2025-01-22 16:02:18 +0100171 )
garciadeblasad6d1ba2025-01-22 16:02:18 +0100172 except Exception as e:
173 self.logger.error(f"Exception: {e}")
garciadeblas26d733c2025-02-03 16:12:43 +0100174 if counter < max_iterations:
175 await asyncio.sleep(retry_time)
garciadeblas40811852024-10-22 11:35:17 +0200176 counter += 1
177 return (
178 False,
garciadeblasc89134b2025-02-05 16:36:17 +0100179 f"Op {op_id}. {item} {name} was not ready after {max_iterations} iterations (aprox {timeout} seconds)",
garciadeblas40811852024-10-22 11:35:17 +0200180 )