Class: Beaker::Kubevirt

Inherits:
Hypervisor
  • Object
show all
Defined in:
lib/beaker/hypervisor/kubevirt.rb

Overview

Beaker support for KubeVirt virtualization platform

Constant Summary collapse

SLEEPWAIT =
5
SSH_TIMEOUT =
300
DEFAULT_BEAKER_DESTROY =

Default value for BEAKER_destroy environment variable

'yes'
BEAKER_DESTROY_PRESERVE_VALUES =

Values of BEAKER_destroy that indicate resources should be preserved

%w[no never onpass].freeze
K8S_NAME_MAX =

Shared Kubernetes 63-character upper bound used by names/labels in this class.

63
LABEL_VALUE_MAX =

Kubernetes label-value maximum length under Kubernetes label value constraints (63 chars).

K8S_NAME_MAX
NAMESPACE_RE =

RFC 1123 DNS label — same constraint kube-apiserver applies to namespaces.

/\A[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?\z/
VMI_TERMINAL_PHASES =

VMI phases that indicate the VM will never reach Running.

%w[Failed Succeeded].freeze
FATAL_CONTAINER_WAITING_REASONS =

virt-launcher container waiting reasons we treat as fatal.

%w[CrashLoopBackOff ImagePullBackOff ErrImagePull].freeze
FATAL_CONTAINER_TERMINATED_REASONS =

virt-launcher container termination reasons we treat as fatal.

%w[OOMKilled Error ContainerCannotRun].freeze

Instance Method Summary collapse

Constructor Details

#initialize(kubevirt_hosts, options) ⇒ Kubevirt

Create a new instance of the KubeVirt hypervisor object

Parameters:

  • kubevirt_hosts (Array<Host>)

    The Array of KubeVirt hosts to provision

  • options (Hash{Symbol=>String})

    The options hash containing configuration values

Options Hash (options):

  • :kubeconfig (String)

    Path to kubeconfig file

  • :kubecontext (String)

    Kubernetes context to use (optional)

  • :namespace (String)

    Kubernetes namespace for VMs (required)

  • :kubevirt_service_account (String)

    Kubernetes service account to use for PVC access (optional, required for cross-namespace PVC cloning)

  • :kubevirt_vm_image (String)

    Base VM image (PVC, container image, etc.)

  • :kubevirt_network_mode (String)

    Network mode (port-forward, nodeport, multus)

  • :kubevirt_ssh_key (String)

    SSH public key to inject

  • :kubevirt_cpus (String)

    CPU resources for VM

  • :kubevirt_memory (String)

    Memory resources for VM

  • :kubevirt_memory_overhead (String)

    Extra memory added to guest for the container memory limit (default: '512Mi'). Needed for Windows guests where KubeVirt's auto-computed virt-launcher overhead is too small and the compute container gets OOMKilled.

  • :kubevirt_memory_request (String)

    Memory request for the VM pod (default: same as :kubevirt_memory).

  • :kubevirt_vm_ssh_port (Integer)

    Port that SSH runs on inside the VM (default: 22)

  • :kubevirt_readiness_probe_disabled (Boolean)

    Skip the VMI SSH readinessProbe. Defaults to false for pod-network modes (port-forward, nodeport) and true for multus (the tcpSocket probe runs from the virt-launcher pod's network namespace and typically can't reach a bridge-only guest on a secondary NIC).

  • :kubevirt_readiness_probe (Hash)

    Probe tuning (all optional, snake_case): :initial_delay_seconds (30), :period_seconds (10), :timeout_seconds (3), :failure_threshold (60), :success_threshold (1). The default failure budget (period_seconds * failure_threshold = 600s) accommodates slow Windows first-boots.

  • :timeout (Integer)

    Timeout for operations. When the readiness probe is enabled, the effective wait is max(:timeout, probe budget + 60s).

  • :kubevirt_disable_virtio (Boolean)

    Disable virtio devices (for compatibility with Windows)

Raises:

  • (ArgumentError)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/beaker/hypervisor/kubevirt.rb', line 93

def initialize(kubevirt_hosts, options)
  require 'beaker/hypervisor/kubevirt_helper'

  super
  @options = options
  @namespace = @options[:namespace]
  raise 'Namespace must be specified in options' unless @namespace
  raise ArgumentError, "Invalid namespace #{@namespace.inspect}: must match RFC 1123 DNS label" unless @namespace.is_a?(String) && NAMESPACE_RE.match?(@namespace)

  @service_account = @options[:kubevirt_service_account]

  @logger = options[:logger]
  @hosts = kubevirt_hosts
  # Ensure the helper gets the validated namespace
  @kubevirt_helper = KubevirtHelper.new(@options)
  @test_group_identifier = "beaker-#{SecureRandom.hex(4)}"
  @cleanup_called = false
  @cleanup_mutex = Mutex.new

  # Register at_exit handler to ensure cleanup happens even on non-success exits
  # This handles cases like Ctrl+C, errors, or test failures that occur after
  # provisioning but before normal cleanup
  # Note: Each instance registers its own at_exit handler, but cleanup is idempotent
  # and scoped to the specific test_group_identifier for this instance
  # Skip registration during this gem's own rspec run (set by spec_helper).
  # Gating on `defined?(RSpec)` misfired in downstream projects that run
  # Beaker from inside their own rspec suite, suppressing the safety net
  # for every consumer.
  return if ENV['BEAKER_KUBEVIRT_DISABLE_AT_EXIT_CLEANUP'] == '1'

  at_exit do
    cleanup_on_exit
  end
end

Instance Method Details

#cleanup(timeout: 10, delay: 1) ⇒ Object

Shutdown and destroy virtual machines in KubeVirt



156
157
158
159
160
161
162
163
164
# File 'lib/beaker/hypervisor/kubevirt.rb', line 156

def cleanup(timeout: 10, delay: 1)
  @cleanup_mutex.synchronize do
    return if @cleanup_called

    @cleanup_called = true
  end

  cleanup_impl(timeout: timeout, delay: delay)
end

#provisionObject

Create and configure virtual machines in KubeVirt



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/beaker/hypervisor/kubevirt.rb', line 130

def provision
  # rubocop:disable Style/CombinableLoops
  @logger.info("Starting KubeVirt provisioning with identifier: #{@test_group_identifier}")

  @hosts.each do |host|
    create_vm(host)
  rescue StandardError, Interrupt => e
    @logger.error("Error creating VM for host #{host.name}: #{e.message}")
    cleanup
    raise e
  end

  @hosts.each do |host|
    wait_for_vm_ready(host)
    setup_networking(host)
  rescue StandardError, Interrupt => e
    @logger.error("Error provisioning host #{host.name}: #{e.message}")
    @logger.error("Cleaning up host #{host.name} due to provisioning failure")
    cleanup
    raise e
  end
  # rubocop:enable Style/CombinableLoops
end