Skip to content

Commit fa441d5

Browse files
authored
Merge pull request #1 from tecracer-theinen/theinen/queueing
Implementation of script-based host queueing
2 parents 03e45be + 5bffc8d commit fa441d5

File tree

10 files changed

+326
-20
lines changed

10 files changed

+326
-20
lines changed

.rubocop.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
# Offense count: 36
2-
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
3-
# URISchemes: http, https
1+
AllCops:
2+
TargetRubyVersion: 2.6
3+
Exclude:
4+
- Guardfile
5+
- Rakefile
6+
47
Metrics/LineLength:
58
Max: 147
9+

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ end
99
group :debug do
1010
gem "pry"
1111
gem "guard"
12-
gem "guard-shell"
12+
gem "guard-rake"
1313
end
1414

1515
# If you want to load debugging tools into the bundle exec sandbox,

Guardfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
notification :terminal_title, display_message: true
22

3-
guard :shell do
4-
watch(/\.rb$/) { `rake install:local` }
3+
guard 'rake', task: 'install:local' do
4+
watch(/\.rb$/)
55
end

README.md

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ This is a Test Kitchen driver for use in cases, where you have an
44
existing machine, such as a physical server which you want
55
to use for your tests.
66

7-
The static driver is directly derived from TK's "proxy" driver,
8-
which is relying on legacy plugin infrastructure - making it directly incompatible
9-
with Windows platforms.
7+
The static driver is directly derived from TK's "proxy" driver,
8+
which is relying on legacy plugin infrastructure - making it directly
9+
incompatible with Windows platforms.
1010

1111
## Usage
1212

@@ -19,19 +19,88 @@ driver:
1919
# now the rest of your kitchen.yml follows
2020
```
2121

22-
The `host` configuration setting, which specifies the hostname/IP you want tests to run against.
22+
The `host` configuration setting, which specifies the hostname/IP you want tests
23+
to run against.
2324

24-
If you have more than one server, for example when testing specific
25-
hardware drivers, just add a suite for each and override the
26-
`host` value in its section
25+
If you have more than one server, for example when testing specific hardware
26+
drivers, just add a suite for each and override the `host` value in its
27+
section
2728

2829
## Supported Platforms
2930

30-
As this is a pure driver which does not interact with the
31-
instances/VMs, it supports all platforms. Specifically Linux
32-
and Windows work.
31+
As this is a pure driver which does not interact with the instances/VMs, it
32+
supports all platforms. Specifically Linux and Windows are known to work.
3333

34-
## <a name="license"></a> License
34+
## Queueing Feature
35+
36+
As physical machines are a limited resource and are rarely bought or thrown
37+
away in a TestKitchen context, some sort of queueing mechanism is needed in
38+
bigger environments.
39+
40+
To enable this feature, set `queueing` to `true` (default: `false`)
41+
42+
```yaml
43+
driver:
44+
name: static
45+
queueing: true
46+
request:
47+
execute: /usr/local/bin/get-host.sh
48+
release:
49+
execute: /usr/local/bin/release-host.sh $STATIC_HOSTNAME
50+
...
51+
```
52+
53+
Queueing knows two Actions:
54+
55+
* `request` to obtain the hostname or IP of the machine to use
56+
* `release` to return this host into the pool
57+
58+
If you are using non-ephemeral test systems, like physical machines, you will
59+
need to trigger some procedure to reset them back to the defined default. Otherwise,
60+
every test will modify the system further until results get unpredictable.
61+
62+
There currently is just one handler for queueing scenarios:
63+
64+
* the `script` handler, which executes a local script
65+
66+
## Driver Options
67+
68+
| Name | Default | Description |
69+
| ------------------- | --------- | --------------------------------------------- |
70+
| `queueing` | false | If to invoke external actions to get hostname |
71+
| `queueing_timeout` | 3600 | Timeout for queueing operations in seconds. |
72+
| `queueing_handlers` | - | Glob to load external queueing handlers |
73+
74+
## Queueing Handler `static`
75+
76+
This handler only executes local commands. These could query remote databases or
77+
even issue more complex programs to obtain/release machines.
78+
79+
### Parameters for `request`
80+
81+
| Name | Default | Description |
82+
| ---------------- | --------- | ---------------------------------------------------------- |
83+
| `type` | `script` | |
84+
| `execute` | - | Command to execute |
85+
| `match_hostname` | `^(.*)$` | Regex to specify what to grab from output. Default: All |
86+
| `match_banner` | - | Regex to specify optional banner to grab. Default: Nothing |
87+
88+
If a banner is grabbed, it's contents are displaed after the message reporting the
89+
hostname. This field can be used for warnings or additional information like access
90+
to management interfaces (ILO, BMC, ...).
91+
92+
### Parameters for `release`
93+
94+
| Name | Default | Description |
95+
| ---------- | --------- | ------------------------------------------------------- |
96+
| `type` | `script` | |
97+
| `execute` | - | Command to execute |
98+
99+
The executed script gets the following environment variables:
100+
101+
* `STATIC_HOSTNAME`: Hostname or IP of the host to be released
102+
103+
## License
35104

36105
Apache 2.0 (see [LICENSE][license])
37106

Rakefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,14 @@ YARD::Rake::YardocTask.new do |t|
1919
end
2020

2121
task default: [:style]
22+
23+
require "guard"
24+
require "guard/commander"
25+
26+
desc "Watch for source changes and redeploy Gem"
27+
task :guard do
28+
Guard.start({ no_interactions: true })
29+
while ::Guard.running do
30+
sleep 0.5
31+
end
32+
end

kitchen-static.gemspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ Gem::Specification.new do |spec|
1919
spec.required_ruby_version = ">= 2.3"
2020

2121
spec.add_dependency "test-kitchen", ">= 1.16", "< 3.0"
22+
spec.add_dependency "mixlib-shellout", "~> 3.0"
2223

2324
spec.add_development_dependency "bundler", ">= 1.16"
25+
spec.add_development_dependency "guard", "~> 2.16"
26+
spec.add_development_dependency "guard-rake", "~> 1.0"
2427
spec.add_development_dependency "rake", "~> 12.0"
2528
spec.add_development_dependency "yard", "~> 0.9"
2629
end
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
module Kitchen::Driver
2+
class Static
3+
module Queueing
4+
class Base
5+
@options = {}
6+
@request_options = {}
7+
@release_options = {}
8+
9+
@hostname = nil
10+
@banner = nil
11+
12+
@env_vars = {}
13+
14+
attr_reader :options, :request_options, :release_options, :env_vars, :banner
15+
16+
def initialize(options)
17+
@options = {
18+
queueing_timeout: 3600,
19+
}
20+
21+
@request_options = {}
22+
@release_options = {}
23+
24+
setup(options)
25+
26+
process_kitchen_options(options)
27+
end
28+
29+
def request(state)
30+
handle_request(state)
31+
end
32+
33+
def release(state)
34+
@env_vars = {
35+
STATIC_HOSTNAME: state[:hostname],
36+
}
37+
38+
handle_release(state)
39+
end
40+
41+
def banner?
42+
! @banner.nil?
43+
end
44+
45+
def self.descendants
46+
ObjectSpace.each_object(Class).select { |klass| klass < self }
47+
end
48+
49+
private
50+
51+
def setup(_options)
52+
# Add setup and defaults in specific handler
53+
end
54+
55+
def handle_request(_state)
56+
raise "Implement request handler"
57+
end
58+
59+
def handle_release(_state)
60+
raise "Implement release handler"
61+
end
62+
63+
def default_request_options(options = {})
64+
@request_options.merge!(options)
65+
end
66+
67+
def default_release_options(options = {})
68+
@release_options.merge!(options)
69+
end
70+
71+
def process_kitchen_options(kitchen_options)
72+
@options = kitchen_options
73+
74+
@request_options.merge!(options[:request])
75+
@options.delete(:request)
76+
77+
@release_options.merge!(options[:release])
78+
@options.delete(:release)
79+
end
80+
81+
def timeout
82+
options[:queueing_timeout]
83+
end
84+
end
85+
end
86+
end
87+
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require "mixlib/shellout"
2+
3+
require_relative "base.rb"
4+
5+
module Kitchen::Driver
6+
class Static
7+
module Queueing
8+
class Script < Base
9+
def setup(_kitchen_options)
10+
default_request_options({
11+
match_hostname: "^(.*)$",
12+
match_banner: nil,
13+
})
14+
15+
default_release_options({})
16+
end
17+
18+
def handle_request(_state)
19+
stdout = execute(request_options[:execute])
20+
21+
matched = stdout.match(request_options[:match_hostname])
22+
raise format("Could not extract hostname from '%s' with regular expression /%s/", stdout, request_options[:match_hostname]) unless matched
23+
24+
# Allow additional feedback from command
25+
@banner = stdout.match(request_options[:match_banner])&.captures&.first if request_options[:match_banner]
26+
27+
matched.captures.first
28+
end
29+
30+
def handle_release(_state)
31+
execute(release_options[:execute])
32+
end
33+
34+
private
35+
36+
def execute(command)
37+
raise format("Received empty command") if command.nil? || command.empty?
38+
39+
cmd = Mixlib::ShellOut.new(command, environment: env_vars, timeout: timeout)
40+
cmd.run_command
41+
42+
raise format("Error executing `%s`: %s", command, cmd.stderr) if cmd.status != 0
43+
44+
cmd.stdout.strip
45+
end
46+
end
47+
end
48+
end
49+
end

0 commit comments

Comments
 (0)