rvc 1.4.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +9 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/rvc +28 -23
- data/lib/rvc/extensions/VirtualMachine.rb +68 -0
- data/lib/rvc/modules/basic.rb +35 -14
- data/lib/rvc/modules/cluster.rb +1 -0
- data/lib/rvc/modules/host.rb +20 -0
- data/lib/rvc/modules/snapshot.rb +61 -0
- data/lib/rvc/modules/statsinterval.rb +89 -0
- data/lib/rvc/modules/vim.rb +7 -2
- data/lib/rvc/modules/vm.rb +115 -46
- data/lib/rvc/modules/vnc.rb +1 -1
- data/lib/rvc/option_parser.rb +2 -1
- data/lib/rvc/shell.rb +26 -10
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -131,6 +131,15 @@ is represented by a top-level node in the virtual filesystem. If more than one
|
|
131
131
|
host is given on the command line, RVC will start in the root of the filesystem
|
132
132
|
instead of automatically cd'ing to a connection.
|
133
133
|
|
134
|
+
== VNC Clients
|
135
|
+
=== OSX
|
136
|
+
Using homebrew https://github.com/mxcl/homebrew install tiger-vnc using the
|
137
|
+
command
|
138
|
+
|
139
|
+
"brew install tiger-vnc"
|
140
|
+
|
141
|
+
this will allow you to use the vnc.view command
|
142
|
+
|
134
143
|
== Extensibility
|
135
144
|
|
136
145
|
RVC is designed to let users easily add commands they need. You can create a
|
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ begin
|
|
7
7
|
gem.email = "rlane@vmware.com"
|
8
8
|
#gem.homepage = ""
|
9
9
|
gem.authors = ["Rich Lane"]
|
10
|
-
gem.add_dependency 'rbvmomi', '>= 1.
|
10
|
+
gem.add_dependency 'rbvmomi', '>= 1.3.0'
|
11
11
|
gem.add_dependency 'trollop', '>= 1.16.2'
|
12
12
|
gem.add_dependency 'backports', '>= 1.18.2'
|
13
13
|
gem.add_dependency 'highline', '>= 1.6.1'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.5.0
|
data/bin/rvc
CHANGED
@@ -53,8 +53,11 @@ EOS
|
|
53
53
|
opt :path, "Initial directory", :short => :none, :default => ENV['RVC_PATH'], :type => :string
|
54
54
|
opt :create_directory, "Create the initial directory if it doesn't exist", :short => :none
|
55
55
|
opt :cmd, "command to evaluate", :short => 'c', :multi => true, :type => :string
|
56
|
+
opt :script, "file to execute", :short => 's', :type => :string
|
56
57
|
end
|
57
58
|
|
59
|
+
$interactive = $opts[:script].nil? and $stdin.tty?
|
60
|
+
|
58
61
|
RVC.reload_modules false
|
59
62
|
|
60
63
|
session = if ENV['RVC_SESSION'] and not ENV['RVC_SESSION'].empty?
|
@@ -66,7 +69,7 @@ end
|
|
66
69
|
$shell = RVC::Shell.new session
|
67
70
|
|
68
71
|
# Prompt for hostname if none given. Useful on win32.
|
69
|
-
if $shell.session.connections.empty? and ARGV.empty?
|
72
|
+
if $shell.session.connections.empty? and ARGV.empty? and $interactive
|
70
73
|
ARGV << Readline.readline("Host to connect to (user@host): ")
|
71
74
|
end
|
72
75
|
|
@@ -92,10 +95,12 @@ ARGV.each do |uri|
|
|
92
95
|
end
|
93
96
|
end
|
94
97
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
98
|
+
if $interactive
|
99
|
+
RVC::Completion.install
|
100
|
+
history_fn = "#{ENV['HOME']}/.rvc-history"
|
101
|
+
IO.foreach(history_fn) { |l| Readline::HISTORY << l.chomp } rescue puts "Welcome to RVC. Try the 'help' command."
|
102
|
+
history = File.open(history_fn, 'a')
|
103
|
+
end
|
99
104
|
|
100
105
|
if $opts[:path]
|
101
106
|
begin
|
@@ -106,7 +111,7 @@ if $opts[:path]
|
|
106
111
|
RVC::Util.lookup_single(parent_path).CreateFolder(:name => File.basename($opts[:path]))
|
107
112
|
CMD.basic.cd RVC::Util.lookup_single($opts[:path])
|
108
113
|
end
|
109
|
-
elsif $shell.connections.size == 1
|
114
|
+
elsif $shell.connections.size == 1 and $interactive
|
110
115
|
conn_name, conn = $shell.connections.first
|
111
116
|
if conn.serviceContent.about.apiType == 'VirtualCenter'
|
112
117
|
CMD.basic.cd RVC::Util.lookup_single(conn_name)
|
@@ -121,24 +126,24 @@ if defined? Ocra
|
|
121
126
|
exit
|
122
127
|
end
|
123
128
|
|
124
|
-
|
125
|
-
$stderr.puts "VMRC is not installed. You will be unable to view virtual machine consoles. Use the vmrc.install command to install it."
|
126
|
-
|
127
|
-
|
128
|
-
if $shell.connections.empty?
|
129
|
-
$stderr.puts "Use the 'connect' command to connect to an ESX or VC server."
|
129
|
+
if $interactive
|
130
|
+
$stderr.puts "VMRC is not installed. You will be unable to view virtual machine consoles. Use the vmrc.install command to install it." unless CMD.vmrc.find_vmrc
|
131
|
+
$stderr.puts "Use the 'connect' command to connect to an ESX or VC server." if $shell.connections.empty?
|
132
|
+
CMD.basic.ls RVC::Util.lookup_single('.')
|
130
133
|
end
|
131
134
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
135
|
+
if $opts[:script]
|
136
|
+
$shell.eval_ruby File.read($opts[:script]), $opts[:script]
|
137
|
+
else
|
138
|
+
while true
|
139
|
+
begin
|
140
|
+
input = $opts[:cmd].shift || Readline.readline($shell.prompt, false) or break
|
141
|
+
input = input.strip
|
142
|
+
next if input.empty?
|
143
|
+
(history.puts input; Readline::HISTORY << input) unless input == Readline::HISTORY.to_a[-1]
|
144
|
+
$shell.eval_input input
|
145
|
+
rescue Interrupt
|
146
|
+
puts
|
147
|
+
end
|
143
148
|
end
|
144
149
|
end
|
@@ -71,6 +71,7 @@ class RbVmomi::VIM::VirtualMachine
|
|
71
71
|
'datastores' => RVC::FakeFolder.new(self, :rvc_children_datastores),
|
72
72
|
'networks' => RVC::FakeFolder.new(self, :rvc_children_networks),
|
73
73
|
'files' => RVC::FakeFolder.new(self, :rvc_children_files),
|
74
|
+
'snapshots' => RVC::RootSnapshotFolder.new(self),
|
74
75
|
}
|
75
76
|
end
|
76
77
|
|
@@ -96,3 +97,70 @@ class RbVmomi::VIM::VirtualMachine
|
|
96
97
|
end]
|
97
98
|
end
|
98
99
|
end
|
100
|
+
|
101
|
+
class RVC::RootSnapshotFolder
|
102
|
+
include RVC::InventoryObject
|
103
|
+
|
104
|
+
def initialize vm
|
105
|
+
@vm = vm
|
106
|
+
end
|
107
|
+
|
108
|
+
def children
|
109
|
+
info = @vm.snapshot
|
110
|
+
return {} unless info
|
111
|
+
Hash[info.rootSnapshotList.map { |x| [x.name, RVC::SnapshotFolder.new(@vm, [x.id])] }]
|
112
|
+
end
|
113
|
+
|
114
|
+
def display_info
|
115
|
+
puts "Root of a VM's snapshot tree"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class RVC::SnapshotFolder
|
120
|
+
include RVC::InventoryObject
|
121
|
+
|
122
|
+
def initialize vm, ids
|
123
|
+
@vm = vm
|
124
|
+
@ids = ids
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.to_s
|
128
|
+
'Snapshot'
|
129
|
+
end
|
130
|
+
|
131
|
+
def find_tree
|
132
|
+
cur = nil
|
133
|
+
info = @vm.snapshot
|
134
|
+
fail "snapshot not found" unless info
|
135
|
+
children = info.rootSnapshotList
|
136
|
+
@ids.each do |id|
|
137
|
+
cur = children.find { |x| x.id == id }
|
138
|
+
fail "snapshot not found" unless cur
|
139
|
+
children = cur.childSnapshotList
|
140
|
+
end
|
141
|
+
cur
|
142
|
+
end
|
143
|
+
|
144
|
+
def children
|
145
|
+
tree = find_tree
|
146
|
+
{}.tap do |h|
|
147
|
+
tree.childSnapshotList.each do |x|
|
148
|
+
name = x.name
|
149
|
+
name = x.name + '.1' if h.member? x.name
|
150
|
+
while h.member? name
|
151
|
+
name = name.succ
|
152
|
+
end
|
153
|
+
h[name] = RVC::SnapshotFolder.new(@vm, @ids+[x.id])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def display_info
|
159
|
+
tree = find_tree
|
160
|
+
puts "id: #{tree.id}"
|
161
|
+
puts "name: #{tree.name}"
|
162
|
+
puts "description: #{tree.description}"
|
163
|
+
puts "state: #{tree.state}"
|
164
|
+
puts "creation time: #{tree.createTime}"
|
165
|
+
end
|
166
|
+
end
|
data/lib/rvc/modules/basic.rb
CHANGED
@@ -42,7 +42,14 @@ rvc_alias :help
|
|
42
42
|
HELP_ORDER = %w(basic vm)
|
43
43
|
|
44
44
|
def help path
|
45
|
-
if
|
45
|
+
if mod = RVC::MODULES[path]
|
46
|
+
opts = mod.instance_variable_get(:@opts)
|
47
|
+
opts.each do |method_name,method_opts|
|
48
|
+
parser = RVC::OptionParser.new method_name, &method_opts
|
49
|
+
help_summary parser, path, method_name
|
50
|
+
end
|
51
|
+
return
|
52
|
+
elsif tgt = RVC::ALIASES[path]
|
46
53
|
fail unless tgt =~ /^(.+)\.(.+)$/
|
47
54
|
opts_block = RVC::MODULES[$1].opts_for($2.to_sym)
|
48
55
|
RVC::OptionParser.new(tgt, &opts_block).educate
|
@@ -69,9 +76,7 @@ def help path
|
|
69
76
|
opts.each do |method_name,method_opts|
|
70
77
|
parser = RVC::OptionParser.new method_name, &method_opts
|
71
78
|
next unless obj.nil? or parser.applicable.any? { |x| obj.is_a? x }
|
72
|
-
|
73
|
-
aliases_text = aliases.empty? ? '' : " (#{aliases*', '})"
|
74
|
-
puts "#{mod_name}.#{method_name}#{aliases_text}: #{parser.summary?}" if parser.summary?
|
79
|
+
help_summary parser, mod_name, method_name
|
75
80
|
end
|
76
81
|
end
|
77
82
|
|
@@ -84,6 +89,12 @@ To show only commands relevant to a specific object, use "help /path/to/object".
|
|
84
89
|
end
|
85
90
|
end
|
86
91
|
|
92
|
+
def help_summary parser, mod_name, method_name
|
93
|
+
aliases = ALIASES.select { |k,v| v == "#{mod_name}.#{method_name}" }.map(&:first)
|
94
|
+
aliases_text = aliases.empty? ? '' : " (#{aliases*', '})"
|
95
|
+
puts "#{mod_name}.#{method_name}#{aliases_text}: #{parser.summary?}" if parser.summary?
|
96
|
+
end
|
97
|
+
|
87
98
|
|
88
99
|
opts :debug do
|
89
100
|
summary "Toggle VMOMI logging to stderr"
|
@@ -253,20 +264,30 @@ end
|
|
253
264
|
|
254
265
|
|
255
266
|
opts :mv do
|
256
|
-
summary "Move
|
257
|
-
|
258
|
-
arg :
|
267
|
+
summary "Move entities to another folder"
|
268
|
+
text "The entities' names are unchanged."
|
269
|
+
arg :objs, "Entities to move", :lookup => VIM::ManagedEntity, :multi => true
|
259
270
|
end
|
260
271
|
|
261
272
|
rvc_alias :mv
|
262
273
|
|
263
|
-
def mv
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
274
|
+
def mv objs
|
275
|
+
err "Destination entity missing" unless objs.size > 1
|
276
|
+
dst = objs.pop
|
277
|
+
progress [dst.MoveIntoFolder_Task(:list => objs)]
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
opts :rename do
|
282
|
+
summary "Rename an entity"
|
283
|
+
arg :objs, "Entity to rename", :lookup => VIM::ManagedEntity
|
284
|
+
arg :name, "New name"
|
285
|
+
end
|
286
|
+
|
287
|
+
rvc_alias :rename
|
288
|
+
|
289
|
+
def rename obj, name
|
290
|
+
progress [obj.Rename_Task(:newName => name)]
|
270
291
|
end
|
271
292
|
|
272
293
|
|
data/lib/rvc/modules/cluster.rb
CHANGED
data/lib/rvc/modules/host.rb
CHANGED
@@ -140,3 +140,23 @@ def add_iscsi_target hosts, opts
|
|
140
140
|
storage.RescanAllHba
|
141
141
|
end
|
142
142
|
end
|
143
|
+
|
144
|
+
opts :add_nfs_datastore do
|
145
|
+
arg :host, nil, :lookup => VIM::HostSystem, :multi => true
|
146
|
+
opt :name, "Datastore name", :short => 'n', :type => :string, :required => true
|
147
|
+
opt :address, "Address of NFS server", :short => 'a', :type => :string, :required => true
|
148
|
+
opt :path, "Path on NFS server", :short => 'p', :type => :string, :required => true
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_nfs_datastore hosts, opts
|
152
|
+
hosts.each do |host|
|
153
|
+
datastoreSystem, = host.collect 'configManager.datastoreSystem'
|
154
|
+
spec = {
|
155
|
+
:accessMode => 'readWrite',
|
156
|
+
:localPath => opts[:name],
|
157
|
+
:remoteHost => opts[:address],
|
158
|
+
:remotePath => opts[:path]
|
159
|
+
}
|
160
|
+
datastoreSystem.CreateNasDatastore :spec => spec
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
opts :create do
|
2
|
+
summary "Snapshot a VM"
|
3
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
4
|
+
arg :name, "Name of new snapshot"
|
5
|
+
opt :description, "Description", :short => 'd', :default => ""
|
6
|
+
opt :quiesce, "Quiesce", :short => 'q', :default => false
|
7
|
+
opt :memory, "Memory", :short => 'm', :default => true
|
8
|
+
end
|
9
|
+
|
10
|
+
def create vm, name, opts
|
11
|
+
tasks [vm], :CreateSnapshot, :description => opts[:description], :memory => opts[:memory], :name => name, :quiesce => opts[:quiesce]
|
12
|
+
end
|
13
|
+
|
14
|
+
rvc_alias :create, :snapshot
|
15
|
+
|
16
|
+
|
17
|
+
opts :revert do
|
18
|
+
summary "Revert a VM to a snapshot. Defaults to the current snapshot."
|
19
|
+
arg :arg, nil, :lookup => [VIM::VirtualMachine, RVC::SnapshotFolder]
|
20
|
+
end
|
21
|
+
|
22
|
+
def revert arg
|
23
|
+
if arg.is_a? VIM::VirtualMachine
|
24
|
+
tasks [arg], :RevertToCurrentSnapshot
|
25
|
+
else
|
26
|
+
tasks [arg.find_tree.snapshot], :RevertToSnapshot
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
opts :rename do
|
32
|
+
summary "Rename a snapshot"
|
33
|
+
arg :snapshot, nil, :lookup => RVC::SnapshotFolder
|
34
|
+
arg :name, "New name", :type => :string
|
35
|
+
end
|
36
|
+
|
37
|
+
def rename snapshot, name
|
38
|
+
snapshot.find_tree.snapshot.RenameSnapshot :name => name
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
opts :describe do
|
43
|
+
summary "Change the description of a snapshot"
|
44
|
+
arg :snapshot, nil, :lookup => RVC::SnapshotFolder
|
45
|
+
arg :description, "New description", :type => :string
|
46
|
+
end
|
47
|
+
|
48
|
+
def describe snapshot, description
|
49
|
+
snapshot.find_tree.snapshot.RenameSnapshot :description => description
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
opts :remove do
|
54
|
+
summary "Remove a snapshot"
|
55
|
+
arg :snapshot, nil, :lookup => RVC::SnapshotFolder
|
56
|
+
opt :remove_children, "Whether to remove the snapshot's children too"
|
57
|
+
end
|
58
|
+
|
59
|
+
def remove snapshot, opts
|
60
|
+
tasks [snapshot.find_tree.snapshot], :RemoveSnapshot, :removeChildren => opts[:remove_children]
|
61
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Copyright (c) 2011 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
|
+
|
21
|
+
#see vSphere Client: Administration -> vCenter Server Settings -> Statistics -> Statistics Intervals
|
22
|
+
|
23
|
+
def stats_time secs
|
24
|
+
length = secs / 60
|
25
|
+
|
26
|
+
[[60, "Minutes"], [24, "Hours"], [7, "Days"], [4, "Weeks"], [12, "Months"]].each do |div, name|
|
27
|
+
if length < div
|
28
|
+
return "#{length} #{name}"
|
29
|
+
end
|
30
|
+
length = length / div
|
31
|
+
end
|
32
|
+
|
33
|
+
return "#{length} Years"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
opts :list do
|
38
|
+
summary "List intervals for collecting vCenter statistics"
|
39
|
+
end
|
40
|
+
|
41
|
+
def list
|
42
|
+
conn = single_connection [$shell.fs.cur]
|
43
|
+
perfman = conn.serviceContent.perfManager
|
44
|
+
|
45
|
+
columns = [" Name ", "Interval Duration", "Save For ", "Statistics Level"]
|
46
|
+
format = columns.map {|s| "%-#{s.size}s"}.join(" | ")
|
47
|
+
puts sprintf format, *columns
|
48
|
+
puts columns.map {|s| "-" * s.size }.join("-+-")
|
49
|
+
|
50
|
+
perfman.historicalInterval.each do |interval|
|
51
|
+
puts sprintf format, "[#{interval.enabled ? 'x' : ' '}] #{interval.name}",
|
52
|
+
stats_time(interval.samplingPeriod),
|
53
|
+
stats_time(interval.length), interval.level.to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
opts :update do
|
59
|
+
summary "Update intervals for collecting vCenter statistics"
|
60
|
+
arg :name, "Name of the historical interval"
|
61
|
+
opt :period, "Number of seconds that data is sampled", :short => 's', :type => :int, :required => false
|
62
|
+
opt :length, "Number of seconds that the statistics are saved", :short => 'l', :type => :int, :required => false
|
63
|
+
opt :level, "Statistics collection level", :short => 'v', :type => :int, :required => false
|
64
|
+
opt :enabled, "Enable or disable the the interval", :short => 'e', :type => :string, :required => false
|
65
|
+
end
|
66
|
+
|
67
|
+
def update name, opts
|
68
|
+
conn = single_connection [$shell.fs.cur]
|
69
|
+
perfman = conn.serviceContent.perfManager
|
70
|
+
|
71
|
+
interval = perfman.historicalInterval.select {|i| i.name == name or i.name == "Past #{name}" }.first
|
72
|
+
err "no such interval" unless interval
|
73
|
+
|
74
|
+
interval.samplingPeriod = opts[:period] if opts[:period]
|
75
|
+
interval.length = opts[:length] if opts[:length]
|
76
|
+
interval.level = opts[:level] if opts[:level]
|
77
|
+
|
78
|
+
case opts[:enabled]
|
79
|
+
when nil
|
80
|
+
when "false"
|
81
|
+
interval.enabled = false
|
82
|
+
when "true"
|
83
|
+
interval.enabled = true
|
84
|
+
else
|
85
|
+
err "invalid value for enabled option"
|
86
|
+
end
|
87
|
+
|
88
|
+
perfman.UpdatePerfInterval(:interval => interval)
|
89
|
+
end
|
data/lib/rvc/modules/vim.rb
CHANGED
@@ -81,14 +81,19 @@ def connect uri, opts
|
|
81
81
|
unless opts[:rev]
|
82
82
|
# negotiate API version
|
83
83
|
rev = vim.serviceContent.about.apiVersion
|
84
|
-
vim.rev = [rev, ENV['RVC_VIMREV'] || '
|
84
|
+
vim.rev = [rev, ENV['RVC_VIMREV'] || '5.0'].min
|
85
85
|
end
|
86
86
|
|
87
87
|
isVC = vim.serviceContent.about.apiType == "VirtualCenter"
|
88
88
|
|
89
89
|
# authenticate
|
90
90
|
if username == nil
|
91
|
-
|
91
|
+
if isVC
|
92
|
+
isLinux = vim.serviceContent.about.osType == "linux-x64"
|
93
|
+
username = isLinux ? 'root' : 'Administrator'
|
94
|
+
else
|
95
|
+
username = 'root'
|
96
|
+
end
|
92
97
|
puts "Using default username #{username.inspect}."
|
93
98
|
end
|
94
99
|
|
data/lib/rvc/modules/vm.rb
CHANGED
@@ -104,14 +104,46 @@ opts :create do
|
|
104
104
|
opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
|
105
105
|
opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
|
106
106
|
opt :datastore, "Datastore", :short => 'd', :type => :string, :lookup => VIM::Datastore
|
107
|
-
opt :disksize, "Size in KB of primary disk", :short => 's', :type => :
|
107
|
+
opt :disksize, "Size in KB of primary disk (or add a unit of <M|G|T>)", :short => 's', :type => :string, :default => "4000000"
|
108
108
|
opt :memory, "Size in MB of memory", :short => 'm', :type => :int, :default => 128
|
109
109
|
opt :cpucount, "Number of CPUs", :short => 'c', :type => :int, :default => 1
|
110
|
+
text <<-EOB
|
111
|
+
|
112
|
+
Example:
|
113
|
+
vm.create -p ~foo/resourcePool/pools/prod -d ~data/bigdisk -s 10g ~vms/new
|
114
|
+
|
115
|
+
EOB
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def realdisksize( size )
|
120
|
+
size.downcase!
|
121
|
+
if size =~ /([0-9][0-9,]*)([mgt])?/i
|
122
|
+
size = $1.delete(',').to_i
|
123
|
+
unit = $2
|
124
|
+
|
125
|
+
case unit
|
126
|
+
when 'm'
|
127
|
+
return size * 1024
|
128
|
+
when 'g'
|
129
|
+
return size * ( 1024 ** 2 )
|
130
|
+
when 't'
|
131
|
+
return size * ( 1024 ** 3 )
|
132
|
+
when nil
|
133
|
+
return size
|
134
|
+
else
|
135
|
+
err "Unknown size modifer of '#{unit}'"
|
136
|
+
end
|
137
|
+
else
|
138
|
+
err "Problem with #{size}"
|
139
|
+
end
|
110
140
|
end
|
111
141
|
|
142
|
+
|
112
143
|
def create dest, opts
|
113
144
|
err "must specify resource pool (--pool)" unless opts[:pool]
|
114
145
|
err "must specify datastore (--datastore)" unless opts[:datastore]
|
146
|
+
err "memory must be a multiple of 4MB" unless opts[:memory] % 4 == 0
|
115
147
|
vmFolder, name = *dest
|
116
148
|
datastore_path = "[#{opts[:datastore].name}]"
|
117
149
|
config = {
|
@@ -140,7 +172,7 @@ def create dest, opts
|
|
140
172
|
),
|
141
173
|
:controllerKey => 1000,
|
142
174
|
:unitNumber => 0,
|
143
|
-
:capacityInKB => opts[:disksize]
|
175
|
+
:capacityInKB => realdisksize( opts[:disksize] )
|
144
176
|
)
|
145
177
|
}, {
|
146
178
|
:operation => :add,
|
@@ -217,6 +249,58 @@ def register vmx_file, opts
|
|
217
249
|
:pool => rp).wait_for_completion
|
218
250
|
end
|
219
251
|
|
252
|
+
opts :bootconfig do
|
253
|
+
summary "Alter the boot config settings"
|
254
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
255
|
+
opt :delay, "Time in milliseconds to delay boot", :short => 'd', :type => :int
|
256
|
+
opt :enablebootretry, "Enable rebooting if no boot device found", :short => 'r'
|
257
|
+
opt :disablebootretry, "Disable rebooting if no boot device found"
|
258
|
+
opt :retrydelay, "Time to wait before rebooting to retry", :short => 't', :type => :int
|
259
|
+
opt :show, "Show the current bootoptions", :short => 's'
|
260
|
+
conflicts :enablebootretry, :disablebootretry # not that this currently works nicely, but still.
|
261
|
+
end
|
262
|
+
|
263
|
+
def bootconfig vm, opts
|
264
|
+
|
265
|
+
if opts[:show]
|
266
|
+
pp vm.config.bootOptions
|
267
|
+
return
|
268
|
+
end
|
269
|
+
|
270
|
+
cur_delay = vm.config.bootOptions.bootDelay
|
271
|
+
cur_retrydelay = vm.config.bootOptions.bootRetryDelay
|
272
|
+
cur_retryenabled = vm.config.bootOptions.bootRetryEnabled
|
273
|
+
|
274
|
+
if opts[:delay] and opts[:delay] != cur_delay
|
275
|
+
new_delay = opts[:delay]
|
276
|
+
else
|
277
|
+
new_delay = cur_delay
|
278
|
+
end
|
279
|
+
|
280
|
+
if opts[:retrydelay] and opts[:retrydelay] != cur_retrydelay
|
281
|
+
new_retrydelay = opts[:retrydelay]
|
282
|
+
new_retryenabled = true
|
283
|
+
else
|
284
|
+
new_retrydelay = cur_retrydelay
|
285
|
+
end
|
286
|
+
|
287
|
+
if opts[:enablebootretry]
|
288
|
+
new_retryenabled = true
|
289
|
+
elsif opts[:disablebootretry]
|
290
|
+
new_retryenabled = false
|
291
|
+
else
|
292
|
+
new_retryenabled = cur_retryenabled
|
293
|
+
end
|
294
|
+
|
295
|
+
spec = { :bootOptions => {
|
296
|
+
:bootDelay => new_delay,
|
297
|
+
:bootRetryDelay => new_retrydelay,
|
298
|
+
:bootRetryEnabled => new_retryenabled,
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
vm.ReconfigVM_Task(:spec => spec).wait_for_completion
|
303
|
+
end
|
220
304
|
|
221
305
|
opts :unregister do
|
222
306
|
summary "Unregister a VM"
|
@@ -250,6 +334,8 @@ opts :answer do
|
|
250
334
|
end
|
251
335
|
|
252
336
|
def answer vm, str
|
337
|
+
q = vm.runtime.question
|
338
|
+
err "no question" unless q
|
253
339
|
choice = q.choice.choiceInfo.find { |x| x.label == str }
|
254
340
|
err("invalid answer") unless choice
|
255
341
|
vm.AnswerVM :questionid => q.path, :answerChoice => choice.key
|
@@ -391,17 +477,18 @@ end
|
|
391
477
|
opts :rvc do
|
392
478
|
summary "RVC to a VM"
|
393
479
|
arg :vm, nil, :lookup => VIM::VirtualMachine
|
480
|
+
opt :user, "Username", :type => :string
|
394
481
|
end
|
395
482
|
|
396
483
|
rvc_alias :rvc
|
397
484
|
|
398
|
-
def rvc vm
|
485
|
+
def rvc vm, opts
|
399
486
|
ip = vm_ip vm
|
400
487
|
|
401
488
|
env = Hash[%w(RBVMOMI_PASSWORD RBVMOMI_HOST RBVMOMI_USER RBVMOMI_SSL RBVMOMI_PORT
|
402
489
|
RBVMOMI_FOLDER RBVMOMI_DATASTORE RBVMOMI_PATH RBVMOMI_DATACENTER
|
403
490
|
RBVMOMI_COMPUTER).map { |k| [k,nil] }]
|
404
|
-
cmd = "rvc #{Shellwords.escape ip}"
|
491
|
+
cmd = "rvc #{opts[:user] && Shellwords.escape("#{opts[:user]}@")}#{Shellwords.escape ip}"
|
405
492
|
system_fg(cmd, env)
|
406
493
|
end
|
407
494
|
|
@@ -515,48 +602,6 @@ def remove_device vm, label
|
|
515
602
|
end
|
516
603
|
|
517
604
|
|
518
|
-
opts :snapshot do
|
519
|
-
summary "Snapshot a VM"
|
520
|
-
arg :vm, nil, :lookup => VIM::VirtualMachine
|
521
|
-
arg :name, "Name of new snapshot"
|
522
|
-
opt :description, "Description", :short => 'd', :default => ""
|
523
|
-
opt :quiesce, "Quiesce", :short => 'q', :default => false
|
524
|
-
opt :memory, "Memory", :short => 'm', :default => true
|
525
|
-
end
|
526
|
-
|
527
|
-
def snapshot vm, name, opts
|
528
|
-
tasks [vm], :CreateSnapshot, :description => opts[:description], :memory => opts[:memory], :name => name, :quiesce => opts[:quiesce]
|
529
|
-
end
|
530
|
-
|
531
|
-
|
532
|
-
# TODO make fake folder
|
533
|
-
opts :snapshots do
|
534
|
-
summary "Display VM snapshot tree"
|
535
|
-
arg :vm, nil, :lookup => VIM::VirtualMachine
|
536
|
-
end
|
537
|
-
|
538
|
-
def snapshots vm
|
539
|
-
_display_snapshot_tree vm.snapshot.rootSnapshotList, 0
|
540
|
-
end
|
541
|
-
|
542
|
-
def _display_snapshot_tree nodes, indent
|
543
|
-
nodes.each do |node|
|
544
|
-
puts "#{' '*indent}#{node.name} #{node.createTime}"
|
545
|
-
_display_snapshot_tree node.childSnapshotList, (indent+1)
|
546
|
-
end
|
547
|
-
end
|
548
|
-
|
549
|
-
|
550
|
-
opts :revert do
|
551
|
-
summary "Revert a VM to its current snapshot"
|
552
|
-
arg :vm, nil, :lookup => VIM::VirtualMachine
|
553
|
-
end
|
554
|
-
|
555
|
-
def revert vm
|
556
|
-
tasks [vm], :RevertToCurrentSnapshot
|
557
|
-
end
|
558
|
-
|
559
|
-
|
560
605
|
opts :migrate do
|
561
606
|
summary "Migrate a VM"
|
562
607
|
arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
|
@@ -639,6 +684,30 @@ def annotate vm, str
|
|
639
684
|
end
|
640
685
|
|
641
686
|
|
687
|
+
opts :modify_cpu do
|
688
|
+
summary "Change CPU configuration"
|
689
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
690
|
+
opt :num, "New number of CPUs", :type => :int, :required => true
|
691
|
+
end
|
692
|
+
|
693
|
+
def modify_cpu vm, opts
|
694
|
+
spec = { :numCPUs => opts[:num] }
|
695
|
+
tasks [vm], :ReconfigVM, :spec => spec
|
696
|
+
end
|
697
|
+
|
698
|
+
|
699
|
+
opts :modify_memory do
|
700
|
+
summary "Change memory configuration"
|
701
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
702
|
+
opt :size, "New memory size in MB", :type => :int, :required => true
|
703
|
+
end
|
704
|
+
|
705
|
+
def modify_memory vm, opts
|
706
|
+
spec = { :memoryMB => opts[:size] }
|
707
|
+
tasks [vm], :ReconfigVM, :spec => spec
|
708
|
+
end
|
709
|
+
|
710
|
+
|
642
711
|
def find_vmx_files ds
|
643
712
|
datastorePath = "[#{ds.name}] /"
|
644
713
|
searchSpec = {
|
data/lib/rvc/modules/vnc.rb
CHANGED
@@ -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')
|
21
|
+
VNC = ENV['VNC'] || search_path('vinagre') || search_path('tightvnc') || search_path('vncviewer')
|
22
22
|
|
23
23
|
opts :view do
|
24
24
|
summary "Spawn a VNC client"
|
data/lib/rvc/option_parser.rb
CHANGED
@@ -76,7 +76,8 @@ class OptionParser < Trollop::Parser
|
|
76
76
|
@args << [name,spec]
|
77
77
|
description = "Path to a" if description == nil and spec[:lookup]
|
78
78
|
description = "Child of a" if description == nil and spec[:lookup_parent]
|
79
|
-
|
79
|
+
lookups = [spec[:lookup], spec[:lookup_parent]].flatten.compact
|
80
|
+
text " #{name}: " + [description, lookups*' or '].compact.join(' ')
|
80
81
|
end
|
81
82
|
|
82
83
|
def parse argv
|
data/lib/rvc/shell.rb
CHANGED
@@ -113,14 +113,15 @@ class Shell
|
|
113
113
|
nil
|
114
114
|
end
|
115
115
|
|
116
|
-
def eval_ruby input
|
117
|
-
result = @ruby_evaluator.do_eval input
|
118
|
-
if
|
119
|
-
|
120
|
-
|
121
|
-
|
116
|
+
def eval_ruby input, file="<input>"
|
117
|
+
result = @ruby_evaluator.do_eval input, file
|
118
|
+
if $interactive
|
119
|
+
if input =~ /\#$/
|
120
|
+
introspect_object result
|
121
|
+
else
|
122
|
+
pp result
|
123
|
+
end
|
122
124
|
end
|
123
|
-
nil
|
124
125
|
end
|
125
126
|
|
126
127
|
def prompt
|
@@ -187,13 +188,27 @@ class Shell
|
|
187
188
|
end
|
188
189
|
|
189
190
|
class RubyEvaluator
|
191
|
+
include RVC::Util
|
192
|
+
|
190
193
|
def initialize fs
|
191
|
-
@binding =
|
194
|
+
@binding = toplevel
|
192
195
|
@fs = fs
|
193
196
|
end
|
194
197
|
|
195
|
-
def
|
196
|
-
|
198
|
+
def toplevel
|
199
|
+
binding
|
200
|
+
end
|
201
|
+
|
202
|
+
def do_eval input, file
|
203
|
+
begin
|
204
|
+
eval input, @binding, file
|
205
|
+
rescue Exception => e
|
206
|
+
bt = e.backtrace
|
207
|
+
bt = bt.reverse.drop_while { |x| !(x =~ /toplevel/) }.reverse
|
208
|
+
bt[-1].gsub! ':in `toplevel\'', ''
|
209
|
+
e.set_backtrace bt
|
210
|
+
raise
|
211
|
+
end
|
197
212
|
end
|
198
213
|
|
199
214
|
def this
|
@@ -209,6 +224,7 @@ class RubyEvaluator
|
|
209
224
|
end
|
210
225
|
|
211
226
|
def method_missing sym, *a
|
227
|
+
super unless $shell
|
212
228
|
str = sym.to_s
|
213
229
|
if a.empty?
|
214
230
|
if MODULES.member? str
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: rvc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 1.
|
5
|
+
version: 1.5.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Rich Lane
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-09-02 00:00:00 -07:00
|
14
14
|
default_executable: rvc
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
requirements:
|
22
22
|
- - ">="
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: 1.
|
24
|
+
version: 1.3.0
|
25
25
|
type: :runtime
|
26
26
|
version_requirements: *id001
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -114,6 +114,8 @@ files:
|
|
114
114
|
- lib/rvc/modules/permissions.rb
|
115
115
|
- lib/rvc/modules/resource_pool.rb
|
116
116
|
- lib/rvc/modules/role.rb
|
117
|
+
- lib/rvc/modules/snapshot.rb
|
118
|
+
- lib/rvc/modules/statsinterval.rb
|
117
119
|
- lib/rvc/modules/vim.rb
|
118
120
|
- lib/rvc/modules/vm.rb
|
119
121
|
- lib/rvc/modules/vmrc.rb
|