rvc 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/README.rdoc +1 -1
  2. data/Rakefile +2 -1
  3. data/VERSION +1 -1
  4. data/bin/rvc +53 -9
  5. data/lib/rvc/completion.rb +57 -19
  6. data/lib/rvc/extensions/ComputeResource.rb +2 -2
  7. data/lib/rvc/extensions/DVPortSetting.rb +108 -0
  8. data/lib/rvc/extensions/Datacenter.rb +19 -4
  9. data/lib/rvc/extensions/Datastore.rb +6 -1
  10. data/lib/rvc/extensions/DistributedVirtualPort.rb +146 -0
  11. data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +274 -10
  12. data/lib/rvc/extensions/DistributedVirtualSwitch.rb +124 -3
  13. data/lib/rvc/extensions/Folder.rb +9 -2
  14. data/lib/rvc/extensions/HostSystem.rb +60 -0
  15. data/lib/rvc/extensions/ManagedEntity.rb +19 -0
  16. data/lib/rvc/extensions/ParaVirtualSCSIController.rb +25 -0
  17. data/lib/rvc/extensions/PerfCounterInfo.rb +26 -0
  18. data/lib/rvc/extensions/PerformanceManager.rb +83 -0
  19. data/lib/rvc/extensions/ResourcePool.rb +21 -0
  20. data/lib/rvc/extensions/VirtualDevice.rb +59 -0
  21. data/lib/rvc/extensions/VirtualDisk.rb +25 -0
  22. data/lib/rvc/extensions/VirtualEthernetCard.rb +32 -0
  23. data/lib/rvc/extensions/VirtualMachine.rb +112 -1
  24. data/lib/rvc/field.rb +122 -0
  25. data/lib/rvc/filesystem_session.rb +20 -0
  26. data/lib/rvc/inventory.rb +35 -12
  27. data/lib/rvc/known_hosts.rb +20 -0
  28. data/lib/rvc/memory_session.rb +20 -0
  29. data/lib/rvc/modules.rb +67 -7
  30. data/lib/rvc/modules/alarm.rb +37 -0
  31. data/lib/rvc/modules/basic.rb +172 -41
  32. data/lib/rvc/modules/cluster.rb +18 -2
  33. data/lib/rvc/modules/core.rb +63 -0
  34. data/lib/rvc/modules/datastore.rb +158 -0
  35. data/lib/rvc/modules/device.rb +275 -0
  36. data/lib/rvc/modules/esxcli.rb +193 -0
  37. data/lib/rvc/modules/find.rb +125 -0
  38. data/lib/rvc/modules/issue.rb +33 -0
  39. data/lib/rvc/modules/perf.rb +284 -0
  40. data/lib/rvc/modules/permissions.rb +20 -0
  41. data/lib/rvc/modules/resource_pool.rb +69 -0
  42. data/lib/rvc/modules/role.rb +23 -3
  43. data/lib/rvc/modules/snapshot.rb +20 -0
  44. data/lib/rvc/modules/vds.rb +605 -0
  45. data/lib/rvc/modules/vim.rb +103 -26
  46. data/lib/rvc/modules/vm.rb +93 -220
  47. data/lib/rvc/modules/vnc.rb +50 -13
  48. data/lib/rvc/option_parser.rb +50 -2
  49. data/lib/rvc/readline-ffi.rb +2 -1
  50. data/lib/rvc/shell.rb +34 -33
  51. data/lib/rvc/util.rb +120 -2
  52. data/test/test_fs.rb +9 -5
  53. data/test/test_metric.rb +79 -0
  54. metadata +33 -3
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- VNC = ENV['VNC'] || search_path('vinagre') || search_path('tightvnc') || search_path('vncviewer')
21
+ VNC = ENV['VNC'] || search_path('tightvnc') || search_path('vncviewer') || search_path('vinagre')
22
22
 
23
23
  opts :view do
24
24
  summary "Spawn a VNC client"
@@ -29,8 +29,8 @@ rvc_alias :view, :vnc
29
29
  rvc_alias :view, :V
30
30
 
31
31
  def view vm
32
- ip = reachable_ip vm.runtime.host
33
- extraConfig = vm.config.extraConfig
32
+ ip = reachable_ip vm.collect('runtime.host')[0]
33
+ extraConfig, = vm.collect('config.extraConfig')
34
34
  already_enabled = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.enabled' && x.value.downcase == 'true' }
35
35
  if already_enabled
36
36
  puts "VNC already enabled"
@@ -68,7 +68,7 @@ end
68
68
 
69
69
 
70
70
  def reachable_ip host
71
- ips = host.config.network.vnic.map { |x| x.spec.ip.ipAddress } # TODO optimize
71
+ ips = host.collect('config.network.vnic')[0].map { |x| x.spec.ip.ipAddress }
72
72
  ips.find do |x|
73
73
  begin
74
74
  Timeout.timeout(1) { TCPSocket.new(x, 443).close; true }
@@ -95,17 +95,54 @@ def vnc_password
95
95
  end
96
96
 
97
97
  # Override this to spawn a VNC client differently
98
+ #
99
+ # We can save the vnc pasword out to a file, then call vncviewer with it
100
+ # directly so we don't need to "password" auth.
98
101
  def vnc_client ip, port, password
99
- if VNC
100
- fork do
101
- $stderr.reopen("#{ENV['HOME']||'.'}/.rvc-vmrc.log", "w")
102
- Process.setpgrp
103
- exec VNC, "#{ip}:#{port}"
104
- end
105
- puts "spawning #{VNC}"
106
- puts "#{ip}:#{port} password: #{password}"
107
- else
102
+ unless VNC
108
103
  puts "no VNC client configured"
109
104
  puts "#{ip}:#{port} password: #{password}"
105
+ return false
106
+ end
107
+
108
+ if File.basename(VNC) == 'vncviewer' # or other vnc clients that support the same -passwd
109
+ tightvnc = %x(#{VNC} --version 2>&1).lines.first['TightVNC'] != nil
110
+ file = Tempfile.new('rvcvncpass')
111
+ filename = file.path
112
+ begin
113
+ if tightvnc
114
+ IO.popen("vncpasswd -f > #{filename}", 'w+') do |vncpass|
115
+ vncpass.puts password
116
+ vncpass.puts password
117
+ end
118
+ else
119
+ IO.popen("vncpasswd #{filename}", 'w+') do |vncpass|
120
+ vncpass.puts password
121
+ vncpass.puts password
122
+ end
123
+ end
124
+
125
+ vnc_client_connect ip, port, password, "-passwd #{filename}"
126
+ ensure
127
+ 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.
128
+ file.close
129
+ file.unlink
130
+ end
131
+ else
132
+ vnc_client_connect ip, port, password
110
133
  end
111
134
  end
135
+
136
+ def vnc_client_connect ip, port, password, vnc_opts=nil
137
+ fork do
138
+ $stdout.reopen("#{ENV['HOME']||'.'}/.rvc-vnc.log", "w")
139
+ $stderr.reopen("#{ENV['HOME']||'.'}/.rvc-vnc.err", "w")
140
+ Process.setpgrp
141
+ exec [ VNC, vnc_opts, "#{ip}:#{port}" ].join ' '
142
+ end
143
+ puts "spawning #{VNC}"
144
+ print "#{ip}:#{port} password: #{password}"
145
+ print " options: #{vnc_opts}" unless vnc_opts.nil?
146
+ puts
147
+ end
148
+
@@ -20,6 +20,13 @@
20
20
 
21
21
  require 'trollop'
22
22
 
23
+ begin
24
+ require 'chronic'
25
+ RVC::HAVE_CHRONIC = true
26
+ rescue LoadError
27
+ RVC::HAVE_CHRONIC = false
28
+ end
29
+
23
30
  module RVC
24
31
 
25
32
  class OptionParser < Trollop::Parser
@@ -33,7 +40,10 @@ class OptionParser < Trollop::Parser
33
40
  @seen_not_required = false
34
41
  @seen_multi = false
35
42
  @applicable = Set.new
36
- super &b
43
+ super() do
44
+ opt :help, "Show this message", :short => 'h'
45
+ instance_eval &b
46
+ end
37
47
  end
38
48
 
39
49
  def summary str
@@ -47,7 +57,9 @@ class OptionParser < Trollop::Parser
47
57
 
48
58
  def opt name, *a
49
59
  super
50
- @applicable << @specs[name][:lookup] if @specs[name][:lookup]
60
+ spec = @specs[name]
61
+ @applicable << spec[:lookup] if spec[:lookup]
62
+ spec[:type] = :string if spec[:lookup] || spec[:lookup_parent]
51
63
  @has_options = true unless name == :help
52
64
  end
53
65
 
@@ -112,6 +124,16 @@ class OptionParser < Trollop::Parser
112
124
  return args, opts
113
125
  end
114
126
 
127
+ def parse_date_parameter param, arg
128
+ if RVC::HAVE_CHRONIC
129
+ Chronic.parse(param)
130
+ else
131
+ Time.parse param
132
+ end
133
+ rescue
134
+ raise ::Trollop::CommandlineError, "option '#{arg}' needs a time"
135
+ end
136
+
115
137
  def postprocess_arg x, spec
116
138
  if spec[:lookup]
117
139
  RVC::Util.lookup!(x, spec[:lookup]).
@@ -138,4 +160,30 @@ class OptionParser < Trollop::Parser
138
160
  end
139
161
  end
140
162
 
163
+ class RawOptionParser
164
+ attr_reader :applicable
165
+
166
+ def initialize cmd, summary
167
+ @cmd = cmd
168
+ @summary = summary
169
+ @applicable = []
170
+ end
171
+
172
+ def summary?
173
+ @summary
174
+ end
175
+
176
+ def parse args
177
+ [args, {}]
178
+ end
179
+
180
+ def has_options?
181
+ false
182
+ end
183
+
184
+ def educate
185
+ # XXX
186
+ end
187
+ end
188
+
141
189
  end
@@ -22,7 +22,8 @@ require 'ffi'
22
22
 
23
23
  module RVC::ReadlineFFI
24
24
  extend FFI::Library
25
- ffi_lib "readline.so"
25
+ libreadline = ENV['RVC_READLINE'] == nil ? 'readline.so' : ENV['RVC_READLINE']
26
+ ffi_lib libreadline
26
27
  callback :rl_linebuf_func_t, [ :string, :int ], :bool
27
28
  attach_variable :rl_char_is_quoted_p, :rl_char_is_quoted_p, :rl_linebuf_func_t
28
29
  attach_variable :rl_line_buffer, :rl_line_buffer, :string
@@ -58,7 +58,7 @@ class Shell
58
58
  end
59
59
  rescue SystemExit, IOError
60
60
  raise
61
- rescue RVC::Util::UserError, RuntimeError, RbVmomi::Fault
61
+ rescue RVC::Util::UserError, RuntimeError, RbVmomi::Fault, Trollop::CommandlineError
62
62
  if ruby or debug
63
63
  puts "#{$!.class}: #{$!.message}"
64
64
  puts $!.backtrace * "\n"
@@ -73,51 +73,52 @@ class Shell
73
73
  rescue Exception
74
74
  puts "#{$!.class}: #{$!.message}"
75
75
  puts $!.backtrace * "\n"
76
+ ensure
77
+ $stdout.flush
76
78
  end
77
79
  end
78
80
 
79
- def eval_command input
81
+ def self.parse_input input
80
82
  cmd, *args = Shellwords.shellwords(input)
81
- return unless cmd
82
- RVC::Util.err "invalid command" unless cmd.is_a? String
83
- case cmd
84
- when RVC::FS::MARK_PATTERN
85
- CMD.basic.cd RVC::Util.lookup_single(cmd)
86
- else
87
- if cmd.include? '.'
88
- module_name, cmd, = cmd.split '.'
89
- elsif ALIASES.member? cmd
90
- module_name, cmd, = ALIASES[cmd].split '.'
91
- else
92
- RVC::Util.err "unknown alias #{cmd}"
93
- end
94
-
95
- m = MODULES[module_name] or RVC::Util.err("unknown module #{module_name}")
83
+ return nil unless cmd
84
+ if cmd.include? '.'
85
+ module_name, cmd, = cmd.split '.'
86
+ elsif ALIASES.member? cmd
87
+ module_name, cmd, = ALIASES[cmd].split '.'
88
+ end
89
+ [MODULES[module_name], cmd.to_sym, args]
90
+ end
96
91
 
97
- opts_block = m.opts_for(cmd.to_sym)
98
- parser = RVC::OptionParser.new cmd, &opts_block
92
+ def eval_command input
93
+ m, cmd, args = Shell.parse_input input
94
+ RVC::Util.err "invalid command" unless m != nil and
95
+ cmd.is_a? Symbol and
96
+ m.has_command? cmd
97
+ parser = m.opts_for(cmd)
99
98
 
100
- begin
101
- args, opts = parser.parse args
102
- rescue Trollop::HelpNeeded
103
- parser.educate
104
- return
105
- end
99
+ begin
100
+ args, opts = parser.parse args
101
+ rescue Trollop::HelpNeeded
102
+ parser.educate
103
+ return
104
+ end
106
105
 
107
- if parser.has_options?
108
- m.send cmd.to_sym, *(args + [opts])
109
- else
110
- m.send cmd.to_sym, *args
111
- end
106
+ if parser.has_options?
107
+ m.send cmd.to_sym, *(args + [opts])
108
+ else
109
+ m.send cmd.to_sym, *args
112
110
  end
113
- nil
114
111
  end
115
112
 
116
113
  def eval_ruby input, file="<input>"
117
114
  result = @ruby_evaluator.do_eval input, file
118
115
  if $interactive
119
116
  if input =~ /\#$/
120
- introspect_object result
117
+ if result.is_a? Class
118
+ introspect_class result
119
+ else
120
+ introspect_object result
121
+ end
121
122
  else
122
123
  pp result
123
124
  end
@@ -205,7 +206,7 @@ class RubyEvaluator
205
206
  rescue Exception => e
206
207
  bt = e.backtrace
207
208
  bt = bt.reverse.drop_while { |x| !(x =~ /toplevel/) }.reverse
208
- bt[-1].gsub! ':in `toplevel\'', ''
209
+ bt[-1].gsub! ':in `toplevel\'', '' if bt[-1]
209
210
  e.set_backtrace bt
210
211
  raise
211
212
  end
@@ -18,6 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require 'delegate'
22
+
21
23
  module RVC
22
24
  module Util
23
25
  extend self
@@ -104,6 +106,7 @@ module Util
104
106
  end
105
107
 
106
108
  def progress tasks
109
+ results = {}
107
110
  interested = %w(info.progress info.state info.entityName info.error info.name)
108
111
  connection = single_connection tasks
109
112
  connection.serviceInstance.wait_for_multiple_tasks interested, tasks do |h|
@@ -120,8 +123,10 @@ module Util
120
123
  $stdout.write "\e[K#{text}#{progress_bar}\n"
121
124
  elsif state == 'error'
122
125
  error = props['info.error']
126
+ results[task] = error
123
127
  $stdout.write "\e[K#{name} #{entityName}: #{error.fault.class.wsdl_name}: #{error.localizedMessage}\n"
124
128
  else
129
+ results[task] = task.info.result if state == 'success'
125
130
  $stdout.write "\e[K#{name} #{entityName}: #{state}\n"
126
131
  end
127
132
  end
@@ -130,7 +135,13 @@ module Util
130
135
  end
131
136
  end
132
137
  $stdout.write "\e[#{tasks.size}B" if interactive?
133
- true
138
+ results
139
+ end
140
+
141
+ def one_progress task
142
+ progress([task])[task].tap do |r|
143
+ raise r if r.is_a? VIM::LocalizedMethodFault
144
+ end
134
145
  end
135
146
 
136
147
  def terminal_columns
@@ -160,7 +171,7 @@ module Util
160
171
  tcsetpgrp
161
172
  exec cmd
162
173
  end
163
- Process.waitpid2 pid
174
+ Process.waitpid2 pid rescue nil
164
175
  tcsetpgrp
165
176
  nil
166
177
  end
@@ -194,5 +205,112 @@ module Util
194
205
 
195
206
  Hash[results.map { |r| [r['name'], r.obj] }]
196
207
  end
208
+
209
+ def status_color str, status
210
+ $terminal.color(str, *VIM::ManagedEntity::STATUS_COLORS[status])
211
+ end
212
+
213
+ def metric num
214
+ MetricNumber.new(num.to_f, '', false).to_s
215
+ end
216
+
217
+ def retrieve_fields objs, fields
218
+ Hash[objs.map do |o|
219
+ begin
220
+ [o, Hash[fields.map { |f| [f, o.field(f)] }]]
221
+ rescue VIM::ManagedObjectNotFound
222
+ next
223
+ end
224
+ end]
225
+ end
226
+ end
227
+ end
228
+
229
+ class Numeric
230
+ def metric
231
+ RVC::Util.metric self
232
+ end
233
+ end
234
+
235
+ class TimeDiff < SimpleDelegator
236
+ def to_s
237
+ i = self.to_i
238
+ seconds = i % 60
239
+ i /= 60
240
+ minutes = i % 60
241
+ i /= 60
242
+ hours = i
243
+ [hours, minutes, seconds].join ':'
244
+ end
245
+
246
+ def self.parse str
247
+ a = str.split(':', 3).reverse
248
+ seconds = a[0].to_i rescue 0
249
+ minutes = a[1].to_i rescue 0
250
+ hours = a[2].to_i rescue 0
251
+ TimeDiff.new(hours * 3600 + minutes * 60 + seconds)
252
+ end
197
253
  end
254
+
255
+ class MetricNumber < SimpleDelegator
256
+ attr_reader :unit, :binary
257
+
258
+ def initialize val, unit, binary=false
259
+ @unit = unit
260
+ @binary = binary
261
+ super val.to_f
262
+ end
263
+
264
+ def to_s
265
+ limit = @binary ? 1024 : 1000
266
+ if self < limit
267
+ prefix = ''
268
+ multiple = 1
269
+ else
270
+ prefixes = @binary ? BINARY_PREFIXES : DECIMAL_PREFIXES
271
+ prefixes = prefixes.sort_by { |k,v| v }
272
+ prefix, multiple = prefixes.find { |k,v| self/v < limit }
273
+ prefix, multiple = prefixes.last unless prefix
274
+ end
275
+ ("%0.2f %s%s" % [self/multiple, prefix, @unit]).strip
276
+ end
277
+
278
+ # http://physics.nist.gov/cuu/Units/prefixes.html
279
+ DECIMAL_PREFIXES = {
280
+ 'k' => 10 ** 3,
281
+ 'M' => 10 ** 6,
282
+ 'G' => 10 ** 9,
283
+ 'T' => 10 ** 12,
284
+ 'P' => 10 ** 15,
285
+ }
286
+
287
+ # http://physics.nist.gov/cuu/Units/binary.html
288
+ BINARY_PREFIXES = {
289
+ 'Ki' => 2 ** 10,
290
+ 'Mi' => 2 ** 20,
291
+ 'Gi' => 2 ** 30,
292
+ 'Ti' => 2 ** 40,
293
+ 'Pi' => 2 ** 50,
294
+ }
295
+
296
+ CANONICAL_PREFIXES = Hash[(DECIMAL_PREFIXES.keys + BINARY_PREFIXES.keys).map { |x| [x.downcase, x] }]
297
+
298
+ def self.parse str
299
+ if str =~ /^([0-9,.]+)\s*([kmgtp]i?)?/i
300
+ x = $1.delete(',').to_f
301
+ binary = false
302
+ if $2
303
+ prefix = $2.downcase
304
+ binary = prefix[1..1] == 'i'
305
+ prefixes = binary ? BINARY_PREFIXES : DECIMAL_PREFIXES
306
+ multiple = prefixes[CANONICAL_PREFIXES[prefix]]
307
+ else
308
+ multiple = 1
309
+ end
310
+ units = $'
311
+ new x*multiple, units, binary
312
+ else
313
+ raise "Problem parsing SI number #{str.inspect}"
314
+ end
315
+ end
198
316
  end
@@ -18,16 +18,20 @@ class FSTest < Test::Unit::TestCase
18
18
 
19
19
  def setup
20
20
  @context = RVC::FS.new Root
21
+ session = RVC::MemorySession.new
22
+ $shell = RVC::Shell.new session
23
+ $shell.instance_variable_set :@fs, @context
21
24
  end
22
25
 
23
26
  def teardown
24
27
  @context = nil
28
+ $shell = nil
25
29
  end
26
30
 
27
31
  def test_new
28
32
  assert_equal Root, @context.cur
29
33
  assert_equal "", @context.display_path
30
- assert_equal 0, @context.marks.size
34
+ assert_equal 0, $shell.session.marks.size
31
35
  assert_equal [['', Root]], @context.cur.rvc_path
32
36
  end
33
37
 
@@ -93,20 +97,20 @@ class FSTest < Test::Unit::TestCase
93
97
  assert_equal nil, obj
94
98
 
95
99
  ['foo', '~', '7', ''].each do |mark|
96
- @context.mark mark, [b_obj]
100
+ $shell.session.set_mark mark, [b_obj]
97
101
  obj = @context.lookup("~#{mark}")[0]
98
102
  assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], obj.rvc_path
99
103
 
100
- @context.mark mark, []
104
+ $shell.session.set_mark mark, []
101
105
  obj = @context.lookup("~#{mark}")[0]
102
106
  assert_equal nil, obj
103
107
  end
104
108
 
105
- @context.mark '7', [b_obj]
109
+ $shell.session.set_mark '7', [b_obj]
106
110
  obj = @context.lookup("7")[0]
107
111
  assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], obj.rvc_path
108
112
 
109
- @context.mark '7', []
113
+ $shell.session.set_mark '7', []
110
114
  obj = @context.lookup("7")[0]
111
115
  assert_equal nil, obj
112
116
  end