Top Level Namespace

Defined Under Namespace

Modules: RVC

Constant Summary collapse

URI_REGEX =
%r{
  ^
  (?:
    ([^@:]+)
    (?::
     ([^@]*)
    )?
    @
  )?
  ([^@:]+)
  (?::(.*))?
  $
}x
VNC =

Copyright © 2011 VMware, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

ENV['VNC'] || search_path('vinagre') || search_path('tightvnc')
VMRC_NAME =
"vmware-vmrc-linux-x86-3.0.0"
VMRC_PKGVER =
1
VMRC_BASENAME =
"#{VMRC_NAME}.#{VMRC_PKGVER}.tar.bz2"
VMRC_URL =
"http://cloud.github.com/downloads/vmware/rvc/#{VMRC_BASENAME}"
VMRC_SHA256 =
"cda9ba0b0078aee9a7b9704d720ef4c7d74ae2028efb71815d0eb91a5de75921"
CURL =
ENV['CURL'] || 'curl'
HELP_ORDER =
%w(basic vm)

Instance Method Summary collapse

Instance Method Details

#_add_device(vm, dev) ⇒ Object



473
474
475
476
477
478
479
480
# File 'lib/rvc/modules/vm.rb', line 473

def _add_device vm, dev
  spec = {
    :deviceChange => [
      { :operation => :add, :device => dev },
    ]
  }
  vm.ReconfigVM_Task(:spec => spec).wait_for_completion
end

#_add_net_device(vm, klass, network) ⇒ Object



482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/rvc/modules/vm.rb', line 482

def _add_net_device vm, klass, network
  _add_device vm, klass.new(
    :key => -1,
    :deviceInfo => {
      :summary => network,
      :label => `uuidgen`.chomp
    },
    :backing => VIM.VirtualEthernetCardNetworkBackingInfo(
      :deviceName => network
    ),
    :addressType => 'generated'
  )
end

#_display_snapshot_tree(nodes, indent) ⇒ Object



536
537
538
539
540
541
# File 'lib/rvc/modules/vm.rb', line 536

def _display_snapshot_tree nodes, indent
  nodes.each do |node|
    puts "#{' '*indent}#{node.name} #{node.createTime}"
    _display_snapshot_tree node.childSnapshotList, (indent+1)
  end
end

#_extraConfig(vm, *regexes) ⇒ Object



361
362
363
364
365
366
367
368
# File 'lib/rvc/modules/vm.rb', line 361

def _extraConfig vm, *regexes
  vm.config.extraConfig.each do |h|
    if regexes.empty? or regexes.any? { |r| h[:key] =~ r }
      puts "#{h[:key]}: #{h[:value]}"
    end
  end
  nil
end

#_setExtraConfig(vm, hash) ⇒ Object



354
355
356
357
358
359
# File 'lib/rvc/modules/vm.rb', line 354

def _setExtraConfig vm, hash
  cfg = {
    :extraConfig => hash.map { |k,v| { :key => k, :value => v } },
  }
  vm.ReconfigVM_Task(:spec => cfg).wait_for_completion
end

#add_host(cluster, hostname, opts) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rvc/modules/cluster.rb', line 40

def add_host cluster, hostname, opts
  sslThumbprint = nil
  while true
    spec = {
      :force => false,
      :hostName => hostname,
      :userName => opts[:username],
      :password => opts[:password],
      :sslThumbprint => sslThumbprint,
    }
    task = cluster.AddHost_Task :spec => spec,
                                :asConnected => false
    begin
      task.wait_for_completion
    rescue VIM::SSLVerifyFault
      puts "SSL thumbprint: #{$!.fault.thumbprint}"
      $stdout.write "Accept this thumbprint? (y/n) "
      $stdout.flush
      answer = $stdin.readline.chomp
      err "Aborted" unless answer == 'y' or answer == 'yes'
      sslThumbprint = $!.fault.thumbprint
    end
  end
end

#add_net_device(vm, opts) ⇒ Object



462
463
464
465
466
467
468
469
470
# File 'lib/rvc/modules/vm.rb', line 462

def add_net_device vm, opts
  case opts[:type]
  when 'e1000'
    _add_net_device vm, VIM::VirtualE1000, opts[:network]
  when 'vmxnet3'
    _add_net_device vm, VIM::VirtualVmxnet3, opts[:network]
  else err "unknown device"
  end
end

#annotate(vm, str) ⇒ Object



631
632
633
# File 'lib/rvc/modules/vm.rb', line 631

def annotate vm, str
  vm.ReconfigVM_Task(:spec => { :annotation => str }).wait_for_completion
end

#answer(vm, str) ⇒ Object



249
250
251
252
253
# File 'lib/rvc/modules/vm.rb', line 249

def answer vm, str
  choice = q.choice.choiceInfo.find { |x| x.label == str }
  err("invalid answer") unless choice
  vm.AnswerVM :questionid => q.path, :answerChoice => choice.key
end

#cd(obj) ⇒ Object



121
122
123
124
125
126
# File 'lib/rvc/modules/basic.rb', line 121

def cd obj
  $shell.fs.cd(obj)
  $shell.fs.mark '', [find_ancestor(RbVmomi::VIM::Datacenter)].compact
  $shell.fs.mark '@', [find_ancestor(RbVmomi::VIM)].compact
  $shell.fs.marks.delete_if { |k,v| k =~ /^\d+$/ }
end

#change_device_connectivity(vm, label, connected) ⇒ Object



658
659
660
661
662
663
664
665
666
667
668
# File 'lib/rvc/modules/vm.rb', line 658

def change_device_connectivity vm, label, connected
  dev = vm.config.hardware.device.find { |x| x.deviceInfo.label == label }
  err "no such device" unless dev
  dev.connectable.connected = connected
  spec = {
    :deviceChange => [
      { :operation => :edit, :device => dev },
    ]
  }
  vm.ReconfigVM_Task(:spec => spec).wait_for_completion
end

#clone(src, dst, opts) ⇒ Object



579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/rvc/modules/vm.rb', line 579

def clone src, dst, opts
  folder, name = *dst
  diskMoveType = nil

  if opts[:linked]
    deltaize_disks src
    diskMoveType = :moveChildMostDiskBacking
  end

  task = src.CloneVM_Task(:folder => folder,
                          :name => name,
                          :spec => {
                            :location => {
                              :diskMoveType => diskMoveType,
                              :host => opts[:host],
                              :pool => opts[:pool],
                            },
                            :template => opts[:template],
                            :powerOn => opts[:powerOn],
                          })
  progress [task]
end

#connect(uri, opts) ⇒ Object



289
290
291
# File 'lib/rvc/modules/vm.rb', line 289

def connect vm, label
  change_device_connectivity vm, label, true
end

#create(name, parent, opts) ⇒ Object



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
137
138
139
140
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
171
172
173
174
175
176
# File 'lib/rvc/modules/vm.rb', line 109

def create dest, opts
  err "must specify resource pool (--pool)" unless opts[:pool]
  err "must specify datastore (--datastore)" unless opts[:datastore]
  vmFolder, name = *dest
  datastore_path = "[#{opts[:datastore].name}]"
  config = {
    :name => name,
    :guestId => 'otherGuest',
    :files => { :vmPathName => datastore_path },
    :numCPUs => 1,
    :memoryMB => 128,
    :deviceChange => [
      {
        :operation => :add,
        :device => VIM.VirtualLsiLogicController(
          :key => 1000,
          :busNumber => 0,
          :sharedBus => :noSharing
        )
      }, {
        :operation => :add,
        :fileOperation => :create,
        :device => VIM.VirtualDisk(
          :key => -1,
          :backing => VIM.VirtualDiskFlatVer2BackingInfo(
            :fileName => datastore_path,
            :diskMode => :persistent,
            :thinProvisioned => true
          ),
          :controllerKey => 1000,
          :unitNumber => 0,
          :capacityInKB => 4000000
        )
      }, {
        :operation => :add,
        :device => VIM.VirtualCdrom(
          :key => -2,
          :connectable => {
            :allowGuestControl => true,
            :connected => true,
            :startConnected => true,
          },
          :backing => VIM.VirtualCdromIsoBackingInfo(
            :fileName => datastore_path
          ),
          :controllerKey => 200,
          :unitNumber => 0
        )
      }, {
        :operation => :add,
        :device => VIM.VirtualE1000(
          :key => -3,
          :deviceInfo => {
            :label => 'Network Adapter 1',
            :summary => 'VM Network'
          },
          :backing => VIM.VirtualEthernetCardNetworkBackingInfo(
            :deviceName => 'VM Network'
          ),
          :addressType => 'generated'
        )
      }
    ],
  }
  vmFolder.CreateVM_Task(:config => config,
                         :pool => opts[:pool],
                         :host => opts[:host]).wait_for_completion
end

#debugObject



82
83
84
85
86
87
# File 'lib/rvc/modules/basic.rb', line 82

def debug
  debug = $shell.debug = !$shell.debug
  $shell.connections.each do |name,conn|
    conn.debug = debug
  end
end

#deltaize_disks(vm) ⇒ Object



603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/rvc/modules/vm.rb', line 603

def deltaize_disks vm
  real_disks = vm.config.hardware.device.grep(VIM::VirtualDisk).select { |x| x.backing.parent == nil }
  unless real_disks.empty?
    puts "Reconfiguring source VM to use delta disks..."
    deviceChange = []
    real_disks.each do |disk|
      deviceChange << { :operation => :remove, :device => disk }
      deviceChange << {
        :operation => :add,
        :fileOperation => :create,
        :device => disk.dup.tap { |x|
          x.backing = x.backing.dup
          x.backing.fileName = "[#{disk.backing.datastore.name}]"
          x.backing.parent = disk.backing
        }
      }
    end
    progress [vm.ReconfigVM_Task(:spec => { :deviceChange => deviceChange })]
  end
end

#destroy(objs) ⇒ Object



211
212
213
# File 'lib/rvc/modules/basic.rb', line 211

def destroy objs
  tasks objs, :Destroy
end

#devices(vm) ⇒ Object



273
274
275
276
277
278
279
280
# File 'lib/rvc/modules/vm.rb', line 273

def devices vm
  devs = vm.config.hardware.device
  devs.each do |dev|
    tags = []
    tags << (dev.connectable.connected ? :connected : :disconnected) if dev.props.member? :connectable
    puts "#{dev.deviceInfo.label} (#{dev.class}): #{dev.deviceInfo.summary}; #{tags * ' '}"
  end
end

#disconnect(connection) ⇒ Object



300
301
302
# File 'lib/rvc/modules/vm.rb', line 300

def disconnect vm, label
  change_device_connectivity vm, label, false
end

#download(datastore_path, local_path) ⇒ Object



27
28
29
30
31
# File 'lib/rvc/modules/datastore.rb', line 27

def download datastore_path, local_path
  file = lookup_single(datastore_path)
  err "not a datastore file" unless file.is_a? RbVmomi::VIM::Datastore::FakeDatastoreFile
  file.datastore.download file.path, local_path
end

#edit(file) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/rvc/modules/mark.rb', line 43

def edit key
  editor = ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
  objs = $shell.fs.marks[key] or err "no such mark #{key.inspect}"
  filename = File.join(Dir.tmpdir, "rvc.#{Time.now.to_i}.#{rand(65536)}")
  File.open(filename, 'w') { |io| objs.each { |obj| io.puts(obj.rvc_path_str) } }
  begin
    system("#{editor} #{filename}")
    new_paths = File.readlines(filename).map(&:chomp) rescue return
    new_objs = new_paths.map { |path| lookup(path) }.inject([], &:+)
    mark key, new_objs
  ensure
    File.unlink filename
  end
end

#enter_maintenance_mode(hosts, opts) ⇒ Object



81
82
83
# File 'lib/rvc/modules/host.rb', line 81

def enter_maintenance_mode hosts, opts
  tasks hosts, :EnterMaintenanceMode, :timeout => opts[:timeout]
end

#evacuate(src, dsts, opts) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/rvc/modules/host.rb', line 39

def evacuate src, dsts, opts
  vim = src._connection
  vms = src.vm
  dst_hosts = dsts.map(&:host).flatten
  checks = ['cpu', 'software']

  dst_hosts.reject! { |host| host == src ||
                             host.runtime.connectionState != 'connected' ||
                             host.runtime.inMaintenanceMode }

  candidates = {}
  vms.each do |vm|
    required_datastores = vm.datastore
    result = vim.serviceInstance.QueryVMotionCompatibility(:vm => vm,
                                                           :host => dst_hosts,
                                                           :compatibility => checks)
    result.reject! { |x| x.compatibility != checks ||
                         x.host.datastore & required_datastores != required_datastores }
    candidates[vm] = result.map { |x| x.host }
  end

  if candidates.any? { |vm,hosts| hosts.empty? }
    puts "The following VMs have no compatible vMotion destination:"
    candidates.select { |vm,hosts| hosts.empty? }.each { |vm,hosts| puts " #{vm.name}" }
    return
  end

  tasks = candidates.map do |vm,hosts|
    host = hosts[rand(hosts.size)]
    vm.MigrateVM_Task(:host => host, :priority => :defaultPriority)
  end

  progress tasks
end

#exit_maintenance_mode(hosts, opts) ⇒ Object



92
93
94
# File 'lib/rvc/modules/host.rb', line 92

def exit_maintenance_mode hosts, opts
  tasks hosts, :ExitMaintenanceMode, :timeout => opts[:timeout]
end

#extraConfig(vm, regexes) ⇒ Object



337
338
339
# File 'lib/rvc/modules/vm.rb', line 337

def extraConfig vm, regexes
  _extraConfig(vm, *regexes.map { |x| /#{x}/ })
end

#find(ds, opts) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/rvc/modules/vm.rb', line 312

def find ds, opts
  folder = opts[:folder]
  rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool

  paths = find_vmx_files(ds)
  if paths.empty?
    puts "no VMX files found"
    return
  end

  puts "Select a VMX file"
  path = menu(paths) or return

  folder.RegisterVM_Task(:path => path,
                         :asTemplate => false,
                         :pool => rp).wait_for_completion
end

#find_ancestor(klass) ⇒ Object



128
129
130
# File 'lib/rvc/modules/basic.rb', line 128

def find_ancestor klass
  $shell.fs.cur.rvc_path.map { |k,v| v }.reverse.find { |x| x.is_a? klass }
end

#find_local_vmrcObject



31
32
33
34
# File 'lib/rvc/modules/vmrc.rb', line 31

def find_local_vmrc
  path = File.join(Dir.tmpdir, VMRC_NAME, 'plugins', 'vmware-vmrc')
  File.exists?(path) && path
end

#find_vmrcObject



36
37
38
# File 'lib/rvc/modules/vmrc.rb', line 36

def find_vmrc
  find_local_vmrc || search_path('vmrc')
end

#find_vmx_files(ds) ⇒ Object



636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
# File 'lib/rvc/modules/vm.rb', line 636

def find_vmx_files ds
  datastorePath = "[#{ds.name}] /"
  searchSpec = {
    :details => { :fileOwner => false, :fileSize => false, :fileType => true, :modification => false  },
    :query => [
      VIM::VmConfigFileQuery()
    ]
  }
  task = ds.browser.SearchDatastoreSubFolders_Task(:datastorePath => datastorePath, :searchSpec => searchSpec)

  results = task.wait_for_completion

  files = []
  results.each do |result|
    result.file.each do |file|
      files << result.folderPath + file.path
    end
  end

  files
end

#help(path) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/rvc/modules/basic.rb', line 44

def help path
  obj = lookup_single(path) if path

  if obj
    puts "Relevant commands for #{obj.class}:"
  else
    puts "All commands:"
  end

  MODULES.sort_by do |mod_name,mod|
    HELP_ORDER.index(mod_name) || HELP_ORDER.size
  end.each do |mod_name,mod|
    opts = mod.instance_variable_get(:@opts)
    opts.each do |method_name,method_opts|
      parser = RVC::OptionParser.new method_name, &method_opts
      next unless obj.nil? or parser.applicable.any? { |x| obj.is_a? x }
      aliases = ALIASES.select { |k,v| v == "#{mod_name}.#{method_name}" }.map(&:first)
      aliases_text = aliases.empty? ? '' : " (#{aliases*', '})"
      puts "#{mod_name}.#{method_name}#{aliases_text}: #{parser.summary?}" if parser.summary?
    end
  end

  if not obj
    puts (<<-EOS)

To see detailed help for a command, use its --help option.
To show only commands relevant to a specific object, use "help /path/to/object".
    EOS
  end
end

#info(obj) ⇒ Object



194
195
196
197
198
199
200
201
# File 'lib/rvc/modules/basic.rb', line 194

def info obj
  puts "path: #{obj.rvc_path_str}"
  if obj.respond_to? :display_info
    obj.display_info
  else
    puts "class: #{obj.class.name}"
  end
end

#insert_cdrom(vm, iso) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/rvc/modules/vm.rb', line 185

def insert_cdrom vm, iso
  device = vm.config.hardware.device.grep(VIM::VirtualCdrom)[0]
  err "No virtual CDROM drive found" unless device

  device.backing = VIM.VirtualCdromIsoBackingInfo(:fileName => iso.datastore_path)

  spec = {
    :deviceChange => [
      {
        :operation => :edit,
        :device => device
      }
    ]
  }
  
  vm.ReconfigVM_Task(:spec => spec)
end

#installObject



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/rvc/modules/vmrc.rb', line 75

def install
  system "which #{CURL} > /dev/null" or err "curl not found"
  system "which sha256sum > /dev/null" or err "sha256sum not found"
  puts "Downloading VMRC..."
  dir = Dir.mktmpdir
  vmrc_file = "#{dir}/#{VMRC_BASENAME}"
  checksum_file = "#{dir}/sha256sums"
  system "#{CURL} -L #{VMRC_URL} -o #{vmrc_file}" or err "download failed"
  puts "Checking integrity..."
  File.open(checksum_file, 'w') { |io| io.puts "#{VMRC_SHA256} *#{vmrc_file}" }
  system "sha256sum -c #{checksum_file}" or err "integrity check failed"
  puts "Installing VMRC..."
  system "tar -xj -f #{vmrc_file} -C #{Dir.tmpdir}" or err("VMRC installation failed")
  puts "VMRC was installed successfully."
  FileUtils.rm_r dir
end

#ip(vms) ⇒ Object



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
# File 'lib/rvc/modules/vm.rb', line 424

def ip vms
  props = %w(summary.runtime.powerState summary.guest.ipAddress summary.config.annotation)
  connection = single_connection vms

  filters = vms.map do |vm|
    connection.propertyCollector.CreateFilter :spec => {
      :propSet => [{ :type => 'VirtualMachine', :all => false, :pathSet => props }],
      :objectSet => [{ :obj => vm }],
    }, :partialUpdates => false
  end

  ver = ''
  while not vms.empty?
    result = connection.propertyCollector.WaitForUpdates(:version => ver)
    ver = result.version

    vms.reject! do |vm|
      begin
        ip = vm_ip(vm)
        puts "#{vm.name}: #{ip}"
        true
      rescue UserError
        false
      end
    end
  end
ensure
  filters.each(&:DestroyPropertyFilter) if filters
end

#kill(vms) ⇒ Object



236
237
238
239
240
# File 'lib/rvc/modules/vm.rb', line 236

def kill vms
  on_vms = vms.select { |x| x.summary.runtime.powerState == 'poweredOn' }
  off on_vms unless on_vms.empty?
  CMD.basic.destroy vms unless vms.empty?
end

#layout(vm) ⇒ Object



261
262
263
264
265
# File 'lib/rvc/modules/vm.rb', line 261

def layout vm
  vm.layoutEx.file.each do |f|
    puts "#{f.type}: #{f.name}"
  end
end

#ls(obj) ⇒ Object



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
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/rvc/modules/basic.rb', line 141

def ls obj
  children = obj.children
  name_map = children.invert
  children, fake_children = children.partition { |k,v| v.is_a? VIM::ManagedEntity }
  i = 0

  fake_children.each do |name,child|
    puts "#{i} #{name}#{child.ls_text(nil)}"
    child.rvc_link obj, name
    $shell.fs.mark i.to_s, [child]
    i += 1
  end

  return if children.empty?

  filterSpec = VIM.PropertyFilterSpec(:objectSet => [], :propSet => [])
  filteredTypes = Set.new

  children.each do |name,child|
    filterSpec.objectSet << { :obj => child }
    filteredTypes << child.class
  end

  filteredTypes.each do |x|
    filterSpec.propSet << {
      :type => x.wsdl_name,
      :pathSet => x.ls_properties+%w(name),
    }
  end

  connection = single_connection(children.map { |k,v| v })
  results = connection.propertyCollector.RetrieveProperties(:specSet => [filterSpec])

  results.each do |r|
    name = name_map[r.obj]
    text = r.obj.ls_text(r) rescue " (error)"
    realname = r['name'] if name != r['name']
    puts "#{i} #{name}#{realname && " [#{realname}]"}#{text}"
    r.obj.rvc_link obj, name
    $shell.fs.mark i.to_s, [r.obj]
    i += 1
  end
end

#mark(key, objs) ⇒ Object



30
31
32
33
# File 'lib/rvc/modules/mark.rb', line 30

def mark key, objs
  err "invalid mark name" unless key =~ /^\w+$/
  $shell.fs.mark key, objs
end

#migrate(vms, opts) ⇒ Object



561
562
563
564
565
# File 'lib/rvc/modules/vm.rb', line 561

def migrate vms, opts
  tasks vms, :MigrateVM, :pool => opts[:pool],
                         :host => opts[:host],
                         :priority => :defaultPriority
end

#mkdir(datastore_path) ⇒ Object

TODO dispatch to datastore.mkdir if path is in a datastore



281
282
283
284
# File 'lib/rvc/modules/basic.rb', line 281

def mkdir path
  parent = lookup_single! File.dirname(path), RbVmomi::VIM::Folder
  parent.CreateFolder(:name => File.basename(path))
end

#mv(src, dst) ⇒ Object



251
252
253
254
255
256
257
258
# File 'lib/rvc/modules/basic.rb', line 251

def mv src, dst
  src_dir = File.dirname(src)
  dst_dir = File.dirname(dst)
  err "cross-directory mv not yet supported" unless src_dir == dst_dir
  dst_name = File.basename(dst)
  obj = lookup(src)
  obj.Rename_Task(:newName => dst_name).wait_for_completion
end

#off(vm) ⇒ Object



40
41
42
# File 'lib/rvc/modules/vm.rb', line 40

def off vms
  tasks vms, :PowerOffVM
end

#on(vms) ⇒ Object



28
29
30
# File 'lib/rvc/modules/vm.rb', line 28

def on vms
  tasks vms, :PowerOnVM
end

#ping(vm) ⇒ Object



413
414
415
416
# File 'lib/rvc/modules/vm.rb', line 413

def ping vm
  ip = vm_ip vm
  system_fg "ping #{Shellwords.escape ip}"
end

#prompt_cert_insecureObject



143
144
145
# File 'lib/rvc/modules/vim.rb', line 143

def prompt_cert_insecure
  agree("SSL certificate verification failed. Connect anyway (y/n)? ", true)
end

#prompt_cert_unknownObject



147
148
149
# File 'lib/rvc/modules/vim.rb', line 147

def prompt_cert_unknown
  agree("Are you sure you want to continue connecting (y/n)? ", true)
end

#prompt_passwordObject



139
140
141
# File 'lib/rvc/modules/vim.rb', line 139

def prompt_password
  ask("password: ") { |q| q.echo = false }
end

#quitObject



98
99
100
# File 'lib/rvc/modules/basic.rb', line 98

def quit
  exit
end

#reachable_ip(host) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/rvc/modules/vnc.rb', line 70

def reachable_ip host
  ips = host.config.network.vnic.map { |x| x.spec.ip.ipAddress } # TODO optimize
  ips.find do |x|
    begin
      Timeout.timeout(1) { TCPSocket.new(x, 443).close; true }
    rescue
      false
    end
  end or err("could not find IP for server #{host.name}")
end

#reboot(hosts, opts) ⇒ Object



27
28
29
# File 'lib/rvc/modules/host.rb', line 27

def reboot hosts, opts
  tasks hosts, :RebootHost, :force => opts[:force]
end

#reboot_guest(vms) ⇒ Object



96
97
98
# File 'lib/rvc/modules/vm.rb', line 96

def reboot_guest vms
  vms.each(&:RebootGuest)
end

#reconnect(hosts, opts) ⇒ Object



114
115
116
117
118
119
120
121
# File 'lib/rvc/modules/host.rb', line 114

def reconnect hosts, opts
  spec = {
    :force => false,
    :userName => opts[:username],
    :password => opts[:password],
  }
  tasks hosts, :ReconnectHost
end

#register(vmx_file, opts) ⇒ Object



210
211
212
213
214
215
# File 'lib/rvc/modules/vm.rb', line 210

def register vmx_file, opts
  rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool
  vm = opts[:folder].RegisterVM_Task(:path => vmx_file.datastore_path,
                                     :asTemplate => false,
                                     :pool => rp).wait_for_completion
end

#reloadObject



109
110
111
# File 'lib/rvc/modules/basic.rb', line 109

def reload
  RVC.reload_modules
end

#reload_entity(objs) ⇒ Object



223
224
225
# File 'lib/rvc/modules/basic.rb', line 223

def reload_entity objs
  objs.each(&:Reload)
end

#remove_device(vm, label) ⇒ Object



503
504
505
506
507
508
509
510
511
512
# File 'lib/rvc/modules/vm.rb', line 503

def remove_device vm, label
  dev = vm.config.hardware.device.find { |x| x.deviceInfo.label == label }
  err "no such device" unless dev
  spec = {
    :deviceChange => [
      { :operation => :remove, :device => dev },
    ]
  }
  vm.ReconfigVM_Task(:spec => spec).wait_for_completion
end

#reset(vms) ⇒ Object



53
54
55
# File 'lib/rvc/modules/vm.rb', line 53

def reset vms
  tasks vms, :ResetVM
end

#revert(vm) ⇒ Object



549
550
551
# File 'lib/rvc/modules/vm.rb', line 549

def revert vm
  tasks [vm], :RevertToCurrentSnapshot
end

#rvc(vm) ⇒ Object



395
396
397
398
399
400
401
402
403
# File 'lib/rvc/modules/vm.rb', line 395

def rvc vm
  ip = vm_ip vm

  env = Hash[%w(RBVMOMI_PASSWORD RBVMOMI_HOST RBVMOMI_USER RBVMOMI_SSL RBVMOMI_PORT
                RBVMOMI_FOLDER RBVMOMI_DATASTORE RBVMOMI_PATH RBVMOMI_DATACENTER
                RBVMOMI_COMPUTER).map { |k| [k,nil] }]
  cmd = "rvc #{Shellwords.escape ip}"
  system_fg(cmd, env)
end

#setExtraConfig(vm, pairs) ⇒ Object



348
349
350
351
# File 'lib/rvc/modules/vm.rb', line 348

def setExtraConfig vm, pairs
  h = Hash[pairs.map { |x| x.split('=', 2).tap { |a| a << '' if a.size == 1 } }]
  _setExtraConfig vm, h
end

#shares_from_string(str) ⇒ Object



35
36
37
38
39
40
41
42
43
44
# File 'lib/rvc/modules/resource_pool.rb', line 35

def shares_from_string str
  case str
  when 'normal', 'low', 'high'
    { :level => str, :shares => 0 }
  when /^\d+$/
    { :level => 'custom', :shares => str.to_i }
  else
    err "Invalid shares argument #{str.inspect}"
  end
end

#show(objs) ⇒ Object



236
237
238
239
240
# File 'lib/rvc/modules/basic.rb', line 236

def show objs
  objs.each do |obj|
    puts "#{obj.rvc_path_str}: #{obj.class}"
  end
end

#shutdown_guest(vms) ⇒ Object



76
77
78
# File 'lib/rvc/modules/vm.rb', line 76

def shutdown_guest vms
  vms.each(&:ShutdownGuest)
end

#snapshot(vm, name) ⇒ Object



521
522
523
# File 'lib/rvc/modules/vm.rb', line 521

def snapshot vm, name
  tasks [vm], :CreateSnapshot, :memory => true, :name => name, :quiesce => false
end

#snapshots(vm) ⇒ Object



532
533
534
# File 'lib/rvc/modules/vm.rb', line 532

def snapshots vm
  _display_snapshot_tree vm.snapshot.rootSnapshotList, 0
end

#ssh(vm, cmd, opts) ⇒ Object



380
381
382
383
384
385
# File 'lib/rvc/modules/vm.rb', line 380

def ssh vm, cmd, opts
  ip = vm_ip vm
  cmd_arg = cmd ? Shellwords.escape(cmd) : ""
  ssh_cmd = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l #{Shellwords.escape opts[:login]} #{Shellwords.escape ip} #{cmd_arg}"
  system_fg(ssh_cmd)
end

#standby_guest(vms) ⇒ Object



86
87
88
# File 'lib/rvc/modules/vm.rb', line 86

def standby_guest vms
  vms.each(&:StandbyGuest)
end

#suspend(vms) ⇒ Object



66
67
68
# File 'lib/rvc/modules/vm.rb', line 66

def suspend vms
  tasks vms, :SuspendVM
end

#type(name) ⇒ Object



28
29
30
31
32
# File 'lib/rvc/modules/basic.rb', line 28

def type name
  klass = RbVmomi::VIM.type(name) rescue err("#{name.inspect} is not a VMODL type.")
  $shell.introspect_class klass
  nil
end

#unregister(vm) ⇒ Object



223
224
225
# File 'lib/rvc/modules/vm.rb', line 223

def unregister vm
  vm.UnregisterVM
end

#unused_vnc_port(ip) ⇒ Object



81
82
83
84
85
86
87
88
# File 'lib/rvc/modules/vnc.rb', line 81

def unused_vnc_port ip
  10.times do
    port = 5901 + rand(64)
    unused = (TCPSocket.connect(ip, port).close rescue true)
    return port if unused
  end
  err "no unused port found"
end

#update(pool, opts) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/rvc/modules/resource_pool.rb', line 79

def update pool, opts
  spec = {
    :cpuAllocation => {
      :limit => opts[:cpu_limit],
      :reservation => opts[:cpu_reservation],
      :expandableReservation => opts[:cpu_expandable],
      :shares => shares_from_string(opts[:cpu_shares]),
    },
    :memoryAllocation => {
      :limit => opts[:mem_limit],
      :reservation => opts[:mem_reservation],
      :expandableReservation => opts[:mem_expandable],
      :shares => shares_from_string(opts[:mem_shares]),
    },
  }
  pool.UpdateConfig(:name => opts[:name], :spec => spec)
end

#upload(local_path, datastore_path) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/rvc/modules/datastore.rb', line 40

def upload local_path, datastore_path
  datastore_dir_path = File.dirname datastore_path
  dir = lookup_single(datastore_dir_path)
  err "datastore directory does not exist" unless dir.is_a? RbVmomi::VIM::Datastore::FakeDatastoreFolder
  err "local file does not exist" unless File.exists? local_path
  real_datastore_path = "#{dir.path}/#{File.basename(datastore_path)}"
  dir.datastore.upload real_datastore_path, local_path
end

#view(vms) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/rvc/modules/vnc.rb', line 31

def view vm
  ip = reachable_ip vm.runtime.host
  extraConfig = vm.config.extraConfig
  already_enabled = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.enabled' && x.value.downcase == 'true' }
  if already_enabled
    puts "VNC already enabled"
    port = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.port' }.value
    password = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.password' }.value
  else
    port = unused_vnc_port ip
    password = vnc_password
    vm.ReconfigVM_Task(:spec => {
      :extraConfig => [
        { :key => 'RemoteDisplay.vnc.enabled', :value => 'true' },
        { :key => 'RemoteDisplay.vnc.password', :value => password },
        { :key => 'RemoteDisplay.vnc.port', :value => port.to_s }
      ]
    }).wait_for_completion
  end
  vnc_client ip, port, password
end

#vm_ip(vm) ⇒ Object



670
671
672
673
674
675
676
677
678
679
680
681
682
# File 'lib/rvc/modules/vm.rb', line 670

def vm_ip vm
  summary = vm.summary

  err "VM is not powered on" unless summary.runtime.powerState == 'poweredOn'

  ip = if summary.guest.ipAddress and summary.guest.ipAddress != '127.0.0.1'
    summary.guest.ipAddress
  elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note.member? 'ip'
    note['ip']
  else
    err "no IP known for this VM"
  end
end

#vnc_client(ip, port, password) ⇒ Object

Override this to spawn a VNC client differently



98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rvc/modules/vnc.rb', line 98

def vnc_client ip, port, password
  if VNC
    fork do
      $stderr.reopen("#{ENV['HOME']||'.'}/.rvc-vmrc.log", "w")
      Process.setpgrp
      exec VNC, "#{ip}:#{port}"
    end
    puts "spawning #{VNC}"
    puts "#{ip}:#{port} password: #{password}"
  else
    puts "no VNC client configured"
    puts "#{ip}:#{port} password: #{password}"
  end
end

#vnc_passwordObject

Override this if you don’t want a random password



91
92
93
94
95
# File 'lib/rvc/modules/vnc.rb', line 91

def vnc_password
  n = 8
  chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
  (0...n).map { chars[rand(chars.length)].chr }.join
end