vcloud-rest 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +21 -9
- data/lib/vcloud-rest/connection.rb +72 -890
- data/lib/vcloud-rest/vcloud/catalog.rb +113 -0
- data/lib/vcloud-rest/vcloud/network.rb +78 -0
- data/lib/vcloud-rest/vcloud/org.rb +145 -0
- data/lib/vcloud-rest/vcloud/ovf.rb +251 -0
- data/lib/vcloud-rest/vcloud/vapp.rb +428 -0
- data/lib/vcloud-rest/vcloud/vapp_networking.rb +331 -0
- data/lib/vcloud-rest/vcloud/vdc.rb +67 -0
- data/lib/vcloud-rest/vcloud/vm.rb +396 -0
- data/lib/vcloud-rest/version.rb +3 -0
- metadata +18 -9
@@ -20,6 +20,15 @@ require 'rest-client'
|
|
20
20
|
require 'nokogiri'
|
21
21
|
require 'httpclient'
|
22
22
|
require 'ruby-progressbar'
|
23
|
+
require 'logger'
|
24
|
+
|
25
|
+
require 'vcloud-rest/vcloud/vapp'
|
26
|
+
require 'vcloud-rest/vcloud/org'
|
27
|
+
require 'vcloud-rest/vcloud/catalog'
|
28
|
+
require 'vcloud-rest/vcloud/vdc'
|
29
|
+
require 'vcloud-rest/vcloud/vm'
|
30
|
+
require 'vcloud-rest/vcloud/ovf'
|
31
|
+
require 'vcloud-rest/vcloud/network'
|
23
32
|
|
24
33
|
module VCloudClient
|
25
34
|
class UnauthorizedAccess < StandardError; end
|
@@ -27,9 +36,10 @@ module VCloudClient
|
|
27
36
|
class WrongItemIDError < StandardError; end
|
28
37
|
class InvalidStateError < StandardError; end
|
29
38
|
class InternalServerError < StandardError; end
|
39
|
+
class OVFError < StandardError; end
|
40
|
+
class MethodNotAllowed < StandardError; end
|
30
41
|
class UnhandledError < StandardError; end
|
31
42
|
|
32
|
-
|
33
43
|
# Main class to access vCloud rest APIs
|
34
44
|
class Connection
|
35
45
|
attr_reader :api_url, :auth_key
|
@@ -42,6 +52,8 @@ module VCloudClient
|
|
42
52
|
@password = password
|
43
53
|
@org_name = org_name
|
44
54
|
@api_version = (api_version || "5.1")
|
55
|
+
|
56
|
+
init_logger
|
45
57
|
end
|
46
58
|
|
47
59
|
##
|
@@ -70,664 +82,8 @@ module VCloudClient
|
|
70
82
|
}
|
71
83
|
|
72
84
|
response, headers = send_request(params)
|
73
|
-
|
74
|
-
|
75
|
-
##
|
76
|
-
# Fetch existing organizations and their IDs
|
77
|
-
def get_organizations
|
78
|
-
params = {
|
79
|
-
'method' => :get,
|
80
|
-
'command' => '/org'
|
81
|
-
}
|
82
|
-
|
83
|
-
response, headers = send_request(params)
|
84
|
-
orgs = response.css('OrgList Org')
|
85
|
-
|
86
|
-
results = {}
|
87
|
-
orgs.each do |org|
|
88
|
-
results[org['name']] = org['href'].gsub("#{@api_url}/org/", "")
|
89
|
-
end
|
90
|
-
results
|
91
|
-
end
|
92
|
-
|
93
|
-
##
|
94
|
-
# Fetch details about an organization:
|
95
|
-
# - catalogs
|
96
|
-
# - vdcs
|
97
|
-
# - networks
|
98
|
-
def get_organization(orgId)
|
99
|
-
params = {
|
100
|
-
'method' => :get,
|
101
|
-
'command' => "/org/#{orgId}"
|
102
|
-
}
|
103
|
-
|
104
|
-
response, headers = send_request(params)
|
105
|
-
catalogs = {}
|
106
|
-
response.css("Link[type='application/vnd.vmware.vcloud.catalog+xml']").each do |item|
|
107
|
-
catalogs[item['name']] = item['href'].gsub("#{@api_url}/catalog/", "")
|
108
|
-
end
|
109
|
-
|
110
|
-
vdcs = {}
|
111
|
-
response.css("Link[type='application/vnd.vmware.vcloud.vdc+xml']").each do |item|
|
112
|
-
vdcs[item['name']] = item['href'].gsub("#{@api_url}/vdc/", "")
|
113
|
-
end
|
114
|
-
|
115
|
-
networks = {}
|
116
|
-
response.css("Link[type='application/vnd.vmware.vcloud.orgNetwork+xml']").each do |item|
|
117
|
-
networks[item['name']] = item['href'].gsub("#{@api_url}/network/", "")
|
118
|
-
end
|
119
|
-
|
120
|
-
tasklists = {}
|
121
|
-
response.css("Link[type='application/vnd.vmware.vcloud.tasksList+xml']").each do |item|
|
122
|
-
tasklists[item['name']] = item['href'].gsub("#{@api_url}/tasksList/", "")
|
123
|
-
end
|
124
|
-
|
125
|
-
{ :catalogs => catalogs, :vdcs => vdcs, :networks => networks, :tasklists => tasklists }
|
126
|
-
end
|
127
|
-
|
128
|
-
##
|
129
|
-
# Fetch details about a given catalog
|
130
|
-
def get_catalog(catalogId)
|
131
|
-
params = {
|
132
|
-
'method' => :get,
|
133
|
-
'command' => "/catalog/#{catalogId}"
|
134
|
-
}
|
135
|
-
|
136
|
-
response, headers = send_request(params)
|
137
|
-
description = response.css("Description").first
|
138
|
-
description = description.text unless description.nil?
|
139
|
-
|
140
|
-
items = {}
|
141
|
-
response.css("CatalogItem[type='application/vnd.vmware.vcloud.catalogItem+xml']").each do |item|
|
142
|
-
items[item['name']] = item['href'].gsub("#{@api_url}/catalogItem/", "")
|
143
|
-
end
|
144
|
-
{ :description => description, :items => items }
|
145
|
-
end
|
146
|
-
|
147
|
-
##
|
148
|
-
# Fetch details about a given vdc:
|
149
|
-
# - description
|
150
|
-
# - vapps
|
151
|
-
# - networks
|
152
|
-
def get_vdc(vdcId)
|
153
|
-
params = {
|
154
|
-
'method' => :get,
|
155
|
-
'command' => "/vdc/#{vdcId}"
|
156
|
-
}
|
157
|
-
|
158
|
-
response, headers = send_request(params)
|
159
|
-
description = response.css("Description").first
|
160
|
-
description = description.text unless description.nil?
|
161
|
-
|
162
|
-
vapps = {}
|
163
|
-
response.css("ResourceEntity[type='application/vnd.vmware.vcloud.vApp+xml']").each do |item|
|
164
|
-
vapps[item['name']] = item['href'].gsub("#{@api_url}/vApp/vapp-", "")
|
165
|
-
end
|
166
|
-
|
167
|
-
networks = {}
|
168
|
-
response.css("Network[type='application/vnd.vmware.vcloud.network+xml']").each do |item|
|
169
|
-
networks[item['name']] = item['href'].gsub("#{@api_url}/network/", "")
|
170
|
-
end
|
171
|
-
{ :description => description, :vapps => vapps, :networks => networks }
|
172
|
-
end
|
173
|
-
|
174
|
-
##
|
175
|
-
# Fetch details about a given catalog item:
|
176
|
-
# - description
|
177
|
-
# - vApp templates
|
178
|
-
def get_catalog_item(catalogItemId)
|
179
|
-
params = {
|
180
|
-
'method' => :get,
|
181
|
-
'command' => "/catalogItem/#{catalogItemId}"
|
182
|
-
}
|
183
|
-
|
184
|
-
response, headers = send_request(params)
|
185
|
-
description = response.css("Description").first
|
186
|
-
description = description.text unless description.nil?
|
187
|
-
|
188
|
-
items = {}
|
189
|
-
response.css("Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']").each do |item|
|
190
|
-
items[item['name']] = item['href'].gsub("#{@api_url}/vAppTemplate/vappTemplate-", "")
|
191
|
-
end
|
192
|
-
{ :description => description, :items => items }
|
193
|
-
end
|
194
|
-
|
195
|
-
##
|
196
|
-
# Fetch details about a given vapp:
|
197
|
-
# - name
|
198
|
-
# - description
|
199
|
-
# - status
|
200
|
-
# - IP
|
201
|
-
# - Children VMs:
|
202
|
-
# -- IP addresses
|
203
|
-
# -- status
|
204
|
-
# -- ID
|
205
|
-
def get_vapp(vAppId)
|
206
|
-
params = {
|
207
|
-
'method' => :get,
|
208
|
-
'command' => "/vApp/vapp-#{vAppId}"
|
209
|
-
}
|
210
|
-
|
211
|
-
response, headers = send_request(params)
|
212
|
-
|
213
|
-
vapp_node = response.css('VApp').first
|
214
|
-
if vapp_node
|
215
|
-
name = vapp_node['name']
|
216
|
-
status = convert_vapp_status(vapp_node['status'])
|
217
|
-
end
|
218
|
-
|
219
|
-
description = response.css("Description").first
|
220
|
-
description = description.text unless description.nil?
|
221
|
-
|
222
|
-
ip = response.css('IpAddress').first
|
223
|
-
ip = ip.text unless ip.nil?
|
224
|
-
|
225
|
-
vms = response.css('Children Vm')
|
226
|
-
vms_hash = {}
|
227
|
-
|
228
|
-
# ipAddress could be namespaced or not: see https://github.com/astratto/vcloud-rest/issues/3
|
229
|
-
vms.each do |vm|
|
230
|
-
vapp_local_id = vm.css('VAppScopedLocalId')
|
231
|
-
addresses = vm.css('rasd|Connection').collect{|n| n['vcloud:ipAddress'] || n['ipAddress'] }
|
232
|
-
vms_hash[vm['name']] = {
|
233
|
-
:addresses => addresses,
|
234
|
-
:status => convert_vapp_status(vm['status']),
|
235
|
-
:id => vm['href'].gsub("#{@api_url}/vApp/vm-", ''),
|
236
|
-
:vapp_scoped_local_id => vapp_local_id.text
|
237
|
-
}
|
238
|
-
end
|
239
|
-
|
240
|
-
# TODO: EXPAND INFO FROM RESPONSE
|
241
|
-
{ :name => name, :description => description, :status => status, :ip => ip, :vms_hash => vms_hash }
|
242
|
-
end
|
243
|
-
|
244
|
-
##
|
245
|
-
# Delete a given vapp
|
246
|
-
# NOTE: It doesn't verify that the vapp is shutdown
|
247
|
-
def delete_vapp(vAppId)
|
248
|
-
params = {
|
249
|
-
'method' => :delete,
|
250
|
-
'command' => "/vApp/vapp-#{vAppId}"
|
251
|
-
}
|
252
|
-
|
253
|
-
response, headers = send_request(params)
|
254
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
255
|
-
task_id
|
256
|
-
end
|
257
|
-
|
258
|
-
##
|
259
|
-
# Shutdown a given vapp
|
260
|
-
def poweroff_vapp(vAppId)
|
261
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
262
|
-
xml.UndeployVAppParams(
|
263
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5") {
|
264
|
-
xml.UndeployPowerAction 'powerOff'
|
265
|
-
}
|
266
|
-
end
|
267
|
-
|
268
|
-
params = {
|
269
|
-
'method' => :post,
|
270
|
-
'command' => "/vApp/vapp-#{vAppId}/action/undeploy"
|
271
|
-
}
|
272
|
-
|
273
|
-
response, headers = send_request(params, builder.to_xml,
|
274
|
-
"application/vnd.vmware.vcloud.undeployVAppParams+xml")
|
275
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
276
|
-
task_id
|
277
|
-
end
|
278
|
-
|
279
|
-
##
|
280
|
-
# Suspend a given vapp
|
281
|
-
def suspend_vapp(vAppId)
|
282
|
-
params = {
|
283
|
-
'method' => :post,
|
284
|
-
'command' => "/vApp/vapp-#{vAppId}/power/action/suspend"
|
285
|
-
}
|
286
|
-
|
287
|
-
response, headers = send_request(params)
|
288
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
289
|
-
task_id
|
290
|
-
end
|
291
|
-
|
292
|
-
##
|
293
|
-
# reboot a given vapp
|
294
|
-
# This will basically initial a guest OS reboot, and will only work if
|
295
|
-
# VMware-tools are installed on the underlying VMs.
|
296
|
-
# vShield Edge devices are not affected
|
297
|
-
def reboot_vapp(vAppId)
|
298
|
-
params = {
|
299
|
-
'method' => :post,
|
300
|
-
'command' => "/vApp/vapp-#{vAppId}/power/action/reboot"
|
301
|
-
}
|
302
|
-
|
303
|
-
response, headers = send_request(params)
|
304
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
305
|
-
task_id
|
306
|
-
end
|
307
|
-
|
308
|
-
##
|
309
|
-
# reset a given vapp
|
310
|
-
# This will basically reset the VMs within the vApp
|
311
|
-
# vShield Edge devices are not affected.
|
312
|
-
def reset_vapp(vAppId)
|
313
|
-
params = {
|
314
|
-
'method' => :post,
|
315
|
-
'command' => "/vApp/vapp-#{vAppId}/power/action/reset"
|
316
|
-
}
|
317
|
-
|
318
|
-
response, headers = send_request(params)
|
319
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
320
|
-
task_id
|
321
|
-
end
|
322
|
-
|
323
|
-
##
|
324
|
-
# Boot a given vapp
|
325
|
-
def poweron_vapp(vAppId)
|
326
|
-
params = {
|
327
|
-
'method' => :post,
|
328
|
-
'command' => "/vApp/vapp-#{vAppId}/power/action/powerOn"
|
329
|
-
}
|
330
|
-
|
331
|
-
response, headers = send_request(params)
|
332
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
333
|
-
task_id
|
334
|
-
end
|
335
|
-
|
336
|
-
##
|
337
|
-
# Create a vapp starting from a template
|
338
|
-
#
|
339
|
-
# Params:
|
340
|
-
# - vdc: the associated VDC
|
341
|
-
# - vapp_name: name of the target vapp
|
342
|
-
# - vapp_description: description of the target vapp
|
343
|
-
# - vapp_templateid: ID of the vapp template
|
344
|
-
def create_vapp_from_template(vdc, vapp_name, vapp_description, vapp_templateid, poweron=false)
|
345
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
346
|
-
xml.InstantiateVAppTemplateParams(
|
347
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
348
|
-
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
|
349
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
|
350
|
-
"name" => vapp_name,
|
351
|
-
"deploy" => "true",
|
352
|
-
"powerOn" => poweron) {
|
353
|
-
xml.Description vapp_description
|
354
|
-
xml.Source("href" => "#{@api_url}/vAppTemplate/#{vapp_templateid}")
|
355
|
-
}
|
356
|
-
end
|
357
|
-
|
358
|
-
params = {
|
359
|
-
"method" => :post,
|
360
|
-
"command" => "/vdc/#{vdc}/action/instantiateVAppTemplate"
|
361
|
-
}
|
362
|
-
|
363
|
-
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml")
|
364
|
-
|
365
|
-
vapp_id = headers[:location].gsub("#{@api_url}/vApp/vapp-", "")
|
366
|
-
|
367
|
-
task = response.css("VApp Task[operationName='vdcInstantiateVapp']").first
|
368
|
-
task_id = task["href"].gsub("#{@api_url}/task/", "")
|
369
|
-
|
370
|
-
{ :vapp_id => vapp_id, :task_id => task_id }
|
371
|
-
end
|
372
|
-
|
373
|
-
##
|
374
|
-
# Compose a vapp using existing virtual machines
|
375
|
-
#
|
376
|
-
# Params:
|
377
|
-
# - vdc: the associated VDC
|
378
|
-
# - vapp_name: name of the target vapp
|
379
|
-
# - vapp_description: description of the target vapp
|
380
|
-
# - vm_list: hash with IDs of the VMs to be used in the composing process
|
381
|
-
# - network_config: hash of the network configuration for the vapp
|
382
|
-
def compose_vapp_from_vm(vdc, vapp_name, vapp_description, vm_list={}, network_config={})
|
383
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
384
|
-
xml.ComposeVAppParams(
|
385
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
386
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
|
387
|
-
"name" => vapp_name) {
|
388
|
-
xml.Description vapp_description
|
389
|
-
xml.InstantiationParams {
|
390
|
-
xml.NetworkConfigSection {
|
391
|
-
xml['ovf'].Info "Configuration parameters for logical networks"
|
392
|
-
xml.NetworkConfig("networkName" => network_config[:name]) {
|
393
|
-
xml.Configuration {
|
394
|
-
xml.IpScopes {
|
395
|
-
xml.IpScope {
|
396
|
-
xml.IsInherited(network_config[:is_inherited] || "false")
|
397
|
-
xml.Gateway network_config[:gateway]
|
398
|
-
xml.Netmask network_config[:netmask]
|
399
|
-
xml.Dns1 network_config[:dns1] if network_config[:dns1]
|
400
|
-
xml.Dns2 network_config[:dns2] if network_config[:dns2]
|
401
|
-
xml.DnsSuffix network_config[:dns_suffix] if network_config[:dns_suffix]
|
402
|
-
xml.IpRanges {
|
403
|
-
xml.IpRange {
|
404
|
-
xml.StartAddress network_config[:start_address]
|
405
|
-
xml.EndAddress network_config[:end_address]
|
406
|
-
}
|
407
|
-
}
|
408
|
-
}
|
409
|
-
}
|
410
|
-
xml.ParentNetwork("href" => "#{@api_url}/network/#{network_config[:parent_network]}")
|
411
|
-
xml.FenceMode network_config[:fence_mode]
|
412
|
-
|
413
|
-
xml.Features {
|
414
|
-
xml.FirewallService {
|
415
|
-
xml.IsEnabled(network_config[:enable_firewall] || "false")
|
416
|
-
}
|
417
|
-
}
|
418
|
-
}
|
419
|
-
}
|
420
|
-
}
|
421
|
-
}
|
422
|
-
vm_list.each do |vm_name, vm_id|
|
423
|
-
xml.SourcedItem {
|
424
|
-
xml.Source("href" => "#{@api_url}/vAppTemplate/vm-#{vm_id}", "name" => vm_name)
|
425
|
-
xml.InstantiationParams {
|
426
|
-
xml.NetworkConnectionSection(
|
427
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
|
428
|
-
"type" => "application/vnd.vmware.vcloud.networkConnectionSection+xml",
|
429
|
-
"href" => "#{@api_url}/vAppTemplate/vm-#{vm_id}/networkConnectionSection/") {
|
430
|
-
xml['ovf'].Info "Network config for sourced item"
|
431
|
-
xml.PrimaryNetworkConnectionIndex "0"
|
432
|
-
xml.NetworkConnection("network" => network_config[:name]) {
|
433
|
-
xml.NetworkConnectionIndex "0"
|
434
|
-
xml.IsConnected "true"
|
435
|
-
xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || "POOL")
|
436
|
-
}
|
437
|
-
}
|
438
|
-
}
|
439
|
-
xml.NetworkAssignment("containerNetwork" => network_config[:name], "innerNetwork" => network_config[:name])
|
440
|
-
}
|
441
|
-
end
|
442
|
-
xml.AllEULAsAccepted "true"
|
443
|
-
}
|
444
|
-
end
|
445
|
-
|
446
|
-
params = {
|
447
|
-
"method" => :post,
|
448
|
-
"command" => "/vdc/#{vdc}/action/composeVApp"
|
449
|
-
}
|
450
|
-
|
451
|
-
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.composeVAppParams+xml")
|
452
|
-
|
453
|
-
vapp_id = headers[:location].gsub("#{@api_url}/vApp/vapp-", "")
|
454
|
-
|
455
|
-
task = response.css("VApp Task[operationName='vdcComposeVapp']").first
|
456
|
-
task_id = task["href"].gsub("#{@api_url}/task/", "")
|
457
|
-
|
458
|
-
{ :vapp_id => vapp_id, :task_id => task_id }
|
459
|
-
end
|
460
|
-
|
461
|
-
# Fetch details about a given vapp template:
|
462
|
-
# - name
|
463
|
-
# - description
|
464
|
-
# - Children VMs:
|
465
|
-
# -- ID
|
466
|
-
def get_vapp_template(vAppId)
|
467
|
-
params = {
|
468
|
-
'method' => :get,
|
469
|
-
'command' => "/vAppTemplate/vappTemplate-#{vAppId}"
|
470
|
-
}
|
471
|
-
|
472
|
-
response, headers = send_request(params)
|
473
|
-
|
474
|
-
vapp_node = response.css('VAppTemplate').first
|
475
|
-
if vapp_node
|
476
|
-
name = vapp_node['name']
|
477
|
-
status = convert_vapp_status(vapp_node['status'])
|
478
|
-
end
|
479
|
-
|
480
|
-
description = response.css("Description").first
|
481
|
-
description = description.text unless description.nil?
|
482
|
-
|
483
|
-
ip = response.css('IpAddress').first
|
484
|
-
ip = ip.text unless ip.nil?
|
485
|
-
|
486
|
-
vms = response.css('Children Vm')
|
487
|
-
vms_hash = {}
|
488
|
-
|
489
|
-
vms.each do |vm|
|
490
|
-
vms_hash[vm['name']] = {
|
491
|
-
:id => vm['href'].gsub("#{@api_url}/vAppTemplate/vm-", '')
|
492
|
-
}
|
493
|
-
end
|
494
|
-
|
495
|
-
# TODO: EXPAND INFO FROM RESPONSE
|
496
|
-
{ :name => name, :description => description, :vms_hash => vms_hash }
|
497
|
-
end
|
498
|
-
|
499
|
-
##
|
500
|
-
# Set vApp port forwarding rules
|
501
|
-
#
|
502
|
-
# - vappid: id of the vapp to be modified
|
503
|
-
# - network_name: name of the vapp network to be modified
|
504
|
-
# - config: hash with network configuration specifications, must contain an array inside :nat_rules with the nat rules to be applied.
|
505
|
-
def set_vapp_port_forwarding_rules(vappid, network_name, config={})
|
506
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
507
|
-
xml.NetworkConfigSection(
|
508
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
509
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
|
510
|
-
xml['ovf'].Info "Network configuration"
|
511
|
-
xml.NetworkConfig("networkName" => network_name) {
|
512
|
-
xml.Configuration {
|
513
|
-
xml.ParentNetwork("href" => "#{@api_url}/network/#{config[:parent_network]}")
|
514
|
-
xml.FenceMode(config[:fence_mode] || 'isolated')
|
515
|
-
xml.Features {
|
516
|
-
xml.NatService {
|
517
|
-
xml.IsEnabled "true"
|
518
|
-
xml.NatType "portForwarding"
|
519
|
-
xml.Policy(config[:nat_policy_type] || "allowTraffic")
|
520
|
-
config[:nat_rules].each do |nat_rule|
|
521
|
-
xml.NatRule {
|
522
|
-
xml.VmRule {
|
523
|
-
xml.ExternalPort nat_rule[:nat_external_port]
|
524
|
-
xml.VAppScopedVmId nat_rule[:vm_scoped_local_id]
|
525
|
-
xml.VmNicId(nat_rule[:nat_vmnic_id] || "0")
|
526
|
-
xml.InternalPort nat_rule[:nat_internal_port]
|
527
|
-
xml.Protocol(nat_rule[:nat_protocol] || "TCP")
|
528
|
-
}
|
529
|
-
}
|
530
|
-
end
|
531
|
-
}
|
532
|
-
}
|
533
|
-
}
|
534
|
-
}
|
535
|
-
}
|
536
|
-
end
|
537
|
-
|
538
|
-
params = {
|
539
|
-
'method' => :put,
|
540
|
-
'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
|
541
|
-
}
|
542
|
-
|
543
|
-
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConfigSection+xml")
|
544
|
-
|
545
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
546
|
-
task_id
|
547
|
-
end
|
548
|
-
|
549
|
-
##
|
550
|
-
# Get vApp port forwarding rules
|
551
|
-
#
|
552
|
-
# - vappid: id of the vApp
|
553
|
-
def get_vapp_port_forwarding_rules(vAppId)
|
554
|
-
params = {
|
555
|
-
'method' => :get,
|
556
|
-
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
557
|
-
}
|
558
|
-
|
559
|
-
response, headers = send_request(params)
|
560
|
-
|
561
|
-
# FIXME: this will return nil if the vApp uses multiple vApp Networks
|
562
|
-
# with Edge devices in natRouted/portForwarding mode.
|
563
|
-
config = response.css('NetworkConfigSection/NetworkConfig/Configuration')
|
564
|
-
fenceMode = config.css('/FenceMode').text
|
565
|
-
natType = config.css('/Features/NatService/NatType').text
|
566
|
-
|
567
|
-
raise InvalidStateError, "Invalid request because FenceMode must be set to natRouted." unless fenceMode == "natRouted"
|
568
|
-
raise InvalidStateError, "Invalid request because NatType must be set to portForwarding." unless natType == "portForwarding"
|
569
|
-
|
570
|
-
nat_rules = {}
|
571
|
-
config.css('/Features/NatService/NatRule').each do |rule|
|
572
|
-
# portforwarding rules information
|
573
|
-
ruleId = rule.css('Id').text
|
574
|
-
vmRule = rule.css('VmRule')
|
575
|
-
|
576
|
-
nat_rules[rule.css('Id').text] = {
|
577
|
-
:ExternalIpAddress => vmRule.css('ExternalIpAddress').text,
|
578
|
-
:ExternalPort => vmRule.css('ExternalPort').text,
|
579
|
-
:VAppScopedVmId => vmRule.css('VAppScopedVmId').text,
|
580
|
-
:VmNicId => vmRule.css('VmNicId').text,
|
581
|
-
:InternalPort => vmRule.css('InternalPort').text,
|
582
|
-
:Protocol => vmRule.css('Protocol').text
|
583
|
-
}
|
584
|
-
end
|
585
|
-
nat_rules
|
586
|
-
end
|
587
|
-
##
|
588
|
-
# get vApp edge public IP from the vApp ID
|
589
|
-
# Only works when:
|
590
|
-
# - vApp needs to be poweredOn
|
591
|
-
# - FenceMode is set to "natRouted"
|
592
|
-
# - NatType" is set to "portForwarding
|
593
|
-
# This will be required to know how to connect to VMs behind the Edge device.
|
594
|
-
def get_vapp_edge_public_ip(vAppId)
|
595
|
-
# Check the network configuration section
|
596
|
-
params = {
|
597
|
-
'method' => :get,
|
598
|
-
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
599
|
-
}
|
600
|
-
|
601
|
-
response, headers = send_request(params)
|
602
|
-
|
603
|
-
# FIXME: this will return nil if the vApp uses multiple vApp Networks
|
604
|
-
# with Edge devices in natRouted/portForwarding mode.
|
605
|
-
config = response.css('NetworkConfigSection/NetworkConfig/Configuration')
|
606
|
-
|
607
|
-
fenceMode = config.css('/FenceMode').text
|
608
|
-
natType = config.css('/Features/NatService/NatType').text
|
609
|
-
|
610
|
-
raise InvalidStateError, "Invalid request because FenceMode must be set to natRouted." unless fenceMode == "natRouted"
|
611
|
-
raise InvalidStateError, "Invalid request because NatType must be set to portForwarding." unless natType == "portForwarding"
|
612
|
-
|
613
|
-
# Check the routerInfo configuration where the global external IP is defined
|
614
|
-
edgeIp = config.css('/RouterInfo/ExternalIp')
|
615
|
-
edgeIp = edgeIp.text unless edgeIp.nil?
|
616
|
-
end
|
617
|
-
|
618
|
-
##
|
619
|
-
# Upload an OVF package
|
620
|
-
# - vdcId
|
621
|
-
# - vappName
|
622
|
-
# - vappDescription
|
623
|
-
# - ovfFile
|
624
|
-
# - catalogId
|
625
|
-
# - uploadOptions {}
|
626
|
-
def upload_ovf(vdcId, vappName, vappDescription, ovfFile, catalogId, uploadOptions={})
|
627
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
628
|
-
xml.UploadVAppTemplateParams(
|
629
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
630
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
|
631
|
-
"manifestRequired" => "true",
|
632
|
-
"name" => vappName) {
|
633
|
-
xml.Description vappDescription
|
634
|
-
}
|
635
|
-
end
|
636
|
-
|
637
|
-
params = {
|
638
|
-
'method' => :post,
|
639
|
-
'command' => "/vdc/#{vdcId}/action/uploadVAppTemplate"
|
640
|
-
}
|
641
|
-
|
642
|
-
response, headers = send_request(params, builder.to_xml,
|
643
|
-
"application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml")
|
644
|
-
|
645
|
-
# Get vAppTemplate Link from location
|
646
|
-
vAppTemplate = headers[:location].gsub("#{@api_url}/vAppTemplate/vappTemplate-", "")
|
647
|
-
descriptorUpload = response.css("Files Link [rel='upload:default']").first[:href].gsub("#{@host_url}/transfer/", "")
|
648
|
-
transferGUID = descriptorUpload.gsub("/descriptor.ovf", "")
|
649
|
-
|
650
|
-
ovfFileBasename = File.basename(ovfFile, ".ovf")
|
651
|
-
ovfDir = File.dirname(ovfFile)
|
652
|
-
|
653
|
-
# Send OVF Descriptor
|
654
|
-
uploadURL = "/transfer/#{descriptorUpload}"
|
655
|
-
uploadFile = "#{ovfDir}/#{ovfFileBasename}.ovf"
|
656
|
-
upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
|
657
|
-
|
658
|
-
# Begin the catch for upload interruption
|
659
|
-
begin
|
660
|
-
params = {
|
661
|
-
'method' => :get,
|
662
|
-
'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
|
663
|
-
}
|
664
|
-
|
665
|
-
# Loop to wait for the upload links to show up in the vAppTemplate we just created
|
666
|
-
while true
|
667
|
-
response, headers = send_request(params)
|
668
|
-
break unless response.css("Files Link [rel='upload:default']").count == 1
|
669
|
-
sleep 1
|
670
|
-
end
|
671
|
-
|
672
|
-
# Send Manifest
|
673
|
-
uploadURL = "/transfer/#{transferGUID}/descriptor.mf"
|
674
|
-
uploadFile = "#{ovfDir}/#{ovfFileBasename}.mf"
|
675
|
-
upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
|
676
|
-
|
677
|
-
# Start uploading OVF VMDK files
|
678
|
-
params = {
|
679
|
-
'method' => :get,
|
680
|
-
'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
|
681
|
-
}
|
682
|
-
response, headers = send_request(params)
|
683
|
-
response.css("Files File [bytesTransferred='0'] Link [rel='upload:default']").each do |file|
|
684
|
-
fileName = file[:href].gsub("#{@host_url}/transfer/#{transferGUID}/","")
|
685
|
-
uploadFile = "#{ovfDir}/#{fileName}"
|
686
|
-
uploadURL = "/transfer/#{transferGUID}/#{fileName}"
|
687
|
-
upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
|
688
|
-
end
|
689
|
-
|
690
|
-
# Add item to the catalog catalogId
|
691
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
692
|
-
xml.CatalogItem(
|
693
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
694
|
-
"type" => "application/vnd.vmware.vcloud.catalogItem+xml",
|
695
|
-
"name" => vappName) {
|
696
|
-
xml.Description vappDescription
|
697
|
-
xml.Entity(
|
698
|
-
"href" => "#{@api_url}/vAppTemplate/vappTemplate-#{vAppTemplate}"
|
699
|
-
)
|
700
|
-
}
|
701
|
-
end
|
702
|
-
|
703
|
-
params = {
|
704
|
-
'method' => :post,
|
705
|
-
'command' => "/catalog/#{catalogId}/catalogItems"
|
706
|
-
}
|
707
|
-
|
708
|
-
response, headers = send_request(params, builder.to_xml,
|
709
|
-
"application/vnd.vmware.vcloud.catalogItem+xml")
|
710
|
-
|
711
|
-
rescue Exception => e
|
712
|
-
puts "Exception detected: #{e.message}."
|
713
|
-
puts "Aborting task..."
|
714
|
-
|
715
|
-
# Get vAppTemplate Task
|
716
|
-
params = {
|
717
|
-
'method' => :get,
|
718
|
-
'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
|
719
|
-
}
|
720
|
-
response, headers = send_request(params)
|
721
|
-
|
722
|
-
# Cancel Task
|
723
|
-
cancelHook = response.css("Tasks Task Link [rel='task:cancel']").first[:href].gsub("#{@api_url}","")
|
724
|
-
params = {
|
725
|
-
'method' => :post,
|
726
|
-
'command' => cancelHook
|
727
|
-
}
|
728
|
-
response, headers = send_request(params)
|
729
|
-
raise
|
730
|
-
end
|
85
|
+
# reset auth key to nil
|
86
|
+
@auth_key = nil
|
731
87
|
end
|
732
88
|
|
733
89
|
##
|
@@ -751,145 +107,22 @@ module VCloudClient
|
|
751
107
|
##
|
752
108
|
# Poll a given task until completion
|
753
109
|
def wait_task_completion(taskid)
|
754
|
-
|
110
|
+
errormsg = nil
|
111
|
+
task = {}
|
112
|
+
|
755
113
|
loop do
|
756
114
|
task = get_task(taskid)
|
757
115
|
break if task[:status] != 'running'
|
758
116
|
sleep 1
|
759
117
|
end
|
760
118
|
|
761
|
-
if status == 'error'
|
762
|
-
errormsg = response.css("Error").first
|
119
|
+
if task[:status] == 'error'
|
120
|
+
errormsg = task[:response].css("Error").first
|
763
121
|
errormsg = "Error code #{errormsg['majorErrorCode']} - #{errormsg['message']}"
|
764
122
|
end
|
765
123
|
|
766
|
-
{ :status => status, :errormsg => errormsg,
|
767
|
-
|
768
|
-
|
769
|
-
##
|
770
|
-
# Set vApp Network Config
|
771
|
-
def set_vapp_network_config(vappid, network_name, config={})
|
772
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
773
|
-
xml.NetworkConfigSection(
|
774
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
775
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
|
776
|
-
xml['ovf'].Info "Network configuration"
|
777
|
-
xml.NetworkConfig("networkName" => network_name) {
|
778
|
-
xml.Configuration {
|
779
|
-
xml.FenceMode(config[:fence_mode] || 'isolated')
|
780
|
-
xml.RetainNetInfoAcrossDeployments(config[:retain_net] || false)
|
781
|
-
xml.ParentNetwork("href" => config[:parent_network])
|
782
|
-
}
|
783
|
-
}
|
784
|
-
}
|
785
|
-
end
|
786
|
-
|
787
|
-
params = {
|
788
|
-
'method' => :put,
|
789
|
-
'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
|
790
|
-
}
|
791
|
-
|
792
|
-
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConfigSection+xml")
|
793
|
-
|
794
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
795
|
-
task_id
|
796
|
-
end
|
797
|
-
|
798
|
-
##
|
799
|
-
# Set VM Network Config
|
800
|
-
def set_vm_network_config(vmid, network_name, config={})
|
801
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
802
|
-
xml.NetworkConnectionSection(
|
803
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
804
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
|
805
|
-
xml['ovf'].Info "VM Network configuration"
|
806
|
-
xml.PrimaryNetworkConnectionIndex(config[:primary_index] || 0)
|
807
|
-
xml.NetworkConnection("network" => network_name, "needsCustomization" => true) {
|
808
|
-
xml.NetworkConnectionIndex(config[:network_index] || 0)
|
809
|
-
xml.IpAddress config[:ip] if config[:ip]
|
810
|
-
xml.IsConnected(config[:is_connected] || true)
|
811
|
-
xml.IpAddressAllocationMode config[:ip_allocation_mode] if config[:ip_allocation_mode]
|
812
|
-
}
|
813
|
-
}
|
814
|
-
end
|
815
|
-
|
816
|
-
params = {
|
817
|
-
'method' => :put,
|
818
|
-
'command' => "/vApp/vm-#{vmid}/networkConnectionSection"
|
819
|
-
}
|
820
|
-
|
821
|
-
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConnectionSection+xml")
|
822
|
-
|
823
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
824
|
-
task_id
|
825
|
-
end
|
826
|
-
|
827
|
-
|
828
|
-
##
|
829
|
-
# Set VM Guest Customization Config
|
830
|
-
def set_vm_guest_customization(vmid, computer_name, config={})
|
831
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
832
|
-
xml.GuestCustomizationSection(
|
833
|
-
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
834
|
-
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
|
835
|
-
xml['ovf'].Info "VM Guest Customization configuration"
|
836
|
-
xml.Enabled config[:enabled] if config[:enabled]
|
837
|
-
xml.AdminPasswordEnabled config[:admin_passwd_enabled] if config[:admin_passwd_enabled]
|
838
|
-
xml.AdminPassword config[:admin_passwd] if config[:admin_passwd]
|
839
|
-
xml.ComputerName computer_name
|
840
|
-
}
|
841
|
-
end
|
842
|
-
|
843
|
-
params = {
|
844
|
-
'method' => :put,
|
845
|
-
'command' => "/vApp/vm-#{vmid}/guestCustomizationSection"
|
846
|
-
}
|
847
|
-
|
848
|
-
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.guestCustomizationSection+xml")
|
849
|
-
|
850
|
-
task_id = headers[:location].gsub("#{@api_url}/task/", "")
|
851
|
-
task_id
|
852
|
-
end
|
853
|
-
|
854
|
-
##
|
855
|
-
# Fetch details about a given VM
|
856
|
-
def get_vm(vmId)
|
857
|
-
params = {
|
858
|
-
'method' => :get,
|
859
|
-
'command' => "/vApp/vm-#{vmId}"
|
860
|
-
}
|
861
|
-
|
862
|
-
response, headers = send_request(params)
|
863
|
-
|
864
|
-
os_desc = response.css('ovf|OperatingSystemSection ovf|Description').first.text
|
865
|
-
|
866
|
-
networks = {}
|
867
|
-
response.css('NetworkConnection').each do |network|
|
868
|
-
ip = network.css('IpAddress').first
|
869
|
-
ip = ip.text if ip
|
870
|
-
|
871
|
-
networks[network['network']] = {
|
872
|
-
:index => network.css('NetworkConnectionIndex').first.text,
|
873
|
-
:ip => ip,
|
874
|
-
:is_connected => network.css('IsConnected').first.text,
|
875
|
-
:mac_address => network.css('MACAddress').first.text,
|
876
|
-
:ip_allocation_mode => network.css('IpAddressAllocationMode').first.text
|
877
|
-
}
|
878
|
-
end
|
879
|
-
|
880
|
-
admin_password = response.css('GuestCustomizationSection AdminPassword').first
|
881
|
-
admin_password = admin_password.text if admin_password
|
882
|
-
|
883
|
-
guest_customizations = {
|
884
|
-
:enabled => response.css('GuestCustomizationSection Enabled').first.text,
|
885
|
-
:admin_passwd_enabled => response.css('GuestCustomizationSection AdminPasswordEnabled').first.text,
|
886
|
-
:admin_passwd_auto => response.css('GuestCustomizationSection AdminPasswordAuto').first.text,
|
887
|
-
:admin_passwd => admin_password,
|
888
|
-
:reset_passwd_required => response.css('GuestCustomizationSection ResetPasswordRequired').first.text,
|
889
|
-
:computer_name => response.css('GuestCustomizationSection ComputerName').first.text
|
890
|
-
}
|
891
|
-
|
892
|
-
{ :os_desc => os_desc, :networks => networks, :guest_customizations => guest_customizations }
|
124
|
+
{ :status => task[:status], :errormsg => errormsg,
|
125
|
+
:start_time => task[:start_time], :end_time => task[:end_time] }
|
893
126
|
end
|
894
127
|
|
895
128
|
private
|
@@ -912,33 +145,21 @@ module VCloudClient
|
|
912
145
|
:url => "#{@api_url}#{params['command']}",
|
913
146
|
:payload => payload)
|
914
147
|
|
915
|
-
|
916
148
|
begin
|
917
149
|
response = request.execute
|
918
150
|
if ![200, 201, 202, 204].include?(response.code)
|
919
|
-
|
151
|
+
@logger.warn "Warning: unattended code #{response.code}"
|
920
152
|
end
|
921
153
|
|
922
|
-
|
154
|
+
@logger.debug "Send request result: #{Nokogiri.parse(response)}"
|
155
|
+
|
923
156
|
[Nokogiri.parse(response), response.headers]
|
924
157
|
rescue RestClient::Unauthorized => e
|
925
158
|
raise UnauthorizedAccess, "Client not authorized. Please check your credentials."
|
926
159
|
rescue RestClient::BadRequest => e
|
927
160
|
body = Nokogiri.parse(e.http_body)
|
928
161
|
message = body.css("Error").first["message"]
|
929
|
-
|
930
|
-
case message
|
931
|
-
when /The request has invalid accept header/
|
932
|
-
raise WrongAPIVersion, "Invalid accept header. Please verify that the server supports v.#{@api_version} or specify a different API Version."
|
933
|
-
when /validation error on field 'id': String value has invalid format or length/
|
934
|
-
raise WrongItemIDError, "Invalid ID specified. Please verify that the item exists and correctly typed."
|
935
|
-
when /The requested operation could not be executed on vApp "(.*)". Stop the vApp and try again/
|
936
|
-
raise InvalidStateError, "Invalid request because vApp is running. Stop vApp '#{$1}' and try again."
|
937
|
-
when /The requested operation could not be executed since vApp "(.*)" is not running/
|
938
|
-
raise InvalidStateError, "Invalid request because vApp is stopped. Start vApp '#{$1}' and try again."
|
939
|
-
else
|
940
|
-
raise UnhandledError, "BadRequest - unhandled error: #{message}.\nPlease report this issue."
|
941
|
-
end
|
162
|
+
humanize_badrequest(message)
|
942
163
|
rescue RestClient::Forbidden => e
|
943
164
|
body = Nokogiri.parse(e.http_body)
|
944
165
|
message = body.css("Error").first["message"]
|
@@ -947,94 +168,13 @@ module VCloudClient
|
|
947
168
|
body = Nokogiri.parse(e.http_body)
|
948
169
|
message = body.css("Error").first["message"]
|
949
170
|
raise InternalServerError, "Internal Server Error: #{message}."
|
171
|
+
rescue RestClient::MethodNotAllowed => e
|
172
|
+
body = Nokogiri.parse(e.http_body)
|
173
|
+
message = body.css("Error").first["message"]
|
174
|
+
raise MethodNotAllowed, "#{params['method']} to #{params['command']} not allowed: #{message}."
|
950
175
|
end
|
951
176
|
end
|
952
177
|
|
953
|
-
##
|
954
|
-
# Upload a large file in configurable chunks, output an optional progressbar
|
955
|
-
def upload_file(uploadURL, uploadFile, vAppTemplate, config={})
|
956
|
-
|
957
|
-
# Set chunksize to 10M if not specified otherwise
|
958
|
-
chunkSize = (config[:chunksize] || 10485760)
|
959
|
-
|
960
|
-
# Set progress bar to default format if not specified otherwise
|
961
|
-
progressBarFormat = (config[:progressbar_format] || "%e <%B> %p%% %t")
|
962
|
-
|
963
|
-
# Set progress bar length to 120 if not specified otherwise
|
964
|
-
progressBarLength = (config[:progressbar_length] || 120)
|
965
|
-
|
966
|
-
# Open our file for upload
|
967
|
-
uploadFileHandle = File.new(uploadFile, "rb" )
|
968
|
-
fileName = File.basename(uploadFileHandle)
|
969
|
-
|
970
|
-
progressBarTitle = "Uploading: " + uploadFile.to_s
|
971
|
-
|
972
|
-
# Create a progressbar object if progress bar is enabled
|
973
|
-
if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
|
974
|
-
progressbar = ProgressBar.create(:title => progressBarTitle, :starting_at => 0, :total => uploadFileHandle.size.to_i, :length => progressBarLength, :format => progressBarFormat)
|
975
|
-
else
|
976
|
-
puts progressBarTitle
|
977
|
-
end
|
978
|
-
# Create a new HTTP client
|
979
|
-
clnt = HTTPClient.new
|
980
|
-
|
981
|
-
# Disable SSL cert verification
|
982
|
-
clnt.ssl_config.verify_mode=(OpenSSL::SSL::VERIFY_NONE)
|
983
|
-
|
984
|
-
# Suppress SSL depth message
|
985
|
-
clnt.ssl_config.verify_callback=proc{ |ok, ctx|; true };
|
986
|
-
|
987
|
-
# Perform ranged upload until the file reaches its end
|
988
|
-
until uploadFileHandle.eof?
|
989
|
-
|
990
|
-
# Create ranges for this chunk upload
|
991
|
-
rangeStart = uploadFileHandle.pos
|
992
|
-
rangeStop = uploadFileHandle.pos.to_i + chunkSize
|
993
|
-
|
994
|
-
# Read current chunk
|
995
|
-
fileContent = uploadFileHandle.read(chunkSize)
|
996
|
-
|
997
|
-
# If statement to handle last chunk transfer if is > than filesize
|
998
|
-
if rangeStop.to_i > uploadFileHandle.size.to_i
|
999
|
-
contentRange = "bytes " + rangeStart.to_s + "-" + uploadFileHandle.size.to_s + "/" + uploadFileHandle.size.to_s
|
1000
|
-
rangeLen = uploadFileHandle.size.to_i - rangeStart.to_i
|
1001
|
-
else
|
1002
|
-
contentRange = "bytes " + rangeStart.to_s + "-" + rangeStop.to_s + "/" + uploadFileHandle.size.to_s
|
1003
|
-
rangeLen = rangeStop.to_i - rangeStart.to_i
|
1004
|
-
end
|
1005
|
-
|
1006
|
-
# Build headers
|
1007
|
-
extheader = {
|
1008
|
-
'x-vcloud-authorization' => @auth_key,
|
1009
|
-
'Content-Range' => contentRange,
|
1010
|
-
'Content-Length' => rangeLen.to_s
|
1011
|
-
}
|
1012
|
-
|
1013
|
-
begin
|
1014
|
-
uploadRequest = "#{@host_url}#{uploadURL}"
|
1015
|
-
connection = clnt.request('PUT', uploadRequest, nil, fileContent, extheader)
|
1016
|
-
if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
|
1017
|
-
params = {
|
1018
|
-
'method' => :get,
|
1019
|
-
'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
|
1020
|
-
}
|
1021
|
-
response, headers = send_request(params)
|
1022
|
-
|
1023
|
-
response.css("Files File [name='#{fileName}']").each do |file|
|
1024
|
-
progressbar.progress=file[:bytesTransferred].to_i
|
1025
|
-
end
|
1026
|
-
end
|
1027
|
-
rescue
|
1028
|
-
retryTime = (config[:retry_time] || 5)
|
1029
|
-
puts "Range #{contentRange} failed to upload, retrying the chunk in #{retryTime.to_s} seconds, to stop the action press CTRL+C."
|
1030
|
-
sleep retryTime.to_i
|
1031
|
-
retry
|
1032
|
-
end
|
1033
|
-
end
|
1034
|
-
uploadFileHandle.close
|
1035
|
-
end
|
1036
|
-
|
1037
|
-
|
1038
178
|
##
|
1039
179
|
# Convert vApp status codes into human readable description
|
1040
180
|
def convert_vapp_status(status_code)
|
@@ -1053,5 +193,47 @@ module VCloudClient
|
|
1053
193
|
"Unknown #{status_code}"
|
1054
194
|
end
|
1055
195
|
end
|
196
|
+
|
197
|
+
def init_logger
|
198
|
+
level = if ENV["VCLOUD_REST_DEBUG_LEVEL"]
|
199
|
+
Logger::Severity.constants.find_index ENV["VCLOUD_REST_DEBUG_LEVEL"].upcase.to_sym
|
200
|
+
else
|
201
|
+
Logger::WARN
|
202
|
+
end
|
203
|
+
@logger = Logger.new(ENV["VCLOUD_REST_LOG_FILE"] || STDOUT)
|
204
|
+
@logger.level = level
|
205
|
+
end
|
206
|
+
|
207
|
+
def humanize_badrequest(message)
|
208
|
+
case message
|
209
|
+
when /The request has invalid accept header/
|
210
|
+
raise WrongAPIVersion, "Invalid accept header. Please verify that the server supports v.#{@api_version} or specify a different API Version."
|
211
|
+
when /validation error on field 'id': String value has invalid format or length/
|
212
|
+
raise WrongItemIDError, "Invalid ID specified. Please verify that the item exists and correctly typed."
|
213
|
+
when /The requested operation could not be executed on vApp "(.*)". Stop the vApp and try again/
|
214
|
+
raise InvalidStateError, "Invalid request because vApp is running. Stop vApp '#{$1}' and try again."
|
215
|
+
when /The requested operation could not be executed since vApp "(.*)" is not running/
|
216
|
+
raise InvalidStateError, "Invalid request because vApp is stopped. Start vApp '#{$1}' and try again."
|
217
|
+
else
|
218
|
+
raise UnhandledError, "BadRequest - unhandled error: #{message}.\nPlease report this issue."
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Generic method to send power actions to vApp/VM
|
224
|
+
#
|
225
|
+
# i.e., 'suspend', 'powerOn'
|
226
|
+
def power_action(id, action, type=:vapp)
|
227
|
+
target = "#{type}-#{id}"
|
228
|
+
|
229
|
+
params = {
|
230
|
+
'method' => :post,
|
231
|
+
'command' => "/vApp/#{target}/power/action/#{action}"
|
232
|
+
}
|
233
|
+
|
234
|
+
response, headers = send_request(params)
|
235
|
+
task_id = headers[:location].gsub(/.*\/task\//, "")
|
236
|
+
task_id
|
237
|
+
end
|
1056
238
|
end # class
|
1057
239
|
end
|