Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/kitchen/driver/cloudstack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def client

# Deploys the instance and waits for CloudStack to finish building it.
#
# @param state [Hash] mutable instance state; gains +server_id+
# @return [Hash] the "virtualmachine" payload describing the instance
def deploy_instance(state)
options = ServerOptions.new(config, instance_name: instance.name).to_h
Expand All @@ -144,6 +145,11 @@ def deploy_instance(state)

# Works out the address Test Kitchen should connect to, allocating a
# public address and forwarding the transport's port when asked to.
#
# @param state [Hash] mutable instance state; gains +ipaddressid+ and
# +forwardingruleid+ when a public address is associated
# @param server_info [Hash] the "virtualmachine" payload from CloudStack
# @return [String] the address the transport should connect to
def hostname_for(state, server_info)
unless config[:associate_public_ip]
return config[:cloudstack_vm_public_ip] ||
Expand All @@ -161,6 +167,10 @@ def hostname_for(state, server_info)

# Credentials go into state because the transport merges state over its
# own config, so this is how a driver tells the transport how to log in.
#
# @param state [Hash] mutable instance state; gains the credential keys
# @param server_info [Hash] the "virtualmachine" payload from CloudStack
# @return [void]
def apply_credentials(state, server_info)
credentials = Credentials.new(config)
state.merge!(credentials.to_state(server_info))
Expand Down
7 changes: 7 additions & 0 deletions lib/kitchen/driver/cloudstack/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class Client
# not configured.
DEFAULT_TIMEOUT = 600

# @param config [Hash] the driver configuration
# @param compute [Fog::Compute, nil] an existing connection, for tests
# @param sleeper [#call, nil] receives a number of seconds to wait,
# for tests that must not actually sleep
def initialize(config, compute: nil, sleeper: nil)
@config = config
@compute = compute
Expand Down Expand Up @@ -124,6 +128,9 @@ def timeout

# CloudStack reports failures as an "errortext" inside the job result,
# but falls back to the whole payload when the shape is unexpected.
#
# @param response [Hash] the async job payload
# @return [String] a message describing the failure
def job_error(response)
result = response["jobresult"]
return response.inspect unless result.is_a?(Hash)
Expand Down
3 changes: 3 additions & 0 deletions lib/kitchen/driver/cloudstack/credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class Credentials

attr_reader :warnings

# @param config [Hash] the driver configuration
# @param home [String, nil] the home directory keypair paths expand against
# @param working_dir [String] the directory relative keypair paths expand against
def initialize(config, home: ENV["HOME"], working_dir: ".")
@config = config
@home = home
Expand Down
17 changes: 17 additions & 0 deletions lib/kitchen/driver/cloudstack/networking.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class Networking
# this in the message. Teardown treats it as success.
ALREADY_GONE = /does not exist/

# @param config [Hash] the driver configuration
# @param client [Client] the shared CloudStack client
# @param port [Integer] the transport port to forward and open
# @param logger [#debug, nil] where already-gone resources are noted
def initialize(config, client:, port:, logger: nil)
@config = config
@client = client
Expand Down Expand Up @@ -56,6 +60,10 @@ def associate_public_ip(state)
end

# Forwards the transport's port on the public address to the instance.
#
# @param state [Hash] mutable instance state; gains +forwardingruleid+
# @param virtualmachine_id [String] the instance to forward to
# @return [void]
def create_port_forward(state, virtualmachine_id)
response = compute.create_port_forwarding_rule(
"ipaddressid" => state[:ipaddressid],
Expand All @@ -72,6 +80,9 @@ def create_port_forward(state, virtualmachine_id)

# Removes everything {#associate_public_ip} and {#create_port_forward}
# created, in the reverse order, tolerating resources already gone.
#
# @param state [Hash] instance state naming the resources to remove
# @return [void]
def teardown(state)
delete_port_forward(state) if state[:forwardingruleid]
delete_firewall_rule(state) if state[:firewall_rule_id]
Expand Down Expand Up @@ -138,6 +149,12 @@ def release_public_ip(state)

# Teardown should not fail because something is already deleted, but
# any other API error is worth surfacing.
#
# @param description [String] names the resource, for the debug message
# @yield the deletion call to attempt
# @return [void]
# @raise [Fog::Cloudstack::Compute::BadRequest] for any error other
# than the resource already being gone
def tolerating_missing(description)
yield
rescue Fog::Cloudstack::Compute::BadRequest => e
Expand Down
8 changes: 8 additions & 0 deletions lib/kitchen/driver/cloudstack/server_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class ServerOptions
# pre-encoded is passed through rather than double-encoded.
BASE64_PATTERN = %r{^(?:[A-Za-z0-9+/]{4}\n?)*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$}

# @param config [Hash] the driver configuration
# @param instance_name [String] the Test Kitchen instance name
# @param login [String] the local username, used in the generated name
# @param hostname [String] the local hostname, used in the generated name
def initialize(config, instance_name:, login: Etc.getlogin, hostname: Socket.gethostname)
@config = config
@instance_name = instance_name
Expand Down Expand Up @@ -106,6 +110,10 @@ def generate_name

# Shortens the longest part repeatedly until the parts fit the budget,
# which keeps the shorter, more identifying parts intact.
#
# @param parts [Array<String>] the name components
# @param budget [Integer] the total characters the parts may occupy
# @return [Array<String>] the components, shortened to fit
def truncate_to_budget(parts, budget)
parts = parts.dup
while parts.sum(&:length) > budget
Expand Down
Loading