Class: Beaker::KubevirtHelper
- Inherits:
-
Object
- Object
- Beaker::KubevirtHelper
- Defined in:
- lib/beaker/hypervisor/kubevirt_helper.rb
Overview
Helper class for KubeVirt operations
Instance Attribute Summary collapse
-
#cdi_client ⇒ Object
readonly
Returns the value of attribute cdi_client.
-
#k8s_client ⇒ Object
readonly
Returns the value of attribute k8s_client.
-
#kubevirt_client ⇒ Object
readonly
Returns the value of attribute kubevirt_client.
-
#namespace ⇒ Object
readonly
Returns the value of attribute namespace.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
-
#cleanup_data_volumes(test_group_identifier) ⇒ Object
Cleanup DataVolumes associated with a test group.
-
#cleanup_secrets(test_group_identifier) ⇒ Object
Cleanup secrets associated with a test group.
-
#cleanup_services(test_group_identifier) ⇒ Object
Cleanup services associated with a test group.
-
#cleanup_temp_files ⇒ Object
Unlink the kubeconfig-derived cert/key tempfiles kept alive for the process lifetime.
-
#cleanup_vms(test_group_identifier) ⇒ Object
Cleanup VMs created by this test group.
-
#create_nodeport_service(vm_name, service_name, test_group_identifier = nil) ⇒ Hash
Create a NodePort service for SSH access.
-
#create_secret(secret_spec) ⇒ Object
Create a secret.
-
#create_vm(vm_spec) ⇒ Object
Create a virtual machine.
-
#delete_vm(vm_name) ⇒ Object
Delete a virtual machine.
- #forwarder_error_handler(error) ⇒ Object
-
#get_virt_launcher_pod(vmi_name) ⇒ Hash?
Get the virt-launcher pod backing a VMI.
-
#get_vm(vm_name) ⇒ Hash
Get a virtual machine.
-
#get_vmi(vmi_name) ⇒ Hash
Get a virtual machine instance.
-
#initialize(options) ⇒ KubevirtHelper
constructor
A new instance of KubevirtHelper.
-
#node_ip ⇒ String
Get a cluster node IP address.
-
#setup_port_forward(vm_name, vm_port, local_port) ⇒ Process
Setup port forwarding for a VM.
Constructor Details
#initialize(options) ⇒ KubevirtHelper
Returns a new instance of KubevirtHelper.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 13 def initialize() @options = @namespace = [:namespace] || 'default' raw_kubeconfig = [:kubeconfig] || ENV['KUBECONFIG'] || File.join(Dir.home, '.kube', 'config') @kubeconfig_path = File.(raw_kubeconfig) @kubecontext = [:kubecontext] || ENV.fetch('KUBECONTEXT', nil) @logger = [:logger] # Store original SSL options for port forwarding (before kubeclient processes them) @original_ssl_options = nil # Keep temp files alive to prevent garbage collection @temp_files = [] # Allow injection of clients for testing @k8s_client = [:k8s_client] @kubevirt_client = [:kubevirt_client] @cdi_client = [:cdi_client] # Only setup clients if not provided (for testing) return if @k8s_client && @kubevirt_client # Manually extract SSL options with CA file by parsing kubeconfig directly # (Kubeclient context doesn't expose the raw CA file path/data) @original_ssl_options = extract_ssl_from_kubeconfig setup_kubernetes_client setup_kubevirt_client setup_cdi_client unless @cdi_client end |
Instance Attribute Details
#cdi_client ⇒ Object (readonly)
Returns the value of attribute cdi_client.
11 12 13 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 11 def cdi_client @cdi_client end |
#k8s_client ⇒ Object (readonly)
Returns the value of attribute k8s_client.
11 12 13 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 11 def k8s_client @k8s_client end |
#kubevirt_client ⇒ Object (readonly)
Returns the value of attribute kubevirt_client.
11 12 13 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 11 def kubevirt_client @kubevirt_client end |
#namespace ⇒ Object (readonly)
Returns the value of attribute namespace.
11 12 13 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 11 def namespace @namespace end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
11 12 13 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 11 def @options end |
Instance Method Details
#cleanup_data_volumes(test_group_identifier) ⇒ Object
Cleanup DataVolumes associated with a test group. No-op if CDI isn't available on the cluster (the @cdi_client stays nil in that case).
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 210 def cleanup_data_volumes(test_group_identifier) return unless @cdi_client @logger.info("Cleaning up DataVolumes for test group: #{test_group_identifier}") label_selector = "beaker/test-group=#{test_group_identifier}" begin dvs = @cdi_client.get_data_volumes(namespace: @namespace, label_selector: label_selector) rescue StandardError => e @logger.warn("Failed to list DataVolumes for cleanup: #{e.class}: #{e.}") return end @logger.info("Found #{dvs.length} DataVolume(s) with label #{label_selector}") dvs.each do |dv| md = dv[:metadata] || dv['metadata'] dv_name = md[:name] || md['name'] if md unless dv_name && !dv_name.empty? @logger.error("Skipping DataVolume with missing name: #{dv.inspect[0, 200]}") next end @cdi_client.delete_data_volume(dv_name, @namespace) @logger.info("Deleted DataVolume #{dv_name}") rescue Kubeclient::ResourceNotFoundError @logger.debug("DataVolume #{dv_name} not found during cleanup") rescue StandardError => e @logger.error("Error deleting DataVolume #{dv_name}: #{e.}") end end |
#cleanup_secrets(test_group_identifier) ⇒ Object
Cleanup secrets associated with a test group
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 141 def cleanup_secrets(test_group_identifier) @logger.info("Cleaning up secrets for test group: #{test_group_identifier}") label_selector = "beaker/test-group=#{test_group_identifier}" @logger.debug("Using label selector: #{label_selector}") secrets = @k8s_client.get_secrets(namespace: @namespace, label_selector: label_selector) @logger.info("Found #{secrets.length} secret(s) with label beaker/test-group=#{test_group_identifier}") secrets.each do |secret| # Extract secret name - access hash-style to avoid RSpec metadata collision = secret[:metadata] || secret['metadata'] secret_name = [:name] || ['name'] if unless secret_name && !secret_name.empty? @logger.error('Cannot delete secret with empty or nil name.') @logger.error("Secret structure: #{secret.inspect[0..1000]}") next end secret_labels = [:labels] || ['labels'] if @logger.debug("Deleting secret #{secret_name} with labels: #{secret_labels.inspect}") @k8s_client.delete_secret(secret_name, @namespace) @logger.info("Deleted secret #{secret_name}") rescue Kubeclient::ResourceNotFoundError @logger.debug("Secret #{secret_name} not found during cleanup") rescue StandardError => e @logger.error("Error deleting secret #{secret_name}: #{e.}") end end |
#cleanup_services(test_group_identifier) ⇒ Object
Cleanup services associated with a test group
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 175 def cleanup_services(test_group_identifier) @logger.info("Cleaning up services for test group: #{test_group_identifier}") label_selector = "beaker/test-group=#{test_group_identifier}" @logger.debug("Using label selector: #{label_selector}") services = @k8s_client.get_services(namespace: @namespace, label_selector: label_selector) @logger.info("Found #{services.length} service(s) with label beaker/test-group=#{test_group_identifier}") services.each do |service| # Extract service name - access hash-style to avoid RSpec metadata collision = service[:metadata] || service['metadata'] service_name = [:name] || ['name'] if unless service_name && !service_name.empty? @logger.error('Cannot delete service with empty or nil name.') @logger.error("Service structure: #{service.inspect[0..1000]}") next end service_labels = [:labels] || ['labels'] if @logger.debug("Deleting service #{service_name} with labels: #{service_labels.inspect}") @k8s_client.delete_service(service_name, @namespace) @logger.info("Deleted service #{service_name}") rescue Kubeclient::ResourceNotFoundError @logger.debug("Service #{service_name} not found during cleanup") rescue StandardError => e @logger.error("Error deleting service #{service_name}: #{e.}") end end |
#cleanup_temp_files ⇒ Object
Unlink the kubeconfig-derived cert/key tempfiles kept alive for the process lifetime. Safe to call after all API operations that need the files have completed.
320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 320 def cleanup_temp_files return unless @temp_files @temp_files.each do |file| path = file.path file.close unless file.closed? file.unlink rescue StandardError => e @logger&.warn("Failed to unlink temp file #{path}: #{e.}") end @temp_files.clear end |
#cleanup_vms(test_group_identifier) ⇒ Object
Cleanup VMs created by this test group
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 106 def cleanup_vms(test_group_identifier) @logger.info("Cleaning up VMs for test group: #{test_group_identifier}") label_selector = "beaker/test-group=#{test_group_identifier}" @logger.debug("Using label selector: #{label_selector}") vms = @kubevirt_client.get_virtual_machines(namespace: @namespace, label_selector: label_selector) @logger.info("Found #{vms.length} VM(s) with label beaker/test-group=#{test_group_identifier}") vms.each do |vm| # Extract VM name - access hash-style to avoid RSpec metadata collision = vm[:metadata] || vm['metadata'] vm_name = [:name] || ['name'] if unless vm_name && !vm_name.empty? @logger.error('Cannot delete VM with empty or nil name.') @logger.error("VM structure: #{vm.inspect[0..1000]}") next end vm_labels = [:labels] || ['labels'] if @logger.debug("Deleting VM #{vm_name} with labels: #{vm_labels.inspect}") @kubevirt_client.delete_virtual_machine(vm_name, @namespace) @logger.info("Deleted VM #{vm_name}") rescue Kubeclient::ResourceNotFoundError @logger.debug("VM #{vm_name} not found during cleanup") rescue StandardError => e @logger.error("Error deleting VM #{vm_name}: #{e.}") @logger.error("Backtrace: #{e.backtrace[0..5].join("\n")}") end end |
#create_nodeport_service(vm_name, service_name, test_group_identifier = nil) ⇒ Hash
Create a NodePort service for SSH access
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 284 def create_nodeport_service(vm_name, service_name, test_group_identifier = nil) labels = { 'beaker/vm' => vm_name } labels['beaker/test-group'] = test_group_identifier if test_group_identifier service_spec = { 'apiVersion' => 'v1', 'kind' => 'Service', 'metadata' => { 'name' => service_name, 'namespace' => @namespace, 'labels' => labels, }, 'spec' => { 'type' => 'NodePort', 'selector' => { 'kubevirt.io/vm' => vm_name, }, 'ports' => [ { 'name' => 'ssh', 'port' => 22, 'targetPort' => 22, 'protocol' => 'TCP', }, ], }, } service_spec_sym = symbolize_keys(service_spec) @k8s_client.create_service(service_spec_sym) end |
#create_secret(secret_spec) ⇒ Object
Create a secret
55 56 57 58 59 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 55 def create_secret(secret_spec) # Convert all keys to symbols recursively secret_spec_sym = symbolize_keys(secret_spec) @k8s_client.create_secret(secret_spec_sym) end |
#create_vm(vm_spec) ⇒ Object
Create a virtual machine
46 47 48 49 50 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 46 def create_vm(vm_spec) # Convert all keys to symbols recursively vm_spec_sym = symbolize_keys(vm_spec) @kubevirt_client.create_virtual_machine(vm_spec_sym) end |
#delete_vm(vm_name) ⇒ Object
Delete a virtual machine
96 97 98 99 100 101 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 96 def delete_vm(vm_name) @kubevirt_client.delete_virtual_machine(vm_name, @namespace) @logger.debug("Deleted VM #{vm_name}") rescue Kubeclient::ResourceNotFoundError @logger.debug("VM #{vm_name} not found during deletion") end |
#forwarder_error_handler(error) ⇒ Object
271 272 273 274 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 271 def forwarder_error_handler(error) @logger.error("Port forwarder error: #{error.}") # Optionally, you can implement retry logic or cleanup here end |
#get_virt_launcher_pod(vmi_name) ⇒ Hash?
Get the virt-launcher pod backing a VMI.
85 86 87 88 89 90 91 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 85 def get_virt_launcher_pod(vmi_name) pods = @k8s_client.get_pods(namespace: @namespace, label_selector: "kubevirt.io/vm=#{vmi_name}") pods.first rescue Kubeclient::ResourceNotFoundError nil end |
#get_vm(vm_name) ⇒ Hash
Get a virtual machine
65 66 67 68 69 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 65 def get_vm(vm_name) @kubevirt_client.get_virtual_machine(vm_name, @namespace) rescue Kubeclient::ResourceNotFoundError nil end |
#get_vmi(vmi_name) ⇒ Hash
Get a virtual machine instance
75 76 77 78 79 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 75 def get_vmi(vmi_name) @kubevirt_client.get_virtual_machine_instance(vmi_name, @namespace) rescue Kubeclient::ResourceNotFoundError nil end |
#node_ip ⇒ String
Get a cluster node IP address
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 336 def node_ip nodes = @k8s_client.get_nodes node = nodes.first # Try to get external IP first, fallback to internal IP addresses = node.dig('status', 'addresses') || [] external_ip = addresses.find { |addr| addr['type'] == 'ExternalIP' } internal_ip = addresses.find { |addr| addr['type'] == 'InternalIP' } if external_ip external_ip['address'] elsif internal_ip internal_ip['address'] else raise 'Could not determine node IP address' end end |
#setup_port_forward(vm_name, vm_port, local_port) ⇒ Process
Setup port forwarding for a VM
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/beaker/hypervisor/kubevirt_helper.rb', line 248 def setup_port_forward(vm_name, vm_port, local_port) require 'beaker/hypervisor/port_forward' forwarder = KubeVirtPortForwarder.new( kube_client: @kubevirt_client, namespace: @namespace, vmi_name: vm_name, target_port: vm_port, local_port: local_port, logger: @logger, on_error: method(:forwarder_error_handler), ssl_options: @original_ssl_options, ) # Start the port forwarder in a background thread forwarder.start # Check if the forwarder started correctly. return forwarder if forwarder.state == :running @logger.error("Port forwarder failed to start for VM #{vm_name} on port #{vm_port}") raise "Port forwarder failed to start for VM #{vm_name} on port #{vm_port}" end |