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