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.
Files changed (132) hide show
  1. ocp_resources/__init__.py +0 -0
  2. ocp_resources/api_service.py +9 -0
  3. ocp_resources/backup.py +52 -0
  4. ocp_resources/benchmark.py +92 -0
  5. ocp_resources/catalog_source.py +63 -0
  6. ocp_resources/catalog_source_config.py +86 -0
  7. ocp_resources/cdi.py +13 -0
  8. ocp_resources/cdi_config.py +52 -0
  9. ocp_resources/chaos_engine.py +30 -0
  10. ocp_resources/chaos_result.py +11 -0
  11. ocp_resources/cluster_operator.py +5 -0
  12. ocp_resources/cluster_role.py +70 -0
  13. ocp_resources/cluster_role_binding.py +36 -0
  14. ocp_resources/cluster_service_version.py +8 -0
  15. ocp_resources/cluster_version.py +5 -0
  16. ocp_resources/configmap.py +37 -0
  17. ocp_resources/console_cli_download.py +9 -0
  18. ocp_resources/console_plugin.py +9 -0
  19. ocp_resources/console_quick_start.py +9 -0
  20. ocp_resources/constants.py +26 -0
  21. ocp_resources/csidriver.py +9 -0
  22. ocp_resources/custom_resource_definition.py +5 -0
  23. ocp_resources/daemonset.py +61 -0
  24. ocp_resources/data_import_cron.py +100 -0
  25. ocp_resources/data_source.py +59 -0
  26. ocp_resources/datavolume.py +275 -0
  27. ocp_resources/deployment.py +65 -0
  28. ocp_resources/destination_rule.py +11 -0
  29. ocp_resources/event.py +114 -0
  30. ocp_resources/forklift_controller.py +10 -0
  31. ocp_resources/gateway.py +11 -0
  32. ocp_resources/hook.py +52 -0
  33. ocp_resources/host.py +64 -0
  34. ocp_resources/hostpath_provisioner.py +50 -0
  35. ocp_resources/hyperconverged.py +43 -0
  36. ocp_resources/image.py +9 -0
  37. ocp_resources/image_content_source_policy.py +9 -0
  38. ocp_resources/image_stream.py +48 -0
  39. ocp_resources/imagestreamtag.py +9 -0
  40. ocp_resources/infrastructure.py +18 -0
  41. ocp_resources/installplan.py +8 -0
  42. ocp_resources/job.py +65 -0
  43. ocp_resources/kube_descheduler.py +65 -0
  44. ocp_resources/kubevirt.py +5 -0
  45. ocp_resources/kubevirt_common_templates_bundle.py +5 -0
  46. ocp_resources/kubevirt_metrics_aggregation.py +5 -0
  47. ocp_resources/kubevirt_node_labeller_bundle.py +5 -0
  48. ocp_resources/kubevirt_template_validaotr.py +5 -0
  49. ocp_resources/logger.py +76 -0
  50. ocp_resources/machine.py +50 -0
  51. ocp_resources/machine_config.py +5 -0
  52. ocp_resources/machine_config_pool.py +9 -0
  53. ocp_resources/machine_health_check.py +76 -0
  54. ocp_resources/machine_set.py +189 -0
  55. ocp_resources/migration.py +63 -0
  56. ocp_resources/migration_policy.py +66 -0
  57. ocp_resources/mtv.py +171 -0
  58. ocp_resources/mutating_webhook_config.py +9 -0
  59. ocp_resources/namespace.py +40 -0
  60. ocp_resources/network.py +5 -0
  61. ocp_resources/network_addons_config.py +9 -0
  62. ocp_resources/network_attachment_definition.py +24 -0
  63. ocp_resources/network_map.py +64 -0
  64. ocp_resources/network_policy.py +9 -0
  65. ocp_resources/nmstate.py +9 -0
  66. ocp_resources/node.py +41 -0
  67. ocp_resources/node_health_check.py +10 -0
  68. ocp_resources/node_maintenance.py +40 -0
  69. ocp_resources/node_network_configuration_enactment.py +20 -0
  70. ocp_resources/node_network_configuration_policy.py +472 -0
  71. ocp_resources/node_network_state.py +121 -0
  72. ocp_resources/oauth.py +9 -0
  73. ocp_resources/operator_condition.py +5 -0
  74. ocp_resources/operator_group.py +37 -0
  75. ocp_resources/operator_hub.py +5 -0
  76. ocp_resources/operator_source.py +50 -0
  77. ocp_resources/package_manifest.py +5 -0
  78. ocp_resources/peer_authentication.py +18 -0
  79. ocp_resources/persistent_volume.py +27 -0
  80. ocp_resources/persistent_volume_claim.py +103 -0
  81. ocp_resources/plan.py +143 -0
  82. ocp_resources/pod.py +175 -0
  83. ocp_resources/pod_disruption_budget.py +53 -0
  84. ocp_resources/priority_class.py +48 -0
  85. ocp_resources/project.py +60 -0
  86. ocp_resources/prometheus.py +9 -0
  87. ocp_resources/prometheus_rule.py +9 -0
  88. ocp_resources/provider.py +59 -0
  89. ocp_resources/replicaset.py +11 -0
  90. ocp_resources/resource.py +1383 -0
  91. ocp_resources/restore.py +52 -0
  92. ocp_resources/role.py +11 -0
  93. ocp_resources/role_binding.py +67 -0
  94. ocp_resources/route.py +76 -0
  95. ocp_resources/secret.py +75 -0
  96. ocp_resources/security_context_constraints.py +9 -0
  97. ocp_resources/service.py +16 -0
  98. ocp_resources/service_account.py +9 -0
  99. ocp_resources/service_mesh_control_plane.py +11 -0
  100. ocp_resources/service_mesh_member_roll.py +14 -0
  101. ocp_resources/service_monitor.py +9 -0
  102. ocp_resources/sriov_network.py +54 -0
  103. ocp_resources/sriov_network_node_policy.py +67 -0
  104. ocp_resources/sriov_network_node_state.py +44 -0
  105. ocp_resources/ssp.py +9 -0
  106. ocp_resources/storage_class.py +47 -0
  107. ocp_resources/storage_map.py +67 -0
  108. ocp_resources/storage_profile.py +7 -0
  109. ocp_resources/subscription.py +66 -0
  110. ocp_resources/tekton_tasks.py +9 -0
  111. ocp_resources/template.py +74 -0
  112. ocp_resources/upload_token_request.py +39 -0
  113. ocp_resources/utils.py +278 -0
  114. ocp_resources/validating_webhook_config.py +9 -0
  115. ocp_resources/virtual_machine.py +177 -0
  116. ocp_resources/virtual_machine_Instance_replica_set.py +9 -0
  117. ocp_resources/virtual_machine_cluster_flavor.py +5 -0
  118. ocp_resources/virtual_machine_flavor.py +5 -0
  119. ocp_resources/virtual_machine_import.py +307 -0
  120. ocp_resources/virtual_machine_instance.py +322 -0
  121. ocp_resources/virtual_machine_instance_migration.py +36 -0
  122. ocp_resources/virtual_machine_instance_preset.py +9 -0
  123. ocp_resources/virtual_machine_restore.py +99 -0
  124. ocp_resources/virtual_machine_snapshot.py +96 -0
  125. ocp_resources/virtual_service.py +11 -0
  126. ocp_resources/vm_import_config.py +9 -0
  127. ocp_resources/volume_snapshot.py +9 -0
  128. ocp_resources/volume_snapshot_class.py +9 -0
  129. openshift_python_wrapper-4.11.61.dist-info/LICENSE +201 -0
  130. openshift_python_wrapper-4.11.61.dist-info/METADATA +188 -0
  131. openshift_python_wrapper-4.11.61.dist-info/RECORD +132 -0
  132. openshift_python_wrapper-4.11.61.dist-info/WHEEL +4 -0
@@ -0,0 +1,64 @@
1
+ from ocp_resources.constants import TIMEOUT_4MINUTES
2
+ from ocp_resources.mtv import MTV
3
+ from ocp_resources.resource import NamespacedResource
4
+
5
+
6
+ class NetworkMap(NamespacedResource, MTV):
7
+ """
8
+ Migration Toolkit For Virtualization (MTV) NetworkMap object.
9
+
10
+ Args:
11
+ source_provider_name (str): MTV Source Provider CR name.
12
+ source_provider_namespace (str): MTV Source Provider CR namespace.
13
+ destination_provider_name (str): MTV Destination Provider CR name.
14
+ destination_provider_namespace (str): MTV Destination Provider CR namespace.
15
+ mapping (dict): Storage Resources Mapping
16
+ Exaple:
17
+ [ { "destination" : { "type": "pod",
18
+ "source" : { "id": "network-13" }},
19
+
20
+ { "destination" : { "name": "nad_cr_name",
21
+ "namespace": "nad_cr_namespace",
22
+ "type": "multus"},
23
+ "source" : { "name": "VM Netowrk" }},
24
+
25
+ ]
26
+ """
27
+
28
+ api_group = NamespacedResource.ApiGroup.FORKLIFT_KONVEYOR_IO
29
+
30
+ def __init__(
31
+ self,
32
+ name=None,
33
+ namespace=None,
34
+ mapping=None,
35
+ source_provider_name=None,
36
+ source_provider_namespace=None,
37
+ destination_provider_name=None,
38
+ destination_provider_namespace=None,
39
+ client=None,
40
+ teardown=True,
41
+ yaml_file=None,
42
+ delete_timeout=TIMEOUT_4MINUTES,
43
+ **kwargs,
44
+ ):
45
+ super().__init__(
46
+ name=name,
47
+ namespace=namespace,
48
+ client=client,
49
+ teardown=teardown,
50
+ yaml_file=yaml_file,
51
+ delete_timeout=delete_timeout,
52
+ **kwargs,
53
+ )
54
+ self.mapping = mapping
55
+ self.source_provider_name = source_provider_name
56
+ self.source_provider_namespace = source_provider_namespace
57
+ self.destination_provider_name = destination_provider_name
58
+ self.destination_provider_namespace = destination_provider_namespace
59
+ self.condition_message_ready = self.ConditionMessage.NETWORK_MAP_READY
60
+
61
+ def to_dict(self):
62
+ super().to_dict()
63
+ if not self.yaml_file:
64
+ self.res.update(self.map_to_dict)
@@ -0,0 +1,9 @@
1
+ from ocp_resources.resource import NamespacedResource
2
+
3
+
4
+ class NetworkPolicy(NamespacedResource):
5
+ """
6
+ NetworkPolicy object.
7
+ """
8
+
9
+ api_group = NamespacedResource.ApiGroup.NETWORKING_K8S_IO
@@ -0,0 +1,9 @@
1
+ from ocp_resources.resource import Resource
2
+
3
+
4
+ class NMState(Resource):
5
+ """
6
+ NMState, a CR of k8s-nmstate
7
+ """
8
+
9
+ api_group = Resource.ApiGroup.NMSTATE_IO
ocp_resources/node.py ADDED
@@ -0,0 +1,41 @@
1
+ from ocp_resources.resource import Resource
2
+
3
+
4
+ class Node(Resource):
5
+ """
6
+ Node object, inherited from Resource.
7
+ """
8
+
9
+ api_version = Resource.ApiVersion.V1
10
+
11
+ class Status(Resource.Status):
12
+ READY = "Ready"
13
+ SCHEDULING_DISABLED = "Ready,SchedulingDisabled"
14
+
15
+ @property
16
+ def kubelet_ready(self):
17
+ return any(
18
+ stat["reason"] == "KubeletReady"
19
+ and stat["status"] == self.Condition.Status.TRUE
20
+ for stat in self.instance.status.conditions
21
+ )
22
+
23
+ @property
24
+ def machine_name(self):
25
+ return self.instance.metadata.annotations[
26
+ f"{self.ApiGroup.MACHINE_OPENSHIFT_IO}/machine"
27
+ ].split("/")[-1]
28
+
29
+ @property
30
+ def internal_ip(self):
31
+ for addr in self.instance.status.addresses:
32
+ if addr.type == "InternalIP":
33
+ return addr.address
34
+
35
+ @property
36
+ def hostname(self):
37
+ return self.labels["kubernetes.io/hostname"]
38
+
39
+ @property
40
+ def taints(self):
41
+ return self.instance.get("spec", {}).get("taints")
@@ -0,0 +1,10 @@
1
+ from ocp_resources.resource import NamespacedResource
2
+
3
+
4
+ class NodeHealthCheck(NamespacedResource):
5
+ """
6
+ NodeHealthCheck object.
7
+ Reference : https://github.com/medik8s/node-healthcheck-operator
8
+ """
9
+
10
+ api_group = NamespacedResource.ApiGroup.REMEDIATION_MEDIK8S_IO
@@ -0,0 +1,40 @@
1
+ from ocp_resources.constants import TIMEOUT_4MINUTES
2
+ from ocp_resources.resource import Resource
3
+
4
+
5
+ class NodeMaintenance(Resource):
6
+ """
7
+ Node Maintenance object, inherited from Resource.
8
+ """
9
+
10
+ api_group = Resource.ApiGroup.NODEMAINTENANCE_KUBEVIRT_IO
11
+
12
+ def __init__(
13
+ self,
14
+ name=None,
15
+ client=None,
16
+ node=None,
17
+ reason="TEST Reason",
18
+ teardown=True,
19
+ timeout=TIMEOUT_4MINUTES,
20
+ yaml_file=None,
21
+ delete_timeout=TIMEOUT_4MINUTES,
22
+ **kwargs,
23
+ ):
24
+ super().__init__(
25
+ name=name,
26
+ client=client,
27
+ teardown=teardown,
28
+ timeout=timeout,
29
+ yaml_file=yaml_file,
30
+ delete_timeout=delete_timeout,
31
+ **kwargs,
32
+ )
33
+ self.node = node
34
+ self.reason = reason
35
+
36
+ def to_dict(self):
37
+ super().to_dict()
38
+ if not self.yaml_file:
39
+ assert self.node, "node is mandatory for create"
40
+ self.res["spec"] = {"nodeName": self.node.name, "reason": self.reason}
@@ -0,0 +1,20 @@
1
+ from ocp_resources.resource import Resource
2
+
3
+
4
+ class NodeNetworkConfigurationEnactment(Resource):
5
+ api_group = Resource.ApiGroup.NMSTATE_IO
6
+
7
+ class Conditions:
8
+ class Type:
9
+ FAILING = "Failing"
10
+ AVAILABLE = "Available"
11
+ PROGRESSING = "Progressing"
12
+ PENDING = "Pending"
13
+ ABORTED = "Aborted"
14
+
15
+ class Reason:
16
+ CONFIGURATION_PROGRESSING = "ConfigurationProgressing"
17
+ SUCCESSFULLY_CONFIGURED = "SuccessfullyConfigured"
18
+ FAILED_TO_CONFIGURE = "FailedToConfigure"
19
+ CONFIGURATION_ABORTED = "ConfigurationAborted"
20
+ MAX_UNAVAILABLE_LIMIT_REACHED = "MaxUnavailableLimitReached"
@@ -0,0 +1,472 @@
1
+ import re
2
+
3
+ from openshift.dynamic.exceptions import ConflictError
4
+
5
+ from ocp_resources.constants import TIMEOUT_4MINUTES
6
+ from ocp_resources.node import Node
7
+ from ocp_resources.node_network_configuration_enactment import (
8
+ NodeNetworkConfigurationEnactment,
9
+ )
10
+ from ocp_resources.node_network_state import NodeNetworkState
11
+ from ocp_resources.resource import Resource, ResourceEditor
12
+ from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
13
+
14
+
15
+ IPV4_STR = "ipv4"
16
+ IPV6_STR = "ipv6"
17
+
18
+
19
+ class NNCPConfigurationFailed(Exception):
20
+ pass
21
+
22
+
23
+ class NodeNetworkConfigurationPolicy(Resource):
24
+ api_group = Resource.ApiGroup.NMSTATE_IO
25
+
26
+ class Conditions:
27
+ class Type:
28
+ DEGRADED = "Degraded"
29
+ AVAILABLE = "Available"
30
+
31
+ class Reason:
32
+ CONFIGURATION_PROGRESSING = "ConfigurationProgressing"
33
+ SUCCESSFULLY_CONFIGURED = "SuccessfullyConfigured"
34
+ FAILED_TO_CONFIGURE = "FailedToConfigure"
35
+ NO_MATCHING_NODE = "NoMatchingNode"
36
+
37
+ def __init__(
38
+ self,
39
+ name=None,
40
+ client=None,
41
+ capture=None,
42
+ node_selector=None,
43
+ node_selector_labels=None,
44
+ teardown_absent_ifaces=True,
45
+ teardown=True,
46
+ mtu=None,
47
+ ports=None,
48
+ ipv4_enable=False,
49
+ ipv4_dhcp=False,
50
+ ipv4_auto_dns=True,
51
+ ipv4_addresses=None,
52
+ ipv6_enable=False,
53
+ ipv6_dhcp=False,
54
+ ipv6_auto_dns=True,
55
+ ipv6_addresses=None,
56
+ dns_resolver=None,
57
+ routes=None,
58
+ yaml_file=None,
59
+ set_ipv4=True,
60
+ set_ipv6=True,
61
+ max_unavailable=None,
62
+ state=None,
63
+ success_timeout=480,
64
+ delete_timeout=TIMEOUT_4MINUTES,
65
+ **kwargs,
66
+ ):
67
+ """
68
+ ipv4_addresses should be sent in this format:
69
+ [{"ip": <ip1-string>, "prefix-length": <prefix-len1-int>},
70
+ {"ip": <ip2-string>, "prefix-length": <prefix-len2-int>}, ...]
71
+ For example:
72
+ [{"ip": "10.1.2.3", "prefix-length": 24},
73
+ {"ip": "10.4.5.6", "prefix-length": 24},
74
+ {"ip": "10.7.8.9", "prefix-length": 23}]
75
+ """
76
+ super().__init__(
77
+ name=name,
78
+ client=client,
79
+ teardown=teardown,
80
+ yaml_file=yaml_file,
81
+ delete_timeout=delete_timeout,
82
+ node_selector=node_selector,
83
+ node_selector_labels=node_selector_labels,
84
+ **kwargs,
85
+ )
86
+ self.desired_state = {"interfaces": []}
87
+ self.mtu = mtu
88
+ self.capture = capture
89
+ self.mtu_dict = {}
90
+ self.ports = ports or []
91
+ self.iface = None
92
+ self.ipv4_enable = ipv4_enable
93
+ self.ipv4_dhcp = ipv4_dhcp
94
+ self.ipv4_auto_dns = ipv4_auto_dns
95
+ self.ipv4_addresses = ipv4_addresses or []
96
+ self.ipv4_iface_state = {}
97
+ self.ipv6_enable = ipv6_enable
98
+ self.ipv6_dhcp = ipv6_dhcp
99
+ self.ipv6_autoconf = self.ipv6_dhcp
100
+ self.ipv6_auto_dns = ipv6_auto_dns
101
+ self.ipv6_addresses = ipv6_addresses
102
+ self.dns_resolver = dns_resolver
103
+ self.routes = routes
104
+ self.state = state or self.Interface.State.UP
105
+ self.set_ipv4 = set_ipv4
106
+ self.set_ipv6 = set_ipv6
107
+ self.success_timeout = success_timeout
108
+ self.max_unavailable = max_unavailable
109
+ self.ipv4_ports_backup_dict = {}
110
+ self.ipv6_ports_backup_dict = {}
111
+ self.nodes = self._nodes()
112
+ self.teardown_absent_ifaces = teardown_absent_ifaces
113
+
114
+ def _nodes(self):
115
+ if self.node_selector:
116
+ return list(Node.get(dyn_client=self.client, name=self.node_selector))
117
+ if self.node_selector_labels:
118
+ node_labels = ",".join(
119
+ [
120
+ f"{label_key}={label_value}"
121
+ for label_key, label_value in self.node_selector_labels.items()
122
+ ]
123
+ )
124
+ return list(Node.get(dyn_client=self.client, label_selector=node_labels))
125
+
126
+ def set_interface(self, interface):
127
+ if not self.res:
128
+ super().to_dict()
129
+ # First drop the interface if it's already in the list
130
+ interfaces = [
131
+ iface
132
+ for iface in self.desired_state["interfaces"]
133
+ if iface["name"] != interface["name"]
134
+ ]
135
+ # Add the interface
136
+ interfaces.append(interface)
137
+ self.desired_state["interfaces"] = interfaces
138
+ self.res.setdefault("spec", {}).setdefault("desiredState", {})[
139
+ "interfaces"
140
+ ] = self.desired_state["interfaces"]
141
+
142
+ def to_dict(self):
143
+ super().to_dict()
144
+ if not self.yaml_file:
145
+ if self.dns_resolver or self.routes or self.iface:
146
+ self.res.setdefault("spec", {}).setdefault("desiredState", {})
147
+
148
+ if self.node_selector_spec:
149
+ self.res.setdefault("spec", {}).setdefault(
150
+ "nodeSelector", self.node_selector_spec
151
+ )
152
+
153
+ if self.capture:
154
+ self.res["spec"]["capture"] = self.capture
155
+
156
+ if self.dns_resolver:
157
+ self.res["spec"]["desiredState"]["dns-resolver"] = self.dns_resolver
158
+
159
+ if self.routes:
160
+ self.res["spec"]["desiredState"]["routes"] = self.routes
161
+
162
+ if self.max_unavailable:
163
+ self.res.setdefault("spec", {}).setdefault(
164
+ "maxUnavailable", self.max_unavailable
165
+ )
166
+
167
+ if self.iface:
168
+ """
169
+ It's the responsibility of the caller to verify the desired configuration they send.
170
+ For example: "ipv4.dhcp.enabled: false" without specifying any static IP address
171
+ is a valid desired state and therefore not blocked in the code, but nmstate would
172
+ reject it. Such configuration might be used for negative tests.
173
+ """
174
+ self.res = self.add_interface(
175
+ iface=self.iface,
176
+ state=self.state,
177
+ set_ipv4=self.set_ipv4,
178
+ ipv4_enable=self.ipv4_enable,
179
+ ipv4_dhcp=self.ipv4_dhcp,
180
+ ipv4_auto_dns=self.ipv4_auto_dns,
181
+ ipv4_addresses=self.ipv4_addresses,
182
+ set_ipv6=self.set_ipv6,
183
+ ipv6_enable=self.ipv6_enable,
184
+ ipv6_dhcp=self.ipv6_dhcp,
185
+ ipv6_auto_dns=self.ipv6_auto_dns,
186
+ ipv6_addresses=self.ipv6_addresses,
187
+ ipv6_autoconf=self.ipv6_autoconf,
188
+ )
189
+
190
+ def add_interface(
191
+ self,
192
+ iface=None,
193
+ name=None,
194
+ type_=None,
195
+ state=None,
196
+ set_ipv4=True,
197
+ ipv4_enable=False,
198
+ ipv4_dhcp=False,
199
+ ipv4_auto_dns=True,
200
+ ipv4_addresses=None,
201
+ set_ipv6=True,
202
+ ipv6_enable=False,
203
+ ipv6_dhcp=False,
204
+ ipv6_auto_dns=True,
205
+ ipv6_addresses=None,
206
+ ipv6_autoconf=False,
207
+ ):
208
+ # If self.res is already defined (from to_dict()), don't call it again.
209
+ if not self.res:
210
+ self.to_dict()
211
+
212
+ self.res.setdefault("spec", {}).setdefault("desiredState", {})
213
+ if not iface:
214
+ iface = {
215
+ "name": name,
216
+ "type": type_,
217
+ "state": state,
218
+ }
219
+ if set_ipv4:
220
+ if isinstance(set_ipv4, str):
221
+ iface[IPV4_STR] = set_ipv4
222
+
223
+ else:
224
+ iface[IPV4_STR] = {
225
+ "enabled": ipv4_enable,
226
+ "dhcp": ipv4_dhcp,
227
+ "auto-dns": ipv4_auto_dns,
228
+ }
229
+ if ipv4_addresses:
230
+ iface[IPV4_STR]["address"] = ipv4_addresses
231
+
232
+ if set_ipv6:
233
+ if isinstance(set_ipv6, str):
234
+ iface[IPV6_STR] = set_ipv6
235
+
236
+ else:
237
+ iface[IPV6_STR] = {
238
+ "enabled": ipv6_enable,
239
+ "dhcp": ipv6_dhcp,
240
+ "auto-dns": ipv6_auto_dns,
241
+ "autoconf": ipv6_autoconf,
242
+ }
243
+ if ipv6_addresses:
244
+ iface[IPV6_STR]["address"] = ipv6_addresses
245
+
246
+ self.set_interface(interface=iface)
247
+ return self.res
248
+
249
+ def _get_port_from_nns(self, port_name):
250
+ if not self.nodes:
251
+ return None
252
+
253
+ nns = NodeNetworkState(name=self.nodes[0].name)
254
+ _port = [_iface for _iface in nns.interfaces if _iface["name"] == port_name]
255
+ return _port[0] if _port else None
256
+
257
+ def _ports_backup(self, ip_family):
258
+ for port in self.ports:
259
+ _port = self._get_port_from_nns(port_name=port)
260
+ if _port:
261
+ if ip_family == IPV4_STR:
262
+ self.ipv4_ports_backup_dict[port] = _port[ip_family]
263
+ elif ip_family == IPV6_STR:
264
+ self.ipv6_ports_backup_dict[port] = _port[ip_family]
265
+ else:
266
+ raise ValueError(
267
+ f"'ip_family' must be either '{IPV4_STR}' or '{IPV6_STR}'"
268
+ )
269
+
270
+ def ipv4_ports_backup(self):
271
+ self._ports_backup(ip_family=IPV4_STR)
272
+
273
+ def ipv6_ports_backup(self):
274
+ self._ports_backup(ip_family=IPV6_STR)
275
+
276
+ def add_ports(self):
277
+ for port in self.ports:
278
+ _port = self._get_port_from_nns(port_name=port)
279
+ if _port:
280
+ ipv4_backup = self.ipv4_ports_backup_dict.get(port)
281
+ ipv6_backup = self.ipv6_ports_backup_dict.get(port)
282
+ if ipv4_backup or ipv6_backup:
283
+ iface = {
284
+ "name": port,
285
+ "type": _port["type"],
286
+ "state": _port["state"],
287
+ }
288
+ if ipv4_backup:
289
+ iface[IPV4_STR] = ipv4_backup
290
+
291
+ if ipv6_backup:
292
+ iface[IPV6_STR] = ipv6_backup
293
+
294
+ self.set_interface(interface=iface)
295
+
296
+ def apply(self, resource=None):
297
+ if not resource:
298
+ super().to_dict()
299
+ resource = self.res
300
+ samples = TimeoutSampler(
301
+ wait_timeout=3,
302
+ sleep=1,
303
+ exceptions_dict={ConflictError: []},
304
+ func=self.update,
305
+ resource_dict=resource,
306
+ )
307
+ self.logger.info(f"Applying {resource}")
308
+ for _ in samples:
309
+ return
310
+
311
+ def deploy(self, wait=False):
312
+ self.ipv4_ports_backup()
313
+ self.ipv6_ports_backup()
314
+
315
+ self.create(wait=wait)
316
+ try:
317
+ self.wait_for_status_success()
318
+ return self
319
+ except Exception as exp:
320
+ self.logger.error(exp)
321
+ super().__exit__(exception_type=None, exception_value=None, traceback=None)
322
+ raise
323
+
324
+ def clean_up(self):
325
+ if self.teardown_absent_ifaces:
326
+ try:
327
+ self._absent_interface()
328
+ self.wait_for_status_success()
329
+ except Exception as exp:
330
+ self.logger.error(exp)
331
+
332
+ super().clean_up()
333
+
334
+ def _absent_interface(self):
335
+ for _iface in self.desired_state["interfaces"]:
336
+ _iface["state"] = self.Interface.State.ABSENT
337
+ self.set_interface(interface=_iface)
338
+
339
+ if self.ports:
340
+ self.add_ports()
341
+
342
+ ResourceEditor(
343
+ patches={
344
+ self: {
345
+ "spec": {
346
+ "desiredState": {"interfaces": self.desired_state["interfaces"]}
347
+ }
348
+ }
349
+ }
350
+ ).update()
351
+
352
+ @property
353
+ def status(self):
354
+ for condition in self.instance.status.conditions:
355
+ if condition["type"] == self.Conditions.Type.AVAILABLE:
356
+ return condition["reason"]
357
+
358
+ def wait_for_configuration_conditions_unknown_or_progressing(self, wait_timeout=30):
359
+ samples = TimeoutSampler(
360
+ wait_timeout=wait_timeout,
361
+ sleep=1,
362
+ func=lambda: self.instance.status.conditions,
363
+ )
364
+ for sample in samples:
365
+ if (
366
+ sample
367
+ and sample[0]["type"] == self.Conditions.Type.AVAILABLE
368
+ and (
369
+ sample[0]["status"] == self.Condition.Status.UNKNOWN
370
+ or sample[0]["reason"]
371
+ == self.Conditions.Reason.CONFIGURATION_PROGRESSING
372
+ )
373
+ ):
374
+ return sample
375
+
376
+ def _process_failed_status(self, failed_condition_reason):
377
+ last_err_msg = None
378
+ for failed_nnce in self._get_failed_nnce():
379
+ nnce_name = failed_nnce.instance.metadata.name
380
+ nnce_dict = failed_nnce.instance.to_dict()
381
+ for cond in nnce_dict["status"]["conditions"]:
382
+ err_msg = self._get_nnce_error_msg(
383
+ nnce_name=nnce_name, nnce_condition=cond
384
+ )
385
+ if err_msg:
386
+ last_err_msg = err_msg
387
+
388
+ raise NNCPConfigurationFailed(
389
+ f"Reason: {failed_condition_reason}\n{last_err_msg}"
390
+ )
391
+
392
+ def wait_for_status_success(self):
393
+ failed_condition_reason = self.Conditions.Reason.FAILED_TO_CONFIGURE
394
+ no_match_node_condition_reason = self.Conditions.Reason.NO_MATCHING_NODE
395
+
396
+ # if we get here too fast there are no conditions, we need to wait.
397
+ self.wait_for_configuration_conditions_unknown_or_progressing()
398
+
399
+ samples = TimeoutSampler(
400
+ wait_timeout=self.success_timeout, sleep=1, func=lambda: self.status
401
+ )
402
+ try:
403
+ for sample in samples:
404
+ if sample == self.Conditions.Reason.SUCCESSFULLY_CONFIGURED:
405
+ self.logger.info(f"NNCP {self.name} configured Successfully")
406
+ return sample
407
+
408
+ elif sample == no_match_node_condition_reason:
409
+ raise NNCPConfigurationFailed(
410
+ f"{self.name}. Reason: {no_match_node_condition_reason}"
411
+ )
412
+
413
+ elif sample == failed_condition_reason:
414
+ self._process_failed_status(
415
+ failed_condition_reason=failed_condition_reason
416
+ )
417
+
418
+ except (TimeoutExpiredError, NNCPConfigurationFailed):
419
+ self.logger.error(
420
+ f"Unable to configure NNCP {self.name} "
421
+ f"{f'nodes: {[node.name for node in self.nodes]}' if self.nodes else ''}"
422
+ )
423
+ raise
424
+
425
+ @property
426
+ def nnces(self):
427
+ nnces = []
428
+ for nnce in NodeNetworkConfigurationEnactment.get(dyn_client=self.client):
429
+ if nnce.name.endswith(f".{self.name}"):
430
+ nnces.append(nnce)
431
+ return nnces
432
+
433
+ def node_nnce(self, node_name):
434
+ nnce = [
435
+ nnce for nnce in self.nnces if nnce.labels["nmstate.io/node"] == node_name
436
+ ]
437
+ return nnce[0] if nnce else None
438
+
439
+ @staticmethod
440
+ def _get_nnce_error_msg(nnce_name, nnce_condition):
441
+ err_msg = ""
442
+ nnce_prefix = f"NNCE {nnce_name}"
443
+ nnce_msg = nnce_condition.get("message")
444
+ if not nnce_msg:
445
+ return err_msg
446
+
447
+ errors = nnce_msg.split("->")
448
+ if errors:
449
+ err_msg += f"{nnce_prefix}: {errors[0]}"
450
+ if len(errors) > 1:
451
+ err_msg += errors[-1]
452
+
453
+ libnmstate_err = re.findall(r"libnmstate.error.*", nnce_msg)
454
+ if libnmstate_err:
455
+ err_msg += f"{nnce_prefix}: {libnmstate_err[0]}"
456
+
457
+ return err_msg
458
+
459
+ def _get_failed_nnce(self):
460
+ for nnce in self.nnces:
461
+ try:
462
+ nnce.wait_for_conditions()
463
+ except TimeoutExpiredError:
464
+ self.logger.error(f"Failed to get NNCE {nnce.name} status")
465
+ continue
466
+
467
+ for nnce_cond in nnce.instance.status.conditions:
468
+ if (
469
+ nnce_cond.type == "Failing"
470
+ and nnce_cond.status == Resource.Condition.Status.TRUE
471
+ ):
472
+ yield nnce