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.
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/rvc +46 -70
- data/devel/test-dependencies.sh +4 -0
- data/lib/rvc.rb +5 -6
- data/lib/rvc/command.rb +65 -0
- data/lib/rvc/command_slate.rb +112 -0
- data/lib/rvc/completion.rb +89 -58
- data/lib/rvc/connection.rb +48 -0
- data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +1 -1
- data/lib/rvc/extensions/DistributedVirtualSwitch.rb +3 -3
- data/lib/rvc/extensions/HostSystem.rb +90 -0
- data/lib/rvc/extensions/VirtualMachine.rb +37 -7
- data/lib/rvc/field.rb +59 -12
- data/lib/rvc/fs.rb +34 -4
- data/lib/rvc/inventory.rb +5 -1
- data/lib/rvc/modules/alarm.rb +2 -0
- data/lib/rvc/modules/basic.rb +66 -61
- data/lib/rvc/modules/cluster.rb +117 -22
- data/lib/rvc/modules/connection.rb +40 -0
- data/lib/rvc/modules/core.rb +4 -16
- data/lib/rvc/modules/datacenter.rb +2 -0
- data/lib/rvc/modules/datastore.rb +11 -78
- data/lib/rvc/modules/device.rb +40 -5
- data/lib/rvc/modules/diagnostics.rb +169 -0
- data/lib/rvc/modules/esxcli.rb +9 -5
- data/lib/rvc/modules/find.rb +5 -3
- data/lib/rvc/modules/host.rb +46 -3
- data/lib/rvc/modules/issue.rb +2 -0
- data/lib/rvc/modules/mark.rb +5 -3
- data/lib/rvc/modules/perf.rb +99 -33
- data/lib/rvc/modules/permissions.rb +2 -0
- data/lib/rvc/modules/resource_pool.rb +2 -0
- data/lib/rvc/modules/role.rb +3 -1
- data/lib/rvc/modules/snapshot.rb +12 -4
- data/lib/rvc/modules/statsinterval.rb +13 -11
- data/lib/rvc/modules/vds.rb +67 -10
- data/lib/rvc/modules/vim.rb +19 -53
- data/lib/rvc/modules/vm.rb +27 -6
- data/lib/rvc/modules/vm_guest.rb +490 -0
- data/lib/rvc/modules/vmrc.rb +60 -32
- data/lib/rvc/modules/vnc.rb +2 -0
- data/lib/rvc/namespace.rb +114 -0
- data/lib/rvc/option_parser.rb +12 -15
- data/lib/rvc/readline-ffi.rb +4 -1
- data/lib/rvc/ruby_evaluator.rb +84 -0
- data/lib/rvc/shell.rb +68 -83
- data/lib/rvc/uri_parser.rb +59 -0
- data/lib/rvc/util.rb +134 -29
- data/lib/rvc/{extensions/PerfCounterInfo.rb → version.rb} +2 -4
- data/lib/rvc/{memory_session.rb → vim.rb} +10 -32
- data/test/modules/foo.rb +9 -0
- data/test/modules/foo/bar.rb +9 -0
- data/test/test_completion.rb +17 -0
- data/test/test_fs.rb +9 -11
- data/test/test_help.rb +46 -0
- data/test/test_helper.rb +12 -0
- data/test/test_metric.rb +1 -2
- data/test/test_modules.rb +38 -0
- data/test/test_parse_path.rb +1 -2
- data/test/test_shell.rb +138 -0
- data/test/test_uri.rb +34 -0
- metadata +115 -81
- data/lib/rvc/extensions/PerformanceManager.rb +0 -83
- data/lib/rvc/filesystem_session.rb +0 -101
- data/lib/rvc/modules.rb +0 -138
data/lib/rvc/shell.rb
CHANGED
@@ -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, :
|
25
|
-
|
29
|
+
attr_reader :fs, :completion
|
30
|
+
attr_reader :connections, :connection
|
31
|
+
attr_accessor :debug, :cmds
|
26
32
|
|
27
|
-
def initialize
|
28
|
-
@session = session
|
33
|
+
def initialize
|
29
34
|
@persist_ruby = false
|
30
|
-
@fs = RVC::FS.new RVC::RootNode.new
|
31
|
-
@ruby_evaluator = RubyEvaluator.new
|
32
|
-
@
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
123
|
+
if cmd.parser.has_options?
|
124
|
+
cmd.invoke *(args + [opts])
|
108
125
|
else
|
109
|
-
|
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
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
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
|
224
|
-
@
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
data/lib/rvc/util.rb
CHANGED
@@ -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
|
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
|
-
|
22
|
-
|
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
|
|