vcloud-rest 0.2.2 → 0.3.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 +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
|