Class: Kitchen::Driver::Rackspace
- Inherits:
-
Base
- Object
- Base
- Kitchen::Driver::Rackspace
- Defined in:
- lib/kitchen/driver/rackspace.rb
Overview
Test Kitchen driver for Rackspace Cloud Servers.
Creates a Cloud Server for the instance under test, waits for it to become reachable, and destroys it afterwards. Talking to the server is the transport's job; this driver only tells Test Kitchen where it is and who to log in as.
Rackspace can run post-build automation -- RackConnect and Managed
Service Level -- that keeps modifying a server after it first reports
ready, including changing its public address. Set rackconnect_wait
or servicelevel_wait so the driver waits for that to finish before
handing the server over, or the transport will connect to an address
that is about to change.
Constant Summary collapse
- LIVE_STATES =
Server states Rackspace reports for a server that is up and reachable.
%w{ACTIVE}.freeze
Instance Method Summary collapse
-
#compute ⇒ Fog::Compute
private
Builds the fog compute connection from the configured credentials.
-
#create(state) ⇒ void
Creates the Cloud Server and waits until it can be logged into.
-
#create_server ⇒ Fog::Compute::RackspaceV2::Server
private
Bootstraps the Cloud Server from the configured options.
-
#credential_problems ⇒ Array<String>
private
Confirms the configured credentials can actually talk to Rackspace, which is cheaper to learn here than half way through a converge.
-
#default_image ⇒ String?
Looks up the base image for the platform under test.
-
#default_name ⇒ String
Generates a server name that is unique per run and fits Rackspace's 63-character limit.
-
#destroy(state) ⇒ void
Destroys the Cloud Server, if one was created.
-
#doctor(state) ⇒ Boolean
Checks the configuration for the mistakes that only show up as a confusing failure part way through
create. -
#hostname(server) ⇒ String
private
Picks the address Test Kitchen should connect to.
-
#images ⇒ Hash{String => String}
private
The bundled platform-name to image-ID map.
-
#initialize(config) ⇒ Rackspace
constructor
Sets up the driver and applies
wait_foras fog's global timeout. -
#lookup_server(server_id) ⇒ Fog::Compute::RackspaceV2::Server?
private
Looks a server up without turning a missing one into a failure.
-
#networks ⇒ Array<String>?
private
Builds the network list for the new server.
-
#rackconnect_check(server) ⇒ void
private
Waits for RackConnect automation to finish.
-
#servicelevel_check(server) ⇒ void
private
Waits for Managed Service Level automation to finish.
-
#status(state) ⇒ Hash
Reports what Rackspace currently thinks of the server.
-
#tcp_check(state) ⇒ void
private
Waits until the server will accept a login.
-
#wait_for_sshd(state) ⇒ void
private
Blocks until the configured transport can connect to the instance.
Constructor Details
#initialize(config) ⇒ Rackspace
Sets up the driver and applies wait_for as fog's global timeout.
Fog.timeout is process-wide, so the last driver constructed wins if
several are in play.
83 84 85 86 |
# File 'lib/kitchen/driver/rackspace.rb', line 83 def initialize(config) super Fog.timeout = config[:wait_for].to_i end |
Instance Method Details
#compute ⇒ Fog::Compute (private)
Builds the fog compute connection from the configured credentials.
235 236 237 238 239 240 241 242 243 |
# File 'lib/kitchen/driver/rackspace.rb', line 235 def compute server_def = { provider: "Rackspace" } opts = %i{version rackspace_username rackspace_api_key rackspace_region} opts.each do |opt| server_def[opt] = config[opt] end Fog::Compute.new(server_def) end |
#create(state) ⇒ void
This method returns an undefined value.
Creates the Cloud Server and waits until it can be logged into.
Waits for the server to report ready, then optionally for RackConnect and Managed Service Level automation, then for the transport to accept a connection.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/kitchen/driver/rackspace.rb', line 98 def create(state) server = create_server state[:server_id] = server.id info("Rackspace instance <#{state[:server_id]}> created.") server.wait_for { ready? } puts "(server ready)" rackconnect_check(server) if config[:rackconnect_wait] servicelevel_check(server) if config[:servicelevel_wait] state[:hostname] = hostname(server) state[:username] = config[:username] state[:port] = config[:port] if config[:port] tcp_check(state) rescue Fog::Errors::Error, Excon::Errors::Error => ex raise ActionFailed, ex. end |
#create_server ⇒ Fog::Compute::RackspaceV2::Server (private)
fog's bootstrap calls Server#setup, which rescues
Errno::ECONNREFUSED and retries forever, one second apart. A server
that never opens port 22 hangs here rather than failing, and
wait_for does not bound it.
Bootstraps the Cloud Server from the configured options.
RackConnect and Managed Service Level both need the root password left
unlocked to do their work, so no_passwd_lock is forced on whenever
either wait is requested, regardless of how it was configured.
257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/kitchen/driver/rackspace.rb', line 257 def create_server server_def = { name: config[:server_name], networks: } %i{image_id flavor_id public_key_path no_passwd_lock user_data config_drive}.each do |opt| server_def[opt] = config[opt] end # RackConnect and Managed Service Level need the root password left # unlocked; see the note above. no_passwd_lock = config[:rackconnect_wait] || config[:servicelevel_wait] server_def[:no_passwd_lock] = no_passwd_lock if no_passwd_lock compute.servers.bootstrap(server_def) end |
#credential_problems ⇒ Array<String> (private)
Confirms the configured credentials can actually talk to Rackspace, which is cheaper to learn here than half way through a converge.
Listing servers is the cheapest authenticated call fog-rackspace
offers: GET /servers returns only an ID and a name per server, and
it exercises the compute endpoint for the configured region rather
than just the identity endpoint.
213 214 215 216 217 218 219 |
# File 'lib/kitchen/driver/rackspace.rb', line 213 def credential_problems compute.servers.all [] rescue Fog::Errors::Error, Excon::Errors::Error => e ["Rackspace rejected the configured credentials for region " \ "#{config[:rackspace_region]}: #{e.}"] end |
#default_image ⇒ String?
Looks up the base image for the platform under test.
134 135 136 |
# File 'lib/kitchen/driver/rackspace.rb', line 134 def default_image images[instance.platform.name] end |
#default_name ⇒ String
Generates a server name that is unique per run and fits Rackspace's 63-character limit.
The budget is spent as base name 15, username 15, hostname 23, random suffix 7, and three separators, for 63 exactly. Each part is stripped of non-word characters and truncated to its share, so a long login or hostname cannot push the result over.
147 148 149 150 151 152 153 154 |
# File 'lib/kitchen/driver/rackspace.rb', line 147 def default_name [ instance.name.gsub(/\W/, "")[0..14], (Etc.getlogin || "nologin").gsub(/\W/, "")[0..14], Socket.gethostname.gsub(/\W/, "")[0..22], Array.new(7) { rand(36).to_s(36) }.join, ].join("-") end |
#destroy(state) ⇒ void
This method returns an undefined value.
Destroys the Cloud Server, if one was created.
119 120 121 122 123 124 125 126 127 |
# File 'lib/kitchen/driver/rackspace.rb', line 119 def destroy(state) return if state[:server_id].nil? server = compute.servers.get(state[:server_id]) server.destroy unless server.nil? info("Rackspace instance <#{state[:server_id]}> destroyed.") state.delete(:server_id) state.delete(:hostname) end |
#doctor(state) ⇒ Boolean
Checks the configuration for the mistakes that only show up as a
confusing failure part way through create.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/kitchen/driver/rackspace.rb', line 183 def doctor(state) # rubocop:disable Lint/UnusedMethodArgument problems = [] if config[:image_id].nil? problems << "No image_id is set and #{instance.platform.name} is not " \ "in the bundled image list. Set image_id explicitly." end if config[:public_key_path].nil? problems << "No public key was found in ~/.ssh. Set public_key_path " \ "to the key Rackspace should install on the server." end problems.concat(credential_problems) problems.each { |problem| warn(problem) } !problems.empty? end |
#hostname(server) ⇒ String (private)
Picks the address Test Kitchen should connect to.
336 337 338 339 340 341 342 |
# File 'lib/kitchen/driver/rackspace.rb', line 336 def hostname(server) if config[:servicenet] == false server.public_ip_address else server.private_ip_address end end |
#images ⇒ Hash{String => String} (private)
The bundled platform-name to image-ID map.
272 273 274 275 276 277 |
# File 'lib/kitchen/driver/rackspace.rb', line 272 def images @images ||= begin json_file = File.("../../../data/images.json", __dir__) JSON.parse(IO.read(json_file)) end end |
#lookup_server(server_id) ⇒ Fog::Compute::RackspaceV2::Server? (private)
Looks a server up without turning a missing one into a failure.
226 227 228 229 230 |
# File 'lib/kitchen/driver/rackspace.rb', line 226 def lookup_server(server_id) compute.servers.get(server_id) rescue Fog::Errors::Error, Excon::Errors::Error nil end |
#networks ⇒ Array<String>? (private)
Builds the network list for the new server.
Rackspace's PublicNet and ServiceNet have fixed, well-known IDs. Any configured networks are added to those two rather than replacing them, since dropping PublicNet would leave the server unreachable.
352 353 354 355 356 357 358 |
# File 'lib/kitchen/driver/rackspace.rb', line 352 def networks base_nets = %w{ 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111 } config[:networks] ? base_nets + config[:networks] : nil end |
#rackconnect_check(server) ⇒ void (private)
This method returns an undefined value.
Waits for RackConnect automation to finish.
The server is refreshed afterwards, because RackConnect assigns a new public address as part of its work and the stale one would otherwise be handed to the transport.
314 315 316 317 318 319 |
# File 'lib/kitchen/driver/rackspace.rb', line 314 def rackconnect_check(server) server.wait_for \ { .all["rackconnect_automation_status"] == "DEPLOYED" } puts "(rackconnect automation complete)" server.update # refresh accessIPv4 with new IP end |
#servicelevel_check(server) ⇒ void (private)
This method returns an undefined value.
Waits for Managed Service Level automation to finish.
325 326 327 328 329 |
# File 'lib/kitchen/driver/rackspace.rb', line 325 def servicelevel_check(server) server.wait_for \ { .all["rax_service_level_automation"] == "Complete" } puts "(service level automation complete)" end |
#status(state) ⇒ Hash
Reports what Rackspace currently thinks of the server.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/kitchen/driver/rackspace.rb', line 161 def status(state) return super unless state[:server_id] server = lookup_server(state[:server_id]) return super unless server { live: LIVE_STATES.include?(server.state), state: server.state, source: "driver", resource_id: state[:server_id], message: "Rackspace reports the server as #{server.state}", checked_at: Time.now.utc.iso8601, } end |
#tcp_check(state) ⇒ void (private)
This method returns an undefined value.
Waits until the server will accept a login.
The TCP check does not honour ssh_config, which some setups need, so
no_ssh_tcp_check swaps the check for a fixed sleep of
no_ssh_tcp_check_sleep seconds instead.
287 288 289 290 291 292 293 |
# File 'lib/kitchen/driver/rackspace.rb', line 287 def tcp_check(state) # allow driver config to bypass SSH tcp check -- because # it doesn't respect ssh_config values that might be required wait_for_sshd(state) unless config[:no_ssh_tcp_check] sleep(config[:no_ssh_tcp_check_sleep]) if config[:no_ssh_tcp_check] puts "(ssh ready)" end |
#wait_for_sshd(state) ⇒ void (private)
This method returns an undefined value.
Blocks until the configured transport can connect to the instance.
Kitchen::Driver::SSHBase used to supply this; the configured transport knows how to wait for the instance to accept connections.
302 303 304 |
# File 'lib/kitchen/driver/rackspace.rb', line 302 def wait_for_sshd(state) instance.transport.connection(state, &:wait_until_ready) end |