vagrant-vcloud 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE +20 -0
  5. data/README.md +80 -0
  6. data/Rakefile +17 -0
  7. data/example_box/README.md +13 -0
  8. data/example_box/Vagrantfile +6 -0
  9. data/example_box/metadata.json +1 -0
  10. data/lib/vagrant-vcloud.rb +65 -0
  11. data/lib/vagrant-vcloud/action.rb +226 -0
  12. data/lib/vagrant-vcloud/action/announce_ssh_exec.rb +17 -0
  13. data/lib/vagrant-vcloud/action/build_vapp.rb +197 -0
  14. data/lib/vagrant-vcloud/action/connect_vcloud.rb +68 -0
  15. data/lib/vagrant-vcloud/action/destroy.rb +69 -0
  16. data/lib/vagrant-vcloud/action/disconnect_vcloud.rb +33 -0
  17. data/lib/vagrant-vcloud/action/forward_ports.rb +127 -0
  18. data/lib/vagrant-vcloud/action/handle_nat_port_collisions.rb +129 -0
  19. data/lib/vagrant-vcloud/action/inventory_check.rb +156 -0
  20. data/lib/vagrant-vcloud/action/is_created.rb +36 -0
  21. data/lib/vagrant-vcloud/action/is_paused.rb +22 -0
  22. data/lib/vagrant-vcloud/action/is_running.rb +22 -0
  23. data/lib/vagrant-vcloud/action/message_already_running.rb +17 -0
  24. data/lib/vagrant-vcloud/action/message_cannot_suspend.rb +17 -0
  25. data/lib/vagrant-vcloud/action/message_not_created.rb +17 -0
  26. data/lib/vagrant-vcloud/action/message_will_not_destroy.rb +17 -0
  27. data/lib/vagrant-vcloud/action/power_off.rb +33 -0
  28. data/lib/vagrant-vcloud/action/power_on.rb +46 -0
  29. data/lib/vagrant-vcloud/action/read_ssh_info.rb +69 -0
  30. data/lib/vagrant-vcloud/action/read_state.rb +59 -0
  31. data/lib/vagrant-vcloud/action/resume.rb +33 -0
  32. data/lib/vagrant-vcloud/action/suspend.rb +33 -0
  33. data/lib/vagrant-vcloud/action/sync_folders.rb +82 -0
  34. data/lib/vagrant-vcloud/action/unmap_port_forwardings.rb +75 -0
  35. data/lib/vagrant-vcloud/config.rb +132 -0
  36. data/lib/vagrant-vcloud/driver/base.rb +459 -0
  37. data/lib/vagrant-vcloud/driver/meta.rb +151 -0
  38. data/lib/vagrant-vcloud/driver/version_5_1.rb +1669 -0
  39. data/lib/vagrant-vcloud/errors.rb +62 -0
  40. data/lib/vagrant-vcloud/model/forwarded_port.rb +64 -0
  41. data/lib/vagrant-vcloud/plugin.rb +78 -0
  42. data/lib/vagrant-vcloud/provider.rb +41 -0
  43. data/lib/vagrant-vcloud/util/compile_forwarded_ports.rb +31 -0
  44. data/lib/vagrant-vcloud/version.rb +5 -0
  45. data/locales/en.yml +55 -0
  46. data/vagrant-vcloud.gemspec +34 -0
  47. metadata +273 -0
@@ -0,0 +1,151 @@
1
+ #
2
+ # Copyright 2012 Stefano Tortarolo
3
+ # Copyright 2013 Fabio Rapposelli and Timo Sugliani
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "forwardable"
19
+ require "log4r"
20
+ require "rest-client"
21
+ require "nokogiri"
22
+
23
+
24
+ require File.expand_path("../base", __FILE__)
25
+
26
+ module VagrantPlugins
27
+ module VCloud
28
+ module Driver
29
+
30
+ class Meta < Base
31
+
32
+ # We use forwardable to do all our driver forwarding
33
+ extend Forwardable
34
+ attr_reader :driver
35
+
36
+ def initialize(hostname, username, password, org_name)
37
+
38
+ # Setup the base
39
+ super()
40
+
41
+ @logger = Log4r::Logger.new("vagrant::provider::vcloud::meta")
42
+ @hostname = hostname
43
+ @username = username
44
+ @password = password
45
+ @org_name = org_name
46
+
47
+ # Read and assign the version of vCloud we know which
48
+ # specific driver to instantiate.
49
+ @version = get_api_version(@hostname) || ""
50
+
51
+ # Instantiate the proper version driver for vCloud
52
+ @logger.debug("Finding driver for vCloud version: #{@version}")
53
+ driver_map = {
54
+ "5.1" => Version_5_1
55
+ }
56
+
57
+ if @version.start_with?("0.9") || @version.start_with?("1.0") || @version.start_with?("1.5")
58
+ # We support only vCloud Director 5.1 or higher so show error.
59
+ raise Errors::VCloudOldVersion, :version => @version
60
+ end
61
+
62
+ driver_klass = nil
63
+ driver_map.each do |key, klass|
64
+ if @version.start_with?(key)
65
+ driver_klass = klass
66
+ break
67
+ end
68
+ end
69
+
70
+ if !driver_klass
71
+ supported_versions = driver_map.keys.sort.join(", ")
72
+ raise Errors::VCloudInvalidVersion, :supported_versions => supported_versions
73
+ end
74
+
75
+ @logger.info("Using vCloud driver: #{driver_klass}")
76
+ @driver = driver_klass.new(@hostname, @username, @password, @org_name)
77
+
78
+ end
79
+
80
+ def_delegators :@driver,
81
+ :login,
82
+ :logout,
83
+ :get_organizations,
84
+ :get_organization_id_by_name,
85
+ :get_organization_by_name,
86
+ :get_organization,
87
+ :get_catalog,
88
+ :get_catalog_id_by_name,
89
+ :get_catalog_by_name,
90
+ :get_vdc,
91
+ :get_vdc_id_by_name,
92
+ :get_vdc_by_name,
93
+ :get_catalog_item,
94
+ :get_catalog_item_by_name,
95
+ :get_vapp,
96
+ :delete_vapp,
97
+ :poweroff_vapp,
98
+ :suspend_vapp,
99
+ :reboot_vapp,
100
+ :reset_vapp,
101
+ :poweron_vapp,
102
+ :create_vapp_from_template,
103
+ :compose_vapp_from_vm,
104
+ :get_vapp_template,
105
+ :set_vapp_port_forwarding_rules,
106
+ :get_vapp_port_forwarding_rules,
107
+ :get_vapp_edge_public_ip,
108
+ :upload_ovf,
109
+ :get_task,
110
+ :wait_task_completion,
111
+ :set_vapp_network_config,
112
+ :set_vm_network_config,
113
+ :set_vm_guest_customization,
114
+ :get_vm,
115
+ :send_request,
116
+ :upload_file,
117
+ :convert_vapp_status
118
+
119
+
120
+ protected
121
+
122
+ def get_api_version(host_url)
123
+
124
+ request = RestClient::Request.new(
125
+ :method => "GET",
126
+ :url => "#{host_url}/api/versions"
127
+ )
128
+
129
+ begin
130
+ response = request.execute
131
+ if ![200, 201, 202, 204].include?(response.code)
132
+ puts "Warning: unattended code #{response.code}"
133
+ end
134
+
135
+ versionInfo = Nokogiri.parse(response)
136
+ # FIXME: Find a smarter way to check for vCloud API version
137
+ # Changed from .first to .last because that's the way it's defined
138
+ # in the request answer.
139
+ apiVersion = versionInfo.css("VersionInfo Version")
140
+
141
+ apiVersion.last.text
142
+
143
+ rescue SocketError, Errno::ECONNREFUSED, RestClient::ResourceNotFound
144
+ raise Errors::HostNotFound, :message => host_url
145
+ end
146
+
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,1669 @@
1
+ #
2
+ # Copyright 2012 Stefano Tortarolo
3
+ # Copyright 2013 Fabio Rapposelli and Timo Sugliani
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "rest-client"
19
+ require "nokogiri"
20
+ require "httpclient"
21
+ require "ruby-progressbar"
22
+ require "set"
23
+ require "netaddr"
24
+
25
+ module VagrantPlugins
26
+ module VCloud
27
+ module Driver
28
+
29
+ # Main class to access vCloud rest APIs
30
+ class Version_5_1 < Base
31
+ attr_reader :auth_key, :id
32
+
33
+ def initialize(host, username, password, org_name)
34
+
35
+ @logger = Log4r::Logger.new("vagrant::provider::vcloud::driver_5_1")
36
+
37
+ @host = host
38
+ @api_url = "#{host}/api"
39
+ @host_url = "#{host}"
40
+ @username = username
41
+ @password = password
42
+ @org_name = org_name
43
+ @api_version = "5.1"
44
+ @id = nil
45
+ end
46
+
47
+ ##
48
+ # Authenticate against the specified server
49
+ def login
50
+ params = {
51
+ 'method' => :post,
52
+ 'command' => '/sessions'
53
+ }
54
+
55
+ response, headers = send_request(params)
56
+
57
+ if !headers.has_key?(:x_vcloud_authorization)
58
+ raise "Unable to authenticate: missing x_vcloud_authorization header"
59
+ end
60
+
61
+ @auth_key = headers[:x_vcloud_authorization]
62
+ end
63
+
64
+ ##
65
+ # Destroy the current session
66
+ def logout
67
+ params = {
68
+ 'method' => :delete,
69
+ 'command' => '/session'
70
+ }
71
+
72
+ response, headers = send_request(params)
73
+ # reset auth key to nil
74
+ @auth_key = nil
75
+ end
76
+
77
+ ##
78
+ # Fetch existing organizations and their IDs
79
+ def get_organizations
80
+ params = {
81
+ 'method' => :get,
82
+ 'command' => '/org'
83
+ }
84
+
85
+ response, headers = send_request(params)
86
+ orgs = response.css('OrgList Org')
87
+
88
+ results = {}
89
+ orgs.each do |org|
90
+ results[org['name']] = org['href'].gsub("#{@api_url}/org/", "")
91
+ end
92
+ results
93
+ end
94
+
95
+ ##
96
+ # friendly helper method to fetch an Organization Id by name
97
+ # - name (this isn't case sensitive)
98
+ def get_organization_id_by_name(name)
99
+ result = nil
100
+
101
+ # Fetch all organizations
102
+ organizations = get_organizations()
103
+
104
+ organizations.each do |organization|
105
+ if organization[0].downcase == name.downcase
106
+ result = organization[1]
107
+ end
108
+ end
109
+ result
110
+ end
111
+
112
+
113
+ ##
114
+ # friendly helper method to fetch an Organization by name
115
+ # - name (this isn't case sensitive)
116
+ def get_organization_by_name(name)
117
+ result = nil
118
+
119
+ # Fetch all organizations
120
+ organizations = get_organizations()
121
+
122
+ organizations.each do |organization|
123
+ if organization[0].downcase == name.downcase
124
+ result = get_organization(organization[1])
125
+ end
126
+ end
127
+ result
128
+ end
129
+
130
+ ##
131
+ # Fetch details about an organization:
132
+ # - catalogs
133
+ # - vdcs
134
+ # - networks
135
+ def get_organization(orgId)
136
+ params = {
137
+ 'method' => :get,
138
+ 'command' => "/org/#{orgId}"
139
+ }
140
+
141
+ response, headers = send_request(params)
142
+ catalogs = {}
143
+ response.css("Link[type='application/vnd.vmware.vcloud.catalog+xml']").each do |item|
144
+ catalogs[item['name']] = item['href'].gsub("#{@api_url}/catalog/", "")
145
+ end
146
+
147
+ vdcs = {}
148
+ response.css("Link[type='application/vnd.vmware.vcloud.vdc+xml']").each do |item|
149
+ vdcs[item['name']] = item['href'].gsub("#{@api_url}/vdc/", "")
150
+ end
151
+
152
+ networks = {}
153
+ response.css("Link[type='application/vnd.vmware.vcloud.orgNetwork+xml']").each do |item|
154
+ networks[item['name']] = item['href'].gsub("#{@api_url}/network/", "")
155
+ end
156
+
157
+ tasklists = {}
158
+ response.css("Link[type='application/vnd.vmware.vcloud.tasksList+xml']").each do |item|
159
+ tasklists[item['name']] = item['href'].gsub("#{@api_url}/tasksList/", "")
160
+ end
161
+
162
+ { :catalogs => catalogs, :vdcs => vdcs, :networks => networks, :tasklists => tasklists }
163
+ end
164
+
165
+ ##
166
+ # Fetch details about a given catalog
167
+ def get_catalog(catalogId)
168
+ params = {
169
+ 'method' => :get,
170
+ 'command' => "/catalog/#{catalogId}"
171
+ }
172
+
173
+ response, headers = send_request(params)
174
+ description = response.css("Description").first
175
+ description = description.text unless description.nil?
176
+
177
+ items = {}
178
+ response.css("CatalogItem[type='application/vnd.vmware.vcloud.catalogItem+xml']").each do |item|
179
+ items[item['name']] = item['href'].gsub("#{@api_url}/catalogItem/", "")
180
+ end
181
+ { :description => description, :items => items }
182
+ end
183
+
184
+ ##
185
+ # Friendly helper method to fetch an catalog id by name
186
+ # - organization hash (from get_organization/get_organization_by_name)
187
+ # - catalog name
188
+ def get_catalog_id_by_name(organization, catalogName)
189
+ result = nil
190
+
191
+ organization[:catalogs].each do |catalog|
192
+ if catalog[0].downcase == catalogName.downcase
193
+ result = catalog[1]
194
+ end
195
+ end
196
+
197
+ result
198
+ end
199
+
200
+ ##
201
+ # Friendly helper method to fetch an catalog by name
202
+ # - organization hash (from get_organization/get_organization_by_name)
203
+ # - catalog name
204
+ def get_catalog_by_name(organization, catalogName)
205
+ result = nil
206
+
207
+ organization[:catalogs].each do |catalog|
208
+ if catalog[0].downcase == catalogName.downcase
209
+ result = get_catalog(catalog[1])
210
+ end
211
+ end
212
+
213
+ result
214
+ end
215
+
216
+ ##
217
+ # Fetch details about a given vdc:
218
+ # - description
219
+ # - vapps
220
+ # - networks
221
+ def get_vdc(vdcId)
222
+ params = {
223
+ 'method' => :get,
224
+ 'command' => "/vdc/#{vdcId}"
225
+ }
226
+
227
+ response, headers = send_request(params)
228
+ description = response.css("Description").first
229
+ description = description.text unless description.nil?
230
+
231
+ vapps = {}
232
+ response.css("ResourceEntity[type='application/vnd.vmware.vcloud.vApp+xml']").each do |item|
233
+ vapps[item['name']] = item['href'].gsub("#{@api_url}/vApp/vapp-", "")
234
+ end
235
+
236
+ networks = {}
237
+ response.css("Network[type='application/vnd.vmware.vcloud.network+xml']").each do |item|
238
+ networks[item['name']] = item['href'].gsub("#{@api_url}/network/", "")
239
+ end
240
+ { :description => description, :vapps => vapps, :networks => networks }
241
+ end
242
+
243
+ ##
244
+ # Friendly helper method to fetch a Organization VDC Id by name
245
+ # - Organization object
246
+ # - Organization VDC Name
247
+ def get_vdc_id_by_name(organization, vdcName)
248
+ result = nil
249
+
250
+ organization[:vdcs].each do |vdc|
251
+ if vdc[0].downcase == vdcName.downcase
252
+ result = vdc[1]
253
+ end
254
+ end
255
+
256
+ result
257
+ end
258
+
259
+ ##
260
+ # Friendly helper method to fetch a Organization VDC by name
261
+ # - Organization object
262
+ # - Organization VDC Name
263
+ def get_vdc_by_name(organization, vdcName)
264
+ result = nil
265
+
266
+ organization[:vdcs].each do |vdc|
267
+ if vdc[0].downcase == vdcName.downcase
268
+ result = get_vdc(vdc[1])
269
+ end
270
+ end
271
+
272
+ result
273
+ end
274
+
275
+ ##
276
+ # Fetch details about a given catalog item:
277
+ # - description
278
+ # - vApp templates
279
+ def get_catalog_item(catalogItemId)
280
+ params = {
281
+ 'method' => :get,
282
+ 'command' => "/catalogItem/#{catalogItemId}"
283
+ }
284
+
285
+ response, headers = send_request(params)
286
+ description = response.css("Description").first
287
+ description = description.text unless description.nil?
288
+
289
+ items = {}
290
+ response.css("Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']").each do |item|
291
+ items[item['name']] = item['href'].gsub("#{@api_url}/vAppTemplate/vappTemplate-", "")
292
+ end
293
+ { :description => description, :items => items }
294
+ end
295
+
296
+ ##
297
+ # friendly helper method to fetch an catalogItem by name
298
+ # - catalogId (use get_catalog_name(org, name))
299
+ # - catalagItemName
300
+ def get_catalog_item_by_name(catalogId, catalogItemName)
301
+ result = nil
302
+ catalogElems = get_catalog(catalogId)
303
+
304
+ catalogElems[:items].each do |catalogElem|
305
+
306
+ catalogItem = get_catalog_item(catalogElem[1])
307
+ if catalogItem[:items][catalogItemName]
308
+ # This is a vApp Catalog Item
309
+
310
+ # fetch CatalogItemId
311
+ catalogItemId = catalogItem[:items][catalogItemName]
312
+
313
+ # Fetch the catalogItemId information
314
+ params = {
315
+ 'method' => :get,
316
+ 'command' => "/vAppTemplate/vappTemplate-#{catalogItemId}"
317
+ }
318
+ response, headers = send_request(params)
319
+
320
+ # VMs Hash for all the vApp VM entities
321
+ vms_hash = {}
322
+ response.css("/VAppTemplate/Children/Vm").each do |vmElem|
323
+ vmName = vmElem["name"]
324
+ vmId = vmElem["href"].gsub("#{@api_url}/vAppTemplate/vm-", "")
325
+
326
+ # Add the VM name/id to the VMs Hash
327
+ vms_hash[vmName] = { :id => vmId }
328
+ end
329
+ result = { catalogItemName => catalogItemId, :vms_hash => vms_hash }
330
+ end
331
+ end
332
+ result
333
+ end
334
+
335
+ ##
336
+ # Fetch details about a given vapp:
337
+ # - name
338
+ # - description
339
+ # - status
340
+ # - IP
341
+ # - Children VMs:
342
+ # -- IP addresses
343
+ # -- status
344
+ # -- ID
345
+ def get_vapp(vAppId)
346
+ params = {
347
+ 'method' => :get,
348
+ 'command' => "/vApp/vapp-#{vAppId}"
349
+ }
350
+
351
+ response, headers = send_request(params)
352
+
353
+ vapp_node = response.css('VApp').first
354
+ if vapp_node
355
+ name = vapp_node['name']
356
+ status = convert_vapp_status(vapp_node['status'])
357
+ end
358
+
359
+ description = response.css("Description").first
360
+ description = description.text unless description.nil?
361
+
362
+ ip = response.css('IpAddress').first
363
+ ip = ip.text unless ip.nil?
364
+
365
+ vms = response.css('Children Vm')
366
+ vms_hash = {}
367
+
368
+ # ipAddress could be namespaced or not: see https://github.com/astratto/vcloud-rest/issues/3
369
+ vms.each do |vm|
370
+ vapp_local_id = vm.css('VAppScopedLocalId')
371
+ addresses = vm.css('rasd|Connection').collect{|n| n['vcloud:ipAddress'] || n['ipAddress'] }
372
+ vms_hash[vm['name'].to_sym] = {
373
+ :addresses => addresses,
374
+ :status => convert_vapp_status(vm['status']),
375
+ :id => vm['href'].gsub("#{@api_url}/vApp/vm-", ''),
376
+ :vapp_scoped_local_id => vapp_local_id.text
377
+ }
378
+ end
379
+
380
+ # TODO: EXPAND INFO FROM RESPONSE
381
+ { :name => name, :description => description, :status => status, :ip => ip, :vms_hash => vms_hash }
382
+ end
383
+
384
+ ##
385
+ # Delete a given vapp
386
+ # NOTE: It doesn't verify that the vapp is shutdown
387
+ def delete_vapp(vAppId)
388
+ params = {
389
+ 'method' => :delete,
390
+ 'command' => "/vApp/vapp-#{vAppId}"
391
+ }
392
+
393
+ response, headers = send_request(params)
394
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
395
+ task_id
396
+ end
397
+
398
+ ##
399
+ # Shutdown a given vapp
400
+ def poweroff_vapp(vAppId)
401
+ builder = Nokogiri::XML::Builder.new do |xml|
402
+ xml.UndeployVAppParams(
403
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5") {
404
+ xml.UndeployPowerAction 'powerOff'
405
+ }
406
+ end
407
+
408
+ params = {
409
+ 'method' => :post,
410
+ 'command' => "/vApp/vapp-#{vAppId}/action/undeploy"
411
+ }
412
+
413
+ response, headers = send_request(params, builder.to_xml,
414
+ "application/vnd.vmware.vcloud.undeployVAppParams+xml")
415
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
416
+ task_id
417
+ end
418
+
419
+ ##
420
+ # Suspend a given vapp
421
+ def suspend_vapp(vAppId)
422
+ params = {
423
+ 'method' => :post,
424
+ 'command' => "/vApp/vapp-#{vAppId}/power/action/suspend"
425
+ }
426
+
427
+ response, headers = send_request(params)
428
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
429
+ task_id
430
+ end
431
+
432
+ ##
433
+ # reboot a given vapp
434
+ # This will basically initial a guest OS reboot, and will only work if
435
+ # VMware-tools are installed on the underlying VMs.
436
+ # vShield Edge devices are not affected
437
+ def reboot_vapp(vAppId)
438
+ params = {
439
+ 'method' => :post,
440
+ 'command' => "/vApp/vapp-#{vAppId}/power/action/reboot"
441
+ }
442
+
443
+ response, headers = send_request(params)
444
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
445
+ task_id
446
+ end
447
+
448
+ ##
449
+ # reset a given vapp
450
+ # This will basically reset the VMs within the vApp
451
+ # vShield Edge devices are not affected.
452
+ def reset_vapp(vAppId)
453
+ params = {
454
+ 'method' => :post,
455
+ 'command' => "/vApp/vapp-#{vAppId}/power/action/reset"
456
+ }
457
+
458
+ response, headers = send_request(params)
459
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
460
+ task_id
461
+ end
462
+
463
+ ##
464
+ # Boot a given vapp
465
+ def poweron_vapp(vAppId)
466
+ params = {
467
+ 'method' => :post,
468
+ 'command' => "/vApp/vapp-#{vAppId}/power/action/powerOn"
469
+ }
470
+
471
+ response, headers = send_request(params)
472
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
473
+ task_id
474
+ end
475
+
476
+ #### VM operations ####
477
+ ##
478
+ # Delete a given vm
479
+ # NOTE: It doesn't verify that the vm is shutdown
480
+ def delete_vm(vmId)
481
+ params = {
482
+ 'method' => :delete,
483
+ 'command' => "/vApp/vm-#{vmId}"
484
+ }
485
+
486
+ response, headers = send_request(params)
487
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
488
+ task_id
489
+ end
490
+
491
+ ##
492
+ # Shutdown a given VM
493
+ # Using undeploy as a REAL powerOff
494
+ # Only poweroff will put the VM into a partially powered off state.
495
+ def poweroff_vm(vmId)
496
+ builder = Nokogiri::XML::Builder.new do |xml|
497
+ xml.UndeployVAppParams(
498
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5") {
499
+ xml.UndeployPowerAction 'powerOff'
500
+ }
501
+ end
502
+
503
+ params = {
504
+ 'method' => :post,
505
+ 'command' => "/vApp/vm-#{vmId}/action/undeploy"
506
+ }
507
+
508
+ response, headers = send_request(params, builder.to_xml,
509
+ "application/vnd.vmware.vcloud.undeployVAppParams+xml")
510
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
511
+ task_id
512
+ end
513
+
514
+ ##
515
+ # Suspend a given VM
516
+ def suspend_vm(vmId)
517
+ builder = Nokogiri::XML::Builder.new do |xml|
518
+ xml.UndeployVAppParams(
519
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5") {
520
+ xml.UndeployPowerAction 'suspend'
521
+ }
522
+ end
523
+
524
+ params = {
525
+ 'method' => :post,
526
+ 'command' => "/vApp/vm-#{vmId}/action/undeploy"
527
+ }
528
+
529
+ response, headers = send_request(params, builder.to_xml,
530
+ "application/vnd.vmware.vcloud.undeployVAppParams+xml")
531
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
532
+ task_id
533
+ end
534
+
535
+ ##
536
+ # reboot a given VM
537
+ # This will basically initial a guest OS reboot, and will only work if
538
+ # VMware-tools are installed on the underlying VMs.
539
+ # vShield Edge devices are not affected
540
+ def reboot_vm(vmId)
541
+ params = {
542
+ 'method' => :post,
543
+ 'command' => "/vApp/vm-#{vmId}/power/action/reboot"
544
+ }
545
+
546
+ response, headers = send_request(params)
547
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
548
+ task_id
549
+ end
550
+
551
+ ##
552
+ # reset a given VM
553
+ # This will basically reset the VMs within the vApp
554
+ # vShield Edge devices are not affected.
555
+ def reset_vm(vmId)
556
+ params = {
557
+ 'method' => :post,
558
+ 'command' => "/vApp/vm-#{vmId}/power/action/reset"
559
+ }
560
+
561
+ response, headers = send_request(params)
562
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
563
+ task_id
564
+ end
565
+
566
+ ##
567
+ # Boot a given VM
568
+ def poweron_vm(vmId)
569
+ params = {
570
+ 'method' => :post,
571
+ 'command' => "/vApp/vm-#{vmId}/power/action/powerOn"
572
+ }
573
+
574
+ response, headers = send_request(params)
575
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
576
+ task_id
577
+ end
578
+
579
+ ### End Of VM operations ###
580
+
581
+
582
+
583
+ ##
584
+ # Boot a given vm
585
+ def poweron_vm(vmId)
586
+ params = {
587
+ 'method' => :post,
588
+ 'command' => "/vApp/vm-#{vmId}/power/action/powerOn"
589
+ }
590
+
591
+ response, headers = send_request(params)
592
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
593
+ task_id
594
+ end
595
+
596
+
597
+ ##
598
+ # Create a catalog in an organization
599
+ def create_catalog(orgId, catalogName, catalogDescription)
600
+ builder = Nokogiri::XML::Builder.new do |xml|
601
+
602
+ xml.AdminCatalog(
603
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
604
+ "name" => catalogName
605
+ ) {
606
+ xml.Description catalogDescription
607
+ }
608
+
609
+ end
610
+
611
+ params = {
612
+ 'method' => :post,
613
+ 'command' => "/admin/org/#{orgId}/catalogs"
614
+
615
+ }
616
+
617
+ response, headers = send_request(params, builder.to_xml,
618
+ "application/vnd.vmware.admin.catalog+xml")
619
+ task_id = response.css("AdminCatalog Tasks Task[operationName='catalogCreateCatalog']").first[:href].gsub("#{@api_url}/task/","")
620
+ catalog_id = response.css("AdminCatalog Link [type='application/vnd.vmware.vcloud.catalog+xml']").first[:href].gsub("#{@api_url}/catalog/","")
621
+ { :task_id => task_id, :catalog_id => catalog_id }
622
+ end
623
+
624
+
625
+
626
+
627
+ ##
628
+ # Create a vapp starting from a template
629
+ #
630
+ # Params:
631
+ # - vdc: the associated VDC
632
+ # - vapp_name: name of the target vapp
633
+ # - vapp_description: description of the target vapp
634
+ # - vapp_templateid: ID of the vapp template
635
+ def create_vapp_from_template(vdc, vapp_name, vapp_description, vapp_templateid, poweron=false)
636
+ builder = Nokogiri::XML::Builder.new do |xml|
637
+ xml.InstantiateVAppTemplateParams(
638
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
639
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
640
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
641
+ "name" => vapp_name,
642
+ "deploy" => "true",
643
+ "powerOn" => poweron) {
644
+ xml.Description vapp_description
645
+ xml.Source("href" => "#{@api_url}/vAppTemplate/#{vapp_templateid}")
646
+ }
647
+ end
648
+
649
+ params = {
650
+ "method" => :post,
651
+ "command" => "/vdc/#{vdc}/action/instantiateVAppTemplate"
652
+ }
653
+
654
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml")
655
+
656
+ vapp_id = headers[:location].gsub("#{@api_url}/vApp/vapp-", "")
657
+
658
+ task = response.css("VApp Task[operationName='vdcInstantiateVapp']").first
659
+ task_id = task["href"].gsub("#{@api_url}/task/", "")
660
+
661
+ { :vapp_id => vapp_id, :task_id => task_id }
662
+ end
663
+
664
+ ##
665
+ # Compose a vapp using existing virtual machines
666
+ #
667
+ # Params:
668
+ # - vdc: the associated VDC
669
+ # - vapp_name: name of the target vapp
670
+ # - vapp_description: description of the target vapp
671
+ # - vm_list: hash with IDs of the VMs to be used in the composing process
672
+ # - network_config: hash of the network configuration for the vapp
673
+ def compose_vapp_from_vm(vdc, vapp_name, vapp_description, vm_list={}, network_config={})
674
+ builder = Nokogiri::XML::Builder.new do |xml|
675
+ xml.ComposeVAppParams(
676
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
677
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
678
+ "name" => vapp_name,
679
+ "deploy" => "false",
680
+ "powerOn" => "false") {
681
+ xml.Description vapp_description
682
+ xml.InstantiationParams {
683
+ xml.NetworkConfigSection {
684
+ xml['ovf'].Info "Configuration parameters for logical networks"
685
+ xml.NetworkConfig("networkName" => network_config[:name]) {
686
+ xml.Configuration {
687
+ xml.IpScopes {
688
+ xml.IpScope {
689
+ xml.IsInherited(network_config[:is_inherited] || "false")
690
+ xml.Gateway network_config[:gateway]
691
+ xml.Netmask network_config[:netmask]
692
+ xml.Dns1 network_config[:dns1] if network_config[:dns1]
693
+ xml.Dns2 network_config[:dns2] if network_config[:dns2]
694
+ xml.DnsSuffix network_config[:dns_suffix] if network_config[:dns_suffix]
695
+ xml.IpRanges {
696
+ xml.IpRange {
697
+ xml.StartAddress network_config[:start_address]
698
+ xml.EndAddress network_config[:end_address]
699
+ }
700
+ }
701
+ }
702
+ }
703
+ xml.ParentNetwork("href" => "#{@api_url}/network/#{network_config[:parent_network]}")
704
+ xml.FenceMode network_config[:fence_mode]
705
+
706
+ xml.Features {
707
+ xml.FirewallService {
708
+ xml.IsEnabled(network_config[:enable_firewall] || "false")
709
+ }
710
+ xml.NatService {
711
+ xml.IsEnabled "true"
712
+ xml.NatType "portForwarding"
713
+ xml.Policy(network_config[:nat_policy_type] || "allowTraffic")
714
+ }
715
+ }
716
+ }
717
+ }
718
+ }
719
+ }
720
+ vm_list.each do |vm_name, vm_id|
721
+ xml.SourcedItem {
722
+ xml.Source("href" => "#{@api_url}/vAppTemplate/vm-#{vm_id}", "name" => vm_name)
723
+ xml.InstantiationParams {
724
+ xml.NetworkConnectionSection(
725
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
726
+ "type" => "application/vnd.vmware.vcloud.networkConnectionSection+xml",
727
+ "href" => "#{@api_url}/vAppTemplate/vm-#{vm_id}/networkConnectionSection/") {
728
+ xml['ovf'].Info "Network config for sourced item"
729
+ xml.PrimaryNetworkConnectionIndex "0"
730
+ xml.NetworkConnection("network" => network_config[:name]) {
731
+ xml.NetworkConnectionIndex "0"
732
+ xml.IsConnected "true"
733
+ xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || "POOL")
734
+ }
735
+ }
736
+ }
737
+ xml.NetworkAssignment("containerNetwork" => network_config[:name], "innerNetwork" => network_config[:name])
738
+ }
739
+ end
740
+ xml.AllEULAsAccepted "true"
741
+ }
742
+ end
743
+
744
+ params = {
745
+ "method" => :post,
746
+ "command" => "/vdc/#{vdc}/action/composeVApp"
747
+ }
748
+
749
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.composeVAppParams+xml")
750
+
751
+ vapp_id = headers[:location].gsub("#{@api_url}/vApp/vapp-", "")
752
+
753
+ task = response.css("VApp Task[operationName='vdcComposeVapp']").first
754
+ task_id = task["href"].gsub("#{@api_url}/task/", "")
755
+
756
+ { :vapp_id => vapp_id, :task_id => task_id }
757
+ end
758
+
759
+
760
+ ##
761
+ # Recompose an existing vapp using existing virtual machines
762
+ #
763
+ # Params:
764
+ # - vdc: the associated VDC
765
+ # - vapp_name: name of the target vapp
766
+ # - vapp_description: description of the target vapp
767
+ # - vm_list: hash with IDs of the VMs to be used in the composing process
768
+ # - network_config: hash of the network configuration for the vapp
769
+
770
+ def recompose_vapp_from_vm(vAppId, vm_list={}, network_config={})
771
+ originalVApp = get_vapp(vAppId)
772
+
773
+ builder = Nokogiri::XML::Builder.new do |xml|
774
+ xml.RecomposeVAppParams(
775
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
776
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
777
+ "name" => originalVApp[:name]) {
778
+ xml.Description originalVApp[:description]
779
+ xml.InstantiationParams {}
780
+ vm_list.each do |vm_name, vm_id|
781
+ xml.SourcedItem {
782
+ xml.Source("href" => "#{@api_url}/vAppTemplate/vm-#{vm_id}", "name" => vm_name)
783
+ xml.InstantiationParams {
784
+ xml.NetworkConnectionSection(
785
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
786
+ "type" => "application/vnd.vmware.vcloud.networkConnectionSection+xml",
787
+ "href" => "#{@api_url}/vAppTemplate/vm-#{vm_id}/networkConnectionSection/") {
788
+ xml['ovf'].Info "Network config for sourced item"
789
+ xml.PrimaryNetworkConnectionIndex "0"
790
+ xml.NetworkConnection("network" => network_config[:name]) {
791
+ xml.NetworkConnectionIndex "0"
792
+ xml.IsConnected "true"
793
+ xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || "POOL")
794
+ }
795
+ }
796
+ }
797
+ xml.NetworkAssignment("containerNetwork" => network_config[:name], "innerNetwork" => network_config[:name])
798
+ }
799
+ end
800
+ xml.AllEULAsAccepted "true"
801
+ }
802
+ end
803
+
804
+ params = {
805
+ "method" => :post,
806
+ "command" => "/vApp/vapp-#{vAppId}/action/recomposeVApp"
807
+ }
808
+
809
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.recomposeVAppParams+xml")
810
+
811
+ vapp_id = headers[:location].gsub("#{@api_url}/vApp/vapp-", "")
812
+
813
+ task = response.css("Task [operationName='vdcRecomposeVapp']").first
814
+ task_id = task["href"].gsub("#{@api_url}/task/", "")
815
+
816
+ { :vapp_id => vapp_id, :task_id => task_id }
817
+ end
818
+
819
+
820
+
821
+
822
+ # Fetch details about a given vapp template:
823
+ # - name
824
+ # - description
825
+ # - Children VMs:
826
+ # -- ID
827
+ def get_vapp_template(vAppId)
828
+ params = {
829
+ 'method' => :get,
830
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppId}"
831
+ }
832
+
833
+ response, headers = send_request(params)
834
+
835
+ vapp_node = response.css('VAppTemplate').first
836
+ if vapp_node
837
+ name = vapp_node['name']
838
+ status = convert_vapp_status(vapp_node['status'])
839
+ end
840
+
841
+ description = response.css("Description").first
842
+ description = description.text unless description.nil?
843
+
844
+ ip = response.css('IpAddress').first
845
+ ip = ip.text unless ip.nil?
846
+
847
+ vms = response.css('Children Vm')
848
+ vms_hash = {}
849
+
850
+ vms.each do |vm|
851
+ vms_hash[vm['name']] = {
852
+ :id => vm['href'].gsub("#{@api_url}/vAppTemplate/vm-", '')
853
+ }
854
+ end
855
+
856
+ # TODO: EXPAND INFO FROM RESPONSE
857
+ { :name => name, :description => description, :vms_hash => vms_hash }
858
+ end
859
+
860
+ ##
861
+ # Set vApp port forwarding rules
862
+ #
863
+ # - vappid: id of the vapp to be modified
864
+ # - network_name: name of the vapp network to be modified
865
+ # - config: hash with network configuration specifications, must contain an array inside :nat_rules with the nat rules to be applied.
866
+ def set_vapp_port_forwarding_rules(vappid, network_name, config={})
867
+ builder = Nokogiri::XML::Builder.new do |xml|
868
+ xml.NetworkConfigSection(
869
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
870
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
871
+ xml['ovf'].Info "Network configuration"
872
+ xml.NetworkConfig("networkName" => network_name) {
873
+ xml.Configuration {
874
+ xml.ParentNetwork("href" => "#{@api_url}/network/#{config[:parent_network]}")
875
+ xml.FenceMode(config[:fence_mode] || 'isolated')
876
+ xml.Features {
877
+ xml.NatService {
878
+ xml.IsEnabled "true"
879
+ xml.NatType "portForwarding"
880
+ xml.Policy(config[:nat_policy_type] || "allowTraffic")
881
+ config[:nat_rules].each do |nat_rule|
882
+ xml.NatRule {
883
+ xml.VmRule {
884
+ xml.ExternalPort nat_rule[:nat_external_port]
885
+ xml.VAppScopedVmId nat_rule[:vapp_scoped_local_id]
886
+ xml.VmNicId(nat_rule[:nat_vmnic_id] || "0")
887
+ xml.InternalPort nat_rule[:nat_internal_port]
888
+ xml.Protocol(nat_rule[:nat_protocol] || "TCP")
889
+ }
890
+ }
891
+ end
892
+ }
893
+ }
894
+ }
895
+ }
896
+ }
897
+ end
898
+
899
+ params = {
900
+ 'method' => :put,
901
+ 'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
902
+ }
903
+
904
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConfigSection+xml")
905
+
906
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
907
+ task_id
908
+ end
909
+
910
+ ##
911
+ # Add vApp port forwarding rules
912
+ #
913
+ # - vappid: id of the vapp to be modified
914
+ # - network_name: name of the vapp network to be modified
915
+ # - config: hash with network configuration specifications, must contain an array inside :nat_rules with the nat rules to be added.
916
+
917
+ # nat_rules << { :nat_external_port => j.to_s, :nat_internal_port => "22", :nat_protocol => "TCP", :vm_scoped_local_id => value[:vapp_scoped_local_id]}
918
+
919
+ def add_vapp_port_forwarding_rules(vappid, network_name, config={})
920
+ builder = Nokogiri::XML::Builder.new do |xml|
921
+ xml.NetworkConfigSection(
922
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
923
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
924
+ xml['ovf'].Info "Network configuration"
925
+ xml.NetworkConfig("networkName" => network_name) {
926
+ xml.Configuration {
927
+ xml.ParentNetwork("href" => "#{@api_url}/network/#{config[:parent_network]}")
928
+ xml.FenceMode(config[:fence_mode] || 'isolated')
929
+ xml.Features {
930
+ xml.NatService {
931
+ xml.IsEnabled "true"
932
+ xml.NatType "portForwarding"
933
+ xml.Policy(config[:nat_policy_type] || "allowTraffic")
934
+
935
+ preExisting = get_vapp_port_forwarding_rules(vappid)
936
+ @logger.debug("This is the PREEXISTING RULE BLOCK: #{preExisting.inspect}")
937
+
938
+ config[:nat_rules].concat(preExisting)
939
+
940
+ config[:nat_rules].each do |nat_rule|
941
+ xml.NatRule {
942
+ xml.VmRule {
943
+ xml.ExternalPort nat_rule[:nat_external_port]
944
+ xml.VAppScopedVmId nat_rule[:vapp_scoped_local_id]
945
+ xml.VmNicId(nat_rule[:nat_vmnic_id] || "0")
946
+ xml.InternalPort nat_rule[:nat_internal_port]
947
+ xml.Protocol(nat_rule[:nat_protocol] || "TCP")
948
+ }
949
+ }
950
+ end
951
+ }
952
+ }
953
+ }
954
+ }
955
+ }
956
+ end
957
+
958
+ params = {
959
+ 'method' => :put,
960
+ 'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
961
+ }
962
+
963
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConfigSection+xml")
964
+
965
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
966
+ task_id
967
+ end
968
+
969
+
970
+
971
+ ##
972
+ # Get vApp port forwarding rules
973
+ #
974
+ # - vappid: id of the vApp
975
+
976
+ # nat_rules << { :nat_external_port => j.to_s, :nat_internal_port => "22", :nat_protocol => "TCP", :vm_scoped_local_id => value[:vapp_scoped_local_id]}
977
+
978
+ def get_vapp_port_forwarding_rules(vAppId)
979
+ params = {
980
+ 'method' => :get,
981
+ 'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
982
+ }
983
+
984
+ response, headers = send_request(params)
985
+
986
+ # FIXME: this will return nil if the vApp uses multiple vApp Networks
987
+ # with Edge devices in natRouted/portForwarding mode.
988
+ config = response.css('NetworkConfigSection/NetworkConfig/Configuration')
989
+ fenceMode = config.css('/FenceMode').text
990
+ natType = config.css('/Features/NatService/NatType').text
991
+
992
+ raise InvalidStateError, "Invalid request because FenceMode must be set to natRouted." unless fenceMode == "natRouted"
993
+ raise InvalidStateError, "Invalid request because NatType must be set to portForwarding." unless natType == "portForwarding"
994
+
995
+ nat_rules = []
996
+ config.css('/Features/NatService/NatRule').each do |rule|
997
+ # portforwarding rules information
998
+ ruleId = rule.css('Id').text
999
+ vmRule = rule.css('VmRule')
1000
+
1001
+ nat_rules << {
1002
+ :nat_external_ip => vmRule.css('ExternalIpAddress').text,
1003
+ :nat_external_port => vmRule.css('ExternalPort').text,
1004
+ :vapp_scoped_local_id => vmRule.css('VAppScopedVmId').text,
1005
+ :vm_nic_id => vmRule.css('VmNicId').text,
1006
+ :nat_internal_port => vmRule.css('InternalPort').text,
1007
+ :nat_protocol => vmRule.css('Protocol').text
1008
+ }
1009
+ end
1010
+ nat_rules
1011
+ end
1012
+
1013
+ ##
1014
+ # Get vApp port forwarding rules external ports used and returns a set instead
1015
+ # of an HASH.
1016
+ #
1017
+ # - vappid: id of the vApp
1018
+ def get_vapp_port_forwarding_external_ports(vAppId)
1019
+ params = {
1020
+ 'method' => :get,
1021
+ 'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
1022
+ }
1023
+
1024
+ @logger.debug("these are the params: #{params.inspect}")
1025
+
1026
+ response, headers = send_request(params)
1027
+
1028
+ # FIXME: this will return nil if the vApp uses multiple vApp Networks
1029
+ # with Edge devices in natRouted/portForwarding mode.
1030
+ config = response.css('NetworkConfigSection/NetworkConfig/Configuration')
1031
+ fenceMode = config.css('/FenceMode').text
1032
+ natType = config.css('/Features/NatService/NatType').text
1033
+
1034
+ raise InvalidStateError, "Invalid request because FenceMode must be set to natRouted." unless fenceMode == "natRouted"
1035
+ raise InvalidStateError, "Invalid request because NatType must be set to portForwarding." unless natType == "portForwarding"
1036
+
1037
+ nat_rules = Set.new
1038
+ config.css('/Features/NatService/NatRule').each do |rule|
1039
+ # portforwarding rules information
1040
+ vmRule = rule.css('VmRule')
1041
+ nat_rules.add(vmRule.css('ExternalPort').text.to_i)
1042
+ end
1043
+ nat_rules
1044
+ end
1045
+
1046
+
1047
+ def find_edge_gateway_id(edge_gateway_name, vdc_id)
1048
+ params = {
1049
+ 'method' => :get,
1050
+ 'command' => "/query?type=edgeGateway&format=records&filter=vdc==#{@api_url}/vdc/#{vdc_id}&filter=name==#{edge_gateway_name}"
1051
+ }
1052
+
1053
+ response, headers = send_request(params)
1054
+
1055
+ edgeGateway = response.css('EdgeGatewayRecord').first
1056
+
1057
+ if edgeGateway
1058
+ return edgeGateway['href'].gsub("#{@api_url}/admin/edgeGateway/", "")
1059
+ else
1060
+ return nil
1061
+ end
1062
+ end
1063
+
1064
+ def find_edge_gateway_network(edge_gateway_name, vdc_id, edge_gateway_ip)
1065
+
1066
+ params = {
1067
+ 'method' => :get,
1068
+ 'command' => "/query?type=edgeGateway&format=records&filter=vdc==#{@api_url}/vdc/#{vdc_id}&filter=name==#{edge_gateway_name}"
1069
+ }
1070
+
1071
+ response, headers = send_request(params)
1072
+
1073
+ edgeGateway = response.css('EdgeGatewayRecord').first
1074
+
1075
+ if edgeGateway
1076
+ edgeGatewayId = edgeGateway['href'].gsub("#{@api_url}/admin/edgeGateway/", "")
1077
+ end
1078
+
1079
+ params = {
1080
+ 'method' => :get,
1081
+ 'command' => "/admin/edgeGateway/#{edgeGatewayId}"
1082
+ }
1083
+
1084
+ response, headers = send_request(params)
1085
+
1086
+ response.css("EdgeGateway Configuration GatewayInterfaces GatewayInterface").each do |gw|
1087
+
1088
+ if gw.css("InterfaceType").text == "internal"
1089
+ next
1090
+ end
1091
+
1092
+ lowip = gw.css("SubnetParticipation IpRanges IpRange StartAddress").first.text
1093
+ highip = gw.css("SubnetParticipation IpRanges IpRange EndAddress").first.text
1094
+
1095
+ rangeIpLow = NetAddr.ip_to_i(lowip)
1096
+ rangeIpHigh = NetAddr.ip_to_i(highip)
1097
+ testIp = NetAddr.ip_to_i(edge_gateway_ip)
1098
+
1099
+ if (rangeIpLow..rangeIpHigh) === testIp
1100
+ return gw.css("Network").first[:href]
1101
+ end
1102
+ end
1103
+
1104
+ end
1105
+
1106
+
1107
+ ##
1108
+ # Set Org Edge port forwarding and firewall rules
1109
+ #
1110
+ # - vappid: id of the vapp to be modified
1111
+ # - network_name: name of the vapp network to be modified
1112
+ # - config: hash with network configuration specifications, must contain an array inside :nat_rules with the nat rules to be applied.
1113
+ def set_edge_gateway_rules(edge_gateway_name, vdc_id, edge_gateway_ip, vAppId)
1114
+
1115
+ edge_vapp_ip = get_vapp_edge_public_ip(vAppId)
1116
+ edge_network_id = find_edge_gateway_network(edge_gateway_name, vdc_id, edge_gateway_ip)
1117
+ edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id)
1118
+
1119
+ params = {
1120
+ 'method' => :get,
1121
+ 'command' => "/admin/edgeGateway/#{edge_gateway_id}"
1122
+ }
1123
+
1124
+ response, headers = send_request(params)
1125
+
1126
+ interesting = response.css("EdgeGateway Configuration EdgeGatewayServiceConfiguration")
1127
+
1128
+ natRule1 = Nokogiri::XML::Node.new 'NatRule', response
1129
+ ruleType = Nokogiri::XML::Node.new 'RuleType', response
1130
+ ruleType.content = "DNAT"
1131
+ natRule1.add_child ruleType
1132
+
1133
+ isEnabled = Nokogiri::XML::Node.new 'IsEnabled', response
1134
+ isEnabled.content = "true"
1135
+ natRule1.add_child isEnabled
1136
+
1137
+ gatewayNatRule = Nokogiri::XML::Node.new 'GatewayNatRule', response
1138
+ natRule1.add_child gatewayNatRule
1139
+
1140
+ interface = Nokogiri::XML::Node.new 'Interface', response
1141
+ interface["href"] = edge_network_id
1142
+ gatewayNatRule.add_child interface
1143
+
1144
+ originalIp = Nokogiri::XML::Node.new 'OriginalIp', response
1145
+ originalIp.content = edge_gateway_ip
1146
+ gatewayNatRule.add_child originalIp
1147
+
1148
+ originalPort = Nokogiri::XML::Node.new 'OriginalPort', response
1149
+ originalPort.content = "any"
1150
+ gatewayNatRule.add_child originalPort
1151
+
1152
+ translatedIp = Nokogiri::XML::Node.new 'TranslatedIp', response
1153
+ translatedIp.content = edge_vapp_ip
1154
+ gatewayNatRule.add_child translatedIp
1155
+
1156
+ translatedPort = Nokogiri::XML::Node.new 'TranslatedPort', response
1157
+ translatedPort.content = "any"
1158
+ gatewayNatRule.add_child translatedPort
1159
+
1160
+ protocol = Nokogiri::XML::Node.new 'Protocol', response
1161
+ protocol.content = "any"
1162
+ gatewayNatRule.add_child protocol
1163
+
1164
+ icmpSubType = Nokogiri::XML::Node.new 'IcmpSubType', response
1165
+ icmpSubType.content = "any"
1166
+ gatewayNatRule.add_child icmpSubType
1167
+
1168
+ natRule2 = Nokogiri::XML::Node.new 'NatRule', response
1169
+
1170
+ ruleType = Nokogiri::XML::Node.new 'RuleType', response
1171
+ ruleType.content = "SNAT"
1172
+ natRule2.add_child ruleType
1173
+
1174
+ isEnabled = Nokogiri::XML::Node.new 'IsEnabled', response
1175
+ isEnabled.content = "true"
1176
+ natRule2.add_child isEnabled
1177
+
1178
+ gatewayNatRule = Nokogiri::XML::Node.new 'GatewayNatRule', response
1179
+ natRule2.add_child gatewayNatRule
1180
+
1181
+ interface = Nokogiri::XML::Node.new 'Interface', response
1182
+ interface["href"] = edge_network_id
1183
+ gatewayNatRule.add_child interface
1184
+
1185
+ originalIp = Nokogiri::XML::Node.new 'OriginalIp', response
1186
+ originalIp.content = edge_vapp_ip
1187
+ gatewayNatRule.add_child originalIp
1188
+
1189
+ translatedIp = Nokogiri::XML::Node.new 'TranslatedIp', response
1190
+ translatedIp.content = edge_gateway_ip
1191
+ gatewayNatRule.add_child translatedIp
1192
+
1193
+ protocol = Nokogiri::XML::Node.new 'Protocol', response
1194
+ protocol.content = "any"
1195
+ gatewayNatRule.add_child protocol
1196
+
1197
+
1198
+ firewallRule1 = Nokogiri::XML::Node.new 'FirewallRule', response
1199
+
1200
+ isEnabled = Nokogiri::XML::Node.new 'IsEnabled', response
1201
+ isEnabled.content = "true"
1202
+ firewallRule1.add_child isEnabled
1203
+
1204
+ description = Nokogiri::XML::Node.new 'Description', response
1205
+ description.content = "Allow Vagrant Comms"
1206
+ firewallRule1.add_child description
1207
+
1208
+ policy = Nokogiri::XML::Node.new 'Policy', response
1209
+ policy.content = "allow"
1210
+ firewallRule1.add_child policy
1211
+
1212
+ protocols = Nokogiri::XML::Node.new 'Protocols', response
1213
+ firewallRule1.add_child protocols
1214
+
1215
+ any = Nokogiri::XML::Node.new 'Any', response
1216
+ any.content = "true"
1217
+ protocols.add_child any
1218
+
1219
+ destinationPortRange = Nokogiri::XML::Node.new 'DestinationPortRange', response
1220
+ destinationPortRange.content = "Any"
1221
+ firewallRule1.add_child destinationPortRange
1222
+
1223
+ destinationIp = Nokogiri::XML::Node.new 'DestinationIp', response
1224
+ destinationIp.content = edge_gateway_ip
1225
+ firewallRule1.add_child destinationIp
1226
+
1227
+ sourcePortRange = Nokogiri::XML::Node.new 'SourcePortRange', response
1228
+ sourcePortRange.content = "Any"
1229
+ firewallRule1.add_child sourcePortRange
1230
+
1231
+ sourceIp = Nokogiri::XML::Node.new 'SourceIp', response
1232
+ sourceIp.content = "Any"
1233
+ firewallRule1.add_child sourceIp
1234
+
1235
+ enableLogging = Nokogiri::XML::Node.new 'EnableLogging', response
1236
+ enableLogging.content = "false"
1237
+ firewallRule1.add_child enableLogging
1238
+
1239
+ builder = Nokogiri::XML::Builder.new
1240
+ builder << interesting
1241
+
1242
+ set_edge_rules = Nokogiri::XML(builder.to_xml) do |config|
1243
+ config.default_xml.noblanks
1244
+ end
1245
+
1246
+ nat_rules = set_edge_rules.at_css("NatService")
1247
+
1248
+ nat_rules << natRule1
1249
+ nat_rules << natRule2
1250
+
1251
+ fw_rules = set_edge_rules.at_css("FirewallService")
1252
+
1253
+ fw_rules << firewallRule1
1254
+
1255
+ xml1 = set_edge_rules.at_css "EdgeGatewayServiceConfiguration"
1256
+ xml1["xmlns"] = "http://www.vmware.com/vcloud/v1.5"
1257
+
1258
+
1259
+ params = {
1260
+ 'method' => :post,
1261
+ 'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/configureServices"
1262
+ }
1263
+
1264
+ @logger.debug("OUR XML: #{set_edge_rules.to_xml(:indent => 2)}")
1265
+
1266
+ response, headers = send_request(params, set_edge_rules.to_xml(:indent => 2), "application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml")
1267
+
1268
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
1269
+ task_id
1270
+
1271
+ end
1272
+
1273
+ def remove_edge_gateway_rules(edge_gateway_name, vdc_id, edge_gateway_ip, vAppId)
1274
+
1275
+ edge_vapp_ip = get_vapp_edge_public_ip(vAppId)
1276
+ edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id)
1277
+
1278
+ params = {
1279
+ 'method' => :get,
1280
+ 'command' => "/admin/edgeGateway/#{edge_gateway_id}"
1281
+ }
1282
+
1283
+ response, headers = send_request(params)
1284
+
1285
+ interesting = response.css("EdgeGateway Configuration EdgeGatewayServiceConfiguration")
1286
+ interesting.css("NatService NatRule").each do |node|
1287
+ if node.css("RuleType").text == "DNAT" && node.css("GatewayNatRule/OriginalIp").text == edge_gateway_ip && node.css("GatewayNatRule/TranslatedIp").text == edge_vapp_ip
1288
+ node.remove
1289
+ end
1290
+ if node.css("RuleType").text == "SNAT" && node.css("GatewayNatRule/OriginalIp").text == edge_vapp_ip && node.css("GatewayNatRule/TranslatedIp").text == edge_gateway_ip
1291
+ node.remove
1292
+ end
1293
+ end
1294
+
1295
+ interesting.css("FirewallService FirewallRule").each do |node|
1296
+ if node.css("Port").text == "-1" && node.css("DestinationIp").text == edge_gateway_ip && node.css("DestinationPortRange").text == "Any"
1297
+ node.remove
1298
+ end
1299
+ end
1300
+
1301
+ builder = Nokogiri::XML::Builder.new
1302
+ builder << interesting
1303
+
1304
+ remove_edge_rules = Nokogiri::XML(builder.to_xml)
1305
+
1306
+ xml1 = remove_edge_rules.at_css "EdgeGatewayServiceConfiguration"
1307
+ xml1["xmlns"] = "http://www.vmware.com/vcloud/v1.5"
1308
+
1309
+ params = {
1310
+ 'method' => :post,
1311
+ 'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/configureServices"
1312
+ }
1313
+
1314
+ @logger.debug("OUR XML: #{remove_edge_rules.to_xml}")
1315
+
1316
+ response, headers = send_request(params, remove_edge_rules.to_xml, "application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml")
1317
+
1318
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
1319
+ task_id
1320
+ end
1321
+
1322
+
1323
+
1324
+
1325
+ ##
1326
+ # get vApp edge public IP from the vApp ID
1327
+ # Only works when:
1328
+ # - vApp needs to be poweredOn
1329
+ # - FenceMode is set to "natRouted"
1330
+ # - NatType" is set to "portForwarding
1331
+ # This will be required to know how to connect to VMs behind the Edge device.
1332
+ def get_vapp_edge_public_ip(vAppId)
1333
+ # Check the network configuration section
1334
+ params = {
1335
+ 'method' => :get,
1336
+ 'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
1337
+ }
1338
+
1339
+ response, headers = send_request(params)
1340
+
1341
+ # FIXME: this will return nil if the vApp uses multiple vApp Networks
1342
+ # with Edge devices in natRouted/portForwarding mode.
1343
+ config = response.css('NetworkConfigSection/NetworkConfig/Configuration')
1344
+
1345
+ fenceMode = config.css('/FenceMode').text
1346
+ natType = config.css('/Features/NatService/NatType').text
1347
+
1348
+ raise InvalidStateError, "Invalid request because FenceMode must be set to natRouted." unless fenceMode == "natRouted"
1349
+ raise InvalidStateError, "Invalid request because NatType must be set to portForwarding." unless natType == "portForwarding"
1350
+
1351
+ # Check the routerInfo configuration where the global external IP is defined
1352
+ edgeIp = config.css('/RouterInfo/ExternalIp').text
1353
+ if edgeIp == ""
1354
+ return nil
1355
+ else
1356
+ return edgeIp
1357
+ end
1358
+ end
1359
+
1360
+ ##
1361
+ # Upload an OVF package
1362
+ # - vdcId
1363
+ # - vappName
1364
+ # - vappDescription
1365
+ # - ovfFile
1366
+ # - catalogId
1367
+ # - uploadOptions {}
1368
+ def upload_ovf(vdcId, vappName, vappDescription, ovfFile, catalogId, uploadOptions={})
1369
+
1370
+ # if send_manifest is not set, setting it true
1371
+ if uploadOptions[:send_manifest].nil? || uploadOptions[:send_manifest]
1372
+ uploadManifest = "true"
1373
+ else
1374
+ uploadManifest = "false"
1375
+ end
1376
+
1377
+ builder = Nokogiri::XML::Builder.new do |xml|
1378
+ xml.UploadVAppTemplateParams(
1379
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
1380
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
1381
+ "manifestRequired" => uploadManifest,
1382
+ "name" => vappName) {
1383
+ xml.Description vappDescription
1384
+ }
1385
+ end
1386
+
1387
+ params = {
1388
+ 'method' => :post,
1389
+ 'command' => "/vdc/#{vdcId}/action/uploadVAppTemplate"
1390
+ }
1391
+
1392
+ @logger.debug("Sending uploadVAppTemplate request...")
1393
+
1394
+ response, headers = send_request(
1395
+ params,
1396
+ builder.to_xml,
1397
+ "application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml"
1398
+ )
1399
+
1400
+ # Get vAppTemplate Link from location
1401
+ vAppTemplate = headers[:location].gsub("#{@api_url}/vAppTemplate/vappTemplate-", "")
1402
+ @logger.debug("Getting vAppTemplate ID: #{vAppTemplate}")
1403
+ descriptorUpload = response.css("Files Link [rel='upload:default']").first[:href].gsub("#{@host_url}/transfer/", "")
1404
+ transferGUID = descriptorUpload.gsub("/descriptor.ovf", "")
1405
+
1406
+ ovfFileBasename = File.basename(ovfFile, ".ovf")
1407
+ ovfDir = File.dirname(ovfFile)
1408
+
1409
+ # Send OVF Descriptor
1410
+ @logger.debug("Sending OVF Descriptor...")
1411
+ uploadURL = "/transfer/#{descriptorUpload}"
1412
+ uploadFile = "#{ovfDir}/#{ovfFileBasename}.ovf"
1413
+ upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
1414
+
1415
+ # Begin the catch for upload interruption
1416
+ begin
1417
+ params = {
1418
+ 'method' => :get,
1419
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
1420
+ }
1421
+
1422
+ response, headers = send_request(params)
1423
+
1424
+ task = response.css("VAppTemplate Task[operationName='vdcUploadOvfContents']").first
1425
+ task_id = task["href"].gsub("#{@api_url}/task/", "")
1426
+
1427
+ # Loop to wait for the upload links to show up in the vAppTemplate we just created
1428
+ @logger.debug("Waiting for the upload links to show up in the vAppTemplate we just created.")
1429
+ while true
1430
+ response, headers = send_request(params)
1431
+ @logger.debug("Request...")
1432
+ break unless response.css("Files Link [rel='upload:default']").count == 1
1433
+ sleep 1
1434
+ end
1435
+
1436
+ if uploadManifest == "true"
1437
+ uploadURL = "/transfer/#{transferGUID}/descriptor.mf"
1438
+ uploadFile = "#{ovfDir}/#{ovfFileBasename}.mf"
1439
+ upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
1440
+ end
1441
+
1442
+ # Start uploading OVF VMDK files
1443
+ params = {
1444
+ 'method' => :get,
1445
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
1446
+ }
1447
+ response, headers = send_request(params)
1448
+ response.css("Files File [bytesTransferred='0'] Link [rel='upload:default']").each do |file|
1449
+ fileName = file[:href].gsub("#{@host_url}/transfer/#{transferGUID}/","")
1450
+ uploadFile = "#{ovfDir}/#{fileName}"
1451
+ uploadURL = "/transfer/#{transferGUID}/#{fileName}"
1452
+ upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
1453
+ end
1454
+
1455
+ # Add item to the catalog catalogId
1456
+ builder = Nokogiri::XML::Builder.new do |xml|
1457
+ xml.CatalogItem(
1458
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
1459
+ "type" => "application/vnd.vmware.vcloud.catalogItem+xml",
1460
+ "name" => vappName) {
1461
+ xml.Description vappDescription
1462
+ xml.Entity(
1463
+ "href" => "#{@api_url}/vAppTemplate/vappTemplate-#{vAppTemplate}"
1464
+ )
1465
+ }
1466
+ end
1467
+
1468
+ params = {
1469
+ 'method' => :post,
1470
+ 'command' => "/catalog/#{catalogId}/catalogItems"
1471
+ }
1472
+ response, headers = send_request(params, builder.to_xml,
1473
+ "application/vnd.vmware.vcloud.catalogItem+xml")
1474
+
1475
+ task_id
1476
+
1477
+ ######
1478
+
1479
+ rescue Exception => e
1480
+ puts "Exception detected: #{e.message}."
1481
+ puts "Aborting task..."
1482
+
1483
+ # Get vAppTemplate Task
1484
+ params = {
1485
+ 'method' => :get,
1486
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
1487
+ }
1488
+ response, headers = send_request(params)
1489
+
1490
+ # Cancel Task
1491
+ cancelHook = response.css("Tasks Task Link [rel='task:cancel']").first[:href].gsub("#{@api_url}","")
1492
+ params = {
1493
+ 'method' => :post,
1494
+ 'command' => cancelHook
1495
+ }
1496
+ response, headers = send_request(params)
1497
+ raise
1498
+ end
1499
+ end
1500
+
1501
+ ##
1502
+ # Fetch information for a given task
1503
+ def get_task(taskid)
1504
+ params = {
1505
+ 'method' => :get,
1506
+ 'command' => "/task/#{taskid}"
1507
+ }
1508
+
1509
+ response, headers = send_request(params)
1510
+
1511
+ task = response.css('Task').first
1512
+ status = task['status']
1513
+ start_time = task['startTime']
1514
+ end_time = task['endTime']
1515
+
1516
+ { :status => status, :start_time => start_time, :end_time => end_time, :response => response }
1517
+ end
1518
+
1519
+ ##
1520
+ # Poll a given task until completion
1521
+ def wait_task_completion(taskid)
1522
+ task, status, errormsg, start_time, end_time, response = nil
1523
+ loop do
1524
+ task = get_task(taskid)
1525
+ @logger.debug("Evaluating taskid: #{taskid}, current status #{task[:status]}")
1526
+ break if task[:status] != 'running'
1527
+ sleep 1
1528
+ end
1529
+
1530
+ if task[:status] == 'error'
1531
+ @logger.debug("Task Errored out")
1532
+ errormsg = task[:response].css("Error").first
1533
+ @logger.debug("Task Error Message #{errormsg['majorErrorCode']} - #{errormsg['message']}")
1534
+ errormsg = "Error code #{errormsg['majorErrorCode']} - #{errormsg['message']}"
1535
+ end
1536
+
1537
+ { :status => task[:status], :errormsg => errormsg, :start_time => task[:start_time], :end_time => task[:end_time] }
1538
+ end
1539
+
1540
+ ##
1541
+ # Set vApp Network Config
1542
+ def set_vapp_network_config(vappid, network_name, config={})
1543
+ builder = Nokogiri::XML::Builder.new do |xml|
1544
+ xml.NetworkConfigSection(
1545
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
1546
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
1547
+ xml['ovf'].Info "Network configuration"
1548
+ xml.NetworkConfig("networkName" => network_name) {
1549
+ xml.Configuration {
1550
+ xml.FenceMode(config[:fence_mode] || 'isolated')
1551
+ xml.RetainNetInfoAcrossDeployments(config[:retain_net] || false)
1552
+ xml.ParentNetwork("href" => config[:parent_network])
1553
+ }
1554
+ }
1555
+ }
1556
+ end
1557
+
1558
+ params = {
1559
+ 'method' => :put,
1560
+ 'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
1561
+ }
1562
+
1563
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConfigSection+xml")
1564
+
1565
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
1566
+ task_id
1567
+ end
1568
+
1569
+ ##
1570
+ # Set VM Network Config
1571
+ def set_vm_network_config(vmid, network_name, config={})
1572
+ builder = Nokogiri::XML::Builder.new do |xml|
1573
+ xml.NetworkConnectionSection(
1574
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
1575
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
1576
+ xml['ovf'].Info "VM Network configuration"
1577
+ xml.PrimaryNetworkConnectionIndex(config[:primary_index] || 0)
1578
+ xml.NetworkConnection("network" => network_name, "needsCustomization" => true) {
1579
+ xml.NetworkConnectionIndex(config[:network_index] || 0)
1580
+ xml.IpAddress config[:ip] if config[:ip]
1581
+ xml.IsConnected(config[:is_connected] || true)
1582
+ xml.IpAddressAllocationMode config[:ip_allocation_mode] if config[:ip_allocation_mode]
1583
+ }
1584
+ }
1585
+ end
1586
+
1587
+ params = {
1588
+ 'method' => :put,
1589
+ 'command' => "/vApp/vm-#{vmid}/networkConnectionSection"
1590
+ }
1591
+
1592
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConnectionSection+xml")
1593
+
1594
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
1595
+ task_id
1596
+ end
1597
+
1598
+
1599
+ ##
1600
+ # Set VM Guest Customization Config
1601
+ def set_vm_guest_customization(vmid, computer_name, config={})
1602
+ builder = Nokogiri::XML::Builder.new do |xml|
1603
+ xml.GuestCustomizationSection(
1604
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
1605
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
1606
+ xml['ovf'].Info "VM Guest Customization configuration"
1607
+ xml.Enabled config[:enabled] if config[:enabled]
1608
+ xml.AdminPasswordEnabled config[:admin_passwd_enabled] if config[:admin_passwd_enabled]
1609
+ xml.AdminPassword config[:admin_passwd] if config[:admin_passwd]
1610
+ xml.ComputerName computer_name
1611
+ }
1612
+ end
1613
+
1614
+ params = {
1615
+ 'method' => :put,
1616
+ 'command' => "/vApp/vm-#{vmid}/guestCustomizationSection"
1617
+ }
1618
+
1619
+ response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.guestCustomizationSection+xml")
1620
+ task_id = headers[:location].gsub("#{@api_url}/task/", "")
1621
+ task_id
1622
+ end
1623
+
1624
+ ##
1625
+ # Fetch details about a given VM
1626
+ def get_vm(vmId)
1627
+ params = {
1628
+ 'method' => :get,
1629
+ 'command' => "/vApp/vm-#{vmId}"
1630
+ }
1631
+
1632
+ response, headers = send_request(params)
1633
+
1634
+ os_desc = response.css('ovf|OperatingSystemSection ovf|Description').first.text
1635
+
1636
+ networks = {}
1637
+ response.css('NetworkConnection').each do |network|
1638
+ ip = network.css('IpAddress').first
1639
+ ip = ip.text if ip
1640
+
1641
+ networks[network['network']] = {
1642
+ :index => network.css('NetworkConnectionIndex').first.text,
1643
+ :ip => ip,
1644
+ :is_connected => network.css('IsConnected').first.text,
1645
+ :mac_address => network.css('MACAddress').first.text,
1646
+ :ip_allocation_mode => network.css('IpAddressAllocationMode').first.text
1647
+ }
1648
+ end
1649
+
1650
+ admin_password = response.css('GuestCustomizationSection AdminPassword').first
1651
+ admin_password = admin_password.text if admin_password
1652
+
1653
+ guest_customizations = {
1654
+ :enabled => response.css('GuestCustomizationSection Enabled').first.text,
1655
+ :admin_passwd_enabled => response.css('GuestCustomizationSection AdminPasswordEnabled').first.text,
1656
+ :admin_passwd_auto => response.css('GuestCustomizationSection AdminPasswordAuto').first.text,
1657
+ :admin_passwd => admin_password,
1658
+ :reset_passwd_required => response.css('GuestCustomizationSection ResetPasswordRequired').first.text,
1659
+ :computer_name => response.css('GuestCustomizationSection ComputerName').first.text
1660
+ }
1661
+
1662
+ { :os_desc => os_desc, :networks => networks, :guest_customizations => guest_customizations }
1663
+ end
1664
+
1665
+
1666
+ end # class
1667
+ end
1668
+ end
1669
+ end