kitchen-rackspace 0.21.2 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/publish.yaml +1 -1
- data/.release-please-manifest.json +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +1 -3
- data/.yamllint +6 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +111 -3
- data/CONTRIBUTING.md +207 -0
- data/Gemfile +7 -5
- data/README.md +327 -58
- data/Rakefile +18 -2
- data/helpers/dump_flavor_list.rb +46 -0
- data/helpers/dump_image_list.rb +97 -66
- data/kitchen-rackspace.gemspec +6 -4
- data/lib/kitchen/driver/rackspace.rb +201 -12
- data/lib/kitchen/driver/rackspace_version.rb +4 -1
- data/spec/kitchen/driver/rackspace/compute_spec.rb +162 -0
- data/spec/kitchen/driver/rackspace/configuration_spec.rb +210 -0
- data/spec/kitchen/driver/rackspace/create_spec.rb +150 -0
- data/spec/kitchen/driver/rackspace/destroy_spec.rb +63 -0
- data/spec/kitchen/driver/rackspace/image_spec.rb +58 -0
- data/spec/kitchen/driver/rackspace/plugin_spec.rb +14 -0
- data/spec/kitchen/driver/rackspace/server_name_spec.rb +63 -0
- data/spec/kitchen/driver/rackspace/status_spec.rb +151 -0
- data/spec/kitchen/driver/rackspace/tcp_check_spec.rb +37 -0
- data/spec/spec_helper.rb +21 -5
- data/spec/support/driver_context.rb +51 -0
- data/spec/support/rackspace_environment.rb +27 -0
- metadata +24 -12
- data/.github/dependabot.yml +0 -8
- data/spec/kitchen/driver/rackspace_spec.rb +0 -664
data/helpers/dump_image_list.rb
CHANGED
|
@@ -1,86 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Regenerates data/images.json from the images a Rackspace account can see.
|
|
5
|
+
#
|
|
6
|
+
# The driver maps a Test Kitchen platform name (`ubuntu-22.04`) to a Rackspace
|
|
7
|
+
# image ID. That mapping lives in data/images.json and has to be refreshed
|
|
8
|
+
# whenever Rackspace publishes new base images.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
#
|
|
12
|
+
# export RACKSPACE_USERNAME=myuser
|
|
13
|
+
# export RACKSPACE_API_KEY=myapikey
|
|
14
|
+
# export RACKSPACE_REGION=ord # optional, defaults to dfw
|
|
15
|
+
#
|
|
16
|
+
# bundle exec ruby helpers/dump_image_list.rb # human-readable table
|
|
17
|
+
# bundle exec ruby helpers/dump_image_list.rb --json # data/images.json content
|
|
18
|
+
#
|
|
19
|
+
# Run it under Bundler. fog-rackspace only loads against the fog-core version
|
|
20
|
+
# this gem pins; on a newer fog-core, `require "fog/rackspace"` raises NameError
|
|
21
|
+
# on its "CDN v2" service name.
|
|
22
|
+
#
|
|
23
|
+
# Image IDs are per-region, so regenerate from the region most users build in.
|
|
24
|
+
|
|
1
25
|
require "fog/rackspace"
|
|
2
26
|
require "json" unless defined?(JSON)
|
|
3
27
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
i_care_about = {
|
|
9
|
-
"Arch Linux (PVHVM)" => %w{arch arch-2016 arch-2016.8},
|
|
10
|
-
"CentOS 7 (PVHVM)" => %w{centos centos-7},
|
|
11
|
-
"CentOS 6 (PVHVM)" => %w{centos-6},
|
|
12
|
-
"CoreOS (Stable)" => %w{coreos coreos-stable},
|
|
13
|
-
"CoreOS (Beta)" => %w{coreos-beta},
|
|
14
|
-
"CoreOS (Alpha)" => %w{coreos-alpha},
|
|
15
|
-
"Debian 8 (Jessie) (PVHVM)" => %w{debian debian-8},
|
|
16
|
-
"Debian 7 (Wheezy) (PVHVM)" => %w{debian-7},
|
|
17
|
-
"Debian Testing (Stretch) (PVHVM)" => %w{debian-testing},
|
|
18
|
-
"Debian Unstable (Sid) (PVHVM)" => %w{debian-unstable},
|
|
19
|
-
"Fedora 25 (PVHVM)" => %w{fedora fedora-25},
|
|
20
|
-
"Fedora 24 (PVHVM)" => %w{fedora-24},
|
|
21
|
-
"FreeBSD 11 (PVHVM)" => %w{freebsd freebsd-11},
|
|
22
|
-
"FreeBSD 10 (PVHVM)" => %w{freebsd-10},
|
|
23
|
-
"Gentoo 15.3 (PVHVM)" => %w{gentoo gentoo-15 gentoo-15.3},
|
|
24
|
-
"OpenSUSE Leap 42 (PVHVM)" => %w{opensuse opensuse-42},
|
|
25
|
-
"Scientific Linux 7 (PVHVM)" => %w{scientific scientific-7},
|
|
26
|
-
"Scientific Linux 6 (PVHVM)" => %w{scientific-6},
|
|
27
|
-
"Ubuntu 16.04 LTS (Xenial Xerus) (PVHVM)" => %w{ubuntu ubuntu-16 ubuntu-16.04},
|
|
28
|
-
"Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM)" => %w{ubuntu-14 ubuntu-14.04},
|
|
29
|
-
"Ubuntu 12.04 LTS (Precise Pangolin) (PVHVM)" => %w{ubuntu-12 ubuntu-12.04},
|
|
30
|
-
"Vyatta Network OS 6.7R12" => %w{vyatta vyatta-6 vyatta-6.7 vyatta-6.7R12},
|
|
31
|
-
"Windows Server 2012 R2" => %w{windows windows-2012 windows-2012R2},
|
|
32
|
-
"Windows Server 2008 R2 SP1" => %w{windows-2008
|
|
33
|
-
windows-2008R2
|
|
34
|
-
windows-2008R2SP1},
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
names_to_clean = {
|
|
28
|
+
# Rackspace reports the distro in OpenStack metadata using reverse-DNS names.
|
|
29
|
+
# Map the ones that do not reduce to a sensible short name on their own.
|
|
30
|
+
DISTRO_NAMES = {
|
|
38
31
|
"com.microsoft.server" => "windows",
|
|
39
|
-
"org.fedoraproject" => "fedora",
|
|
40
32
|
"org.archlinux" => "arch",
|
|
33
|
+
"org.fedoraproject" => "fedora",
|
|
41
34
|
"org.scientificlinux" => "scientific",
|
|
42
|
-
}
|
|
35
|
+
}.freeze
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
# Builds the platform aliases for one image.
|
|
38
|
+
#
|
|
39
|
+
# An image advertising Ubuntu 22.04 yields "ubuntu-22.04", plus "ubuntu-22" as
|
|
40
|
+
# a convenience. The bare distro name ("ubuntu") is assigned separately, to the
|
|
41
|
+
# newest version found, so that it does not depend on iteration order.
|
|
42
|
+
#
|
|
43
|
+
# @param image [Fog::Compute::RackspaceV2::Image] the image to describe
|
|
44
|
+
# @return [Array(String, String, Array<String>), nil] distro, version, and
|
|
45
|
+
# aliases, or nil when the image carries no usable OpenStack metadata
|
|
46
|
+
def describe(image)
|
|
47
|
+
meta = image.metadata
|
|
48
|
+
distro_id = meta["org.openstack__1__os_distro"]
|
|
49
|
+
version = meta["org.openstack__1__os_version"]
|
|
50
|
+
return nil if distro_id.nil? || version.nil?
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
res = aliases.each_with_object({}) do |a, hsh|
|
|
51
|
-
raise "Alias '#{a}' was listed twice" if hsh.include?(a)
|
|
52
|
+
distro = DISTRO_NAMES.fetch(distro_id) { distro_id.split(".").last }
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
aliases = ["#{distro}-#{version}"]
|
|
55
|
+
# "ubuntu-22" alongside "ubuntu-22.04", but not "centos-7" twice.
|
|
56
|
+
major = version.split(".").first
|
|
57
|
+
aliases << "#{distro}-#{major}" if major != version
|
|
58
|
+
|
|
59
|
+
[distro, version, aliases]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Sorts version strings numerically, so that 22.04 beats 8 and 9 beats 10 is
|
|
63
|
+
# never asserted. Non-numeric segments (Vyatta's "6.7R12") sort last.
|
|
64
|
+
#
|
|
65
|
+
# @param version [String] an OS version
|
|
66
|
+
# @return [Array<Integer>] a comparable key
|
|
67
|
+
def version_key(version)
|
|
68
|
+
version.split(".").map(&:to_i)
|
|
55
69
|
end
|
|
56
70
|
|
|
57
|
-
compute
|
|
58
|
-
|
|
71
|
+
compute = Fog::Compute.new(
|
|
72
|
+
provider: "Rackspace",
|
|
73
|
+
rackspace_username: ENV.fetch("RACKSPACE_USERNAME"),
|
|
74
|
+
rackspace_api_key: ENV.fetch("RACKSPACE_API_KEY"),
|
|
75
|
+
rackspace_region: ENV.fetch("RACKSPACE_REGION", "dfw"),
|
|
76
|
+
version: :v2
|
|
77
|
+
)
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
images = compute.images.to_a
|
|
80
|
+
abort "No images visible to this account." if images.empty?
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
if ARGV.include?("--json")
|
|
83
|
+
mapping = {}
|
|
84
|
+
newest = {}
|
|
65
85
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
distro_id.split(".").last
|
|
70
|
-
end
|
|
86
|
+
images.each do |image|
|
|
87
|
+
described = describe(image)
|
|
88
|
+
next if described.nil?
|
|
71
89
|
|
|
72
|
-
|
|
90
|
+
distro, version, aliases = described
|
|
91
|
+
aliases.each { |a| mapping[a] = image.id }
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
current = newest[distro]
|
|
94
|
+
if current.nil? || (version_key(version) <=> version_key(current[:version])) == 1
|
|
95
|
+
newest[distro] = { version:, id: image.id }
|
|
96
|
+
end
|
|
77
97
|
end
|
|
78
98
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
99
|
+
# The bare distro name points at the newest version of that distro.
|
|
100
|
+
newest.each { |distro, info| mapping[distro] = info[:id] }
|
|
101
|
+
|
|
102
|
+
puts JSON.pretty_generate(mapping.sort.to_h)
|
|
103
|
+
else
|
|
104
|
+
rows = images.map do |image|
|
|
105
|
+
described = describe(image)
|
|
106
|
+
[image.id, image.name, described ? described[2].join(", ") : "-"]
|
|
107
|
+
end.sort_by { |row| row[1] }
|
|
82
108
|
|
|
83
|
-
|
|
109
|
+
widths = [0, 1, 2].map { |i| rows.map { |r| r[i].length }.max }
|
|
110
|
+
header = ["IMAGE ID", "NAME", "PLATFORM NAMES"]
|
|
111
|
+
widths = widths.each_with_index.map { |w, i| [w, header[i].length].max }
|
|
84
112
|
|
|
85
|
-
|
|
86
|
-
puts
|
|
113
|
+
puts header.each_with_index.map { |h, i| h.ljust(widths[i]) }.join(" ")
|
|
114
|
+
puts widths.map { |w| "-" * w }.join(" ")
|
|
115
|
+
rows.each { |row| puts row.each_with_index.map { |c, i| c.ljust(widths[i]) }.join(" ") }
|
|
116
|
+
warn "\n#{rows.count} images. Re-run with --json to regenerate data/images.json."
|
|
117
|
+
end
|
data/kitchen-rackspace.gemspec
CHANGED
|
@@ -10,16 +10,18 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.description = "A Test Kitchen Rackspace driver"
|
|
11
11
|
spec.summary = "A Test Kitchen Rackspace driver built on Fog"
|
|
12
12
|
spec.homepage = "https://github.com/test-kitchen/kitchen-rackspace"
|
|
13
|
-
spec.license = "Apache"
|
|
13
|
+
spec.license = "Apache-2.0"
|
|
14
14
|
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
17
|
spec.require_paths = %w{lib}
|
|
19
18
|
|
|
20
19
|
spec.required_ruby_version = ">= 3.1"
|
|
21
20
|
|
|
22
21
|
spec.add_dependency "fog-rackspace", "~> 0.1.6"
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# fog-rackspace 0.1.6 registers a service literally named "CDN v2"; fog-core
|
|
23
|
+
# 2.3.0+ passes that through const_defined?, which rejects it as an invalid
|
|
24
|
+
# constant name, so `require "fog/rackspace"` raises NameError.
|
|
25
|
+
spec.add_dependency "fog-core", ">= 1.35", "< 2.3"
|
|
26
|
+
spec.add_dependency "test-kitchen", ">= 3.0", "< 5.0"
|
|
25
27
|
end
|
|
@@ -4,12 +4,35 @@ require "kitchen"
|
|
|
4
4
|
require "etc" unless defined?(Etc)
|
|
5
5
|
require "socket" unless defined?(Socket)
|
|
6
6
|
require "json" unless defined?(JSON)
|
|
7
|
+
require "time" unless defined?(Time.now.iso8601)
|
|
8
|
+
require_relative "rackspace_version"
|
|
7
9
|
|
|
8
10
|
module Kitchen
|
|
11
|
+
# Test Kitchen's driver plugins.
|
|
9
12
|
module Driver
|
|
10
|
-
|
|
13
|
+
# Test Kitchen driver for Rackspace Cloud Servers.
|
|
14
|
+
#
|
|
15
|
+
# Creates a Cloud Server for the instance under test, waits for it to
|
|
16
|
+
# become reachable, and destroys it afterwards. Talking to the server is
|
|
17
|
+
# the transport's job; this driver only tells Test Kitchen where it is and
|
|
18
|
+
# who to log in as.
|
|
19
|
+
#
|
|
20
|
+
# Rackspace can run post-build automation -- RackConnect and Managed
|
|
21
|
+
# Service Level -- that keeps modifying a server after it first reports
|
|
22
|
+
# +ready+, including changing its public address. Set +rackconnect_wait+
|
|
23
|
+
# or +servicelevel_wait+ so the driver waits for that to finish before
|
|
24
|
+
# handing the server over, or the transport will connect to an address
|
|
25
|
+
# that is about to change.
|
|
26
|
+
class Rackspace < Kitchen::Driver::Base
|
|
27
|
+
kitchen_driver_api_version 2
|
|
28
|
+
|
|
29
|
+
plugin_version Kitchen::Driver::RACKSPACE_VERSION
|
|
30
|
+
|
|
31
|
+
# Server states Rackspace reports for a server that is up and reachable.
|
|
32
|
+
LIVE_STATES = %w{ACTIVE}.freeze
|
|
33
|
+
|
|
11
34
|
default_config :version, "v2"
|
|
12
|
-
default_config :flavor_id, "
|
|
35
|
+
default_config :flavor_id, "general1-2"
|
|
13
36
|
default_config :username, "root"
|
|
14
37
|
default_config :port, "22"
|
|
15
38
|
default_config :wait_for, 600
|
|
@@ -51,11 +74,27 @@ module Kitchen
|
|
|
51
74
|
required_config :image_id
|
|
52
75
|
required_config :public_key_path
|
|
53
76
|
|
|
77
|
+
# Sets up the driver and applies +wait_for+ as fog's global timeout.
|
|
78
|
+
#
|
|
79
|
+
# +Fog.timeout+ is process-wide, so the last driver constructed wins if
|
|
80
|
+
# several are in play.
|
|
81
|
+
#
|
|
82
|
+
# @param config [Hash] the driver configuration
|
|
54
83
|
def initialize(config)
|
|
55
84
|
super
|
|
56
85
|
Fog.timeout = config[:wait_for].to_i
|
|
57
86
|
end
|
|
58
87
|
|
|
88
|
+
# Creates the Cloud Server and waits until it can be logged into.
|
|
89
|
+
#
|
|
90
|
+
# Waits for the server to report ready, then optionally for RackConnect
|
|
91
|
+
# and Managed Service Level automation, then for the transport to accept
|
|
92
|
+
# a connection.
|
|
93
|
+
#
|
|
94
|
+
# @param state [Hash] mutable instance state; gains +server_id+,
|
|
95
|
+
# +hostname+, +username+, and +port+
|
|
96
|
+
# @return [void]
|
|
97
|
+
# @raise [Kitchen::ActionFailed] if the Rackspace API rejects the build
|
|
59
98
|
def create(state)
|
|
60
99
|
server = create_server
|
|
61
100
|
state[:server_id] = server.id
|
|
@@ -65,11 +104,18 @@ module Kitchen
|
|
|
65
104
|
rackconnect_check(server) if config[:rackconnect_wait]
|
|
66
105
|
servicelevel_check(server) if config[:servicelevel_wait]
|
|
67
106
|
state[:hostname] = hostname(server)
|
|
107
|
+
state[:username] = config[:username]
|
|
108
|
+
state[:port] = config[:port] if config[:port]
|
|
68
109
|
tcp_check(state)
|
|
69
110
|
rescue Fog::Errors::Error, Excon::Errors::Error => ex
|
|
70
111
|
raise ActionFailed, ex.message
|
|
71
112
|
end
|
|
72
113
|
|
|
114
|
+
# Destroys the Cloud Server, if one was created.
|
|
115
|
+
#
|
|
116
|
+
# @param state [Hash] mutable instance state; +server_id+ and +hostname+
|
|
117
|
+
# are removed
|
|
118
|
+
# @return [void]
|
|
73
119
|
def destroy(state)
|
|
74
120
|
return if state[:server_id].nil?
|
|
75
121
|
|
|
@@ -80,18 +126,24 @@ module Kitchen
|
|
|
80
126
|
state.delete(:hostname)
|
|
81
127
|
end
|
|
82
128
|
|
|
129
|
+
# Looks up the base image for the platform under test.
|
|
130
|
+
#
|
|
131
|
+
# @return [String, nil] the Rackspace image ID, or nil when the platform
|
|
132
|
+
# is not one the bundled image list knows about, in which case
|
|
133
|
+
# +image_id+ must be set explicitly
|
|
83
134
|
def default_image
|
|
84
135
|
images[instance.platform.name]
|
|
85
136
|
end
|
|
86
137
|
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
138
|
+
# Generates a server name that is unique per run and fits Rackspace's
|
|
139
|
+
# 63-character limit.
|
|
140
|
+
#
|
|
141
|
+
# The budget is spent as base name 15, username 15, hostname 23, random
|
|
142
|
+
# suffix 7, and three separators, for 63 exactly. Each part is stripped
|
|
143
|
+
# of non-word characters and truncated to its share, so a long login or
|
|
144
|
+
# hostname cannot push the result over.
|
|
145
|
+
#
|
|
146
|
+
# @return [String] e.g. +default-alice-buildbox01-x7f2p9q+
|
|
95
147
|
def default_name
|
|
96
148
|
[
|
|
97
149
|
instance.name.gsub(/\W/, "")[0..14],
|
|
@@ -101,8 +153,85 @@ module Kitchen
|
|
|
101
153
|
].join("-")
|
|
102
154
|
end
|
|
103
155
|
|
|
156
|
+
# Reports what Rackspace currently thinks of the server.
|
|
157
|
+
#
|
|
158
|
+
# @param state [Hash] instance state naming the server
|
|
159
|
+
# @return [Hash] a Test Kitchen status hash, or the base implementation's
|
|
160
|
+
# answer when there is no server or Rackspace does not know it
|
|
161
|
+
def status(state)
|
|
162
|
+
return super unless state[:server_id]
|
|
163
|
+
|
|
164
|
+
server = lookup_server(state[:server_id])
|
|
165
|
+
return super unless server
|
|
166
|
+
|
|
167
|
+
{
|
|
168
|
+
live: LIVE_STATES.include?(server.state),
|
|
169
|
+
state: server.state,
|
|
170
|
+
source: "driver",
|
|
171
|
+
resource_id: state[:server_id],
|
|
172
|
+
message: "Rackspace reports the server as #{server.state}",
|
|
173
|
+
checked_at: Time.now.utc.iso8601,
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Checks the configuration for the mistakes that only show up as a
|
|
178
|
+
# confusing failure part way through +create+.
|
|
179
|
+
#
|
|
180
|
+
# @param state [Hash] instance state; accepted for the Test Kitchen hook
|
|
181
|
+
# signature and not read
|
|
182
|
+
# @return [Boolean] true when a problem was reported
|
|
183
|
+
def doctor(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
184
|
+
problems = []
|
|
185
|
+
|
|
186
|
+
if config[:image_id].nil?
|
|
187
|
+
problems << "No image_id is set and #{instance.platform.name} is not " \
|
|
188
|
+
"in the bundled image list. Set image_id explicitly."
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
if config[:public_key_path].nil?
|
|
192
|
+
problems << "No public key was found in ~/.ssh. Set public_key_path " \
|
|
193
|
+
"to the key Rackspace should install on the server."
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
problems.concat(credential_problems)
|
|
197
|
+
|
|
198
|
+
problems.each { |problem| warn(problem) }
|
|
199
|
+
!problems.empty?
|
|
200
|
+
end
|
|
201
|
+
|
|
104
202
|
private
|
|
105
203
|
|
|
204
|
+
# Confirms the configured credentials can actually talk to Rackspace,
|
|
205
|
+
# which is cheaper to learn here than half way through a converge.
|
|
206
|
+
#
|
|
207
|
+
# Listing servers is the cheapest authenticated call fog-rackspace
|
|
208
|
+
# offers: +GET /servers+ returns only an ID and a name per server, and
|
|
209
|
+
# it exercises the compute endpoint for the configured region rather
|
|
210
|
+
# than just the identity endpoint.
|
|
211
|
+
#
|
|
212
|
+
# @return [Array<String>] a problem description, or an empty array
|
|
213
|
+
def credential_problems
|
|
214
|
+
compute.servers.all
|
|
215
|
+
[]
|
|
216
|
+
rescue Fog::Errors::Error, Excon::Errors::Error => e
|
|
217
|
+
["Rackspace rejected the configured credentials for region " \
|
|
218
|
+
"#{config[:rackspace_region]}: #{e.message}"]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Looks a server up without turning a missing one into a failure.
|
|
222
|
+
#
|
|
223
|
+
# @param server_id [String] the Rackspace server ID
|
|
224
|
+
# @return [Fog::Compute::RackspaceV2::Server, nil] the server, or nil
|
|
225
|
+
# when Rackspace does not know it or cannot be reached
|
|
226
|
+
def lookup_server(server_id)
|
|
227
|
+
compute.servers.get(server_id)
|
|
228
|
+
rescue Fog::Errors::Error, Excon::Errors::Error
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Builds the fog compute connection from the configured credentials.
|
|
233
|
+
#
|
|
234
|
+
# @return [Fog::Compute] a Rackspace compute connection
|
|
106
235
|
def compute
|
|
107
236
|
server_def = { provider: "Rackspace" }
|
|
108
237
|
opts = %i{version rackspace_username rackspace_api_key
|
|
@@ -113,17 +242,33 @@ module Kitchen
|
|
|
113
242
|
Fog::Compute.new(server_def)
|
|
114
243
|
end
|
|
115
244
|
|
|
245
|
+
# Bootstraps the Cloud Server from the configured options.
|
|
246
|
+
#
|
|
247
|
+
# RackConnect and Managed Service Level both need the root password left
|
|
248
|
+
# unlocked to do their work, so +no_passwd_lock+ is forced on whenever
|
|
249
|
+
# either wait is requested, regardless of how it was configured.
|
|
250
|
+
#
|
|
251
|
+
# @note fog's +bootstrap+ calls +Server#setup+, which rescues
|
|
252
|
+
# +Errno::ECONNREFUSED+ and retries forever, one second apart. A server
|
|
253
|
+
# that never opens port 22 hangs here rather than failing, and
|
|
254
|
+
# +wait_for+ does not bound it.
|
|
255
|
+
#
|
|
256
|
+
# @return [Fog::Compute::RackspaceV2::Server] the newly built server
|
|
116
257
|
def create_server
|
|
117
258
|
server_def = { name: config[:server_name], networks: }
|
|
118
259
|
%i{image_id flavor_id public_key_path no_passwd_lock user_data config_drive}.each do |opt|
|
|
119
260
|
server_def[opt] = config[opt]
|
|
120
261
|
end
|
|
121
|
-
#
|
|
262
|
+
# RackConnect and Managed Service Level need the root password left
|
|
263
|
+
# unlocked; see the note above.
|
|
122
264
|
no_passwd_lock = config[:rackconnect_wait] || config[:servicelevel_wait]
|
|
123
265
|
server_def[:no_passwd_lock] = no_passwd_lock if no_passwd_lock
|
|
124
266
|
compute.servers.bootstrap(server_def)
|
|
125
267
|
end
|
|
126
268
|
|
|
269
|
+
# The bundled platform-name to image-ID map.
|
|
270
|
+
#
|
|
271
|
+
# @return [Hash{String => String}] parsed from +data/images.json+
|
|
127
272
|
def images
|
|
128
273
|
@images ||= begin
|
|
129
274
|
json_file = File.expand_path("../../../data/images.json", __dir__)
|
|
@@ -131,14 +276,41 @@ module Kitchen
|
|
|
131
276
|
end
|
|
132
277
|
end
|
|
133
278
|
|
|
279
|
+
# Waits until the server will accept a login.
|
|
280
|
+
#
|
|
281
|
+
# The TCP check does not honour +ssh_config+, which some setups need, so
|
|
282
|
+
# +no_ssh_tcp_check+ swaps the check for a fixed sleep of
|
|
283
|
+
# +no_ssh_tcp_check_sleep+ seconds instead.
|
|
284
|
+
#
|
|
285
|
+
# @param state [Hash] instance state describing how to connect
|
|
286
|
+
# @return [void]
|
|
134
287
|
def tcp_check(state)
|
|
135
288
|
# allow driver config to bypass SSH tcp check -- because
|
|
136
289
|
# it doesn't respect ssh_config values that might be required
|
|
137
|
-
wait_for_sshd(state
|
|
290
|
+
wait_for_sshd(state) unless config[:no_ssh_tcp_check]
|
|
138
291
|
sleep(config[:no_ssh_tcp_check_sleep]) if config[:no_ssh_tcp_check]
|
|
139
292
|
puts "(ssh ready)"
|
|
140
293
|
end
|
|
141
294
|
|
|
295
|
+
# Blocks until the configured transport can connect to the instance.
|
|
296
|
+
#
|
|
297
|
+
# Kitchen::Driver::SSHBase used to supply this; the configured transport
|
|
298
|
+
# knows how to wait for the instance to accept connections.
|
|
299
|
+
#
|
|
300
|
+
# @param state [Hash] instance state describing how to connect
|
|
301
|
+
# @return [void]
|
|
302
|
+
def wait_for_sshd(state)
|
|
303
|
+
instance.transport.connection(state, &:wait_until_ready)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Waits for RackConnect automation to finish.
|
|
307
|
+
#
|
|
308
|
+
# The server is refreshed afterwards, because RackConnect assigns a new
|
|
309
|
+
# public address as part of its work and the stale one would otherwise
|
|
310
|
+
# be handed to the transport.
|
|
311
|
+
#
|
|
312
|
+
# @param server [Fog::Compute::RackspaceV2::Server] the server to poll
|
|
313
|
+
# @return [void]
|
|
142
314
|
def rackconnect_check(server)
|
|
143
315
|
server.wait_for \
|
|
144
316
|
{ metadata.all["rackconnect_automation_status"] == "DEPLOYED" }
|
|
@@ -146,12 +318,21 @@ module Kitchen
|
|
|
146
318
|
server.update # refresh accessIPv4 with new IP
|
|
147
319
|
end
|
|
148
320
|
|
|
321
|
+
# Waits for Managed Service Level automation to finish.
|
|
322
|
+
#
|
|
323
|
+
# @param server [Fog::Compute::RackspaceV2::Server] the server to poll
|
|
324
|
+
# @return [void]
|
|
149
325
|
def servicelevel_check(server)
|
|
150
326
|
server.wait_for \
|
|
151
327
|
{ metadata.all["rax_service_level_automation"] == "Complete" }
|
|
152
328
|
puts "(service level automation complete)"
|
|
153
329
|
end
|
|
154
330
|
|
|
331
|
+
# Picks the address Test Kitchen should connect to.
|
|
332
|
+
#
|
|
333
|
+
# @param server [Fog::Compute::RackspaceV2::Server] the built server
|
|
334
|
+
# @return [String] the private address when +servicenet+ is set,
|
|
335
|
+
# otherwise the public one
|
|
155
336
|
def hostname(server)
|
|
156
337
|
if config[:servicenet] == false
|
|
157
338
|
server.public_ip_address
|
|
@@ -160,6 +341,14 @@ module Kitchen
|
|
|
160
341
|
end
|
|
161
342
|
end
|
|
162
343
|
|
|
344
|
+
# Builds the network list for the new server.
|
|
345
|
+
#
|
|
346
|
+
# Rackspace's PublicNet and ServiceNet have fixed, well-known IDs. Any
|
|
347
|
+
# configured networks are added to those two rather than replacing them,
|
|
348
|
+
# since dropping PublicNet would leave the server unreachable.
|
|
349
|
+
#
|
|
350
|
+
# @return [Array<String>, nil] network IDs, or nil to let Rackspace
|
|
351
|
+
# apply its own defaults
|
|
163
352
|
def networks
|
|
164
353
|
base_nets = %w{
|
|
165
354
|
00000000-0000-0000-0000-000000000000
|