blob: 84a2b9e97356259e8675a512fba98e1cd0d38735 [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
garciadeblas6c82c352025-01-27 16:53:45 +010021from jsonpath_ng.ext import parse
garciadeblas96b94f52024-07-08 16:18:21 +020022
23
24async def check_workflow_status(self, workflow_name):
garciadeblas40811852024-10-22 11:35:17 +020025 self.logger.info(f"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:
garciadeblas891f2002025-02-03 16:12:43 +010029 # First check if the workflow ends successfully
30 completed, message = await self.readiness_loop(
garciadeblasb33813b2024-10-22 11:56:37 +020031 item="workflow",
garciadeblas96b94f52024-07-08 16:18:21 +020032 name=workflow_name,
garciadeblasb33813b2024-10-22 11:56:37 +020033 namespace="osm-workflows",
garciadeblas6c82c352025-01-27 16:53:45 +010034 condition={
35 "jsonpath_filter": "status.conditions[?(@.type=='Completed')].status",
36 "value": "True",
37 },
garciadeblasbc96f382025-01-22 16:02:18 +010038 deleted=False,
garciadeblasb33813b2024-10-22 11:56:37 +020039 timeout=300,
garciadeblas96b94f52024-07-08 16:18:21 +020040 )
garciadeblas891f2002025-02-03 16:12:43 +010041 if completed:
42 # Then check if the workflow has a failed task
43 return await self.readiness_loop(
44 item="workflow",
45 name=workflow_name,
46 namespace="osm-workflows",
47 condition={
48 "jsonpath_filter": "status.phase",
49 "value": "Succeeded",
50 },
51 deleted=False,
52 timeout=0,
53 )
54 else:
55 return False, f"Workflow was not completed: {message}"
garciadeblasb33813b2024-10-22 11:56:37 +020056 except Exception as e:
garciadeblas891f2002025-02-03 16:12:43 +010057 return False, f"Workflow could not be completed. Unexpected exception: {e}"
garciadeblas40811852024-10-22 11:35:17 +020058
59
garciadeblasbc96f382025-01-22 16:02:18 +010060async def readiness_loop(
garciadeblas6c82c352025-01-27 16:53:45 +010061 self, item, name, namespace, condition, deleted, timeout, kubectl=None
garciadeblasbc96f382025-01-22 16:02:18 +010062):
63 if kubectl is None:
64 kubectl = self._kubectl
garciadeblas40811852024-10-22 11:35:17 +020065 self.logger.info("readiness_loop Enter")
66 self.logger.info(
garciadeblas6c82c352025-01-27 16:53:45 +010067 f"{item} {name}. Namespace: '{namespace}'. Condition: {condition}. Deleted: {deleted}. Timeout: {timeout}"
garciadeblas40811852024-10-22 11:35:17 +020068 )
69 item_api_map = {
garciadeblasb33813b2024-10-22 11:56:37 +020070 "workflow": {
71 "api_group": "argoproj.io",
72 "api_plural": "workflows",
73 "api_version": "v1alpha1",
74 },
garciadeblas40811852024-10-22 11:35:17 +020075 "kustomization": {
76 "api_group": "kustomize.toolkit.fluxcd.io",
77 "api_plural": "kustomizations",
78 "api_version": "v1",
79 },
80 "cluster_aws": {
81 "api_group": "eks.aws.upbound.io",
82 "api_plural": "clusters",
83 "api_version": "v1beta1",
84 },
85 "cluster_azure": {
86 "api_group": "containerservice.azure.upbound.io",
87 "api_plural": "kubernetesclusters",
88 "api_version": "v1beta1",
89 },
90 "cluster_gcp": {
91 "api_group": "container.gcp.upbound.io",
92 "api_plural": "clusters",
93 "api_version": "v1beta2",
94 },
garciadeblasceaa19d2024-10-24 12:52:11 +020095 "nodepool_aws": {
96 "api_group": "eks.aws.upbound.io",
97 "api_plural": "nodegroups",
98 "api_version": "v1beta1",
99 },
100 "nodepool_gcp": {
101 "api_group": "container.gcp.upbound.io",
102 "api_plural": "nodepools",
103 "api_version": "v1beta2",
104 },
garciadeblas40811852024-10-22 11:35:17 +0200105 }
106 counter = 1
107 retry_time = self._odu_checkloop_retry_time
108 max_iterations = ceil(timeout / retry_time)
garciadeblas891f2002025-02-03 16:12:43 +0100109 if max_iterations < 1:
110 max_iterations = 1
garciadeblasb33813b2024-10-22 11:56:37 +0200111 api_group = item_api_map[item]["api_group"]
112 api_plural = item_api_map[item]["api_plural"]
113 api_version = item_api_map[item]["api_version"]
garciadeblas40811852024-10-22 11:35:17 +0200114
115 while counter <= max_iterations:
garciadeblasbc96f382025-01-22 16:02:18 +0100116 try:
117 self.logger.info(f"Iteration {counter}/{max_iterations}")
118 generic_object = await kubectl.get_generic_object(
119 api_group=api_group,
120 api_plural=api_plural,
121 api_version=api_version,
122 namespace=namespace,
123 name=name,
garciadeblasceaa19d2024-10-24 12:52:11 +0200124 )
garciadeblasbc96f382025-01-22 16:02:18 +0100125 if deleted:
126 if generic_object:
127 self.logger.info(
garciadeblase3462922025-02-03 08:44:19 +0100128 f"Iteration {counter}/{max_iterations}: Found {api_plural}. Name: {name}. Namespace: '{namespace}'. API: {api_group}/{api_version}"
garciadeblasbc96f382025-01-22 16:02:18 +0100129 )
130 else:
131 self.logger.info(
132 f"{item} {name} deleted after {counter} iterations (aprox {counter*retry_time} seconds)"
133 )
134 return True, "COMPLETED"
135 else:
garciadeblas6c82c352025-01-27 16:53:45 +0100136 if not condition:
garciadeblasbc96f382025-01-22 16:02:18 +0100137 return True, "Nothing to check"
138 if generic_object:
garciadeblase3462922025-02-03 08:44:19 +0100139 # If there is object, conditions must be checked
garciadeblasbc96f382025-01-22 16:02:18 +0100140 # self.logger.debug(f"{yaml.safe_dump(generic_object)}")
141 conditions = generic_object.get("status", {}).get("conditions", [])
garciadeblase3462922025-02-03 08:44:19 +0100142 self.logger.info(
143 f"Iteration {counter}/{max_iterations}. Object found: {item} status conditions: {conditions}"
144 )
145 jsonpath_expr = parse(condition["jsonpath_filter"])
146 match = jsonpath_expr.find(generic_object)
147 if match:
148 value = match[0].value
149 condition_function = condition.get(
150 "function", lambda x, y: x == y
151 )
152 if condition_function(condition["value"], value):
153 self.logger.info(
154 f"{item} {name} met the condition {condition} with {value} in {counter} iterations (aprox {counter*retry_time} seconds)"
155 )
156 return True, "COMPLETED"
157 else:
158 self.logger.info(
159 f"Iteration {counter}/{max_iterations}: {item} {name} did not meet the condition {condition} with value {value}"
160 )
161 else:
162 self.logger.info(
163 f"Iteration {counter}/{max_iterations}. No match for filter {condition.get('jsonpath_filter', '-')} in {item} {name}"
164 )
garciadeblasbc96f382025-01-22 16:02:18 +0100165 else:
166 self.logger.info(
garciadeblase3462922025-02-03 08:44:19 +0100167 f"Iteration {counter}/{max_iterations}: Could not find {api_plural}. Name: {name}. Namespace: '{namespace}'. API: {api_group}/{api_version}"
garciadeblasbc96f382025-01-22 16:02:18 +0100168 )
garciadeblasbc96f382025-01-22 16:02:18 +0100169 except Exception as e:
170 self.logger.error(f"Exception: {e}")
garciadeblas891f2002025-02-03 16:12:43 +0100171 if counter < max_iterations:
172 await asyncio.sleep(retry_time)
garciadeblas40811852024-10-22 11:35:17 +0200173 counter += 1
174 return (
175 False,
garciadeblase3462922025-02-03 08:44:19 +0100176 f"{item} {name} was not ready after {max_iterations} iterations (aprox {timeout} seconds)",
garciadeblas40811852024-10-22 11:35:17 +0200177 )