beaker-hostgenerator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/.simplecov +9 -0
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +54 -0
- data/Gemfile +27 -0
- data/HISTORY.md +211 -0
- data/LICENSE +202 -0
- data/README.md +112 -0
- data/Rakefile +48 -0
- data/acceptance/config/nodes/vagrant-ubuntu-1404.yml +8 -0
- data/acceptance/tests/first.rb +8 -0
- data/beaker-hostgenerator.gemspec +44 -0
- data/bin/beaker-hostgenerator +8 -0
- data/bin/genconfig2 +10 -0
- data/lib/beaker-hostgenerator.rb +8 -0
- data/lib/beaker-hostgenerator/cli.rb +126 -0
- data/lib/beaker-hostgenerator/data.rb +55 -0
- data/lib/beaker-hostgenerator/data/vmpooler.rb +397 -0
- data/lib/beaker-hostgenerator/error.rb +6 -0
- data/lib/beaker-hostgenerator/generator.rb +143 -0
- data/lib/beaker-hostgenerator/generator/vmpooler.rb +57 -0
- data/lib/beaker-hostgenerator/roles.rb +22 -0
- data/lib/beaker-hostgenerator/util.rb +64 -0
- data/lib/beaker-hostgenerator/version.rb +5 -0
- data/spec/beaker-hostgenerator/generator_spec.rb +98 -0
- data/spec/helpers.rb +109 -0
- data/spec/spec_helper.rb +10 -0
- metadata +230 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'beaker-hostgenerator/util'
|
2
|
+
require 'beaker-hostgenerator/data'
|
3
|
+
require 'beaker-hostgenerator/error'
|
4
|
+
require 'beaker-hostgenerator/roles'
|
5
|
+
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module BeakerHostGenerator
|
9
|
+
class Generator
|
10
|
+
include BeakerHostGenerator::Data
|
11
|
+
include BeakerHostGenerator::Errors
|
12
|
+
include BeakerHostGenerator::Utils
|
13
|
+
|
14
|
+
BASE_HOST_CONFIG = {
|
15
|
+
'pe_dir' => BeakerHostGenerator::Utils.pe_dir(PE_VERSION, PE_FAMILY),
|
16
|
+
'pe_ver' => PE_VERSION,
|
17
|
+
'pe_upgrade_dir' => BeakerHostGenerator::Utils.pe_dir(PE_UPGRADE_VERSION, PE_UPGRADE_FAMILY),
|
18
|
+
'pe_upgrade_ver' => PE_UPGRADE_VERSION,
|
19
|
+
}
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
def initialize options
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create( options )
|
27
|
+
hypervisor_type = options[:hypervisor]
|
28
|
+
|
29
|
+
hclass = case hypervisor_type
|
30
|
+
when /vmpooler/
|
31
|
+
BeakerHostGenerator::Vmpooler
|
32
|
+
when /vagrant/
|
33
|
+
BeakerHostGenerator::Vagrant
|
34
|
+
else
|
35
|
+
raise "Invalid hypervisor #{type}"
|
36
|
+
end
|
37
|
+
|
38
|
+
return hclass.new(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate tokens
|
42
|
+
nodeid = Hash.new(1)
|
43
|
+
ostype = nil
|
44
|
+
|
45
|
+
tokens.each do |token|
|
46
|
+
if is_ostype_token?(token)
|
47
|
+
if nodeid[ostype] == 1 and ostype != nil
|
48
|
+
raise "Error: no nodes generated for #{ostype}"
|
49
|
+
end
|
50
|
+
ostype = token
|
51
|
+
next
|
52
|
+
end
|
53
|
+
|
54
|
+
node_info = __parse_node_info_token(token)
|
55
|
+
|
56
|
+
node_info['ostype'] = ostype
|
57
|
+
node_info['nodeid'] = nodeid[ostype]
|
58
|
+
|
59
|
+
host_name, host_config = generate_node(node_info, BASE_HOST_CONFIG)
|
60
|
+
|
61
|
+
if PE_USE_WIN32 && ostype =~ /windows/ && node_info['bits'] == "64"
|
62
|
+
host_config['ruby_arch'] = 'x86'
|
63
|
+
host_config['install_32'] = true
|
64
|
+
end
|
65
|
+
|
66
|
+
if not @options[:disable_default_role]
|
67
|
+
host_config['roles'] = ['agent']
|
68
|
+
else
|
69
|
+
host_config['roles'] = []
|
70
|
+
end
|
71
|
+
|
72
|
+
host_config['roles'].concat __generate_host_roles(node_info)
|
73
|
+
host_config['roles'].uniq!
|
74
|
+
|
75
|
+
if not @options[:disable_role_config]
|
76
|
+
host_config['roles'].each do |role|
|
77
|
+
host_config.deep_merge! __get_role_config(role)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@config['HOSTS'][host_name] = host_config
|
82
|
+
nodeid[ostype] += 1
|
83
|
+
end
|
84
|
+
|
85
|
+
return @config.to_yaml
|
86
|
+
end
|
87
|
+
|
88
|
+
def __get_role_config role
|
89
|
+
begin
|
90
|
+
r = BeakerHostGenerator::Roles.new
|
91
|
+
m = r.method(role)
|
92
|
+
rescue NameError
|
93
|
+
return {}
|
94
|
+
end
|
95
|
+
|
96
|
+
return m.call
|
97
|
+
end
|
98
|
+
|
99
|
+
def __parse_node_info_token token
|
100
|
+
node_info = NODE_REGEX.match(token)
|
101
|
+
|
102
|
+
if node_info
|
103
|
+
node_info = Hash[ node_info.names.zip( node_info.captures ) ]
|
104
|
+
else
|
105
|
+
raise InvalidNodeSpecError.new, "Invalid node_info token: #{token}"
|
106
|
+
end
|
107
|
+
|
108
|
+
if node_info['arbitrary_roles']
|
109
|
+
node_info['arbitrary_roles'] = node_info['arbitrary_roles'].split(',') || ''
|
110
|
+
else
|
111
|
+
# Default to empty list to avoid having to check for nil elsewhere
|
112
|
+
node_info['arbitrary_roles'] = []
|
113
|
+
end
|
114
|
+
|
115
|
+
return node_info
|
116
|
+
end
|
117
|
+
|
118
|
+
def __generate_host_roles node_info
|
119
|
+
roles = []
|
120
|
+
|
121
|
+
node_info['roles'].each_char do |c|
|
122
|
+
roles << ROLES[c]
|
123
|
+
end
|
124
|
+
|
125
|
+
node_info['arbitrary_roles'].each do |role|
|
126
|
+
roles << role
|
127
|
+
end
|
128
|
+
|
129
|
+
return roles
|
130
|
+
end
|
131
|
+
|
132
|
+
def is_ostype_token?
|
133
|
+
raise "Method 'is_ostype_token?' not implemented!"
|
134
|
+
end
|
135
|
+
|
136
|
+
def generate_node
|
137
|
+
raise "Method 'generate_node' not implemented!"
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
require 'beaker-hostgenerator/generator/vmpooler'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'beaker-hostgenerator/generator'
|
2
|
+
require 'beaker-hostgenerator/data/vmpooler'
|
3
|
+
require 'beaker-hostgenerator/data'
|
4
|
+
require 'deep_merge'
|
5
|
+
|
6
|
+
module BeakerHostGenerator
|
7
|
+
class Vmpooler < BeakerHostGenerator::Generator
|
8
|
+
include BeakerHostGenerator::Data
|
9
|
+
include BeakerHostGenerator::Data::Vmpooler
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
@config = {}
|
13
|
+
@config.deep_merge! BASE_CONFIG
|
14
|
+
@config.deep_merge! VMPOOLER_CONFIG
|
15
|
+
super(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_node node_info, base_config
|
19
|
+
host_config = {}
|
20
|
+
host_config.deep_merge! base_config
|
21
|
+
|
22
|
+
# set hypervisor
|
23
|
+
host_config['hypervisor'] = 'vmpooler'
|
24
|
+
|
25
|
+
# generate node definition
|
26
|
+
ostype = node_info['ostype']
|
27
|
+
nodeid = node_info['nodeid']
|
28
|
+
bits = node_info['bits']
|
29
|
+
|
30
|
+
platform = "#{ostype}-#{bits}"
|
31
|
+
name = "#{platform}-#{nodeid}"
|
32
|
+
|
33
|
+
host_config.deep_merge! OSINFO[platform]
|
34
|
+
|
35
|
+
# Some vmpooler/vsphere platforms have special requirements. We munge the
|
36
|
+
# node host_config here if that is necessary.
|
37
|
+
fixup_node host_config
|
38
|
+
|
39
|
+
return name, host_config
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_ostype_token? token
|
43
|
+
OSINFO.each do |key,value|
|
44
|
+
|
45
|
+
ostype = key.split('-')[0]
|
46
|
+
|
47
|
+
if ostype == token
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'beaker-hostgenerator/data'
|
2
|
+
require 'beaker-hostgenerator/data/vmpooler'
|
3
|
+
require 'deep_merge'
|
4
|
+
|
5
|
+
module BeakerHostGenerator
|
6
|
+
class Roles
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile_master
|
12
|
+
return {
|
13
|
+
'frictionless_options' => {
|
14
|
+
'main' => {
|
15
|
+
'dns_alt_names' => 'puppet',
|
16
|
+
'environmentpath' => '/etc/puppetlabs/puppet/environments',
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'beaker-hostgenerator/data'
|
2
|
+
require 'beaker-hostgenerator/data/vmpooler'
|
3
|
+
require 'deep_merge'
|
4
|
+
|
5
|
+
module BeakerHostGenerator
|
6
|
+
module Utils
|
7
|
+
include BeakerHostGenerator::Data
|
8
|
+
include BeakerHostGenerator::Data::Vmpooler
|
9
|
+
|
10
|
+
def pe_dir(version, family)
|
11
|
+
# If our version is the same as our family, we're installing a
|
12
|
+
# released version. Use the archive path. Otherwise, we want to use
|
13
|
+
# the development build path.
|
14
|
+
if version && family
|
15
|
+
if version == family
|
16
|
+
return "http://enterprise.delivery.puppetlabs.net/archives/releases/#{family}/"
|
17
|
+
else
|
18
|
+
return "http://enterprise.delivery.puppetlabs.net/#{family}/ci-ready"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def fixup_node(cfg)
|
24
|
+
# PE 2.8 doesn't exist for EL 4. We use 2.0 instead.
|
25
|
+
if cfg['platform'] =~ /el-4/ and PE_VERSION =~ /2\.8/
|
26
|
+
cfg['pe_ver'] = "2.0.3"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def dump_hosts(hosts, path)
|
31
|
+
config = {}
|
32
|
+
config.deep_merge! BASE_CONFIG
|
33
|
+
config.deep_merge! VMPOOLER_CONFIG
|
34
|
+
|
35
|
+
hosts.each do |host|
|
36
|
+
config['HOSTS'][host.node_name] = {
|
37
|
+
'roles' => host['roles'],
|
38
|
+
'hypervisor' => "#{host['hypervisor']}",
|
39
|
+
'platform' => "#{host['platform']}",
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
File.open(path, 'w') do |file|
|
44
|
+
file.write(config.to_yaml)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_platforms(hypervisor_type='vmpooler')
|
49
|
+
case hypervisor_type
|
50
|
+
when /vmpooler/
|
51
|
+
osinfo = BeakerHostGenerator::Data::Vmpooler::OSINFO
|
52
|
+
else
|
53
|
+
raise "Invalid hypervisor #{hypervisor_type}"
|
54
|
+
end
|
55
|
+
return osinfo
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_roles
|
59
|
+
return BeakerHostGenerator::Data::ROLES
|
60
|
+
end
|
61
|
+
|
62
|
+
module_function :dump_hosts, :get_platforms, :get_roles, :pe_dir
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'beaker-hostgenerator'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module BeakerHostGenerator
|
5
|
+
describe Generator do
|
6
|
+
let(:options) {
|
7
|
+
{
|
8
|
+
list_platforms_and_roles: false,
|
9
|
+
disable_default_role: false,
|
10
|
+
disable_role_config: false,
|
11
|
+
hypervisor: 'vmpooler',
|
12
|
+
}
|
13
|
+
}
|
14
|
+
let(:generator) { BeakerHostGenerator::Generator.new(options) }
|
15
|
+
|
16
|
+
describe '__generate_host_roles' do
|
17
|
+
it 'Generates a list of roles' do
|
18
|
+
{
|
19
|
+
{
|
20
|
+
"roles" => "aulcdfm",
|
21
|
+
"arbitrary_roles" => [],
|
22
|
+
} => [
|
23
|
+
'agent',
|
24
|
+
'ca',
|
25
|
+
'classifier',
|
26
|
+
'dashboard',
|
27
|
+
'database',
|
28
|
+
'frictionless',
|
29
|
+
'master',
|
30
|
+
],
|
31
|
+
{
|
32
|
+
"roles" => "a",
|
33
|
+
"arbitrary_roles" => ["meow","hello","compile_master"],
|
34
|
+
} => [
|
35
|
+
'agent',
|
36
|
+
'meow',
|
37
|
+
'hello',
|
38
|
+
'compile_master',
|
39
|
+
],
|
40
|
+
{
|
41
|
+
"roles" => "",
|
42
|
+
"arbitrary_roles" => ["compile_master"],
|
43
|
+
} => [
|
44
|
+
'compile_master'
|
45
|
+
],
|
46
|
+
{
|
47
|
+
"roles" => "",
|
48
|
+
"arbitrary_roles" => [],
|
49
|
+
} => [],
|
50
|
+
}.each do |node_info, roles|
|
51
|
+
expect( generator.__generate_host_roles(node_info) ).to eq( roles )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '__parse_node_info_token' do
|
57
|
+
|
58
|
+
it 'Raises InvalidNodeSpecError for invalid tokens.' do
|
59
|
+
expect{ generator.__parse_node_info_token("64compile_master") }.to \
|
60
|
+
raise_error(BeakerHostGenerator::Errors::InvalidNodeSpecError)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'Supports no roles in the spec.' do
|
64
|
+
node_info = generator.__parse_node_info_token("64")
|
65
|
+
expect( node_info ).to eq({
|
66
|
+
"roles" => "",
|
67
|
+
"arbitrary_roles" => [],
|
68
|
+
"bits" => "64",
|
69
|
+
})
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'Supports the use of arbitrary roles.' do
|
73
|
+
node_info = generator.__parse_node_info_token("64compile_master,ca,blah.mad")
|
74
|
+
expect( node_info ).to eq({
|
75
|
+
"roles" => "mad",
|
76
|
+
"arbitrary_roles" => ["compile_master", "ca", "blah"],
|
77
|
+
"bits" => "64",
|
78
|
+
})
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'When using arbitrary roles'do
|
82
|
+
it 'Fails without a role-type delimiter (a period)' do
|
83
|
+
expect{ generator.__parse_node_info_token("64compile_master,ca,blah") }.to \
|
84
|
+
raise_error(BeakerHostGenerator::Errors::InvalidNodeSpecError)
|
85
|
+
end
|
86
|
+
it 'It supports no static roles.' do
|
87
|
+
node_info = generator.__parse_node_info_token("64compile_master,ca,blah.")
|
88
|
+
expect( node_info ).to eq({
|
89
|
+
"roles" => "",
|
90
|
+
"arbitrary_roles" => ["compile_master", "ca", "blah"],
|
91
|
+
"bits" => "64",
|
92
|
+
})
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module TestFileHelpers
|
2
|
+
def create_files file_array
|
3
|
+
file_array.each do |f|
|
4
|
+
FileUtils.mkdir_p File.dirname(f)
|
5
|
+
FileUtils.touch f
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def fog_file_contents
|
10
|
+
{ :default => { :aws_access_key_id => "IMANACCESSKEY",
|
11
|
+
:aws_secret_access_key => "supersekritkey",
|
12
|
+
:aix_hypervisor_server => "aix_hypervisor.labs.net",
|
13
|
+
:aix_hypervisor_username => "aixer",
|
14
|
+
:aix_hypervisor_keyfile => "/Users/user/.ssh/id_rsa-acceptance",
|
15
|
+
:solaris_hypervisor_server => "solaris_hypervisor.labs.net",
|
16
|
+
:solaris_hypervisor_username => "harness",
|
17
|
+
:solaris_hypervisor_keyfile => "/Users/user/.ssh/id_rsa-old.private",
|
18
|
+
:solaris_hypervisor_vmpath => "rpoooool/zs",
|
19
|
+
:solaris_hypervisor_snappaths => ["rpoooool/USER/z0"],
|
20
|
+
:vsphere_server => "vsphere.labs.net",
|
21
|
+
:vsphere_username => "vsphere@labs.com",
|
22
|
+
:vsphere_password => "supersekritpassword"} }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
module HostHelpers
|
28
|
+
HOST_DEFAULTS = { :platform => 'unix',
|
29
|
+
:snapshot => 'pe',
|
30
|
+
:box => 'box_name',
|
31
|
+
:roles => ['agent'],
|
32
|
+
:snapshot => 'snap',
|
33
|
+
:ip => 'default.ip.address',
|
34
|
+
:box => 'default_box_name',
|
35
|
+
:box_url => 'http://default.box.url',
|
36
|
+
}
|
37
|
+
|
38
|
+
HOST_NAME = "vm%d"
|
39
|
+
HOST_SNAPSHOT = "snapshot%d"
|
40
|
+
HOST_IP = "ip.address.for.%s"
|
41
|
+
HOST_BOX = "%s_of_my_box"
|
42
|
+
HOST_BOX_URL = "http://address.for.my.box.%s"
|
43
|
+
HOST_TEMPLATE = "%s_has_a_template"
|
44
|
+
|
45
|
+
def logger
|
46
|
+
double( 'logger' ).as_null_object
|
47
|
+
end
|
48
|
+
|
49
|
+
def make_opts
|
50
|
+
opts = StringifyHash.new
|
51
|
+
opts.merge( { :logger => logger,
|
52
|
+
:host_config => 'sample.config',
|
53
|
+
:type => nil,
|
54
|
+
:pooling_api => 'http://vcloud.delivery.puppetlabs.net/',
|
55
|
+
:datastore => 'instance0',
|
56
|
+
:folder => 'Delivery/Quality Assurance/Staging/Dynamic',
|
57
|
+
:resourcepool => 'delivery/Quality Assurance/Staging/Dynamic',
|
58
|
+
:gce_project => 'beaker-compute',
|
59
|
+
:gce_keyfile => '/path/to/keyfile.p12',
|
60
|
+
:gce_password => 'notasecret',
|
61
|
+
:gce_email => '12345678910@developer.gserviceaccount.com' } )
|
62
|
+
end
|
63
|
+
|
64
|
+
def generate_result (name, opts )
|
65
|
+
result = double( 'result' )
|
66
|
+
stdout = opts.has_key?(:stdout) ? opts[:stdout] : name
|
67
|
+
stderr = opts.has_key?(:stderr) ? opts[:stderr] : name
|
68
|
+
exit_code = opts.has_key?(:exit_code) ? opts[:exit_code] : 0
|
69
|
+
exit_code = [exit_code].flatten
|
70
|
+
allow( result ).to receive( :stdout ).and_return( stdout )
|
71
|
+
allow( result ).to receive( :stderr ).and_return( stderr )
|
72
|
+
allow( result ).to receive( :exit_code ).and_return( *exit_code )
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
def make_host_opts name, opts
|
77
|
+
make_opts.merge( { 'HOSTS' => { name => opts } } ).merge( opts )
|
78
|
+
end
|
79
|
+
|
80
|
+
def make_host name, host_hash
|
81
|
+
host_hash = StringifyHash.new.merge(HOST_DEFAULTS.merge(host_hash))
|
82
|
+
|
83
|
+
host = make_opts.merge(host_hash)
|
84
|
+
|
85
|
+
allow(host).to receive( :name ).and_return( name )
|
86
|
+
allow(host).to receive( :to_s ).and_return( name )
|
87
|
+
allow(host).to receive( :exec ).and_return( generate_result( name, host_hash ) )
|
88
|
+
host
|
89
|
+
end
|
90
|
+
|
91
|
+
def make_hosts preset_opts = {}, amt = 3
|
92
|
+
hosts = []
|
93
|
+
(1..amt).each do |num|
|
94
|
+
name = HOST_NAME % num
|
95
|
+
opts = { :snapshot => HOST_SNAPSHOT % num,
|
96
|
+
:ip => HOST_IP % name,
|
97
|
+
:template => HOST_TEMPLATE % name,
|
98
|
+
:box => HOST_BOX % name,
|
99
|
+
:box_url => HOST_BOX_URL % name }.merge( preset_opts )
|
100
|
+
hosts << make_host(name, opts)
|
101
|
+
end
|
102
|
+
hosts
|
103
|
+
end
|
104
|
+
|
105
|
+
def make_instance instance_data = {}
|
106
|
+
OpenStruct.new instance_data
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|