Class: OpenNebula::VirtualMachine

Inherits:
Object
  • Object
show all
Defined in:
service/objects/vm.rb

Overview

Extensions for OpenNebula::VirtualMachine class

Defined Under Namespace

Classes: ShowbackError

Constant Summary collapse

SCHEDULABLE_ACTIONS =

Actions supported by OpenNebula scheduler

%w(
  terminate
  terminate-hard
  hold
  release
  stop
  suspend
  resume
  reboot
  reboot-hard
  poweroff
  poweroff-hard
  undeploy
  undeploy-hard
  snapshot-create
)
CONF_KEYS =

VM Template keys updatable(and rewritable) by #updateconf

Set[
  "INPUT",
  "RAW",
  "OS",
  "FEATURES",
  "GRAPHICS",
  "CONTEXT"
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(xml, client) ⇒ VirtualMachine

initialize method, watchout updates in main code branch



38
39
40
41
42
43
# File 'service/objects/vm.rb', line 38

def initialize(xml, client)
  @vim_vm = nil
  LockableExt.make_lockable(self, VM_METHODS)

  super(xml, client)
end

Instance Attribute Details

#vim_vmObject (readonly)

Returns the value of attribute vim_vm.



7
8
9
# File 'service/objects/vm.rb', line 7

def vim_vm
  @vim_vm
end

Instance Method Details

#at_vcenter_ds?(ds_name) ⇒ Boolean

Checks if vm is on given vCenter Datastore

Returns:

  • (Boolean)


220
221
222
# File 'service/objects/vm.rb', line 220

def at_vcenter_ds? ds_name
  vcenter_datastore_name == ds_name
end

#calculate_showback(stime_req, etime_req, _group_by_day = false) ⇒ Hash

Calculates VMs Showback

Parameters:

  • stime_req (Integer)
    • Point from which calculation starts(timestamp)

  • etime_req (Integer)
    • Point at which calculation stops(timestamp)

  • group_by_day (Boolean)
    • Groups showbacks by days

Returns:

Raises:



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'service/objects/vm.rb', line 391

def calculate_showback stime_req, etime_req, _group_by_day = false
  raise ShowbackError, ["Wrong Time-period given", stime_req, etime_req] if stime_req >= etime_req

  info!

  stime, etime = stime_req, etime_req

  raise ShowbackError, ["VM didn't exist in given time-period", etime, self['/VM/STIME'].to_i] if self['/VM/STIME'].to_i > etime

  stime = self['/VM/STIME'].to_i if self['/VM/STIME'].to_i > stime
  etime = self['/VM/ETIME'].to_i if self['/VM/ETIME'].to_i < etime && self['/VM/ETIME'].to_i != 0

  bp = self['//BILLING_PERIOD']

  if bp.nil? || bp == 'PAYG' then
    billing = Billing.new self, stime, etime
    billing.make_bill
    billing.receipt

    return {
      id: id, name: name,
      showback: billing.bill,
      TOTAL: billing.total
    }
  elsif bp.include? 'PRE' then
    curr = self['/VM/STIME'].to_i
    period = bp.split('_')[1].to_i
    delta = period * 86400

    total = 0

    while curr < etime do
      if (stime..etime).include? curr then
        b = Billing.new self, curr, curr + delta
        b.make_bill
        b.receipt

        total += b.total
      end
      curr += delta
    end

    reduce_factor = 1
    reduce_factors = IONe::Settings['PRE_PAID_REDUCE_FACTOR'].keys_to_i!.sort.to_h

    reduce_factors.each do | period_key, factor |
      if period >= period_key then
        reduce_factor = factor
      else
        break
      end
    end
    reduce_factor = reduce_factor.to_f

    return {
      id: id, name: name,
      total_billed: total, reduce_factor: reduce_factor,
      TOTAL: total * reduce_factor
    }
  else
    raise ShowbackError, ["Unknown BILLING_PERIOD!", bp]
  end
end

#confObject

Returns VM conf(template parts rewrittable by #updateconf)



589
590
591
592
593
594
# File 'service/objects/vm.rb', line 589

def conf
  info!
  to_hash['VM']['TEMPLATE'].select do | key |
    CONF_KEYS === key
  end
end

#DELETE File.join('/var/lib/one/sunstone_vnc_tokens/', "one-#{id}")Object



579
# File 'service/objects/vm.rb', line 579

File.delete(File.join('/var/lib/one/sunstone_vnc_tokens/', "one-#{id}"))

#drivesArray<Hash>

List VM Drives

Returns:



485
486
487
488
# File 'service/objects/vm.rb', line 485

def drives
  r = to_hash!['VM']['TEMPLATE']['DISK']
  r.class == Array ? r : [r]
end

#generate_schedule_str(id, action, time) ⇒ Object

Generates template for OpenNebula scheduler record



46
47
48
49
50
51
# File 'service/objects/vm.rb', line 46

def generate_schedule_str id, action, time
  "\nSCHED_ACTION=[\n" +
    "  ACTION=\"#{action}\",\n" +
    "  ID=\"#{id}\",\n" +
    "  TIME=\"#{time}\" ]"
end

#getResourcesAllocationLimitsHash | String

Note:

Method searches VM by it's default name: one-(id)-(name), if target vm got another name, you should provide it

Gets resources allocation limits from vCenter node

Returns:

  • (Hash | String)

    Returns limits Hash if success or exception message if fails



303
304
305
306
307
308
309
310
311
# File 'service/objects/vm.rb', line 303

def getResourcesAllocationLimits
  begin
    vm = vcenter_get_vm true
    vm_disk = vm.disks.first
    { cpu: vm.config.cpuAllocation.limit, ram: vm.config.memoryAllocation.limit, iops: vm_disk.storageIOAllocation.limit }
  rescue => e
    "Unexpected error, cannot handle it: #{e.message}"
  end
end

#got_disk_snapshots?Boolean

Returns:

  • (Boolean)


342
343
344
345
# File 'service/objects/vm.rb', line 342

def got_disk_snapshots?
  self.info!
  !self.to_hash['VM']['SNAPSHOTS'].nil?
end

#got_snapshots?Boolean Also known as: got_snapshot?

Gives info about snapshots availability

Returns:

  • (Boolean)


336
337
338
339
# File 'service/objects/vm.rb', line 336

def got_snapshots?
  self.info!
  !self.to_hash['VM']['TEMPLATE']['SNAPSHOT'].nil?
end

#hostArray<String> | nil

Returns host id and name, where VM has been deployed

Examples:

=> ['0', 'example-node-vcenter'] => Host was found
=> nil => Host wasn't found

Returns:



205
206
207
208
209
210
211
# File 'service/objects/vm.rb', line 205

def host
  history = to_hash!['VM']["HISTORY_RECORDS"]['HISTORY'] # Searching hostname at VM allocation history
  history = history.last if history.class == Array # If history consists of 2 or more lines - returns last
  return history['HID'], history['HOSTNAME']
rescue
  return nil
end

#hot_resize(spec = {}) ⇒ Boolean | String

Note:

Method returns true if resize action ended correct, false if VM not support hot reconfiguring

Resizes VM without powering off the VM

Parameters:

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

Options Hash (spec):

  • :cpu (Integer)

    CPU amount to set

  • :ram (Integer)

    RAM amount in MB to set

Returns:



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'service/objects/vm.rb', line 236

def hot_resize spec = {}
  return false if !self.hotAddEnabled?

  begin
    vm = vcenter_get_vm
    query = {
      :numCPUs => spec[:cpu],
      :memoryMB => spec[:ram]
    }
    vm.ReconfigVM_Task(:spec => query).wait_for_completion
    return true
  rescue => e
    return "Reconfigure Error:#{e.message}"
  end
end

#hotAddEnabled?Hash | String

Note:

Method searches VM by it's default name: one-(id)-(name), if target vm got another name, you should provide it

Checks if resources hot add enabled

Returns:

  • (Hash | String)

    Returns limits Hash if success or exception message if fails



255
256
257
258
259
260
261
262
263
264
# File 'service/objects/vm.rb', line 255

def hotAddEnabled?
  begin
    vm = vcenter_get_vm
    return {
      :cpu => vm.config.cpuHotAddEnabled, :ram => vm.config.memoryHotAddEnabled
    }
  rescue => e
    return "Unexpected error, cannot handle it: #{e.message}"
  end
end

#hotResourcesControlConf(spec = { :cpu => true, :ram => true }) ⇒ true | String

Sets resources hot add settings

Parameters:

  • spec (Hash) (defaults to: { :cpu => true, :ram => true })

Options Hash (spec):

  • :cpu (Boolean)
  • :ram (Boolean)

Returns:



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'service/objects/vm.rb', line 271

def hotResourcesControlConf spec = { :cpu => true, :ram => true }
  begin
    vm = vcenter_get_vm
    query = {
      :cpuHotAddEnabled => spec[:cpu],
      :memoryHotAddEnabled => spec[:ram]
    }
    state = true
    begin
      LOG_DEBUG 'Powering VM Off'
      LOG_DEBUG vm.PowerOffVM_Task.wait_for_completion
    rescue
      state = false
    end

    LOG_DEBUG 'Reconfiguring VM'
    LOG_DEBUG vm.ReconfigVM_Task(:spec => query).wait_for_completion

    begin
      LOG_DEBUG 'Powering VM On'
      LOG_DEBUG vm.PowerOnVM_Task.wait_for_completion
    rescue
      nil
    end if state
  rescue => e
    "Unexpected error, cannot handle it: #{e.message}"
  end
end

#lcm_state!Object

Returns actual lcm state without calling info! method



372
373
374
# File 'service/objects/vm.rb', line 372

def lcm_state!
  self.info! || self.lcm_state
end

#lcm_state_str!Object

Returns actual lcm state as string without calling info! method



382
383
384
# File 'service/objects/vm.rb', line 382

def lcm_state_str!
  self.info! || self.lcm_state_str
end

#list_disk_snapshotsHash

Returns all available snapshots in Hash form(DISK_ID => Array<Hash<Snapshot>>)

Returns:



356
357
358
359
360
361
362
363
364
# File 'service/objects/vm.rb', line 356

def list_disk_snapshots
  snaps = to_hash!['VM']['SNAPSHOTS']
  return {} if snaps.nil?

  (snaps.class == Array ? snaps : [snaps]).inject({}) do | r, snap |
    r[snap['DISK_ID']] = snap['SNAPSHOT'].class == Array ? snap['SNAPSHOT'] : [snap['SNAPSHOT']]
    r
  end
end

#list_snapshotsArray<Hash>, ...

Returns all available snapshots

Returns:



349
350
351
352
# File 'service/objects/vm.rb', line 349

def list_snapshots
  out = self.to_hash!['VM']['TEMPLATE']['SNAPSHOT']
  out.class == Array ? out : [out]
end

#passwd(password) ⇒ Object

Changes VM password in Context(must be changing on VM immediately)

Parameters:

  • password (String)
    • new VM password



584
585
586
# File 'service/objects/vm.rb', line 584

def passwd password
  updateconf_safe({ CONTEXT: { PASSWORD: password } })
end

#schedule(action, time, _periodic = nil) ⇒ Object

Adds actions to OpenNebula internal scheduler, like –schedule in 'onevm' cli utility

Parameters:

  • action (String)
    • Action which should be scheduled

  • time (Integer)
    • Time when action schould be perfomed in secs

  • periodic (String)
    • Not working now

Returns:

  • true



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'service/objects/vm.rb', line 64

def schedule action, time, _periodic = nil
  return 'Unsupported action' if !SCHEDULABLE_ACTIONS.include? action

  self.info!
  id =
    begin
      ids = self.to_hash['VM']['USER_TEMPLATE']['SCHED_ACTION']
      if ids.class == Array then
        ids.last['ID'].to_i + 1
      elsif ids.class == Hash then
        ids['ID'].to_i + 1
      elsif ids.class == NilClass then
        ids.to_i
      else
        raise
      end
    rescue
      0
    end

  # str_periodic = ''

  self.update(self.user_template_str << generate_schedule_str(id, action, time))
end

#schedule_actionsArray

Returns allowed actions to schedule

Returns:



55
56
57
# File 'service/objects/vm.rb', line 55

def schedule_actions
  SCHEDULABLE_ACTIONS
end

#schedulerNilClass | Hash | Array

Lists actions scheduled in OpenNebula

Returns:



114
115
116
117
# File 'service/objects/vm.rb', line 114

def scheduler
  self.info!
  self.to_hash['VM']['USER_TEMPLATE']['SCHED_ACTION']
end

#setResourcesAllocationLimits(spec) ⇒ NilClass | String, String | Array(backtrace)

Note:

Attention!!! VM will be rebooted at the process

Note:

Valid units are: CPU - MHz, RAM - MB

Note:

Method searches VM by it's default name: one-(id)-(name), if target vm got another name, you should provide it

Sets resources allocation limits at vCenter node

Examples:

Return messages decode

vm.setResourcesAllocationLimits(spec)
  => nil, 'Success' -- Task finished with success code, all specs are equal to given
  => 'Reconfigure Unsuccessed' -- Some of specs didn't changed
  => 'Reconfigure Error:{error message}', [...] -- Exception has been generated while proceed, check your configuration

Parameters:

  • spec (Hash)
    • List of limits should be applied to target VM

Options Hash (spec):

  • :cpu (Integer)

    MHz limit for VMs CPU usage

  • :ram (Integer)

    MBytes limit for VMs RAM space usage

  • :iops (Integer)

    IOPS limit for VMs disk

Returns:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'service/objects/vm.rb', line 150

def setResourcesAllocationLimits spec
  return nil, 'Unsupported query' if self['//IMPORTED'] == 'YES'

  return nil, 'Nothing to do' if spec.empty?

  query, vm = {}, vcenter_get_vm
  disk = vm.disks.first

  query[:cpuAllocation] = { :limit => spec[:cpu].to_i, :reservation => 0 } if !spec[:cpu].nil?
  query[:memoryAllocation] = { :limit => spec[:ram].to_i } if !spec[:ram].nil?
  if !spec[:iops].nil? then
    disk.storageIOAllocation.limit = spec[:iops].to_i
    disk.backing.sharing = nil
    query[:deviceChange] = [{
      :device => disk,
      :operation => :edit
    }]
  end

  return nil, 'Nothing to do' if query.empty?

  vm.ReconfigVM_Task(:spec => query).wait_for_completion

  return nil, 'Success'
rescue => e
  return "Reconfigure Error:#{e.message}", e.backtrace
end

#snapshot_create(name = "") ⇒ Object

Create snapshot overload, brings restriction and quota check



458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'service/objects/vm.rb', line 458

def snapshot_create name = ""
  info!

  if self['/VM/USER_TEMPLATE/SNAPSHOTS_ALLOWED'] != 'TRUE' && !IONe::Settings['SNAPSHOTS_ALLOWED_DEFAULT'] then
    return OpenNebula::Error.new("Snapshots aren't allowed for this VM. Set SNAPSHOTS_ALLOWED attribute to TRUE")
  end

  snapshots_quota = self['/VM/USER_TEMPLATE/SNAPSHOTS_QUOTA']
  if !snapshots_quota.nil? and list_snapshots.length >= snapshots_quota.to_i then
    return OpenNebula::Error.new("Unable to create a snapshot, snapshots quota exceed")
  end

  snapshot_create_original name
end

#snapshot_create_originalObject

Original OpenNebula#VirtualMachine.snapshot_create method



456
# File 'service/objects/vm.rb', line 456

alias :snapshot_create_original :snapshot_create

#start_vmrcObject



500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'service/objects/vm.rb', line 500

def start_vmrc
  r = info!
  return { error: "No access to VM" } if OpenNebula.is_error? r

  unless [state, lcm_state] == [3, 3] then
    return { error: "VM isn't running" }
  end

  unless self['USER_TEMPLATE/HYPERVISOR'] == 'vcenter' then
    return { error: "VM isn't vCenter VM" }
  end

  unless self['MONITORING/VCENTER_ESX_HOST'] then
    return { error: "Can't determine ESX host from monitoring, try again later"}
  end

  vcenter_get_vm

  ticket = @vim_vm.AcquireTicket(:ticketType => 'webmks')

  begin
    f = File.open(File.join('/var/lib/one/sunstone_vmrc_tokens/', ticket.ticket.sanitize), 'w')
    f.write("https://#{ticket.host}:#{ticket.port}")
    f.close
  rescue
    return { error: "Cannot create VNC proxy token" }
  end

  return { ticket: ticket.ticket }
end

#start_vncObject

Generates VNC proxy token file



532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'service/objects/vm.rb', line 532

def start_vnc
  r = info!
  return { error: "No access to VM" } if OpenNebula.is_error? r

  if self['TEMPLATE/GRAPHICS/TYPE'].nil? ||
     !(["vnc", "spice"].include?(self['TEMPLATE/GRAPHICS/TYPE'].downcase))
    return { error: "VM has no VNC configured" }
  end

  # Proxy data
  host     = self['/VM/HISTORY_RECORDS/HISTORY[last()]/HOSTNAME']
  vnc_port = self['TEMPLATE/GRAPHICS/PORT']
  # vnc_pw   = self['TEMPLATE/GRAPHICS/PASSWD']

  # If it is a vCenter VM
  if self['USER_TEMPLATE/HYPERVISOR'] == "vcenter"
    if self['MONITORING/VCENTER_ESX_HOST']
      host = self['MONITORING/VCENTER_ESX_HOST']
    else
      return {
        error: "Could not determine the vCenter ESX host where the VM is running. Wait till the VCENTER_ESX_HOST attribute is retrieved once the host has been monitored"
      }
    end
  end

  # Generate token random_str: host:port
  random_str = rand(36**20).to_s(36) # random string a-z0-9 length 20
  token = "#{random_str}: #{host}:#{vnc_port}"
  token_file = "one-#{self['ID']}"

  # Create token file
  begin
    f = File.open(File.join('/var/lib/one/sunstone_vnc_tokens/', token_file), 'w')
    f.write(token)
    f.close
  rescue
    return { error: "Cannot create VNC proxy token" }
  end

  return {
    :token => random_str,
    :vm_name => self['NAME']
  }
end

#state!Object

Returns actual state without calling info! method



367
368
369
# File 'service/objects/vm.rb', line 367

def state!
  self.info! || self.state
end

#state_str!Object

Returns actual state as string without calling info! method



377
378
379
# File 'service/objects/vm.rb', line 377

def state_str!
  self.info! || self.state_str
end

#stop_vncObject

Deletes VNC proxy token file



578
579
580
# File 'service/objects/vm.rb', line 578

def stop_vnc
  File.delete(File.join('/var/lib/one/sunstone_vnc_tokens/', "one-#{id}"))
end

#traffic_recordsHash

List TrafficRecords

Returns:



492
493
494
495
496
497
498
# File 'service/objects/vm.rb', line 492

def traffic_records
  info!
  {
    records: TrafficRecords.new(id).records,
    monitoring: monitoring(['NETTX', 'NETRX'])
  }
end

#uid(info = true) ⇒ Integer

Returns owner user ID

Parameters:

  • info (Boolean) (defaults to: true)
    • method doesn't get object full info one more time – usefull if collecting data from pool

Returns:

  • (Integer)


318
319
320
321
# File 'service/objects/vm.rb', line 318

def uid info = true
  self.info! if info
  self['UID'].to_i
end

#uname(info = true, from_pool = false) ⇒ String

Returns owner user name

Parameters:

  • info (Boolean) (defaults to: true)
    • method doesn't get object full info one more time – usefull if collecting data from pool

  • from_pool (Boolean) (defaults to: false)
    • levels differenct between object and object received from pool.each | object |

Returns:



327
328
329
330
331
332
# File 'service/objects/vm.rb', line 327

def uname info = true, from_pool = false
  self.info! if info
  return @xml[0].children[3].text.to_i unless from_pool

  @xml.children[3].text
end

#unschedule(id) ⇒ Object

Note:

Not working, if action is already initialized

Unschedules given action by ID



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'service/objects/vm.rb', line 91

def unschedule id
  self.info!
  schedule_data, object = self.to_hash['VM']['USER_TEMPLATE']['SCHED_ACTION'], nil

  if schedule_data.class == Array then
    schedule_data.map do | el |
      object = el if el['ID'] == id.to_s
    end
  elsif schedule_data.class == Hash then
    return 'none' if schedule_data['ID'] != id.to_s

    object = schedule_data
  else
    return 'none'
  end
  action, time = object['ACTION'], object['TIME']
  template = self.user_template_str
  template.slice!(generate_schedule_str(id, action, time))
  self.update(template)
end

#updateconf_safe(new_conf) ⇒ Object

Safe updateconf method - doesn't delete ANY keys. Merges new conf with actual conf

Examples:

Updating password only

```ruby
vm.updateconf_safe CONTEXT: PASSWORD: "new_password"
```

Parameters:

  • new_conf (Hash)

    Config keys to change(must be nested)



605
606
607
608
609
610
# File 'service/objects/vm.rb', line 605

def updateconf_safe new_conf 
  curr_conf = conf
  updateconf(
    curr_conf.deep_merge(new_conf.to_s!).to_one_template
  )
end

#vcenter_datastore_nameString

Gets the datastore, where VM allocated is

Returns:



226
227
228
# File 'service/objects/vm.rb', line 226

def vcenter_datastore_name
  vcenter_get_vm.datastore.first.name
end

#vcenter_get_vm(force = false) ⇒ RbVmomi::VIM::VirtualMachine

Generates RbVmomi::VIM::VirtualMachine object with inited connection and ref

Returns:

  • (RbVmomi::VIM::VirtualMachine)


191
192
193
194
195
196
197
198
# File 'service/objects/vm.rb', line 191

def vcenter_get_vm force = false
  return @vim_vm if @vim_vm && !force

  info!
  h = Host.new_with_id(host.first, @client)

  @vim_vm = RbVmomi::VIM::VirtualMachine.new(h.vim, deploy_id.split('_').first)
end

#vcenter_get_vm_dsString

Gets the datastore, where VM allocated is

Returns:



215
216
217
# File 'service/objects/vm.rb', line 215

def vcenter_get_vm_ds
  return vcenter_get_vm.datastore.first
end

#vcenter_powerStateString

Returns VM power state on vCenter

Examples:

=> "poweredOn"

Returns:



182
183
184
185
186
187
# File 'service/objects/vm.rb', line 182

def vcenter_powerState
  vm = vcenter_get_vm
  vm.summary.runtime.powerState
rescue => e
  "Unexpected error, cannot handle it: #{e.message}"
end

#wait_for_state(st = 3, lcm_s = 3) ⇒ Boolean

Waits until VM will have the given state

Parameters:

  • s (Integer)
    • VM state to wait for

  • lcm_s (Integer) (defaults to: 3)
    • VM LCM state to wait for

Returns:

  • (Boolean)


123
124
125
126
127
128
129
130
131
132
# File 'service/objects/vm.rb', line 123

def wait_for_state st = 3, lcm_s = 3
  i = 0
  until state!() == st && lcm_state!() == lcm_s do
    return false if i >= 3600

    sleep(1)
    i += 1
  end
  true
end