vagrant-cosmic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/CHANGELOG.md +8 -0
  4. data/Docker/.dockerignore +2 -0
  5. data/Docker/Dockerfile +48 -0
  6. data/Docker/Dockerfile.chefdk_0_17 +49 -0
  7. data/Docker/Dockerfile.latest_dependencies +49 -0
  8. data/Docker/README.md +95 -0
  9. data/Docker/vac.ps1 +29 -0
  10. data/Docker/vac.sh +30 -0
  11. data/Gemfile +21 -0
  12. data/Gemfile.lock +187 -0
  13. data/LICENSE +8 -0
  14. data/README.md +409 -0
  15. data/Rakefile +106 -0
  16. data/build_rpm.sh +7 -0
  17. data/functional-tests/basic/Vagrantfile.basic_networking +34 -0
  18. data/functional-tests/basic/basic_spec.rb +15 -0
  19. data/functional-tests/networking/Vagrantfile.advanced_networking +106 -0
  20. data/functional-tests/networking/networking_spec.rb +14 -0
  21. data/functional-tests/rsync/Vagrantfile.advanced_networking +40 -0
  22. data/functional-tests/rsync/rsync_spec.rb +9 -0
  23. data/functional-tests/vmlifecycle/Vagrantfile.advanced_networking +64 -0
  24. data/functional-tests/vmlifecycle/vmlifecycle_spec.rb +25 -0
  25. data/lib/vagrant-cosmic/action/connect_cosmic.rb +47 -0
  26. data/lib/vagrant-cosmic/action/is_created.rb +18 -0
  27. data/lib/vagrant-cosmic/action/is_stopped.rb +18 -0
  28. data/lib/vagrant-cosmic/action/message_already_created.rb +16 -0
  29. data/lib/vagrant-cosmic/action/message_not_created.rb +16 -0
  30. data/lib/vagrant-cosmic/action/message_will_not_destroy.rb +16 -0
  31. data/lib/vagrant-cosmic/action/read_rdp_info.rb +42 -0
  32. data/lib/vagrant-cosmic/action/read_ssh_info.rb +70 -0
  33. data/lib/vagrant-cosmic/action/read_state.rb +38 -0
  34. data/lib/vagrant-cosmic/action/read_transport_info.rb +59 -0
  35. data/lib/vagrant-cosmic/action/read_winrm_info.rb +69 -0
  36. data/lib/vagrant-cosmic/action/run_instance.rb +819 -0
  37. data/lib/vagrant-cosmic/action/start_instance.rb +81 -0
  38. data/lib/vagrant-cosmic/action/stop_instance.rb +28 -0
  39. data/lib/vagrant-cosmic/action/terminate_instance.rb +208 -0
  40. data/lib/vagrant-cosmic/action/timed_provision.rb +21 -0
  41. data/lib/vagrant-cosmic/action/wait_for_state.rb +41 -0
  42. data/lib/vagrant-cosmic/action/warn_networks.rb +19 -0
  43. data/lib/vagrant-cosmic/action.rb +210 -0
  44. data/lib/vagrant-cosmic/capabilities/rdp.rb +12 -0
  45. data/lib/vagrant-cosmic/capabilities/winrm.rb +12 -0
  46. data/lib/vagrant-cosmic/config.rb +422 -0
  47. data/lib/vagrant-cosmic/errors.rb +27 -0
  48. data/lib/vagrant-cosmic/exceptions/exceptions.rb +15 -0
  49. data/lib/vagrant-cosmic/model/cosmic_resource.rb +51 -0
  50. data/lib/vagrant-cosmic/plugin.rb +82 -0
  51. data/lib/vagrant-cosmic/provider.rb +58 -0
  52. data/lib/vagrant-cosmic/service/cosmic_resource_service.rb +76 -0
  53. data/lib/vagrant-cosmic/util/timer.rb +17 -0
  54. data/lib/vagrant-cosmic/version.rb +5 -0
  55. data/lib/vagrant-cosmic.rb +17 -0
  56. data/locales/en.yml +131 -0
  57. data/spec/spec_helper.rb +53 -0
  58. data/spec/vagrant-cosmic/action/read_ssh_info_spec.rb +80 -0
  59. data/spec/vagrant-cosmic/action/retrieve_public_ip_port_spec.rb +94 -0
  60. data/spec/vagrant-cosmic/action/run_instance_spec.rb +573 -0
  61. data/spec/vagrant-cosmic/action/terminate_instance_spec.rb +207 -0
  62. data/spec/vagrant-cosmic/config_spec.rb +340 -0
  63. data/spec/vagrant-cosmic/model/cosmic_resource_spec.rb +95 -0
  64. data/spec/vagrant-cosmic/service/cosmic_resource_service_spec.rb +43 -0
  65. data/spec/vagrant-cosmic/support/be_a_resource.rb +6 -0
  66. data/vagrant-cosmic.gemspec +59 -0
  67. data/vagrant-cosmic.spec +42 -0
  68. metadata +218 -0
@@ -0,0 +1,819 @@
1
+ require 'log4r'
2
+ require 'vagrant/util/retryable'
3
+ require 'vagrant-cosmic/exceptions/exceptions'
4
+ require 'vagrant-cosmic/util/timer'
5
+ require 'vagrant-cosmic/model/cosmic_resource'
6
+ require 'vagrant-cosmic/service/cosmic_resource_service'
7
+
8
+ module VagrantPlugins
9
+ module Cosmic
10
+ module Action
11
+ # This runs the configured instance.
12
+ class RunInstance
13
+ include Vagrant::Util::Retryable
14
+ include VagrantPlugins::Cosmic::Model
15
+ include VagrantPlugins::Cosmic::Service
16
+ include VagrantPlugins::Cosmic::Exceptions
17
+
18
+ def initialize(app, env)
19
+ @app = app
20
+ @logger = Log4r::Logger.new('vagrant_cosmic::action::run_instance')
21
+ @resource_service = CosmicResourceService.new(env[:cosmic_compute], env[:ui])
22
+ end
23
+
24
+ def call(env)
25
+ # Initialize metrics if they haven't been
26
+ env[:metrics] ||= {}
27
+ @env = env
28
+
29
+ sanatize_creation_parameters
30
+
31
+ show_creation_summary
32
+
33
+ create_vm
34
+
35
+ wait_for_instance_ready
36
+
37
+ store_volumes
38
+
39
+ store_password
40
+
41
+ configure_networking
42
+
43
+ unless @env[:interrupted]
44
+ wait_for_communicator_ready
45
+ @env[:ui].info(I18n.t('vagrant_cosmic.ready'))
46
+ end
47
+
48
+ # Terminate the instance if we were interrupted
49
+ terminate if @env[:interrupted]
50
+
51
+ @app.call(@env)
52
+ end
53
+
54
+ def sanatize_creation_parameters
55
+ # Get the domain we're going to booting up in
56
+ @domain = @env[:machine].provider_config.domain_id
57
+ # Get the configs
58
+ @domain_config = @env[:machine].provider_config.get_domain_config(@domain)
59
+
60
+ sanitize_domain_config
61
+
62
+ initialize_parameters
63
+
64
+ if @zone.is_undefined?
65
+ @env[:ui].error("No Zone specified!")
66
+ exit(false)
67
+ end
68
+
69
+ resolve_parameters
70
+
71
+ cs_zone = @env[:cosmic_compute].zones.find {|f| f.id == @zone.id}
72
+ resolve_network(cs_zone)
73
+
74
+ @domain_config.display_name = generate_display_name if @domain_config.display_name.nil?
75
+
76
+ if @domain_config.keypair.nil? && @domain_config.ssh_key.nil?
77
+ @env[:ui].warn(I18n.t('vagrant_cosmic.launch_no_keypair_no_sshkey'))
78
+ generate_ssh_keypair("vagacs_#{@domain_config.display_name}_#{sprintf('%04d', rand(9999))}",
79
+ nil, @domain, @domain_config.project_id)
80
+ end
81
+ end
82
+
83
+ def sanitize_domain_config
84
+ # Accept a single entry as input, convert it to array
85
+ @domain_config.pf_trusted_networks = [@domain_config.pf_trusted_networks] if @domain_config.pf_trusted_networks
86
+
87
+ if @domain_config.network_id.nil?
88
+ # Use names if ids are not present
89
+ @domain_config.network_id = []
90
+
91
+ if @domain_config.network_name.nil?
92
+ @domain_config.network_name = []
93
+ else
94
+ @domain_config.network_name = Array(@domain_config.network_name)
95
+ end
96
+ else
97
+ # Use ids if present
98
+ @domain_config.network_id = Array(@domain_config.network_id)
99
+ @domain_config.network_name = []
100
+ end
101
+ end
102
+
103
+ def initialize_parameters
104
+ @zone = CosmicResource.new(@domain_config.zone_id, @domain_config.zone_name, 'zone')
105
+ @networks = CosmicResource.create_list(@domain_config.network_id, @domain_config.network_name, 'network')
106
+ @service_offering = CosmicResource.new(@domain_config.service_offering_id, @domain_config.service_offering_name, 'service_offering')
107
+ @disk_offering = CosmicResource.new(@domain_config.disk_offering_id, @domain_config.disk_offering_name, 'disk_offering')
108
+ @template = CosmicResource.new(@domain_config.template_id, @domain_config.template_name || @env[:machine].config.vm.box, 'template')
109
+ @pf_ip_address = CosmicResource.new(@domain_config.pf_ip_address_id, @domain_config.pf_ip_address, 'public_ip_address')
110
+ end
111
+
112
+ def resolve_parameters
113
+ begin
114
+ @resource_service.sync_resource(@zone, {available: true, name: @zone.name})
115
+ @resource_service.sync_resource(@service_offering, {listall: true, name: @service_offering.name})
116
+ @resource_service.sync_resource(@disk_offering, {listall: true, name: @disk_offering.name})
117
+ @resource_service.sync_resource(@template, {zoneid: @zone.id, templatefilter: 'executable', listall: true, name: @template.name})
118
+ @resource_service.sync_resource(@pf_ip_address)
119
+ rescue CosmicResourceNotFound => e
120
+ @env[:ui].error(e.message)
121
+ exit(false)
122
+ end
123
+ end
124
+
125
+ def resolve_network(cs_zone)
126
+ if cs_zone.network_type.downcase == 'basic'
127
+ # No network specification in basic zone
128
+ @env[:ui].warn(I18n.t('vagrant_cosmic.basic_network', :zone_name => @zone.name)) if !@networks.empty? && (@networks[0].id || @networks[0].name)
129
+ @networks = [CosmicResource.new(nil, nil, 'network')]
130
+
131
+ # No portforwarding in basic zone, so none of the below
132
+ @domain_config.pf_ip_address = nil
133
+ @domain_config.pf_ip_address_id = nil
134
+ @domain_config.pf_public_port = nil
135
+ @domain_config.pf_public_rdp_port = nil
136
+ @domain_config.pf_public_port_randomrange = nil
137
+ else
138
+ @networks.each do |network|
139
+ @resource_service.sync_resource(network)
140
+ end
141
+ end
142
+ end
143
+
144
+ def generate_display_name
145
+ local_user = ENV['USER'] ? ENV['USER'].dup : 'VACS'
146
+ local_user.gsub!(/[^-a-z0-9_]/i, '')
147
+ prefix = @env[:root_path].basename.to_s
148
+ prefix.gsub!(/[^-a-z0-9_]/i, '')
149
+
150
+ local_user + '_' + prefix + "_#{Time.now.to_i}"
151
+ end
152
+
153
+ def show_creation_summary
154
+ # Launch!
155
+ @env[:ui].info(I18n.t('vagrant_cosmic.launching_instance'))
156
+ @env[:ui].info(" -- Display Name: #{@domain_config.display_name}")
157
+ @env[:ui].info(" -- Group: #{@domain_config.group}") if @domain_config.group
158
+ @env[:ui].info(" -- Service offering: #{@service_offering.name} (#{@service_offering.id})")
159
+ @env[:ui].info(" -- Disk offering: #{@disk_offering.name} (#{@disk_offering.id})") unless @disk_offering.id.nil?
160
+ @env[:ui].info(" -- Template: #{@template.name} (#{@template.id})")
161
+ @env[:ui].info(" -- Project UUID: #{@domain_config.project_id}") unless @domain_config.project_id.nil?
162
+ @env[:ui].info(" -- Zone: #{@zone.name} (#{@zone.id})")
163
+ @networks.each do |network|
164
+ @env[:ui].info(" -- Network: #{network.name} (#{network.id})")
165
+ end
166
+ @env[:ui].info(" -- Keypair: #{@domain_config.keypair}") if @domain_config.keypair
167
+ @env[:ui].info(' -- User Data: Yes') if @domain_config.user_data
168
+ end
169
+
170
+ def create_vm
171
+ @server = nil
172
+ begin
173
+ options = compose_server_creation_options
174
+
175
+ @server = @env[:cosmic_compute].servers.create(options)
176
+ @server_job_id = @server.job_id
177
+ rescue Fog::Cosmic::Compute::NotFound => e
178
+ # Invalid subnet doesn't have its own error so we catch and
179
+ # check the error message here.
180
+ # XXX FIXME vpc?
181
+ if e.message =~ /subnet ID/
182
+ raise Fog::Cosmic::Compute::Error,
183
+ :message => "Subnet ID not found: #{@networks.map(&:id).compact.join(",")}"
184
+ end
185
+
186
+ raise
187
+ rescue Fog::Cosmic::Compute::Error => e
188
+ raise Fog::Cosmic::Compute::Error, :message => e.message
189
+ end
190
+
191
+ @env[:machine].id = @server.id
192
+ end
193
+
194
+ def compose_server_creation_options
195
+ options = {
196
+ :display_name => @domain_config.display_name,
197
+ :group => @domain_config.group,
198
+ :zone_id => @zone.id,
199
+ :flavor_id => @service_offering.id,
200
+ :image_id => @template.id
201
+ }
202
+
203
+ unless @networks.empty?
204
+ nets = @networks.map(&:id).compact.join(",")
205
+ options['network_ids'] = nets unless nets.empty?
206
+ end
207
+ options['project_id'] = @domain_config.project_id unless @domain_config.project_id.nil?
208
+ options['key_name'] = @domain_config.keypair unless @domain_config.keypair.nil?
209
+ options['name'] = @domain_config.name unless @domain_config.name.nil?
210
+ options['ip_address'] = @domain_config.private_ip_address unless @domain_config.private_ip_address.nil?
211
+ options['disk_offering_id'] = @disk_offering.id unless @disk_offering.id.nil?
212
+
213
+ if @domain_config.user_data != nil
214
+ options['user_data'] = Base64.urlsafe_encode64(@domain_config.user_data)
215
+ if options['user_data'].length > 2048
216
+ raise Errors::UserdataError,
217
+ :userdataLength => options['user_data'].length
218
+ end
219
+ end
220
+ options
221
+ end
222
+
223
+ def configure_networking
224
+ begin
225
+ enable_static_nat_rules
226
+
227
+ configure_port_forwarding
228
+
229
+ # First create_port_forwardings,
230
+ # as it may generate 'pf_public_port' or 'pf_public_rdp_port',
231
+ # which after this may need a firewall rule
232
+ configure_firewall
233
+
234
+ rescue CosmicResourceNotFound => e
235
+ @env[:ui].error(e.message)
236
+ terminate
237
+ exit(false)
238
+ end
239
+ end
240
+
241
+ def enable_static_nat_rules
242
+ unless @domain_config.static_nat.empty?
243
+ @domain_config.static_nat.each do |rule|
244
+ enable_static_nat(rule)
245
+ end
246
+ end
247
+ end
248
+
249
+ def enable_static_nat(rule)
250
+ @env[:ui].info(I18n.t('vagrant_cosmic.enabling_static_nat'))
251
+
252
+ begin
253
+ ip_address = sync_ip_address(rule[:ipaddressid], rule[:ipaddress])
254
+ rescue IpNotFoundException
255
+ return
256
+ end
257
+
258
+ @env[:ui].info(" -- IP address : #{ip_address.name} (#{ip_address.id})")
259
+
260
+ options = {
261
+ :command => 'enableStaticNat',
262
+ :ipaddressid => ip_address_id,
263
+ :virtualmachineid => @env[:machine].id
264
+ }
265
+
266
+ begin
267
+ resp = @env[:cosmic_compute].request(options)
268
+ is_success = resp['enablestaticnatresponse']['success']
269
+
270
+ if is_success != 'true'
271
+ @env[:ui].warn(" -- Failed to enable static nat: #{resp['enablestaticnatresponse']['errortext']}")
272
+ return
273
+ end
274
+ rescue Fog::Cosmic::Compute::Error => e
275
+ raise Fog::Cosmic::Compute::Error, :message => e.message
276
+ end
277
+
278
+ # Save ipaddress id to the data dir so it can be disabled when the instance is destroyed
279
+ static_nat_file = @env[:machine].data_dir.join('static_nat')
280
+ static_nat_file.open('a+') do |f|
281
+ f.write("#{ip_address.id}\n")
282
+ end
283
+ end
284
+
285
+ def configure_port_forwarding
286
+
287
+ unless @pf_ip_address.is_undefined?
288
+ evaluate_pf_private_port
289
+ evaluate_pf_private_rdp_port
290
+ generate_and_apply_port_forwarding_rules_for_communicators
291
+ end
292
+
293
+ apply_port_forwarding_rules
294
+ end
295
+
296
+ def get_communicator_short_name(communicator)
297
+ communicator_short_names = {
298
+ 'VagrantPlugins::CommunicatorSSH::Communicator' => 'ssh',
299
+ 'VagrantPlugins::CommunicatorWinRM::Communicator' => 'winrm'
300
+ }
301
+ communicator_short_names[communicator.class.name]
302
+ end
303
+
304
+ def evaluate_pf_private_port
305
+ return unless @domain_config.pf_private_port.nil?
306
+
307
+ communicator_short_name = get_communicator_short_name(@env[:machine].communicate)
308
+ communicator_config = @env[:machine].config.send(communicator_short_name)
309
+
310
+ @domain_config.pf_private_port = communicator_config.port if communicator_config.respond_to?('port')
311
+ @domain_config.pf_private_port = communicator_config.guest_port if communicator_config.respond_to?('guest_port')
312
+ @domain_config.pf_private_port = communicator_config.default.port if (communicator_config.respond_to?('default') && communicator_config.default.respond_to?('port'))
313
+ end
314
+
315
+ def evaluate_pf_private_rdp_port
316
+ @domain_config.pf_private_rdp_port = @env[:machine].config.vm.rdp.port if
317
+ @env[:machine].config.vm.respond_to?(:rdp) &&
318
+ @env[:machine].config.vm.rdp.respond_to?(:port)
319
+ end
320
+
321
+ def generate_and_apply_port_forwarding_rules_for_communicators
322
+ communicators_port_names = [Hash[:public_port => 'pf_public_port', :private_port => 'pf_private_port']]
323
+ communicators_port_names << Hash[:public_port => 'pf_public_rdp_port', :private_port => 'pf_private_rdp_port'] if is_windows_guest
324
+
325
+ communicators_port_names.each do |communicator_port_names|
326
+ public_port_name = communicator_port_names[:public_port]
327
+ private_port_name = communicator_port_names[:private_port]
328
+
329
+ next unless @domain_config.send(public_port_name) || @domain_config.pf_public_port_randomrange
330
+
331
+ port_forwarding_rule = {
332
+ :ipaddressid => @domain_config.pf_ip_address_id,
333
+ :ipaddress => @domain_config.pf_ip_address,
334
+ :protocol => 'tcp',
335
+ :publicport => @domain_config.send(public_port_name),
336
+ :privateport => @domain_config.send(private_port_name),
337
+ :openfirewall => @domain_config.pf_open_firewall
338
+ }
339
+
340
+ public_port = create_randomport_forwarding_rule(
341
+ port_forwarding_rule,
342
+ @domain_config.pf_public_port_randomrange[:start]...@domain_config.pf_public_port_randomrange[:end],
343
+ public_port_name
344
+ )
345
+ @domain_config.send("#{public_port_name}=", public_port)
346
+ end
347
+ end
348
+
349
+ def create_randomport_forwarding_rule(rule, randomrange, filename)
350
+ pf_public_port = rule[:publicport]
351
+ retryable(:on => DuplicatePFRule, :tries => 10) do
352
+ begin
353
+ # Only if public port is missing, will generate a random one
354
+ rule[:publicport] = Kernel.rand(randomrange) if pf_public_port.nil?
355
+
356
+ apply_port_forwarding_rule(rule)
357
+
358
+ if pf_public_port.nil?
359
+ pf_port_file = @env[:machine].data_dir.join(filename)
360
+ pf_port_file.open('a+') do |f|
361
+ f.write("#{rule[:publicport]}")
362
+ end
363
+ end
364
+ rescue Fog::Cosmic::Compute::Error => e
365
+ if pf_public_port.nil? && !(e.message =~ /The range specified,.*conflicts with rule.*which has/).nil?
366
+ raise DuplicatePFRule, :message => e.message
367
+ else
368
+ raise Fog::Cosmic::Compute::Error, :message => e.message
369
+ end
370
+ end
371
+ end
372
+ pf_public_port.nil? ? (rule[:publicport]) : (pf_public_port)
373
+ end
374
+
375
+ def apply_port_forwarding_rules
376
+ return if @domain_config.port_forwarding_rules.empty?
377
+
378
+ @domain_config.port_forwarding_rules.each do |port_forwarding_rule|
379
+ # Sanatize/defaults pf rule before applying
380
+ port_forwarding_rule[:ipaddressid] = @domain_config.pf_ip_address_id if port_forwarding_rule[:ipaddressid].nil?
381
+ port_forwarding_rule[:ipaddress] = @domain_config.pf_ip_address if port_forwarding_rule[:ipaddress].nil?
382
+ port_forwarding_rule[:protocol] = 'tcp' if port_forwarding_rule[:protocol].nil?
383
+ port_forwarding_rule[:openfirewall] = @domain_config.pf_open_firewall if port_forwarding_rule[:openfirewall].nil?
384
+ port_forwarding_rule[:publicport] = port_forwarding_rule[:privateport] if port_forwarding_rule[:publicport].nil?
385
+ port_forwarding_rule[:privateport] = port_forwarding_rule[:publicport] if port_forwarding_rule[:privateport].nil?
386
+
387
+ apply_port_forwarding_rule(port_forwarding_rule)
388
+ end
389
+ end
390
+
391
+ def apply_port_forwarding_rule(rule)
392
+ port_forwarding_rule = nil
393
+ @env[:ui].info(I18n.t('vagrant_cosmic.creating_port_forwarding_rule'))
394
+
395
+ return unless (options = compose_port_forwarding_rule_creation_options(rule))
396
+
397
+ begin
398
+ resp = @env[:cosmic_compute].create_port_forwarding_rule(options)
399
+ job_id = resp['createportforwardingruleresponse']['jobid']
400
+
401
+ if job_id.nil?
402
+ @env[:ui].warn(" -- Failed to create port forwarding rule: #{resp['createportforwardingruleresponse']['errortext']}")
403
+ return
404
+ end
405
+
406
+ while true
407
+ response = @env[:cosmic_compute].query_async_job_result({:jobid => job_id})
408
+ if response['queryasyncjobresultresponse']['jobstatus'] != 0
409
+ port_forwarding_rule = response['queryasyncjobresultresponse']['jobresult']['portforwardingrule']
410
+ break
411
+ else
412
+ sleep 2
413
+ end
414
+ end
415
+ rescue Fog::Cosmic::Compute::Error => e
416
+ raise Fog::Cosmic::Compute::Error, :message => e.message
417
+ end
418
+
419
+ store_port_forwarding_rules(port_forwarding_rule)
420
+ end
421
+
422
+ def compose_port_forwarding_rule_creation_options(rule)
423
+ begin
424
+ ip_address = sync_ip_address(rule[:ipaddressid], rule[:ipaddress])
425
+ rescue IpNotFoundException
426
+ return
427
+ end
428
+
429
+ @env[:ui].info(" -- IP address : #{ip_address.name} (#{ip_address.id})")
430
+ @env[:ui].info(" -- Protocol : #{rule[:protocol]}")
431
+ @env[:ui].info(" -- Public port : #{rule[:publicport]}")
432
+ @env[:ui].info(" -- Private port : #{rule[:privateport]}")
433
+ @env[:ui].info(" -- Open Firewall : #{rule[:openfirewall]}")
434
+
435
+ network = get_network_from_public_ip(ip_address)
436
+
437
+ options = {
438
+ :networkid => network.id,
439
+ :ipaddressid => ip_address.id,
440
+ :publicport => rule[:publicport],
441
+ :privateport => rule[:privateport],
442
+ :protocol => rule[:protocol],
443
+ :openfirewall => rule[:openfirewall],
444
+ :virtualmachineid => @env[:machine].id
445
+ }
446
+
447
+ options.delete(:openfirewall) if network.details.has_key?('vpcid')
448
+ options
449
+ end
450
+
451
+ def get_network_from_public_ip(ip_address)
452
+ if ip_address.details.has_key?('associatednetworkid')
453
+ network = @networks.find {|f| f.id == ip_address.details['associatednetworkid']}
454
+ elsif ip_address.details.has_key?('vpcid')
455
+ # In case of VPC and ip has not yet been used, a network MUST be specified
456
+ network = @networks.find {|f| f.details['vpcid'] == ip_address.details['vpcid']}
457
+ end
458
+ network
459
+ end
460
+
461
+ def configure_firewall
462
+ if @domain_config.pf_trusted_networks
463
+ generate_firewall_rules_for_communicators unless @pf_ip_address.is_undefined?
464
+ generate_firewall_rules_for_portforwarding_rules
465
+ end
466
+
467
+ return if @domain_config.firewall_rules.empty?
468
+ auto_complete_firewall_rules
469
+ apply_firewall_rules
470
+ end
471
+
472
+ def generate_firewall_rules_for_communicators
473
+
474
+ return if @pf_ip_address.is_undefined? ||
475
+ !@domain_config.pf_trusted_networks ||
476
+ @domain_config.pf_open_firewall
477
+
478
+ ports = [Hash[publicport: 'pf_public_port', privateport: 'pf_private_port']]
479
+ ports << Hash[publicport: 'pf_public_rdp_port', privateport: 'pf_private_rdp_port'] if is_windows_guest
480
+
481
+ ports.each do |port_set|
482
+ forward_portname = @pf_ip_address.details.key?('vpcid') ? port_set[:privateport] : port_set[:publicport]
483
+ check_portname = port_set[:publicport]
484
+
485
+ next unless @domain_config.send(check_portname)
486
+
487
+ # Allow access to public port from trusted networks only
488
+ fw_rule_trusted_networks = {
489
+ ipaddressid: @pf_ip_address.id,
490
+ ipaddress: @pf_ip_address.name,
491
+ protocol: 'tcp',
492
+ startport: @domain_config.send(forward_portname),
493
+ endport: @domain_config.send(forward_portname),
494
+ cidrlist: @domain_config.pf_trusted_networks.join(',')
495
+ }
496
+ @domain_config.firewall_rules = [] unless @domain_config.firewall_rules
497
+ @domain_config.firewall_rules << fw_rule_trusted_networks
498
+
499
+ end
500
+ end
501
+
502
+ def generate_firewall_rules_for_portforwarding_rules
503
+ @pf_ip_address.details.has_key?('vpcid') ? port_name = :privateport : port_name = :publicport
504
+
505
+ unless @domain_config.port_forwarding_rules.empty?
506
+ @domain_config.port_forwarding_rules.each do |port_forwarding_rule|
507
+ if port_forwarding_rule[:generate_firewall] && @domain_config.pf_trusted_networks && !port_forwarding_rule[:openfirewall]
508
+ # Allow access to public port from trusted networks only
509
+ fw_rule_trusted_networks = {
510
+ :ipaddressid => port_forwarding_rule[:ipaddressid],
511
+ :ipaddress => port_forwarding_rule[:ipaddress],
512
+ :protocol => port_forwarding_rule[:protocol],
513
+ :startport => port_forwarding_rule[port_name],
514
+ :endport => port_forwarding_rule[port_name],
515
+ :cidrlist => @domain_config.pf_trusted_networks.join(',')
516
+ }
517
+ @domain_config.firewall_rules = [] unless @domain_config.firewall_rules
518
+ @domain_config.firewall_rules << fw_rule_trusted_networks
519
+ end
520
+ end
521
+ end
522
+ end
523
+
524
+ def auto_complete_firewall_rules
525
+ @domain_config.firewall_rules.each do |firewall_rule|
526
+ firewall_rule[:ipaddressid] = @domain_config.pf_ip_address_id if firewall_rule[:ipaddressid].nil?
527
+ firewall_rule[:ipaddress] = @domain_config.pf_ip_address if firewall_rule[:ipaddress].nil?
528
+ firewall_rule[:cidrlist] = @domain_config.pf_trusted_networks.join(',') if firewall_rule[:cidrlist].nil?
529
+ firewall_rule[:protocol] = 'tcp' if firewall_rule[:protocol].nil?
530
+ firewall_rule[:startport] = firewall_rule[:endport] if firewall_rule[:startport].nil?
531
+ end
532
+ end
533
+
534
+ def apply_firewall_rules
535
+ @domain_config.firewall_rules.each do |firewall_rule|
536
+ create_firewall_rule(firewall_rule)
537
+ end
538
+ end
539
+
540
+ def create_firewall_rule(rule)
541
+ acl_name = ''
542
+ firewall_rule = nil
543
+ @env[:ui].info(I18n.t('vagrant_cosmic.creating_firewall_rule'))
544
+
545
+ ip_address = CosmicResource.new(rule[:ipaddressid], rule[:ipaddress], 'public_ip_address')
546
+ @resource_service.sync_resource(ip_address)
547
+
548
+ @env[:ui].info(" -- IP address : #{ip_address.name} (#{ip_address.id})")
549
+ @env[:ui].info(" -- Protocol : #{rule[:protocol]}")
550
+ @env[:ui].info(" -- CIDR list : #{rule[:cidrlist]}")
551
+ @env[:ui].info(" -- Start port : #{rule[:startport]}")
552
+ @env[:ui].info(" -- End port : #{rule[:endport]}")
553
+ @env[:ui].info(" -- ICMP code : #{rule[:icmpcode]}")
554
+ @env[:ui].info(" -- ICMP type : #{rule[:icmptype]}")
555
+
556
+ if ip_address.details.has_key?('vpcid')
557
+ network = @networks.find{ |f| f.id == ip_address.details['associatednetworkid']}
558
+ acl_id = network.details['aclid']
559
+
560
+ raise CosmicResourceNotFound.new("No ACL found associated with VPC tier #{network.details['name']} (id: #{network.details['id']})") unless acl_id
561
+
562
+ resp = @env[:cosmic_compute].list_network_acl_lists(
563
+ id: network.details[acl_id]
564
+ )
565
+ acl_name = resp['listnetworkacllistsresponse']['networkacllist'][0]['name']
566
+
567
+ options, response_string, type_string = compose_firewall_rule_creation_options_vpc(network, rule)
568
+ else
569
+ options, response_string, type_string = compose_firewall_rule_creation_options_non_vpc(ip_address, rule)
570
+ end
571
+
572
+ firewall_rule = apply_firewall_rule(acl_name, options, response_string, type_string)
573
+
574
+ store_firewall_rule(firewall_rule, type_string) if firewall_rule
575
+ end
576
+
577
+ def compose_firewall_rule_creation_options_vpc(network, rule)
578
+ command_string = 'createNetworkACL'
579
+ response_string = 'createnetworkaclresponse'
580
+ type_string = 'networkacl'
581
+ options = {
582
+ :command => command_string,
583
+ :aclid => network.details['aclid'],
584
+ :action => 'Allow',
585
+ :protocol => rule[:protocol],
586
+ :cidrlist => rule[:cidrlist],
587
+ :startport => rule[:startport],
588
+ :endport => rule[:endport],
589
+ :icmpcode => rule[:icmpcode],
590
+ :icmptype => rule[:icmptype],
591
+ :traffictype => 'Ingress'
592
+ }
593
+ return options, response_string, type_string
594
+ end
595
+
596
+ def compose_firewall_rule_creation_options_non_vpc(ip_address, rule)
597
+ command_string = 'createFirewallRule'
598
+ response_string = 'createfirewallruleresponse'
599
+ type_string = 'firewallrule'
600
+ options = {
601
+ :command => command_string,
602
+ :ipaddressid => ip_address.id,
603
+ :protocol => rule[:protocol],
604
+ :cidrlist => rule[:cidrlist],
605
+ :startport => rule[:startport],
606
+ :endeport => rule[:endport],
607
+ :icmpcode => rule[:icmpcode],
608
+ :icmptype => rule[:icmptype]
609
+ }
610
+ return options, response_string, type_string
611
+ end
612
+
613
+ def get_next_free_acl_entry(network)
614
+ resp = @env[:cosmic_compute].list_network_acls(
615
+ aclid: network.details['aclid']
616
+ )
617
+ number = 0
618
+ if resp["listnetworkaclsresponse"].key?("networkacl")
619
+ resp["listnetworkaclsresponse"]["networkacl"].each {|ace| number = [number, ace["number"]].max}
620
+ end
621
+ number = number+1
622
+ end
623
+
624
+ def apply_firewall_rule(acl_name, options, response_string, type_string)
625
+ firewall_rule = nil
626
+ begin
627
+ resp = @env[:cosmic_compute].request(options)
628
+ job_id = resp[response_string]['jobid']
629
+
630
+ if job_id.nil?
631
+ @env[:ui].warn(" -- Failed to create firewall rule: #{resp[response_string]['errortext']}")
632
+ return
633
+ end
634
+
635
+ while true
636
+ response = @env[:cosmic_compute].query_async_job_result({:jobid => job_id})
637
+ if response['queryasyncjobresultresponse']['jobstatus'] != 0
638
+ firewall_rule = response['queryasyncjobresultresponse']['jobresult'][type_string]
639
+ break
640
+ else
641
+ sleep 2
642
+ end
643
+ end
644
+ rescue Fog::Cosmic::Compute::Error => e
645
+ if e.message =~ /The range specified,.*conflicts with rule/
646
+ @env[:ui].warn(" -- Failed to create firewall rule: #{e.message}")
647
+ elsif e.message =~ /Default ACL cannot be modified/
648
+ @env[:ui].warn(" -- Failed to create network acl: #{e.message}: #{acl_name}")
649
+ else
650
+ raise Fog::Cosmic::Compute::Error, :message => e.message
651
+ end
652
+ end
653
+ firewall_rule
654
+ end
655
+
656
+ def is_windows_guest
657
+ false || @env[:machine].config.vm.guest == :windows || get_communicator_short_name(@env[:machine].communicate) == 'winrm'
658
+ end
659
+
660
+ def generate_ssh_keypair(keyname, account = nil, domainid = nil, projectid = nil)
661
+ response = @env[:cosmic_compute].create_ssh_key_pair(keyname, account, domainid, projectid)
662
+ sshkeypair = response['createsshkeypairresponse']['keypair']
663
+
664
+ # Save private key to file
665
+ sshkeyfile_file = @env[:machine].data_dir.join('sshkeyfile')
666
+ sshkeyfile_file.open('w') do |f|
667
+ f.write("#{sshkeypair['privatekey']}")
668
+ end
669
+ @domain_config.ssh_key = sshkeyfile_file.to_s
670
+
671
+ sshkeyname_file = @env[:machine].data_dir.join('sshkeyname')
672
+ sshkeyname_file.open('w') do |f|
673
+ f.write("#{sshkeypair['name']}")
674
+ end
675
+
676
+ @domain_config.keypair = sshkeypair['name']
677
+ end
678
+
679
+ def store_password
680
+ password = nil
681
+ if @server.password_enabled and @server.respond_to?('job_id')
682
+ server_job_result = @env[:cosmic_compute].query_async_job_result({:jobid => @server_job_id})
683
+ if server_job_result.nil?
684
+ @env[:ui].warn(' -- Failed to retrieve job_result for retrieving the password')
685
+ return
686
+ end
687
+
688
+ while true
689
+ server_job_result = @env[:cosmic_compute].query_async_job_result({:jobid => @server_job_id})
690
+ if server_job_result['queryasyncjobresultresponse']['jobstatus'] != 0
691
+ password = server_job_result['queryasyncjobresultresponse']['jobresult']['virtualmachine']['password']
692
+ break
693
+ else
694
+ sleep 2
695
+ end
696
+ end
697
+
698
+ @env[:ui].info("Password of virtualmachine: #{password}")
699
+ # Set the password on the current communicator
700
+ @domain_config.vm_password = password
701
+
702
+ # Save password to file
703
+ vmcredentials_file = @env[:machine].data_dir.join('vmcredentials')
704
+ vmcredentials_file.open('w') do |f|
705
+ f.write("#{password}")
706
+ end
707
+ end
708
+ end
709
+
710
+ def store_volumes
711
+ volumes = @env[:cosmic_compute].volumes.find_all { |f| f.server_id == @server.id }
712
+ # volumes refuses to be iterated directly, do it by index
713
+ (0...volumes.length).each do |idx|
714
+ unless volumes[idx].type == 'ROOT'
715
+ volumes_file = @env[:machine].data_dir.join('volumes')
716
+ volumes_file.open('a+') do |f|
717
+ f.write("#{volumes[idx].id}\n")
718
+ end
719
+ end
720
+ end
721
+ end
722
+
723
+ def store_firewall_rule(firewall_rule, type_string)
724
+ unless firewall_rule.nil?
725
+ # Save firewall rule id to the data dir so it can be released when the instance is destroyed
726
+ firewall_file = @env[:machine].data_dir.join('firewall')
727
+ firewall_file.open('a+') do |f|
728
+ f.write("#{firewall_rule['id']},#{type_string}\n")
729
+ end
730
+ end
731
+ end
732
+
733
+ def store_port_forwarding_rules(port_forwarding_rule)
734
+ port_forwarding_file = @env[:machine].data_dir.join('port_forwarding')
735
+ port_forwarding_file.open('a+') do |f|
736
+ f.write("#{port_forwarding_rule['id']}\n")
737
+ end
738
+ end
739
+
740
+ def wait_for_communicator_ready
741
+ @env[:metrics]['instance_ssh_time'] = Util::Timer.time do
742
+ # Wait for communicator to be ready.
743
+ communicator_short_name = get_communicator_short_name(@env[:machine].communicate)
744
+ @env[:ui].info(
745
+ I18n.t('vagrant_cosmic.waiting_for_communicator',
746
+ communicator: communicator_short_name.to_s.upcase)
747
+ )
748
+ while true
749
+ # If we're interrupted then just back out
750
+ break if @env[:interrupted]
751
+ break if @env[:machine].communicate.ready?
752
+ sleep 2
753
+ end
754
+ end
755
+ @logger.info("Time for SSH ready: #{@env[:metrics]['instance_ssh_time']}")
756
+ end
757
+
758
+ def wait_for_instance_ready
759
+ @env[:metrics]['instance_ready_time'] = Util::Timer.time do
760
+ tries = @domain_config.instance_ready_timeout / 2
761
+
762
+ @env[:ui].info(I18n.t('vagrant_cosmic.waiting_for_ready'))
763
+ begin
764
+ retryable(:on => Fog::Errors::TimeoutError, :tries => tries) do
765
+ # If we're interrupted don't worry about waiting
766
+ next if @env[:interrupted]
767
+
768
+ # Wait for the server to be ready
769
+ @server.wait_for(2) { ready? }
770
+ end
771
+ rescue Fog::Errors::TimeoutError
772
+ # Delete the instance
773
+ terminate
774
+
775
+ # Notify the user
776
+ raise Errors::InstanceReadyTimeout,
777
+ :timeout => @domain_config.instance_ready_timeout
778
+ end
779
+ end
780
+ @logger.info("Time to instance ready: #{@env[:metrics]['instance_ready_time']}")
781
+ end
782
+
783
+ def recover(env)
784
+ return if env['vagrant.error'].is_a?(Vagrant::Errors::VagrantError)
785
+
786
+ if env[:machine].provider.state.id != :not_created
787
+ # Undo the import
788
+ terminate
789
+ end
790
+ end
791
+
792
+ def terminate
793
+ destroy_env = @env.dup
794
+ destroy_env.delete(:interrupted)
795
+ destroy_env[:config_validate] = false
796
+ destroy_env[:force_confirm_destroy] = true
797
+ @env[:action_runner].run(Action.action_destroy, destroy_env)
798
+ end
799
+
800
+ private
801
+
802
+ def sync_ip_address(ip_address_id, ip_address_value)
803
+ ip_address = CosmicResource.new(ip_address_id, ip_address_value, 'public_ip_address')
804
+
805
+ if ip_address.is_undefined?
806
+ message = 'IP address is not specified. Skip creating port forwarding rule.'
807
+ @logger.info(message)
808
+ @env[:ui].info(I18n.t(message))
809
+ raise IpNotFoundException
810
+ end
811
+
812
+ @resource_service.sync_resource(ip_address)
813
+
814
+ ip_address
815
+ end
816
+ end
817
+ end
818
+ end
819
+ end