Skip to content

Commit

Permalink
Merge pull request #20 from canihavethisone/add-parallel-creation
Browse files Browse the repository at this point in the history
Added parallel instance creation opt-in option
  • Loading branch information
bastelfreak authored Feb 3, 2024
2 parents 4b1fabd + 3b01677 commit 3ea64fd
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 70 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ CONFIG:
openstack_volume_support: <true/false>
security_group: ['default']
preserve_hosts: <always/onfail/onpass/never>
create_in_parallel: true
run_in_parallel: ['configure', 'install']
type: <foss/git/pe>
```
Expand All @@ -154,6 +155,11 @@ Further, you can opt to use a static master by setting the master's hypervisor t
ip: <master_ip>
```
Additionally, you can set instance creation to occur in parallel instead of sequentially via this CONFIG entry:
```yaml
create_in_parallel: true
```
Additional parameter information is available at https://github.com/voxpupuli/beaker/blob/master/docs/concepts/argument_processing_and_precedence.md
There is a simple rake task to invoke acceptance test for the library once the two environment variables are set:
Expand Down
177 changes: 107 additions & 70 deletions lib/beaker/hypervisor/openstack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,86 +250,123 @@ def get_floating_ip
ip
end

#Create new instances in OpenStack
# Create new instances in OpenStack, depending on if create_in_parallel is true or not
def provision
@logger.notify "Provisioning OpenStack"

@hosts.each do |host|
if @options[:openstack_floating_ip]
ip = get_floating_ip
hostname = ip.ip.gsub('.','-')
host[:vmhostname] = hostname + '.rfc1918.puppetlabs.net'
else
hostname = ('a'..'z').to_a.shuffle[0, 10].join
host[:vmhostname] = hostname
end

create_or_associate_keypair(host, hostname)
@logger.debug "Provisioning #{host.name} (#{host[:vmhostname]})"
options = {
:flavor_ref => flavor(host[:flavor]).id,
:image_ref => image(host[:image]).id,
:nics => [ {'net_id' => network(@options[:openstack_network]).id } ],
:name => host[:vmhostname],
:hostname => host[:vmhostname],
:user_data => host[:user_data] || "#cloud-config\nmanage_etc_hosts: true\n",
:key_name => host[:keyname],
}
options[:security_groups] = security_groups(@options[:security_group]) unless @options[:security_group].nil?
vm = @compute_client.servers.create(options)

#wait for the new instance to start up
try = 1
attempts = @options[:timeout].to_i / SLEEPWAIT

while try <= attempts
begin
vm.wait_for(5) { ready? }
break
rescue Fog::Errors::TimeoutError => e
if try >= attempts
@logger.debug "Failed to connect to new OpenStack instance #{host.name} (#{host[:vmhostname]})"
raise e
end
@logger.debug "Timeout connecting to instance #{host.name} (#{host[:vmhostname]}), trying again..."
end
sleep SLEEPWAIT
try += 1
end
if @options[:create_in_parallel]
# Enable abort on exception for threads
Thread.abort_on_exception = true
@logger.notify "Provisioning OpenStack in parallel"
provision_parallel
else
@logger.notify "Provisioning OpenStack sequentially"
provision_sequential
end
hack_etc_hosts @hosts, @options
end

if @options[:openstack_floating_ip]
# Associate a public IP to the VM
ip.server = vm
host[:ip] = ip.ip
else
# Get the first address of the VM that was just created just like in the
# OpenStack UI
host[:ip] = vm.addresses.first[1][0]["addr"]
# Parallel creation wrapper
def provision_parallel
# Array to store threads
threads = @hosts.map do |host|
Thread.new do
create_instance_resources(host)
end
end
# Wait for all threads to finish
threads.each(&:join)
end

@logger.debug "OpenStack host #{host.name} (#{host[:vmhostname]}) assigned ip: #{host[:ip]}"

#set metadata
vm.metadata.update({:jenkins_build_url => @options[:jenkins_build_url].to_s,
:department => @options[:department].to_s,
:project => @options[:project].to_s })
@vms << vm

# Wait for the host to accept ssh logins
host.wait_for_port(22)
# Sequential creation wrapper
def provision_sequential
@hosts.each do |host|
create_instance_resources(host)
end
end

#enable root if user is not root
enable_root(host)
# Create the actual instance resources
def create_instance_resources(host)
@logger.notify "Provisioning OpenStack"
if @options[:openstack_floating_ip]
ip = get_floating_ip
hostname = ip.ip.gsub('.', '-')
host[:vmhostname] = hostname + '.rfc1918.puppetlabs.net'
else
hostname = ('a'..'z').to_a.shuffle[0, 10].join
host[:vmhostname] = hostname
end

provision_storage(host, vm) if @options[:openstack_volume_support]
@logger.notify "OpenStack Volume Support Disabled, can't provision volumes" if not @options[:openstack_volume_support]
create_or_associate_keypair(host, hostname)
@logger.debug "Provisioning #{host.name} (#{host[:vmhostname]})"
options = {
:flavor_ref => flavor(host[:flavor]).id,
:image_ref => image(host[:image]).id,
:nics => [{'net_id' => network(@options[:openstack_network]).id}],
:name => host[:vmhostname],
:hostname => host[:vmhostname],
:user_data => host[:user_data] || "#cloud-config\nmanage_etc_hosts: true\n",
:key_name => host[:keyname],
}
options[:security_groups] = security_groups(@options[:security_group]) unless @options[:security_group].nil?
vm = @compute_client.servers.create(options)

# Wait for the new instance to start up
try = 1
attempts = @options[:timeout].to_i / SLEEPWAIT

while try <= attempts
begin
vm.wait_for(5) { ready? }
break
rescue Fog::Errors::TimeoutError => e
if try >= attempts
@logger.debug "Failed to connect to new OpenStack instance #{host.name} (#{host[:vmhostname]})"
raise e
end
@logger.debug "Timeout connecting to instance #{host.name} (#{host[:vmhostname]}), trying again..."
end
sleep SLEEPWAIT
try += 1
end

hack_etc_hosts @hosts, @options
if @options[:openstack_floating_ip]
# Associate a public IP to the VM
ip.server = vm
host[:ip] = ip.ip
else
# Get the first address of the VM that was just created just like in the
# OpenStack UI
host[:ip] = vm.addresses.first[1][0]["addr"]
end

@logger.debug "OpenStack host #{host.name} (#{host[:vmhostname]}) assigned ip: #{host[:ip]}"

# Set metadata
vm.metadata.update({:jenkins_build_url => @options[:jenkins_build_url].to_s,
:department => @options[:department].to_s,
:project => @options[:project].to_s })
@vms << vm

# Wait for the host to accept SSH logins
host.wait_for_port(22)

# Enable root if the user is not root
enable_root(host)

provision_storage(host, vm) if @options[:openstack_volume_support]
@logger.notify "OpenStack Volume Support Disabled, can't provision volumes" if not @options[:openstack_volume_support]

# Handle exceptions in the thread
rescue => e
@logger.error "Thread #{host} failed with error: #{e.message}"
# Call cleanup function to delete orphaned hosts
cleanup
# Pass the error to the main thread to terminate all threads
Thread.main.raise(e)
# Terminate the current thread (to prevent hack_etc_hosts trying to run after error raised)
Thread.kill(Thread.current)
end

#Destroy any OpenStack instances
# Destroy any OpenStack instances
def cleanup
@logger.notify "Cleaning up OpenStack"
@vms.each do |vm|
Expand Down Expand Up @@ -361,7 +398,7 @@ def enable_root_on_hosts
end
end

# enable root on a single host (the current one presumably) but only
# Enable root on a single host (the current one presumably) but only
# if the username isn't 'root'
def enable_root(host)
if host['user'] != 'root'
Expand Down

0 comments on commit 3ea64fd

Please sign in to comment.