rvc 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -5,8 +5,8 @@ begin
5
5
  gem.summary = "vSphere console UI"
6
6
  #gem.description = ""
7
7
  gem.email = "rlane@vmware.com"
8
- #gem.homepage = ""
9
- gem.authors = ["Rich Lane"]
8
+ gem.homepage = "https://github.com/vmware/rvc"
9
+ gem.authors = ["Rich Lane", "Christian Dickmann"]
10
10
  gem.add_dependency 'rbvmomi', '>= 1.6.0'
11
11
  gem.add_dependency 'trollop', '>= 1.16.2'
12
12
  gem.add_dependency 'backports', '>= 1.18.2'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.7.0
1
+ 1.8.0
@@ -164,7 +164,7 @@ class Completion
164
164
  base = absolute ? @shell.fs.root : @shell.fs.cur
165
165
  cur = @shell.fs.traverse(base, arcs).first or return []
166
166
  arcs.unshift '' if absolute
167
- children = @cache[cur, :children] rescue []
167
+ children = @cache[cur, :rvc_children] rescue []
168
168
  children.
169
169
  select { |k,v| k.gsub(' ', '\\ ') =~ /^#{Regexp.escape(last)}/ }.
170
170
  map { |k,v| (arcs+[k])*'/' }.
@@ -39,4 +39,15 @@ class RbVmomi::VIM::ClusterComputeResource
39
39
  def rvc_host_children
40
40
  RVC::Util.collect_children self, :host
41
41
  end
42
+
43
+ def display_info
44
+ super
45
+ pc = _connection.serviceContent.propertyCollector
46
+ cfg, = collect 'configurationEx'
47
+ drs = cfg.drsConfig
48
+ ha = cfg.dasConfig
49
+ puts "DRS: #{drs.enabled ? drs.defaultVmBehavior : 'disabled'}"
50
+ puts "HA: #{ha.enabled ? 'enabled' : 'disabled'}"
51
+ puts "VM Swap Placement: #{cfg.vmSwapPlacement}"
52
+ end
42
53
  end
@@ -21,15 +21,19 @@
21
21
  class RbVmomi::VIM::ComputeResource
22
22
  # TODO expand, optimize
23
23
  def display_info
24
+ pc = _connection.serviceContent.propertyCollector
25
+ name, host = collect 'name', 'host'
24
26
  stats = self.stats
25
27
  pct_cpu_used = 100.0*stats[:usedCPU]/stats[:totalCPU]
26
28
  pct_mem_used = 100.0*stats[:usedMem]/stats[:totalMem]
27
29
  puts "name: #{name}"
28
30
  puts "cpu: #{stats[:totalCPU]/1e3} GHz (#{pct_cpu_used.to_i}% used)"
29
31
  puts "memory: #{stats[:totalMem]/1e3} GB (#{pct_mem_used.to_i}% used)"
32
+
33
+ host_names = pc.collectMultiple host, 'name'
30
34
  puts "hosts:"
31
35
  host.each do |h|
32
- puts " #{h.name}"
36
+ puts " #{host_names[h]['name']}"
33
37
  end
34
38
  end
35
39
 
@@ -36,7 +36,7 @@ class RbVmomi::VIM::Datacenter
36
36
 
37
37
  # For compatibility with previous RVC versions
38
38
  def traverse_one arc
39
- children = self.children
39
+ children = self.rvc_children
40
40
  return children[arc] if children.member? arc
41
41
  if arc == 'vm' then return vmFolder
42
42
  elsif arc == 'datastore' then return datastoreFolder
@@ -41,6 +41,21 @@ class RbVmomi::VIM::HostSystem
41
41
  property 'runtime.powerState'
42
42
  end
43
43
 
44
+ field 'state.maintenancemode' do
45
+ summary "Host maintenance mode."
46
+ property 'runtime.inMaintenanceMode'
47
+ end
48
+
49
+ field 'build' do
50
+ summary "ESX build number."
51
+ property 'summary.config.product.build'
52
+ end
53
+
54
+ field 'productname' do
55
+ summary "ESX product name."
56
+ property 'summary.config.product.fullName'
57
+ end
58
+
44
59
  field 'uptime' do
45
60
  summary "Host's uptime in days"
46
61
  properties %w(runtime.bootTime)
@@ -155,6 +155,7 @@ class RbVmomi::VIM::VirtualMachine
155
155
  puts "hostname: #{guest.hostName} (#{guest.ipAddress})" if guest.hostName and guest.ipAddress
156
156
  puts "VC UUID: #{config.instanceUuid}" if config.instanceUuid and !config.instanceUuid.empty?
157
157
  puts "power: #{runtime.powerState}"
158
+ puts "connectionState: #{runtime.connectionState}"
158
159
  if runtime.question
159
160
  puts "question: #{runtime.question.text.lines.to_a.join("> ")}"
160
161
  puts "choices: #{runtime.question.choice.choiceInfo.map(&:label) * ', '}"
@@ -190,11 +191,15 @@ class RbVmomi::VIM::VirtualMachine
190
191
  end
191
192
 
192
193
  def self.ls_properties
193
- %w(name runtime.powerState)
194
+ %w(name runtime.powerState runtime.connectionState)
194
195
  end
195
196
 
196
197
  def ls_text r
197
- ": #{r['runtime.powerState']}"
198
+ out = ": #{r['runtime.powerState']}"
199
+ if r['runtime.connectionState'] != 'connected'
200
+ out = "#{out}, #{r['runtime.connectionState']}"
201
+ end
202
+ out
198
203
  end
199
204
 
200
205
  def children
@@ -78,10 +78,10 @@ class FS
78
78
  end
79
79
  when REGEX_PATTERN
80
80
  regex = Regexp.new($')
81
- cur.children.select { |k,v| k =~ regex }.map { |k,v| v.rvc_link(cur, k); v }
81
+ cur.rvc_children.select { |k,v| k =~ regex }.map { |k,v| v.rvc_link(cur, k); v }
82
82
  when GLOB_PATTERN
83
83
  regex = glob_to_regex arc
84
- cur.children.select { |k,v| k =~ regex }.map { |k,v| v.rvc_link(cur, k); v }
84
+ cur.rvc_children.select { |k,v| k =~ regex }.map { |k,v| v.rvc_link(cur, k); v }
85
85
  else
86
86
  # XXX check for ambiguous child
87
87
  if first and arc =~ /^\d+$/ and objs = @marks[arc]
@@ -65,12 +65,20 @@ module InventoryObject
65
65
  end
66
66
 
67
67
  def traverse_one arc
68
- children[arc]
68
+ rvc_children[arc]
69
69
  end
70
70
 
71
71
  def children
72
72
  {}
73
73
  end
74
+
75
+ def rvc_children
76
+ out = self.children
77
+ methods.grep(/rvc_list_children_/).each do |method|
78
+ out.merge!(self.send(method))
79
+ end
80
+ out
81
+ end
74
82
 
75
83
  def rvc_path
76
84
  [].tap do |a|
@@ -146,7 +146,7 @@ def ls obj
146
146
  return obj.rvc_ls
147
147
  end
148
148
 
149
- children = obj.children
149
+ children = obj.rvc_children
150
150
  name_map = children.invert
151
151
  children, fake_children = children.partition { |k,v| v.is_a? VIM::ManagedEntity }
152
152
  i = 0
@@ -265,6 +265,9 @@ def info obj
265
265
  puts "path: #{obj.rvc_path_str}"
266
266
  if obj.respond_to? :display_info
267
267
  obj.display_info
268
+ obj.methods.grep(/rvc_display_info_/).sort.each do |method|
269
+ obj.send(method)
270
+ end
268
271
  else
269
272
  puts "class: #{obj.class.name}"
270
273
  end
@@ -75,17 +75,61 @@ end
75
75
 
76
76
  opts :configure_ha do
77
77
  summary "Configure HA on a cluster"
78
- arg :cluster, nil, :lookup => VIM::ClusterComputeResource
78
+ arg :cluster, nil, :lookup => VIM::ClusterComputeResource, :multi => true
79
79
  opt :disabled, "Disable HA", :default => false
80
80
  end
81
81
 
82
- def configure_ha cluster, opts
82
+ def configure_ha clusters, opts
83
83
  spec = VIM::ClusterConfigSpecEx(
84
84
  :dasConfig => {
85
85
  :enabled => !opts[:disabled],
86
86
  }
87
87
  )
88
- one_progress(cluster.ReconfigureComputeResource_Task :spec => spec, :modify => true)
88
+ tasks = clusters.map do |cluster|
89
+ cluster.ReconfigureComputeResource_Task(:spec => spec, :modify => true)
90
+ end
91
+ progress(tasks)
92
+ childtasks = tasks.map{|t| t.child_tasks}.flatten.compact
93
+ if childtasks && childtasks.length > 0
94
+ progress(childtasks)
95
+ end
96
+
97
+ end
98
+
99
+ opts :configure_drs do
100
+ summary "Configure DRS on a cluster"
101
+ opt :disabled, "Disable HA", :default => false
102
+ opt :mode, "DRS mode (manual, partiallyAutomated, fullyAutomated)", :type => :string
103
+ arg :cluster, "Path to a Cluster", :lookup => VIM::ClusterComputeResource, :multi => true
104
+ end
105
+
106
+ def configure_drs clusters, opts
107
+ clusterSpec = VIM::ClusterConfigSpecEx(
108
+ :drsConfig => {
109
+ :defaultVmBehavior => opts[:mode] ? opts[:mode].to_sym : nil,
110
+ :enabled => !opts[:disabled]
111
+ }
112
+ )
113
+ tasks = clusters.map do |cluster|
114
+ cluster.ReconfigureComputeResource_Task(:modify => true, :spec => clusterSpec)
115
+ end
116
+ progress(tasks)
117
+ end
118
+
119
+ opts :configure_swap do
120
+ summary "Configure VM Swap Placement on a cluster"
121
+ opt :mode, "Swap mode (hostLocal, vmDirectory)", :type => :string
122
+ arg :cluster, "Path to a Cluster", :lookup => VIM::ClusterComputeResource, :multi => true
123
+ end
124
+
125
+ def configure_swap clusters, opts
126
+ clusterSpec = VIM::ClusterConfigSpecEx(
127
+ :vmSwapPlacement => opts[:mode]
128
+ )
129
+ tasks = clusters.map do |cluster|
130
+ cluster.ReconfigureComputeResource_Task(:modify => true, :spec => clusterSpec)
131
+ end
132
+ progress(tasks)
89
133
  end
90
134
 
91
135
  opts :recommendations do
@@ -109,30 +153,41 @@ def recommendations cluster
109
153
  name_map = pc.collectMultiple(targets, 'name')
110
154
 
111
155
  # Compose the output (tries to avoid making any API calls)
112
- out = Terminal::Table.new(['Key', 'Reason', 'Target', 'Actions']) do
113
- recommendation.each do |r|
114
- target_name = r.target ? name_map[r.target]['name'] : ""
115
- actions = r.action.map do |a|
116
- action = "#{a.class.wsdl_name}: #{name_map[a.target]['name']}"
117
- dst = nil
118
- if a.is_a?(RbVmomi::VIM::ClusterMigrationAction)
119
- dst = a.drsMigration.destination
120
- end
121
- if a.is_a?(RbVmomi::VIM::StorageMigrationAction)
122
- dst = a.destination
123
- end
124
- if dst
125
- if !name_map[dst]
126
- name_map[dst] = {'name' => dst.name}
127
- end
128
- action += " (to #{name_map[dst]['name']})"
156
+ t = Terminal::Table.new()
157
+ t << ['Key', 'Reason', 'Target', 'Actions']
158
+ recommendation.each do |r|
159
+ target_name = r.target ? name_map[r.target]['name'] : ""
160
+ actions = r.action.map do |a|
161
+ action = "#{a.class.wsdl_name}: #{name_map[a.target]['name']}"
162
+ dst = nil
163
+ if a.is_a?(RbVmomi::VIM::ClusterMigrationAction)
164
+ dst = a.drsMigration.destination
165
+ end
166
+ if a.is_a?(RbVmomi::VIM::StorageMigrationAction)
167
+ dst = a.destination
168
+ end
169
+ if dst
170
+ if !name_map[dst]
171
+ name_map[dst] = {'name' => dst.name}
129
172
  end
130
- action
173
+ action += " (to #{name_map[dst]['name']})"
131
174
  end
132
- add_row [r.key, r.reasonText, target_name, actions.join("\n")]
175
+ action
133
176
  end
177
+ t << [r.key, r.reasonText, target_name, actions.join("\n")]
178
+ end
179
+ puts t
180
+ end
181
+
182
+ def _filtered_cluster_recommendations cluster, key, type
183
+ recommendation = cluster.recommendation
184
+ if key && key.length > 0
185
+ recommendation.select! { |x| key.member?(x.key) }
134
186
  end
135
- puts out
187
+ if type && type.length > 0
188
+ recommendation.select! { |x| (type & x.action.map { |y| y.class.wsdl_name }).length > 0 }
189
+ end
190
+ recommendation
136
191
  end
137
192
 
138
193
  opts :apply_recommendations do
@@ -144,13 +199,9 @@ end
144
199
 
145
200
  def apply_recommendations cluster, opts
146
201
  pc = cluster._connection.serviceContent.propertyCollector
147
- recommendation = cluster.recommendation
148
- if opts[:key] && opts[:key].length > 0
149
- recommendation.select! { |x| opts[:key].member?(x.key) }
150
- end
151
- if opts[:type] && opts[:type].length > 0
152
- recommendation.select! { |x| (opts[:type] & x.action.map { |y| y.class.wsdl_name }).length > 0 }
153
- end
202
+ recommendation = _filtered_cluster_recommendations(
203
+ cluster, opts[:key], opts[:type]
204
+ )
154
205
  all_tasks = []
155
206
 
156
207
  # We do recommendations in chunks, because VC can't process more than a
@@ -160,16 +211,26 @@ def apply_recommendations cluster, opts
160
211
  # exceed the screensize with queued tasks
161
212
  while recommendation.length > 0
162
213
  recommendation.pop(20).each do |r|
163
- targets = r.action.map { |y| y.target }
164
- recent_tasks = pc.collectMultiple(targets, 'recentTask')
165
- prev_tasks = targets.map { |x| recent_tasks[x]['recentTask'] }
166
- cluster.ApplyRecommendation(:key => r.key)
167
- recent_tasks = pc.collectMultiple(targets, 'recentTask')
168
- tasks = targets.map { |x| recent_tasks[x]['recentTask'] }
169
- all_tasks += (tasks.flatten - prev_tasks.flatten)
214
+ begin
215
+ targets = r.action.map { |y| y.target }
216
+ recent_tasks = pc.collectMultiple(targets, 'recentTask')
217
+ prev_tasks = targets.map { |x| recent_tasks[x]['recentTask'] }
218
+ cluster.ApplyRecommendation(:key => r.key)
219
+ recent_tasks = pc.collectMultiple(targets, 'recentTask')
220
+ tasks = targets.map { |x| recent_tasks[x]['recentTask'] }
221
+ all_tasks += (tasks.flatten - prev_tasks.flatten)
222
+ rescue VIM::InvalidArgument
223
+ end
224
+ end
225
+
226
+ if all_tasks.length > 0
227
+ progress all_tasks
228
+ all_tasks = []
170
229
  end
171
230
 
172
- progress all_tasks
231
+ recommendation = _filtered_cluster_recommendations(
232
+ cluster, opts[:key], opts[:type]
233
+ )
173
234
  end
174
235
  end
175
236
 
@@ -18,6 +18,8 @@
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/vim'
22
+
21
23
  opts :switch do
22
24
  summary "Switch to another connection"
23
25
  arg :name, "Connection name"
@@ -103,6 +103,41 @@ def add_net vm, network, opts
103
103
  end
104
104
 
105
105
 
106
+ opts :reconfig_net do
107
+ summary "Attach a network adapter to a different network"
108
+ arg :device, nil, :lookup => VIM::VirtualDevice, :multi => true
109
+ opt :network, "Network to attach to", :lookup => VIM::Network, :required => true
110
+ end
111
+
112
+ def reconfig_net devs, opts
113
+ network = opts[:network]
114
+ case network
115
+ when VIM::DistributedVirtualPortgroup
116
+ switch, pg_key = network.collect 'config.distributedVirtualSwitch', 'key'
117
+ port = VIM.DistributedVirtualSwitchPortConnection(
118
+ :switchUuid => switch.uuid,
119
+ :portgroupKey => pg_key)
120
+ summary = network.name
121
+ backing = VIM.VirtualEthernetCardDistributedVirtualPortBackingInfo(:port => port)
122
+ when VIM::Network
123
+ summary = network.name
124
+ backing = VIM.VirtualEthernetCardNetworkBackingInfo(:deviceName => network.name)
125
+ else fail
126
+ end
127
+
128
+ vm_devs = devs.group_by(&:rvc_vm)
129
+ tasks = vm_devs.map do |vm,my_devs|
130
+ device_changes = my_devs.map do |dev|
131
+ dev = dev.dup
132
+ dev.backing = backing
133
+ { :operation => :edit, :device => dev }
134
+ end
135
+ spec = { :deviceChange => device_changes }
136
+ vm.ReconfigVM_Task(:spec => spec)
137
+ end
138
+ progress(tasks)
139
+ end
140
+
106
141
  opts :add_disk do
107
142
  summary "Add a hard drive to a virtual machine"
108
143
  arg :vm, nil, :lookup => VIM::VirtualMachine
@@ -110,6 +145,7 @@ opts :add_disk do
110
145
  opt :size, 'Size', :default => '10G'
111
146
  opt :controller, 'Virtual controller', :type => :string, :lookup => VIM::VirtualController
112
147
  opt :file_op, 'File operation (create|reuse|replace)', :default => 'create'
148
+ opt :thick, "Use thick provisioning", :type => :boolean
113
149
  end
114
150
 
115
151
  def add_disk vm, path, opts
@@ -130,7 +166,7 @@ def add_disk vm, path, opts
130
166
  :backing => VIM.VirtualDiskFlatVer2BackingInfo(
131
167
  :fileName => filename,
132
168
  :diskMode => :persistent,
133
- :thinProvisioned => true
169
+ :thinProvisioned => !(opts[:thick] == true),
134
170
  ),
135
171
  :capacityInKB => MetricNumber.parse(opts[:size]).to_i/1000,
136
172
  :controllerKey => controller.key,
@@ -18,6 +18,8 @@
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/vim'
22
+
21
23
  DEFAULT_SERVER_PLACEHOLDER = '0.0.0.0'
22
24
 
23
25
  def wait_for_multiple_tasks tasks, timeout
@@ -48,6 +50,80 @@ def wait_for_multiple_tasks tasks, timeout
48
50
  results
49
51
  end
50
52
 
53
+ # http://stackoverflow.com/questions/3386233/how-to-get-exit-status-with-rubys-netssh-library
54
+ def ssh_exec!(ssh, command)
55
+ stdout_data = ""
56
+ stderr_data = ""
57
+ exit_code = nil
58
+ exit_signal = nil
59
+ ssh.open_channel do |channel|
60
+ channel.exec(command) do |ch, success|
61
+ unless success
62
+ abort "FAILED: couldn't execute command (ssh.channel.exec)"
63
+ end
64
+ channel.on_data do |ch,data|
65
+ stdout_data+=data
66
+ end
67
+
68
+ channel.on_extended_data do |ch,type,data|
69
+ stderr_data+=data
70
+ end
71
+
72
+ channel.on_request("exit-status") do |ch,data|
73
+ exit_code = data.read_long
74
+ end
75
+
76
+ end
77
+ end
78
+ ssh.loop
79
+ [stdout_data, stderr_data, exit_code]
80
+ end
81
+
82
+ opts :restart_services do
83
+ summary "Restart all services in hosts"
84
+ arg :cluster, nil, :lookup => VIM::ComputeResource, :multi => true
85
+ opt :host, "Host name (multi ok)", type: :string, short: 'n', :multi => true
86
+ opt :password, "Host password (multi ok)", type: :string, short: 'p', :multi => true
87
+ end
88
+
89
+ def restart_services clusters, opts
90
+ hosts = opts[:host]
91
+ pwds = opts[:password]
92
+ puts "Need to specify password(s) for fixing" if pwds == []
93
+
94
+ hosts.each do |host|
95
+ finished = false
96
+ pwds.each do |pwd|
97
+ break if finished
98
+ puts "\nTrying restart #{host} with pwd #{pwd}"
99
+ begin
100
+ Net::SSH.start(host, "root", :password => pwd, :paranoid => false) do |ssh|
101
+ # HZ 1258412 discusses the commands to fix a node with hostd problems
102
+ cmd = "/sbin/chkconfig usbarbitrator off"
103
+ puts "Running #{cmd}"
104
+ out = ssh_exec!(ssh,cmd)
105
+ if out[2] != 0
106
+ puts "Failed to execute #{cmd} on host #{host}"
107
+ puts out[1]
108
+ end
109
+
110
+ cmd = "/sbin/services.sh restart > /tmp/restart_services.log 2>&1"
111
+ puts "Running #{cmd}"
112
+ out = ssh_exec!(ssh,cmd)
113
+ if out[2] != 0
114
+ puts "Failed to restart all services on host #{host}"
115
+ puts out[1]
116
+ else
117
+ puts "Host #{host} restarted all services"
118
+ finished = true
119
+ end
120
+ end
121
+ rescue Net::SSH::AuthenticationFailed
122
+ puts "Failed to authenticate on host #{host}"
123
+ end
124
+ end
125
+ end
126
+ end
51
127
 
52
128
  opts :vm_create do
53
129
  summary "Check that VMs can be created on all hosts in a cluster"
@@ -55,6 +131,8 @@ opts :vm_create do
55
131
  opt :datastore, "Datastore to put (temporary) VMs on", :lookup => VIM::Datastore
56
132
  opt :vm_folder, "VM Folder to place (temporary) VMs in", :lookup => VIM::Folder
57
133
  opt :timeout, "Time to wait for VM creation to finish", :type => :int, :default => 3 * 60
134
+ opt :fix, "Fix the failing ESX hosts", :type => :boolean , :default => false
135
+ opt :password, "Passwords for fixing hosts", :type => :string, short: 'p', :multi => true
58
136
  end
59
137
 
60
138
  def vm_create clusters, opts
@@ -64,15 +142,34 @@ def vm_create clusters, opts
64
142
  err "vm_folder is a required parameter" unless vm_folder
65
143
 
66
144
  puts "Creating one VM per host ... (timeout = #{opts[:timeout]} sec)"
67
- result = _vm_create clusters, datastore, vm_folder, opts
68
-
69
- errors = result.select{|h, x| x['status'] != 'green'}
70
- errors.each do |host, info|
71
- puts "Failed to create VM on host #{host} (in cluster #{info['cluster']}): #{info['error']}"
145
+ errors = []
146
+ failed_hosts = []
147
+ begin
148
+ result = _vm_create clusters, datastore, vm_folder, opts
149
+ errors = result.select{|h, x| x['status'] != 'green'}
150
+ errors.each do |host, info|
151
+ puts "Failed to create VM on host #{host} (in cluster #{info['cluster']}): #{info['error']}"
152
+ err_msgs = ["Timed out", "InvalidState", "InvalidHostState", "InvalidHostConnectionState", "HostCommunication"]
153
+ err_msgs.each do |msg|
154
+ if info['error'].include? msg
155
+ failed_hosts << host
156
+ break
157
+ end
158
+ end
159
+ end
160
+ rescue Exception => e
161
+ puts "An error occurred:\n"
162
+ puts "e.message:", e.message
163
+ puts "e.backtrace:", e.backtrace.join("\n")
164
+ errors = [e]
72
165
  end
73
166
  if errors.length == 0
74
167
  puts "Success"
75
168
  end
169
+ if opts[:fix] && failed_hosts != []
170
+ opts[:host] = failed_hosts
171
+ restart_services(clusters, opts)
172
+ end
76
173
  end
77
174
 
78
175
  def _vm_create clusters, datastore, vm_folder, opts = {}
@@ -99,6 +196,7 @@ def _vm_create clusters, datastore, vm_folder, opts = {}
99
196
  :files => { :vmPathName => datastore_path },
100
197
  :numCPUs => 1,
101
198
  :memoryMB => 16,
199
+ :annotation => YAML.dump({'lease' => Time.now + 2 * opts[:timeout] + 60}),
102
200
  :deviceChange => [
103
201
  {
104
202
  :operation => :add,
@@ -118,11 +216,15 @@ def _vm_create clusters, datastore, vm_folder, opts = {}
118
216
  }
119
217
  ],
120
218
  }
121
- task = vm_folder.CreateVM_Task(:config => config,
122
- :pool => rp,
123
- :host => host)
124
- tasks_map[task] = host
125
- hosts_infos[host][:create_task] = task
219
+ begin
220
+ task = vm_folder.CreateVM_Task(:config => config,
221
+ :pool => rp,
222
+ :host => host)
223
+ tasks_map[task] = host
224
+ hosts_infos[host][:create_task] = task
225
+ rescue
226
+ puts "Failed to create task for host #{host.name}"
227
+ end
126
228
  end
127
229
  end
128
230
 
@@ -148,6 +250,7 @@ def _vm_create clusters, datastore, vm_folder, opts = {}
148
250
 
149
251
  result = host_info[:create_result]
150
252
  result = host_info[:destroy_result] if result.is_a?(VIM::VirtualMachine)
253
+ error_detail = nil
151
254
  if result == nil
152
255
  error_str = nil
153
256
  status = 'green'
@@ -157,12 +260,17 @@ def _vm_create clusters, datastore, vm_folder, opts = {}
157
260
  else
158
261
  error_str = "#{result.fault.class.wsdl_name}: #{result.localizedMessage}"
159
262
  status = 'red'
263
+ begin
264
+ error_detail = result.fault.faultMessage
265
+ rescue
266
+ end
160
267
  end
161
268
 
162
269
  out[host_props['name']] = {
163
270
  'cluster' => cluster_props['name'],
164
271
  'status' => status,
165
- 'error' => error_str
272
+ 'error' => error_str,
273
+ 'error_detail' => error_detail,
166
274
  }
167
275
  end
168
276
  out