kitchen-openstack 7.0.0 → 8.0.0
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/README.md +477 -81
- data/lib/kitchen/driver/openstack/clouds.rb +139 -26
- data/lib/kitchen/driver/openstack/config.rb +97 -30
- data/lib/kitchen/driver/openstack/helpers.rb +44 -5
- data/lib/kitchen/driver/openstack/networking.rb +127 -32
- data/lib/kitchen/driver/openstack/server_helper.rb +92 -20
- data/lib/kitchen/driver/openstack/volume.rb +121 -25
- data/lib/kitchen/driver/openstack.rb +146 -17
- data/lib/kitchen/driver/openstack_version.rb +7 -1
- metadata +2 -2
|
@@ -34,8 +34,38 @@ require_relative "openstack/volume"
|
|
|
34
34
|
|
|
35
35
|
module Kitchen
|
|
36
36
|
module Driver
|
|
37
|
-
#
|
|
37
|
+
# Test Kitchen driver for OpenStack Nova.
|
|
38
|
+
#
|
|
39
|
+
# Creates and destroys Nova instances, optionally attaching floating IPs,
|
|
40
|
+
# Cinder volumes and specific Neutron networks. Credentials come from
|
|
41
|
+
# kitchen.yml, `OS_*` environment variables, or a standard
|
|
42
|
+
# `clouds.yaml` -- see {Clouds} for the precedence rules.
|
|
38
43
|
class Openstack < Kitchen::Driver::Base
|
|
44
|
+
# Settings Fog requires as Strings. Fog re-coerces anything that looks
|
|
45
|
+
# numeric back to an Integer, so these are stringified on the way in.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Symbol>]
|
|
48
|
+
FOG_STRING_SETTINGS = %i{
|
|
49
|
+
openstack_username
|
|
50
|
+
openstack_api_key
|
|
51
|
+
openstack_auth_url
|
|
52
|
+
openstack_project_name
|
|
53
|
+
openstack_project_id
|
|
54
|
+
openstack_user_domain
|
|
55
|
+
openstack_user_domain_id
|
|
56
|
+
openstack_project_domain
|
|
57
|
+
openstack_project_domain_id
|
|
58
|
+
openstack_domain_id
|
|
59
|
+
openstack_domain_name
|
|
60
|
+
openstack_region
|
|
61
|
+
openstack_endpoint_type
|
|
62
|
+
openstack_identity_api_version
|
|
63
|
+
openstack_application_credential_id
|
|
64
|
+
openstack_application_credential_secret
|
|
65
|
+
openstack_tenant
|
|
66
|
+
openstack_tenant_id
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
39
69
|
include Clouds
|
|
40
70
|
include Config
|
|
41
71
|
include Helpers
|
|
@@ -49,13 +79,6 @@ module Kitchen
|
|
|
49
79
|
default_config :clouds_yaml_path, nil
|
|
50
80
|
default_config :server_name, nil
|
|
51
81
|
|
|
52
|
-
# Merge clouds.yaml values into config so they are visible in
|
|
53
|
-
# `kitchen diagnose` and available to all driver methods.
|
|
54
|
-
def finalize_config!(instance)
|
|
55
|
-
super
|
|
56
|
-
apply_clouds_config
|
|
57
|
-
self
|
|
58
|
-
end
|
|
59
82
|
default_config :server_name_prefix, nil
|
|
60
83
|
default_config :key_name, nil
|
|
61
84
|
default_config :port, "22"
|
|
@@ -82,6 +105,28 @@ module Kitchen
|
|
|
82
105
|
default_config :write_timeout, 60
|
|
83
106
|
default_config :metadata, nil
|
|
84
107
|
|
|
108
|
+
# Merges clouds.yaml and `OS_*` values into the config hash.
|
|
109
|
+
#
|
|
110
|
+
# Done at finalize time rather than lazily so the resolved values show up
|
|
111
|
+
# in `kitchen diagnose` and are available to every driver method.
|
|
112
|
+
#
|
|
113
|
+
# @param instance [Kitchen::Instance] the instance this driver serves
|
|
114
|
+
# @return [self]
|
|
115
|
+
def finalize_config!(instance)
|
|
116
|
+
super
|
|
117
|
+
apply_clouds_config
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Creates a Nova instance and waits until it is reachable.
|
|
122
|
+
#
|
|
123
|
+
# Idempotent: returns immediately if `state` already names a server.
|
|
124
|
+
#
|
|
125
|
+
# @param state [Hash] mutable instance state; gains `:server_id` and
|
|
126
|
+
# `:hostname`
|
|
127
|
+
# @return [void]
|
|
128
|
+
# @raise [Kitchen::ActionFailed] on any Fog or Excon failure
|
|
129
|
+
# @raise [Kitchen::InstanceFailure] if the server builds to ERROR state
|
|
85
130
|
def create(state)
|
|
86
131
|
config_server_name
|
|
87
132
|
if state[:server_id]
|
|
@@ -117,6 +162,14 @@ module Kitchen
|
|
|
117
162
|
raise ActionFailed, e.message
|
|
118
163
|
end
|
|
119
164
|
|
|
165
|
+
# Destroys the Nova instance named by `state`, releasing its floating IP
|
|
166
|
+
# first when this driver allocated one.
|
|
167
|
+
#
|
|
168
|
+
# Safe to call when the server is already gone.
|
|
169
|
+
#
|
|
170
|
+
# @param state [Hash] mutable instance state; loses `:server_id` and
|
|
171
|
+
# `:hostname`
|
|
172
|
+
# @return [void]
|
|
120
173
|
def destroy(state)
|
|
121
174
|
return if state[:server_id].nil?
|
|
122
175
|
|
|
@@ -129,12 +182,7 @@ module Kitchen
|
|
|
129
182
|
pub, priv = get_public_private_ips(server)
|
|
130
183
|
pub, = parse_ips(pub, priv)
|
|
131
184
|
pub_ip = pub[config[:public_ip_order].to_i] || nil
|
|
132
|
-
if pub_ip
|
|
133
|
-
info "Retrieve the ID of floating IP <#{pub_ip}>"
|
|
134
|
-
floating_ip_id = network.list_floating_ips(floating_ip_address: pub_ip).body["floatingips"][0]["id"]
|
|
135
|
-
network.delete_floating_ip(floating_ip_id)
|
|
136
|
-
info "OpenStack Floating IP <#{pub_ip}> released."
|
|
137
|
-
end
|
|
185
|
+
release_floating_ip(pub_ip) if pub_ip
|
|
138
186
|
end
|
|
139
187
|
server.destroy
|
|
140
188
|
end
|
|
@@ -145,45 +193,126 @@ module Kitchen
|
|
|
145
193
|
|
|
146
194
|
private
|
|
147
195
|
|
|
196
|
+
# Releases a floating IP back to its pool.
|
|
197
|
+
#
|
|
198
|
+
# A floating IP that Neutron no longer knows about is not an error worth
|
|
199
|
+
# failing a destroy over, so an unknown address is logged and skipped.
|
|
200
|
+
#
|
|
201
|
+
# @param pub_ip [String] the floating IP to release
|
|
202
|
+
# @return [void]
|
|
203
|
+
def release_floating_ip(pub_ip)
|
|
204
|
+
info "Retrieve the ID of floating IP <#{pub_ip}>"
|
|
205
|
+
net = network
|
|
206
|
+
floating_ips = net.list_floating_ips(floating_ip_address: pub_ip).body["floatingips"]
|
|
207
|
+
if floating_ips.nil? || floating_ips.empty?
|
|
208
|
+
warn "No floating IP found matching <#{pub_ip}>; nothing to release."
|
|
209
|
+
return
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
net.delete_floating_ip(floating_ips[0]["id"])
|
|
213
|
+
info "OpenStack Floating IP <#{pub_ip}> released."
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Builds the settings hash handed to every Fog service constructor.
|
|
217
|
+
#
|
|
218
|
+
# @return [Hash] Fog connection settings
|
|
148
219
|
def openstack_server
|
|
149
220
|
server_def = {
|
|
150
221
|
connection_options: {},
|
|
151
222
|
}
|
|
152
|
-
required_server_settings.each { |s| server_def[s] = config[s] }
|
|
153
|
-
optional_server_settings.each { |s| server_def[s] = config[s] if config[s] }
|
|
223
|
+
required_server_settings.each { |s| server_def[s] = normalize_fog_setting(s, config[s]) }
|
|
224
|
+
optional_server_settings.each { |s| server_def[s] = normalize_fog_setting(s, config[s]) if config[s] }
|
|
154
225
|
connection_options.each { |s| server_def[:connection_options][s] = config[s] if config[s] }
|
|
155
226
|
server_def
|
|
156
227
|
end
|
|
157
228
|
|
|
229
|
+
# Settings always sent to Fog, even when nil.
|
|
230
|
+
#
|
|
231
|
+
# @return [Array<Symbol>]
|
|
158
232
|
def required_server_settings
|
|
159
233
|
%i{openstack_username openstack_api_key openstack_auth_url openstack_domain_id}
|
|
160
234
|
end
|
|
161
235
|
|
|
236
|
+
# Every other `openstack_*` setting Fog recognizes, sent only when set.
|
|
237
|
+
#
|
|
238
|
+
# @return [Array<Symbol>]
|
|
162
239
|
def optional_server_settings
|
|
163
240
|
Fog::OpenStack::Compute.recognized.select do |k|
|
|
164
241
|
k.to_s.start_with?("openstack")
|
|
165
242
|
end - required_server_settings
|
|
166
243
|
end
|
|
167
244
|
|
|
245
|
+
# Settings passed through to Excon rather than to Fog itself.
|
|
246
|
+
#
|
|
247
|
+
# `ssl_ca_file` belongs here, not in the Fog settings: Fog does not
|
|
248
|
+
# recognize it, so a CA bundle from `OS_CACERT` or a clouds.yaml
|
|
249
|
+
# `cacert` entry would otherwise be parsed and then silently dropped.
|
|
250
|
+
#
|
|
251
|
+
# @return [Array<Symbol>]
|
|
168
252
|
def connection_options
|
|
169
|
-
%i{read_timeout write_timeout connect_timeout}
|
|
253
|
+
%i{read_timeout write_timeout connect_timeout ssl_ca_file}
|
|
170
254
|
end
|
|
171
255
|
|
|
256
|
+
# @return [Fog::OpenStack::Network] a Neutron connection
|
|
172
257
|
def network
|
|
173
258
|
Fog::OpenStack::Network.new(openstack_server)
|
|
174
259
|
end
|
|
175
260
|
|
|
261
|
+
# @return [Fog::OpenStack::Compute] a Nova connection
|
|
176
262
|
def compute
|
|
177
263
|
Fog::OpenStack::Compute.new(openstack_server)
|
|
178
264
|
end
|
|
179
265
|
|
|
266
|
+
# @return [Kitchen::Driver::Openstack::Volume] a Cinder helper
|
|
180
267
|
def volume
|
|
181
268
|
Volume.new(logger)
|
|
182
269
|
end
|
|
183
270
|
|
|
271
|
+
# Resolves the block device mapping to send to Nova.
|
|
272
|
+
#
|
|
273
|
+
# @param config [Hash] the driver config
|
|
274
|
+
# @return [Hash] a Nova block device mapping
|
|
184
275
|
def get_bdm(config)
|
|
185
276
|
volume.get_bdm(config, openstack_server)
|
|
186
277
|
end
|
|
278
|
+
|
|
279
|
+
# Coerces one setting to the type Fog expects.
|
|
280
|
+
#
|
|
281
|
+
# @param setting [Symbol] the Fog config key
|
|
282
|
+
# @param value [Object] the configured value
|
|
283
|
+
# @return [Object] the coerced value
|
|
284
|
+
def normalize_fog_setting(setting, value)
|
|
285
|
+
return value if value.nil?
|
|
286
|
+
return normalize_identity_api_version(value) if setting == :openstack_identity_api_version
|
|
287
|
+
return value unless FOG_STRING_SETTINGS.include?(setting)
|
|
288
|
+
|
|
289
|
+
value.to_s
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Normalizes the identity API version into a form Fog will not mangle.
|
|
293
|
+
#
|
|
294
|
+
# Fog::Service#coerce_options re-coerces any value where
|
|
295
|
+
# `value.to_s.to_i.to_s == value.to_s` back to an Integer, which
|
|
296
|
+
# then breaks Fog::OpenStack::Auth::Token.build (it calls `=~`
|
|
297
|
+
# on the value). Prefixing with "v" keeps Fog from coercing and
|
|
298
|
+
# still satisfies Token.build's `/(v)*2(\.0)*/i` regex check.
|
|
299
|
+
#
|
|
300
|
+
# @example
|
|
301
|
+
# normalize_identity_api_version(3) #=> "v3"
|
|
302
|
+
# normalize_identity_api_version("2.0") #=> "v2.0"
|
|
303
|
+
#
|
|
304
|
+
# @param value [String, Integer] the configured identity API version
|
|
305
|
+
# @return [String] a version string prefixed with "v"
|
|
306
|
+
def normalize_identity_api_version(value)
|
|
307
|
+
str = value.to_s.strip
|
|
308
|
+
return str if str.empty?
|
|
309
|
+
return str if str.start_with?("v", "V")
|
|
310
|
+
|
|
311
|
+
case str
|
|
312
|
+
when "2", "2.0" then "v2.0"
|
|
313
|
+
else "v#{str}"
|
|
314
|
+
end
|
|
315
|
+
end
|
|
187
316
|
end
|
|
188
317
|
end
|
|
189
318
|
end
|
|
@@ -23,6 +23,12 @@ module Kitchen
|
|
|
23
23
|
#
|
|
24
24
|
# @author Jonathan Hartman <j@p4nt5.com>
|
|
25
25
|
module Driver
|
|
26
|
-
|
|
26
|
+
# The kitchen-openstack gem version.
|
|
27
|
+
#
|
|
28
|
+
# Read by the gemspec and bumped by Release Please, so it must stay a
|
|
29
|
+
# plain string literal on a single line.
|
|
30
|
+
#
|
|
31
|
+
# @return [String]
|
|
32
|
+
OPENSTACK_VERSION = "8.0.0"
|
|
27
33
|
end
|
|
28
34
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-openstack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 8.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan Hartman
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-
|
|
12
|
+
date: 2026-08-23 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: test-kitchen
|