beaker 3.21.1 → 3.22.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,336 +0,0 @@
1
- module Beaker
2
- class Docker < Beaker::Hypervisor
3
-
4
- # Docker hypvervisor initializtion
5
- # Env variables supported:
6
- # DOCKER_REGISTRY: Docker registry URL
7
- # DOCKER_HOST: Remote docker host
8
- # DOCKER_BUILDARGS: Docker buildargs map
9
- # @param [Host, Array<Host>, String, Symbol] hosts One or more hosts to act upon,
10
- # or a role (String or Symbol) that identifies one or more hosts.
11
- # @param [Hash{Symbol=>String}] options Options to pass on to the hypervisor
12
- def initialize(hosts, options)
13
- require 'docker'
14
- @options = options
15
- @logger = options[:logger]
16
- @hosts = hosts
17
-
18
- # increase the http timeouts as provisioning images can be slow
19
- default_docker_options = { :write_timeout => 300, :read_timeout => 300 }.merge(::Docker.options || {})
20
- # Merge docker options from the entry in hosts file
21
- ::Docker.options = default_docker_options.merge(@options[:docker_options] || {})
22
- # assert that the docker-api gem can talk to your docker
23
- # enpoint. Will raise if there is a version mismatch
24
- begin
25
- ::Docker.validate_version!
26
- rescue Excon::Errors::SocketError => e
27
- raise "Docker instance not connectable.\nError was: #{e}\nCheck your DOCKER_HOST variable has been set\nIf you are on OSX or Windows, you might not have Docker Machine setup correctly: https://docs.docker.com/machine/\n"
28
- end
29
-
30
- # Pass on all the logging from docker-api to the beaker logger instance
31
- ::Docker.logger = @logger
32
-
33
- # Find out what kind of remote instance we are talking against
34
- if ::Docker.version['Version'] =~ /swarm/
35
- @docker_type = 'swarm'
36
- unless ENV['DOCKER_REGISTRY']
37
- raise "Using Swarm with beaker requires a private registry. Please setup the private registry and set the 'DOCKER_REGISTRY' env var"
38
- else
39
- @registry = ENV['DOCKER_REGISTRY']
40
- end
41
- else
42
- @docker_type = 'docker'
43
- end
44
-
45
- end
46
-
47
- def provision
48
- @logger.notify "Provisioning docker"
49
-
50
- @hosts.each do |host|
51
- @logger.notify "provisioning #{host.name}"
52
-
53
- @logger.debug("Creating image")
54
- image = ::Docker::Image.build(dockerfile_for(host), {
55
- :rm => true, :buildargs => buildargs_for(host)
56
- })
57
-
58
- if @docker_type == 'swarm'
59
- image_name = "#{@registry}/beaker/#{image.id}"
60
- ret = ::Docker::Image.search(:term => image_name)
61
- if ret.first.nil?
62
- @logger.debug("Image does not exist on registry. Pushing.")
63
- image.tag({:repo => image_name, :force => true})
64
- image.push
65
- end
66
- else
67
- image_name = image.id
68
- end
69
-
70
- container_opts = {
71
- 'Image' => image_name,
72
- 'Hostname' => host.name,
73
- }
74
- container = find_container(host)
75
-
76
- # If the specified container exists, then use it rather creating a new one
77
- if container.nil?
78
- unless host['mount_folders'].nil?
79
- container_opts['HostConfig'] ||= {}
80
- container_opts['HostConfig']['Binds'] = host['mount_folders'].values.map do |mount|
81
- a = [ File.expand_path(mount['host_path']), mount['container_path'] ]
82
- a << mount['opts'] if mount.has_key?('opts')
83
- a.join(':')
84
- end
85
- end
86
-
87
- if @options[:provision]
88
- if host['docker_container_name']
89
- container_opts['name'] = host['docker_container_name']
90
- end
91
-
92
- @logger.debug("Creating container from image #{image_name}")
93
- container = ::Docker::Container.create(container_opts)
94
- end
95
- end
96
-
97
- if container.nil?
98
- raise RuntimeError, 'Cannot continue because no existing container ' +
99
- 'could be found and provisioning is disabled.'
100
- end
101
-
102
- fix_ssh(container) if @options[:provision] == false
103
-
104
- @logger.debug("Starting container #{container.id}")
105
- container.start({"PublishAllPorts" => true, "Privileged" => true})
106
-
107
- # Find out where the ssh port is from the container
108
- # When running on swarm DOCKER_HOST points to the swarm manager so we have to get the
109
- # IP of the swarm slave via the container data
110
- # When we are talking to a normal docker instance DOCKER_HOST can point to a remote docker instance.
111
-
112
- # Talking against a remote docker host which is a normal docker host
113
- if @docker_type == 'docker' && ENV['DOCKER_HOST']
114
- ip = URI.parse(ENV['DOCKER_HOST']).host
115
- else
116
- # Swarm or local docker host
117
- ip = container.json["NetworkSettings"]["Ports"]["22/tcp"][0]["HostIp"]
118
- end
119
-
120
- @logger.info("Using docker server at #{ip}")
121
- port = container.json["NetworkSettings"]["Ports"]["22/tcp"][0]["HostPort"]
122
-
123
- forward_ssh_agent = @options[:forward_ssh_agent] || false
124
-
125
- # Update host metadata
126
- host['ip'] = ip
127
- host['port'] = port
128
- host['ssh'] = {
129
- :password => root_password,
130
- :port => port,
131
- :forward_agent => forward_ssh_agent,
132
- }
133
-
134
- @logger.debug("node available as ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{ip} -p #{port}")
135
- host['docker_container'] = container
136
- host['docker_image'] = image
137
- host['vm_ip'] = container.json["NetworkSettings"]["IPAddress"].to_s
138
-
139
- end
140
-
141
- hack_etc_hosts @hosts, @options
142
-
143
- end
144
-
145
- def cleanup
146
- @logger.notify "Cleaning up docker"
147
- @hosts.each do |host|
148
- if container = host['docker_container']
149
- @logger.debug("stop container #{container.id}")
150
- begin
151
- container.kill
152
- sleep 2 # avoid a race condition where the root FS can't unmount
153
- rescue Excon::Errors::ClientError => e
154
- @logger.warn("stop of container #{container.id} failed: #{e.response.body}")
155
- end
156
- @logger.debug("delete container #{container.id}")
157
- begin
158
- container.delete
159
- rescue Excon::Errors::ClientError => e
160
- @logger.warn("deletion of container #{container.id} failed: #{e.response.body}")
161
- end
162
- end
163
-
164
- # Do not remove the image if docker_reserve_image is set to true, otherwise remove it
165
- if image = (host['docker_preserve_image'] ? nil : host['docker_image'])
166
- @logger.debug("delete image #{image.id}")
167
- begin
168
- image.delete
169
- rescue Excon::Errors::ClientError => e
170
- @logger.warn("deletion of image #{image.id} failed: #{e.response.body}")
171
- rescue ::Docker::Error::DockerError => e
172
- @logger.warn("deletion of image #{image.id} caused internal Docker error: #{e.message}")
173
- end
174
- end
175
- end
176
- end
177
-
178
- private
179
-
180
- def root_password
181
- 'root'
182
- end
183
-
184
- def buildargs_for(host)
185
- docker_buildargs = {}
186
- docker_buildargs_env = ENV['DOCKER_BUILDARGS']
187
- if docker_buildargs_env != nil
188
- docker_buildargs_env.split(/ +|\t+/).each do |arg|
189
- key,value=arg.split(/=/)
190
- if key
191
- docker_buildargs[key]=value
192
- else
193
- @logger.warn("DOCKER_BUILDARGS environment variable appears invalid, no key found for value #{value}" )
194
- end
195
- end
196
- end
197
- if docker_buildargs.empty?
198
- buildargs = host['docker_buildargs'] || {}
199
- else
200
- buildargs = docker_buildargs
201
- end
202
- @logger.debug("Docker build buildargs: #{buildargs}")
203
- JSON.generate(buildargs)
204
- end
205
-
206
- def dockerfile_for(host)
207
- if host['dockerfile'] then
208
- @logger.debug("attempting to load user Dockerfile from #{host['dockerfile']}")
209
- if File.exist?(host['dockerfile']) then
210
- dockerfile = File.read(host['dockerfile'])
211
- else
212
- raise "requested Dockerfile #{host['dockerfile']} does not exist"
213
- end
214
- else
215
- raise("Docker image undefined!") if (host['image']||= nil).to_s.empty?
216
-
217
- # specify base image
218
- dockerfile = <<-EOF
219
- FROM #{host['image']}
220
- ENV container docker
221
- EOF
222
-
223
- # additional options to specify to the sshd
224
- # may vary by platform
225
- sshd_options = ''
226
-
227
- # add platform-specific actions
228
- service_name = "sshd"
229
- case host['platform']
230
- when /ubuntu/, /debian/
231
- service_name = "ssh"
232
- dockerfile += <<-EOF
233
- RUN apt-get update
234
- RUN apt-get install -y openssh-server openssh-client #{Beaker::HostPrebuiltSteps::DEBIAN_PACKAGES.join(' ')}
235
- EOF
236
- when /cumulus/
237
- dockerfile += <<-EOF
238
- RUN apt-get update
239
- RUN apt-get install -y openssh-server openssh-client #{Beaker::HostPrebuiltSteps::CUMULUS_PACKAGES.join(' ')}
240
- EOF
241
- when /fedora-(2[2-9])/
242
- dockerfile += <<-EOF
243
- RUN dnf clean all
244
- RUN dnf install -y sudo openssh-server openssh-clients #{Beaker::HostPrebuiltSteps::UNIX_PACKAGES.join(' ')}
245
- RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
246
- RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
247
- EOF
248
- when /^el-/, /centos/, /fedora/, /redhat/, /eos/
249
- dockerfile += <<-EOF
250
- RUN yum clean all
251
- RUN yum install -y sudo openssh-server openssh-clients #{Beaker::HostPrebuiltSteps::UNIX_PACKAGES.join(' ')}
252
- RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
253
- RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
254
- EOF
255
- when /opensuse/, /sles/
256
- dockerfile += <<-EOF
257
- RUN zypper -n in openssh #{Beaker::HostPrebuiltSteps::SLES_PACKAGES.join(' ')}
258
- RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
259
- RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
260
- RUN sed -ri 's/^#?UsePAM .*/UsePAM no/' /etc/ssh/sshd_config
261
- EOF
262
- else
263
- # TODO add more platform steps here
264
- raise "platform #{host['platform']} not yet supported on docker"
265
- end
266
-
267
- # Make sshd directory, set root password
268
- dockerfile += <<-EOF
269
- RUN mkdir -p /var/run/sshd
270
- RUN echo root:#{root_password} | chpasswd
271
- EOF
272
-
273
- # Configure sshd service to allowroot login using password
274
- # Also, disable reverse DNS lookups to prevent every. single. ssh
275
- # operation taking 30 seconds while the lookup times out.
276
- dockerfile += <<-EOF
277
- RUN sed -ri 's/^#?PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config
278
- RUN sed -ri 's/^#?PasswordAuthentication .*/PasswordAuthentication yes/' /etc/ssh/sshd_config
279
- RUN sed -ri 's/^#?UseDNS .*/UseDNS no/' /etc/ssh/sshd_config
280
- EOF
281
-
282
-
283
- # Any extra commands specified for the host
284
- dockerfile += (host['docker_image_commands'] || []).map { |command|
285
- "RUN #{command}\n"
286
- }.join('')
287
-
288
- # Override image entrypoint
289
- if host['docker_image_entrypoint']
290
- dockerfile += "ENTRYPOINT #{host['docker_image_entrypoint']}\n"
291
- end
292
-
293
- # How to start a sshd on port 22. May be an init for more supervision
294
- # Ensure that the ssh server can be restarted (done from set_env) and container keeps running
295
- cmd = host['docker_cmd'] || ["sh","-c","service #{service_name} start ; tail -f /dev/null"]
296
- dockerfile += <<-EOF
297
- EXPOSE 22
298
- CMD #{cmd}
299
- EOF
300
-
301
- end
302
-
303
- @logger.debug("Dockerfile is #{dockerfile}")
304
- return dockerfile
305
- end
306
-
307
- # a puppet run may have changed the ssh config which would
308
- # keep us out of the container. This is a best effort to fix it.
309
- def fix_ssh(container)
310
- @logger.debug("Fixing ssh on container #{container.id}")
311
- container.exec(['sed','-ri',
312
- 's/^#?PermitRootLogin .*/PermitRootLogin yes/',
313
- '/etc/ssh/sshd_config'])
314
- container.exec(['sed','-ri',
315
- 's/^#?PasswordAuthentication .*/PasswordAuthentication yes/',
316
- '/etc/ssh/sshd_config'])
317
- container.exec(['sed','-ri',
318
- 's/^#?UseDNS .*/UseDNS no/',
319
- '/etc/ssh/sshd_config'])
320
- container.exec(%w(service ssh restart))
321
- end
322
-
323
-
324
- # return the existing container if we're not provisioning
325
- # and docker_container_name is set
326
- def find_container(host)
327
- return nil if host['docker_container_name'].nil? || @options[:provision]
328
- @logger.debug("Looking for an existing container called #{host['docker_container_name']}")
329
-
330
- ::Docker::Container.all.select do |c|
331
- c.info['Names'].include? "/#{host['docker_container_name']}"
332
- end.first
333
- end
334
-
335
- end
336
- end
@@ -1,363 +0,0 @@
1
- module Beaker
2
- #Beaker support for OpenStack
3
- #This code is EXPERIMENTAL!
4
- #Please file any issues/concerns at https://github.com/puppetlabs/beaker/issues
5
- class OpenStack < Beaker::Hypervisor
6
-
7
- SLEEPWAIT = 5
8
-
9
- #Create a new instance of the OpenStack hypervisor object
10
- #@param [<Host>] openstack_hosts The array of OpenStack hosts to provision
11
- #@param [Hash{Symbol=>String}] options The options hash containing configuration values
12
- #@option options [String] :openstack_api_key The key to access the OpenStack instance with (required)
13
- #@option options [String] :openstack_username The username to access the OpenStack instance with (required)
14
- #@option options [String] :openstack_auth_url The URL to access the OpenStack instance with (required)
15
- #@option options [String] :openstack_tenant The tenant to access the OpenStack instance with (required)
16
- #@option options [String] :openstack_region The region that each OpenStack instance should be provisioned on (optional)
17
- #@option options [String] :openstack_network The network that each OpenStack instance should be contacted through (required)
18
- #@option options [String] :openstack_keyname The name of an existing key pair that should be auto-loaded onto each
19
- #@option options [Hash] :security_group An array of security groups to associate with the instance
20
- # OpenStack instance (optional)
21
- #@option options [String] :jenkins_build_url Added as metadata to each OpenStack instance
22
- #@option options [String] :department Added as metadata to each OpenStack instance
23
- #@option options [String] :project Added as metadata to each OpenStack instance
24
- #@option options [Integer] :timeout The amount of time to attempt execution before quiting and exiting with failure
25
- def initialize(openstack_hosts, options)
26
- require 'fog'
27
- @options = options
28
- @logger = options[:logger]
29
- @hosts = openstack_hosts
30
- @vms = []
31
-
32
- raise 'You must specify an Openstack API key (:openstack_api_key) for OpenStack instances!' unless @options[:openstack_api_key]
33
- raise 'You must specify an Openstack username (:openstack_username) for OpenStack instances!' unless @options[:openstack_username]
34
- raise 'You must specify an Openstack auth URL (:openstack_auth_url) for OpenStack instances!' unless @options[:openstack_auth_url]
35
- raise 'You must specify an Openstack tenant (:openstack_tenant) for OpenStack instances!' unless @options[:openstack_tenant]
36
- raise 'You must specify an Openstack network (:openstack_network) for OpenStack instances!' unless @options[:openstack_network]
37
-
38
- # Common keystone authentication credentials
39
- @credentials = {
40
- :provider => :openstack,
41
- :openstack_auth_url => @options[:openstack_auth_url],
42
- :openstack_api_key => @options[:openstack_api_key],
43
- :openstack_username => @options[:openstack_username],
44
- :openstack_tenant => @options[:openstack_tenant],
45
- :openstack_region => @options[:openstack_region],
46
- }
47
-
48
- # Keystone version 3 requires users and projects to be scoped
49
- if @credentials[:openstack_auth_url].include?('/v3/')
50
- @credentials[:openstack_user_domain] = @options[:openstack_user_domain] || 'Default'
51
- @credentials[:openstack_project_domain] = @options[:openstack_project_domain] || 'Default'
52
- end
53
-
54
- @compute_client ||= Fog::Compute.new(@credentials)
55
-
56
- if not @compute_client
57
- raise "Unable to create OpenStack Compute instance (api key: #{@options[:openstack_api_key]}, username: #{@options[:openstack_username]}, auth_url: #{@options[:openstack_auth_url]}, tenant: #{@options[:openstack_tenant]})"
58
- end
59
-
60
- @network_client ||= Fog::Network.new(@credentials)
61
-
62
- if not @network_client
63
- raise "Unable to create OpenStack Network instance (api_key: #{@options[:openstack_api_key]}, username: #{@options[:openstack_username]}, auth_url: #{@options[:openstack_auth_url]}, tenant: #{@options[:openstack_tenant]})"
64
- end
65
-
66
- # Validate openstack_volume_support setting value, reset to boolean if passed via ENV value string
67
- @options[:openstack_volume_support] = true if @options[:openstack_volume_support].to_s.match(/\btrue\b/i)
68
- @options[:openstack_volume_support] = false if @options[:openstack_volume_support].to_s.match(/\bfalse\b/i)
69
- [true,false].include? @options[:openstack_volume_support] or raise "Invalid openstack_volume_support setting, current: @options[:openstack_volume_support]"
70
-
71
- end
72
-
73
- #Provided a flavor name return the OpenStack id for that flavor
74
- #@param [String] f The flavor name
75
- #@return [String] Openstack id for provided flavor name
76
- def flavor f
77
- @logger.debug "OpenStack: Looking up flavor '#{f}'"
78
- @compute_client.flavors.find { |x| x.name == f } || raise("Couldn't find flavor: #{f}")
79
- end
80
-
81
- #Provided an image name return the OpenStack id for that image
82
- #@param [String] i The image name
83
- #@return [String] Openstack id for provided image name
84
- def image i
85
- @logger.debug "OpenStack: Looking up image '#{i}'"
86
- @compute_client.images.find { |x| x.name == i } || raise("Couldn't find image: #{i}")
87
- end
88
-
89
- #Provided a network name return the OpenStack id for that network
90
- #@param [String] n The network name
91
- #@return [String] Openstack id for provided network name
92
- def network n
93
- @logger.debug "OpenStack: Looking up network '#{n}'"
94
- @network_client.networks.find { |x| x.name == n } || raise("Couldn't find network: #{n}")
95
- end
96
-
97
- #Provided an array of security groups return that array if all
98
- #security groups are present
99
- #@param [Array] sgs The array of security group names
100
- #@return [Array] The array of security group names
101
- def security_groups sgs
102
- for sg in sgs
103
- @logger.debug "Openstack: Looking up security group '#{sg}'"
104
- @compute_client.security_groups.find { |x| x.name == sg } || raise("Couldn't find security group: #{sg}")
105
- sgs
106
- end
107
- end
108
-
109
- # Create a volume client on request
110
- # @return [Fog::OpenStack::Volume] OpenStack volume client
111
- def volume_client_create
112
- @volume_client ||= Fog::Volume.new(@credentials)
113
- unless @volume_client
114
- raise "Unable to create OpenStack Volume instance"\
115
- " (api_key: #{@options[:openstack_api_key]},"\
116
- " username: #{@options[:openstack_username]},"\
117
- " auth_url: #{@options[:openstack_auth_url]},"\
118
- " tenant: #{@options[:openstack_tenant]})"
119
- end
120
- end
121
-
122
- # Get a hash of volumes from the host
123
- def get_volumes host
124
- return host['volumes'] if host['volumes']
125
- {}
126
- end
127
-
128
- # Get the API version
129
- def get_volume_api_version
130
- case @volume_client
131
- when Fog::Volume::OpenStack::V1
132
- 1
133
- else
134
- -1
135
- end
136
- end
137
-
138
- # Create and attach dynamic volumes
139
- #
140
- # Creates an array of volumes and attaches them to the current host.
141
- # The host bus type is determined by the image type, so by default
142
- # devices appear as /dev/vdb, /dev/vdc etc. Setting the glance
143
- # properties hw_disk_bus=scsi, hw_scsi_model=virtio-scsi will present
144
- # them as /dev/sdb, /dev/sdc (or 2:0:0:1, 2:0:0:2 in SCSI addresses)
145
- #
146
- # @param host [Hash] thet current host defined in the nodeset
147
- # @param vm [Fog::Compute::OpenStack::Server] the server to attach to
148
- def provision_storage host, vm
149
- volumes = get_volumes(host)
150
- if !volumes.empty?
151
- # Lazily create the volume client if needed
152
- volume_client_create
153
- volumes.keys.each_with_index do |volume, index|
154
- @logger.debug "Creating volume #{volume} for OpenStack host #{host.name}"
155
-
156
- # The node defintion file defines volume sizes in MB (due to precedent
157
- # with the vagrant virtualbox implementation) however OpenStack requires
158
- # this translating into GB
159
- openstack_size = volumes[volume]['size'].to_i / 1000
160
-
161
- # Set up the volume creation arguments
162
- args = {
163
- :size => openstack_size,
164
- :description => "Beaker volume: host=#{host.name} volume=#{volume}",
165
- }
166
-
167
- # Between version 1 and subsequent versions the API was updated to
168
- # rename 'display_name' to just 'name' for better consistency
169
- if get_volume_api_version == 1
170
- args[:display_name] = volume
171
- else
172
- args[:name] = volume
173
- end
174
-
175
- # Create the volume and wait for it to become available
176
- vol = @volume_client.volumes.create(**args)
177
- vol.wait_for { ready? }
178
-
179
- # Fog needs a device name to attach as, so invent one. The guest
180
- # doesn't pay any attention to this
181
- device = "/dev/vd#{('b'.ord + index).chr}"
182
- vm.attach_volume(vol.id, device)
183
- end
184
- end
185
- end
186
-
187
- # Detach and delete guest volumes
188
- # @param vm [Fog::Compute::OpenStack::Server] the server to detach from
189
- def cleanup_storage vm
190
- vm.volumes.each do |vol|
191
- @logger.debug "Deleting volume #{vol.name} for OpenStack host #{vm.name}"
192
- vm.detach_volume(vol.id)
193
- vol.wait_for { ready? }
194
- vol.destroy
195
- end
196
- end
197
-
198
- # Get a floating IP address to associate with the instance, try
199
- # to allocate a new one from the specified pool if none are available
200
- def get_ip
201
- begin
202
- @logger.debug "Creating IP"
203
- ip = @compute_client.addresses.create
204
- rescue Fog::Compute::OpenStack::NotFound
205
- # If there are no more floating IP addresses, allocate a
206
- # new one and try again.
207
- @compute_client.allocate_address(@options[:floating_ip_pool])
208
- ip = @compute_client.addresses.find { |ip| ip.instance_id.nil? }
209
- end
210
- raise 'Could not find or allocate an address' if not ip
211
- ip
212
- end
213
-
214
- #Create new instances in OpenStack
215
- def provision
216
- @logger.notify "Provisioning OpenStack"
217
-
218
- @hosts.each do |host|
219
- ip = get_ip
220
- hostname = ip.ip.gsub('.','-')
221
- host[:vmhostname] = hostname + '.rfc1918.puppetlabs.net'
222
- create_or_associate_keypair(host, hostname)
223
- @logger.debug "Provisioning #{host.name} (#{host[:vmhostname]})"
224
- options = {
225
- :flavor_ref => flavor(host[:flavor]).id,
226
- :image_ref => image(host[:image]).id,
227
- :nics => [ {'net_id' => network(@options[:openstack_network]).id } ],
228
- :name => host[:vmhostname],
229
- :hostname => host[:vmhostname],
230
- :user_data => host[:user_data] || "#cloud-config\nmanage_etc_hosts: true\n",
231
- :key_name => host[:keyname],
232
- }
233
- options[:security_groups] = security_groups(@options[:security_group]) unless @options[:security_group].nil?
234
- vm = @compute_client.servers.create(options)
235
-
236
- #wait for the new instance to start up
237
- try = 1
238
- attempts = @options[:timeout].to_i / SLEEPWAIT
239
-
240
- while try <= attempts
241
- begin
242
- vm.wait_for(5) { ready? }
243
- break
244
- rescue Fog::Errors::TimeoutError => e
245
- if try >= attempts
246
- @logger.debug "Failed to connect to new OpenStack instance #{host.name} (#{host[:vmhostname]})"
247
- raise e
248
- end
249
- @logger.debug "Timeout connecting to instance #{host.name} (#{host[:vmhostname]}), trying again..."
250
- end
251
- sleep SLEEPWAIT
252
- try += 1
253
- end
254
-
255
- # Associate a public IP to the server
256
- ip.server = vm
257
- host[:ip] = ip.ip
258
-
259
- @logger.debug "OpenStack host #{host.name} (#{host[:vmhostname]}) assigned ip: #{host[:ip]}"
260
-
261
- #set metadata
262
- vm.metadata.update({:jenkins_build_url => @options[:jenkins_build_url].to_s,
263
- :department => @options[:department].to_s,
264
- :project => @options[:project].to_s })
265
- @vms << vm
266
-
267
- # Wait for the host to accept ssh logins
268
- host.wait_for_port(22)
269
-
270
- #enable root if user is not root
271
- enable_root(host)
272
-
273
- provision_storage(host, vm) if @options[:openstack_volume_support]
274
- @logger.notify "OpenStack Volume Support Disabled, can't provision volumes" if not @options[:openstack_volume_support]
275
- end
276
-
277
- hack_etc_hosts @hosts, @options
278
-
279
- end
280
-
281
- #Destroy any OpenStack instances
282
- def cleanup
283
- @logger.notify "Cleaning up OpenStack"
284
- @vms.each do |vm|
285
- cleanup_storage(vm) if @options[:openstack_volume_support]
286
- @logger.debug "Release floating IPs for OpenStack host #{vm.name}"
287
- floating_ips = vm.all_addresses # fetch and release its floating IPs
288
- floating_ips.each do |address|
289
- @compute_client.disassociate_address(vm.id, address['ip'])
290
- @compute_client.release_address(address['id'])
291
- end
292
- @logger.debug "Destroying OpenStack host #{vm.name}"
293
- vm.destroy
294
- if @options[:openstack_keyname].nil?
295
- @logger.debug "Deleting random keypair"
296
- @compute_client.delete_key_pair vm.key_name
297
- end
298
- end
299
- end
300
-
301
- # Enables root access for a host when username is not root
302
- # This method ripped from the aws_sdk implementation and is probably wrong
303
- # because it iterates on a collection when there's no guarantee the collection
304
- # has all been brought up in openstack yet and will thus explode
305
- # @return [void]
306
- # @api private
307
- def enable_root_on_hosts
308
- @hosts.each do |host|
309
- enable_root(host)
310
- end
311
- end
312
-
313
- # enable root on a single host (the current one presumably) but only
314
- # if the username isn't 'root'
315
- def enable_root(host)
316
- if host['user'] != 'root'
317
- copy_ssh_to_root(host, @options)
318
- enable_root_login(host, @options)
319
- host['user'] = 'root'
320
- host.close
321
- end
322
- end
323
-
324
- #Get key_name from options or generate a new rsa key and add it to
325
- #OpenStack keypairs
326
- #
327
- #@param [Host] host The OpenStack host to provision
328
- #@api private
329
- def create_or_associate_keypair(host, keyname)
330
- if @options[:openstack_keyname]
331
- @logger.debug "Adding optional key_name #{@options[:openstack_keyname]} to #{host.name} (#{host[:vmhostname]})"
332
- keyname = @options[:openstack_keyname]
333
- else
334
- @logger.debug "Generate a new rsa key"
335
-
336
- # There is apparently an error that can occur when generating RSA keys, probably
337
- # due to some timing issue, probably similar to the issue described here:
338
- # https://github.com/negativecode/vines/issues/34
339
- # In order to mitigate this error, we will simply try again up to three times, and
340
- # then fail if we continue to error out.
341
- begin
342
- retries ||= 0
343
- key = OpenSSL::PKey::RSA.new 2048
344
- rescue OpenSSL::PKey::RSAError => e
345
- retries += 1
346
- if retries > 2
347
- @logger.notify "error generating RSA key #{retries} times, exiting"
348
- raise e
349
- end
350
- retry
351
- end
352
-
353
- type = key.ssh_type
354
- data = [ key.to_blob ].pack('m0')
355
- @logger.debug "Creating Openstack keypair '#{keyname}' for public key '#{type} #{data}'"
356
- @compute_client.create_key_pair keyname, "#{type} #{data}"
357
- host['ssh'][:key_data] = [ key.to_pem ]
358
- end
359
-
360
- host[:keyname] = keyname
361
- end
362
- end
363
- end