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,307 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
|
|
5
|
+
from ocp_resources.resource import NamespacedResource
|
|
6
|
+
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
|
|
7
|
+
from ocp_resources.virtual_machine import VirtualMachine
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _map_mappings(mappings):
|
|
11
|
+
mappings_list = []
|
|
12
|
+
for mapping in mappings:
|
|
13
|
+
mapping_dict = {"target": {"name": mapping.target_name}}
|
|
14
|
+
if mapping.target_namespace:
|
|
15
|
+
mapping_dict["target"]["namespace"] = mapping.target_namespace
|
|
16
|
+
if mapping.target_type:
|
|
17
|
+
mapping_dict["type"] = mapping.target_type
|
|
18
|
+
if mapping.source_id:
|
|
19
|
+
mapping_dict.setdefault("source", {})["id"] = mapping.source_id
|
|
20
|
+
if mapping.source_name:
|
|
21
|
+
mapping_dict.setdefault("source", {})["name"] = mapping.source_name
|
|
22
|
+
if mapping.target_access_modes:
|
|
23
|
+
mapping_dict["accessMode"] = mapping.target_access_modes
|
|
24
|
+
if mapping.target_volume_mode:
|
|
25
|
+
mapping_dict["volumeMode"] = mapping.target_volume_mode
|
|
26
|
+
mappings_list.append(mapping_dict)
|
|
27
|
+
return mappings_list
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class VirtualMachineImport(NamespacedResource):
|
|
31
|
+
"""
|
|
32
|
+
Virtual Machine Import object, inherited from NamespacedResource.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
api_group = NamespacedResource.ApiGroup.V2V_KUBEVIRT_IO
|
|
36
|
+
|
|
37
|
+
class Condition(NamespacedResource.Condition):
|
|
38
|
+
SUCCEEDED = "Succeeded"
|
|
39
|
+
VALID = "Valid"
|
|
40
|
+
MAPPING_RULES_VERIFIED = "MappingRulesVerified"
|
|
41
|
+
PROCESSING = "Processing"
|
|
42
|
+
|
|
43
|
+
class ValidConditionReason:
|
|
44
|
+
"""
|
|
45
|
+
Valid condition reason object
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
VALIDATION_COMPLETED = "ValidationCompleted"
|
|
49
|
+
SECRET_NOT_FOUND = "SecretNotFound" # pragma: allowlist secret
|
|
50
|
+
RESOURCE_MAPPING_NOT_FOUND = "ResourceMappingNotFound"
|
|
51
|
+
UNINITIALIZED_PROVIDER = "UninitializedProvider"
|
|
52
|
+
SOURCE_VM_NOT_FOUND = "SourceVMNotFound"
|
|
53
|
+
INCOMPLETE_MAPPING_RULES = "IncompleteMappingRules"
|
|
54
|
+
|
|
55
|
+
class MappingRulesConditionReason:
|
|
56
|
+
"""
|
|
57
|
+
Mapping rules verified condition reason object
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
MAPPING_COMPLETED = "MappingRulesVerificationCompleted"
|
|
61
|
+
MAPPING_FAILED = "MappingRulesVerificationFailed"
|
|
62
|
+
MAPPING_REPORTED_WARNINGS = "MappingRulesVerificationReportedWarnings"
|
|
63
|
+
|
|
64
|
+
class ProcessingConditionReason:
|
|
65
|
+
"""
|
|
66
|
+
Processing condition reason object
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
CREATING_TARGET_VM = "CreatingTargetVM"
|
|
70
|
+
COPYING_DISKS = "CopyingDisks"
|
|
71
|
+
COMPLETED = "ProcessingCompleted"
|
|
72
|
+
FAILED = "ProcessingFailed"
|
|
73
|
+
|
|
74
|
+
class SucceededConditionReason:
|
|
75
|
+
"""
|
|
76
|
+
Succeeced cond reason object
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
VALIDATION_FAILED = "ValidationFailed"
|
|
80
|
+
VM_CREATION_FAILED = "VMCreationFailed"
|
|
81
|
+
DATAVOLUME_CREATION_FAILED = "DataVolumeCreationFailed"
|
|
82
|
+
VIRTUAL_MACHINE_READY = "VirtualMachineReady"
|
|
83
|
+
VIRTUAL_MACHINE_RUNNING = "VirtualMachineRunning"
|
|
84
|
+
VMTEMPLATE_MATCHING_FAILED = "VMTemplateMatchingFailed"
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
name=None,
|
|
89
|
+
namespace=None,
|
|
90
|
+
provider_credentials_secret_name=None,
|
|
91
|
+
provider_type=None,
|
|
92
|
+
provider_credentials_secret_namespace=None,
|
|
93
|
+
client=None,
|
|
94
|
+
teardown=True,
|
|
95
|
+
vm_id=None,
|
|
96
|
+
vm_name=None,
|
|
97
|
+
cluster_id=None,
|
|
98
|
+
cluster_name=None,
|
|
99
|
+
target_vm_name=None,
|
|
100
|
+
start_vm=False,
|
|
101
|
+
provider_mappings=None,
|
|
102
|
+
resource_mapping_name=None,
|
|
103
|
+
resource_mapping_namespace=None,
|
|
104
|
+
warm=False,
|
|
105
|
+
finalize_date=None,
|
|
106
|
+
privileged_client=None,
|
|
107
|
+
yaml_file=None,
|
|
108
|
+
delete_timeout=TIMEOUT_4MINUTES,
|
|
109
|
+
**kwargs,
|
|
110
|
+
):
|
|
111
|
+
super().__init__(
|
|
112
|
+
name=name,
|
|
113
|
+
namespace=namespace,
|
|
114
|
+
client=client,
|
|
115
|
+
teardown=teardown,
|
|
116
|
+
privileged_client=privileged_client,
|
|
117
|
+
yaml_file=yaml_file,
|
|
118
|
+
delete_timeout=delete_timeout,
|
|
119
|
+
**kwargs,
|
|
120
|
+
)
|
|
121
|
+
self.vm_id = vm_id
|
|
122
|
+
self.vm_name = vm_name
|
|
123
|
+
self.cluster_id = cluster_id
|
|
124
|
+
self.cluster_name = cluster_name
|
|
125
|
+
self.target_vm_name = target_vm_name
|
|
126
|
+
self.start_vm = start_vm
|
|
127
|
+
self.provider_credentials_secret_name = provider_credentials_secret_name
|
|
128
|
+
self.provider_credentials_secret_namespace = (
|
|
129
|
+
provider_credentials_secret_namespace
|
|
130
|
+
)
|
|
131
|
+
self.provider_mappings = provider_mappings
|
|
132
|
+
self.resource_mapping_name = resource_mapping_name
|
|
133
|
+
self.resource_mapping_namespace = resource_mapping_namespace
|
|
134
|
+
self.provider_type = provider_type
|
|
135
|
+
self.warm = warm
|
|
136
|
+
self.finalize_date = finalize_date
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def vm(self):
|
|
140
|
+
return VirtualMachine(
|
|
141
|
+
name=self.target_vm_name,
|
|
142
|
+
namespace=self.namespace,
|
|
143
|
+
client=self.client,
|
|
144
|
+
privileged_client=self.privileged_client or self.client,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def to_dict(self):
|
|
148
|
+
super().to_dict()
|
|
149
|
+
if not self.yaml_file:
|
|
150
|
+
spec = self.res.setdefault("spec", {})
|
|
151
|
+
|
|
152
|
+
secret = spec.setdefault("providerCredentialsSecret", {})
|
|
153
|
+
secret["name"] = self.provider_credentials_secret_name
|
|
154
|
+
|
|
155
|
+
if self.provider_credentials_secret_namespace:
|
|
156
|
+
secret["namespace"] = self.provider_credentials_secret_namespace
|
|
157
|
+
|
|
158
|
+
if self.resource_mapping_name:
|
|
159
|
+
spec.setdefault("resourceMapping", {})[
|
|
160
|
+
"name"
|
|
161
|
+
] = self.resource_mapping_name
|
|
162
|
+
if self.resource_mapping_namespace:
|
|
163
|
+
spec.setdefault("resourceMapping", {})[
|
|
164
|
+
"namespace"
|
|
165
|
+
] = self.resource_mapping_namespace
|
|
166
|
+
|
|
167
|
+
if self.target_vm_name:
|
|
168
|
+
spec["targetVmName"] = self.target_vm_name
|
|
169
|
+
|
|
170
|
+
if self.start_vm is not None:
|
|
171
|
+
spec["startVm"] = self.start_vm
|
|
172
|
+
|
|
173
|
+
if self.warm:
|
|
174
|
+
spec["warm"] = self.warm
|
|
175
|
+
if self.finalize_date:
|
|
176
|
+
spec["finalizeDate"] = self.finalize_date.strftime(
|
|
177
|
+
format="%Y-%m-%dT%H:%M:%SZ"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
provider_source = spec.setdefault("source", {}).setdefault(
|
|
181
|
+
self.provider_type, {}
|
|
182
|
+
)
|
|
183
|
+
vm = provider_source.setdefault("vm", {})
|
|
184
|
+
if self.vm_id:
|
|
185
|
+
vm["id"] = self.vm_id
|
|
186
|
+
if self.vm_name:
|
|
187
|
+
vm["name"] = self.vm_name
|
|
188
|
+
|
|
189
|
+
if self.cluster_id:
|
|
190
|
+
vm.setdefault("cluster", {})["id"] = self.cluster_id
|
|
191
|
+
if self.cluster_name:
|
|
192
|
+
vm.setdefault("cluster", {})["name"] = self.cluster_name
|
|
193
|
+
|
|
194
|
+
if self.provider_mappings:
|
|
195
|
+
if self.provider_mappings.disk_mappings:
|
|
196
|
+
mappings = _map_mappings(
|
|
197
|
+
mappings=self.provider_mappings.disk_mappings
|
|
198
|
+
)
|
|
199
|
+
provider_source.setdefault("mappings", {}).setdefault(
|
|
200
|
+
"diskMappings", mappings
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if self.provider_mappings.network_mappings:
|
|
204
|
+
mappings = _map_mappings(
|
|
205
|
+
mappings=self.provider_mappings.network_mappings
|
|
206
|
+
)
|
|
207
|
+
provider_source.setdefault("mappings", {}).setdefault(
|
|
208
|
+
"networkMappings", mappings
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if self.provider_mappings.storage_mappings:
|
|
212
|
+
mappings = _map_mappings(
|
|
213
|
+
mappings=self.provider_mappings.storage_mappings
|
|
214
|
+
)
|
|
215
|
+
provider_source.setdefault("mappings", {}).setdefault(
|
|
216
|
+
"storageMappings", mappings
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def wait(
|
|
220
|
+
self,
|
|
221
|
+
timeout=600,
|
|
222
|
+
cond_reason=SucceededConditionReason.VIRTUAL_MACHINE_READY,
|
|
223
|
+
cond_status=Condition.Status.TRUE,
|
|
224
|
+
cond_type=Condition.SUCCEEDED,
|
|
225
|
+
):
|
|
226
|
+
self.logger.info(
|
|
227
|
+
f"Wait for {self.kind} {self.name} {cond_reason} condition to be {cond_status}"
|
|
228
|
+
)
|
|
229
|
+
samples = TimeoutSampler(
|
|
230
|
+
wait_timeout=timeout,
|
|
231
|
+
sleep=1,
|
|
232
|
+
exceptions_dict=PROTOCOL_ERROR_EXCEPTION_DICT,
|
|
233
|
+
func=self.api.get,
|
|
234
|
+
field_selector=f"metadata.name=={self.name}",
|
|
235
|
+
namespace=self.namespace,
|
|
236
|
+
)
|
|
237
|
+
last_condition = None
|
|
238
|
+
try:
|
|
239
|
+
for sample in samples:
|
|
240
|
+
if sample.items:
|
|
241
|
+
sample_status = sample.items[0].status
|
|
242
|
+
if sample_status:
|
|
243
|
+
current_conditions = sample_status.conditions
|
|
244
|
+
for cond in current_conditions:
|
|
245
|
+
last_condition = cond
|
|
246
|
+
if (
|
|
247
|
+
cond.type == cond_type
|
|
248
|
+
and cond.status == cond_status
|
|
249
|
+
and cond.reason == cond_reason
|
|
250
|
+
):
|
|
251
|
+
msg = (
|
|
252
|
+
f"Status of {self.kind} {self.name} {cond.type} is "
|
|
253
|
+
f"{cond.status} ({cond.reason}: {cond.message})"
|
|
254
|
+
)
|
|
255
|
+
self.logger.info(msg)
|
|
256
|
+
return
|
|
257
|
+
except TimeoutExpiredError:
|
|
258
|
+
raise TimeoutExpiredError(
|
|
259
|
+
f"Last condition of {self.kind} {self.name} {last_condition.type} was "
|
|
260
|
+
f"{last_condition.status} ({last_condition.reason}: {last_condition.message})"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class ResourceMapping(NamespacedResource):
|
|
265
|
+
"""
|
|
266
|
+
ResourceMapping object.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
api_group = NamespacedResource.ApiGroup.V2V_KUBEVIRT_IO
|
|
270
|
+
|
|
271
|
+
def __init__(
|
|
272
|
+
self,
|
|
273
|
+
name=None,
|
|
274
|
+
namespace=None,
|
|
275
|
+
mapping=None,
|
|
276
|
+
client=None,
|
|
277
|
+
teardown=True,
|
|
278
|
+
yaml_file=None,
|
|
279
|
+
**kwargs,
|
|
280
|
+
):
|
|
281
|
+
super().__init__(
|
|
282
|
+
name=name,
|
|
283
|
+
namespace=namespace,
|
|
284
|
+
client=client,
|
|
285
|
+
teardown=teardown,
|
|
286
|
+
yaml_file=yaml_file,
|
|
287
|
+
**kwargs,
|
|
288
|
+
)
|
|
289
|
+
self.mapping = mapping
|
|
290
|
+
|
|
291
|
+
def to_dict(self):
|
|
292
|
+
super().to_dict()
|
|
293
|
+
if not self.yaml_file:
|
|
294
|
+
for provider, mapping in self.mapping.items():
|
|
295
|
+
res_provider_section = self.res.setdefault("spec", {}).setdefault(
|
|
296
|
+
provider, {}
|
|
297
|
+
)
|
|
298
|
+
if mapping.network_mappings is not None:
|
|
299
|
+
res_provider_section.setdefault(
|
|
300
|
+
"networkMappings",
|
|
301
|
+
_map_mappings(mappings=mapping.network_mappings),
|
|
302
|
+
)
|
|
303
|
+
if mapping.storage_mappings is not None:
|
|
304
|
+
res_provider_section.setdefault(
|
|
305
|
+
"storageMappings",
|
|
306
|
+
_map_mappings(mappings=mapping.storage_mappings),
|
|
307
|
+
)
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
|
|
3
|
+
import xmltodict
|
|
4
|
+
from openshift.dynamic.exceptions import ResourceNotFoundError
|
|
5
|
+
|
|
6
|
+
from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
|
|
7
|
+
from ocp_resources.node import Node
|
|
8
|
+
from ocp_resources.pod import Pod
|
|
9
|
+
from ocp_resources.resource import NamespacedResource
|
|
10
|
+
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VirtualMachineInstance(NamespacedResource):
|
|
14
|
+
"""
|
|
15
|
+
Virtual Machine Instance object, inherited from Resource.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
api_group = NamespacedResource.ApiGroup.KUBEVIRT_IO
|
|
19
|
+
|
|
20
|
+
class Status(NamespacedResource.Status):
|
|
21
|
+
SCHEDULING = "Scheduling"
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
name=None,
|
|
26
|
+
namespace=None,
|
|
27
|
+
client=None,
|
|
28
|
+
privileged_client=None,
|
|
29
|
+
yaml_file=None,
|
|
30
|
+
delete_timeout=TIMEOUT_4MINUTES,
|
|
31
|
+
**kwargs,
|
|
32
|
+
):
|
|
33
|
+
super().__init__(
|
|
34
|
+
name=name,
|
|
35
|
+
namespace=namespace,
|
|
36
|
+
client=client,
|
|
37
|
+
privileged_client=privileged_client,
|
|
38
|
+
yaml_file=yaml_file,
|
|
39
|
+
delete_timeout=delete_timeout,
|
|
40
|
+
**kwargs,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def _subresource_api_url(self):
|
|
45
|
+
return (
|
|
46
|
+
f"{self.client.configuration.host}/"
|
|
47
|
+
f"apis/subresources.kubevirt.io/{self.api.api_version}/"
|
|
48
|
+
f"namespaces/{self.namespace}/virtualmachineinstances/{self.name}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def api_request(self, method, action, **params):
|
|
52
|
+
return super().api_request(
|
|
53
|
+
method=method, action=action, url=self._subresource_api_url, **params
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def pause(self, timeout=TIMEOUT_4MINUTES, wait=False):
|
|
57
|
+
self.api_request(method="PUT", action="pause")
|
|
58
|
+
if wait:
|
|
59
|
+
return self.wait_for_pause_status(pause=True, timeout=timeout)
|
|
60
|
+
|
|
61
|
+
def unpause(self, timeout=TIMEOUT_4MINUTES, wait=False):
|
|
62
|
+
self.api_request(method="PUT", action="unpause")
|
|
63
|
+
if wait:
|
|
64
|
+
return self.wait_for_pause_status(pause=False, timeout=timeout)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def interfaces(self):
|
|
68
|
+
return self.instance.status.interfaces
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def virt_launcher_pod(self):
|
|
72
|
+
pods = list(
|
|
73
|
+
Pod.get(
|
|
74
|
+
dyn_client=self.privileged_client or self.client,
|
|
75
|
+
namespace=self.namespace,
|
|
76
|
+
label_selector=f"kubevirt.io=virt-launcher,kubevirt.io/created-by={self.instance.metadata.uid}",
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
migration_state = self.instance.status.migrationState
|
|
80
|
+
if migration_state:
|
|
81
|
+
# After VM migration there are two pods, one in Completed status and one in Running status.
|
|
82
|
+
# We need to return the Pod that is not in Completed status.
|
|
83
|
+
for pod in pods:
|
|
84
|
+
if migration_state.targetPod == pod.name:
|
|
85
|
+
return pod
|
|
86
|
+
else:
|
|
87
|
+
return pods[0]
|
|
88
|
+
|
|
89
|
+
raise ResourceNotFoundError
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def virt_handler_pod(self):
|
|
93
|
+
pods = list(
|
|
94
|
+
Pod.get(
|
|
95
|
+
dyn_client=self.privileged_client or self.client,
|
|
96
|
+
label_selector="kubevirt.io=virt-handler",
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
for pod in pods:
|
|
100
|
+
if pod.instance["spec"]["nodeName"] == self.instance.status.nodeName:
|
|
101
|
+
return pod
|
|
102
|
+
|
|
103
|
+
raise ResourceNotFoundError
|
|
104
|
+
|
|
105
|
+
def wait_until_running(self, timeout=TIMEOUT_4MINUTES, logs=True, stop_status=None):
|
|
106
|
+
"""
|
|
107
|
+
Wait until VMI is running
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
timeout (int): Time to wait for VMI.
|
|
111
|
+
logs (bool): True to extract logs from the VMI pod and from the VMI.
|
|
112
|
+
stop_status (str): Status which should stop the wait and failed.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
TimeoutExpiredError: If VMI failed to run.
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
self.wait_for_status(
|
|
119
|
+
status=self.Status.RUNNING, timeout=timeout, stop_status=stop_status
|
|
120
|
+
)
|
|
121
|
+
except TimeoutExpiredError:
|
|
122
|
+
if not logs:
|
|
123
|
+
raise
|
|
124
|
+
|
|
125
|
+
virt_pod = self.virt_launcher_pod
|
|
126
|
+
if virt_pod:
|
|
127
|
+
self.logger.debug(f"{virt_pod.name} *****LOGS*****")
|
|
128
|
+
self.logger.debug(virt_pod.log(container="compute"))
|
|
129
|
+
|
|
130
|
+
raise
|
|
131
|
+
|
|
132
|
+
def wait_for_pause_status(self, pause, timeout=TIMEOUT_4MINUTES):
|
|
133
|
+
"""
|
|
134
|
+
Wait for Virtual Machine Instance to be paused / unpaused.
|
|
135
|
+
Paused status is checked in libvirt and in the VMI conditions.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
pause (bool): True for paused, False for unpause
|
|
139
|
+
timeout (int): Time to wait for the resource.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
TimeoutExpiredError: If resource not exists.
|
|
143
|
+
"""
|
|
144
|
+
self.logger.info(
|
|
145
|
+
f"Wait until {self.kind} {self.name} is "
|
|
146
|
+
f"{'Paused' if pause else 'Unpuased'}"
|
|
147
|
+
)
|
|
148
|
+
self.wait_for_domstate_pause_status(pause=pause, timeout=timeout)
|
|
149
|
+
self.wait_for_vmi_condition_pause_status(pause=pause, timeout=timeout)
|
|
150
|
+
|
|
151
|
+
def wait_for_domstate_pause_status(self, pause, timeout=TIMEOUT_4MINUTES):
|
|
152
|
+
pause_status = "paused" if pause else "running"
|
|
153
|
+
samples = TimeoutSampler(
|
|
154
|
+
wait_timeout=timeout,
|
|
155
|
+
sleep=1,
|
|
156
|
+
exceptions_dict=PROTOCOL_ERROR_EXCEPTION_DICT,
|
|
157
|
+
func=self.get_domstate,
|
|
158
|
+
)
|
|
159
|
+
for sample in samples:
|
|
160
|
+
if pause_status in sample:
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
def wait_for_vmi_condition_pause_status(self, pause, timeout=TIMEOUT_4MINUTES):
|
|
164
|
+
samples = TimeoutSampler(
|
|
165
|
+
wait_timeout=timeout,
|
|
166
|
+
sleep=1,
|
|
167
|
+
exceptions_dict=PROTOCOL_ERROR_EXCEPTION_DICT,
|
|
168
|
+
func=self.get_vmi_active_condition,
|
|
169
|
+
)
|
|
170
|
+
for sample in samples:
|
|
171
|
+
# VM in state change
|
|
172
|
+
# We have commanded a [un]pause condition via the API but the CR has not been updated yet to match.
|
|
173
|
+
# 'reason' may not exist yet
|
|
174
|
+
# or
|
|
175
|
+
# 'reason' may still exist after unpause if the CR has not been updated before we perform this check
|
|
176
|
+
if (pause and not sample.get("reason")) or (
|
|
177
|
+
sample.get("reason") == "PausedByUser" and not pause
|
|
178
|
+
):
|
|
179
|
+
continue
|
|
180
|
+
# Paused VM
|
|
181
|
+
if pause and sample["reason"] == "PausedByUser":
|
|
182
|
+
return
|
|
183
|
+
# Unpaused VM
|
|
184
|
+
if not (pause and sample.get("reason")):
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def node(self):
|
|
189
|
+
"""
|
|
190
|
+
Get the node name where the VM is running
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Node: Node
|
|
194
|
+
"""
|
|
195
|
+
return Node(
|
|
196
|
+
client=self.privileged_client or self.client,
|
|
197
|
+
name=self.instance.status.nodeName,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def virsh_cmd(self, action):
|
|
201
|
+
return shlex.split(
|
|
202
|
+
f"virsh {self.virt_launcher_pod_hypervisor_connection_uri} {action} {self.namespace}_{self.name}"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def get_xml(self):
|
|
206
|
+
"""
|
|
207
|
+
Get virtual machine instance XML
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
xml_output(string): VMI XML in the multi-line string
|
|
211
|
+
"""
|
|
212
|
+
return self.execute_virsh_command(command="dumpxml")
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def virt_launcher_pod_user_uid(self):
|
|
216
|
+
"""
|
|
217
|
+
Get Virt Launcher Pod User UID value
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Int: Virt Launcher Pod UID value
|
|
221
|
+
"""
|
|
222
|
+
return self.virt_launcher_pod.instance.spec.securityContext.runAsUser
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def is_virt_launcher_pod_root(self):
|
|
226
|
+
"""
|
|
227
|
+
Check if Virt Launcher Pod is Root
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Bool: True if Virt Launcher Pod is Root.
|
|
231
|
+
"""
|
|
232
|
+
return not bool(self.virt_launcher_pod_user_uid)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def virt_launcher_pod_hypervisor_connection_uri(self):
|
|
236
|
+
"""
|
|
237
|
+
Get Virt Launcher Pod Hypervisor Connection URI
|
|
238
|
+
|
|
239
|
+
Required to connect to Hypervisor for
|
|
240
|
+
Non-Root Virt-Launcher Pod.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
String: Hypervisor Connection URI
|
|
244
|
+
"""
|
|
245
|
+
return (
|
|
246
|
+
""
|
|
247
|
+
if self.is_virt_launcher_pod_root
|
|
248
|
+
else "-c qemu+unix:///session?socket=/var/run/libvirt/libvirt-sock"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def get_domstate(self):
|
|
252
|
+
"""
|
|
253
|
+
Get virtual machine instance Status.
|
|
254
|
+
|
|
255
|
+
Current workaround, as VM/VMI shows no status/phase == Paused yet.
|
|
256
|
+
Bug: https://bugzilla.redhat.com/show_bug.cgi?id=1805178
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
String: VMI Status as string
|
|
260
|
+
"""
|
|
261
|
+
return self.execute_virsh_command(command="domstate")
|
|
262
|
+
|
|
263
|
+
def get_dommemstat(self):
|
|
264
|
+
"""
|
|
265
|
+
Get virtual machine domain memory stats
|
|
266
|
+
link: https://libvirt.org/manpages/virsh.html#dommemstat
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
String: VMI domain memory stats as string
|
|
270
|
+
"""
|
|
271
|
+
return self.execute_virsh_command(command="dommemstat")
|
|
272
|
+
|
|
273
|
+
def get_vmi_active_condition(self):
|
|
274
|
+
"""A VMI may have multiple conditions; the active one it the one with
|
|
275
|
+
'lastTransitionTime'"""
|
|
276
|
+
return {
|
|
277
|
+
k: v
|
|
278
|
+
for condition in self.instance.status.conditions
|
|
279
|
+
for k, v in condition.items()
|
|
280
|
+
if condition["lastTransitionTime"]
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def xml_dict(self):
|
|
285
|
+
"""Get virtual machine instance XML as dict"""
|
|
286
|
+
|
|
287
|
+
return xmltodict.parse(xml_input=self.get_xml(), process_namespaces=True)
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def guest_os_info(self):
|
|
291
|
+
return self.api_request(method="GET", action="guestosinfo")
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def guest_fs_info(self):
|
|
295
|
+
return self.api_request(method="GET", action="filesystemlist")
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def guest_user_info(self):
|
|
299
|
+
return self.api_request(method="GET", action="userlist")
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def os_version(self):
|
|
303
|
+
vmi_os_version = self.instance.status.guestOSInfo.get("version", {})
|
|
304
|
+
if not vmi_os_version:
|
|
305
|
+
self.logger.warning(
|
|
306
|
+
"Guest agent is not installed on the VM; OS version is not available."
|
|
307
|
+
)
|
|
308
|
+
return vmi_os_version
|
|
309
|
+
|
|
310
|
+
def interface_ip(self, interface):
|
|
311
|
+
iface_ip = [
|
|
312
|
+
iface["ipAddress"]
|
|
313
|
+
for iface in self.interfaces
|
|
314
|
+
if iface["interfaceName"] == interface
|
|
315
|
+
]
|
|
316
|
+
return iface_ip[0] if iface_ip else None
|
|
317
|
+
|
|
318
|
+
def execute_virsh_command(self, command):
|
|
319
|
+
return self.virt_launcher_pod.execute(
|
|
320
|
+
command=self.virsh_cmd(action=command),
|
|
321
|
+
container="compute",
|
|
322
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from ocp_resources.constants import TIMEOUT_4MINUTES
|
|
2
|
+
from ocp_resources.resource import NamespacedResource
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class VirtualMachineInstanceMigration(NamespacedResource):
|
|
6
|
+
api_group = NamespacedResource.ApiGroup.KUBEVIRT_IO
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
name=None,
|
|
11
|
+
namespace=None,
|
|
12
|
+
vmi=None,
|
|
13
|
+
client=None,
|
|
14
|
+
teardown=True,
|
|
15
|
+
yaml_file=None,
|
|
16
|
+
delete_timeout=TIMEOUT_4MINUTES,
|
|
17
|
+
**kwargs,
|
|
18
|
+
):
|
|
19
|
+
super().__init__(
|
|
20
|
+
name=name,
|
|
21
|
+
namespace=namespace,
|
|
22
|
+
client=client,
|
|
23
|
+
teardown=teardown,
|
|
24
|
+
yaml_file=yaml_file,
|
|
25
|
+
delete_timeout=delete_timeout,
|
|
26
|
+
**kwargs,
|
|
27
|
+
)
|
|
28
|
+
self._vmi = vmi
|
|
29
|
+
|
|
30
|
+
def to_dict(self):
|
|
31
|
+
# When creating VirtualMachineInstanceMigration vmi is mandatory but when calling get()
|
|
32
|
+
# we cannot pass vmi.
|
|
33
|
+
super().to_dict()
|
|
34
|
+
if not self.yaml_file:
|
|
35
|
+
assert self._vmi, "vmi is mandatory for create"
|
|
36
|
+
self.res["spec"] = {"vmiName": self._vmi.name}
|