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
A new instance of Rackspace.
-
#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) ⇒ Object
private
Kitchen::Driver::SSHBase used to supply this; the configured transport knows how to wait for the instance to accept connections.
Constructor Details
#initialize(config) ⇒ Rackspace
Returns a new instance of Rackspace.
78 79 80 81 |
# File 'lib/kitchen/driver/rackspace.rb', line 78 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.
224 225 226 227 228 229 230 231 232 |
# File 'lib/kitchen/driver/rackspace.rb', line 224 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.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/kitchen/driver/rackspace.rb', line 93 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)
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.
241 242 243 244 245 246 247 248 249 250 |
# File 'lib/kitchen/driver/rackspace.rb', line 241 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 # see @note on bootstrap def about rackconnect 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.
202 203 204 205 206 207 208 |
# File 'lib/kitchen/driver/rackspace.rb', line 202 def credential_problems compute.servers.summary [] 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.
129 130 131 |
# File 'lib/kitchen/driver/rackspace.rb', line 129 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.
142 143 144 145 146 147 148 149 |
# File 'lib/kitchen/driver/rackspace.rb', line 142 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.
114 115 116 117 118 119 120 121 122 |
# File 'lib/kitchen/driver/rackspace.rb', line 114 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.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/kitchen/driver/rackspace.rb', line 177 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.
314 315 316 317 318 319 320 |
# File 'lib/kitchen/driver/rackspace.rb', line 314 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.
255 256 257 258 259 260 |
# File 'lib/kitchen/driver/rackspace.rb', line 255 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.
215 216 217 218 219 |
# File 'lib/kitchen/driver/rackspace.rb', line 215 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.
330 331 332 333 334 335 336 |
# File 'lib/kitchen/driver/rackspace.rb', line 330 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.
292 293 294 295 296 297 |
# File 'lib/kitchen/driver/rackspace.rb', line 292 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.
303 304 305 306 307 |
# File 'lib/kitchen/driver/rackspace.rb', line 303 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.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/kitchen/driver/rackspace.rb', line 156 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.
270 271 272 273 274 275 276 |
# File 'lib/kitchen/driver/rackspace.rb', line 270 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) ⇒ Object (private)
Kitchen::Driver::SSHBase used to supply this; the configured transport knows how to wait for the instance to accept connections.
280 281 282 |
# File 'lib/kitchen/driver/rackspace.rb', line 280 def wait_for_sshd(state) instance.transport.connection(state, &:wait_until_ready) end |