vagrant-vcloud 0.1.0

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