beaker-hostgenerator 0.1.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.
@@ -0,0 +1,6 @@
1
+ module BeakerHostGenerator
2
+ module Errors
3
+ class Error < RuntimeError ; end
4
+ class InvalidNodeSpecError < BeakerHostGenerator::Errors::Error ; end
5
+ end
6
+ end
@@ -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,5 @@
1
+ module BeakerHostGenerator
2
+ module Version
3
+ STRING = '0.1.0'
4
+ end
5
+ 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