vcloud-rest 0.2.2 → 0.3.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 +25 -0
- data/README.md +14 -3
- data/lib/vcloud-rest/connection.rb +538 -37
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c88e00c344ec71b95abf4340a2a1401d6617747
|
4
|
+
data.tar.gz: 07d79063ef3ab8906b30196c47758471c1473d88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdd41a731e6204c0b1279904412dc5546bb57540e8c43b646274e44c3f17f84359d90665d122bb3f8c43c154f5459cbb7e3b4d5006917b0bc0830c4f0d3ba7e9
|
7
|
+
data.tar.gz: 144ce4ac54ef6b04b072306a86e9ea724091f419be29c2f8b6c681248c1ee264f214641decf658200c3d71ced465d5a37dbfe929c407cd019a269e954fb871c9
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
Changes
|
2
2
|
==
|
3
|
+
2013-07-25 (0.3.0)
|
4
|
+
|
5
|
+
FEATURES:
|
6
|
+
|
7
|
+
* Add ```compose_vapp_from_vm``` to compose a vapp using VMs in a catalog
|
8
|
+
* Add ```get_vapp_template``` to get information on VMs inside a vapp template
|
9
|
+
* Add ```set_vapp_port_forwarding_rules``` to set NAT port forwarding rules in an existing vapp
|
10
|
+
* ```set_vapp_network_config```: add parent network specification
|
11
|
+
* ```get_vapp```: ```vms_hash``` now contains also ```vapp_scoped_local_id```
|
12
|
+
* Add ```get_vapp_edge_public_ip``` to fetch the public IP of a vApp (vShield Edge)
|
13
|
+
* Add ```get_vapp_port_forwarding_rules``` to return vApp portforwarding rules
|
14
|
+
* Add ``reboot_vapp/suspend_vapp/reset_vapp``
|
15
|
+
* Add ```upload_ovf``` to upload an OVF Package
|
16
|
+
|
17
|
+
CHANGES:
|
18
|
+
|
19
|
+
* ```RetainNetInfoAcrossDeployments``` now defaults to false (fenced deployments)
|
20
|
+
|
21
|
+
FIXES:
|
22
|
+
|
23
|
+
* Better handling of 500 errors
|
24
|
+
|
25
|
+
REMARKS:
|
26
|
+
A big thanks to Fabio Rapposelli and Timo Sugliani for the great work done!
|
27
|
+
|
3
28
|
2013-05-13 (0.2.2)
|
4
29
|
--
|
5
30
|
|
data/README.md
CHANGED
@@ -19,7 +19,7 @@ This plugin is distributed as a Ruby Gem. To install it, run:
|
|
19
19
|
|
20
20
|
Depending on your system's configuration, you may need to run this command with root privileges.
|
21
21
|
|
22
|
-
vcloud-rest is tested against ruby 1.9.x and 1.8.7+.
|
22
|
+
vcloud-rest is tested against ruby 2.0.0, 1.9.x and 1.8.7+.
|
23
23
|
|
24
24
|
FEATURES
|
25
25
|
--
|
@@ -33,6 +33,9 @@ FEATURES
|
|
33
33
|
- basic vApp network configuration
|
34
34
|
- basic VM network configuration
|
35
35
|
- basic VM Guest Customization configuration
|
36
|
+
- basic vApp compose capabilities
|
37
|
+
- basic vApp NAT port forwarding creation
|
38
|
+
- Catalog item upload with byterange upload and retry capabilities
|
36
39
|
|
37
40
|
TODO
|
38
41
|
--
|
@@ -41,8 +44,10 @@ TODO
|
|
41
44
|
|
42
45
|
PREREQUISITES
|
43
46
|
--
|
44
|
-
- nokogiri ~> 1.
|
47
|
+
- nokogiri ~> 1.6.0
|
45
48
|
- rest-client ~> 1.6.7
|
49
|
+
- httpclient ~> 2.3.3
|
50
|
+
- ruby-progressbar ~> 1.1.1
|
46
51
|
|
47
52
|
For testing purpose:
|
48
53
|
- minitest (included in ruby 1.9)
|
@@ -56,6 +61,12 @@ USAGE
|
|
56
61
|
conn.login
|
57
62
|
conn.list_organizations
|
58
63
|
|
64
|
+
EXAMPLE
|
65
|
+
--
|
66
|
+
A (mostly complete) example can be found in
|
67
|
+
|
68
|
+
examples/example.rb
|
69
|
+
|
59
70
|
TESTING
|
60
71
|
--
|
61
72
|
Simply run:
|
@@ -72,7 +83,7 @@ LICENSE
|
|
72
83
|
|
73
84
|
Author:: Stefano Tortarolo <stefano.tortarolo@gmail.com>
|
74
85
|
|
75
|
-
Copyright:: Copyright (c) 2012
|
86
|
+
Copyright:: Copyright (c) 2012-2013
|
76
87
|
License:: Apache License, Version 2.0
|
77
88
|
|
78
89
|
Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -18,14 +18,18 @@
|
|
18
18
|
|
19
19
|
require 'rest-client'
|
20
20
|
require 'nokogiri'
|
21
|
+
require 'httpclient'
|
22
|
+
require 'ruby-progressbar'
|
21
23
|
|
22
24
|
module VCloudClient
|
23
25
|
class UnauthorizedAccess < StandardError; end
|
24
26
|
class WrongAPIVersion < StandardError; end
|
25
27
|
class WrongItemIDError < StandardError; end
|
26
28
|
class InvalidStateError < StandardError; end
|
29
|
+
class InternalServerError < StandardError; end
|
27
30
|
class UnhandledError < StandardError; end
|
28
31
|
|
32
|
+
|
29
33
|
# Main class to access vCloud rest APIs
|
30
34
|
class Connection
|
31
35
|
attr_reader :api_url, :auth_key
|
@@ -33,6 +37,7 @@ module VCloudClient
|
|
33
37
|
def initialize(host, username, password, org_name, api_version)
|
34
38
|
@host = host
|
35
39
|
@api_url = "#{host}/api"
|
40
|
+
@host_url = "#{host}"
|
36
41
|
@username = username
|
37
42
|
@password = password
|
38
43
|
@org_name = org_name
|
@@ -68,8 +73,8 @@ module VCloudClient
|
|
68
73
|
end
|
69
74
|
|
70
75
|
##
|
71
|
-
#
|
72
|
-
def
|
76
|
+
# Fetch existing organizations and their IDs
|
77
|
+
def get_organizations
|
73
78
|
params = {
|
74
79
|
'method' => :get,
|
75
80
|
'command' => '/org'
|
@@ -86,11 +91,11 @@ module VCloudClient
|
|
86
91
|
end
|
87
92
|
|
88
93
|
##
|
89
|
-
#
|
94
|
+
# Fetch details about an organization:
|
90
95
|
# - catalogs
|
91
96
|
# - vdcs
|
92
97
|
# - networks
|
93
|
-
def
|
98
|
+
def get_organization(orgId)
|
94
99
|
params = {
|
95
100
|
'method' => :get,
|
96
101
|
'command' => "/org/#{orgId}"
|
@@ -117,12 +122,12 @@ module VCloudClient
|
|
117
122
|
tasklists[item['name']] = item['href'].gsub("#{@api_url}/tasksList/", "")
|
118
123
|
end
|
119
124
|
|
120
|
-
|
125
|
+
{ :catalogs => catalogs, :vdcs => vdcs, :networks => networks, :tasklists => tasklists }
|
121
126
|
end
|
122
127
|
|
123
128
|
##
|
124
|
-
#
|
125
|
-
def
|
129
|
+
# Fetch details about a given catalog
|
130
|
+
def get_catalog(catalogId)
|
126
131
|
params = {
|
127
132
|
'method' => :get,
|
128
133
|
'command' => "/catalog/#{catalogId}"
|
@@ -136,16 +141,15 @@ module VCloudClient
|
|
136
141
|
response.css("CatalogItem[type='application/vnd.vmware.vcloud.catalogItem+xml']").each do |item|
|
137
142
|
items[item['name']] = item['href'].gsub("#{@api_url}/catalogItem/", "")
|
138
143
|
end
|
139
|
-
|
140
|
-
[description, items]
|
144
|
+
{ :description => description, :items => items }
|
141
145
|
end
|
142
146
|
|
143
147
|
##
|
144
|
-
#
|
148
|
+
# Fetch details about a given vdc:
|
145
149
|
# - description
|
146
150
|
# - vapps
|
147
151
|
# - networks
|
148
|
-
def
|
152
|
+
def get_vdc(vdcId)
|
149
153
|
params = {
|
150
154
|
'method' => :get,
|
151
155
|
'command' => "/vdc/#{vdcId}"
|
@@ -164,15 +168,14 @@ module VCloudClient
|
|
164
168
|
response.css("Network[type='application/vnd.vmware.vcloud.network+xml']").each do |item|
|
165
169
|
networks[item['name']] = item['href'].gsub("#{@api_url}/network/", "")
|
166
170
|
end
|
167
|
-
|
168
|
-
[description, vapps, networks]
|
171
|
+
{ :description => description, :vapps => vapps, :networks => networks }
|
169
172
|
end
|
170
173
|
|
171
174
|
##
|
172
|
-
#
|
175
|
+
# Fetch details about a given catalog item:
|
173
176
|
# - description
|
174
177
|
# - vApp templates
|
175
|
-
def
|
178
|
+
def get_catalog_item(catalogItemId)
|
176
179
|
params = {
|
177
180
|
'method' => :get,
|
178
181
|
'command' => "/catalogItem/#{catalogItemId}"
|
@@ -186,12 +189,11 @@ module VCloudClient
|
|
186
189
|
response.css("Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']").each do |item|
|
187
190
|
items[item['name']] = item['href'].gsub("#{@api_url}/vAppTemplate/vappTemplate-", "")
|
188
191
|
end
|
189
|
-
|
190
|
-
[description, items]
|
192
|
+
{ :description => description, :items => items }
|
191
193
|
end
|
192
194
|
|
193
195
|
##
|
194
|
-
#
|
196
|
+
# Fetch details about a given vapp:
|
195
197
|
# - name
|
196
198
|
# - description
|
197
199
|
# - status
|
@@ -200,7 +202,7 @@ module VCloudClient
|
|
200
202
|
# -- IP addresses
|
201
203
|
# -- status
|
202
204
|
# -- ID
|
203
|
-
def
|
205
|
+
def get_vapp(vAppId)
|
204
206
|
params = {
|
205
207
|
'method' => :get,
|
206
208
|
'command' => "/vApp/vapp-#{vAppId}"
|
@@ -225,15 +227,18 @@ module VCloudClient
|
|
225
227
|
|
226
228
|
# ipAddress could be namespaced or not: see https://github.com/astratto/vcloud-rest/issues/3
|
227
229
|
vms.each do |vm|
|
230
|
+
vapp_local_id = vm.css('VAppScopedLocalId')
|
228
231
|
addresses = vm.css('rasd|Connection').collect{|n| n['vcloud:ipAddress'] || n['ipAddress'] }
|
229
|
-
vms_hash[vm['name']] = {
|
232
|
+
vms_hash[vm['name']] = {
|
233
|
+
:addresses => addresses,
|
230
234
|
:status => convert_vapp_status(vm['status']),
|
231
|
-
:id => vm['href'].gsub("#{@api_url}/vApp/vm-", '')
|
235
|
+
:id => vm['href'].gsub("#{@api_url}/vApp/vm-", ''),
|
236
|
+
:vapp_scoped_local_id => vapp_local_id.text
|
232
237
|
}
|
233
238
|
end
|
234
239
|
|
235
240
|
# TODO: EXPAND INFO FROM RESPONSE
|
236
|
-
|
241
|
+
{ :name => name, :description => description, :status => status, :ip => ip, :vms_hash => vms_hash }
|
237
242
|
end
|
238
243
|
|
239
244
|
##
|
@@ -271,6 +276,50 @@ module VCloudClient
|
|
271
276
|
task_id
|
272
277
|
end
|
273
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
|
+
|
274
323
|
##
|
275
324
|
# Boot a given vapp
|
276
325
|
def poweron_vapp(vAppId)
|
@@ -318,12 +367,372 @@ module VCloudClient
|
|
318
367
|
task = response.css("VApp Task[operationName='vdcInstantiateVapp']").first
|
319
368
|
task_id = task["href"].gsub("#{@api_url}/task/", "")
|
320
369
|
|
321
|
-
|
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
|
322
731
|
end
|
323
732
|
|
324
733
|
##
|
325
|
-
#
|
326
|
-
def
|
734
|
+
# Fetch information for a given task
|
735
|
+
def get_task(taskid)
|
327
736
|
params = {
|
328
737
|
'method' => :get,
|
329
738
|
'command' => "/task/#{taskid}"
|
@@ -336,7 +745,7 @@ module VCloudClient
|
|
336
745
|
start_time = task['startTime']
|
337
746
|
end_time = task['endTime']
|
338
747
|
|
339
|
-
|
748
|
+
{ :status => status, :start_time => start_time, :end_time => end_time, :response => response }
|
340
749
|
end
|
341
750
|
|
342
751
|
##
|
@@ -344,8 +753,8 @@ module VCloudClient
|
|
344
753
|
def wait_task_completion(taskid)
|
345
754
|
status, errormsg, start_time, end_time, response = nil
|
346
755
|
loop do
|
347
|
-
|
348
|
-
break if status != 'running'
|
756
|
+
task = get_task(taskid)
|
757
|
+
break if task[:status] != 'running'
|
349
758
|
sleep 1
|
350
759
|
end
|
351
760
|
|
@@ -354,7 +763,7 @@ module VCloudClient
|
|
354
763
|
errormsg = "Error code #{errormsg['majorErrorCode']} - #{errormsg['message']}"
|
355
764
|
end
|
356
765
|
|
357
|
-
|
766
|
+
{ :status => status, :errormsg => errormsg, :start_time => start_time, :end_time => end_time }
|
358
767
|
end
|
359
768
|
|
360
769
|
##
|
@@ -367,8 +776,9 @@ module VCloudClient
|
|
367
776
|
xml['ovf'].Info "Network configuration"
|
368
777
|
xml.NetworkConfig("networkName" => network_name) {
|
369
778
|
xml.Configuration {
|
370
|
-
xml.FenceMode
|
371
|
-
xml.RetainNetInfoAcrossDeployments
|
779
|
+
xml.FenceMode(config[:fence_mode] || 'isolated')
|
780
|
+
xml.RetainNetInfoAcrossDeployments(config[:retain_net] || false)
|
781
|
+
xml.ParentNetwork("href" => config[:parent_network])
|
372
782
|
}
|
373
783
|
}
|
374
784
|
}
|
@@ -393,11 +803,11 @@ module VCloudClient
|
|
393
803
|
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
394
804
|
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
|
395
805
|
xml['ovf'].Info "VM Network configuration"
|
396
|
-
xml.PrimaryNetworkConnectionIndex
|
806
|
+
xml.PrimaryNetworkConnectionIndex(config[:primary_index] || 0)
|
397
807
|
xml.NetworkConnection("network" => network_name, "needsCustomization" => true) {
|
398
|
-
xml.NetworkConnectionIndex
|
808
|
+
xml.NetworkConnectionIndex(config[:network_index] || 0)
|
399
809
|
xml.IpAddress config[:ip] if config[:ip]
|
400
|
-
xml.IsConnected
|
810
|
+
xml.IsConnected(config[:is_connected] || true)
|
401
811
|
xml.IpAddressAllocationMode config[:ip_allocation_mode] if config[:ip_allocation_mode]
|
402
812
|
}
|
403
813
|
}
|
@@ -442,8 +852,8 @@ module VCloudClient
|
|
442
852
|
end
|
443
853
|
|
444
854
|
##
|
445
|
-
#
|
446
|
-
def
|
855
|
+
# Fetch details about a given VM
|
856
|
+
def get_vm(vmId)
|
447
857
|
params = {
|
448
858
|
'method' => :get,
|
449
859
|
'command' => "/vApp/vm-#{vmId}"
|
@@ -479,7 +889,7 @@ module VCloudClient
|
|
479
889
|
:computer_name => response.css('GuestCustomizationSection ComputerName').first.text
|
480
890
|
}
|
481
891
|
|
482
|
-
|
892
|
+
{ :os_desc => os_desc, :networks => networks, :guest_customizations => guest_customizations }
|
483
893
|
end
|
484
894
|
|
485
895
|
private
|
@@ -501,6 +911,8 @@ module VCloudClient
|
|
501
911
|
:headers => headers,
|
502
912
|
:url => "#{@api_url}#{params['command']}",
|
503
913
|
:payload => payload)
|
914
|
+
|
915
|
+
|
504
916
|
begin
|
505
917
|
response = request.execute
|
506
918
|
if ![200, 201, 202, 204].include?(response.code)
|
@@ -531,9 +943,98 @@ module VCloudClient
|
|
531
943
|
body = Nokogiri.parse(e.http_body)
|
532
944
|
message = body.css("Error").first["message"]
|
533
945
|
raise UnauthorizedAccess, "Operation not permitted: #{message}."
|
946
|
+
rescue RestClient::InternalServerError => e
|
947
|
+
body = Nokogiri.parse(e.http_body)
|
948
|
+
message = body.css("Error").first["message"]
|
949
|
+
raise InternalServerError, "Internal Server Error: #{message}."
|
534
950
|
end
|
535
951
|
end
|
536
952
|
|
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
|
+
|
537
1038
|
##
|
538
1039
|
# Convert vApp status codes into human readable description
|
539
1040
|
def convert_vapp_status(status_code)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vcloud-rest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefano Tortarolo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-07-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.6.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.6.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rest-client
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 1.6.7
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: httpclient
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.3.3
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.3.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ruby-progressbar
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.1.1
|
41
69
|
description: Ruby bindings to create, list and manage vCloud servers
|
42
70
|
email:
|
43
71
|
- stefano.tortarolo@gmail.com
|