Top Level Namespace

Includes:
RVC::Util

Defined Under Namespace

Modules: RVC Classes: LazyDVPort, MetricNumber, Numeric, ProgressStream, TimeDiff

Constant Summary collapse

VIM =
RbVmomi::VIM
DEFAULT_SERVER_PLACEHOLDER =

Copyright © 2012 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.

'0.0.0.0'
VNC =
ENV['VNC'] || search_path('tightvnc') || search_path('vncviewer') || search_path('vinagre')
TIMEFMT =
'%Y-%m-%dT%H:%M:%SZ'
DISPLAY_TIMEFMT =
{
  :realtime => '%H:%M',
  1 => '%H:%M',
  2 => '%m/%d',
  3 => '%m/%d',
  4 => '%Y/%m/%d',
}
VMRC_CHECKSUMS =
{
  "i686-linux" => "b8f11c92853502c3dd208da79514a66d2dd4734b8564aceb9952333037859d04",
  "x86_64-linux" => "86ec4bc6f23da0c33045d9bf48d9fe66ab2f426b523d8b37531646819891bf54",
  "i686-mswin" => "f8455f0df038fbc8e817e4381af44fa2141496cb4e2b61f505f75bc447841949",
}
PACKAGE_VERSION =
'A'
ARCH =
RbConfig::CONFIG['arch']
ON_WINDOWS =
(RbConfig::CONFIG['host_os'] =~ /(mswin|mingw)/) != nil
IGNORE_STATUSES =
%w(green gray)
HELP_ORDER =
%w(basic vm)
NET_DEVICE_CLASSES =
{
  'e1000' => VIM::VirtualE1000,
  'vmxnet3' => VIM::VirtualVmxnet3,
}
SCSI_CONTROLLER_TYPES =
{
  'pvscsi' => VIM::ParaVirtualSCSIController,
  'buslogic' => VIM::VirtualBusLogicController,
  'lsilogic' => VIM::VirtualLsiLogicController,
  'lsilogic-sas' => VIM::VirtualLsiLogicSASController,
}
SCSI_BUS_NUMBERS =
[0, 1, 2, 3]
EsxcliCache =
RVC::TTLCache.new 60

Constants included from RVC::Util

RVC::Util::PROGRESS_BAR_LEFT, RVC::Util::PROGRESS_BAR_MIDDLE, RVC::Util::PROGRESS_BAR_RIGHT, RVC::Util::UserError

Instance Method Summary collapse

Methods included from RVC::Util

#collect_children, #display_inventory, #err, #http_clone, #http_download, #http_upload, #interactive?, #menu, #metric, #one_progress, #progress, #retrieve_fields, #search_path, #single_connection, #status_color, #system_fg, #tcsetpgrp, #terminal_columns

Instance Method Details

#_add_device(vm, fileOp, dev) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/rvc/modules/device.rb', line 262

def _add_device vm, fileOp, dev
  spec = {
    :deviceChange => [
      { :operation => :add, :fileOperation => fileOp, :device => dev },
    ]
  }
  task = vm.ReconfigVM_Task(:spec => spec)
  result = progress([task])[task]
  if result == nil
    new_device = vm.collect('config.hardware.device')[0].grep(dev.class).last
    puts "Added device #{new_device.name}"
  end
end

#_extra_config(vm, *regexes) ⇒ Object



374
375
376
377
378
379
380
381
# File 'lib/rvc/modules/vm.rb', line 374

def _extra_config 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

#_set_extra_config(vm, hash) ⇒ Object



367
368
369
370
371
372
# File 'lib/rvc/modules/vm.rb', line 367

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

#_vm_create(clusters, datastore, vm_folder, opts = {}) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
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
# File 'lib/rvc/modules/diagnostics.rb', line 78

def _vm_create clusters, datastore, vm_folder, opts = {}
  pc = datastore._connection.serviceContent.propertyCollector
  datastore_path = "[#{datastore.name}]"
  run = Time.now.to_i % 1000
  tasks_map = {}
  cluster_host_map = {}
  clusters_props = pc.collectMultiple(clusters, 'name', 'resourcePool', 'host')
  all_hosts = clusters_props.map{|c, p| p['host']}.flatten
  hosts_props = pc.collectMultiple(all_hosts, 'name')

  hosts_infos = Hash[all_hosts.map{|host| [host, {}]}]
  
  clusters.each do |cluster|
    cluster_props = clusters_props[cluster]
    rp = cluster_props['resourcePool']
    hosts = cluster_props['host']
    hosts.map do |host|
      cluster_host_map[host] = cluster
      config = {
        :name => "VM-on-#{hosts_props[host]['name']}-#{run}",
        :guestId => 'otherGuest',
        :files => { :vmPathName => datastore_path },
        :numCPUs => 1,
        :memoryMB => 16,
        :deviceChange => [
          {
            :operation => :add,
            :device => VIM.VirtualCdrom(
              :key => -2,
              :connectable => {
                :allowGuestControl => true,
                :connected => true,
                :startConnected => true,
              },
              :backing => VIM.VirtualCdromIsoBackingInfo(
                :fileName => datastore_path
              ),
              :controllerKey => 200,
              :unitNumber => 0
            )
          }
        ],
      }
      task = vm_folder.CreateVM_Task(:config => config,
                                     :pool => rp,
                                     :host => host)
      tasks_map[task] = host
      hosts_infos[host][:create_task] = task
    end
  end
  
  create_tasks = tasks_map.keys
  create_results = wait_for_multiple_tasks create_tasks, opts[:timeout]
  create_results.each { |t, r| hosts_infos[tasks_map[t]][:create_result] = r }

  vms = create_results.select{|t, x| x.is_a? VIM::VirtualMachine}
  destroy_tasks = Hash[vms.map{|t, x| [x.Destroy_Task, t]}]

  destroy_results = wait_for_multiple_tasks destroy_tasks.keys, opts[:timeout]
  destroy_results.each do |t, r|
    create_task = destroy_tasks[t]
    hosts_infos[tasks_map[create_task]][:destroy_result] = r 
  end

  out = {}
  all_hosts.each do |host|
    host_info = hosts_infos[host]
    host_props = hosts_props[host]
    cluster = cluster_host_map[host]
    cluster_props = clusters_props[cluster]

    result = host_info[:create_result]
    result = host_info[:destroy_result] if result.is_a?(VIM::VirtualMachine)
    if result == nil
      error_str = nil
      status = 'green'
    elsif result.is_a?(String)
      error_str = result
      status = 'red'
    else
      error_str = "#{result.fault.class.wsdl_name}: #{result.localizedMessage}"
      status = 'red'
    end

    out[host_props['name']] = {
      'cluster' => cluster_props['name'],
      'status' => status,
      'error' => error_str
    }    
  end
  out
end

#abbrev_hostnames(names) ⇒ Object



581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/rvc/modules/vds.rb', line 581

def abbrev_hostnames names
  min_len = 999
  split_names = names.map { |name|
    new_r = name.split('.').reverse
    min_len = [min_len, new_r.size].min
    new_r
  }

  matches = 0
  (0..(min_len-1)).each do |i|
    if split_names.first[i] == split_names.last[i]
      matches = i+1
    else
      break
    end
  end

  if matches == min_len
    matches -= 1
  end

  if matches > 0
    names.each { |n|
      n.replace(n.split('.').reverse.drop(matches).reverse.join('.') + '.~')
    }
  end
end

#add_cdrom(vm, opts) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/rvc/modules/device.rb', line 148

def add_cdrom vm, opts
  controller, unit_number = pick_controller vm, opts[:controller], [VIM::VirtualIDEController]
  id = "cdrom-#{controller.key}-#{unit_number}"
  _add_device vm, nil, VIM.VirtualCdrom(
    :controllerKey => controller.key,
    :key => -1,
    :unitNumber => unit_number,
    :backing => VIM.VirtualCdromAtapiBackingInfo(
      :deviceName => id,
      :useAutoDetect => false
    ),
    :connectable => VIM.VirtualDeviceConnectInfo(
      :allowGuestControl => true,
      :connected => true,
      :startConnected => true
    )
  )
end

#add_disk(vm, path, opts) ⇒ Object



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

def add_disk vm, path, opts
  controller, unit_number = pick_controller vm, opts[:controller], [VIM::VirtualSCSIController, VIM::VirtualIDEController]
  id = "disk-#{controller.key}-#{unit_number}"

  if path
    dir, file = *path
    filename = "#{dir.datastore_path}/#{file}"
  else
    filename = "#{File.dirname(vm.summary.config.vmPathName)}/#{id}.vmdk"
  end

  opts[:file_op] = nil if opts[:file_op] == 'reuse'

  _add_device vm, opts[:file_op], VIM::VirtualDisk(
    :key => -1,
    :backing => VIM.VirtualDiskFlatVer2BackingInfo(
      :fileName => filename,
      :diskMode => :persistent,
      :thinProvisioned => true
    ),
    :capacityInKB => MetricNumber.parse(opts[:size]).to_i/1000,
    :controllerKey => controller.key,
    :unitNumber => unit_number
  )
end

#add_host(cluster, hostnames, opts) ⇒ Object



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/cluster.rb', line 45

def add_host cluster, hostnames, opts
  sslThumbprint = nil
  hostnames.each do |hostname|
    while true
      spec = {
        :force => opts[:force],
        :hostName => hostname,
        :userName => opts[:username],
        :password => opts[:password],
        :sslThumbprint => sslThumbprint,
      }
      task = cluster.AddHost_Task :spec => spec,
                                  :asConnected => true
      begin
        one_progress task
        break
      rescue VIM::SSLVerifyFault
        unless opts[:insecure]
          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'
        end
        sslThumbprint = $!.fault.thumbprint
      end
    end
  end
end

#add_hosts(vds, hosts, opts) ⇒ Object



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/rvc/modules/vds.rb', line 616

def add_hosts vds, hosts, opts
  pnicSpec = opts[:vmnic].map do |x| 
    VIM::DistributedVirtualSwitchHostMemberPnicSpec({:pnicDevice => x})
  end
  dvsConfig = VIM::DVSConfigSpec({
    :configVersion => vds.config.configVersion, 
    :host => hosts.map do |host|
      {
        :operation => :add, 
        :host => host, 
        :backing => VIM::DistributedVirtualSwitchHostMemberPnicBacking({
          :pnicSpec => pnicSpec
        })
      }
    end
  })
  task = vds.ReconfigureDvs_Task(:spec => dvsConfig)
  progress([task])
end

#add_iscsi_target(hosts, opts) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/rvc/modules/host.rb', line 159

def add_iscsi_target hosts, opts
  hosts.each do |host|
    puts "configuring host #{host.name}"
    storage = host.configManager.storageSystem
    storage.UpdateSoftwareInternetScsiEnabled(:enabled => true)
    adapter = storage.storageDeviceInfo.hostBusAdapter.grep(VIM::HostInternetScsiHba)[0]
    storage.AddInternetScsiStaticTargets(
      :iScsiHbaDevice => adapter.device,
      :targets => [ VIM::HostInternetScsiHbaStaticTarget(:address => opts[:address], :iScsiName => opts[:iqn]) ]
    )
    storage.RescanAllHba
  end
end

#add_net(vm, network, opts) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/rvc/modules/device.rb', line 77

def add_net vm, network, opts
  klass = NET_DEVICE_CLASSES[opts[:type]] or err "unknown network adapter type #{opts[:type].inspect}"

  case network
  when VIM::DistributedVirtualPortgroup
    switch, pg_key = network.collect 'config.distributedVirtualSwitch', 'key'
    port = VIM.DistributedVirtualSwitchPortConnection(
      :switchUuid => switch.uuid,
      :portgroupKey => pg_key)
    summary = network.name
    backing = VIM.VirtualEthernetCardDistributedVirtualPortBackingInfo(:port => port)
  when VIM::Network
    summary = network.name
    backing = VIM.VirtualEthernetCardNetworkBackingInfo(:deviceName => network.name)
  else fail
  end

  _add_device vm, nil, klass.new(
    :key => -1,
    :deviceInfo => {
      :summary => summary,
      :label => "",
    },
    :backing => backing,
    :addressType => 'generated'
  )
end

#add_nfs_datastore(hosts, opts) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/rvc/modules/host.rb', line 180

def add_nfs_datastore hosts, opts
  hosts.each do |host|
    datastoreSystem, = host.collect 'configManager.datastoreSystem'
    spec = {
      :accessMode => 'readWrite',
      :localPath => opts[:name],
      :remoteHost => opts[:address],
      :remotePath => opts[:path]
    }
    datastoreSystem.CreateNasDatastore :spec => spec
  end
end

#add_privilege(name, privileges) ⇒ Object



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

def add_privilege name, privileges
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{name.inspect}" unless role
  cur_auth_mgr.UpdateAuthorizationRole :roleId => role.roleId,
                                       :newName => role.name,
                                       :privIds => (role.privilege | privileges)

end

#add_scsi_controller(vm, opts) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/rvc/modules/device.rb', line 211

def add_scsi_controller vm, opts
  klass = SCSI_CONTROLLER_TYPES[opts[:type]] or err "invalid SCSI controller type #{opts[:type].inspect}"
  err "invalid value for --sharing" unless VIM::VirtualSCSISharing.values.member? opts[:sharing]

  existing_devices, = vm.collect 'config.hardware.device'
  used_bus_numbers = existing_devices.grep(VIM::VirtualSCSIController).map(&:busNumber)
  bus_number = (SCSI_BUS_NUMBERS - used_bus_numbers).min
  err "unable to allocate a bus number, too many SCSI controllers" unless bus_number

  controller = klass.new(
    :key => -1,
    :busNumber => bus_number,
    :sharedBus => opts[:sharing],
    :hotAddRemove => opts[:hot_add]
  )

  _add_device vm, nil, controller
end

#add_serial(vm) ⇒ Object



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

def add_serial vm
  # create an initial no-op backing
  backing = VIM::VirtualSerialPortURIBackingInfo(:direction => :client, :serviceURI => 'localhost:0')
  _add_device vm, nil, VIM::VirtualSerialPort(:yieldOnPoll => true, :key => -1, :backing => backing)
end

#annotate(vm, str) ⇒ Object



573
574
575
# File 'lib/rvc/modules/vm.rb', line 573

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

#answer(str, vms) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/rvc/modules/vm.rb', line 288

def answer str, vms
  vms.each do |vm|
    begin
      if q = vm.runtime.question
        choices = q.choice.choiceInfo
        choice = choices.find { |x| x.label == str }
        if !choice
          puts "#{vm.name}: #{choice} invalid, choices: #{choices.map{ |x| x.label }}"
        else
          vm.AnswerVM :questionId => q.id, :answerChoice => choice.key
        end
      end
    rescue
      puts "#{vm.name rescue vm}: #{$!.message}"
    end
  end
end

#apply_recommendations(cluster, opts) ⇒ Object



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

def apply_recommendations cluster, opts
  pc = cluster._connection.serviceContent.propertyCollector
  recommendation = cluster.recommendation
  if opts[:key] && opts[:key].length > 0
    recommendation.select! { |x| opts[:key].member?(x.key) }
  end
  if opts[:type] && opts[:type].length > 0
    recommendation.select! { |x| (opts[:type] & x.action.map { |y| y.class.wsdl_name }).length > 0 }
  end
  all_tasks = []

  # We do recommendations in chunks, because VC can't process more than a
  # few migrations anyway and this way we get more fair queuing, less
  # timeouts of long queued migrations and a better RVC user experience
  # due to less queued tasks at a time. It would otherwise be easy to
  # exceed the screensize with queued tasks
  while recommendation.length > 0
    recommendation.pop(20).each do |r|
      targets = r.action.map { |y| y.target }
      recent_tasks = pc.collectMultiple(targets, 'recentTask')
      prev_tasks = targets.map { |x| recent_tasks[x]['recentTask'] }
      cluster.ApplyRecommendation(:key => r.key)
      recent_tasks = pc.collectMultiple(targets, 'recentTask')
      tasks = targets.map { |x| recent_tasks[x]['recentTask'] }
      all_tasks += (tasks.flatten - prev_tasks.flatten)
    end

    progress all_tasks
  end
end

#apply_settings(obj, port_spec) ⇒ Object



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

def apply_settings obj, port_spec
  if obj.is_a?(VIM::DistributedVirtualSwitch)
    tasks [obj], :ReconfigureDvs, :spec =>{:defaultPortConfig => port_spec,
                                           :configVersion => obj.config.configVersion}
  elsif obj.is_a?(VIM::DistributedVirtualPortgroup)
    vds = obj.config.distributedVirtualSwitch
    collapse_inheritance vds.config.defaultPortConfig, port_spec
    tasks [obj], :ReconfigureDVPortgroup,
                 :spec => { :defaultPortConfig => port_spec,
                            :configVersion => obj.config.configVersion}
  elsif obj.is_a?(VIM::DistributedVirtualPort)
    config = obj.rvc_parent.config
    vds = config.distributedVirtualSwitch
    collapse_inheritance config.defaultPortConfig, port_spec
    tasks [vds], :ReconfigureDVPort, :port => [{ :key => obj.key,
                                                 :operation => 'edit',
                                                 :setting => port_spec }]
    obj.invalidate vds
  end
end

#authenticate(vm, opts) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/rvc/modules/vm_guest.rb', line 9

def authenticate vm, opts
  auth = ((@auths ||= {})[vm] ||= {})[opts[:username]]

  if opts[:password].nil? or opts[:password].empty?
    opts[:password] = ask("password: ") { |q| q.echo = false }
  end

  auth = VIM.NamePasswordAuthentication(
    :username => opts[:username],
    :password => opts[:password],
    :interactiveSession => opts[:interactive_session]
  )

  @auths[vm][opts[:username]] = auth
  begin
    check_auth vm, opts
  rescue
    clear_auth vm, opts
    err "Could not authenticate: #{$!}"
  end
end

#block(obj) ⇒ Object



189
190
191
# File 'lib/rvc/modules/vds.rb', line 189

def block obj
  apply_settings obj, { :blocked => { :value => true, :inherited => false } }
end

#bootconfig(vm, opts) ⇒ Object



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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/rvc/modules/vm.rb', line 216

def bootconfig vm, opts

  if opts[:show]
    pp vm.config.bootOptions
    return
  end

  cur_delay        = vm.config.bootOptions.bootDelay
  cur_retrydelay   = vm.config.bootOptions.bootRetryDelay
  cur_retryenabled = vm.config.bootOptions.bootRetryEnabled

  if opts[:delay] and opts[:delay] != cur_delay
    new_delay = opts[:delay]
  else
    new_delay = cur_delay
  end

  if opts[:retrydelay] and opts[:retrydelay] != cur_retrydelay
    new_retrydelay = opts[:retrydelay]
    new_retryenabled = true
  else
    new_retrydelay = cur_retrydelay
  end

  if opts[:enablebootretry]
    new_retryenabled = true
  elsif opts[:disablebootretry]
    new_retryenabled = false
  else
    new_retryenabled = cur_retryenabled
  end

  spec = { :bootOptions => {
    :bootDelay => new_delay,
    :bootRetryDelay => new_retrydelay,
    :bootRetryEnabled => new_retryenabled,
    }
  }

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

#cd(obj) ⇒ Object



124
125
126
127
128
129
# File 'lib/rvc/modules/basic.rb', line 124

def cd obj
  shell.fs.cd(obj)
  shell.fs.marks[''] = [find_ancestor(RbVmomi::VIM::Datacenter)].compact
  shell.fs.marks['@'] = [find_ancestor(RbVmomi::VIM)].compact
  shell.fs.delete_numeric_marks
end

#change_devices_connectivity(devs, connected) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/rvc/modules/device.rb', line 276

def change_devices_connectivity devs, connected
  if dev = devs.find { |dev| dev.connectable.nil? }
    err "#{dev.name} is not connectable."
  end

  vm_devs = devs.group_by(&:rvc_vm)
  tasks = vm_devs.map do |vm,my_devs|
    device_changes = my_devs.map do |dev|
      dev = dev.dup
      dev.connectable = dev.connectable.dup
      dev.connectable.connected = connected
      dev.connectable.startConnected = connected
      { :operation => :edit, :device => dev }
    end
    spec = { :deviceChange => device_changes }
    vm.ReconfigVM_Task(:spec => spec)
  end

  progress tasks
end

#check_auth(vm, opts) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rvc/modules/vm_guest.rb', line 38

def check_auth vm, opts
  auth = get_auth vm, opts

  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager

  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :authManager
  guestOperationsManager.authManager.ValidateCredentialsInGuest(
    :vm => vm,
    :auth => auth
  )
end

#check_installedObject



46
47
48
# File 'lib/rvc/modules/vmrc.rb', line 46

def check_installed
  File.exists? local_vmrc_dir(ARCH)
end

#check_known_hosts(host, peer_public_key) ⇒ Object



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

def check_known_hosts host, peer_public_key
  known_hosts = RVC::KnownHosts.new
  result, arg = known_hosts.verify 'vim', host, peer_public_key.to_s

  if result == :not_found
    puts "The authenticity of host '#{host}' can't be established."
    puts "Public key fingerprint is #{arg}."
    err "Connection failed" unless agree("Are you sure you want to continue connecting (y/n)? ", true)
    puts "Warning: Permanently added '#{host}' (vim) to the list of known hosts"
    known_hosts.add 'vim', host, peer_public_key.to_s
  elsif result == :mismatch
    err "Public key fingerprint for host '#{host}' does not match #{known_hosts.filename}:#{arg}."
  elsif result == :ok
  else
    err "Unexpected result from known_hosts check"
  end
end

#chmod(vm, opts) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rvc/modules/vm_guest.rb', line 115

def chmod vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  opts[:permissions] = opts[:permissions].to_i(8) if opts[:permissions]

  auth = get_auth vm, opts

  fileManager.
    ChangeFileAttributesInGuest(
      :vm => vm,
      :auth => auth,
      :guestFilePath => opts[:guest_path],
      :fileAttributes => VIM.GuestPosixFileAttributes(
        :groupId => opts[:group_id],
        :ownerId => opts[:owner_id],
        :permissions => opts[:permissions]
      )
    )
end

#choose_vmrc_version(vim_version) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/rvc/modules/vmrc.rb', line 61

def choose_vmrc_version vim_version
  if vim_version >= '5.1.0'
    '5.0.0'
  else
    '3.0.0'
  end
end

#clear_auth(vm, opts) ⇒ Object



86
87
88
89
90
91
92
93
94
# File 'lib/rvc/modules/vm_guest.rb', line 86

def clear_auth vm, opts
  unless @auths.nil? or vm.nil?
    if @auths.member? vm
      @auths[vm].delete opts[:username]
      @auths.delete vm if @auths[vm].empty?
      @auths = nil if @auths.empty?
    end
  end
end

#clone(src, dst, opts) ⇒ Object



522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
# File 'lib/rvc/modules/vm.rb', line 522

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[:power_on],
                          })
  progress [task]
end

#coerce_str(type, v) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rvc/modules/find.rb', line 116

def coerce_str type, v
  fail "expected String, got #{v.class}" unless v.is_a? String
  if type <= Integer then v.to_i
  elsif type == Float then v.to_f
  elsif type == TrueClass or type == FalseClass then v == 'true'
  elsif type == NilClass then v == 'nil' ? nil : !nil
  elsif v == 'nil' then nil
  elsif type == String then v
  elsif type.respond_to? :parse then type.parse(v)
  else fail "unexpected coercion type #{type}"
  end
end

#collapse_inheritance(default_spec, port_spec) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/rvc/modules/vds.rb', line 99

def collapse_inheritance default_spec, port_spec
  inherited = true
  if port_spec.is_a? Hash
    port_spec.keys.each do |key|
      if key == :inherited then next end
      default_child = default_spec.send key
      child = port_spec[key]
      child_inheritance = collapse_inheritance default_child, child
      inherited = inherited && child_inheritance
    end
    if port_spec.has_key?(:inherited)
      port_spec[:inherited] = inherited
    end
    inherited
  else
    if default_spec == port_spec
      true
    else
      false
    end
  end
end

#configure_ha(cluster, opts) ⇒ Object



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

def configure_ha cluster, opts
  spec = VIM::ClusterConfigSpecEx(
    :dasConfig => {
      :enabled => !opts[:disabled],
    }
  )
  one_progress(cluster.ReconfigureComputeResource_Task :spec => spec, :modify => true)
end

#connect(devs) ⇒ Object



34
35
36
37
38
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/rvc/modules/vim.rb', line 34

def connect uri, opts
  uri = RVC::URIParser.parse uri unless uri.is_a? URI

  username = uri.user || ENV['RBVMOMI_USER']
  password = uri.password || ENV['RBVMOMI_PASSWORD']
  host = uri.host
  port = uri.port || 443
  certdigest = opts[:certdigest] # TODO put in URI
  bad_cert = false

  vim = nil
  loop do
    begin
      vim = RbVmomi::VIM.new :host => host,
                             :port => port,
                             :path => '/sdk',
                             :ns => 'urn:vim25',
                             :rev => (opts[:rev]||'4.0'),
                             :ssl => true,
                             :insecure => bad_cert
      break
    rescue OpenSSL::SSL::SSLError
      # We'll check known_hosts next
      raise if bad_cert
      bad_cert = true
    rescue Errno::EHOSTUNREACH, SocketError
      err $!.message
    end
  end

  if bad_cert
    peer_public_key = vim.http.peer_cert.public_key
    # if user specified a hash on the commandline, verify against that
    if certdigest
      if certdigest != Digest::SHA2.hexdigest(peer_public_key.to_s())
        err "Bad certificate digest specified for #{host}!"
      end
    else
      # Fall back to SSH-style known_hosts
      check_known_hosts(host, peer_public_key)
    end
  end

  unless opts[:rev]
    # negotiate API version
    rev = vim.serviceContent.about.apiVersion
    env_rev = ENV['RVC_VIMREV']
    if env_rev && env_rev.to_f == 0
      vim.rev = env_rev
    else
      vim.rev = [rev, env_rev || '5.1'].min
    end
  end

  isVC = vim.serviceContent.about.apiType == "VirtualCenter"

  # authenticate
  if username == nil
    if isVC
      isLinux = vim.serviceContent.about.osType == "linux-x64"
      username = isLinux ? 'root' : 'Administrator'
    else
      username = 'root'
    end
    puts "Using default username #{username.inspect}."
  end

  # If we already have a password, then don't bother querying if we have an OSX
  # keychain entry for it. If we have either of them, use it.
  # So will use command line first, then ENV, then keychain on OSX, then prompt.
  loaded_from_keychain = nil
  password = keychain_password( username ,  host ) if password.nil?
  if not password.nil?
    loaded_from_keychain = password
  end

  if opts[:cookie]
    vim.cookie = opts[:cookie]
  else
    password_given = password != nil
    loop do
      begin
        password = prompt_password unless password_given
        vim.serviceContent.sessionManager.Login :userName => username,
                                                :password => password
        break
      rescue RbVmomi::VIM::InvalidLogin
        err $!.message if password_given
      end
    end
  end

  Thread.new do
    while true
      sleep 600
      vim.serviceInstance.CurrentTime
    end
  end

  # if we got to here, save the password, unless we loaded it from keychain
  save_keychain_password( username , password , host ) unless loaded_from_keychain == password

  # Stash the address we used to connect so VMRC can use it.
  vim.define_singleton_method(:_host) { host }

  conn_name = host.dup
  conn_name = "#{conn_name}:1" if shell.connections.member? conn_name
  conn_name.succ! while shell.connections.member? conn_name

  shell.connections[conn_name] = vim
end

#connect_serial_uri(dev, uri, opts) ⇒ Object



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

def connect_serial_uri dev, uri, opts
  err "must specify --client or --server" unless opts[:client] || opts[:server]
  direction = opts[:client] ? 'client' : 'server'
  dev = dev.dup
  dev.backing = VIM::VirtualSerialPortURIBackingInfo(:direction => direction, :serviceURI => uri)
  spec = { :deviceChange => [ { :operation => :edit, :device => dev } ] }
  progress [dev.rvc_vm.ReconfigVM_Task(:spec => spec)]
end

#counter(counter_name, obj) ⇒ Object



267
268
269
270
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
299
300
301
302
303
304
305
306
# File 'lib/rvc/modules/perf.rb', line 267

def counter counter_name, obj
  vim = obj ? obj._connection : lookup_single('~@')
  pm = vim.serviceContent.perfManager
  counter = pm.perfcounter_hash[counter_name] or err "no such counter #{counter_name.inspect}"

  active_intervals = pm.active_intervals
  active_intervals_text = lambda do |level|
    xs = active_intervals[level]
    xs.empty? ? 'none' : xs.map(&:name).map(&:inspect) * ', '
  end

  puts "Label: #{counter.nameInfo.label}"
  puts "Summary: #{counter.nameInfo.summary}"
  puts "Unit label: #{counter.unitInfo.label}"
  puts "Unit summary: #{counter.unitInfo.summary}"
  puts "Rollup type: #{counter.rollupType}"
  puts "Stats type: #{counter.statsType}"
  puts "Level: #{counter.level}"
  puts " Enabled in intervals: #{active_intervals_text[counter.level]}"
  puts "Per-device level: #{counter.perDeviceLevel}"
  puts " Enabled in intervals: #{active_intervals_text[counter.perDeviceLevel]}"

  if obj
    interval = pm.provider_summary(obj).refreshRate
    if interval == -1
      # Object does not support real time stats
      interval = nil
    end
    puts "Real time interval: #{interval || 'N/A'}"
    metrics = pm.QueryAvailablePerfMetric(:entity => obj, :intervalId => interval)
    metrics.select! { |x| x.counterId == counter.key }
    instances = metrics.map(&:instance).reject(&:empty?)
    unless instances.empty?
      puts "Instances:"
      instances.map do |x|
        puts "  #{x}"
      end
    end
  end
end

#counters(obj) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/rvc/modules/perf.rb', line 222

def counters obj
  pm = obj._connection.serviceContent.perfManager
  interval = pm.provider_summary(obj).refreshRate
  if interval == -1
    # Object does not support real time stats
    interval = nil
  end

  active_intervals = pm.active_intervals
  active_intervals_text = lambda do |level|
    return '' unless level
    xs = active_intervals[level]
    return 'none' if xs.empty?
    xs.map { |x| x.name.match(/Past (\w+)/)[1] } * ','
  end

  metrics = pm.QueryAvailablePerfMetric(
    :entity => obj, 
    :intervalId => interval)
  available_counters = metrics.map(&:counterId).uniq.
                               map { |id| pm.perfcounter_idhash[id] }

  groups = available_counters.group_by { |counter| counter.groupInfo }

  table = Terminal::Table.new
  table.add_row ["Name", "Description", "Unit", "Level", "Active Intervals"]
  groups.sort_by { |group,counters| group.key }.each do |group,counters|
    table.add_separator
    table.add_row [{ :value => group.label, :colspan => 5}]
    table.add_separator
    counters.sort_by(&:name).each do |counter|
      table.add_row [counter.name, counter.nameInfo.label, counter.unitInfo.label,
                     counter.level, active_intervals_text[counter.level]]
    end
  end
  puts(table)
end

#create(name, parent, opts) ⇒ Object



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
184
185
186
187
188
# File 'lib/rvc/modules/vm.rb', line 154

def create dest, opts
  err "must specify resource pool (--pool)" unless opts[:pool]
  err "must specify datastore (--datastore)" unless opts[:datastore]
  err "memory must be a multiple of 4MB" unless opts[:memory] % 4 == 0
  vmFolder, name = *dest
  datastore_path = "[#{opts[:datastore].name}]"
  config = {
    :name => name,
    :guestId => opts[:guest_id],
    :files => { :vmPathName => datastore_path },
    :numCPUs => opts[:cpucount],
    :memoryMB => opts[:memory],
    :deviceChange => [
      {
        :operation => :add,
        :device => VIM.VirtualCdrom(
          :key => -2,
          :connectable => {
            :allowGuestControl => true,
            :connected => true,
            :startConnected => true,
          },
          :backing => VIM.VirtualCdromIsoBackingInfo(
            :fileName => datastore_path
          ),
          :controllerKey => 200,
          :unitNumber => 0
        )
      }
    ],
  }
  vmFolder.CreateVM_Task(:config => config,
                         :pool => opts[:pool],
                         :host => opts[:host]).wait_for_completion
end

#create_portgroup(vds, name, opts) ⇒ Object



47
48
49
50
51
# File 'lib/rvc/modules/vds.rb', line 47

def create_portgroup vds, name, opts
  tasks [vds], :AddDVPortgroup, :spec => [{ :name => name,
                                            :type => opts[:type],
                                            :numPorts => opts[:num_ports] }]
end

#create_vds(dest, opts) ⇒ Object



60
61
62
63
64
65
# File 'lib/rvc/modules/vds.rb', line 60

def create_vds dest, opts
  folder, name = *dest
  tasks [folder], :CreateDVS, :spec => { :configSpec => { :name => name },
                                         :productInfo => {
                                           :version => opts[:vds_version] } }
end

#create_vmknic(portgroup, hosts, opts) ⇒ Object



642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/rvc/modules/vds.rb', line 642

def create_vmknic portgroup, hosts, opts
  if !portgroup.is_a?(VIM::DistributedVirtualPortgroup)
    err "Legacy switches not supported yet"
  end
  hosts.each do |host|
    ns = host.configManager.networkSystem 
    vmknic_name = ns.AddVirtualNic(
      :portgroup => "", 
      :nic => {
        :ip => {
          :dhcp => true
        },
        :distributedVirtualPort => VIM::DistributedVirtualSwitchPortConnection(
          :portgroupKey => portgroup.key, 
          :switchUuid => portgroup.config.distributedVirtualSwitch.uuid,
        )
      }
    )
    puts "Host #{host.name}: Added vmknic #{vmknic_name}"
  end
end

#cur_auth_mgrObject



23
24
25
26
# File 'lib/rvc/modules/role.rb', line 23

def cur_auth_mgr
  conn = shell.fs.cur._connection
  conn.serviceContent.authorizationManager
end

#debugObject



108
109
110
111
112
113
114
# File 'lib/rvc/modules/basic.rb', line 108

def debug
  debug = shell.debug = !shell.debug
  shell.connections.each do |name,conn|
    conn.debug = debug if conn.respond_to? :debug
  end
  puts "debug mode #{debug ? 'en' : 'dis'}abled"
end

#delete(objs) ⇒ Object



87
88
89
90
91
# File 'lib/rvc/modules/role.rb', line 87

def delete name, opts
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{name.inspect}" unless role
  cur_auth_mgr.RemoveAuthorizationRole :roleId => role.roleId, :failIfUsed => opts[:force]
end

#deltaize_disks(vm) ⇒ Object



545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
# File 'lib/rvc/modules/vm.rb', line 545

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

#describe(snapshot, description) ⇒ Object



71
72
73
# File 'lib/rvc/modules/snapshot.rb', line 71

def describe snapshot, description
  snapshot.find_tree.snapshot.RenameSnapshot :description => description
end

#destroy(objs) ⇒ Object



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

def destroy objs
  tasks objs, :Destroy
end

#disconnect(devs) ⇒ Object



131
132
133
# File 'lib/rvc/modules/host.rb', line 131

def disconnect hosts
  tasks hosts, :DisconnectHost
end

#download(file, local_path) ⇒ Object



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

def download url_str, dest
  puts "Downloading VMRC..."

  url = URI.parse(url_str)

  http = if ENV['http_proxy']
    proxy_uri = URI.parse(ENV['http_proxy'])
    proxy_user, proxy_pass = proxy_uri.userinfo.split(/:/) if proxy_uri.userinfo
    Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_user, proxy_pass)
  else
    Net::HTTP
  end

  begin
    File.open(dest, 'wb') do |io|
      res = http.start(url.host, url.port) do |http|
        http.get(url.path) do |segment|
          io.write segment
        end
      end
      res.value
    end
  rescue Exception
    err "Error downloading VMRC: #{$!.class}: #{$!.message}"
  end
end

#download_file(vm, opts) ⇒ Object



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/rvc/modules/vm_guest.rb', line 252

def download_file vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  download_url = fileManager.
    InitiateFileTransferFromGuest(
      :vm => vm,
      :auth => auth,
      :guestFilePath => opts[:guest_path]
    ).url

  download_uri = URI.parse(download_url.gsub /http(s?):\/\/\*:[0-9]*/, "")
  download_path = "#{download_uri.path}?#{download_uri.query}"

  http_download vm._connection, download_path, opts[:local_path]
end

#edit(file) ⇒ Object



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

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



104
105
106
107
108
109
110
111
112
# File 'lib/rvc/modules/host.rb', line 104

def enter_maintenance_mode hosts, opts
  if opts[:no_wait]
    hosts.each do |host|
      host.EnterMaintenanceMode_Task(:timeout => opts[:timeout], :evacuatePoweredOffVms => opts[:evacuate_powered_off_vms])
    end
  else
    tasks hosts, :EnterMaintenanceMode, :timeout => opts[:timeout], :evacuatePoweredOffVms => opts[:evacuate_powered_off_vms]
  end
end

#evacuate(src, dsts, opts) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/rvc/modules/host.rb', line 60

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

#events(obj, opts) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/rvc/modules/basic.rb', line 376

def events obj, opts
  err "'events' not supported at this level" unless obj.respond_to?(:_connection)
  manager = obj._connection.serviceContent.eventManager
  @event_details ||= Hash[manager.collect("description.eventInfo").first.collect { |d| [d.key, d] }]

  spec = VIM::EventFilterSpec(:entity => VIM::EventFilterSpecByEntity(:entity => obj, :recursion => "all"))

  collector = manager.CreateCollectorForEvents(:filter => spec)
  collector.SetCollectorPageSize(:maxCount => opts[:lines])
  collector.latestPage.reverse.each do |event|
    time = event.createdTime.localtime.strftime("%m/%d/%Y %I:%M %p")
    category = @event_details[event.class.to_s].category
    puts "[#{time}] [#{category}] #{event.fullFormattedMessage.strip}"
  end
ensure
  collector.DestroyCollector if collector
end

#execute(*args) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/rvc/modules/esxcli.rb', line 71

def execute *args
  host_path = args.shift or err "host argument required"
  host = lookup_single! host_path, VIM::HostSystem
  o = lookup_esxcli host, args

  case o
  when VIM::EsxcliCommand
    cmd = o
    parser = cmd.option_parser
    begin
      opts = parser.parse args
    rescue Trollop::CommandlineError
      err "error: #{$!.message}"
    rescue Trollop::HelpNeeded
      parser.educate
      return
    end
    begin
      opts.reject! { |k,v| !opts.member? :"#{k}_given" }
      result = cmd.call(opts)
    rescue RbVmomi::Fault
      puts "#{$!.message}"
      puts "cause: #{$!.faultCause}" if $!.respond_to? :faultCause and $!.faultCause
      $!.faultMessage.each { |x| puts x } if $!.respond_to? :faultMessage
      $!.errMsg.each { |x| puts "error: #{x}" } if $!.respond_to? :errMsg
    end
    output_formatted cmd, result
  when VIM::EsxcliNamespace
    ns = o
    unless ns.commands.empty?
      puts "Available commands:"
      ns.commands.each do |k,v|
        puts "#{k}: #{v.cli_info.help}"
      end
      puts unless ns.namespaces.empty?
    end
    unless ns.namespaces.empty?
      puts "Available namespaces:"
      ns.namespaces.each do |k,v|
        puts "#{k}: #{v.cli_info.help}"
      end
    end
  end
end

#exit_maintenance_mode(hosts, opts) ⇒ Object



121
122
123
# File 'lib/rvc/modules/host.rb', line 121

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

#extra_config(vm, regexes) ⇒ Object



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

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

#extract(src, dst) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/rvc/modules/vmrc.rb', line 179

def extract src, dst
  puts "Installing VMRC..."
  FileUtils.mkdir_p dst
  Zip::ZipFile.open(src) do |zf|
    zf.each do |e|
      dst_filename = File.join(dst, e.name)
      case e.ftype
      when :file
        FileUtils.mkdir_p File.dirname(dst_filename)
        zf.extract e.name, dst_filename
        File.chmod(e.unix_perms, dst_filename) if e.unix_perms
      when :directory
        FileUtils.mkdir_p dst_filename
      else
        $stderr.puts "unknown file type #{e.ftype}"
      end
    end
  end
end

#fields(obj) ⇒ Object



400
401
402
403
404
405
406
407
408
409
# File 'lib/rvc/modules/basic.rb', line 400

def fields obj
  obj.class.ancestors.select { |x| x.respond_to? :fields }.each do |klass|
    fields = klass.fields false
    next if fields.empty?
    puts "Fields on #{klass}:"
    fields.each do |name,field|
      puts " #{name}: #{field.summary}"
    end
  end
end

#find(args, opts) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/rvc/modules/vm.rb', line 325

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



131
132
133
# File 'lib/rvc/modules/basic.rb', line 131

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

#find_interval(pm, start) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/rvc/modules/perf.rb', line 46

def find_interval pm, start
  now = Time.now
  ago = now - start

  if ago < 3600
    #puts "Using realtime interval, period = 20 seconds."
    interval_id = 20
    display_timefmt = DISPLAY_TIMEFMT[:realtime]
  else
    intervals = pm.historicalInterval
    interval = intervals.find { |x| now - x.length < start }
    err "start time is too long ago" unless interval
    #puts "Using historical interval #{interval.name.inspect}, period = #{interval.samplingPeriod} seconds."
    interval_id = interval.samplingPeriod
    display_timefmt = DISPLAY_TIMEFMT[interval.key]
  end

  return interval_id, display_timefmt
end

#find_items(terms = nil, roots = nil, types = nil) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
# File 'lib/rvc/modules/find.rb', line 54

def find_items terms = nil, roots = nil, types = nil
  roots ||= ['.']
  terms ||= []

  types.each { |t| terms <<  "+type=#{t}" }
  roots = roots.map { |x| lookup x }.flatten(1)
  terms = terms.map { |x| term x[1..-1] }

  candidates = leaves roots, types
  results = candidates.select { |r| terms.all? { |t| t[r] } }
end

#find_orphans(ds, 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
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/rvc/modules/datastore.rb', line 109

def find_orphans ds, opts
  pc = ds._connection.serviceContent.propertyCollector
  vms = ds.vm
  
  puts "Collecting file information about #{vms.length} VMs ... (this may take a while)"
  dsName = ds.name
  vmFiles = pc.collectMultiple vms, 'layoutEx.file'
  
  puts "Collecting file information on datastore '#{dsName}' ..."
  dsBrowser = ds.browser
  result = dsBrowser.SearchDatastore_Task(
    :datastorePath => "[#{dsName}] ",
    :searchSpec => {
      :details => {
        :fileType => true,
        :fileSize => false,
        :fileOwner => false,
        :modification => false
      }
    }
  ).wait_for_completion
  dsDirectories = result.file.grep(RbVmomi::VIM::FolderFileInfo).map(&:path)
  
  puts "Checking for any VMs that got added inbetween ..."
  addedVms = ds.vm - vms
  if addedVms.length > 0
    puts "Processing #{addedVms.length} new VMs ..."
    vmFiles.merge!(pc.collectMultiple addedVms, 'layoutEx.file')
  end
  
  begin 
    perDSUsage = pc.collectMultiple vms, 'storage.perDatastoreUsage'
  rescue RbVmomi::Fault => ex
    if ex.fault.is_a?(RbVmomi::VIM::ManagedObjectNotFound)
      vms = vms - [ex.fault.obj]
      retry
    end
    perDSUsage = []
    raise
  end

  puts "Cross-referencing VM files with files on datastore '#{dsName}' ..."
  vmFilenameHash = Hash[vmFiles.map do |vm, info| 
    [
      vm, 
      info["layoutEx.file"].map{|x| x.name}.select{|x| x =~ /^\[#{dsName}\] /}.map{|x| x.gsub(/^\[#{dsName}\] /, '')}
    ]
  end]
  filenames = vmFilenameHash.values.flatten(1)
  vmDirectories = filenames.map{ |x| x.split('/').first }.uniq
  orphanDirectories = (dsDirectories - vmDirectories).reject { |x| x =~ /^\./ }
  puts "Found #{orphanDirectories.length} potentially orphaned directories"
  
  puts "Composing list of potentially orphaned files ... (this may take a while)"
  data = orphanDirectories.map do |dir|
    begin 
      result = dsBrowser.SearchDatastoreSubFolders_Task(
        :datastorePath => "[#{dsName}] #{dir}/",
        :searchSpec => {
          :details => {
            :fileType => false,
            :fileSize => true,
            :fileOwner => false,
            :modification => false
          }
        }
      ).wait_for_completion
      files = result.map(&:file).flatten
      dirSize = files.map(&:fileSize).sum
      $stdout.write "."
      $stdout.flush
      [dir, dirSize, files.length]
    rescue 
      puts "failed to search #{dir.inspect}: #{$!.message}"
      nil
    end
  end.compact
  puts
  puts
  
  if data.empty?
    puts "No orphans found"
  else
    puts(Terminal::Table.new do
      data.sort_by { |a| a[1] }.each do |x|
        dir, dirSize, numFiles = x
        self.headings = 'Directory', 'Space Used', '# Files'
        add_row [dir, "#{dirSize.metric}B", numFiles]
      end
    end)
  end

  puts

  totalSize = data.map{|x| x[1]}.sum
  dsSummary = ds.summary
  vmDsUsage = perDSUsage.map{|vm, x| x['storage.perDatastoreUsage'].find{|y| y.datastore == ds}}.reject{|x| x == nil}
  committed = vmDsUsage.map{|x| x.committed}.sum
  unshared = vmDsUsage.map{|x| x.unshared}.sum
  otherSpace = (dsSummary.capacity - dsSummary.freeSpace) - unshared
  puts "Provisioned on Datastore:  #{dsSummary.uncommitted.metric}B"
  puts "Capacity of Datastore: #{dsSummary.capacity.metric}B"
  puts "Free Space on Datastore: #{dsSummary.freeSpace.metric}B"
  puts "VMs Provisioned on Datastore: #{vmDsUsage.map(&:uncommitted).sum.metric}B"
  puts "VMs Used on Datastore: #{committed.metric}B"
  puts "VMs Unshared on Datastore: #{vmDsUsage.map(&:unshared).sum.metric}B"
  puts "Unaccounted space: #{otherSpace.metric}B"
  puts "Total size of detected potential orphans: #{totalSize.metric}B"
  puts

  results = data.map do |dirInfo|
    RbVmomi::VIM::Datastore::FakeDatastoreFolder.new(ds, "#{dirInfo[0]}")
  end
  opts[:mark] ||= "#{dsName}_orphans"
  shell.cmds.mark.mark opts[:mark], results
  puts "Saved results to mark '#{opts[:mark]}'"

  i = 0
  results.each do |r|
    display_path = r.path
    puts "#{i} #{display_path}"
    shell.cmds.mark.mark i.to_s, [r]
    i += 1
  end
end

#find_vmrc(arch, version) ⇒ Object



50
51
52
53
54
55
56
57
58
59
# File 'lib/rvc/modules/vmrc.rb', line 50

def find_vmrc arch, version
  path = if version == '3.0.0'
    basename = ON_WINDOWS ? 'vmware-vmrc.exe' : 'vmware-vmrc'
    File.join(local_vmrc_dir(arch), version, 'plugins', basename)
  else
    fail "VMRC5 not yet supported on win32" if ON_WINDOWS
    File.join(local_vmrc_dir(arch), version, 'vmware-vmrc-5.0', 'run.sh')
  end
  File.exists?(path) && path
end

#find_vmx_files(ds) ⇒ Object



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

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

#get(objs) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/rvc/modules/role.rb', line 44

def get name
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{name.inspect}" unless role
  puts "label: #{role.info.label}"
  puts "summary: #{role.info.summary}"
  puts "privileges: #{role.privilege.sort * ' '}"
end

#get_auth(vm, opts) ⇒ Object



97
98
99
100
101
# File 'lib/rvc/modules/vm_guest.rb', line 97

def get_auth vm, opts
  auth = @auths.fetch(vm).fetch(opts[:username])
ensure
  err "No credentials found. You must authenticate before executing this command." if auth.nil?
end

#get_inherited_config(obj) ⇒ Object



67
68
69
70
71
72
73
74
75
# File 'lib/rvc/modules/vds.rb', line 67

def get_inherited_config obj
  if obj.is_a?(VIM::DistributedVirtualSwitch)
    nil
  elsif obj.is_a?(VIM::DistributedVirtualPortgroup)
    obj.config.distributedVirtualSwitch.config.defaultPortConfig
  elsif obj.is_a?(VIM::DistributedVirtualPort)
    obj.rvc_parent.config.defaultPortConfig
  end
end

#help(input) ⇒ Object



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

def help input
  if input
    cmdpath, args = Shell.parse_input input
    o = shell.cmds.lookup(cmdpath, Namespace) || shell.cmds.lookup(cmdpath)
    RVC::Util.err "invalid command or namespace" unless o
  else
    o = shell.cmds
  end

  case o
  when Command
    o.parser.educate
  when Namespace
    help_namespace o
  end

  # TODO apropos
  puts (<<-EOS)

To see commands in a namespace: help namespace_name
To see detailed help for a command: help namespace_name.command_name
  EOS
end

#help_namespace(ns) ⇒ Object

TODO namespace summaries



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/rvc/modules/basic.rb', line 73

def help_namespace ns
  unless ns.namespaces.empty?
    puts "Namespaces:"
    ns.namespaces.sort_by do |child_name,child|
      HELP_ORDER.index(child_name.to_s) || HELP_ORDER.size
    end.each do |child_name,child|
      puts child_name
    end
  end

  puts unless ns.namespaces.empty? or ns.commands.empty?

  unless ns.commands.empty?
    puts "Commands:"
    ns.commands.sort_by do |cmd_name,cmd|
      HELP_ORDER.index(cmd_name.to_s) || HELP_ORDER.size
    end.each do |cmd_name,cmd|
      help_summary cmd
    end
  end
end

#help_summary(cmd) ⇒ Object



95
96
97
98
99
# File 'lib/rvc/modules/basic.rb', line 95

def help_summary cmd
  aliases = shell.cmds.aliases.select { |k,v| shell.cmds.lookup(v) == cmd }.map(&:first)
  aliases_text = aliases.empty? ? '' : " (#{aliases*', '})"
  puts "#{cmd.name}#{aliases_text}: #{cmd.summary}"
end

#http_path(dc_name, ds_name, path) ⇒ Object



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

def http_path dc_name, ds_name, path
  "/folder/#{URI.escape path}?dcPath=#{URI.escape dc_name}&dsName=#{URI.escape ds_name}"
end

#info(obj) ⇒ Object



264
265
266
267
268
269
270
271
# File 'lib/rvc/modules/basic.rb', line 264

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(dev, iso) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/rvc/modules/device.rb', line 174

def insert_cdrom dev, iso
  vm = dev.rvc_vm
  backing = VIM.VirtualCdromIsoBackingInfo(:fileName => iso.datastore_path)

  spec = {
    :deviceChange => [
      {
        :operation => :edit,
        :device => dev.class.new(
          :key => dev.key,
          :controllerKey => dev.controllerKey,
          :backing => backing)
      }
    ]
  }

  progress [vm.ReconfigVM_Task(:spec => spec)]
end

#installObject



132
133
134
135
136
137
138
139
140
# File 'lib/rvc/modules/vmrc.rb', line 132

def install
  err "No VMRC available for architecture #{ARCH}" unless VMRC_CHECKSUMS.member? ARCH
  zip_filename = "#{local_vmrc_dir(ARCH)}.zip"
  url = vmrc_url ARCH
  download url, zip_filename
  verify zip_filename, VMRC_CHECKSUMS[ARCH]
  extract zip_filename, local_vmrc_dir(ARCH)
  puts "VMRC was installed successfully."
end

#ip(vms) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/rvc/modules/vm.rb', line 466

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

#keychain_password(username, hostname) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/rvc/modules/vim.rb', line 150

def keychain_password username , hostname
   return nil unless RbConfig::CONFIG['host_os'] =~ /^darwin1[01]/

  begin
    require 'osx_keychain'
  rescue LoadError
    return nil
  end

  keychain = OSXKeychain.new
  return keychain["rvc", "#{username}@#{hostname}" ]

end

#kill(vms) ⇒ Object



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

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

#layout(vm) ⇒ Object



311
312
313
314
315
# File 'lib/rvc/modules/vm.rb', line 311

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

#leaves(roots, types = []) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rvc/modules/find.rb', line 66

def leaves roots, types = []
  leaves = Set.new
  new_nodes = roots
  while not new_nodes.empty?
    nodes = new_nodes
    new_nodes = Set.new
    nodes.each do |node|
      if (node.class.traverse? or roots.member? node) and
          (types & (node.field('type') || [])).empty?
        node.children.each { |k,v| v.rvc_link(node, k); new_nodes << v }
      else
        leaves << node
      end
    end
  end
  leaves
end

#listObject



65
66
67
# File 'lib/rvc/modules/mark.rb', line 65

def list
  shell.fs.marks.each { |k,v| puts k }
end

#list_auth(vm) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rvc/modules/vm_guest.rb', line 56

def list_auth vm
  if @auths.nil?
    puts "No credentials available."
    return
  end

  if vm.nil?
    auth_list = @auths
  elsif !@auths.member? vm
    puts "No credentials available."
    return
  else
    auth_list = { vm => @auths[vm] }
  end

  auth_list.each_key do |vmkey|
    puts vmkey.rvc_path_str
    auth_list[vmkey].each_key do |userkey|
      puts "  #{userkey}"
    end
  end
end

#local_vmrc_dir(arch) ⇒ Object



42
43
44
# File 'lib/rvc/modules/vmrc.rb', line 42

def local_vmrc_dir arch
  File.join(Dir.tmpdir, "vmware-vmrc-#{arch}-#{Process.uid}-#{PACKAGE_VERSION}")
end

#logbundles(servers, opts) ⇒ Object



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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/rvc/modules/vim.rb', line 273

def logbundles servers, opts
  vim = single_connection servers
  diagMgr = vim.serviceContent.diagnosticManager
  name = vim.host
  FileUtils.mkdir_p opts[:dest]

  hosts = servers.grep VIM::HostSystem
  include_default = servers.member? vim

  puts "#{Time.now}: Generating log bundles..."
  bundles =
    begin
      diagMgr.GenerateLogBundles_Task(
        :includeDefault => include_default, 
        :host => hosts
      ).wait_for_completion
    rescue VIM::TaskInProgress
      $!.task.wait_for_completion
    end

  dest_path = nil
  bundles.each do |b|
    uri = URI.parse(b.url.sub('*', DEFAULT_SERVER_PLACEHOLDER))
    bundle_name = b.system ? b.system.name : name
    dest_path = File.join(opts[:dest], "#{bundle_name}-" + File.basename(uri.path))
    puts "#{Time.now}: Downloading bundle #{b.url} to #{dest_path}"
    uri.host = vim.http.address if uri.host == DEFAULT_SERVER_PLACEHOLDER
    Net::HTTP.get_response uri do |res|
      File.open dest_path, 'w' do |io|
        res.read_body do |data|
          io.write data
          if $stdout.tty?
            $stdout.write '.'
            $stdout.flush
          end
        end
      end
      puts if $stdout.tty?
    end
  end
  dest_path
end

#lookup_esxcli(host, args) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rvc/modules/esxcli.rb', line 29

def lookup_esxcli host, args
  cur = EsxcliCache[host, :esxcli]
  i = 0
  while i < args.length
    k = args[i]
    if cur.namespaces.member? k
      cur = cur.namespaces[k]
    elsif cur.commands.member? k
      cur = cur.commands[k]
      break
    else
      err "nonexistent esxcli namespace or command #{k.inspect}"
    end
    i += 1
  end
  return cur
end

#ls(obj) ⇒ Object



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
184
185
186
187
188
189
190
191
# File 'lib/rvc/modules/basic.rb', line 144

def ls obj
  if obj.respond_to?(:rvc_ls)
    return obj.rvc_ls
  end

  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.cmds.mark.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 overallStatus),
    }
  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']
    colored_name = status_color name, r['overallStatus']
    puts "#{i} #{colored_name}#{realname && " [#{realname}]"}#{text}"
    r.obj.rvc_link obj, name
    shell.cmds.mark.mark i.to_s, [r.obj]
    i += 1
  end
end

#ls_guest(vm, opts) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/rvc/modules/vm_guest.rb', line 327

def ls_guest vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  files = fileManager.
    ListFilesInGuest(
      :vm => vm,
      :auth => auth,
      :filePath => opts[:guest_path],
      :index => opts[:index],
      :maxResults => opts[:max_results],
      :matchPattern => opts[:match_pattern]
    )

  files.files.each do |file|
    puts file.path
  end

  puts "Remaining: #{files.remaining}" unless files.remaining.zero?

  return files
end

#mark(key, objs) ⇒ Object



32
33
34
35
# File 'lib/rvc/modules/mark.rb', line 32

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

#merge_ranges(ranges) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/rvc/modules/vds.rb', line 307

def merge_ranges(ranges)
  ranges = ranges.sort_by {|r| r.first }
  if !ranges.empty?
    *outages = ranges.shift
  else
    outages = []
  end
  ranges.each do |r|
    lastr = outages[-1]
    if lastr.last >= r.first - 1
      outages[-1] = lastr.first..[r.last, lastr.last].max
    else
      outages.push(r)
    end
  end
  outages
end

#migrate(vms, opts) ⇒ Object



504
505
506
507
508
# File 'lib/rvc/modules/vm.rb', line 504

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



362
363
364
365
# File 'lib/rvc/modules/basic.rb', line 362

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

#mktmpdir(vm, opts) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/rvc/modules/vm_guest.rb', line 147

def mktmpdir vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  dirname = fileManager.
    CreateTemporaryDirectoryInGuest(
      :vm => vm,
      :auth => auth,
      :prefix => opts[:prefix],
      :suffix => opts[:suffix],
      :directoryPath => opts[:guest_path]
    )
  puts dirname
  return dirname
end

#mktmpfile(vm, opts) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/rvc/modules/vm_guest.rb', line 176

def mktmpfile vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  filename = fileManager.
    CreateTemporaryFileInGuest(
      :vm => vm,
      :auth => auth,
      :prefix => opts[:prefix],
      :suffix => opts[:suffix],
      :directoryPath => opts[:guest_path]
    )
  puts filename
  return filename
end

#modify_cpu(vm, opts) ⇒ Object



584
585
586
587
# File 'lib/rvc/modules/vm.rb', line 584

def modify_cpu vm, opts
  spec = { :numCPUs => opts[:num] }
  tasks [vm], :ReconfigVM, :spec => spec
end

#modify_memory(vm, opts) ⇒ Object



596
597
598
599
600
601
# File 'lib/rvc/modules/vm.rb', line 596

def modify_memory vm, opts
  err "VM needs to be off" unless vm.summary.runtime.powerState == 'poweredOff'
  err "memory must be a multiple of 4MB" unless ( opts[:size]  % 4 ) == 0
  spec = { :memoryMB => opts[:size] }
  tasks [vm], :ReconfigVM, :spec => spec
end

#mv(objs) ⇒ Object



321
322
323
324
325
# File 'lib/rvc/modules/basic.rb', line 321

def mv objs
  err "Destination entity missing" unless objs.size > 1
  dst = objs.pop
  progress [dst.MoveIntoFolder_Task(:list => objs)]
end

#mvdir(vm, opts) ⇒ Object



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/rvc/modules/vm_guest.rb', line 387

def mvdir vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  fileManager.
    MoveDirectoryInGuest(
      :vm => vm,
      :auth => auth,
      :srcDirectoryPath => opts[:src_guest_path],
      :dstDirectoryPath => opts[:dst_guest_path]
    )
end

#mvfile(vm, opts) ⇒ Object



413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/rvc/modules/vm_guest.rb', line 413

def mvfile vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  fileManager.
    MoveFileInGuest(
      :vm => vm,
      :auth => auth,
      :srcFilePath => opts[:src_guest_path],
      :dstFilePath => opts[:dst_guest_path],
      :overwrite => opts[:overwrite]
    )
end

#off(vm) ⇒ Object



43
44
45
# File 'lib/rvc/modules/vm.rb', line 43

def off vms
  tasks vms, :PowerOffVM
end

#on(vms) ⇒ Object



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

def on vms
  tasks vms, :PowerOnVM
end

#output_formatted(cmd, result) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/rvc/modules/esxcli.rb', line 119

def output_formatted cmd, result
  hints = Hash[cmd.cli_info.hints]
  formatter = hints['formatter']
  formatter = "none" if formatter == ""
  sym = :"output_formatted_#{formatter}"
  if respond_to? sym
    send sym, result, cmd.cli_info, hints
  else
    puts "Unknown formatter #{formatter.inspect}"
    pp result
  end
end

#output_formatted_none(result, info, hints) ⇒ Object



132
133
134
# File 'lib/rvc/modules/esxcli.rb', line 132

def output_formatted_none result, info, hints
  pp result if result != true
end

#output_formatted_simple(result, info, hints) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rvc/modules/esxcli.rb', line 136

def output_formatted_simple result, info, hints
  case result
  when Array
    result.each do |r|
      output_formatted_simple r, info, hints
      puts
    end
  when RbVmomi::BasicTypes::DataObject
    prop_descs = result.class.ancestors.
                        take_while { |x| x != RbVmomi::BasicTypes::DataObject &&
                                         x != VIM::DynamicData }.
                        map(&:props_desc).flatten(1)
    prop_descs.each do |desc|
      print "#{desc['name']}: "
      pp result.send desc['name']
    end
  else
    pp result
  end
end

#output_formatted_table(result, info, hints) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/rvc/modules/esxcli.rb', line 161

def output_formatted_table result, info, hints
  if result.empty?
    puts "Empty result"
    return
  end

  columns =
    if hints.member? 'table-columns'
      hints['table-columns'].split ','
    elsif k = hints.keys.find { |k| k =~ /^fields:/ }
      hints[k].split ','
    else []
    end
  ordering = columns.map { |x| table_key x }

  units = Hash[hints.select { |k,v| k =~ /^units:/ }.map { |k,v| [table_key(k.match(/[^.]+$/).to_s), v] }]

  table = Terminal::Table.new :headings => columns
  result.each do |r|
    row = []
    r.class.full_props_desc.each do |desc|
      name = desc['name']
      key = table_key name
      next unless idx = ordering.index(key)
      val = r.send name
      unit = units[key]
      row[idx] =
        case unit
        when nil then val
        when '%' then "#{val}#{unit}"
        else "#{val} #{unit}"
        end
    end
    table.add_row row
  end
  puts table
end

#permissions(name) ⇒ Object



58
59
60
61
62
63
64
65
66
67
# File 'lib/rvc/modules/role.rb', line 58

def permissions name
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{name.inspect}" unless role
  cur_auth_mgr.RetrieveRolePermissions(:roleId => role.roleId).each do |perm|
    flags = []
    flags << 'group' if perm[:group]
    flags << 'propagate' if perm[:propagate]
    puts " #{perm[:principal]}#{flags.empty? ? '' : " (#{flags * ', '})"}: #{perm.entity.name}"
  end
end

#pick_controller(vm, controller, controller_classes) ⇒ Object



297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/rvc/modules/device.rb', line 297

def pick_controller vm, controller, controller_classes
  existing_devices, = vm.collect 'config.hardware.device'

  controller ||= existing_devices.find do |dev|
    controller_classes.any? { |klass| dev.is_a? klass } &&
      dev.device.length < 2
  end
  err "no suitable controller found" unless controller

  used_unit_numbers = existing_devices.select { |dev| dev.controllerKey == controller.key }.map(&:unitNumber)
  unit_number = (used_unit_numbers.max||-1) + 1

  [controller, unit_number]
end

#ping(vm) ⇒ Object



455
456
457
458
# File 'lib/rvc/modules/vm.rb', line 455

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

#plot(counter_name, objs, opts) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/rvc/modules/perf.rb', line 82

def plot counter_name, objs, opts
  require_gnuplot
  vim = single_connection objs
  pm = vim.serviceContent.perfManager
  group_key, counter_key, rollup_type = counter_name.split('.', 3)

  now = Time.now
  opts[:end] ||= now
  opts[:start] ||= opts[:end] - 1800

  err "end time is in the future" unless opts[:end] <= Time.now

  interval_id, display_timefmt = find_interval pm, opts[:start]

  all_counters = Hash[pm.perfCounter.map { |x| [x.key, x] }]

  metrics = pm.QueryAvailablePerfMetric(
    :entity => objs.first,
    :interval => interval_id)

  metric = metrics.find do |metric|
    counter = all_counters[metric.counterId]
    counter.groupInfo.key == group_key &&
      counter.nameInfo.key == counter_key
  end or err "counter #{group_key}.#{counter_key} was not found in the #{interval_id}s interval"
  counter = all_counters[metric.counterId]

  specs = objs.map do |obj|
    {
      :entity => obj,
      :metricId => [metric],
      :intervalId => interval_id,
      :startTime => opts[:start],
      :endTime => opts[:end],
      :format => 'csv',
    }
  end

  with_gnuplot(true) do |gp|
    plot = Gnuplot::Plot.new(gp) do |plot|
      if objs.size == 1
        plot.title "#{counter_name} on #{objs[0].name}"
      else
        plot.title counter_name
      end

      plot.ylabel counter.unitInfo.label
      plot.xlabel "Time"
      plot.terminal 'dumb' if opts[:terminal]

      plot.set 'xdata', 'time'
      plot.set 'format', "x '#{display_timefmt}'"
      plot.set 'timefmt', TIMEFMT.inspect

      if counter.unitInfo.key == 'percent'
        plot.set 'yrange', '[0:100]'
      end

      plot.data = retrieve_datasets pm, counter, specs
    end
    gp.puts
  end
end

#prompt_passwordObject



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

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

#quitObject



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

def quit
  exit
end

#rdp(vms, h) ⇒ Object



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/rvc/modules/vm.rb', line 429

def rdp vms, h
  resolution = h[:resolution]
  if !resolution
    resolution = $rdpResolution ? $rdpResolution : '1024x768'  
  end
  vms.each do |vm|
    ip = vm_ip vm

    begin
      timeout(1) { TCPSocket.new ip, 3389; up = true }
    rescue
      puts "#{vm.name}: Warning: Looks like the RDP port is not responding"
    end
    
    cmd = "rdesktop -u '#{h[:username]}' -p '#{h[:password]}' -g#{resolution} #{ip} >/dev/null 2>&1 &"
    system(cmd)
  end
end

#reachable_ip(host) ⇒ Object



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

def reachable_ip host
  ips = host.collect('config.network.vnic')[0].map { |x| x.spec.ip.ipAddress }
  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



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

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

  if opts[:wait]
    puts "Waiting for hosts to reboot ..."
    # There is no proper way to wait for a host to reboot, so we
    # implement a heuristic that is close enough:
    # First we wait for a moment to give the host time to actually
    # disconnect. Then we just wait for it to be responding again.
    sleep 3 * 60

    hosts.each do |host|
      # We could use the property collector here to wait for an
      # update instead of polling.
      while !(host.runtime.connectionState == "connected" && host.runtime.powerState == "poweredOn")
        sleep 10
      end
      puts "Host #{host.name} is back up"
    end
  end
end

#reboot_guest(vms) ⇒ Object



126
127
128
# File 'lib/rvc/modules/vm.rb', line 126

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

#recommendations(cluster) ⇒ Object



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
127
128
129
130
131
132
133
134
135
136
# File 'lib/rvc/modules/cluster.rb', line 96

def recommendations cluster
  # Collect everything we need from VC with as few calls as possible
  pc = cluster._connection.serviceContent.propertyCollector
  recommendation, hosts, datastores = cluster.collect 'recommendation', 'host', 'datastore'
  if recommendation.length == 0
    puts "None"
    return
  end
  targets = recommendation.map { |x| x.target }
  recommendation.each { |x| targets += x.action.map { |y| y.target } }
  targets += hosts
  targets += datastores
  targets.compact!
  name_map = pc.collectMultiple(targets, 'name')

  # Compose the output (tries to avoid making any API calls)
  out = Terminal::Table.new(['Key', 'Reason', 'Target', 'Actions']) do
    recommendation.each do |r|
      target_name = r.target ? name_map[r.target]['name'] : ""
      actions = r.action.map do |a|
        action = "#{a.class.wsdl_name}: #{name_map[a.target]['name']}"
        dst = nil
        if a.is_a?(RbVmomi::VIM::ClusterMigrationAction)
          dst = a.drsMigration.destination
        end
        if a.is_a?(RbVmomi::VIM::StorageMigrationAction)
          dst = a.destination
        end
        if dst
          if !name_map[dst]
            name_map[dst] = {'name' => dst.name}
          end
          action += " (to #{name_map[dst]['name']})"
        end
        action
      end
      add_row [r.key, r.reasonText, target_name, actions.join("\n")]
    end
  end
  puts out
end

#reconnect(hosts, opts) ⇒ Object



143
144
145
146
147
148
149
150
# File 'lib/rvc/modules/host.rb', line 143

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

#register(vmx_file, opts) ⇒ Object



198
199
200
201
202
203
# File 'lib/rvc/modules/vm.rb', line 198

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

#reload(opts) ⇒ Object



43
44
45
46
47
48
49
50
51
# File 'lib/rvc/modules/core.rb', line 43

def reload opts
  old_verbose = $VERBOSE
  $VERBOSE = nil unless opts[:verbose]

  shell.reload_modules opts[:verbose]
  RbVmomi::VIM.reload_extensions
ensure
  $VERBOSE = old_verbose
end

#reload_entity(objs) ⇒ Object



293
294
295
# File 'lib/rvc/modules/basic.rb', line 293

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

#remove(objs, opts) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/rvc/modules/device.rb', line 50

def remove devs, opts
  vm_devs = devs.group_by(&:rvc_vm)
  tasks = vm_devs.map do |vm,my_devs|
    device_changes = my_devs.map do |dev|
      fileOp = (dev.backing.is_a?(VIM::VirtualDeviceFileBackingInfo) && !opts[:no_destroy]) ? 'destroy' : nil
      { :operation => :remove, :fileOperation => fileOp, :device => dev }
    end
    spec = { :deviceChange => device_changes }
    vm.ReconfigVM_Task(:spec => spec)
  end

  progress tasks
end

#remove_privilege(name, privileges) ⇒ Object



131
132
133
134
135
136
137
138
# File 'lib/rvc/modules/role.rb', line 131

def remove_privilege name, privileges
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{name.inspect}" unless role
  cur_auth_mgr.UpdateAuthorizationRole :roleId => role.roleId,
                                       :newName => role.name,
                                       :privIds => (role.privilege - privileges)

end

#rename(snapshot, name) ⇒ Object



100
101
102
103
104
105
106
# File 'lib/rvc/modules/role.rb', line 100

def rename old, new
  role = cur_auth_mgr.roleList.find { |x| x.name == old }
  err "no such role #{old.inspect}" unless role
  cur_auth_mgr.UpdateAuthorizationRole :roleId => role.roleId,
                                       :newName => new,
                                       :privIds => role.privilege
end

#require_gnuplotObject



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

def require_gnuplot
  begin
    require 'gnuplot'
  rescue LoadError
    Gem::Specification.reset
    begin
      require 'gnuplot'
    rescue LoadError
      err "The gnuplot gem is not installed"
    end
  end
end

#rescan_storage(hosts) ⇒ Object



199
200
201
202
203
204
205
# File 'lib/rvc/modules/host.rb', line 199

def rescan_storage hosts
  hosts.each do |host|
    storageSystem = host.configManager.storageSystem
    storageSystem.RescanAllHba
    storageSystem.RescanVmfs
  end
end

#reset(vms) ⇒ Object



56
57
58
# File 'lib/rvc/modules/vm.rb', line 56

def reset vms
  tasks vms, :ResetVM
end

#retrieve_datasets(pm, counter, specs) ⇒ Object



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

def retrieve_datasets pm, counter, specs
  results = pm.QueryPerf(:querySpec => specs)
  datasets = results.map do |result|
    times = result.sampleInfoCSV.split(',').select { |x| x['T']  }
    if result.value.empty?
      puts "No data for #{result.entity.name} #{counter.name}"
      next
    end
    data = result.value[0].value.split(',').map(&:to_i)

    if counter.unitInfo.key == 'percent'
      times.length.times do |i|
        times[i] = data[i] = nil if data[i] < 0
      end

      times.compact!
      data.compact!
      data.map! { |x| x/100.0 }
    end

    Gnuplot::DataSet.new([times, data]) do |ds|
      ds.notitle if specs.size == 1
      ds.with = "lines"
      ds.using = '1:2'
      ds.title = result.entity.name
    end
  end.compact
end

#revert(arg) ⇒ Object



45
46
47
48
49
50
51
# File 'lib/rvc/modules/snapshot.rb', line 45

def revert arg
  if arg.is_a? VIM::VirtualMachine
    tasks [arg], :RevertToCurrentSnapshot
  else
    tasks [arg.find_tree.snapshot], :RevertToSnapshot
  end
end

#rmdir(vm, opts) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/rvc/modules/vm_guest.rb', line 204

def rmdir vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  fileManager.
    DeleteDirectoryInGuest(
      :vm => vm,
      :auth => auth,
      :directoryPath => opts[:guest_path],
      :recursive => opts[:recursive]
    )
end

#rmfile(vm, opts) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/rvc/modules/vm_guest.rb', line 228

def rmfile vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  auth = get_auth vm, opts

  fileManager.
    DeleteFileInGuest(
      :vm => vm,
      :auth => auth,
      :filePath => opts[:guest_path]
    )
end

#rvc(vm, opts) ⇒ Object



409
410
411
412
413
414
415
416
417
# File 'lib/rvc/modules/vm.rb', line 409

def rvc vm, opts
  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 #{opts[:user] && Shellwords.escape("#{opts[:user]}@")}#{Shellwords.escape ip}"
  system_fg(cmd, env)
end

#save_keychain_password(username, password, hostname) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/rvc/modules/vim.rb', line 164

def save_keychain_password username , password , hostname
  # only works for OSX at the minute.
  return false unless RbConfig::CONFIG['host_os'] =~ /^darwin10/

  # check we already managed to load that gem.
  if defined? OSXKeychain::VERSION

    if agree("Save password for connection (y/n)? ", true)
      keychain = OSXKeychain.new

      # update the keychain, unless it's already set to that.
      keychain.set("rvc", "#{username}@#{hostname}" , password ) unless 
        keychain["rvc", "#{username}@#{hostname}" ] == password
    end
  else
    return false
  end
end

#screenshot(vm, local_path) ⇒ Object



649
650
651
# File 'lib/rvc/modules/vm.rb', line 649

def screenshot vm, local_path
  http_download vm._connection, "/screen?id=#{vm._ref}", local_path
end

#security(obj, opts) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/rvc/modules/vds.rb', line 376

def security obj, opts
  if (opts[:allow_promisc] and opts[:deny_promisc]) or
      (opts[:allow_mac_changes] and opts[:deny_mac_changes]) or
      (opts[:allow_forged] and opts[:deny_forged])
    puts "Can't both allow and deny!"
    return
  end

  policy = { :inherited => false }
  if opts[:allow_promisc]
    policy[:allowPromiscuous] = { :inherited => false, :value => true }
  elsif opts[:deny_promisc]
    policy[:allowPromiscuous] = { :inherited => false, :value => false }
  end

  if opts[:allow_mac_changes]
    policy[:macChanges] = { :inherited => false, :value => true }
  elsif opts[:deny_mac_changes]
    policy[:macChanges] = { :inherited => false, :value => false }
  end

  if opts[:allow_forged]
    policy[:forgedTransmits] = { :inherited => false, :value => true }
  elsif opts[:deny_forged]
    policy[:forgedTransmits] = { :inherited => false, :value => false }
  end

  inherited_spec = get_inherited_config(obj)
  if inherited_spec != nil
    collapse_inheritance inherited_spec.securityPolicy, policy
  end

  spec = VIM::VMwareDVSPortSetting.new()
  spec.securityPolicy = policy

  apply_settings obj, spec
end

#set(objs, opts) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/rvc/modules/permissions.rb', line 54

def set objs, opts
  conn = single_connection objs
  authMgr = conn.serviceContent.authorizationManager
  role = authMgr.roleList.find { |x| x.name == opts[:role] }
  err "no such role #{role.inspect}" unless role
  perm = { :roleId => role.roleId,
           :principal => opts[:principal],
           :group => opts[:group],
           :propagate => opts[:propagate] }
  objs.each do |obj|
    authMgr.SetEntityPermissions(:entity => obj, :permission => [perm])
  end
end

#set_extra_config(vm, pairs) ⇒ Object



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

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

#set_respool(obj, respool) ⇒ Object



434
435
436
437
# File 'lib/rvc/modules/vds.rb', line 434

def set_respool obj, respool
  apply_settings obj, {:networkResourcePoolKey => {:inherited => false,
                                                   :value => respool.key} }
end

#shaper(obj, opts) ⇒ Object



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
177
178
179
# File 'lib/rvc/modules/vds.rb', line 138

def shaper obj, opts
  if !(opts[:tx] or opts[:rx]) or (opts[:tx] and opts[:rx])
    puts "Need to configure either Rx or Tx Shaping!"
    return
  end

  if opts[:enable] and opts[:disable]
    puts "Can't both enable and disable traffic shaping!"
    return
  end

  shaper_spec = { :inherited => false}
  if opts[:enable]
    shaper_spec[:enabled] = { :value => true, :inherited => false }
  end
  if opts[:disable]
    shaper_spec[:enabled] = { :value => false, :inherited => false }
  end

  if opts[:average_bw]
    shaper_spec[:averageBandwidth] = { :value => (opts[:average_bw] * 1000),
                                       :inherited => false }
  end

  if opts[:burst_size]
    shaper_spec[:burstSize] = { :value => (opts[:burst_size] * 1000),
                                :inherited => false }
  end

  if opts[:peak_bw]
    shaper_spec[:peakBandwidth] = { :value => (opts[:peak_bw] * 1000),
                                    :inherited => false }
  end

  if opts[:rx]
    port_spec = { :inShapingPolicy => shaper_spec }
  else
    port_spec = { :outShapingPolicy => shaper_spec }
  end

  apply_settings obj, port_spec
end

#shares_from_string(str) ⇒ Object



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

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



30
31
32
33
34
35
36
37
# File 'lib/rvc/modules/alarm.rb', line 30

def show objs
  alarm_states = objs.map(&:triggeredAlarmState).flatten.uniq
  alarm_states.each do |alarm_state|
    info = alarm_state.alarm.info
    colored_alarm_status = status_color alarm_state.overallStatus, alarm_state.overallStatus
    puts "#{alarm_state.entity.name}: #{info.name} (#{colored_alarm_status}): #{info.description}"
  end
end

#show_all_portgroups(path) ⇒ Object



460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/rvc/modules/vds.rb', line 460

def show_all_portgroups path
  paths = path.map { |p| p.rvc_path_str }
  if paths.empty?
    paths = nil
  end

  vds = shell.cmds.find.find_items nil, paths, ['vds']
  pgs = []
  vds.each do |v|
    v.portgroup.each { |pg| pgs << pg}
  end
  shell.cmds.basic.table pgs, { :field => ["vds_name", "name", "vlan"],
                                        :field_given => true }
end

#show_all_ports(path) ⇒ Object



498
499
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
530
531
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
576
577
578
579
# File 'lib/rvc/modules/vds.rb', line 498

def show_all_ports path
  rows = []
  num_vds = 0
  num_pgs = 0
  path.each do |obj|
    if obj.class < VIM::DistributedVirtualSwitch
      num_vds += 1
      vds = obj
      ports = vds.FetchDVPorts(:criteria => { :active => true })
    else #obj.class < VIM::DistributedVirtualPortgroup
      num_pgs += 1
      vds = obj.config.distributedVirtualSwitch
      ports = vds.FetchDVPorts(:criteria => {
                                 :portgroupKey => [obj.key], :inside => true,
                                 :active => true})
    end
    pc = vds._connection.propertyCollector

    # fetch names of VMs, portgroups, vDS
    objects = []
    ports.each do |port|
      objects << port.proxyHost
      if port.connectee
         objects << port.connectee.connectedEntity
      end
    end
    vds.portgroup.each { |pg| objects << pg }
    objects << vds
    spec = {
      :objectSet => objects.map { |obj| { :obj => obj } },
      :propSet => [{:type => "ManagedEntity", :pathSet => %w(name) },
                   {:type => "DistributedVirtualPortgroup",
                     :pathSet => %w(name key)}]
    }
    props = pc.RetrieveProperties(:specSet => [spec])
    names = {}
    props.each do |prop|
      names[prop.obj] = prop['name']
      if prop['key']
        names[prop['key']] = prop['name']
      end
    end
    vds_name = names[vds]

    # put each port as a row in the table
    ports.each do |port|
      port_key = begin port.key.to_i; rescue port.key; end

      connectee = nil
      hostname = names[port.proxyHost].dup
      if port.connectee and port.connectee.type == "vmVnic"
        connectee = names[port.connectee.connectedEntity]
      elsif port.connectee
        connectee = port.connectee.nicKey
      end

      rows << [port_key, port.config.name, vds_name, names[port.portgroupKey],
               translate_vlan(port.config.setting.vlan),
               port.state.runtimeInfo.blocked, hostname, connectee]
    end
  end

  abbrev_hostnames(rows.map { |r| r[6] })

  columns = ['key', 'name', 'vds', 'portgroup', 'vlan', 'blocked', 'host', 'connectee']

  # if we're just querying against a single vDS, skip the vds name column
  if num_vds <= 1
    columns.delete_at(2)
    rows.each { |r| r.delete_at(2) }
  end

  # if we're just querying against one portgroup, skip portgroup name column
  if num_pgs <= 1 and num_vds < 1
    columns.delete_at(2)
    rows.each { |r| r.delete_at(2) }
  end

  t = Terminal::Table.new(columns)
  rows.sort_by { |o| o[0] }.each { |o| t << o }
  puts t
end

#show_all_vds(path) ⇒ Object



480
481
482
483
484
485
486
487
488
489
# File 'lib/rvc/modules/vds.rb', line 480

def show_all_vds path
  paths = path.map { |p| p.rvc_path_str }
  if paths.empty?
    paths = nil
  end

  vds = shell.cmds.find.find_items nil, paths, ['vds']
  shell.cmds.basic.table vds, { :field => ['name', 'vlans', 'hosts'],
                                        :field_given => true }
end

#show_running_config(vds) ⇒ Object



444
445
446
447
448
449
450
451
452
453
# File 'lib/rvc/modules/vds.rb', line 444

def show_running_config vds
  shell.cmds.basic.info vds
  portgroups = vds.children['portgroups']
  portgroups.rvc_link vds, 'portgroups'
  vds.children['portgroups'].children.each do |name, pg|
    pg.rvc_link portgroups, name
    puts '---'
    shell.cmds.basic.info pg
  end
end

#shutdown_guest(vms, opts) ⇒ Object



105
106
107
108
# File 'lib/rvc/modules/vm.rb', line 105

def shutdown_guest vms, opts
  vms.each(&:ShutdownGuest)
  wait_for_shutdown vms, opts unless opts[:timeout].nil?
end

#spawn_vmrc(vmrc, moref, host, ticket) ⇒ Object



108
109
110
111
112
# File 'lib/rvc/modules/vmrc.rb', line 108

def spawn_vmrc vmrc, moref, host, ticket
  err "Ruby 1.9 required" unless Process.respond_to? :spawn
  Process.spawn vmrc, '-h', host, '-p', ticket, '-M', moref,
                :err => "#{ENV['HOME']||'.'}/.rvc-vmrc.log"
end

#ssh(vm, cmd, opts) ⇒ Object



393
394
395
396
397
398
# File 'lib/rvc/modules/vm.rb', line 393

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



116
117
118
# File 'lib/rvc/modules/vm.rb', line 116

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

#start_program(vm, opts) ⇒ Object



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/rvc/modules/vm_guest.rb', line 447

def start_program vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :processManager
  processManager = guestOperationsManager.processManager

  auth = get_auth vm, opts

  pid = processManager.
    StartProgramInGuest(
      :vm => vm,
      :auth => auth,
      :spec => VIM.GuestProgramSpec(
        :arguments => opts[:arguments],
        :programPath => opts[:program_path],
        :envVariables => opts[:env],
        :workingDirectory => opts[:working_directory]
      )
    )

  Timeout.timeout opts[:timeout] do
    while true
      processes = processManager.
        ListProcessesInGuest(
          :vm => vm,
          :auth => auth,
          :pids => [pid]
        )
      process = processes.first

      if !process.endTime.nil?
        if process.exitCode != 0
          err "Process failed with exit code #{process.exitCode}"
        end
        break
      elsif opts[:background]
        break
      end

      sleep opts[:delay]
    end
  end
rescue Timeout::Error
  err "Timed out waiting for process to finish."
end

#stats(metrics, objs, opts) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/rvc/modules/perf.rb', line 315

def stats metrics, objs, opts
  metrics = metrics.split(",")

  vim = single_connection objs
  pm = vim.serviceContent.perfManager

  metrics.each do |x|
    err "no such metric #{x}" unless pm.perfcounter_hash.member? x
  end

  interval = pm.provider_summary(objs.first).refreshRate
  start_time = nil
  if interval == -1
    # Object does not support real time stats
    interval = 300
    start_time = Time.now - 300 * 5
  end
  stat_opts = {
    :interval => interval,
    :startTime => start_time,
  }
  stat_opts[:max_samples] = opts[:samples] if opts[:samples]
  res = pm.retrieve_stats objs, metrics, stat_opts

  table = Terminal::Table.new
  table.add_row ['Object', 'Metric', 'Values', 'Unit']
  table.add_separator
  objs.each do |obj|
    metrics.each do |metric|
      stat = res[obj][:metrics][metric]
      metric_info = pm.perfcounter_hash[metric]
      table.add_row([obj.name, metric, stat.join(','), metric_info.unitInfo.label])
    end
  end
  puts table
end

#stats_time(secs) ⇒ Object

see vSphere Client: Administration -> vCenter Server Settings -> Statistics -> Statistics Intervals



25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/rvc/modules/statsinterval.rb', line 25

def stats_time secs
  length = secs / 60

  [[60, "Minutes"], [24, "Hours"], [7, "Days"], [4, "Weeks"], [12, "Months"]].each do |div, name|
    if length < div
      return "#{length} #{name}"
    end
    length = length / div
  end

  return "#{length} Years"
end

#storage(root) ⇒ Object



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

def storage root
  propSet = [
    { :type => 'ResourcePool', :pathSet => ['name', 'parent'] },
    { :type => 'VirtualMachine', :pathSet => ['name', 'parent', 'storage', 'resourcePool'] }
  ]

  filterSpec = RbVmomi::VIM.PropertyFilterSpec(
    :objectSet => [
      :obj => root,
      :selectSet => [
        RbVmomi::VIM.TraversalSpec(
          :name => 'tsResourcePool1',
          :type => 'ResourcePool',
          :path => 'resourcePool',
          :skip => false,
          :selectSet => [
            RbVmomi::VIM.SelectionSpec(:name => 'tsResourcePool1'),
            RbVmomi::VIM.SelectionSpec(:name => 'tsResourcePool2')
          ]
        ),
        RbVmomi::VIM.TraversalSpec(
          :name => 'tsResourcePool2',
          :type => 'ResourcePool',
          :path => 'vm',
          :skip => false,
          :selectSet => [
            RbVmomi::VIM.SelectionSpec(:name => 'tsResourcePool1'),
            RbVmomi::VIM.SelectionSpec(:name => 'tsResourcePool2')
          ]
        )
      ]
    ],
    :propSet => propSet
  )

  result = root._connection.propertyCollector.RetrieveProperties(:specSet => [filterSpec])

  objs = Hash[result.map { |r| [r.obj, r] }]
  usages = Hash.new { |h,k| h[k] = 0 }

  objs.each do |obj,r|
    next unless obj.is_a? VIM::VirtualMachine
    cur = r['resourcePool']
    usage = r['storage'].perDatastoreUsage.map(&:unshared).sum
    while cur
      usages[cur] += usage
      cur = cur == root ? nil : objs[cur]['parent']
    end
  end

  children = Hash.new { |h,k| h[k] = [] }
  objs.each { |obj,r| children[r['parent']] << obj }

  display = lambda do |level,obj|
    puts "#{' '*level}#{objs[obj]['name']}: #{usages[obj].metric}B"
    children[obj].each do |child|
      display[level+1, child]
    end
  end

  display[0, root]
end

#subtract_ranges(ranges, minus_ranges) ⇒ Object



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/rvc/modules/vds.rb', line 326

def subtract_ranges(ranges, minus_ranges)
  outages = []
  minus_range = minus_ranges.shift
  ranges.each do |r|
    while true
      if minus_range == nil or (minus_range.first > r.last)
        break
      elsif minus_range.first < r.first and minus_range.last < r.first
        next
      elsif minus_range.first <= r.first and minus_range.last < r.last
        r = ((minus_range.last+1)..r.last)
        minus_range = minus_ranges.shift
        next
      elsif minus_range.first > r.first and minus_range.last >= r.last
        r = (r.first..(minus_range.first-1))
        break
      elsif minus_range.first > r.first and minus_range.last < r.last
        outages << (r.first..(minus_range.first-1))
        r = ((minus_range.last+1)..r.last)
        minus_range = minus_ranges.shift
        break
      elsif minus_range.first <= r.first and minus_range.last >= r.last
        if minus_range.last == r.last
          minus_range = minus_ranges.shift
        end
        r = nil
        break
      end
    end
    if r != nil
      outages << r
    end
  end
  outages
end

#summarize(obj) ⇒ Object



30
31
32
33
34
35
36
# File 'lib/rvc/modules/vds.rb', line 30

def summarize obj
  if !obj.respond_to?(:summarize)
    puts "not a vds or portgroup!"
    return
  end
  obj.summarize
end

#suspend(vms) ⇒ Object



69
70
71
# File 'lib/rvc/modules/vm.rb', line 69

def suspend vms
  tasks vms, :SuspendVM
end

#switch(name) ⇒ Object



26
27
28
# File 'lib/rvc/modules/connection.rb', line 26

def switch name
  shell.switch_connection name
end

#table(objs, opts) ⇒ Object



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/rvc/modules/basic.rb', line 439

def table objs, opts
  if opts[:field_given]
    fields = opts[:field].map { |x| x.split ':' }.flatten(1)
  else
    fields = objs.map(&:class).uniq.
                  map { |x| x.fields.select { |k,v| v.default? } }.
                  map(&:keys).flatten(1).uniq
  end

  data = retrieve_fields(objs, fields).values

  if f = opts[:sort]
    data.sort! { |a,b| table_sort_compare a[f], b[f] }
    data.reverse! if opts[:reverse]
  end

  # Invert field components to get an array of header rows
  field_components = fields.map { |x| x.split '.' }
  header_rows = []
  field_components.each_with_index do |cs,i|
    cs.each_with_index do |c,j|
      header_rows[j] ||= [nil]*field_components.length
      header_rows[j][i] = c
    end
  end
  
  table = Terminal::Table.new
  header_rows.each { |row| table.add_row row }
  table.add_separator
  data.each do |h|
    table.add_row(fields.map { |f| h[f] == nil ? 'N/A' : h[f] })
  end
  puts table
end

#table_key(str) ⇒ Object



157
158
159
# File 'lib/rvc/modules/esxcli.rb', line 157

def table_key str
  str.downcase.gsub(/[^\w\d_]/, '')
end

#table_sort_compare(a, b) ⇒ Object



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

def table_sort_compare a, b
  return a <=> b if a != nil and b != nil
  return 0 if a == nil and b == nil
  return -1 if a == nil
  return 1 if b == nil
  fail
end

#tasksObject



207
208
209
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/rvc/modules/vim.rb', line 207

def tasks
  conn = single_connection [shell.fs.cur]

  begin
    view = conn.serviceContent.viewManager.CreateListView

    collector = conn.serviceContent.taskManager.CreateCollectorForTasks(:filter => {
      :time => {
        :beginTime => conn.serviceInstance.CurrentTime.to_datetime, # XXX
        :timeType => :queuedTime
      }
    })
    collector.SetCollectorPageSize :maxCount => 1

    filter_spec = {
      :objectSet => [
        {
          :obj => view,
          :skip => true,
          :selectSet => [
            VIM::TraversalSpec(:path => 'view', :type => view.class.wsdl_name)
          ]
        },
        { :obj => collector },
      ],
      :propSet => [
        { :type => 'Task', :pathSet => %w(info.state) },
        { :type => 'TaskHistoryCollector', :pathSet => %w(latestPage) },
      ]
    }
    filter = conn.propertyCollector.CreateFilter(:partialUpdates => false, :spec => filter_spec)

    ver = ''
    loop do
      result = conn.propertyCollector.WaitForUpdates(:version => ver)
      ver = result.version
      result.filterSet[0].objectSet.each do |r|
        remove = []
        case r.obj
        when VIM::TaskHistoryCollector
          infos = collector.ReadNextTasks :maxCount => 100
          view.ModifyListView :add => infos.map(&:task)
        when VIM::Task
          puts "#{Time.now} #{r.obj.info.name} #{r.obj.info.entityName} #{r['info.state']}" unless r['info.state'] == nil
          remove << r.obj if %w(error success).member? r['info.state']
        end
        view.ModifyListView :remove => remove unless remove.empty?
      end
    end
  rescue Interrupt
  ensure
    filter.DestroyPropertyFilter if filter
    collector.DestroyCollector if collector
    view.DestroyView if view
  end
end

#term(x) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/rvc/modules/find.rb', line 84

def term x
  case x
  when /^([\w.]+)(!)?(>=|<=|=|>|<|~)/
    lhs = $1
    negate = $2 != nil
    op = $3
    rhs = $'
    lambda do |o|
      a = o.field(lhs)
      a = [a].compact unless a.is_a? Enumerable
      return negate if a.empty?
      type = a.first.class
      fail "all objects in field #{lhs.inspect} must have the same type" unless a.all? { |x| x.is_a? type }
      b = coerce_str type, rhs
      a.any? do |x|
        case op
        when '='  then x == b
        when '>'  then x > b
        when '>=' then x >= b
        when '<'  then x < b
        when '<=' then x <= b
        when '~'  then x =~ Regexp.new(b)
        end
      end ^ negate
    end
  when /^\w+$/
    lambda { |o| o.field(x) }
  else
    err "failed to parse expression #{x.inspect}"
  end
end

#translate_respool(vds, pk, show_inheritance = true) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/rvc/extensions/DistributedVirtualPortgroup.rb', line 289

def translate_respool vds, pk, show_inheritance = true
  if pk.value == '-1'
    poolName = "-"
  else
    poolName = vds.networkResourcePool.find_all { |pool|
      pk.value == pool.key
    }[0].name
  end

  if show_inheritance and !pk.inherited
    poolName += "*"
  end
  poolName
end

#translate_vlan(vlan) ⇒ Object



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/rvc/extensions/DistributedVirtualPortgroup.rb', line 256

def translate_vlan vlan
  case "#{vlan.class}"
  when "VmwareDistributedVirtualSwitchVlanIdSpec"
    config = vlan.vlanId == 0 ? "-" : vlan.vlanId.to_s + " (tagging)"
  when "VmwareDistributedVirtualSwitchTrunkVlanSpec"
    config = vlan.vlanId.map { |r|
      if r.start != r.end
        "#{r.start}-#{r.end}"
      else
        "#{r.start}"
      end
    }.join(',') + " (trunked)"
  when "Array"
    config = vlan.map { |r|
      if r.start != r.end
        "#{r.start}-#{r.end}"
      else
        "#{r.start}"
      end
    }.join(',')
    if config == "0"
      config = "-"
    else
      config += " (trunked)"
    end
  when "VmwareDistributedVirtualSwitchPvlanSpec"
    # XXX needs to be mapped
    config = vlan.pvlanId.to_s + " (pvlan?)"
  end

  config
end

#type(name) ⇒ Object



32
33
34
35
36
# File 'lib/rvc/modules/basic.rb', line 32

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

#unblock(obj) ⇒ Object



203
204
205
# File 'lib/rvc/modules/vds.rb', line 203

def unblock obj
  apply_settings obj, { :blocked => { :value => false, :inherited => false } }
end

#unregister(vm) ⇒ Object



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

def unregister vm
  vm.UnregisterVM
end

#unset_respool(obj) ⇒ Object



421
422
423
424
# File 'lib/rvc/modules/vds.rb', line 421

def unset_respool obj
  apply_settings obj, {:networkResourcePoolKey => {:inherited => false,
                                                   :value => nil} }
end

#unused_vnc_port(ip) ⇒ Object



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

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(name, opts) ⇒ Object



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

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, dest) ⇒ Object



45
46
47
48
49
50
51
52
53
# File 'lib/rvc/modules/datastore.rb', line 45

def upload local_path, dest
  dir, datastore_filename = *dest
  err "local file does not exist" unless File.exists? local_path
  real_datastore_path = "#{dir.path}/#{datastore_filename}"

  upload_path = http_path dir.datastore.send(:datacenter).name, dir.datastore.name, real_datastore_path

  http_upload dir.datastore._connection, local_path, upload_path
end

#upload_file(vm, opts) ⇒ Object



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/rvc/modules/vm_guest.rb', line 285

def upload_file vm, opts
  guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
  err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
  fileManager = guestOperationsManager.fileManager

  opts[:permissions] = opts[:permissions].to_i(8) if opts[:permissions]

  auth = get_auth vm, opts

  file = File.new(opts[:local_path], 'rb')

  upload_url = fileManager.
    InitiateFileTransferToGuest(
      :vm => vm,
      :auth => auth,
      :guestFilePath => opts[:guest_path],
      :fileAttributes => VIM.GuestPosixFileAttributes(
        :groupId => opts[:group_id],
        :ownerId => opts[:owner_id],
        :permissions => opts[:permissions]
      ),
      :fileSize => file.size,
      :overwrite => opts[:overwrite]
    )

  upload_uri = URI.parse(upload_url.gsub /http(s?):\/\/\*:[0-9]*/, "")
  upload_path = "#{upload_uri.path}?#{upload_uri.query}"

  http_upload vm._connection, opts[:local_path], upload_path
end

#verify(filename, expected_hash) ⇒ Object



169
170
171
172
173
174
175
176
177
# File 'lib/rvc/modules/vmrc.rb', line 169

def verify filename, expected_hash
  if expected_hash == :nocheck
    puts "WARNING: skipping hash check"
  else
    puts "Checking integrity..."
    hexdigest = Digest::SHA256.file(filename).hexdigest
    err "Hash mismatch: expected #{expected_hash}, found #{hexdigest}" if hexdigest != expected_hash
  end
end

#view(vms, opts) ⇒ Object



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

def view vm
  ip = reachable_ip vm.collect('runtime.host')[0]
  extraConfig, = vm.collect('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

#vlan_switchtag(obj, vlan) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/rvc/modules/vds.rb', line 288

def vlan_switchtag obj, vlan
  # if it matches, inherit settings from switch or portgroup
  inherited = false
  inherited_spec = get_inherited_config(obj)
  if inherited_spec != nil then inherited_spec = inherited_spec.vlan end

  if inherited_spec.class == VIM::VmwareDistributedVirtualSwitchVlanIdSpec
    if inherited_spec.vlanId.to_s == vlan.to_s
      inherited = true
    end
  end

  spec = VIM::VMwareDVSPortSetting.new()
  spec.vlan = VIM::VmwareDistributedVirtualSwitchVlanIdSpec.new()
  spec.vlan.vlanId = vlan
  spec.vlan.inherited = inherited
  apply_settings obj, spec
end

#vlan_trunk(obj, vlan, opts) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/rvc/modules/vds.rb', line 221

def vlan_trunk obj, vlan, opts
  ranges = []
  vlan.sub(' ', '').split(',').each do |range_str|
    range_val = range_str.split('-')
    ranges << Range.new(range_val[0].to_i,
                        if range_val.length > 1
                          range_val[1].to_i
                        else
                          range_val[0].to_i
                        end)
  end

  if opts[:append] or opts[:exclude]
    old_vlan = obj.config.defaultPortConfig.vlan
    if old_vlan.class == VIM::VmwareDistributedVirtualSwitchVlanIdSpec
      puts "Can't append/exclude trunk range to switch tagging configuration!"
      return
    elsif old_vlan.class == VIM::VmwareDistributedVirtualSwitchTrunkVlanSpec
      old_vlan = old_vlan.vlanId.map { |r| r.start..r.end }
    end
    old_vlan = merge_ranges(old_vlan)
  end

  if opts[:append]
    ranges = ranges + old_vlan
  end

  ranges = merge_ranges(ranges)

  if opts[:exclude]
    ranges = subtract_ranges(old_vlan, ranges)
    ranges = merge_ranges(ranges)
  end

  spec = VIM::VMwareDVSPortSetting.new()
  spec.vlan = VIM::VmwareDistributedVirtualSwitchTrunkVlanSpec.new()
  spec.vlan.vlanId = ranges.map { |r| { :start => r.first, :end => r.last } }
  spec.vlan.inherited = false

  if ranges.empty?
    # if we excluded all ranges, just allow everything
    vlan_switchtag obj, 0
    return
  end

  inherited_spec = get_inherited_config(obj)
  if inherited_spec != nil then inherited_spec = inherited_spec.vlan end

  if inherited_spec.class == VIM::VmwareDistributedVirtualSwitchTrunkVlanSpec
    inherited_ranges = inherited.vlanId.map { |range| range.start..range.end }
    if (merge_ranges(inherited_ranges) - ranges) == []
      spec.vlan.inherited = true
    end
  end

  apply_settings obj, spec
end

#vm_create(clusters, opts) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rvc/modules/diagnostics.rb', line 60

def vm_create clusters, opts
  datastore = opts[:datastore]
  vm_folder = opts[:vm_folder]
  err "datastore is a required parameter" unless datastore
  err "vm_folder is a required parameter" unless vm_folder

  puts "Creating one VM per host ... (timeout = #{opts[:timeout]} sec)"
  result = _vm_create clusters, datastore, vm_folder, opts

  errors = result.select{|h, x| x['status'] != 'green'}
  errors.each do |host, info|
    puts "Failed to create VM on host #{host} (in cluster #{info['cluster']}): #{info['error']}"
  end
  if errors.length == 0
    puts "Success"
  end
end

#vm_ip(vm) ⇒ Object



626
627
628
629
630
631
632
633
634
635
636
637
638
# File 'lib/rvc/modules/vm.rb', line 626

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

#vmrc_url(arch) ⇒ Object



38
39
40
# File 'lib/rvc/modules/vmrc.rb', line 38

def vmrc_url arch
  "http://cloud.github.com/downloads/vmware/rvc/vmware-vmrc-public-#{arch}-#{PACKAGE_VERSION}.zip"
end

#vnc_client(ip, port, password) ⇒ Object

Override this to spawn a VNC client differently

We can save the vnc pasword out to a file, then call vncviewer with it directly so we don’t need to “password” auth.



103
104
105
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/rvc/modules/vnc.rb', line 103

def vnc_client ip, port, password
  unless VNC
    puts "no VNC client configured"
    puts "#{ip}:#{port} password: #{password}"
    return false
  end

  if File.basename(VNC) == 'vncviewer' # or other vnc clients that support the same -passwd
    tightvnc = %x(#{VNC} --version 2>&1).lines.first['TightVNC'] != nil
    file = Tempfile.new('rvcvncpass')
    filename = file.path
    begin
      if tightvnc
        IO.popen("vncpasswd -f > #{filename}", 'w+') do |vncpass|
          vncpass.puts password
          vncpass.puts password
        end
      else
        IO.popen("vncpasswd #{filename}", 'w+') do |vncpass|
          vncpass.puts password
          vncpass.puts password
        end
      end

      vnc_client_connect ip, port, password, "-passwd #{filename}"
    ensure
      sleep 3 # we have to do this, as the vncviewer forks, and we've no simple way of working out if that thread has read the file yet.
      file.close
      file.unlink
    end
  else
    vnc_client_connect ip, port, password
  end
end

#vnc_client_connect(ip, port, password, vnc_opts = nil) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/rvc/modules/vnc.rb', line 138

def vnc_client_connect ip, port, password, vnc_opts=nil
  fork do
    $stdout.reopen("#{ENV['HOME']||'.'}/.rvc-vnc.log", "w")
    $stderr.reopen("#{ENV['HOME']||'.'}/.rvc-vnc.err", "w")
    Process.setpgrp
    exec [ VNC, vnc_opts, "#{ip}:#{port}" ].join ' '
  end
  puts "spawning #{VNC}"
  print "#{ip}:#{port} password: #{password}"
  print " options: #{vnc_opts}" unless vnc_opts.nil?
  puts
end

#vnc_passwordObject

Override this if you don’t want a random password



93
94
95
96
97
# File 'lib/rvc/modules/vnc.rb', line 93

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

#wait_for_multiple_tasks(tasks, timeout) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rvc/modules/diagnostics.rb', line 23

def wait_for_multiple_tasks tasks, timeout
  if tasks == []
    return []
  end
  pc = tasks.first._connection.serviceContent.propertyCollector
  done = false
  t1 = Time.now
  while !done && (Time.now - t1) < timeout
    tasks_props = pc.collectMultiple(tasks, 'info.state')
    if tasks_props.reject{|t,f| ['success', 'error'].member?(f['info.state'])}.empty?
      done = true
    end
    sleep 2
  end
  tasks_props = pc.collectMultiple(tasks, 'info.state', 'info.error')
  results = Hash[tasks_props.map do |task, props|
    result = if props['info.state'] == 'success'
      task.info.result
    elsif props['info.state'] == 'error'
      props['info.error']
    else
      "Timed out"
    end
    [task, result]
  end]
  results
end

#wait_for_shutdown(vms, opts) ⇒ Object



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

def wait_for_shutdown vms, opts
  finish_time = Time.now + opts[:timeout]
  while Time.now < finish_time
    all_off = true
    vms.each do |vm|
      if vm.summary.runtime.powerState == 'poweredOn'
        all_off = false
      end
    end
    return if all_off
    sleep_time = [opts[:delay], finish_time - Time.now].min
    sleep sleep_time if sleep_time > 0
  end
  err "At least one VM did not shut down!"
end

#watch(counter_name, objs, opts) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/rvc/modules/perf.rb', line 200

def watch counter_name, objs, opts
  require_gnuplot
  with_gnuplot false do |gp|
    puts "Press Ctrl-C to stop."
    while true
      plot counter_name, objs, :terminal => opts[:terminal]
      sleep opts[:interval]
      if opts[:terminal]
        $stdout.write "\e[25A"
        $stdout.flush
      end
    end
  end
rescue Interrupt
end

#what(objs) ⇒ Object



306
307
308
309
310
# File 'lib/rvc/modules/basic.rb', line 306

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

#with_gnuplot(persist) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/rvc/modules/perf.rb', line 175

def with_gnuplot persist
  if $rvc_gnuplot
    yield $rvc_gnuplot
  else
    cmd = Gnuplot.gnuplot(persist) or err 'gnuplot not found'
    $rvc_gnuplot = IO::popen(cmd, "w")
    begin
      yield $rvc_gnuplot
    ensure
      gp = $rvc_gnuplot
      $rvc_gnuplot = nil
      gp.close
    end
  end
end