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,459 @@
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 "log4r"
19
+ require "vagrant/util/busy"
20
+ require "vagrant/util/platform"
21
+ require "vagrant/util/retryable"
22
+ require "vagrant/util/subprocess"
23
+
24
+
25
+ module VagrantPlugins
26
+ module VCloud
27
+ module Driver
28
+ class UnauthorizedAccess < StandardError; end
29
+ class WrongAPIVersion < StandardError; end
30
+ class WrongItemIDError < StandardError; end
31
+ class InvalidStateError < StandardError; end
32
+ class InternalServerError < StandardError; end
33
+ class UnhandledError < StandardError; end
34
+
35
+ # Main class to access vCloud rest APIs
36
+ class Base
37
+ include Vagrant::Util::Retryable
38
+
39
+ def initialize
40
+ @logger = Log4r::Logger.new("vagrant::provider::vcloud::base")
41
+ end
42
+
43
+ ##
44
+ # Authenticate against the specified server
45
+ def login
46
+ end
47
+
48
+ ##
49
+ # Destroy the current session
50
+ def logout
51
+ end
52
+
53
+ ##
54
+ # Fetch existing organizations and their IDs
55
+ def get_organizations
56
+ end
57
+
58
+ ##
59
+ # friendly helper method to fetch an Organization Id by name
60
+ # - name (this isn't case sensitive)
61
+ def get_organization_id_by_name(name)
62
+ end
63
+
64
+
65
+ ##
66
+ # friendly helper method to fetch an Organization by name
67
+ # - name (this isn't case sensitive)
68
+ def get_organization_by_name(name)
69
+ end
70
+
71
+ ##
72
+ # Fetch details about an organization:
73
+ # - catalogs
74
+ # - vdcs
75
+ # - networks
76
+ def get_organization(orgId)
77
+ end
78
+
79
+ ##
80
+ # Fetch details about a given catalog
81
+ def get_catalog(catalogId)
82
+ end
83
+
84
+ ##
85
+ # Friendly helper method to fetch an catalog id by name
86
+ # - organization hash (from get_organization/get_organization_by_name)
87
+ # - catalog name
88
+ def get_catalog_id_by_name(organization, catalogName)
89
+ end
90
+
91
+ ##
92
+ # Friendly helper method to fetch an catalog by name
93
+ # - organization hash (from get_organization/get_organization_by_name)
94
+ # - catalog name
95
+ def get_catalog_by_name(organization, catalogName)
96
+ end
97
+
98
+ ##
99
+ # Fetch details about a given vdc:
100
+ # - description
101
+ # - vapps
102
+ # - networks
103
+ def get_vdc(vdcId)
104
+ end
105
+
106
+ ##
107
+ # Friendly helper method to fetch a Organization VDC Id by name
108
+ # - Organization object
109
+ # - Organization VDC Name
110
+ def get_vdc_id_by_name(organization, vdcName)
111
+ end
112
+
113
+ ##
114
+ # Friendly helper method to fetch a Organization VDC by name
115
+ # - Organization object
116
+ # - Organization VDC Name
117
+ def get_vdc_by_name(organization, vdcName)
118
+ end
119
+
120
+ ##
121
+ # Fetch details about a given catalog item:
122
+ # - description
123
+ # - vApp templates
124
+ def get_catalog_item(catalogItemId)
125
+ end
126
+
127
+ ##
128
+ # friendly helper method to fetch an catalogItem by name
129
+ # - catalogId (use get_catalog_name(org, name))
130
+ # - catalagItemName
131
+ def get_catalog_item_by_name(catalogId, catalogItemName)
132
+ end
133
+
134
+ ##
135
+ # Fetch details about a given vapp:
136
+ # - name
137
+ # - description
138
+ # - status
139
+ # - IP
140
+ # - Children VMs:
141
+ # -- IP addresses
142
+ # -- status
143
+ # -- ID
144
+ def get_vapp(vAppId)
145
+ end
146
+
147
+ ##
148
+ # Delete a given vapp
149
+ # NOTE: It doesn't verify that the vapp is shutdown
150
+ def delete_vapp(vAppId)
151
+ end
152
+
153
+ ##
154
+ # Suspend a given vapp
155
+ def suspend_vapp(vAppId)
156
+ end
157
+
158
+ ##
159
+ # reboot a given vapp
160
+ # This will basically initial a guest OS reboot, and will only work if
161
+ # VMware-tools are installed on the underlying VMs.
162
+ # vShield Edge devices are not affected
163
+ def reboot_vapp(vAppId)
164
+ end
165
+
166
+ ##
167
+ # reset a given vapp
168
+ # This will basically reset the VMs within the vApp
169
+ # vShield Edge devices are not affected.
170
+ def reset_vapp(vAppId)
171
+ end
172
+
173
+ ##
174
+ # Boot a given vapp
175
+ def poweron_vapp(vAppId)
176
+ end
177
+
178
+ ##
179
+ # Create a vapp starting from a template
180
+ #
181
+ # Params:
182
+ # - vdc: the associated VDC
183
+ # - vapp_name: name of the target vapp
184
+ # - vapp_description: description of the target vapp
185
+ # - vapp_templateid: ID of the vapp template
186
+ def create_vapp_from_template(vdc, vapp_name, vapp_description, vapp_templateid, poweron=false)
187
+ end
188
+
189
+ ##
190
+ # Compose a vapp using existing virtual machines
191
+ #
192
+ # Params:
193
+ # - vdc: the associated VDC
194
+ # - vapp_name: name of the target vapp
195
+ # - vapp_description: description of the target vapp
196
+ # - vm_list: hash with IDs of the VMs to be used in the composing process
197
+ # - network_config: hash of the network configuration for the vapp
198
+ def compose_vapp_from_vm(vdc, vapp_name, vapp_description, vm_list={}, network_config={})
199
+ end
200
+
201
+ # Fetch details about a given vapp template:
202
+ # - name
203
+ # - description
204
+ # - Children VMs:
205
+ # -- ID
206
+ def get_vapp_template(vAppId)
207
+ end
208
+
209
+ ##
210
+ # Set vApp port forwarding rules
211
+ #
212
+ # - vappid: id of the vapp to be modified
213
+ # - network_name: name of the vapp network to be modified
214
+ # - config: hash with network configuration specifications, must contain an array inside :nat_rules with the nat rules to be applied.
215
+ def set_vapp_port_forwarding_rules(vappid, network_name, config={})
216
+ end
217
+
218
+ ##
219
+ # Get vApp port forwarding rules
220
+ #
221
+ # - vappid: id of the vApp
222
+ def get_vapp_port_forwarding_rules(vAppId)
223
+ end
224
+
225
+ ##
226
+ # get vApp edge public IP from the vApp ID
227
+ # Only works when:
228
+ # - vApp needs to be poweredOn
229
+ # - FenceMode is set to "natRouted"
230
+ # - NatType" is set to "portForwarding
231
+ # This will be required to know how to connect to VMs behind the Edge device.
232
+ def get_vapp_edge_public_ip(vAppId)
233
+ end
234
+
235
+ ##
236
+ # Upload an OVF package
237
+ # - vdcId
238
+ # - vappName
239
+ # - vappDescription
240
+ # - ovfFile
241
+ # - catalogId
242
+ # - uploadOptions {}
243
+ def upload_ovf(vdcId, vappName, vappDescription, ovfFile, catalogId, uploadOptions={})
244
+ end
245
+
246
+ ##
247
+ # Fetch information for a given task
248
+ def get_task(taskid)
249
+ end
250
+
251
+ ##
252
+ # Poll a given task until completion
253
+ def wait_task_completion(taskid)
254
+ end
255
+
256
+ ##
257
+ # Set vApp Network Config
258
+ def set_vapp_network_config(vappid, network_name, config={})
259
+ end
260
+
261
+ ##
262
+ # Set VM Network Config
263
+ def set_vm_network_config(vmid, network_name, config={})
264
+ end
265
+
266
+
267
+ ##
268
+ # Set VM Guest Customization Config
269
+ def set_vm_guest_customization(vmid, computer_name, config={})
270
+ end
271
+
272
+ ##
273
+ # Fetch details about a given VM
274
+ def get_vm(vmId)
275
+ end
276
+
277
+ private
278
+ ##
279
+ # Sends a synchronous request to the vCloud API and returns the response as parsed XML + headers.
280
+ def send_request(params, payload=nil, content_type=nil)
281
+ headers = {:accept => "application/*+xml;version=#{@api_version}"}
282
+ if @auth_key
283
+ headers.merge!({:x_vcloud_authorization => @auth_key})
284
+ end
285
+
286
+ if content_type
287
+ headers.merge!({:content_type => content_type})
288
+ end
289
+
290
+ # FIXME: get rid of RestClient and switch everything to HTTPClient, easier to use and we get rid of another dependency.
291
+
292
+ request = RestClient::Request.new(:method => params['method'],
293
+ :user => "#{@username}@#{@org_name}",
294
+ :password => @password,
295
+ :headers => headers,
296
+ :url => "#{@api_url}#{params['command']}",
297
+ :payload => payload)
298
+
299
+
300
+ begin
301
+ response = request.execute
302
+ if ![200, 201, 202, 204].include?(response.code)
303
+ puts "Warning: unattended code #{response.code}"
304
+ end
305
+
306
+ # TODO: handle asynch properly, see TasksList
307
+ [Nokogiri.parse(response), response.headers]
308
+ rescue RestClient::ResourceNotFound => e
309
+ raise Errors::ObjectNotFound
310
+ rescue RestClient::Unauthorized => e
311
+ raise Errors::UnauthorizedAccess
312
+ rescue RestClient::BadRequest => e
313
+ body = Nokogiri.parse(e.http_body)
314
+ message = body.css("Error").first["message"]
315
+
316
+ case message
317
+ when /The request has invalid accept header/
318
+ raise WrongAPIVersion, "Invalid accept header. Please verify that the server supports v.#{@api_version} or specify a different API Version."
319
+ when /validation error on field 'id': String value has invalid format or length/
320
+ raise WrongItemIDError, "Invalid ID specified. Please verify that the item exists and correctly typed."
321
+ when /The requested operation could not be executed on vApp "(.*)". Stop the vApp and try again/
322
+ raise Errors::InvalidStateError, :message => "Invalid request because vApp is running. Stop vApp '#{$1}' and try again."
323
+ when /The requested operation could not be executed since vApp "(.*)" is not running/
324
+ raise Errors::InvalidStateError, :message => "Invalid request because vApp is stopped. Start vApp '#{$1}' and try again."
325
+ when /The administrator password cannot be empty when it is enabled and automatic password generation is not selected/
326
+ raise Errors::InvalidConfigError
327
+ when /The reference "(.*)" cannot be parsed correctly/ # FIXME: doesn't work
328
+ raise Errors::InvalidNetSpecification
329
+ else
330
+ raise UnhandledError, "BadRequest - unhandled error: #{message}.\nPlease report this issue."
331
+ end
332
+ rescue RestClient::Forbidden => e
333
+ body = Nokogiri.parse(e.http_body)
334
+ message = body.css("Error").first["message"]
335
+ raise Errors::UnauthorizedAccess
336
+ rescue RestClient::InternalServerError => e
337
+ body = Nokogiri.parse(e.http_body)
338
+ message = body.css("Error").first["message"]
339
+ raise InternalServerError, "Internal Server Error: #{message}."
340
+ rescue RestClient::Found => e
341
+ raise Errors::HostRedirect
342
+ end
343
+ end
344
+
345
+ ##
346
+ # Upload a large file in configurable chunks, output an optional progressbar
347
+ def upload_file(uploadURL, uploadFile, vAppTemplate, config={})
348
+
349
+ # Set chunksize to 10M if not specified otherwise
350
+ chunkSize = (config[:chunksize] || 10485760)
351
+
352
+ # Set progress bar to default format if not specified otherwise
353
+ progressBarFormat = (config[:progressbar_format] || "%t Progress: %p%% %e")
354
+
355
+ # Set progress bar length to 120 if not specified otherwise
356
+ progressBarLength = (config[:progressbar_length] || 80)
357
+
358
+ # Open our file for upload
359
+ uploadFileHandle = File.new(uploadFile, "rb" )
360
+ fileName = File.basename(uploadFileHandle)
361
+
362
+ progressBarTitle = "Uploading: " + fileName.to_s
363
+
364
+ # Create a progressbar object if progress bar is enabled
365
+ if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
366
+ progressbar = ProgressBar.create(
367
+ :title => progressBarTitle,
368
+ :starting_at => 0,
369
+ :total => uploadFileHandle.size.to_i,
370
+ ##:length => progressBarLength,
371
+ :format => progressBarFormat
372
+ )
373
+ else
374
+ puts progressBarTitle
375
+ end
376
+ # Create a new HTTP client
377
+ clnt = HTTPClient.new
378
+
379
+ # Disable SSL cert verification
380
+ clnt.ssl_config.verify_mode=(OpenSSL::SSL::VERIFY_NONE)
381
+
382
+ # Suppress SSL depth message
383
+ clnt.ssl_config.verify_callback=proc{ |ok, ctx|; true };
384
+
385
+ # Perform ranged upload until the file reaches its end
386
+ until uploadFileHandle.eof?
387
+
388
+ # Create ranges for this chunk upload
389
+ rangeStart = uploadFileHandle.pos
390
+ rangeStop = uploadFileHandle.pos.to_i + chunkSize
391
+
392
+ # Read current chunk
393
+ fileContent = uploadFileHandle.read(chunkSize)
394
+
395
+ # If statement to handle last chunk transfer if is > than filesize
396
+ if rangeStop.to_i > uploadFileHandle.size.to_i
397
+ contentRange = "bytes #{rangeStart.to_s}-#{uploadFileHandle.size.to_s}/#{uploadFileHandle.size.to_s}"
398
+ rangeLen = uploadFileHandle.size.to_i - rangeStart.to_i
399
+ else
400
+ contentRange = "bytes #{rangeStart.to_s}-#{rangeStop.to_s}/#{uploadFileHandle.size.to_s}"
401
+ rangeLen = rangeStop.to_i - rangeStart.to_i
402
+ end
403
+
404
+ # Build headers
405
+ extheader = {
406
+ 'x-vcloud-authorization' => @auth_key,
407
+ 'Content-Range' => contentRange,
408
+ 'Content-Length' => rangeLen.to_s
409
+ }
410
+
411
+ begin
412
+ uploadRequest = "#{@host_url}#{uploadURL}"
413
+ connection = clnt.request('PUT', uploadRequest, nil, fileContent, extheader)
414
+
415
+ if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
416
+ params = {
417
+ 'method' => :get,
418
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
419
+ }
420
+ response, headers = send_request(params)
421
+
422
+ response.css("Files File [name='#{fileName}']").each do |file|
423
+ progressbar.progress=file[:bytesTransferred].to_i
424
+ end
425
+ end
426
+ rescue # FIXME: HUGE FIXME!!!! DO SOMETHING WITH THIS, IT'S JUST STUPID AS IT IS NOW!!!
427
+ retryTime = (config[:retry_time] || 5)
428
+ puts "Range #{contentRange} failed to upload, retrying the chunk in #{retryTime.to_s} seconds, to stop the action press CTRL+C."
429
+ sleep retryTime.to_i
430
+ retry
431
+ end
432
+ end
433
+ uploadFileHandle.close
434
+ end
435
+
436
+
437
+ ##
438
+ # Convert vApp status codes into human readable description
439
+ def convert_vapp_status(status_code)
440
+ case status_code.to_i
441
+ when 0
442
+ 'suspended'
443
+ when 3
444
+ 'paused'
445
+ when 4
446
+ 'running'
447
+ when 8
448
+ 'stopped'
449
+ when 10
450
+ 'mixed'
451
+ else
452
+ "Unknown #{status_code}"
453
+ end
454
+ end
455
+
456
+ end # class
457
+ end
458
+ end
459
+ end