Coverage for ocp_resources/virtual_machine.py: 0%

80 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-30 10:48 +0200

1from __future__ import annotations 

2from typing import Any 

3 

4 

5from ocp_resources.utils.constants import ( 

6 DEFAULT_CLUSTER_RETRY_EXCEPTIONS, 

7 PROTOCOL_ERROR_EXCEPTION_DICT, 

8 TIMEOUT_4MINUTES, 

9 TIMEOUT_30SEC, 

10 TIMEOUT_5SEC, 

11) 

12from ocp_resources.resource import NamespacedResource 

13from timeout_sampler import TimeoutSampler 

14from ocp_resources.virtual_machine_instance import VirtualMachineInstance 

15 

16 

17class VirtualMachine(NamespacedResource): 

18 """ 

19 Virtual Machine object, inherited from Resource. 

20 Implements actions start / stop / status / wait for VM status / is running 

21 """ 

22 

23 api_group = NamespacedResource.ApiGroup.KUBEVIRT_IO 

24 

25 class RunStrategy: 

26 MANUAL = "Manual" 

27 HALTED = "Halted" 

28 ALWAYS = "Always" 

29 RERUNONFAILURE = "RerunOnFailure" 

30 

31 class Status(NamespacedResource.Status): 

32 MIGRATING = "Migrating" 

33 PAUSED = "Paused" 

34 PROVISIONING = "Provisioning" 

35 STARTING = "Starting" 

36 STOPPED = "Stopped" 

37 STOPPING = "Stopping" 

38 WAITING_FOR_VOLUME_BINDING = "WaitingForVolumeBinding" 

39 ERROR_UNSCHEDULABLE = "ErrorUnschedulable" 

40 DATAVOLUME_ERROR = "DataVolumeError" 

41 ERROR_PVC_NOT_FOUND = "ErrorPvcNotFound" 

42 IMAGE_PULL_BACK_OFF = "ImagePullBackOff" 

43 ERR_IMAGE_PULL = "ErrImagePull" 

44 CRASH_LOOPBACK_OFF = "CrashLoopBackOff" 

45 

46 def __init__( 

47 self, 

48 name=None, 

49 namespace=None, 

50 client=None, 

51 body=None, 

52 teardown=True, 

53 yaml_file=None, 

54 delete_timeout=TIMEOUT_4MINUTES, 

55 **kwargs, 

56 ): 

57 super().__init__( 

58 name=name, 

59 namespace=namespace, 

60 client=client, 

61 teardown=teardown, 

62 yaml_file=yaml_file, 

63 delete_timeout=delete_timeout, 

64 **kwargs, 

65 ) 

66 self.body = body 

67 

68 @property 

69 def _subresource_api_url(self): 

70 return ( 

71 f"{self.client.configuration.host}/" 

72 f"apis/subresources.kubevirt.io/{self.api.api_version}/" 

73 f"namespaces/{self.namespace}/virtualmachines/{self.name}" 

74 ) 

75 

76 def api_request( 

77 self, method: str, action: str, url: str = "", retry_params: dict[str, int] | None = None, **params: Any 

78 ) -> dict[str, Any]: 

79 default_vm_api_request_retry_params: dict[str, int] = {"timeout": TIMEOUT_30SEC, "sleep_time": TIMEOUT_5SEC} 

80 return super().api_request( 

81 method=method, 

82 action=action, 

83 url=url or self._subresource_api_url, 

84 retry_params=retry_params or default_vm_api_request_retry_params, 

85 **params, 

86 ) 

87 

88 def to_dict(self) -> None: 

89 super().to_dict() 

90 if not self.kind_dict and not self.yaml_file: 

91 body_spec = self.body.get("spec") if self.body else None 

92 self.res["spec"] = body_spec or {"template": {"spec": {}}} 

93 

94 def start(self, timeout=TIMEOUT_4MINUTES, wait=False): 

95 self.api_request(method="PUT", action="start") 

96 if wait: 

97 return self.wait_for_ready_status(timeout=timeout, status=True) 

98 

99 def restart(self, timeout=TIMEOUT_4MINUTES, wait=False): 

100 self.api_request(method="PUT", action="restart") 

101 if wait: 

102 self.vmi.virt_launcher_pod.wait_deleted() 

103 return self.vmi.wait_until_running(timeout=timeout, stop_status="dummy") 

104 

105 def stop(self, timeout=TIMEOUT_4MINUTES, vmi_delete_timeout=TIMEOUT_4MINUTES, wait=False): 

106 self.api_request(method="PUT", action="stop") 

107 if wait: 

108 self.wait_for_ready_status(timeout=timeout, status=None) 

109 return self.vmi.wait_deleted(timeout=vmi_delete_timeout) 

110 

111 def wait_for_ready_status(self, status, timeout=TIMEOUT_4MINUTES, sleep=1): 

112 """ 

113 Wait for VM resource ready status to be at desire status 

114 

115 Args: 

116 status (any): True for a running VM, None for a stopped VM. 

117 timeout (int): Time to wait for the resource. 

118 

119 Raises: 

120 TimeoutExpiredError: If timeout reached. 

121 """ 

122 self.logger.info(f"Wait for {self.kind} {self.name} status to be {'ready' if status is True else status}") 

123 samples = TimeoutSampler( 

124 wait_timeout=timeout, 

125 sleep=sleep, 

126 exceptions_dict={ 

127 **PROTOCOL_ERROR_EXCEPTION_DICT, 

128 **DEFAULT_CLUSTER_RETRY_EXCEPTIONS, 

129 }, 

130 func=self.api.get, 

131 field_selector=f"metadata.name=={self.name}", 

132 namespace=self.namespace, 

133 ) 

134 for sample in samples: 

135 if sample.items and self.ready == status: 

136 # VM with runStrategy does not have spec.running attribute 

137 # VM status should be taken from spec.status.ready 

138 return 

139 

140 def get_interfaces(self): 

141 return self.instance.spec.template.spec.domain.devices.interfaces 

142 

143 @property 

144 def vmi(self): 

145 """ 

146 Get VMI 

147 

148 Returns: 

149 VirtualMachineInstance: VMI 

150 """ 

151 return VirtualMachineInstance( 

152 client=self.client, 

153 name=self.name, 

154 namespace=self.namespace, 

155 ) 

156 

157 @property 

158 def ready(self): 

159 """ 

160 Get VM status 

161 

162 Returns: 

163 True if Running else None 

164 """ 

165 return self.instance.get("status", {}).get("ready") 

166 

167 @property 

168 def printable_status(self): 

169 """ 

170 Get VM printableStatus 

171 

172 Returns: 

173 VM printableStatus if VM.status.printableStatus else None 

174 """ 

175 return self.instance.get("status", {}).get("printableStatus") 

176 

177 def wait_for_status_none(self, status, timeout=TIMEOUT_4MINUTES): 

178 self.logger.info(f"Wait for {self.kind} {self.name} status {status} to be None") 

179 for sample in TimeoutSampler( 

180 wait_timeout=timeout, 

181 sleep=1, 

182 exceptions_dict=PROTOCOL_ERROR_EXCEPTION_DICT, 

183 func=lambda: self.instance.get("status", {}).get(status), 

184 ): 

185 if sample is None: 

186 return 

187 

188 @property 

189 def keys_to_hash(self): 

190 return ["spec>template>spec>volumes[]>cloudInitNoCloud>userData"]