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