pyxecm 1.6__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +6 -4
- pyxecm/avts.py +673 -246
- pyxecm/coreshare.py +686 -467
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +914 -0
- pyxecm/customizer/api/auth.py +154 -0
- pyxecm/customizer/api/metrics.py +92 -0
- pyxecm/customizer/api/models.py +13 -0
- pyxecm/customizer/api/payload_list.py +865 -0
- pyxecm/customizer/api/settings.py +103 -0
- pyxecm/customizer/browser_automation.py +332 -139
- pyxecm/customizer/customizer.py +1007 -1130
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +322 -0
- pyxecm/customizer/k8s.py +713 -378
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +2867 -909
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +16817 -7467
- pyxecm/customizer/pht.py +699 -285
- pyxecm/customizer/salesforce.py +516 -342
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +593 -371
- pyxecm/customizer/settings.py +442 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +83 -43
- pyxecm/helper/data.py +2406 -870
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +527 -171
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +234 -140
- pyxecm/otawp.py +1436 -557
- pyxecm/otcs.py +7716 -3161
- pyxecm/otds.py +2150 -919
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1272 -325
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.0.dist-info/METADATA +145 -0
- pyxecm-2.0.0.dist-info/RECORD +54 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
- pyxecm-1.6.dist-info/METADATA +0 -53
- pyxecm-1.6.dist-info/RECORD +0 -32
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
pyxecm/customizer/k8s.py
CHANGED
|
@@ -1,66 +1,61 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""Kubernetes Module to implement functions to read / write Kubernetes objects.
|
|
2
|
+
|
|
3
|
+
This includes as Pods, Stateful Sets, Config Maps, ...
|
|
4
4
|
|
|
5
|
-
https://github.com/kubernetes-client/python
|
|
5
|
+
https://github.com/kubernetes-client/python
|
|
6
6
|
https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md
|
|
7
7
|
https://github.com/kubernetes-client/python/tree/master/examples
|
|
8
|
-
|
|
9
|
-
Class: K8s
|
|
10
|
-
Methods:
|
|
11
|
-
|
|
12
|
-
__init__ : class initializer
|
|
13
|
-
get_core_v1_api: Get Kubernetes API object for Core APIs
|
|
14
|
-
get_apps_v1_api: Get Kubernetes API object for Applications (e.g. Stateful Sets)
|
|
15
|
-
get_networking_v1_api: Get Kubernetes API object for Networking (e.g. Ingress)
|
|
16
|
-
get_namespace: Get the Kubernetes namespace the K8s object is configured for
|
|
17
|
-
|
|
18
|
-
get_pod: Get a Kubernetes Pod based on its name
|
|
19
|
-
list_pods: Get a list of Kubernetes pods based on field and label selectors
|
|
20
|
-
exec_pod_command: Execute a list of commands in a Kubernetes Pod
|
|
21
|
-
exec_pod_command_interactive: Write commands to stdin and wait for response
|
|
22
|
-
delete_pod: Delete a running pod (e.g. to make Kubernetes restart it)
|
|
23
|
-
|
|
24
|
-
get_config_map: Get a Kubernetes Config Map based on its name
|
|
25
|
-
list_config_maps: Get a list of Kubernetes Config Maps based on field and label selectors
|
|
26
|
-
find_config_map: Find a Kubernetes Config Map based on its name
|
|
27
|
-
replace_config_map: Replace the data body of a Kubernetes Config Map
|
|
28
|
-
|
|
29
|
-
get_stateful_set: Gets a Kubernetes Stateful Set based on its name
|
|
30
|
-
get_stateful_set_scale: Gets the number of replicas for a Kubernetes Stateful Set
|
|
31
|
-
patch_stateful_set: Updates the specification of a Kubernetes Stateful Set
|
|
32
|
-
scale_stateful_set: Changes number of replicas for a Kubernetes Stateful Set
|
|
33
|
-
|
|
34
|
-
get_service: Get a Kubernetes Service based on its name
|
|
35
|
-
list_services: Get a list of Kubernetes Services based on field and label selectors
|
|
36
|
-
patch_service: Update the specification of a Kubernetes Service
|
|
37
|
-
|
|
38
|
-
get_ingress: Get a Kubernetes Ingress based on its name
|
|
39
|
-
patch_ingress: Update the specification of a Kubernetes Ingress
|
|
40
|
-
update_ingress_backend_services: Replace the backend service and port for an ingress host
|
|
41
|
-
|
|
42
8
|
"""
|
|
43
9
|
|
|
44
10
|
__author__ = "Dr. Marc Diefenbruch"
|
|
45
|
-
__copyright__ = "Copyright 2024, OpenText"
|
|
11
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
46
12
|
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
47
13
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
48
14
|
__email__ = "mdiefenb@opentext.com"
|
|
49
15
|
|
|
50
16
|
import logging
|
|
17
|
+
import os
|
|
51
18
|
import time
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
|
|
52
21
|
from kubernetes import client, config
|
|
53
|
-
from kubernetes.
|
|
22
|
+
from kubernetes.client import (
|
|
23
|
+
AppsV1Api,
|
|
24
|
+
CoreV1Api,
|
|
25
|
+
NetworkingV1Api,
|
|
26
|
+
V1ConfigMap,
|
|
27
|
+
V1ConfigMapList,
|
|
28
|
+
V1Ingress,
|
|
29
|
+
V1Pod,
|
|
30
|
+
V1PodList,
|
|
31
|
+
V1Scale,
|
|
32
|
+
V1Service,
|
|
33
|
+
V1StatefulSet,
|
|
34
|
+
)
|
|
54
35
|
from kubernetes.client.exceptions import ApiException
|
|
36
|
+
from kubernetes.config.config_exception import ConfigException
|
|
37
|
+
from kubernetes.stream import stream
|
|
55
38
|
|
|
56
|
-
|
|
57
|
-
# config.load_incluster_config()
|
|
58
|
-
|
|
59
|
-
logger = logging.getLogger("pyxecm.customizer.k8s")
|
|
39
|
+
default_logger = logging.getLogger("pyxecm.customizer.k8s")
|
|
60
40
|
|
|
61
41
|
|
|
62
42
|
class K8s:
|
|
63
|
-
"""
|
|
43
|
+
"""Provides an interface to interact with the Kubernetes API.
|
|
44
|
+
|
|
45
|
+
This class can run both in-cluster and locally using kubeconfig.
|
|
46
|
+
It offers methods to interact with Kubernetes namespaces, pods,
|
|
47
|
+
and various API objects like CoreV1, AppsV1, and NetworkingV1.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
logger (logging.Logger): Logger for the class.
|
|
51
|
+
_core_v1_api (CoreV1Api): API client for Kubernetes Core V1.
|
|
52
|
+
_apps_v1_api (AppsV1Api): API client for Kubernetes Apps V1.
|
|
53
|
+
_networking_v1_api (NetworkingV1Api): API client for Kubernetes Networking V1.
|
|
54
|
+
_namespace (str): The namespace in which operations are performed.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
logger: logging.Logger = default_logger
|
|
64
59
|
|
|
65
60
|
_core_v1_api = None
|
|
66
61
|
_apps_v1_api = None
|
|
@@ -69,76 +64,127 @@ class K8s:
|
|
|
69
64
|
|
|
70
65
|
def __init__(
|
|
71
66
|
self,
|
|
72
|
-
|
|
73
|
-
kubeconfig_file: str = "~/.kube/config",
|
|
67
|
+
kubeconfig_file: str | None = None,
|
|
74
68
|
namespace: str = "default",
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
logger: logging.Logger = default_logger,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Initialize the Kubernetes object.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
kubeconfig_file (str | None, optional):
|
|
75
|
+
Path to a kubeconfig file. Defaults to None.
|
|
76
|
+
namespace (str, optional):
|
|
77
|
+
The Kubernetes name space. Defaults to "default".
|
|
78
|
+
logger (logging.Logger, optional):
|
|
79
|
+
The logger object. Defaults to default_logger.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
if logger != default_logger:
|
|
84
|
+
self.logger = logger.getChild("k8s")
|
|
85
|
+
for logfilter in logger.filters:
|
|
86
|
+
self.logger.addFilter(logfilter)
|
|
77
87
|
|
|
78
88
|
# Configure Kubernetes API authentication to use pod serviceAccount
|
|
79
|
-
|
|
89
|
+
|
|
90
|
+
try:
|
|
80
91
|
config.load_incluster_config()
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
configured = True
|
|
93
|
+
except ConfigException:
|
|
94
|
+
configured = False
|
|
95
|
+
self.logger.info("Failed to load in-cluster config")
|
|
96
|
+
|
|
97
|
+
if kubeconfig_file is None:
|
|
98
|
+
kubeconfig_file = os.getenv(
|
|
99
|
+
"KUBECONFIG",
|
|
100
|
+
os.path.expanduser("~/.kube/config"),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if not configured:
|
|
104
|
+
try:
|
|
83
105
|
config.load_kube_config(config_file=kubeconfig_file)
|
|
84
|
-
|
|
85
|
-
logger.
|
|
86
|
-
"
|
|
106
|
+
except ConfigException:
|
|
107
|
+
self.logger.info(
|
|
108
|
+
"Failed to load kubernetes config with file -> '%s'",
|
|
109
|
+
kubeconfig_file,
|
|
87
110
|
)
|
|
88
111
|
|
|
89
|
-
self._core_v1_api =
|
|
90
|
-
self._apps_v1_api =
|
|
91
|
-
self._networking_v1_api =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else:
|
|
112
|
+
self._core_v1_api = CoreV1Api()
|
|
113
|
+
self._apps_v1_api = AppsV1Api()
|
|
114
|
+
self._networking_v1_api = NetworkingV1Api()
|
|
115
|
+
|
|
116
|
+
if namespace == "default":
|
|
95
117
|
# Read current namespace
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
try:
|
|
119
|
+
with open(
|
|
120
|
+
"/var/run/secrets/kubernetes.io/serviceaccount/namespace",
|
|
121
|
+
encoding="utf-8",
|
|
122
|
+
) as namespace_file:
|
|
123
|
+
self._namespace = namespace_file.read()
|
|
124
|
+
except FileNotFoundError:
|
|
125
|
+
self._namespace = namespace
|
|
126
|
+
else:
|
|
127
|
+
self._namespace = namespace
|
|
128
|
+
|
|
129
|
+
# end method definition
|
|
102
130
|
|
|
103
|
-
def get_core_v1_api(self):
|
|
104
|
-
"""
|
|
131
|
+
def get_core_v1_api(self) -> CoreV1Api:
|
|
132
|
+
"""Return Kubernetes Core V1 API object.
|
|
105
133
|
|
|
106
134
|
Returns:
|
|
107
135
|
object: Kubernetes API object
|
|
136
|
+
|
|
108
137
|
"""
|
|
138
|
+
|
|
109
139
|
return self._core_v1_api
|
|
110
140
|
|
|
111
|
-
|
|
112
|
-
|
|
141
|
+
# end method definition
|
|
142
|
+
|
|
143
|
+
def get_apps_v1_api(self) -> AppsV1Api:
|
|
144
|
+
"""Return Kubernetes Apps V1 API object.
|
|
113
145
|
|
|
114
146
|
Returns:
|
|
115
147
|
object: Kubernetes API object
|
|
148
|
+
|
|
116
149
|
"""
|
|
150
|
+
|
|
117
151
|
return self._apps_v1_api
|
|
118
152
|
|
|
119
|
-
|
|
120
|
-
|
|
153
|
+
# end method definition
|
|
154
|
+
|
|
155
|
+
def get_networking_v1_api(self) -> NetworkingV1Api:
|
|
156
|
+
"""Return Kubernetes Networking V1 API object.
|
|
121
157
|
|
|
122
158
|
Returns:
|
|
123
159
|
object: Kubernetes API object
|
|
160
|
+
|
|
124
161
|
"""
|
|
162
|
+
|
|
125
163
|
return self._networking_v1_api
|
|
126
164
|
|
|
127
|
-
|
|
128
|
-
|
|
165
|
+
# end method definition
|
|
166
|
+
|
|
167
|
+
def get_namespace(self) -> str:
|
|
168
|
+
"""Return Kubernetes Namespace.
|
|
129
169
|
|
|
130
170
|
Returns:
|
|
131
171
|
str: Kubernetes namespace
|
|
172
|
+
|
|
132
173
|
"""
|
|
174
|
+
|
|
133
175
|
return self._namespace
|
|
134
176
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
177
|
+
# end method definition
|
|
178
|
+
|
|
179
|
+
def get_pod(self, pod_name: str) -> V1Pod:
|
|
180
|
+
"""Get a pod in the configured namespace (the namespace is defined in the class constructor).
|
|
181
|
+
|
|
182
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#read_namespaced_pod
|
|
139
183
|
|
|
140
184
|
Args:
|
|
141
|
-
pod_name (str):
|
|
185
|
+
pod_name (str):
|
|
186
|
+
The name of the Kubernetes pod in the current namespace.
|
|
187
|
+
|
|
142
188
|
Returns:
|
|
143
189
|
V1Pod (object) or None if the call fails.
|
|
144
190
|
- api_version='v1',
|
|
@@ -146,30 +192,40 @@ class K8s:
|
|
|
146
192
|
- metadata=V1ObjectMeta(...),
|
|
147
193
|
- spec=V1PodSpec(...),
|
|
148
194
|
- status=V1PodStatus(...)
|
|
195
|
+
|
|
149
196
|
"""
|
|
150
197
|
|
|
151
198
|
try:
|
|
152
199
|
response = self.get_core_v1_api().read_namespaced_pod(
|
|
153
|
-
name=pod_name,
|
|
154
|
-
|
|
155
|
-
except ApiException as exception:
|
|
156
|
-
logger.error(
|
|
157
|
-
"Failed to get Pod -> '%s'; error -> %s", pod_name, str(exception)
|
|
200
|
+
name=pod_name,
|
|
201
|
+
namespace=self.get_namespace(),
|
|
158
202
|
)
|
|
159
|
-
|
|
160
|
-
|
|
203
|
+
except ApiException as e:
|
|
204
|
+
if e.status == 404:
|
|
205
|
+
self.logger.info("Pod -> '%s' not found (may be deleted).", pod_name)
|
|
206
|
+
return None
|
|
207
|
+
else:
|
|
208
|
+
self.logger.error("Failed to get Pod -> '%s'!", pod_name)
|
|
209
|
+
return None # Unexpected error, return None
|
|
161
210
|
return response
|
|
162
211
|
|
|
163
212
|
# end method definition
|
|
164
213
|
|
|
165
|
-
def list_pods(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
214
|
+
def list_pods(
|
|
215
|
+
self,
|
|
216
|
+
field_selector: str = "",
|
|
217
|
+
label_selector: str = "",
|
|
218
|
+
) -> V1PodList:
|
|
219
|
+
"""List all Kubernetes pods in a given namespace.
|
|
220
|
+
|
|
221
|
+
The list can be further restricted by specifying a field or label selector.
|
|
222
|
+
|
|
223
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#list_namespaced_pod
|
|
169
224
|
|
|
170
225
|
Args:
|
|
171
226
|
field_selector (str): filter result based on fields
|
|
172
227
|
label_selector (str): filter result based on labels
|
|
228
|
+
|
|
173
229
|
Returns:
|
|
174
230
|
V1PodList (object) or None if the call fails
|
|
175
231
|
Properties can be accessed with the "." notation (this is an object not a dict!):
|
|
@@ -179,6 +235,7 @@ class K8s:
|
|
|
179
235
|
- kind: The Kubernetes object kind, which is always "PodList".
|
|
180
236
|
- metadata: Additional metadata about the pod list, such as the resource version.
|
|
181
237
|
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1PodList.md
|
|
238
|
+
|
|
182
239
|
"""
|
|
183
240
|
|
|
184
241
|
try:
|
|
@@ -187,12 +244,11 @@ class K8s:
|
|
|
187
244
|
label_selector=label_selector,
|
|
188
245
|
namespace=self.get_namespace(),
|
|
189
246
|
)
|
|
190
|
-
except ApiException
|
|
191
|
-
logger.error(
|
|
192
|
-
"Failed to list Pods with field_selector -> '%s' and label_selector -> '%s'
|
|
247
|
+
except ApiException:
|
|
248
|
+
self.logger.error(
|
|
249
|
+
"Failed to list Pods with field_selector -> '%s' and label_selector -> '%s'",
|
|
193
250
|
field_selector,
|
|
194
251
|
label_selector,
|
|
195
|
-
str(exception),
|
|
196
252
|
)
|
|
197
253
|
return None
|
|
198
254
|
|
|
@@ -201,34 +257,46 @@ class K8s:
|
|
|
201
257
|
# end method definition
|
|
202
258
|
|
|
203
259
|
def wait_pod_condition(
|
|
204
|
-
self,
|
|
205
|
-
|
|
260
|
+
self,
|
|
261
|
+
pod_name: str,
|
|
262
|
+
condition_name: str,
|
|
263
|
+
sleep_time: int = 30,
|
|
264
|
+
) -> None:
|
|
206
265
|
"""Wait for the pod to reach a defined condition (e.g. "Ready").
|
|
207
266
|
|
|
208
267
|
Args:
|
|
209
|
-
pod_name (str):
|
|
210
|
-
|
|
268
|
+
pod_name (str):
|
|
269
|
+
The name of the Kubernetes pod in the current namespace.
|
|
270
|
+
condition_name (str):
|
|
271
|
+
The name of the condition, e.g. "Ready".
|
|
272
|
+
sleep_time (int):
|
|
273
|
+
The number of seconds to wait between repetitive status checks.
|
|
274
|
+
|
|
211
275
|
Returns:
|
|
212
|
-
True once the pod reaches the condition - otherwise wait forever
|
|
276
|
+
True once the pod reaches the condition - otherwise wait forever.
|
|
277
|
+
|
|
213
278
|
"""
|
|
214
279
|
|
|
215
280
|
ready = False
|
|
216
281
|
while not ready:
|
|
217
282
|
try:
|
|
218
283
|
pod_status = self.get_core_v1_api().read_namespaced_pod_status(
|
|
219
|
-
pod_name,
|
|
284
|
+
pod_name,
|
|
285
|
+
self.get_namespace(),
|
|
220
286
|
)
|
|
221
287
|
|
|
222
288
|
# Check if the pod has reached the defined condition:
|
|
223
289
|
for cond in pod_status.status.conditions:
|
|
224
290
|
if cond.type == condition_name and cond.status == "True":
|
|
225
|
-
logger.info(
|
|
226
|
-
"Pod -> '%s' is in state -> '%s'!",
|
|
291
|
+
self.logger.info(
|
|
292
|
+
"Pod -> '%s' is in state -> '%s'!",
|
|
293
|
+
pod_name,
|
|
294
|
+
condition_name,
|
|
227
295
|
)
|
|
228
296
|
ready = True
|
|
229
297
|
break
|
|
230
298
|
else:
|
|
231
|
-
logger.info(
|
|
299
|
+
self.logger.info(
|
|
232
300
|
"Pod -> '%s' is not yet in state -> '%s'. Waiting...",
|
|
233
301
|
pod_name,
|
|
234
302
|
condition_name,
|
|
@@ -236,11 +304,10 @@ class K8s:
|
|
|
236
304
|
time.sleep(sleep_time)
|
|
237
305
|
continue
|
|
238
306
|
|
|
239
|
-
except ApiException
|
|
240
|
-
logger.error(
|
|
241
|
-
"Failed to wait for pod -> '%s'
|
|
307
|
+
except ApiException:
|
|
308
|
+
self.logger.error(
|
|
309
|
+
"Failed to wait for pod -> '%s'",
|
|
242
310
|
pod_name,
|
|
243
|
-
str(exception),
|
|
244
311
|
)
|
|
245
312
|
|
|
246
313
|
# end method definition
|
|
@@ -252,23 +319,35 @@ class K8s:
|
|
|
252
319
|
max_retry: int = 3,
|
|
253
320
|
time_retry: int = 10,
|
|
254
321
|
container: str | None = None,
|
|
255
|
-
):
|
|
322
|
+
) -> str:
|
|
256
323
|
"""Execute a command inside a Kubernetes Pod (similar to kubectl exec on command line).
|
|
257
|
-
|
|
324
|
+
|
|
325
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#connect_get_namespaced_pod_exec
|
|
326
|
+
|
|
258
327
|
Args:
|
|
259
|
-
pod_name (str):
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
328
|
+
pod_name (str):
|
|
329
|
+
The name of the Kubernetes pod in the current namespace.
|
|
330
|
+
command (list):
|
|
331
|
+
A list of command and its parameters, e.g. ["/bin/bash", "-c", "pwd"]
|
|
332
|
+
The "-c" is required to make the shell executing the command.
|
|
333
|
+
max_retry (int):
|
|
334
|
+
The maximum number of attempts to execute the command.
|
|
335
|
+
time_retry (int):
|
|
336
|
+
Wait time in seconds between retries.
|
|
337
|
+
container (str):
|
|
338
|
+
The container name if the pod runs multiple containers inside.
|
|
339
|
+
|
|
263
340
|
Returns:
|
|
264
|
-
|
|
341
|
+
str:
|
|
342
|
+
Response of the command or None if the call fails.
|
|
343
|
+
|
|
265
344
|
"""
|
|
266
345
|
|
|
267
|
-
pod = self.get_pod(pod_name)
|
|
346
|
+
pod = self.get_pod(pod_name=pod_name)
|
|
268
347
|
if not pod:
|
|
269
|
-
logger.error("Pod -> '%s' does not exist", pod_name)
|
|
348
|
+
self.logger.error("Pod -> '%s' does not exist", pod_name)
|
|
270
349
|
|
|
271
|
-
logger.debug("Execute command -> %s in pod -> '%s'", command, pod_name)
|
|
350
|
+
self.logger.debug("Execute command -> %s in pod -> '%s'", command, pod_name)
|
|
272
351
|
|
|
273
352
|
retry_counter = 1
|
|
274
353
|
|
|
@@ -285,10 +364,8 @@ class K8s:
|
|
|
285
364
|
stdout=True,
|
|
286
365
|
tty=False,
|
|
287
366
|
)
|
|
288
|
-
logger.debug(response)
|
|
289
|
-
return response
|
|
290
367
|
except ApiException as exc:
|
|
291
|
-
logger.warning(
|
|
368
|
+
self.logger.warning(
|
|
292
369
|
"Failed to execute command, retry (%s/%s) -> %s in pod -> '%s'; error -> %s",
|
|
293
370
|
retry_counter,
|
|
294
371
|
max_retry,
|
|
@@ -298,11 +375,17 @@ class K8s:
|
|
|
298
375
|
)
|
|
299
376
|
retry_counter = retry_counter + 1
|
|
300
377
|
exception = exc
|
|
301
|
-
logger.debug(
|
|
378
|
+
self.logger.debug(
|
|
379
|
+
"Wait %s seconds before next retry...",
|
|
380
|
+
str(time_retry),
|
|
381
|
+
)
|
|
302
382
|
time.sleep(time_retry)
|
|
303
383
|
continue
|
|
384
|
+
else:
|
|
385
|
+
self.logger.debug(response)
|
|
386
|
+
return response
|
|
304
387
|
|
|
305
|
-
logger.error(
|
|
388
|
+
self.logger.error(
|
|
306
389
|
"Failed to execute command with %s retries -> %s in pod -> '%s'; error -> %s",
|
|
307
390
|
max_retry,
|
|
308
391
|
command,
|
|
@@ -321,32 +404,40 @@ class K8s:
|
|
|
321
404
|
commands: list,
|
|
322
405
|
timeout: int = 30,
|
|
323
406
|
write_stderr_to_error_log: bool = True,
|
|
324
|
-
):
|
|
407
|
+
) -> str:
|
|
325
408
|
"""Execute a command inside a Kubernetes pod (similar to kubectl exec on command line).
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
409
|
+
|
|
410
|
+
Other than exec_pod_command() method above this is an interactive execution using
|
|
411
|
+
stdin and reading the output from stdout and stderr. This is required for longer
|
|
412
|
+
running commands. It is currently used for restarting the spawner of Archive Center.
|
|
413
|
+
The output of the command is pushed into the logging.
|
|
330
414
|
|
|
331
415
|
Args:
|
|
332
|
-
pod_name (str):
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
416
|
+
pod_name (str):
|
|
417
|
+
The name of the Kubernetes pod in the current namespace
|
|
418
|
+
commands (list):
|
|
419
|
+
A list of command and its parameters, e.g. ["/bin/bash", "/etc/init.d/spawner restart"]
|
|
420
|
+
Here we should NOT have a "-c" parameter!
|
|
421
|
+
timeout (int):
|
|
422
|
+
Timeout duration that is waited for any response.
|
|
423
|
+
Each time a resonse is found in stdout or stderr we wait another timeout duration
|
|
424
|
+
to make sure we get the full output of the command.
|
|
425
|
+
write_stderr_to_error_log (bool):
|
|
426
|
+
Flag to control if output in stderr should be written to info or error log stream.
|
|
427
|
+
Default is write to error log (True).
|
|
428
|
+
|
|
340
429
|
Returns:
|
|
341
|
-
str:
|
|
430
|
+
str:
|
|
431
|
+
Response of the command or None if the call fails.
|
|
432
|
+
|
|
342
433
|
"""
|
|
343
434
|
|
|
344
|
-
pod = self.get_pod(pod_name)
|
|
435
|
+
pod = self.get_pod(pod_name=pod_name)
|
|
345
436
|
if not pod:
|
|
346
|
-
logger.error("Pod -> %s does not exist", pod_name)
|
|
437
|
+
self.logger.error("Pod -> '%s' does not exist", pod_name)
|
|
347
438
|
|
|
348
439
|
if not commands:
|
|
349
|
-
logger.error("No commands to execute on Pod ->
|
|
440
|
+
self.logger.error("No commands to execute on Pod ->'%s'!", pod_name)
|
|
350
441
|
return None
|
|
351
442
|
|
|
352
443
|
# Get first command - this should be the shell:
|
|
@@ -364,12 +455,11 @@ class K8s:
|
|
|
364
455
|
tty=False,
|
|
365
456
|
_preload_content=False, # This is important!
|
|
366
457
|
)
|
|
367
|
-
except ApiException
|
|
368
|
-
logger.error(
|
|
369
|
-
"Failed to execute command -> %s in pod -> '%s'
|
|
458
|
+
except ApiException:
|
|
459
|
+
self.logger.error(
|
|
460
|
+
"Failed to execute command -> %s in pod -> '%s'",
|
|
370
461
|
command,
|
|
371
462
|
pod_name,
|
|
372
|
-
str(exception),
|
|
373
463
|
)
|
|
374
464
|
return None
|
|
375
465
|
|
|
@@ -377,22 +467,25 @@ class K8s:
|
|
|
377
467
|
got_response = False
|
|
378
468
|
response.update(timeout=timeout)
|
|
379
469
|
if response.peek_stdout():
|
|
380
|
-
logger.debug(response.read_stdout().replace("\n", " "))
|
|
470
|
+
self.logger.debug(response.read_stdout().replace("\n", " "))
|
|
381
471
|
got_response = True
|
|
382
472
|
if response.peek_stderr():
|
|
383
473
|
if write_stderr_to_error_log:
|
|
384
|
-
logger.error(response.read_stderr().replace("\n", " "))
|
|
474
|
+
self.logger.error(response.read_stderr().replace("\n", " "))
|
|
385
475
|
else:
|
|
386
|
-
logger.debug(response.read_stderr().replace("\n", " "))
|
|
476
|
+
self.logger.debug(response.read_stderr().replace("\n", " "))
|
|
387
477
|
got_response = True
|
|
388
478
|
if commands:
|
|
389
479
|
command = commands.pop(0)
|
|
390
|
-
logger.debug(
|
|
480
|
+
self.logger.debug(
|
|
481
|
+
"Execute command -> %s in pod -> '%s'",
|
|
482
|
+
command,
|
|
483
|
+
pod_name,
|
|
484
|
+
)
|
|
391
485
|
response.write_stdin(command + "\n")
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
break
|
|
486
|
+
# We continue as long as we get some response during timeout period
|
|
487
|
+
elif not got_response:
|
|
488
|
+
break
|
|
396
489
|
|
|
397
490
|
response.close()
|
|
398
491
|
|
|
@@ -400,13 +493,16 @@ class K8s:
|
|
|
400
493
|
|
|
401
494
|
# end method definition
|
|
402
495
|
|
|
403
|
-
def delete_pod(self, pod_name: str):
|
|
496
|
+
def delete_pod(self, pod_name: str) -> None:
|
|
404
497
|
"""Delete a pod in the configured namespace (the namespace is defined in the class constructor).
|
|
405
|
-
|
|
498
|
+
|
|
499
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#delete_namespaced_pod
|
|
406
500
|
|
|
407
501
|
Args:
|
|
408
|
-
pod_name (str):
|
|
409
|
-
|
|
502
|
+
pod_name (str):
|
|
503
|
+
The name of the Kubernetes pod in the current namespace.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
410
506
|
V1Status (object) or None if the call fails.
|
|
411
507
|
- api_version: The Kubernetes API version.
|
|
412
508
|
- kind: The Kubernetes object kind, which is always "Status".
|
|
@@ -416,19 +512,22 @@ class K8s:
|
|
|
416
512
|
- reason: A short string that describes the reason for the status.
|
|
417
513
|
- code: An HTTP status code that corresponds to the status.
|
|
418
514
|
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Status.md
|
|
515
|
+
|
|
419
516
|
"""
|
|
420
517
|
|
|
421
|
-
pod = self.get_pod(pod_name)
|
|
518
|
+
pod = self.get_pod(pod_name=pod_name)
|
|
422
519
|
if not pod:
|
|
423
|
-
logger.error("Pod -> %s does not exist", pod_name)
|
|
520
|
+
self.logger.error("Pod -> '%s' does not exist!", pod_name)
|
|
424
521
|
|
|
425
522
|
try:
|
|
426
523
|
response = self.get_core_v1_api().delete_namespaced_pod(
|
|
427
|
-
pod_name,
|
|
524
|
+
pod_name,
|
|
525
|
+
namespace=self.get_namespace(),
|
|
428
526
|
)
|
|
429
|
-
except ApiException
|
|
430
|
-
logger.error(
|
|
431
|
-
"Failed to delete Pod -> '%s'
|
|
527
|
+
except ApiException:
|
|
528
|
+
self.logger.error(
|
|
529
|
+
"Failed to delete Pod -> '%s'",
|
|
530
|
+
pod_name,
|
|
432
531
|
)
|
|
433
532
|
return None
|
|
434
533
|
|
|
@@ -436,35 +535,44 @@ class K8s:
|
|
|
436
535
|
|
|
437
536
|
# end method definition
|
|
438
537
|
|
|
439
|
-
def get_config_map(self, config_map_name: str):
|
|
538
|
+
def get_config_map(self, config_map_name: str) -> V1ConfigMap:
|
|
440
539
|
"""Get a config map in the configured namespace (the namespace is defined in the class constructor).
|
|
441
|
-
|
|
540
|
+
|
|
541
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#read_namespaced_config_map
|
|
442
542
|
|
|
443
543
|
Args:
|
|
444
|
-
config_map_name (str):
|
|
544
|
+
config_map_name (str):
|
|
545
|
+
The name of the Kubernetes config map in the current namespace.
|
|
546
|
+
|
|
445
547
|
Returns:
|
|
446
|
-
V1ConfigMap (object):
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
548
|
+
V1ConfigMap (object):
|
|
549
|
+
Kubernetes Config Map object that includes these fields:
|
|
550
|
+
- api_version:
|
|
551
|
+
The Kubernetes API version.
|
|
552
|
+
- metadata:
|
|
553
|
+
A V1ObjectMeta object representing metadata about the V1ConfigMap object,
|
|
554
|
+
such as its name, labels, and annotations.
|
|
555
|
+
- data:
|
|
556
|
+
A dictionary containing the non-binary data stored in the ConfigMap,
|
|
451
557
|
where the keys represent the keys of the data items and the values represent
|
|
452
558
|
the values of the data items.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
559
|
+
- binary_data:
|
|
560
|
+
A dictionary containing the binary data stored in the ConfigMap,
|
|
561
|
+
where the keys represent the keys of the binary data items and the values
|
|
562
|
+
represent the values of the binary data items. Binary data is encoded as base64
|
|
563
|
+
strings in the dictionary values.
|
|
564
|
+
|
|
457
565
|
"""
|
|
458
566
|
|
|
459
567
|
try:
|
|
460
568
|
response = self.get_core_v1_api().read_namespaced_config_map(
|
|
461
|
-
name=config_map_name,
|
|
569
|
+
name=config_map_name,
|
|
570
|
+
namespace=self.get_namespace(),
|
|
462
571
|
)
|
|
463
|
-
except ApiException
|
|
464
|
-
logger.error(
|
|
465
|
-
"Failed to get Config Map -> '%s'
|
|
572
|
+
except ApiException:
|
|
573
|
+
self.logger.error(
|
|
574
|
+
"Failed to get Config Map -> '%s'",
|
|
466
575
|
config_map_name,
|
|
467
|
-
str(exception),
|
|
468
576
|
)
|
|
469
577
|
return None
|
|
470
578
|
|
|
@@ -472,14 +580,22 @@ class K8s:
|
|
|
472
580
|
|
|
473
581
|
# end method definition
|
|
474
582
|
|
|
475
|
-
def list_config_maps(
|
|
583
|
+
def list_config_maps(
|
|
584
|
+
self,
|
|
585
|
+
field_selector: str = "",
|
|
586
|
+
label_selector: str = "",
|
|
587
|
+
) -> V1ConfigMapList:
|
|
476
588
|
"""List all Kubernetes Config Maps in the current namespace.
|
|
477
|
-
|
|
478
|
-
|
|
589
|
+
|
|
590
|
+
The list can be filtered by providing field selectors and label selectors.
|
|
591
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#list_namespaced_config_map
|
|
479
592
|
|
|
480
593
|
Args:
|
|
481
|
-
field_selector (str):
|
|
482
|
-
|
|
594
|
+
field_selector (str):
|
|
595
|
+
To filter the result based on fields.
|
|
596
|
+
label_selector (str):
|
|
597
|
+
To filter result based on labels.
|
|
598
|
+
|
|
483
599
|
Returns:
|
|
484
600
|
V1ConfigMapList (object) or None if the call fails
|
|
485
601
|
Properties can be accessed with the "." notation (this is an object not a dict!):
|
|
@@ -489,6 +605,7 @@ class K8s:
|
|
|
489
605
|
- kind: The Kubernetes object kind, which is always "ConfigMapList".
|
|
490
606
|
- metadata: Additional metadata about the config map list, such as the resource version.
|
|
491
607
|
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1ConfigMapList.md
|
|
608
|
+
|
|
492
609
|
"""
|
|
493
610
|
|
|
494
611
|
try:
|
|
@@ -497,12 +614,11 @@ class K8s:
|
|
|
497
614
|
label_selector=label_selector,
|
|
498
615
|
namespace=self.get_namespace(),
|
|
499
616
|
)
|
|
500
|
-
except ApiException
|
|
501
|
-
logger.error(
|
|
502
|
-
"Failed to list Config Maps with field_selector -> '%s' and label_selector -> '%s'
|
|
617
|
+
except ApiException:
|
|
618
|
+
self.logger.error(
|
|
619
|
+
"Failed to list Config Maps with field_selector -> '%s' and label_selector -> '%s'",
|
|
503
620
|
field_selector,
|
|
504
621
|
label_selector,
|
|
505
|
-
str(exception),
|
|
506
622
|
)
|
|
507
623
|
return None
|
|
508
624
|
|
|
@@ -510,26 +626,30 @@ class K8s:
|
|
|
510
626
|
|
|
511
627
|
# end method definition
|
|
512
628
|
|
|
513
|
-
def find_config_map(self, config_map_name: str):
|
|
629
|
+
def find_config_map(self, config_map_name: str) -> V1ConfigMapList:
|
|
514
630
|
"""Find a Kubernetes Config Map based on its name.
|
|
515
|
-
|
|
516
|
-
|
|
631
|
+
|
|
632
|
+
This is just a wrapper method for list_config_maps()
|
|
633
|
+
that uses the name as a field selector.
|
|
517
634
|
|
|
518
635
|
Args:
|
|
519
|
-
config_map_name (str):
|
|
636
|
+
config_map_name (str):
|
|
637
|
+
The name of the Kubernetes Config Map to search for.
|
|
638
|
+
|
|
520
639
|
Returns:
|
|
521
|
-
object:
|
|
640
|
+
object:
|
|
641
|
+
V1ConfigMapList (object) or None if the call fails.
|
|
642
|
+
|
|
522
643
|
"""
|
|
523
644
|
|
|
524
645
|
try:
|
|
525
646
|
response = self.list_config_maps(
|
|
526
|
-
field_selector="metadata.name={}".format(config_map_name)
|
|
647
|
+
field_selector="metadata.name={}".format(config_map_name),
|
|
527
648
|
)
|
|
528
|
-
except ApiException
|
|
529
|
-
logger.error(
|
|
530
|
-
"Failed to find Config Map -> '%s'
|
|
649
|
+
except ApiException:
|
|
650
|
+
self.logger.error(
|
|
651
|
+
"Failed to find Config Map -> '%s'",
|
|
531
652
|
config_map_name,
|
|
532
|
-
str(exception),
|
|
533
653
|
)
|
|
534
654
|
return None
|
|
535
655
|
|
|
@@ -537,16 +657,26 @@ class K8s:
|
|
|
537
657
|
|
|
538
658
|
# end method definition
|
|
539
659
|
|
|
540
|
-
def replace_config_map(
|
|
660
|
+
def replace_config_map(
|
|
661
|
+
self,
|
|
662
|
+
config_map_name: str,
|
|
663
|
+
config_map_data: dict,
|
|
664
|
+
) -> V1ConfigMap:
|
|
541
665
|
"""Replace a Config Map with a new specification.
|
|
542
|
-
|
|
666
|
+
|
|
667
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#replace_namespaced_config_map
|
|
543
668
|
|
|
544
669
|
Args:
|
|
545
|
-
config_map_name (str):
|
|
546
|
-
|
|
670
|
+
config_map_name (str):
|
|
671
|
+
The name of the Kubernetes Config Map to replace.
|
|
672
|
+
config_map_data (dict):
|
|
673
|
+
The updated specification of the Config Map.
|
|
674
|
+
|
|
547
675
|
Returns:
|
|
548
|
-
V1ConfigMap (object):
|
|
549
|
-
|
|
676
|
+
V1ConfigMap (object):
|
|
677
|
+
Updated Kubernetes Config Map object or None if the call fails.
|
|
678
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1ConfigMap.md
|
|
679
|
+
|
|
550
680
|
"""
|
|
551
681
|
|
|
552
682
|
try:
|
|
@@ -560,11 +690,10 @@ class K8s:
|
|
|
560
690
|
data=config_map_data,
|
|
561
691
|
),
|
|
562
692
|
)
|
|
563
|
-
except ApiException
|
|
564
|
-
logger.error(
|
|
565
|
-
"Failed to replace Config Map -> '%s'
|
|
693
|
+
except ApiException:
|
|
694
|
+
self.logger.error(
|
|
695
|
+
"Failed to replace Config Map -> '%s'",
|
|
566
696
|
config_map_name,
|
|
567
|
-
str(exception),
|
|
568
697
|
)
|
|
569
698
|
return None
|
|
570
699
|
|
|
@@ -572,26 +701,31 @@ class K8s:
|
|
|
572
701
|
|
|
573
702
|
# end method definition
|
|
574
703
|
|
|
575
|
-
def get_stateful_set(self, sts_name: str):
|
|
704
|
+
def get_stateful_set(self, sts_name: str) -> V1StatefulSet:
|
|
576
705
|
"""Get a Kubernetes Stateful Set based on its name.
|
|
577
|
-
|
|
706
|
+
|
|
707
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/AppsV1Api.md#read_namespaced_stateful_set
|
|
578
708
|
|
|
579
709
|
Args:
|
|
580
|
-
sts_name (str):
|
|
710
|
+
sts_name (str):
|
|
711
|
+
The name of the Kubernetes stateful set
|
|
712
|
+
|
|
581
713
|
Returns:
|
|
582
|
-
V1StatefulSet (object):
|
|
583
|
-
|
|
714
|
+
V1StatefulSet (object):
|
|
715
|
+
Kubernetes Stateful Set object or None if the call fails.
|
|
716
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1StatefulSet.md
|
|
717
|
+
|
|
584
718
|
"""
|
|
585
719
|
|
|
586
720
|
try:
|
|
587
721
|
response = self.get_apps_v1_api().read_namespaced_stateful_set(
|
|
588
|
-
name=sts_name,
|
|
722
|
+
name=sts_name,
|
|
723
|
+
namespace=self.get_namespace(),
|
|
589
724
|
)
|
|
590
|
-
except ApiException
|
|
591
|
-
logger.error(
|
|
592
|
-
"Failed to get Stateful Set -> '%s'
|
|
725
|
+
except ApiException:
|
|
726
|
+
self.logger.error(
|
|
727
|
+
"Failed to get Stateful Set -> '%s'",
|
|
593
728
|
sts_name,
|
|
594
|
-
str(exception),
|
|
595
729
|
)
|
|
596
730
|
return None
|
|
597
731
|
|
|
@@ -599,26 +733,31 @@ class K8s:
|
|
|
599
733
|
|
|
600
734
|
# end method definition
|
|
601
735
|
|
|
602
|
-
def get_stateful_set_scale(self, sts_name: str):
|
|
736
|
+
def get_stateful_set_scale(self, sts_name: str) -> V1Scale:
|
|
603
737
|
"""Get the number of replicas for a Kubernetes Stateful Set.
|
|
604
|
-
|
|
738
|
+
|
|
739
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/AppsV1Api.md#read_namespaced_stateful_set_scale
|
|
605
740
|
|
|
606
741
|
Args:
|
|
607
|
-
sts_name (str):
|
|
742
|
+
sts_name (str):
|
|
743
|
+
The name of the Kubernetes Stateful Set.
|
|
744
|
+
|
|
608
745
|
Returns:
|
|
609
|
-
V1Scale (object):
|
|
610
|
-
|
|
746
|
+
V1Scale (object):
|
|
747
|
+
Kubernetes Scale object or None if the call fails.
|
|
748
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Scale.md
|
|
749
|
+
|
|
611
750
|
"""
|
|
612
751
|
|
|
613
752
|
try:
|
|
614
753
|
response = self.get_apps_v1_api().read_namespaced_stateful_set_scale(
|
|
615
|
-
name=sts_name,
|
|
754
|
+
name=sts_name,
|
|
755
|
+
namespace=self.get_namespace(),
|
|
616
756
|
)
|
|
617
|
-
except ApiException
|
|
618
|
-
logger.error(
|
|
619
|
-
"Failed to get scaling (replicas) of Stateful Set -> '%s'
|
|
757
|
+
except ApiException:
|
|
758
|
+
self.logger.error(
|
|
759
|
+
"Failed to get scaling (replicas) of Stateful Set -> '%s'",
|
|
620
760
|
sts_name,
|
|
621
|
-
str(exception),
|
|
622
761
|
)
|
|
623
762
|
return None
|
|
624
763
|
|
|
@@ -626,28 +765,35 @@ class K8s:
|
|
|
626
765
|
|
|
627
766
|
# end method definition
|
|
628
767
|
|
|
629
|
-
def patch_stateful_set(self, sts_name: str, sts_body: dict):
|
|
768
|
+
def patch_stateful_set(self, sts_name: str, sts_body: dict) -> V1StatefulSet:
|
|
630
769
|
"""Patch a Stateful set with new values.
|
|
631
|
-
|
|
770
|
+
|
|
771
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/AppsV1Api.md#patch_namespaced_stateful_set
|
|
632
772
|
|
|
633
773
|
Args:
|
|
634
|
-
sts_name (str):
|
|
635
|
-
|
|
774
|
+
sts_name (str):
|
|
775
|
+
The name of the Kubernetes stateful set in the current namespace.
|
|
776
|
+
sts_body (str):
|
|
777
|
+
The patch string.
|
|
778
|
+
|
|
636
779
|
Returns:
|
|
637
|
-
V1StatefulSet (object):
|
|
638
|
-
|
|
780
|
+
V1StatefulSet (object):
|
|
781
|
+
The patched Kubernetes Stateful Set object or None if the call fails.
|
|
782
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1StatefulSet.md
|
|
783
|
+
|
|
639
784
|
"""
|
|
640
785
|
|
|
641
786
|
try:
|
|
642
787
|
response = self.get_apps_v1_api().patch_namespaced_stateful_set(
|
|
643
|
-
name=sts_name,
|
|
788
|
+
name=sts_name,
|
|
789
|
+
namespace=self.get_namespace(),
|
|
790
|
+
body=sts_body,
|
|
644
791
|
)
|
|
645
|
-
except ApiException
|
|
646
|
-
logger.error(
|
|
647
|
-
"Failed to patch Stateful Set -> '%s' with -> %s
|
|
792
|
+
except ApiException:
|
|
793
|
+
self.logger.error(
|
|
794
|
+
"Failed to patch Stateful Set -> '%s' with -> %s",
|
|
648
795
|
sts_name,
|
|
649
796
|
sts_body,
|
|
650
|
-
str(exception),
|
|
651
797
|
)
|
|
652
798
|
return None
|
|
653
799
|
|
|
@@ -655,28 +801,34 @@ class K8s:
|
|
|
655
801
|
|
|
656
802
|
# end method definition
|
|
657
803
|
|
|
658
|
-
def scale_stateful_set(self, sts_name: str, scale: int):
|
|
804
|
+
def scale_stateful_set(self, sts_name: str, scale: int) -> V1StatefulSet:
|
|
659
805
|
"""Scale a stateful set to a specific number of replicas.
|
|
660
|
-
|
|
806
|
+
|
|
807
|
+
It uses the class method patch_stateful_set() above.
|
|
661
808
|
|
|
662
809
|
Args:
|
|
663
|
-
sts_name (str):
|
|
664
|
-
|
|
810
|
+
sts_name (str):
|
|
811
|
+
The name of the Kubernetes stateful set in the current namespace.
|
|
812
|
+
scale (int):
|
|
813
|
+
The number of replicas (pods) the stateful set shall be scaled to.
|
|
814
|
+
|
|
665
815
|
Returns:
|
|
666
|
-
V1StatefulSet (object):
|
|
667
|
-
|
|
816
|
+
V1StatefulSet (object):
|
|
817
|
+
Kubernetes Stateful Set object or None if the call fails.
|
|
818
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1StatefulSet.md
|
|
819
|
+
|
|
668
820
|
"""
|
|
669
821
|
|
|
670
822
|
try:
|
|
671
823
|
response = self.patch_stateful_set(
|
|
672
|
-
sts_name,
|
|
824
|
+
sts_name,
|
|
825
|
+
sts_body={"spec": {"replicas": scale}},
|
|
673
826
|
)
|
|
674
|
-
except ApiException
|
|
675
|
-
logger.error(
|
|
676
|
-
"Failed to scale Stateful Set -> '%s' to -> %s replicas
|
|
827
|
+
except ApiException:
|
|
828
|
+
self.logger.error(
|
|
829
|
+
"Failed to scale Stateful Set -> '%s' to -> %s replicas",
|
|
677
830
|
sts_name,
|
|
678
831
|
scale,
|
|
679
|
-
str(exception),
|
|
680
832
|
)
|
|
681
833
|
return None
|
|
682
834
|
|
|
@@ -684,26 +836,30 @@ class K8s:
|
|
|
684
836
|
|
|
685
837
|
# end method definition
|
|
686
838
|
|
|
687
|
-
def get_service(self, service_name: str):
|
|
688
|
-
"""Get a Kubernetes Service with a defined name in the current namespace
|
|
839
|
+
def get_service(self, service_name: str) -> V1Service:
|
|
840
|
+
"""Get a Kubernetes Service with a defined name in the current namespace.
|
|
689
841
|
|
|
690
842
|
Args:
|
|
691
|
-
service_name (str):
|
|
843
|
+
service_name (str):
|
|
844
|
+
The name of the Kubernetes Service in the current namespace.
|
|
845
|
+
|
|
692
846
|
Returns:
|
|
693
|
-
V1Service (object):
|
|
694
|
-
|
|
695
|
-
|
|
847
|
+
V1Service (object):
|
|
848
|
+
Kubernetes Service object or None if the call fails
|
|
849
|
+
This is NOT a dict but an object - the you have to use the "." syntax to access to returned elements.
|
|
850
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Service.md
|
|
851
|
+
|
|
696
852
|
"""
|
|
697
853
|
|
|
698
854
|
try:
|
|
699
855
|
response = self.get_core_v1_api().read_namespaced_service(
|
|
700
|
-
name=service_name,
|
|
856
|
+
name=service_name,
|
|
857
|
+
namespace=self.get_namespace(),
|
|
701
858
|
)
|
|
702
|
-
except ApiException
|
|
703
|
-
logger.error(
|
|
704
|
-
"Failed to get Service -> '%s'
|
|
859
|
+
except ApiException:
|
|
860
|
+
self.logger.error(
|
|
861
|
+
"Failed to get Service -> '%s'",
|
|
705
862
|
service_name,
|
|
706
|
-
str(exception),
|
|
707
863
|
)
|
|
708
864
|
return None
|
|
709
865
|
|
|
@@ -711,24 +867,30 @@ class K8s:
|
|
|
711
867
|
|
|
712
868
|
# end method definition
|
|
713
869
|
|
|
714
|
-
def list_services(self, field_selector: str = "", label_selector: str = ""):
|
|
870
|
+
def list_services(self, field_selector: str = "", label_selector: str = "") -> None:
|
|
715
871
|
"""List all Kubernetes Service in the current namespace.
|
|
716
|
-
|
|
717
|
-
|
|
872
|
+
|
|
873
|
+
The list can be filtered by providing field selectors and label selectors.
|
|
874
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md#list_namespaced_service
|
|
718
875
|
|
|
719
876
|
Args:
|
|
720
|
-
field_selector (str):
|
|
721
|
-
|
|
877
|
+
field_selector (str):
|
|
878
|
+
To filter result based on fields.
|
|
879
|
+
label_selector (str):
|
|
880
|
+
To filter result based on labels.
|
|
881
|
+
|
|
722
882
|
Returns:
|
|
723
|
-
V1ServiceList (object):
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
883
|
+
V1ServiceList (object):
|
|
884
|
+
A list of Kubernetes Services or None if the call fails.
|
|
885
|
+
Properties can be accessed with the "." notation (this is an object not a dict!):
|
|
886
|
+
- api_version: The Kubernetes API version.
|
|
887
|
+
- items: A list of V1Service objects, each representing a service.
|
|
888
|
+
You can access the fields of a V1Service object using dot notation,
|
|
889
|
+
for example, service.metadata.name to access the name of the service
|
|
890
|
+
- kind: The Kubernetes object kind, which is always "ServiceList".
|
|
891
|
+
- metadata: Additional metadata about the pod list, such as the resource version.
|
|
892
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1ServiceList.md
|
|
893
|
+
|
|
732
894
|
"""
|
|
733
895
|
|
|
734
896
|
try:
|
|
@@ -737,12 +899,11 @@ class K8s:
|
|
|
737
899
|
label_selector=label_selector,
|
|
738
900
|
namespace=self.get_namespace(),
|
|
739
901
|
)
|
|
740
|
-
except ApiException
|
|
741
|
-
logger.error(
|
|
742
|
-
"Failed to list Services with field_selector -> '%s' and label_selector -> '%s'
|
|
902
|
+
except ApiException:
|
|
903
|
+
self.logger.error(
|
|
904
|
+
"Failed to list Services with field_selector -> '%s' and label_selector -> '%s'",
|
|
743
905
|
field_selector,
|
|
744
906
|
label_selector,
|
|
745
|
-
str(exception),
|
|
746
907
|
)
|
|
747
908
|
return None
|
|
748
909
|
|
|
@@ -750,30 +911,36 @@ class K8s:
|
|
|
750
911
|
|
|
751
912
|
# end method definition
|
|
752
913
|
|
|
753
|
-
def patch_service(self, service_name: str, service_body: dict):
|
|
754
|
-
"""
|
|
914
|
+
def patch_service(self, service_name: str, service_body: dict) -> V1Service:
|
|
915
|
+
"""Patch a Kubernetes Service with an updated spec.
|
|
755
916
|
|
|
756
917
|
Args:
|
|
757
|
-
service_name (str):
|
|
758
|
-
|
|
759
|
-
|
|
918
|
+
service_name (str):
|
|
919
|
+
The name of the Kubernetes Ingress in the current namespace.
|
|
920
|
+
service_body (dict):
|
|
921
|
+
The new / updated Service body spec.
|
|
922
|
+
(will be merged with existing values)
|
|
923
|
+
|
|
760
924
|
Returns:
|
|
761
|
-
V1Service (object):
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
925
|
+
V1Service (object):
|
|
926
|
+
The patched Kubernetes Service or None if the call fails.
|
|
927
|
+
This is NOT a dict but an object - you have to use the "." syntax
|
|
928
|
+
to access to returned elements
|
|
929
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Service.md
|
|
930
|
+
|
|
765
931
|
"""
|
|
766
932
|
|
|
767
933
|
try:
|
|
768
934
|
response = self.get_core_v1_api().patch_namespaced_service(
|
|
769
|
-
name=service_name,
|
|
935
|
+
name=service_name,
|
|
936
|
+
namespace=self.get_namespace(),
|
|
937
|
+
body=service_body,
|
|
770
938
|
)
|
|
771
|
-
except ApiException
|
|
772
|
-
logger.error(
|
|
773
|
-
"Failed to patch Service -> '%s' with -> %s
|
|
939
|
+
except ApiException:
|
|
940
|
+
self.logger.error(
|
|
941
|
+
"Failed to patch Service -> '%s' with -> %s",
|
|
774
942
|
service_name,
|
|
775
943
|
service_body,
|
|
776
|
-
str(exception),
|
|
777
944
|
)
|
|
778
945
|
return None
|
|
779
946
|
|
|
@@ -781,24 +948,30 @@ class K8s:
|
|
|
781
948
|
|
|
782
949
|
# end method definition
|
|
783
950
|
|
|
784
|
-
def get_ingress(self, ingress_name: str):
|
|
785
|
-
"""Get a Kubernetes Ingress with a defined name in the current namespace
|
|
951
|
+
def get_ingress(self, ingress_name: str) -> V1Ingress:
|
|
952
|
+
"""Get a Kubernetes Ingress with a defined name in the current namespace.
|
|
786
953
|
|
|
787
954
|
Args:
|
|
788
|
-
ingress_name (str):
|
|
955
|
+
ingress_name (str):
|
|
956
|
+
The name of the Kubernetes Ingress in the current namespace.
|
|
957
|
+
|
|
789
958
|
Returns:
|
|
790
|
-
V1Ingress (object):
|
|
791
|
-
|
|
792
|
-
|
|
959
|
+
V1Ingress (object):
|
|
960
|
+
Kubernetes Ingress or None if the call fails
|
|
961
|
+
This is NOT a dict but an object - the you have to use the "." syntax to access to returned elements.
|
|
962
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Ingress.md
|
|
963
|
+
|
|
793
964
|
"""
|
|
794
965
|
|
|
795
966
|
try:
|
|
796
967
|
response = self.get_networking_v1_api().read_namespaced_ingress(
|
|
797
|
-
name=ingress_name,
|
|
968
|
+
name=ingress_name,
|
|
969
|
+
namespace=self.get_namespace(),
|
|
798
970
|
)
|
|
799
|
-
except ApiException
|
|
800
|
-
logger.error(
|
|
801
|
-
"Failed to get Ingress -> %s
|
|
971
|
+
except ApiException:
|
|
972
|
+
self.logger.error(
|
|
973
|
+
"Failed to get Ingress -> '%s'!",
|
|
974
|
+
ingress_name,
|
|
802
975
|
)
|
|
803
976
|
return None
|
|
804
977
|
|
|
@@ -806,19 +979,25 @@ class K8s:
|
|
|
806
979
|
|
|
807
980
|
# end method definition
|
|
808
981
|
|
|
809
|
-
def patch_ingress(self, ingress_name: str, ingress_body: dict):
|
|
982
|
+
def patch_ingress(self, ingress_name: str, ingress_body: dict) -> V1Ingress:
|
|
810
983
|
"""Patch a Kubernetes Ingress with a updated spec.
|
|
811
|
-
|
|
984
|
+
|
|
985
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/NetworkingV1Api.md#patch_namespaced_ingress
|
|
812
986
|
|
|
813
987
|
Args:
|
|
814
|
-
ingress_name (str):
|
|
815
|
-
|
|
816
|
-
|
|
988
|
+
ingress_name (str):
|
|
989
|
+
The name of the Kubernetes Ingress in the current namespace.
|
|
990
|
+
ingress_body (dict):
|
|
991
|
+
The new / updated ingress body spec.
|
|
992
|
+
(will be merged with existing values)
|
|
993
|
+
|
|
817
994
|
Returns:
|
|
818
|
-
V1Ingress (object):
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
995
|
+
V1Ingress (object):
|
|
996
|
+
The patched Kubernetes Ingress object or None if the call fails
|
|
997
|
+
This is NOT a dict but an object - you have to use the
|
|
998
|
+
"." syntax to access to returned elements
|
|
999
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Ingress.md
|
|
1000
|
+
|
|
822
1001
|
"""
|
|
823
1002
|
|
|
824
1003
|
try:
|
|
@@ -827,12 +1006,11 @@ class K8s:
|
|
|
827
1006
|
namespace=self.get_namespace(),
|
|
828
1007
|
body=ingress_body,
|
|
829
1008
|
)
|
|
830
|
-
except ApiException
|
|
831
|
-
logger.error(
|
|
832
|
-
"Failed to patch Ingress -> %s with -> %s
|
|
1009
|
+
except ApiException:
|
|
1010
|
+
self.logger.error(
|
|
1011
|
+
"Failed to patch Ingress -> '%s' with -> %s",
|
|
833
1012
|
ingress_name,
|
|
834
1013
|
ingress_body,
|
|
835
|
-
str(exception),
|
|
836
1014
|
)
|
|
837
1015
|
return None
|
|
838
1016
|
|
|
@@ -841,9 +1019,13 @@ class K8s:
|
|
|
841
1019
|
# end method definition
|
|
842
1020
|
|
|
843
1021
|
def update_ingress_backend_services(
|
|
844
|
-
self,
|
|
845
|
-
|
|
846
|
-
|
|
1022
|
+
self,
|
|
1023
|
+
ingress_name: str,
|
|
1024
|
+
hostname: str,
|
|
1025
|
+
service_name: str,
|
|
1026
|
+
service_port: int,
|
|
1027
|
+
) -> V1Ingress:
|
|
1028
|
+
"""Update a backend service and port of an Kubernetes Ingress.
|
|
847
1029
|
|
|
848
1030
|
"spec": {
|
|
849
1031
|
"rules": [
|
|
@@ -871,18 +1053,25 @@ class K8s:
|
|
|
871
1053
|
}
|
|
872
1054
|
|
|
873
1055
|
Args:
|
|
874
|
-
ingress_name (str):
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1056
|
+
ingress_name (str):
|
|
1057
|
+
The name of the Kubernetes Ingress in the current namespace.
|
|
1058
|
+
hostname (str):
|
|
1059
|
+
The hostname that should get an updated backend service / port.
|
|
1060
|
+
service_name (str):
|
|
1061
|
+
The new backend service name.
|
|
1062
|
+
service_port (int):
|
|
1063
|
+
The new backend service port.
|
|
1064
|
+
|
|
878
1065
|
Returns:
|
|
879
|
-
V1Ingress (object):
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1066
|
+
V1Ingress (object):
|
|
1067
|
+
The updated Kubernetes Ingress object or None if the call fails.
|
|
1068
|
+
This is NOT a dict but an object - you have to use the "." syntax
|
|
1069
|
+
to access to returned elements
|
|
1070
|
+
See: https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Ingress.md
|
|
1071
|
+
|
|
883
1072
|
"""
|
|
884
1073
|
|
|
885
|
-
ingress = self.get_ingress(ingress_name)
|
|
1074
|
+
ingress = self.get_ingress(ingress_name=ingress_name)
|
|
886
1075
|
if not ingress:
|
|
887
1076
|
return None
|
|
888
1077
|
|
|
@@ -896,8 +1085,8 @@ class K8s:
|
|
|
896
1085
|
backend = path.backend
|
|
897
1086
|
service = backend.service
|
|
898
1087
|
|
|
899
|
-
logger.debug(
|
|
900
|
-
"Replace backend service -> %s (%s) with new backend service -> %s (%s)",
|
|
1088
|
+
self.logger.debug(
|
|
1089
|
+
"Replace backend service -> '%s' (%s) with new backend service -> '%s' (%s)",
|
|
901
1090
|
service.name,
|
|
902
1091
|
service.port.number,
|
|
903
1092
|
service_name,
|
|
@@ -907,25 +1096,24 @@ class K8s:
|
|
|
907
1096
|
service.name = service_name
|
|
908
1097
|
service.port.number = service_port
|
|
909
1098
|
break
|
|
910
|
-
|
|
911
|
-
rule_index += 1
|
|
1099
|
+
rule_index += 1
|
|
912
1100
|
|
|
913
1101
|
if not host:
|
|
914
|
-
logger.error("Cannot find host
|
|
1102
|
+
self.logger.error("Cannot find host.")
|
|
915
1103
|
return None
|
|
916
1104
|
|
|
917
1105
|
body = [
|
|
918
1106
|
{
|
|
919
1107
|
"op": "replace",
|
|
920
1108
|
"path": "/spec/rules/{}/http/paths/0/backend/service/name".format(
|
|
921
|
-
rule_index
|
|
1109
|
+
rule_index,
|
|
922
1110
|
),
|
|
923
1111
|
"value": service_name,
|
|
924
1112
|
},
|
|
925
1113
|
{
|
|
926
1114
|
"op": "replace",
|
|
927
1115
|
"path": "/spec/rules/{}/http/paths/0/backend/service/port/number".format(
|
|
928
|
-
rule_index
|
|
1116
|
+
rule_index,
|
|
929
1117
|
),
|
|
930
1118
|
"value": service_port,
|
|
931
1119
|
},
|
|
@@ -933,16 +1121,17 @@ class K8s:
|
|
|
933
1121
|
|
|
934
1122
|
return self.patch_ingress(ingress_name, body)
|
|
935
1123
|
|
|
1124
|
+
# end method definition
|
|
1125
|
+
|
|
936
1126
|
def verify_pod_status(
|
|
937
1127
|
self,
|
|
938
1128
|
pod_name: str,
|
|
939
|
-
timeout: int =
|
|
1129
|
+
timeout: int = 1800,
|
|
940
1130
|
total_containers: int = 1,
|
|
941
1131
|
ready_containers: int = 1,
|
|
942
1132
|
retry_interval: int = 30,
|
|
943
1133
|
) -> bool:
|
|
944
|
-
"""
|
|
945
|
-
Verifies if a pod is in a 'Ready' state by checking the status of its containers.
|
|
1134
|
+
"""Verify if a pod is in a 'Ready' state by checking the status of its containers.
|
|
946
1135
|
|
|
947
1136
|
This function waits for a Kubernetes pod to reach the 'Ready' state, where a specified number
|
|
948
1137
|
of containers are ready. It checks the pod status at regular intervals and reports the status
|
|
@@ -950,93 +1139,239 @@ class K8s:
|
|
|
950
1139
|
it returns `False`.
|
|
951
1140
|
|
|
952
1141
|
Args:
|
|
953
|
-
pod_name (str):
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1142
|
+
pod_name (str):
|
|
1143
|
+
The name of the pod to check the status for.
|
|
1144
|
+
timeout (int, optional):
|
|
1145
|
+
The maximum time (in seconds) to wait for the pod to become ready. Defaults to 1800.
|
|
1146
|
+
total_containers (int, optional):
|
|
1147
|
+
The total number of containers expected to be running in the pod. Defaults to 1.
|
|
1148
|
+
ready_containers (int, optional):
|
|
1149
|
+
The minimum number of containers that need to be in a ready state. Defaults to 1.
|
|
1150
|
+
retry_interval (int, optional):
|
|
1151
|
+
Time interval (in seconds) between each retry to check pod readiness. Defaults to 30.
|
|
958
1152
|
|
|
959
1153
|
Returns:
|
|
960
|
-
bool:
|
|
961
|
-
|
|
1154
|
+
bool:
|
|
1155
|
+
Returns `True` if the pod reaches the 'Ready' state with the specified number of containers ready
|
|
1156
|
+
within the timeout. Otherwise, returns `False`.
|
|
1157
|
+
|
|
962
1158
|
"""
|
|
963
1159
|
|
|
964
1160
|
def wait_for_pod_ready(pod_name: str, timeout: int) -> bool:
|
|
965
|
-
"""
|
|
966
|
-
Waits until the pod is in the 'Ready' state with the specified number of containers ready.
|
|
1161
|
+
"""Wait until the pod is in the 'Ready' state with the specified number of containers ready.
|
|
967
1162
|
|
|
968
|
-
This
|
|
1163
|
+
This sub method repeatedly checks the readiness of the pod, logging the
|
|
969
1164
|
status of the containers. If the pod does not exist, it retries after waiting
|
|
970
1165
|
and logs detailed information at each step.
|
|
971
1166
|
|
|
972
1167
|
Args:
|
|
973
|
-
pod_name (str):
|
|
974
|
-
|
|
1168
|
+
pod_name (str):
|
|
1169
|
+
The name of the pod to check the status for.
|
|
1170
|
+
timeout (int):
|
|
1171
|
+
The maximum time (in seconds) to wait for the pod to become ready.
|
|
975
1172
|
|
|
976
1173
|
Returns:
|
|
977
|
-
bool:
|
|
978
|
-
|
|
1174
|
+
bool:
|
|
1175
|
+
Returns `True` if the pod is ready with the specified number of containers in a 'Ready' state.
|
|
1176
|
+
Otherwise, returns `False`.
|
|
1177
|
+
|
|
979
1178
|
"""
|
|
1179
|
+
|
|
980
1180
|
elapsed_time = 0 # Initialize elapsed time
|
|
981
1181
|
|
|
982
1182
|
while elapsed_time < timeout:
|
|
983
|
-
pod = self.get_pod(pod_name)
|
|
1183
|
+
pod = self.get_pod(pod_name=pod_name)
|
|
984
1184
|
|
|
985
1185
|
if not pod:
|
|
986
|
-
logger.
|
|
987
|
-
"Pod -> %s does not exist, waiting 300 seconds to retry.",
|
|
1186
|
+
self.logger.warning(
|
|
1187
|
+
"Pod -> '%s' does not exist, waiting 300 seconds to retry.",
|
|
988
1188
|
pod_name,
|
|
989
1189
|
)
|
|
990
1190
|
time.sleep(300)
|
|
991
|
-
pod = self.get_pod(pod_name)
|
|
1191
|
+
pod = self.get_pod(pod_name=pod_name)
|
|
992
1192
|
|
|
993
1193
|
if not pod:
|
|
994
|
-
logger.error(
|
|
995
|
-
"Pod -> %s still does not exist after retry!",
|
|
1194
|
+
self.logger.error(
|
|
1195
|
+
"Pod -> '%s' still does not exist after retry!",
|
|
1196
|
+
pod_name,
|
|
996
1197
|
)
|
|
997
1198
|
return False
|
|
998
1199
|
|
|
999
1200
|
# Get the ready status of containers
|
|
1000
1201
|
container_statuses = pod.status.container_statuses
|
|
1001
|
-
if container_statuses and all(
|
|
1002
|
-
|
|
1003
|
-
):
|
|
1004
|
-
current_ready_containers = sum(
|
|
1005
|
-
1 for c in container_statuses if c.ready
|
|
1006
|
-
)
|
|
1202
|
+
if container_statuses and all(container.ready for container in container_statuses):
|
|
1203
|
+
current_ready_containers = sum(1 for c in container_statuses if c.ready)
|
|
1007
1204
|
total_containers_in_pod = len(container_statuses)
|
|
1008
1205
|
|
|
1009
|
-
if
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
):
|
|
1013
|
-
logger.info(
|
|
1014
|
-
"Pod -> %s is ready with %d/%d containers.",
|
|
1206
|
+
if current_ready_containers >= ready_containers and total_containers_in_pod == total_containers:
|
|
1207
|
+
self.logger.info(
|
|
1208
|
+
"Pod -> '%s' is ready with %d/%d containers.",
|
|
1015
1209
|
pod_name,
|
|
1016
1210
|
current_ready_containers,
|
|
1017
1211
|
total_containers_in_pod,
|
|
1018
1212
|
)
|
|
1019
1213
|
return True
|
|
1020
1214
|
else:
|
|
1021
|
-
logger.debug(
|
|
1022
|
-
"Pod -> %s is not yet ready (%d/%d).",
|
|
1215
|
+
self.logger.debug(
|
|
1216
|
+
"Pod -> '%s' is not yet ready (%d/%d).",
|
|
1023
1217
|
pod_name,
|
|
1024
1218
|
current_ready_containers,
|
|
1025
1219
|
total_containers_in_pod,
|
|
1026
1220
|
)
|
|
1027
1221
|
else:
|
|
1028
|
-
logger.debug("Pod -> %s is not yet ready.", pod_name)
|
|
1222
|
+
self.logger.debug("Pod -> '%s' is not yet ready.", pod_name)
|
|
1029
1223
|
|
|
1030
|
-
logger.info(
|
|
1031
|
-
|
|
1224
|
+
self.logger.info(
|
|
1225
|
+
"Waiting %s seconds before next pod status check.",
|
|
1226
|
+
retry_interval,
|
|
1032
1227
|
)
|
|
1033
1228
|
time.sleep(
|
|
1034
|
-
retry_interval
|
|
1229
|
+
retry_interval,
|
|
1035
1230
|
) # Sleep for the retry interval before checking again
|
|
1036
1231
|
elapsed_time += retry_interval
|
|
1037
1232
|
|
|
1038
|
-
logger.error(
|
|
1233
|
+
self.logger.error(
|
|
1234
|
+
"Pod -> '%s' is not ready after %d seconds.",
|
|
1235
|
+
pod_name,
|
|
1236
|
+
timeout,
|
|
1237
|
+
)
|
|
1039
1238
|
return False
|
|
1040
1239
|
|
|
1240
|
+
# end method definition
|
|
1241
|
+
|
|
1041
1242
|
# Wait until the pod is ready
|
|
1042
|
-
return wait_for_pod_ready(pod_name, timeout)
|
|
1243
|
+
return wait_for_pod_ready(pod_name=pod_name, timeout=timeout)
|
|
1244
|
+
|
|
1245
|
+
# end method definition
|
|
1246
|
+
|
|
1247
|
+
def verify_pod_deleted(
|
|
1248
|
+
self,
|
|
1249
|
+
pod_name: str,
|
|
1250
|
+
timeout: int = 300,
|
|
1251
|
+
retry_interval: int = 30,
|
|
1252
|
+
) -> bool:
|
|
1253
|
+
"""Verify if a pod is deleted within the specified timeout.
|
|
1254
|
+
|
|
1255
|
+
Args:
|
|
1256
|
+
pod_name (str):
|
|
1257
|
+
The name of the pod to check.
|
|
1258
|
+
timeout (int):
|
|
1259
|
+
Maximum time to wait for the pod to be deleted (in seconds).
|
|
1260
|
+
retry_interval:
|
|
1261
|
+
Time interval between retries (in seconds).
|
|
1262
|
+
|
|
1263
|
+
Returns:
|
|
1264
|
+
bool:
|
|
1265
|
+
True if the pod is deleted, False otherwise.
|
|
1266
|
+
|
|
1267
|
+
"""
|
|
1268
|
+
|
|
1269
|
+
elapsed_time = 0 # Initialize elapsed time
|
|
1270
|
+
|
|
1271
|
+
while elapsed_time < timeout:
|
|
1272
|
+
pod = self.get_pod(pod_name=pod_name)
|
|
1273
|
+
|
|
1274
|
+
if not pod:
|
|
1275
|
+
self.logger.info("Pod -> '%s' has been deleted successfully.", pod_name)
|
|
1276
|
+
return True
|
|
1277
|
+
|
|
1278
|
+
self.logger.debug(
|
|
1279
|
+
"Pod -> '%s' still exists. Waiting %s seconds before next check.",
|
|
1280
|
+
pod_name,
|
|
1281
|
+
retry_interval,
|
|
1282
|
+
)
|
|
1283
|
+
time.sleep(retry_interval)
|
|
1284
|
+
elapsed_time += retry_interval
|
|
1285
|
+
|
|
1286
|
+
self.logger.error("Pod -> '%s' was not deleted within %d seconds.", pod_name, timeout)
|
|
1287
|
+
|
|
1288
|
+
return False
|
|
1289
|
+
|
|
1290
|
+
# end method definition
|
|
1291
|
+
|
|
1292
|
+
def restart_deployment(self, deployment_name: str) -> bool:
|
|
1293
|
+
"""Restart a Kubernetes deployment using rolling restart.
|
|
1294
|
+
|
|
1295
|
+
Args:
|
|
1296
|
+
deployment_name (str):
|
|
1297
|
+
Name of the Kubernetes deployment.
|
|
1298
|
+
|
|
1299
|
+
Returns:
|
|
1300
|
+
bool:
|
|
1301
|
+
True if successful, False otherwise.
|
|
1302
|
+
|
|
1303
|
+
"""
|
|
1304
|
+
|
|
1305
|
+
now = datetime.now(timezone.utc).isoformat(timespec="seconds") + "Z"
|
|
1306
|
+
|
|
1307
|
+
body = {
|
|
1308
|
+
"spec": {
|
|
1309
|
+
"template": {
|
|
1310
|
+
"metadata": {
|
|
1311
|
+
"annotations": {
|
|
1312
|
+
"kubectl.kubernetes.io/restartedAt": now,
|
|
1313
|
+
},
|
|
1314
|
+
},
|
|
1315
|
+
},
|
|
1316
|
+
},
|
|
1317
|
+
}
|
|
1318
|
+
try:
|
|
1319
|
+
self.get_apps_v1_api().patch_namespaced_deployment(
|
|
1320
|
+
deployment_name,
|
|
1321
|
+
self.get_namespace(),
|
|
1322
|
+
body,
|
|
1323
|
+
pretty="true",
|
|
1324
|
+
)
|
|
1325
|
+
|
|
1326
|
+
except ApiException:
|
|
1327
|
+
self.logger.exception(
|
|
1328
|
+
"Failed to restart deployment -> '%s'!",
|
|
1329
|
+
deployment_name,
|
|
1330
|
+
)
|
|
1331
|
+
return False
|
|
1332
|
+
|
|
1333
|
+
else:
|
|
1334
|
+
return True
|
|
1335
|
+
|
|
1336
|
+
# end method definition
|
|
1337
|
+
|
|
1338
|
+
def restart_stateful_set(self, sts_name: str) -> bool:
|
|
1339
|
+
"""Restart a Kubernetes stateful set using rolling restart.
|
|
1340
|
+
|
|
1341
|
+
Args:
|
|
1342
|
+
sts_name (str):
|
|
1343
|
+
Name of the Kubernetes statefulset.
|
|
1344
|
+
|
|
1345
|
+
Returns:
|
|
1346
|
+
bool:
|
|
1347
|
+
True if successful, False otherwise.
|
|
1348
|
+
|
|
1349
|
+
"""
|
|
1350
|
+
|
|
1351
|
+
now = datetime.now(timezone.utc).isoformat(timespec="seconds") + "Z"
|
|
1352
|
+
|
|
1353
|
+
body = {
|
|
1354
|
+
"spec": {
|
|
1355
|
+
"template": {
|
|
1356
|
+
"metadata": {
|
|
1357
|
+
"annotations": {
|
|
1358
|
+
"kubectl.kubernetes.io/restartedAt": now,
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
},
|
|
1363
|
+
}
|
|
1364
|
+
try:
|
|
1365
|
+
self.get_apps_v1_api().patch_namespaced_stateful_set(sts_name, self.get_namespace(), body, pretty="true")
|
|
1366
|
+
|
|
1367
|
+
except ApiException:
|
|
1368
|
+
self.logger.exception(
|
|
1369
|
+
"Failed to restart stateful set -> '%s'!",
|
|
1370
|
+
sts_name,
|
|
1371
|
+
)
|
|
1372
|
+
return False
|
|
1373
|
+
|
|
1374
|
+
else:
|
|
1375
|
+
return True
|
|
1376
|
+
|
|
1377
|
+
# end method definition
|