rvc 1.6.0 → 1.7.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 (66) hide show
  1. data/Rakefile +1 -1
  2. data/VERSION +1 -1
  3. data/bin/rvc +46 -70
  4. data/devel/test-dependencies.sh +4 -0
  5. data/lib/rvc.rb +5 -6
  6. data/lib/rvc/command.rb +65 -0
  7. data/lib/rvc/command_slate.rb +112 -0
  8. data/lib/rvc/completion.rb +89 -58
  9. data/lib/rvc/connection.rb +48 -0
  10. data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +1 -1
  11. data/lib/rvc/extensions/DistributedVirtualSwitch.rb +3 -3
  12. data/lib/rvc/extensions/HostSystem.rb +90 -0
  13. data/lib/rvc/extensions/VirtualMachine.rb +37 -7
  14. data/lib/rvc/field.rb +59 -12
  15. data/lib/rvc/fs.rb +34 -4
  16. data/lib/rvc/inventory.rb +5 -1
  17. data/lib/rvc/modules/alarm.rb +2 -0
  18. data/lib/rvc/modules/basic.rb +66 -61
  19. data/lib/rvc/modules/cluster.rb +117 -22
  20. data/lib/rvc/modules/connection.rb +40 -0
  21. data/lib/rvc/modules/core.rb +4 -16
  22. data/lib/rvc/modules/datacenter.rb +2 -0
  23. data/lib/rvc/modules/datastore.rb +11 -78
  24. data/lib/rvc/modules/device.rb +40 -5
  25. data/lib/rvc/modules/diagnostics.rb +169 -0
  26. data/lib/rvc/modules/esxcli.rb +9 -5
  27. data/lib/rvc/modules/find.rb +5 -3
  28. data/lib/rvc/modules/host.rb +46 -3
  29. data/lib/rvc/modules/issue.rb +2 -0
  30. data/lib/rvc/modules/mark.rb +5 -3
  31. data/lib/rvc/modules/perf.rb +99 -33
  32. data/lib/rvc/modules/permissions.rb +2 -0
  33. data/lib/rvc/modules/resource_pool.rb +2 -0
  34. data/lib/rvc/modules/role.rb +3 -1
  35. data/lib/rvc/modules/snapshot.rb +12 -4
  36. data/lib/rvc/modules/statsinterval.rb +13 -11
  37. data/lib/rvc/modules/vds.rb +67 -10
  38. data/lib/rvc/modules/vim.rb +19 -53
  39. data/lib/rvc/modules/vm.rb +27 -6
  40. data/lib/rvc/modules/vm_guest.rb +490 -0
  41. data/lib/rvc/modules/vmrc.rb +60 -32
  42. data/lib/rvc/modules/vnc.rb +2 -0
  43. data/lib/rvc/namespace.rb +114 -0
  44. data/lib/rvc/option_parser.rb +12 -15
  45. data/lib/rvc/readline-ffi.rb +4 -1
  46. data/lib/rvc/ruby_evaluator.rb +84 -0
  47. data/lib/rvc/shell.rb +68 -83
  48. data/lib/rvc/uri_parser.rb +59 -0
  49. data/lib/rvc/util.rb +134 -29
  50. data/lib/rvc/{extensions/PerfCounterInfo.rb → version.rb} +2 -4
  51. data/lib/rvc/{memory_session.rb → vim.rb} +10 -32
  52. data/test/modules/foo.rb +9 -0
  53. data/test/modules/foo/bar.rb +9 -0
  54. data/test/test_completion.rb +17 -0
  55. data/test/test_fs.rb +9 -11
  56. data/test/test_help.rb +46 -0
  57. data/test/test_helper.rb +12 -0
  58. data/test/test_metric.rb +1 -2
  59. data/test/test_modules.rb +38 -0
  60. data/test/test_parse_path.rb +1 -2
  61. data/test/test_shell.rb +138 -0
  62. data/test/test_uri.rb +34 -0
  63. metadata +115 -81
  64. data/lib/rvc/extensions/PerformanceManager.rb +0 -83
  65. data/lib/rvc/filesystem_session.rb +0 -101
  66. data/lib/rvc/modules.rb +0 -138
@@ -18,19 +18,35 @@
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 'rvc/namespace'
22
+ require 'rvc/connection'
23
+ require 'rvc/ruby_evaluator'
24
+ require 'shellwords'
25
+
21
26
  module RVC
22
27
 
23
28
  class Shell
24
- attr_reader :fs, :connections, :session
25
- attr_accessor :debug
29
+ attr_reader :fs, :completion
30
+ attr_reader :connections, :connection
31
+ attr_accessor :debug, :cmds
26
32
 
27
- def initialize session
28
- @session = session
33
+ def initialize
29
34
  @persist_ruby = false
30
- @fs = RVC::FS.new RVC::RootNode.new
31
- @ruby_evaluator = RubyEvaluator.new @fs
32
- @connections = {}
35
+ @fs = RVC::FS.new RVC::RootNode.new(self)
36
+ @ruby_evaluator = RVC::RubyEvaluator.new self
37
+ @completion = RVC::Completion.new self
38
+ @connection = NullConnection.new
39
+ @connections = { '' => @connection }
33
40
  @debug = false
41
+ @cmds = nil
42
+ end
43
+
44
+ def switch_connection name
45
+ @connection = @connections[name] || fail("no such connection")
46
+ end
47
+
48
+ def inspect
49
+ "#<RVC::Shell:#{object_id}>"
34
50
  end
35
51
 
36
52
  def eval_input input
@@ -79,34 +95,35 @@ class Shell
79
95
  end
80
96
 
81
97
  def self.parse_input input
82
- cmd, *args = Shellwords.shellwords(input)
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 '.'
98
+ begin
99
+ cmdpath, *args = Shellwords.shellwords(input)
100
+ rescue ArgumentError # unmatched double quote
101
+ cmdpath, *args = Shellwords.shellwords(input + '"')
88
102
  end
89
- [MODULES[module_name], cmd.to_sym, args]
103
+ return nil unless cmdpath
104
+ cmdpath = cmdpath.split('.').map(&:to_sym)
105
+ [cmdpath, args]
90
106
  end
91
107
 
92
108
  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)
109
+ cmdpath, args = Shell.parse_input input
110
+
111
+ RVC::Util.err "invalid input" unless cmdpath
112
+
113
+ cmd = cmds.lookup cmdpath
114
+ RVC::Util.err "invalid command" unless cmd
98
115
 
99
116
  begin
100
- args, opts = parser.parse args
117
+ args, opts = cmd.parser.parse args
101
118
  rescue Trollop::HelpNeeded
102
- parser.educate
119
+ cmd.parser.educate
103
120
  return
104
121
  end
105
122
 
106
- if parser.has_options?
107
- m.send cmd.to_sym, *(args + [opts])
123
+ if cmd.parser.has_options?
124
+ cmd.invoke *(args + [opts])
108
125
  else
109
- m.send cmd.to_sym, *args
126
+ cmd.invoke *args
110
127
  end
111
128
  end
112
129
 
@@ -154,7 +171,7 @@ class Shell
154
171
  introspect_class klasses.map(&:ancestors).inject(&:&)[0]
155
172
  end
156
173
  else
157
- puts obj.class
174
+ introspect_class obj.class
158
175
  end
159
176
  end
160
177
 
@@ -179,68 +196,36 @@ class Shell
179
196
  puts " #{name}(#{params.map { |x| "#{x['name']} : #{q[x['wsdl_type'] || 'void']}#{x['is-array'] ? '[]' : ''}" } * ', '}) : #{q[desc['result']['wsdl_type'] || 'void']}"
180
197
  end
181
198
  else
182
- puts klass
183
- end
184
- end
185
-
186
- def delete_numeric_marks
187
- @session.marks.grep(/^\d+$/).each { |x| @session.set_mark x, nil }
188
- end
189
- end
190
-
191
- class RubyEvaluator
192
- include RVC::Util
193
-
194
- def initialize fs
195
- @binding = toplevel
196
- @fs = fs
197
- end
198
-
199
- def toplevel
200
- binding
201
- end
202
-
203
- def do_eval input, file
204
- begin
205
- eval input, @binding, file
206
- rescue Exception => e
207
- bt = e.backtrace
208
- bt = bt.reverse.drop_while { |x| !(x =~ /toplevel/) }.reverse
209
- bt[-1].gsub! ':in `toplevel\'', '' if bt[-1]
210
- e.set_backtrace bt
211
- raise
199
+ puts "#{klass} < #{klass.superclass}"
200
+ puts
201
+ methods_by_class = klass.instance_methods.map { |x| klass.instance_method(x) }.group_by { |m| m.owner }
202
+ klass.ancestors.each do |ancestor|
203
+ break if ancestor == Object
204
+ if ancestor == klass
205
+ puts "Methods:"
206
+ else
207
+ puts "Methods from #{ancestor}:"
208
+ end
209
+ methods_by_class[ancestor].sort_by { |m| m.name }.each do |m|
210
+ if m.respond_to? :parameters
211
+ puts " #{m.name}(#{m.parameters.map { |mode,name| "#{name}#{mode==:opt ? '?' : ''}" } * ', '})"
212
+ else
213
+ puts " #{m.name}"
214
+ end
215
+ end
216
+ end
212
217
  end
213
218
  end
214
219
 
215
- def this
216
- @fs.cur
217
- end
218
-
219
- def dc
220
- @fs.lookup("~").first
221
- end
220
+ BULTIN_MODULE_PATH = [File.expand_path(File.join(File.dirname(__FILE__), 'modules')),
221
+ File.join(ENV['HOME'], ".rvc")]
222
+ ENV_MODULE_PATH = (ENV['RVC_MODULE_PATH'] || '').split ':'
222
223
 
223
- def conn
224
- @fs.lookup("~@").first
225
- end
226
-
227
- def method_missing sym, *a
228
- super unless $shell
229
- str = sym.to_s
230
- if a.empty?
231
- if MODULES.member? str
232
- MODULES[str]
233
- elsif str =~ /_?([\w\d]+)(!?)/ && objs = $shell.session.get_mark($1)
234
- if $2 == '!'
235
- objs
236
- else
237
- objs.first
238
- end
239
- else
240
- super
241
- end
242
- else
243
- super
224
+ def reload_modules verbose=true
225
+ @cmds = RVC::Namespace.new 'root', self, nil
226
+ module_path = (BULTIN_MODULE_PATH+ENV_MODULE_PATH).select { |d| File.directory?(d) }
227
+ module_path.each do |dir|
228
+ @cmds.load_module_dir dir, verbose
244
229
  end
245
230
  end
246
231
  end
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2012 VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+ require 'uri'
21
+
22
+ module RVC
23
+
24
+ class URIParser
25
+ # XXX comments
26
+ # TODO certdigest
27
+ URI_REGEX = %r{
28
+ ^
29
+ (?:
30
+ (\w+):// # scheme
31
+ )?
32
+ (?:
33
+ ([^@:]+) # username
34
+ (?::
35
+ ([^@]*) # password
36
+ )?
37
+ @
38
+ )?
39
+ ([^@:]+) # host
40
+ (?::(\d{1,5}))? # port
41
+ $
42
+ }x
43
+
44
+ # Loosely parse a URI. This is more forgiving than a standard URI parser.
45
+ def self.parse str
46
+ match = URI_REGEX.match str
47
+ Util.err "invalid URI" unless match
48
+
49
+ URI::Generic.build({}).tap do |uri|
50
+ uri.scheme = match[1] if match[1]
51
+ uri.user = match[2] if match[2]
52
+ uri.password = match[3] if match[3]
53
+ uri.host = match[4] if match[4]
54
+ uri.port = match[5].to_i if match[5]
55
+ end
56
+ end
57
+ end
58
+
59
+ end
@@ -24,34 +24,6 @@ module RVC
24
24
  module Util
25
25
  extend self
26
26
 
27
- # XXX just use an optional argument for type
28
- def lookup path
29
- $shell.fs.lookup path
30
- end
31
-
32
- def lookup_single path
33
- objs = lookup path
34
- err "Not found: #{path.inspect}" if objs.empty?
35
- err "More than one match for #{path.inspect}" if objs.size > 1
36
- objs.first
37
- end
38
-
39
- def lookup! path, types
40
- types = [types] unless types.is_a? Enumerable
41
- lookup(path).tap do |objs|
42
- objs.each do |obj|
43
- err "Expected #{types*'/'} but got #{obj.class} at #{path.inspect}" unless types.any? { |type| obj.is_a? type }
44
- end
45
- end
46
- end
47
-
48
- def lookup_single! path, type
49
- objs = lookup!(path, type)
50
- err "Not found: #{path.inspect}" if objs.empty?
51
- err "More than one match for #{path.inspect}" if objs.size > 1
52
- objs.first
53
- end
54
-
55
27
  def menu items
56
28
  items.each_with_index { |x, i| puts "#{i} #{x}" }
57
29
  input = Readline.readline("? ", false)
@@ -215,14 +187,129 @@ module Util
215
187
  end
216
188
 
217
189
  def retrieve_fields objs, fields
190
+ pc = nil
191
+ if objs.length == 0
192
+ return {}
193
+ end
194
+ conn = objs.first._connection
195
+ pc = conn.propertyCollector
196
+ perfmgr = conn.serviceContent.perfManager
197
+ objs_props = Hash[objs.map{|o| [o, o.field_properties(fields)]}]
198
+ buckets = {}
199
+ objs_props.each{|o,p| buckets[p] ||= []; buckets[p] << o}
200
+ props_values = {}
201
+ buckets.each do |props, o|
202
+ begin
203
+ props_values.merge!(pc.collectMultiple(o, *props))
204
+ rescue VIM::ManagedObjectNotFound => ex
205
+ o -= [ex.obj]
206
+ retry
207
+ end
208
+ end
209
+
210
+ buckets = {}
211
+ objs.each do |o|
212
+ metrics = o.perfmetrics(fields)
213
+ if metrics.length > 0
214
+ buckets[metrics] ||= []
215
+ buckets[metrics] << o
216
+ end
217
+ end
218
+ perf_values = {}
219
+ buckets.each do |metrics, os|
220
+ # XXX: Would be great if we could collapse metrics into a single call
221
+ metrics.each do |metric|
222
+ begin
223
+ stats = perfmgr.retrieve_stats os, metric[:metrics], metric[:opts]
224
+ os.each do |o|
225
+ perf_values[o] = {}
226
+ metric[:metrics].map do |x|
227
+ if stats[o]
228
+ perf_values[o][x] = stats[o][:metrics][x]
229
+ end
230
+ end
231
+ end
232
+ rescue VIM::ManagedObjectNotFound => ex
233
+ o -= [ex.obj]
234
+ retry
235
+ end
236
+ end
237
+ end
238
+
218
239
  Hash[objs.map do |o|
219
240
  begin
220
- [o, Hash[fields.map { |f| [f, o.field(f)] }]]
241
+ [o, Hash[fields.map do |f|
242
+ [f, o.field(f, props_values[o], perf_values[o])]
243
+ end]]
221
244
  rescue VIM::ManagedObjectNotFound
222
245
  next
223
246
  end
224
247
  end]
225
248
  end
249
+
250
+ def http_clone main_http
251
+ http = Net::HTTP.new(main_http.address, main_http.port)
252
+ http.use_ssl = true
253
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
254
+ #http.set_debug_output $stderr
255
+ http.start
256
+ err "certificate mismatch" unless main_http.peer_cert.to_der == http.peer_cert.to_der
257
+ return http
258
+ end
259
+
260
+ def http_download connection, http_path, local_path
261
+ http = http_clone connection.http
262
+
263
+ headers = { 'cookie' => connection.cookie }
264
+ http.request_get(http_path, headers) do |res|
265
+ case res
266
+ when Net::HTTPOK
267
+ len = res.content_length
268
+ count = 0
269
+ File.open(local_path, 'wb') do |io|
270
+ res.read_body do |segment|
271
+ count += segment.length
272
+ io.write segment
273
+ $stdout.write "\e[0G\e[Kdownloading #{count}/#{len} bytes (#{(count*100)/len}%)"
274
+ $stdout.flush
275
+ end
276
+ end
277
+ $stdout.puts
278
+ else
279
+ err "download failed: #{res.message}"
280
+ end
281
+ end
282
+ end
283
+
284
+
285
+ def http_upload connection, local_path, http_path
286
+ err "local file does not exist" unless File.exists? local_path
287
+
288
+ http = http_clone connection.http
289
+
290
+ File.open(local_path, 'rb') do |io|
291
+ stream = ProgressStream.new(io, io.stat.size) do |s|
292
+ $stdout.write "\e[0G\e[Kuploading #{s.count}/#{s.len} bytes (#{(s.count*100)/s.len}%)"
293
+ $stdout.flush
294
+ end
295
+
296
+ headers = {
297
+ 'cookie' => connection.cookie,
298
+ 'content-length' => io.stat.size.to_s,
299
+ 'Content-Type' => 'application/octet-stream',
300
+ }
301
+
302
+ request = Net::HTTP::Put.new http_path, headers
303
+ request.body_stream = stream
304
+ res = http.request(request)
305
+ $stdout.puts
306
+ case res
307
+ when Net::HTTPOK
308
+ else
309
+ err "upload failed: #{res.message}"
310
+ end
311
+ end
312
+ end
226
313
  end
227
314
  end
228
315
 
@@ -314,3 +401,21 @@ class MetricNumber < SimpleDelegator
314
401
  end
315
402
  end
316
403
  end
404
+
405
+ class ProgressStream
406
+ attr_reader :io, :len, :count
407
+
408
+ def initialize io, len, &b
409
+ @io = io
410
+ @len = len
411
+ @count = 0
412
+ @cb = b
413
+ end
414
+
415
+ def read n
416
+ io.read(n).tap do |c|
417
+ @count += c.length if c
418
+ @cb[self]
419
+ end
420
+ end
421
+ end
@@ -18,9 +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
- class RbVmomi::VIM::PerfCounterInfo
22
- def name
23
- "#{groupInfo.key}.#{nameInfo.key}.#{rollupType}"
24
- end
21
+ module RVC
22
+ VERSION = File.read(File.join(File.dirname(__FILE__), '..', '..', 'VERSION'))
25
23
  end
26
24