rvc 1.6.0 → 1.7.0

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