rvc 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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