Class: Fog::Kubevirt::Compute::Real

Inherits:
Object
  • Object
show all
Includes:
Shared
Defined in:
lib/fog/kubevirt/compute/compute.rb,
lib/fog/kubevirt/compute/requests/get_vm.rb,
lib/fog/kubevirt/compute/requests/get_pvc.rb,
lib/fog/kubevirt/compute/requests/get_node.rb,
lib/fog/kubevirt/compute/requests/list_vms.rb,
lib/fog/kubevirt/compute/requests/create_vm.rb,
lib/fog/kubevirt/compute/requests/delete_vm.rb,
lib/fog/kubevirt/compute/requests/list_pvcs.rb,
lib/fog/kubevirt/compute/requests/update_vm.rb,
lib/fog/kubevirt/compute/requests/create_pvc.rb,
lib/fog/kubevirt/compute/requests/delete_pvc.rb,
lib/fog/kubevirt/compute/requests/get_server.rb,
lib/fog/kubevirt/compute/requests/list_nodes.rb,
lib/fog/kubevirt/compute/requests/get_service.rb,
lib/fog/kubevirt/compute/requests/get_template.rb,
lib/fog/kubevirt/compute/requests/list_servers.rb,
lib/fog/kubevirt/compute/requests/list_volumes.rb,
lib/fog/kubevirt/compute/requests/list_services.rb,
lib/fog/kubevirt/compute/requests/create_service.rb,
lib/fog/kubevirt/compute/requests/delete_service.rb,
lib/fog/kubevirt/compute/requests/get_vminstance.rb,
lib/fog/kubevirt/compute/requests/list_templates.rb,
lib/fog/kubevirt/compute/requests/get_storageclass.rb,
lib/fog/kubevirt/compute/requests/list_vminstances.rb,
lib/fog/kubevirt/compute/requests/create_vminstance.rb,
lib/fog/kubevirt/compute/requests/delete_vminstance.rb,
lib/fog/kubevirt/compute/requests/create_storageclass.rb,
lib/fog/kubevirt/compute/requests/delete_storageclass.rb,
lib/fog/kubevirt/compute/requests/list_storageclasses.rb,
lib/fog/kubevirt/compute/requests/get_persistentvolume.rb,
lib/fog/kubevirt/compute/requests/list_persistentvolumes.rb,
lib/fog/kubevirt/compute/requests/create_persistentvolume.rb,
lib/fog/kubevirt/compute/requests/delete_persistentvolume.rb,
lib/fog/kubevirt/compute/requests/get_vnc_console_details.rb,
lib/fog/kubevirt/compute/requests/get_networkattachmentdef.rb,
lib/fog/kubevirt/compute/requests/list_networkattachmentdefs.rb,
lib/fog/kubevirt/compute/requests/create_networkattachmentdef.rb,
lib/fog/kubevirt/compute/requests/delete_networkattachmentdef.rb

Defined Under Namespace

Classes: WatchWrapper

Constant Summary collapse

CORE_GROUP =

The API group of the Kubernetes core:

''.freeze
KUBEVIRT_GROUP =

The API group of KubeVirt:

'kubevirt.io'.freeze
NETWORK_GROUP =

The API group of the Kubernetes network extention:

'k8s.cni.cncf.io'.freeze
STORAGE_GROUP =

The API group of the Kubernetes network extention:

'storage.k8s.io'.freeze
TEMPLATE_GROUP =

The API group of the Openshift Templates extension:

'template.openshift.io'.freeze

Instance Method Summary collapse

Methods included from Shared

#deep_merge!, #object_to_hash

Constructor Details

#initialize(options = {}) ⇒ Real

Returns a new instance of Real.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/fog/kubevirt/compute/compute.rb', line 156

def initialize(options={})
  require 'kubeclient'

  @kubevirt_token = options[:kubevirt_token]
  @host = options[:kubevirt_hostname]
  @port = options[:kubevirt_port]
  @kubevirt_version = options[:kubevirt_version]

  @log = options[:kubevirt_log]
  @log ||= ::Logger.new(STDOUT)

  @namespace = options[:kubevirt_namespace] || 'default'
  @opts = {
    :ssl_options  => obtain_ssl_options(options),
    :auth_options => {
      :bearer_token => @kubevirt_token
    }
  }

  # Kubeclient needs different client objects for different API groups. We will keep in this hash the
  # client objects, indexed by API path/version.
  @clients = {}
end

Instance Method Details

#create_networkattachmentdef(net_att) ⇒ Object

creates netwrork attachment definition object metadata: the net-attachment-def metadata:

name[String]: the netwrork attachment definition definition
spec[Hash]: the specification of the attachment, contains:config
  config[string]: the configuration of the attachment, i.e.
   '{ :cniVersion => "0.3.1", :type => "ovs", :bridge => "red" }'

Example of net_att: metadata:

  name: "ovs-red",
  spec: {
    config: '{ cni_version: "0.3.1", type: "ovs", bridge: "red" }'
}

Parameters:

  • net_att (Hash)

    contains the following elements:



20
21
22
23
24
25
# File 'lib/fog/kubevirt/compute/requests/create_networkattachmentdef.rb', line 20

def create_networkattachmentdef(net_att)
  if net_att.dig(:metadata, :namespace).nil?
    net_att = deep_merge!(net_att, metadata: { namespace: @namespace })
  end
  kube_net_client.create_network_attachment_definition(net_att)
end

#create_persistentvolume(volume) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/create_persistentvolume.rb', line 5

def create_persistentvolume(volume)
  kube_client.create_persistent_volume(volume)
end

#create_pvc(pvc) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/create_pvc.rb', line 5

def create_pvc(pvc)
  kube_client.create_persistent_volume_claim(pvc)
end

#create_service(srv) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/create_service.rb', line 5

def create_service(srv)
  kube_client.create_service(srv)
end

#create_storageclass(storageclass) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/create_storageclass.rb', line 5

def create_storageclass(storageclass)
  kube_storage_client.create_storage_class(storageclass)
end

#create_vm(vm) ⇒ Object



5
6
7
8
9
# File 'lib/fog/kubevirt/compute/requests/create_vm.rb', line 5

def create_vm(vm)
  vm[:apiVersion] = kubevirt_client.version

  kubevirt_client.create_virtual_machine(vm)
end

#create_vminstance(vm) ⇒ Object



6
7
8
# File 'lib/fog/kubevirt/compute/requests/create_vminstance.rb', line 6

def create_vminstance(vm)
  kubevirt_client.create_virtual_machine_instance(vm)
end

#delete_networkattachmentdef(name, namespace) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/delete_networkattachmentdef.rb', line 5

def delete_networkattachmentdef(name, namespace)
  kube_net_client.delete_network_attachment_definition(name, namespace)
end

#delete_persistentvolume(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/delete_persistentvolume.rb', line 5

def delete_persistentvolume(name)
  kube_client.delete_persistent_volume(name)
end

#delete_pvc(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/delete_pvc.rb', line 5

def delete_pvc(name)
  kube_client.delete_persistent_volume_claim(name, namespace)
end

#delete_service(name, namespace) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/delete_service.rb', line 5

def delete_service(name, namespace)
  kube_client.delete_service(name, namespace)
end

#delete_storageclass(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/delete_storageclass.rb', line 5

def delete_storageclass(name)
  kube_storage_client.delete_storage_class(name)
end

#delete_vm(name, namespace) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/delete_vm.rb', line 5

def delete_vm(name, namespace)
  kubevirt_client.delete_virtual_machine(name, namespace)
end

#delete_vminstance(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/delete_vminstance.rb', line 5

def delete_vminstance(name)
  kubevirt_client.delete_virtual_machine_instance(name, @namespace)
end

#get_networkattachmentdef(name) ⇒ Object



7
8
9
10
# File 'lib/fog/kubevirt/compute/requests/get_networkattachmentdef.rb', line 7

def get_networkattachmentdef(name)
  net_attach_def = kube_net_client.get_network_attachment_definition(name, @namespace)
  Networkattachmentdef.parse object_to_hash(net_attach_def)
end

#get_node(name) ⇒ Object



7
8
9
# File 'lib/fog/kubevirt/compute/requests/get_node.rb', line 7

def get_node(name)
  Node.parse object_to_hash( kube_client.get_node(name) )
end

#get_persistentvolume(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/get_persistentvolume.rb', line 5

def get_persistentvolume(name)
  Persistentvolume.parse object_to_hash(kube_client.get_persistent_volume(name))
end

#get_pvc(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/get_pvc.rb', line 5

def get_pvc(name)
  Pvc.parse object_to_hash(kube_client.get_persistent_volume_claim(name, @namespace))
end

#get_raw_vm(name) ⇒ Object



33
34
35
# File 'lib/fog/kubevirt/compute/requests/get_vm.rb', line 33

def get_raw_vm(name)
  object_to_hash(kubevirt_client.get_virtual_machine(name, @namespace))
end

#get_server(name) ⇒ Object



5
6
7
8
9
10
11
12
13
14
# File 'lib/fog/kubevirt/compute/requests/get_server.rb', line 5

def get_server(name)
  vm = get_raw_vm(name)
  vmi = runtime_vm(vm)

  populate_runtime_info(vm, vmi)
  server = Server.parse vm
  populate_pvcs_for_vm(server)
  populate_runtime_nets(server, vmi)
  server
end

#get_service(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/get_service.rb', line 5

def get_service(name)
  Service.parse object_to_hash(kube_client.get_service(name, @namespace))
end

#get_storageclass(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/get_storageclass.rb', line 5

def get_storageclass(name)
  Storageclass.parse object_to_hash(kube_storage_client.get_storage_class(name))
end

#get_template(name) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/get_template.rb', line 5

def get_template(name)
  Template.parse object_to_hash( openshift_template_client.get_template(name, @namespace) )
end

#get_vm(name) ⇒ Object



5
6
7
8
9
# File 'lib/fog/kubevirt/compute/requests/get_vm.rb', line 5

def get_vm(name)
  vm = Vm.parse get_raw_vm(name)
  populate_pvcs_for_vm(vm)
  vm
end

#get_vminstance(name) ⇒ Object



7
8
9
10
# File 'lib/fog/kubevirt/compute/requests/get_vminstance.rb', line 7

def get_vminstance(name)
  # namespace is defined on the Real object
  Vminstance.parse object_to_hash( kubevirt_client.get_virtual_machine_instance(name, @namespace) )
end

#get_vnc_console_details(name, namespace) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/fog/kubevirt/compute/requests/get_vnc_console_details.rb', line 5

def get_vnc_console_details(name, namespace)

  url = URI::Generic.build(
      :scheme => 'https',
      :host   => @host,
      :port   => @port,
      :path   => "/apis/kubevirt.io"
  )
  version = detect_version(url.to_s, @opts[:ssl_options])
    {
     :host => @host,
     :port => @port,
     :path => "/apis/subresources.kubevirt.io/#{version}/namespaces/#{namespace}/virtualmachineinstances/#{name}/vnc",
     :token => @opts[:auth_options][:bearer_token]
    }
end

#list_networkattachmentdefs(_filters = {}) ⇒ Object



5
6
7
8
9
10
11
# File 'lib/fog/kubevirt/compute/requests/list_networkattachmentdefs.rb', line 5

def list_networkattachmentdefs(_filters = {})
  netdefs = kube_net_client.get_network_attachment_definitions
  entities = netdefs.map do |kubevirt_obj|
    Networkattachmentdef.parse object_to_hash(kubevirt_obj)
  end
  EntityCollection.new(netdefs.kind, netdefs.resourceVersion, entities)
end

#list_nodes(_filters = {}) ⇒ Object



5
6
7
8
9
# File 'lib/fog/kubevirt/compute/requests/list_nodes.rb', line 5

def list_nodes(_filters = {})
  nodes = kube_client.get_nodes
  entities = nodes.map { |kubevirt_obj| Node.parse object_to_hash(kubevirt_obj) }
  EntityCollection.new(nodes.kind, nodes.resourceVersion, entities)
end

#list_persistentvolumes(_filters = {}) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/fog/kubevirt/compute/requests/list_persistentvolumes.rb', line 7

def list_persistentvolumes(_filters = {})
  volumes = kube_client.get_persistent_volumes()
  entities = volumes.map do |kubevirt_obj|
    Persistentvolume.parse object_to_hash(kubevirt_obj)
  end
  EntityCollection.new(volumes.kind, volumes.resourceVersion, entities)
end

#list_pvcs(_filters = {}) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/fog/kubevirt/compute/requests/list_pvcs.rb', line 7

def list_pvcs(_filters = {})
  pvcs = kube_client.get_persistent_volume_claims(namespace: @namespace)
  entities = pvcs.map do |kubevirt_obj|
    Pvc.parse object_to_hash(kubevirt_obj)
  end
  EntityCollection.new(pvcs.kind, pvcs.resourceVersion, entities)
end

#list_servers(filters = {}) ⇒ Object

filters - if contains ‘:pvcs’ set to true will popoulate pvcs for vms



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/fog/kubevirt/compute/requests/list_servers.rb', line 9

def list_servers(filters = {})
  vms = kubevirt_client.get_virtual_machines(namespace: @namespace)
  entities = vms.map do |kubevirt_obj|
    vm_obj = object_to_hash(kubevirt_obj)
    vmi = runtime_vm(vm_obj)
    populate_runtime_info(vm_obj, vmi)
    server = Server.parse vm_obj
    populate_runtime_nets(server, vmi)
    if filters[:pvcs]
      populate_pvcs_for_vm(server)
    end
    server
  end
  EntityCollection.new(vms.kind, vms.resourceVersion, entities)
end

#list_services(_filters = {}) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/fog/kubevirt/compute/requests/list_services.rb', line 7

def list_services(_filters = {})
  services = kube_client.get_services(namespace: @namespace)
  entities = services.map do |kubevirt_obj|
    Service.parse object_to_hash(kubevirt_obj)
  end
  EntityCollection.new(services.kind, services.resourceVersion, entities)
end

#list_storageclasses(_filters = {}) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/fog/kubevirt/compute/requests/list_storageclasses.rb', line 7

def list_storageclasses(_filters = {})
  storageclasses = kube_storage_client.get_storage_classes
  entities = storageclasses.map do |kubevirt_obj|
    Storageclass.parse object_to_hash(kubevirt_obj)
  end
  EntityCollection.new(storageclasses.kind, storageclasses.resourceVersion, entities)
end

#list_templates(_filters = {}) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/fog/kubevirt/compute/requests/list_templates.rb', line 7

def list_templates(_filters = {})
  temps = openshift_template_client.get_templates(namespace: @namespace)
  entities = temps.map do |kubevirt_obj|
    Template.parse object_to_hash(kubevirt_obj)
  end
  EntityCollection.new(temps.kind, temps.resourceVersion, entities)
end

#list_vminstances(_filters = {}) ⇒ Object



5
6
7
8
9
10
11
# File 'lib/fog/kubevirt/compute/requests/list_vminstances.rb', line 5

def list_vminstances(_filters = {})
  vminstances = kubevirt_client.get_virtual_machine_instances(namespace: @namespace)
  entities = vminstances.map do |kubevirt_obj|
    Vminstance.parse object_to_hash(kubevirt_obj)
  end
  EntityCollection.new(vminstances.kind, vminstances.resourceVersion, entities)
end

#list_vms(filters = {}) ⇒ Object

filters - if contains ‘:pvcs’ set to true will popoulate pvcs for vms



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/fog/kubevirt/compute/requests/list_vms.rb', line 8

def list_vms(filters = {})
  vms = kubevirt_client.get_virtual_machines(namespace: @namespace)
  entities = vms.map do |kubevirt_obj|
    vm = Vm.parse object_to_hash(kubevirt_obj)
    if filters[:pvcs]
      populate_pvcs_for_vm(vm)
    end
    vm
  end
  EntityCollection.new(vms.kind, vms.resourceVersion, entities)
end

#list_volumes(vm_name = nil) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/fog/kubevirt/compute/requests/list_volumes.rb', line 7

def list_volumes(vm_name = nil)
  if vm_name.nil?
    entities = pvcs.all.map do |pvc|
      volume = Volume.new
      volume.name = pvc.name
      volume.type = 'persistentVolumeClaim'
      set_volume_pvc_attributes(volume, pvc)
      volume
    end
    EntityCollection.new('Volume', pvcs.resource_version, entities)
  else
    vm = vms.get(vm_name)
    EntityCollection.new('Volume', vm.resource_version, vm.volumes)
  end
end

#namespaceObject



315
316
317
# File 'lib/fog/kubevirt/compute/compute.rb', line 315

def namespace
  @namespace
end

#populate_pvcs_for_vm(vm) ⇒ Object



11
12
13
14
15
# File 'lib/fog/kubevirt/compute/requests/get_vm.rb', line 11

def populate_pvcs_for_vm(vm)
  vm[:volumes].each do |vol|
    set_volume_pvc_attributes(vol)
  end
end

#populate_runtime_info(vm, vmi) ⇒ Object

Updates a given VM raw entity with vm instance info if exists

Parameters:

  • vm (Hash)

    A hash with vm raw data.

  • vmi

    VMInstance object



20
21
22
23
24
25
26
27
# File 'lib/fog/kubevirt/compute/requests/get_server.rb', line 20

def populate_runtime_info(vm, vmi)
  return if vmi.nil?

  vm[:ip_address] = vmi[:ip_address]
  vm[:node_name] = vmi[:node_name]
  vm[:phase] = vmi[:status]
  vm
end

#populate_runtime_nets(server, vmi) ⇒ Object

Updates a given Server entity with vm instance networking details if exists

Parameters:

  • server

    Server object

  • vmi

    VMInstance object



33
34
35
36
37
38
# File 'lib/fog/kubevirt/compute/requests/get_server.rb', line 33

def populate_runtime_nets(server, vmi)
  return if vmi.nil?

  server[:networks] = vmi[:networks]
  server[:interfaces] = vmi[:interfaces]
end

#set_volume_pvc_attributes(volume, pvc = nil) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/fog/kubevirt/compute/requests/get_vm.rb', line 17

def set_volume_pvc_attributes(volume, pvc = nil)
  return unless volume.type == 'persistentVolumeClaim'
  if pvc.nil?
    begin
      pvc = pvcs.get(volume.info)
    rescue
      # there is an option that the PVC does not exist
      return
    end
  end

  volume.pvc = pvc
  volume.capacity = pvc&.requests[:storage]
  volume.storage_class = pvc.storage_class
end

#spice_proxy_urlString

Calculates the URL of the SPICE proxy server.

Returns:

  • (String)

    The URL of the spice proxy server.



303
304
305
306
307
308
309
310
311
312
313
# File 'lib/fog/kubevirt/compute/compute.rb', line 303

def spice_proxy_url
  service = kube_client.get_service('spice-proxy', @namespace)
  host = service.spec.externalIPs.first
  port = service.spec.ports.first.port
  url = URI::Generic.build(
    :scheme => 'http',
    :host   => host,
    :port   => port,
  )
  url.to_s
end

#update_vm(update) ⇒ Object



5
6
7
# File 'lib/fog/kubevirt/compute/requests/update_vm.rb', line 5

def update_vm(update)
  kubevirt_client.update_virtual_machine(update)
end

#valid?Boolean

Returns:

  • (Boolean)


193
194
195
196
197
198
199
200
201
202
# File 'lib/fog/kubevirt/compute/compute.rb', line 193

def valid?
  kube_client.api_valid?

  begin
    kube_client.get_namespace(namespace)
  rescue => err
    @log.warn("The namespace [#{namespace}] does not exist on the kubernetes cluster: #{err.message}")
    raise "The namespace '#{namespace}' does not exist on the kubernetes cluster"
  end
end

#virt_supported?Boolean

Returns:

  • (Boolean)


180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/fog/kubevirt/compute/compute.rb', line 180

def virt_supported?
  virt_enabled = false

  begin
    virt_enabled = kubevirt_client.api["versions"].any? { |ver| ver["groupVersion"]&.start_with?(KUBEVIRT_GROUP) }
  rescue => err
    # we failed to communicate or to evaluate the version format
    @log.warn("Failed to detect kubevirt on provider with error: #{err.message}")
  end

  virt_enabled
end

#watch_nodes(opts = {}) ⇒ WatchWrapper

Returns a watcher for nodes.

Parameters:

  • opts (Hash) (defaults to: {})

    A hash with options for the watcher.

Returns:



228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/fog/kubevirt/compute/compute.rb', line 228

def watch_nodes(opts = {})
  mapper = Proc.new do |notice|
    node = OpenStruct.new(Node.parse(notice.object)) if notice.object.kind == 'Node'
    node ||= OpenStruct.new

    populate_notice_attributes(node, notice)
    node
  end
  watch = kube_client.watch_nodes(opts)

  WatchWrapper.new(watch, mapper)
end

#watch_templates(opts = {}) ⇒ WatchWrapper

Returns a watcher for templates.

Parameters:

  • opts (Hash) (defaults to: {})

    A hash with options for the watcher.

Returns:



285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/fog/kubevirt/compute/compute.rb', line 285

def watch_templates(opts = {})
  mapper = Proc.new do |notice|
    template = OpenStruct.new(Template.parse(notice.object)) if notice.object.kind == 'Template'
    template ||= OpenStruct.new

    populate_notice_attributes(template, notice)
    template
  end
  watch = openshift_template_client.watch_templates(opts)

  WatchWrapper.new(watch, mapper)
end

#watch_vminstances(opts = {}) ⇒ WatchWrapper

Returns a watcher for virtual machine instances.

Parameters:

  • opts (Hash) (defaults to: {})

    A hash with options for the watcher.

Returns:



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/fog/kubevirt/compute/compute.rb', line 266

def watch_vminstances(opts = {})
  mapper = Proc.new do |notice|
    vminstance = OpenStruct.new(Vminstance.parse(object_to_hash(notice.object))) if notice.object.kind == 'VirtualMachineInstance'
    vminstance ||= OpenStruct.new

    populate_notice_attributes(vminstance, notice)
    vminstance
  end
  watch = kubevirt_client.watch_virtual_machine_instances(opts)

  WatchWrapper.new(watch, mapper)
end

#watch_vms(opts = {}) ⇒ WatchWrapper

Returns a watcher for virtual machines.

Parameters:

  • opts (Hash) (defaults to: {})

    A hash with options for the watcher.

Returns:



247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/fog/kubevirt/compute/compute.rb', line 247

def watch_vms(opts = {})
  mapper = Proc.new do |notice|
    vm = OpenStruct.new(Vm.parse(object_to_hash(notice.object))) if notice.object.kind == 'VirtualMachine'
    vm ||= OpenStruct.new

    populate_notice_attributes(vm, notice)
    vm
  end
  watch = kubevirt_client.watch_virtual_machines(opts)

  WatchWrapper.new(watch, mapper)
end