openshift-python-wrapper 4.11.61__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.
- ocp_resources/__init__.py +0 -0
- ocp_resources/api_service.py +9 -0
- ocp_resources/backup.py +52 -0
- ocp_resources/benchmark.py +92 -0
- ocp_resources/catalog_source.py +63 -0
- ocp_resources/catalog_source_config.py +86 -0
- ocp_resources/cdi.py +13 -0
- ocp_resources/cdi_config.py +52 -0
- ocp_resources/chaos_engine.py +30 -0
- ocp_resources/chaos_result.py +11 -0
- ocp_resources/cluster_operator.py +5 -0
- ocp_resources/cluster_role.py +70 -0
- ocp_resources/cluster_role_binding.py +36 -0
- ocp_resources/cluster_service_version.py +8 -0
- ocp_resources/cluster_version.py +5 -0
- ocp_resources/configmap.py +37 -0
- ocp_resources/console_cli_download.py +9 -0
- ocp_resources/console_plugin.py +9 -0
- ocp_resources/console_quick_start.py +9 -0
- ocp_resources/constants.py +26 -0
- ocp_resources/csidriver.py +9 -0
- ocp_resources/custom_resource_definition.py +5 -0
- ocp_resources/daemonset.py +61 -0
- ocp_resources/data_import_cron.py +100 -0
- ocp_resources/data_source.py +59 -0
- ocp_resources/datavolume.py +275 -0
- ocp_resources/deployment.py +65 -0
- ocp_resources/destination_rule.py +11 -0
- ocp_resources/event.py +114 -0
- ocp_resources/forklift_controller.py +10 -0
- ocp_resources/gateway.py +11 -0
- ocp_resources/hook.py +52 -0
- ocp_resources/host.py +64 -0
- ocp_resources/hostpath_provisioner.py +50 -0
- ocp_resources/hyperconverged.py +43 -0
- ocp_resources/image.py +9 -0
- ocp_resources/image_content_source_policy.py +9 -0
- ocp_resources/image_stream.py +48 -0
- ocp_resources/imagestreamtag.py +9 -0
- ocp_resources/infrastructure.py +18 -0
- ocp_resources/installplan.py +8 -0
- ocp_resources/job.py +65 -0
- ocp_resources/kube_descheduler.py +65 -0
- ocp_resources/kubevirt.py +5 -0
- ocp_resources/kubevirt_common_templates_bundle.py +5 -0
- ocp_resources/kubevirt_metrics_aggregation.py +5 -0
- ocp_resources/kubevirt_node_labeller_bundle.py +5 -0
- ocp_resources/kubevirt_template_validaotr.py +5 -0
- ocp_resources/logger.py +76 -0
- ocp_resources/machine.py +50 -0
- ocp_resources/machine_config.py +5 -0
- ocp_resources/machine_config_pool.py +9 -0
- ocp_resources/machine_health_check.py +76 -0
- ocp_resources/machine_set.py +189 -0
- ocp_resources/migration.py +63 -0
- ocp_resources/migration_policy.py +66 -0
- ocp_resources/mtv.py +171 -0
- ocp_resources/mutating_webhook_config.py +9 -0
- ocp_resources/namespace.py +40 -0
- ocp_resources/network.py +5 -0
- ocp_resources/network_addons_config.py +9 -0
- ocp_resources/network_attachment_definition.py +24 -0
- ocp_resources/network_map.py +64 -0
- ocp_resources/network_policy.py +9 -0
- ocp_resources/nmstate.py +9 -0
- ocp_resources/node.py +41 -0
- ocp_resources/node_health_check.py +10 -0
- ocp_resources/node_maintenance.py +40 -0
- ocp_resources/node_network_configuration_enactment.py +20 -0
- ocp_resources/node_network_configuration_policy.py +472 -0
- ocp_resources/node_network_state.py +121 -0
- ocp_resources/oauth.py +9 -0
- ocp_resources/operator_condition.py +5 -0
- ocp_resources/operator_group.py +37 -0
- ocp_resources/operator_hub.py +5 -0
- ocp_resources/operator_source.py +50 -0
- ocp_resources/package_manifest.py +5 -0
- ocp_resources/peer_authentication.py +18 -0
- ocp_resources/persistent_volume.py +27 -0
- ocp_resources/persistent_volume_claim.py +103 -0
- ocp_resources/plan.py +143 -0
- ocp_resources/pod.py +175 -0
- ocp_resources/pod_disruption_budget.py +53 -0
- ocp_resources/priority_class.py +48 -0
- ocp_resources/project.py +60 -0
- ocp_resources/prometheus.py +9 -0
- ocp_resources/prometheus_rule.py +9 -0
- ocp_resources/provider.py +59 -0
- ocp_resources/replicaset.py +11 -0
- ocp_resources/resource.py +1383 -0
- ocp_resources/restore.py +52 -0
- ocp_resources/role.py +11 -0
- ocp_resources/role_binding.py +67 -0
- ocp_resources/route.py +76 -0
- ocp_resources/secret.py +75 -0
- ocp_resources/security_context_constraints.py +9 -0
- ocp_resources/service.py +16 -0
- ocp_resources/service_account.py +9 -0
- ocp_resources/service_mesh_control_plane.py +11 -0
- ocp_resources/service_mesh_member_roll.py +14 -0
- ocp_resources/service_monitor.py +9 -0
- ocp_resources/sriov_network.py +54 -0
- ocp_resources/sriov_network_node_policy.py +67 -0
- ocp_resources/sriov_network_node_state.py +44 -0
- ocp_resources/ssp.py +9 -0
- ocp_resources/storage_class.py +47 -0
- ocp_resources/storage_map.py +67 -0
- ocp_resources/storage_profile.py +7 -0
- ocp_resources/subscription.py +66 -0
- ocp_resources/tekton_tasks.py +9 -0
- ocp_resources/template.py +74 -0
- ocp_resources/upload_token_request.py +39 -0
- ocp_resources/utils.py +278 -0
- ocp_resources/validating_webhook_config.py +9 -0
- ocp_resources/virtual_machine.py +177 -0
- ocp_resources/virtual_machine_Instance_replica_set.py +9 -0
- ocp_resources/virtual_machine_cluster_flavor.py +5 -0
- ocp_resources/virtual_machine_flavor.py +5 -0
- ocp_resources/virtual_machine_import.py +307 -0
- ocp_resources/virtual_machine_instance.py +322 -0
- ocp_resources/virtual_machine_instance_migration.py +36 -0
- ocp_resources/virtual_machine_instance_preset.py +9 -0
- ocp_resources/virtual_machine_restore.py +99 -0
- ocp_resources/virtual_machine_snapshot.py +96 -0
- ocp_resources/virtual_service.py +11 -0
- ocp_resources/vm_import_config.py +9 -0
- ocp_resources/volume_snapshot.py +9 -0
- ocp_resources/volume_snapshot_class.py +9 -0
- openshift_python_wrapper-4.11.61.dist-info/LICENSE +201 -0
- openshift_python_wrapper-4.11.61.dist-info/METADATA +188 -0
- openshift_python_wrapper-4.11.61.dist-info/RECORD +132 -0
- openshift_python_wrapper-4.11.61.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from ocp_resources.constants import TIMEOUT_4MINUTES
|
|
2
|
+
from ocp_resources.resource import NamespacedResource
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Subscription(NamespacedResource):
|
|
6
|
+
api_group = NamespacedResource.ApiGroup.OPERATORS_COREOS_COM
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
name=None,
|
|
11
|
+
namespace=None,
|
|
12
|
+
client=None,
|
|
13
|
+
source=None,
|
|
14
|
+
source_namespace=None,
|
|
15
|
+
install_plan_approval=None,
|
|
16
|
+
channel=None,
|
|
17
|
+
starting_csv=None,
|
|
18
|
+
node_selector=None,
|
|
19
|
+
tolerations=None,
|
|
20
|
+
teardown=True,
|
|
21
|
+
yaml_file=None,
|
|
22
|
+
delete_timeout=TIMEOUT_4MINUTES,
|
|
23
|
+
**kwargs,
|
|
24
|
+
):
|
|
25
|
+
super().__init__(
|
|
26
|
+
client=client,
|
|
27
|
+
name=name,
|
|
28
|
+
namespace=namespace,
|
|
29
|
+
teardown=teardown,
|
|
30
|
+
yaml_file=yaml_file,
|
|
31
|
+
delete_timeout=delete_timeout,
|
|
32
|
+
**kwargs,
|
|
33
|
+
)
|
|
34
|
+
self.source = source
|
|
35
|
+
self.source_namespace = source_namespace
|
|
36
|
+
self.channel = channel
|
|
37
|
+
self.install_plan_approval = install_plan_approval
|
|
38
|
+
self.starting_csv = starting_csv
|
|
39
|
+
self.node_selector = node_selector
|
|
40
|
+
self.tolerations = tolerations
|
|
41
|
+
|
|
42
|
+
def to_dict(self):
|
|
43
|
+
super().to_dict()
|
|
44
|
+
if not self.yaml_file:
|
|
45
|
+
self.res.update(
|
|
46
|
+
{
|
|
47
|
+
"spec": {
|
|
48
|
+
"sourceNamespace": self.source_namespace,
|
|
49
|
+
"source": self.source,
|
|
50
|
+
"name": self.name,
|
|
51
|
+
"channel": self.channel,
|
|
52
|
+
"installPlanApproval": self.install_plan_approval,
|
|
53
|
+
"startingCSV": self.starting_csv,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if self.node_selector:
|
|
59
|
+
self.res["spec"].setdefault("config", {}).setdefault(
|
|
60
|
+
"nodeSelector", {}
|
|
61
|
+
).update(self.node_selector)
|
|
62
|
+
|
|
63
|
+
if self.tolerations:
|
|
64
|
+
self.res["spec"].setdefault("config", {}).setdefault(
|
|
65
|
+
"tolerations", []
|
|
66
|
+
).append(self.tolerations)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from ocp_resources.resource import NamespacedResource, Resource
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Template(NamespacedResource):
|
|
7
|
+
api_group = NamespacedResource.ApiGroup.TEMPLATE_OPENSHIFT_IO
|
|
8
|
+
singular_name = "template"
|
|
9
|
+
|
|
10
|
+
class Labels:
|
|
11
|
+
BASE = f"{NamespacedResource.ApiGroup.TEMPLATE_KUBEVIRT_IO}/type=base"
|
|
12
|
+
FLAVOR = "flavor.template.kubevirt.io"
|
|
13
|
+
OS = "os.template.kubevirt.io"
|
|
14
|
+
WORKLOAD = "workload.template.kubevirt.io"
|
|
15
|
+
|
|
16
|
+
class Workload:
|
|
17
|
+
DESKTOP = "desktop"
|
|
18
|
+
HIGHPERFORMANCE = "highperformance"
|
|
19
|
+
SERVER = "server"
|
|
20
|
+
SAPHANA = "saphana"
|
|
21
|
+
|
|
22
|
+
class Flavor:
|
|
23
|
+
LARGE = "large"
|
|
24
|
+
MEDIUM = "medium"
|
|
25
|
+
SMALL = "small"
|
|
26
|
+
TINY = "tiny"
|
|
27
|
+
|
|
28
|
+
class Annotations:
|
|
29
|
+
DEPRECATED = f"{NamespacedResource.ApiGroup.TEMPLATE_KUBEVIRT_IO}/deprecated"
|
|
30
|
+
PROVIDER = f"{NamespacedResource.ApiGroup.TEMPLATE_KUBEVIRT_IO}/provider"
|
|
31
|
+
PROVIDER_SUPPORT_LEVEL = (
|
|
32
|
+
f"{NamespacedResource.ApiGroup.TEMPLATE_KUBEVIRT_IO}/provider-support-level"
|
|
33
|
+
)
|
|
34
|
+
PROVIDER_URL = (
|
|
35
|
+
f"{NamespacedResource.ApiGroup.TEMPLATE_KUBEVIRT_IO}/provider-url"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
class VMAnnotations:
|
|
39
|
+
OS = f"{Resource.ApiGroup.VM_KUBEVIRT_IO}/os"
|
|
40
|
+
FLAVOR = f"{Resource.ApiGroup.VM_KUBEVIRT_IO}/flavor"
|
|
41
|
+
WORKLOAD = f"{Resource.ApiGroup.VM_KUBEVIRT_IO}/workload"
|
|
42
|
+
|
|
43
|
+
def process(self, client=None, **kwargs):
|
|
44
|
+
client = client or self.client
|
|
45
|
+
instance_dict = self.instance.to_dict()
|
|
46
|
+
params = instance_dict["parameters"]
|
|
47
|
+
# filling the template parameters with given kwargs
|
|
48
|
+
for param in params:
|
|
49
|
+
try:
|
|
50
|
+
param["value"] = kwargs[param["name"]]
|
|
51
|
+
except KeyError:
|
|
52
|
+
continue
|
|
53
|
+
instance_dict["parameters"] = params
|
|
54
|
+
# TODO: remove after fix - https://issues.redhat.com/browse/KNIP-1055 (bug 1753554)
|
|
55
|
+
# For template validator to be used - template namespace needs to be updated
|
|
56
|
+
instance_dict["objects"][0]["metadata"]["labels"][
|
|
57
|
+
"vm.kubevirt.io/template.namespace"
|
|
58
|
+
] = instance_dict["metadata"]["namespace"]
|
|
59
|
+
instance_json = json.dumps(instance_dict)
|
|
60
|
+
body = json.loads(instance_json)
|
|
61
|
+
response = client.request(
|
|
62
|
+
method="Post",
|
|
63
|
+
path=f"/apis/{self.api_version}/namespaces/openshift/processedtemplates",
|
|
64
|
+
body=body,
|
|
65
|
+
)
|
|
66
|
+
return response.to_dict()["objects"]
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def generate_template_labels(os, workload, flavor):
|
|
70
|
+
return [
|
|
71
|
+
f"{Template.Labels.OS}/{os}",
|
|
72
|
+
f"{Template.Labels.WORKLOAD}/{getattr(Template.Workload, workload.upper())}",
|
|
73
|
+
f"{Template.Labels.FLAVOR}/{getattr(Template.Flavor, flavor.upper())}",
|
|
74
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ocp_resources.constants import TIMEOUT_4MINUTES
|
|
4
|
+
from ocp_resources.resource import NamespacedResource
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UploadTokenRequest(NamespacedResource):
|
|
8
|
+
"""
|
|
9
|
+
OpenShift UploadTokenRequest object.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
api_group = NamespacedResource.ApiGroup.UPLOAD_CDI_KUBEVIRT_IO
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
name=None,
|
|
17
|
+
namespace=None,
|
|
18
|
+
client=None,
|
|
19
|
+
pvc_name=None,
|
|
20
|
+
teardown=True,
|
|
21
|
+
yaml_file=None,
|
|
22
|
+
delete_timeout=TIMEOUT_4MINUTES,
|
|
23
|
+
**kwargs,
|
|
24
|
+
):
|
|
25
|
+
super().__init__(
|
|
26
|
+
name=name,
|
|
27
|
+
namespace=namespace,
|
|
28
|
+
client=client,
|
|
29
|
+
teardown=teardown,
|
|
30
|
+
yaml_file=yaml_file,
|
|
31
|
+
delete_timeout=delete_timeout,
|
|
32
|
+
**kwargs,
|
|
33
|
+
)
|
|
34
|
+
self.pvc_name = pvc_name
|
|
35
|
+
|
|
36
|
+
def to_dict(self):
|
|
37
|
+
super().to_dict()
|
|
38
|
+
if not self.yaml_file:
|
|
39
|
+
self.res.update({"spec": {"pvcName": self.pvc_name}})
|
ocp_resources/utils.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
|
|
5
|
+
from ocp_resources.logger import get_logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
LOGGER = get_logger(name=__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TimeoutExpiredError(Exception):
|
|
12
|
+
def __init__(self, value):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.value = value
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return f"Timed Out: {self.value}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TimeoutSampler:
|
|
21
|
+
"""
|
|
22
|
+
Samples the function output.
|
|
23
|
+
|
|
24
|
+
This is a generator object that at first yields the output of callable.
|
|
25
|
+
After the yield, it either raises instance of `TimeoutExpiredError` or
|
|
26
|
+
sleeps `sleep` seconds.
|
|
27
|
+
|
|
28
|
+
Yielding the output allows you to handle every value as you wish.
|
|
29
|
+
|
|
30
|
+
exceptions_dict should be in the following format:
|
|
31
|
+
{
|
|
32
|
+
exception0: [exception0_msg0],
|
|
33
|
+
exception1: [
|
|
34
|
+
exception1_msg0,
|
|
35
|
+
exception1_msg1
|
|
36
|
+
],
|
|
37
|
+
exception2: []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
If an exception is raised within `func`:
|
|
41
|
+
Example exception inheritance:
|
|
42
|
+
class Exception
|
|
43
|
+
class AExampleError(Exception)
|
|
44
|
+
class BExampleError(AExampleError)
|
|
45
|
+
|
|
46
|
+
The raised exception's class will fall into one of three categories:
|
|
47
|
+
1. An exception class specifically declared in exceptions_dict
|
|
48
|
+
exceptions_dict: {BExampleError: []}
|
|
49
|
+
raise: BExampleError
|
|
50
|
+
result: continue
|
|
51
|
+
|
|
52
|
+
2. A child class inherited from an exception class in exceptions_dict
|
|
53
|
+
exceptions_dict: {AExampleError: []}
|
|
54
|
+
raise: BExampleError
|
|
55
|
+
result: continue
|
|
56
|
+
|
|
57
|
+
3. Everything else, this will always re-raise the exception
|
|
58
|
+
exceptions_dict: {BExampleError: []}
|
|
59
|
+
raise: AExampleError
|
|
60
|
+
result: raise
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
wait_timeout (int): Time in seconds to wait for func to return a value equating to True
|
|
64
|
+
sleep (int): Time in seconds between calls to func
|
|
65
|
+
func (Callable): to be wrapped by TimeoutSampler
|
|
66
|
+
exceptions_dict (dict): Exception handling definition
|
|
67
|
+
print_log (bool): Print elapsed time to log
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
wait_timeout,
|
|
73
|
+
sleep,
|
|
74
|
+
func,
|
|
75
|
+
exceptions_dict=None,
|
|
76
|
+
print_log=True,
|
|
77
|
+
**func_kwargs,
|
|
78
|
+
):
|
|
79
|
+
self.wait_timeout = wait_timeout
|
|
80
|
+
self.sleep = sleep
|
|
81
|
+
self.func = func
|
|
82
|
+
self.func_kwargs = func_kwargs
|
|
83
|
+
self.elapsed_time = None
|
|
84
|
+
self.print_log = print_log
|
|
85
|
+
|
|
86
|
+
self.exceptions_dict = exceptions_dict or {Exception: []}
|
|
87
|
+
self._exceptions = tuple(self.exceptions_dict.keys())
|
|
88
|
+
|
|
89
|
+
def _get_func_info(self, _func, type_):
|
|
90
|
+
# If func is partial function.
|
|
91
|
+
if getattr(_func, "func", None):
|
|
92
|
+
return self._get_func_info(_func=_func.func, type_=type_)
|
|
93
|
+
|
|
94
|
+
res = getattr(_func, type_, None)
|
|
95
|
+
if res:
|
|
96
|
+
# If func is lambda function.
|
|
97
|
+
if _func.__name__ == "<lambda>":
|
|
98
|
+
if type_ == "__module__":
|
|
99
|
+
return f"{res}.{_func.__qualname__.split('.')[1]}"
|
|
100
|
+
|
|
101
|
+
elif type_ == "__name__":
|
|
102
|
+
free_vars = _func.__code__.co_freevars
|
|
103
|
+
free_vars = f"{'.'.join(free_vars)}." if free_vars else ""
|
|
104
|
+
return f"lambda: {free_vars}{'.'.join(_func.__code__.co_names)}"
|
|
105
|
+
return res
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def _func_log(self):
|
|
109
|
+
"""
|
|
110
|
+
Returns:
|
|
111
|
+
string: `func` information to include in log message
|
|
112
|
+
"""
|
|
113
|
+
_func_kwargs = f"Kwargs: {self.func_kwargs}" if self.func_kwargs else ""
|
|
114
|
+
_func_module = self._get_func_info(_func=self.func, type_="__module__")
|
|
115
|
+
_func_name = self._get_func_info(_func=self.func, type_="__name__")
|
|
116
|
+
return f"Function: {_func_module}.{_func_name} {_func_kwargs}".strip()
|
|
117
|
+
|
|
118
|
+
def __iter__(self):
|
|
119
|
+
"""
|
|
120
|
+
Iterator
|
|
121
|
+
|
|
122
|
+
Yields:
|
|
123
|
+
any: Return value from `func`
|
|
124
|
+
"""
|
|
125
|
+
timeout_watch = TimeoutWatch(timeout=self.wait_timeout)
|
|
126
|
+
if self.print_log:
|
|
127
|
+
LOGGER.info(
|
|
128
|
+
f"Waiting for {self.wait_timeout} seconds, retry every {self.sleep} seconds. ({self._func_log})"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
last_exp = None
|
|
132
|
+
while timeout_watch.remaining_time() > 0:
|
|
133
|
+
try:
|
|
134
|
+
self.elapsed_time = self.wait_timeout - timeout_watch.remaining_time()
|
|
135
|
+
yield self.func(**self.func_kwargs)
|
|
136
|
+
self.elapsed_time = None
|
|
137
|
+
|
|
138
|
+
time.sleep(self.sleep)
|
|
139
|
+
|
|
140
|
+
except self._exceptions as exp:
|
|
141
|
+
last_exp = exp
|
|
142
|
+
last_exp_log = self._get_exception_log(exp=last_exp)
|
|
143
|
+
if self._is_raisable_exception(exp=last_exp):
|
|
144
|
+
LOGGER.error(last_exp_log)
|
|
145
|
+
raise exp
|
|
146
|
+
|
|
147
|
+
self.elapsed_time = None
|
|
148
|
+
time.sleep(self.sleep)
|
|
149
|
+
|
|
150
|
+
finally:
|
|
151
|
+
if self.elapsed_time and self.print_log:
|
|
152
|
+
LOGGER.info(f"Elapsed time: {self.elapsed_time}")
|
|
153
|
+
|
|
154
|
+
raise TimeoutExpiredError(self._get_exception_log(exp=last_exp))
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def _is_exception_matched(exp, exception_messages):
|
|
158
|
+
"""
|
|
159
|
+
Args:
|
|
160
|
+
exp (Exception): Exception object raised by `func`
|
|
161
|
+
exception_messages (list): Either an empty list allowing all text,
|
|
162
|
+
or a list of allowed strings to match against the exception text.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
bool: True if exception text is allowed or no exception text given, False otherwise
|
|
166
|
+
"""
|
|
167
|
+
if not exception_messages:
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
# Prevent match if provided with empty string
|
|
171
|
+
return any(msg and msg in str(exp) for msg in exception_messages)
|
|
172
|
+
|
|
173
|
+
def _is_raisable_exception(self, exp):
|
|
174
|
+
"""
|
|
175
|
+
Verify whether exception should be raised during execution of `func`
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
exp (Exception): Exception object raised by `func`
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
bool: True if exp should be raised, False otherwise
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
for entry in self.exceptions_dict:
|
|
185
|
+
if isinstance(exp, entry): # Check inheritance for raised exception
|
|
186
|
+
exception_messages = self.exceptions_dict.get(entry)
|
|
187
|
+
if self._is_exception_matched(
|
|
188
|
+
exp=exp, exception_messages=exception_messages
|
|
189
|
+
):
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
def _get_exception_log(self, exp):
|
|
195
|
+
"""
|
|
196
|
+
Args:
|
|
197
|
+
exp (any): Raised exception
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
string: Log message for exception
|
|
201
|
+
"""
|
|
202
|
+
exp_name = exp.__class__.__name__ if exp else "N/A"
|
|
203
|
+
|
|
204
|
+
last_exception_log = f"Last exception: {exp_name}: {exp}"
|
|
205
|
+
return f"{self.wait_timeout}\n{self._func_log}\n{last_exception_log}"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TimeoutWatch:
|
|
209
|
+
"""
|
|
210
|
+
A time counter allowing to determine the time remaining since the start
|
|
211
|
+
of a given interval
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def __init__(self, timeout):
|
|
215
|
+
self.timeout = timeout
|
|
216
|
+
self.start_time = time.time()
|
|
217
|
+
|
|
218
|
+
def remaining_time(self):
|
|
219
|
+
"""
|
|
220
|
+
Return the remaining part of timeout since the object was created.
|
|
221
|
+
"""
|
|
222
|
+
return self.start_time + self.timeout - time.time()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def skip_existing_resource_creation_teardown(
|
|
226
|
+
resource, export_str, user_exported_args, check_exists=True
|
|
227
|
+
):
|
|
228
|
+
"""
|
|
229
|
+
Args:
|
|
230
|
+
resource (Resource): Resource to match against.
|
|
231
|
+
export_str (str): The user export str. (REUSE_IF_RESOURCE_EXISTS or SKIP_RESOURCE_TEARDOWN)
|
|
232
|
+
user_exported_args (str): Value of export_str. (os.environ.get)
|
|
233
|
+
check_exists (bool): Check if resource exists before return. (applied only for REUSE_IF_RESOURCE_EXISTS)
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Resource or None: If resource match.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
def _return_resource(_resource, _check_exists, _msg):
|
|
240
|
+
"""
|
|
241
|
+
Return the resource if exists when got _check_exists else return None.
|
|
242
|
+
If _check_exists=False returns the resource.
|
|
243
|
+
"""
|
|
244
|
+
if not _check_exists: # In case of SKIP_RESOURCE_TEARDOWN
|
|
245
|
+
return _resource
|
|
246
|
+
|
|
247
|
+
elif _resource.exists: # In case of REUSE_IF_RESOURCE_EXISTS
|
|
248
|
+
LOGGER.warning(_msg)
|
|
249
|
+
return _resource
|
|
250
|
+
|
|
251
|
+
resource.to_dict()
|
|
252
|
+
resource_name = resource.res["metadata"]["name"]
|
|
253
|
+
resource_namespace = resource.res["metadata"].get("namespace")
|
|
254
|
+
skip_create_warn_msg = (
|
|
255
|
+
f"Skip resource {resource.kind} {resource_name} creation, using existing one."
|
|
256
|
+
f" Got {export_str}={user_exported_args}"
|
|
257
|
+
)
|
|
258
|
+
user_args = yaml.safe_load(stream=user_exported_args)
|
|
259
|
+
if resource.kind in user_args:
|
|
260
|
+
_resource_args = user_args[resource.kind]
|
|
261
|
+
if not _resource_args:
|
|
262
|
+
# Match only by kind, user didn't send name and/or namespace.
|
|
263
|
+
return _return_resource(
|
|
264
|
+
_resource=resource,
|
|
265
|
+
_check_exists=check_exists,
|
|
266
|
+
_msg=skip_create_warn_msg,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
for _name, _namespace in _resource_args.items():
|
|
270
|
+
if resource_name == _name and (
|
|
271
|
+
resource_namespace == _namespace
|
|
272
|
+
or not (resource_namespace and _namespace)
|
|
273
|
+
):
|
|
274
|
+
return _return_resource(
|
|
275
|
+
_resource=resource,
|
|
276
|
+
_check_exists=check_exists,
|
|
277
|
+
_msg=skip_create_warn_msg,
|
|
278
|
+
)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from ocp_resources.constants import (
|
|
5
|
+
DEFAULT_CLUSTER_RETRY_EXCEPTIONS,
|
|
6
|
+
PROTOCOL_ERROR_EXCEPTION_DICT,
|
|
7
|
+
TIMEOUT_4MINUTES,
|
|
8
|
+
)
|
|
9
|
+
from ocp_resources.resource import NamespacedResource
|
|
10
|
+
from ocp_resources.utils import TimeoutSampler
|
|
11
|
+
from ocp_resources.virtual_machine_instance import VirtualMachineInstance
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VirtualMachine(NamespacedResource):
|
|
15
|
+
"""
|
|
16
|
+
Virtual Machine object, inherited from Resource.
|
|
17
|
+
Implements actions start / stop / status / wait for VM status / is running
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
api_group = NamespacedResource.ApiGroup.KUBEVIRT_IO
|
|
21
|
+
|
|
22
|
+
class RunStrategy:
|
|
23
|
+
MANUAL = "Manual"
|
|
24
|
+
HALTED = "Halted"
|
|
25
|
+
ALWAYS = "Always"
|
|
26
|
+
RERUNONFAILURE = "RerunOnFailure"
|
|
27
|
+
|
|
28
|
+
class Status(NamespacedResource.Status):
|
|
29
|
+
MIGRATING = "Migrating"
|
|
30
|
+
PAUSED = "Paused"
|
|
31
|
+
PROVISIONING = "Provisioning"
|
|
32
|
+
STARTING = "Starting"
|
|
33
|
+
STOPPED = "Stopped"
|
|
34
|
+
STOPPING = "Stopping"
|
|
35
|
+
WAITING_FOR_VOLUME_BINDING = "WaitingForVolumeBinding"
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
name=None,
|
|
40
|
+
namespace=None,
|
|
41
|
+
client=None,
|
|
42
|
+
body=None,
|
|
43
|
+
teardown=True,
|
|
44
|
+
privileged_client=None,
|
|
45
|
+
yaml_file=None,
|
|
46
|
+
delete_timeout=TIMEOUT_4MINUTES,
|
|
47
|
+
**kwargs,
|
|
48
|
+
):
|
|
49
|
+
super().__init__(
|
|
50
|
+
name=name,
|
|
51
|
+
namespace=namespace,
|
|
52
|
+
client=client,
|
|
53
|
+
teardown=teardown,
|
|
54
|
+
privileged_client=privileged_client,
|
|
55
|
+
yaml_file=yaml_file,
|
|
56
|
+
delete_timeout=delete_timeout,
|
|
57
|
+
**kwargs,
|
|
58
|
+
)
|
|
59
|
+
self.body = body
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def _subresource_api_url(self):
|
|
63
|
+
return (
|
|
64
|
+
f"{self.client.configuration.host}/"
|
|
65
|
+
f"apis/subresources.kubevirt.io/{self.api.api_version}/"
|
|
66
|
+
f"namespaces/{self.namespace}/virtualmachines/{self.name}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def api_request(self, method, action, **params):
|
|
70
|
+
return super().api_request(
|
|
71
|
+
method=method, action=action, url=self._subresource_api_url, **params
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def to_dict(self):
|
|
75
|
+
super().to_dict()
|
|
76
|
+
if not self.yaml_file:
|
|
77
|
+
body_spec = self.body.get("spec") if self.body else None
|
|
78
|
+
self.res["spec"] = body_spec or {"template": {"spec": {}}}
|
|
79
|
+
|
|
80
|
+
def start(self, timeout=TIMEOUT_4MINUTES, wait=False):
|
|
81
|
+
self.api_request(method="PUT", action="start")
|
|
82
|
+
if wait:
|
|
83
|
+
return self.wait_for_ready_status(timeout=timeout, status=True)
|
|
84
|
+
|
|
85
|
+
def restart(self, timeout=TIMEOUT_4MINUTES, wait=False):
|
|
86
|
+
self.api_request(method="PUT", action="restart")
|
|
87
|
+
if wait:
|
|
88
|
+
self.vmi.virt_launcher_pod.wait_deleted()
|
|
89
|
+
return self.vmi.wait_until_running(timeout=timeout, stop_status="dummy")
|
|
90
|
+
|
|
91
|
+
def stop(
|
|
92
|
+
self, timeout=TIMEOUT_4MINUTES, vmi_delete_timeout=TIMEOUT_4MINUTES, wait=False
|
|
93
|
+
):
|
|
94
|
+
self.api_request(method="PUT", action="stop")
|
|
95
|
+
if wait:
|
|
96
|
+
self.wait_for_ready_status(timeout=timeout, status=None)
|
|
97
|
+
return self.vmi.wait_deleted(timeout=vmi_delete_timeout)
|
|
98
|
+
|
|
99
|
+
def wait_for_ready_status(self, status, timeout=TIMEOUT_4MINUTES, sleep=1):
|
|
100
|
+
"""
|
|
101
|
+
Wait for VM resource ready status to be at desire status
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
status (any): True for a running VM, None for a stopped VM.
|
|
105
|
+
timeout (int): Time to wait for the resource.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
TimeoutExpiredError: If timeout reached.
|
|
109
|
+
"""
|
|
110
|
+
self.logger.info(
|
|
111
|
+
f"Wait for {self.kind} {self.name} status to be {'ready' if status == True else status}"
|
|
112
|
+
)
|
|
113
|
+
samples = TimeoutSampler(
|
|
114
|
+
wait_timeout=timeout,
|
|
115
|
+
sleep=sleep,
|
|
116
|
+
exceptions_dict={
|
|
117
|
+
**PROTOCOL_ERROR_EXCEPTION_DICT,
|
|
118
|
+
**DEFAULT_CLUSTER_RETRY_EXCEPTIONS,
|
|
119
|
+
},
|
|
120
|
+
func=self.api.get,
|
|
121
|
+
field_selector=f"metadata.name=={self.name}",
|
|
122
|
+
namespace=self.namespace,
|
|
123
|
+
)
|
|
124
|
+
for sample in samples:
|
|
125
|
+
if sample.items and self.ready == status:
|
|
126
|
+
# VM with runStrategy does not have spec.running attribute
|
|
127
|
+
# VM status should be taken from spec.status.ready
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
def get_interfaces(self):
|
|
131
|
+
return self.instance.spec.template.spec.domain.devices.interfaces
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def vmi(self):
|
|
135
|
+
"""
|
|
136
|
+
Get VMI
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
VirtualMachineInstance: VMI
|
|
140
|
+
"""
|
|
141
|
+
return VirtualMachineInstance(
|
|
142
|
+
client=self.client,
|
|
143
|
+
name=self.name,
|
|
144
|
+
namespace=self.namespace,
|
|
145
|
+
privileged_client=self.privileged_client or self.client,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def ready(self):
|
|
150
|
+
"""
|
|
151
|
+
Get VM status
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
True if Running else None
|
|
155
|
+
"""
|
|
156
|
+
return self.instance.get("status", {}).get("ready")
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def printable_status(self):
|
|
160
|
+
"""
|
|
161
|
+
Get VM printableStatus
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
VM printableStatus if VM.status.printableStatus else None
|
|
165
|
+
"""
|
|
166
|
+
return self.instance.get("status", {}).get("printableStatus")
|
|
167
|
+
|
|
168
|
+
def wait_for_status_none(self, status, timeout=TIMEOUT_4MINUTES):
|
|
169
|
+
self.logger.info(f"Wait for {self.kind} {self.name} status {status} to be None")
|
|
170
|
+
for sample in TimeoutSampler(
|
|
171
|
+
wait_timeout=timeout,
|
|
172
|
+
sleep=1,
|
|
173
|
+
exceptions_dict=PROTOCOL_ERROR_EXCEPTION_DICT,
|
|
174
|
+
func=lambda: self.instance.get("status", {}).get(status),
|
|
175
|
+
):
|
|
176
|
+
if sample is None:
|
|
177
|
+
return
|