vcloud-rest 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|