cluster_chef 3.0.5
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.
- data/.gitignore +51 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +63 -0
- data/Gemfile +18 -0
- data/LICENSE +201 -0
- data/README.md +332 -0
- data/Rakefile +92 -0
- data/TODO.md +8 -0
- data/VERSION +1 -0
- data/chefignore +41 -0
- data/cluster_chef.gemspec +115 -0
- data/clusters/website_demo.rb +65 -0
- data/config/client.rb +59 -0
- data/lib/cluster_chef/chef_layer.rb +297 -0
- data/lib/cluster_chef/cloud.rb +409 -0
- data/lib/cluster_chef/cluster.rb +118 -0
- data/lib/cluster_chef/compute.rb +144 -0
- data/lib/cluster_chef/cookbook_munger/README.md.erb +47 -0
- data/lib/cluster_chef/cookbook_munger/licenses.yaml +16 -0
- data/lib/cluster_chef/cookbook_munger/metadata.rb.erb +23 -0
- data/lib/cluster_chef/cookbook_munger.rb +588 -0
- data/lib/cluster_chef/deprecated.rb +33 -0
- data/lib/cluster_chef/discovery.rb +158 -0
- data/lib/cluster_chef/dsl_object.rb +123 -0
- data/lib/cluster_chef/facet.rb +144 -0
- data/lib/cluster_chef/fog_layer.rb +134 -0
- data/lib/cluster_chef/private_key.rb +110 -0
- data/lib/cluster_chef/role_implications.rb +49 -0
- data/lib/cluster_chef/security_group.rb +103 -0
- data/lib/cluster_chef/server.rb +265 -0
- data/lib/cluster_chef/server_slice.rb +259 -0
- data/lib/cluster_chef/volume.rb +93 -0
- data/lib/cluster_chef.rb +137 -0
- data/notes/aws_console_screenshot.jpg +0 -0
- data/rspec.watchr +29 -0
- data/spec/cluster_chef/cluster_spec.rb +13 -0
- data/spec/cluster_chef/facet_spec.rb +70 -0
- data/spec/cluster_chef/server_slice_spec.rb +19 -0
- data/spec/cluster_chef/server_spec.rb +112 -0
- data/spec/cluster_chef_spec.rb +193 -0
- data/spec/spec_helper/dummy_chef.rb +25 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/test_config.rb +20 -0
- data/tasks/chef_config.rb +38 -0
- data/tasks/jeweler_use_alt_branch.rb +47 -0
- metadata +227 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module ClusterChef
|
2
|
+
#
|
3
|
+
# Internal or external storage
|
4
|
+
#
|
5
|
+
class Volume < ClusterChef::DslObject
|
6
|
+
attr_reader :parent
|
7
|
+
attr_accessor :fog_volume
|
8
|
+
has_keys(
|
9
|
+
:name,
|
10
|
+
# mountable volume attributes
|
11
|
+
:device, :mount_point, :mount_options, :fstype, :mount_dump, :mount_pass,
|
12
|
+
# cloud volume attributes
|
13
|
+
:attachable, :create_at_launch, :volume_id, :snapshot_id, :size, :keep, :availability_zone,
|
14
|
+
# arbitrary tags
|
15
|
+
:tags
|
16
|
+
)
|
17
|
+
|
18
|
+
VOLUME_DEFAULTS = {
|
19
|
+
:fstype => 'xfs',
|
20
|
+
:mount_options => 'defaults,nouuid,noatime',
|
21
|
+
:attachable => :ebs,
|
22
|
+
:create_at_launch => false,
|
23
|
+
:keep => true,
|
24
|
+
}
|
25
|
+
|
26
|
+
# Describes a volume
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# ClusterChef::Volume.new( :name => 'redis',
|
30
|
+
# :device => '/dev/sdp', :mount_point => '/data/redis', :fstype => 'xfs', :mount_options => 'defaults,nouuid,noatime'
|
31
|
+
# :size => 1024, :snapshot_id => 'snap-66494a08', :volume_id => 'vol-12312',
|
32
|
+
# :tags => {}, :keep => true )
|
33
|
+
#
|
34
|
+
def initialize attrs={}
|
35
|
+
@parent = attrs.delete(:parent)
|
36
|
+
super(attrs)
|
37
|
+
@settings[:tags] ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
# human-readable description for logging messages and such
|
41
|
+
def desc
|
42
|
+
"#{name} on #{parent.fullname} (#{volume_id} @ #{device})"
|
43
|
+
end
|
44
|
+
|
45
|
+
def defaults
|
46
|
+
self.configure(VOLUME_DEFAULTS)
|
47
|
+
end
|
48
|
+
|
49
|
+
def ephemeral_device?
|
50
|
+
volume_id =~ /^ephemeral/
|
51
|
+
end
|
52
|
+
|
53
|
+
# With snapshot specified but volume missing, have it auto-created at launch
|
54
|
+
#
|
55
|
+
# Be careful with this -- you can end up with multiple volumes claiming to
|
56
|
+
# be the same thing.
|
57
|
+
#
|
58
|
+
def create_at_launch?
|
59
|
+
volume_id.blank? && self.create_at_launch
|
60
|
+
end
|
61
|
+
|
62
|
+
def in_cloud?
|
63
|
+
!! fog_volume
|
64
|
+
end
|
65
|
+
|
66
|
+
def has_server?
|
67
|
+
in_cloud? && fog_volume.server_id.present?
|
68
|
+
end
|
69
|
+
|
70
|
+
def reverse_merge!(other_hsh)
|
71
|
+
super(other_hsh)
|
72
|
+
self.tags.reverse_merge!(other_hsh.tags) if other_hsh.respond_to?(:tags) && other_hsh.tags.present?
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# An array of hashes with dorky-looking keys, just like Fog wants it.
|
77
|
+
def block_device_mapping
|
78
|
+
hsh = { 'DeviceName' => device }
|
79
|
+
if ephemeral_device?
|
80
|
+
hsh['VirtualName'] = volume_id
|
81
|
+
elsif create_at_launch?
|
82
|
+
hsh.merge!({
|
83
|
+
'Ebs.SnapshotId' => snapshot_id,
|
84
|
+
'Ebs.VolumeSize' => size,
|
85
|
+
'Ebs.DeleteOnTermination' => (! keep).to_s })
|
86
|
+
else
|
87
|
+
return
|
88
|
+
end
|
89
|
+
hsh
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
data/lib/cluster_chef.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'chef/mash'
|
2
|
+
require 'gorillib/metaprogramming/class_attribute'
|
3
|
+
require 'gorillib/hash/reverse_merge'
|
4
|
+
require 'gorillib/object/blank'
|
5
|
+
require 'gorillib/hash/compact'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
require 'cluster_chef/dsl_object'
|
9
|
+
require 'cluster_chef/cloud'
|
10
|
+
require 'cluster_chef/security_group'
|
11
|
+
require 'cluster_chef/compute' # base class for machine attributes
|
12
|
+
require 'cluster_chef/facet' # similar machines within a cluster
|
13
|
+
require 'cluster_chef/cluster' # group of machines with a common mission
|
14
|
+
require 'cluster_chef/server' # realization of a specific facet
|
15
|
+
require 'cluster_chef/discovery' # pair servers with Fog and Chef objects
|
16
|
+
require 'cluster_chef/server_slice' # collection of server objects
|
17
|
+
require 'cluster_chef/volume' # configure external and internal volumes
|
18
|
+
require 'cluster_chef/private_key' # coordinate chef keys, cloud keypairs, etc
|
19
|
+
require 'cluster_chef/role_implications' # make roles trigger other actions (security groups, etc)
|
20
|
+
#
|
21
|
+
require 'cluster_chef/chef_layer' # interface to chef for server actions
|
22
|
+
require 'cluster_chef/fog_layer' # interface to fog for server actions
|
23
|
+
#
|
24
|
+
require 'cluster_chef/deprecated' # stuff slated to go away
|
25
|
+
|
26
|
+
module ClusterChef
|
27
|
+
|
28
|
+
# path to search for cluster definition files
|
29
|
+
def self.cluster_path
|
30
|
+
Chef::Config[:cluster_path] ||= Chef::Config[:cookbooks_path].map{|dir| File.expand_path('../clusters', dir) }
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Delegates
|
35
|
+
def self.clusters
|
36
|
+
Chef::Config[:clusters] ||= Mash.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.ui=(ui) @ui = ui ; end
|
40
|
+
def self.ui() @ui ; end
|
41
|
+
|
42
|
+
def self.chef_config=(cc) @chef_config = cc ; end
|
43
|
+
def self.chef_config() @chef_config ; end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Defines a cluster with the given name.
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# ClusterChef.cluster 'demosimple' do
|
50
|
+
# cloud :ec2 do
|
51
|
+
# availability_zones ['us-east-1d']
|
52
|
+
# flavor "t1.micro"
|
53
|
+
# image_name "ubuntu-natty"
|
54
|
+
# end
|
55
|
+
# role :base_role
|
56
|
+
# role :chef_client
|
57
|
+
#
|
58
|
+
# facet :sandbox do
|
59
|
+
# instances 2
|
60
|
+
# role :nfs_client
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
#
|
65
|
+
def self.cluster(name, attrs={}, &block)
|
66
|
+
name = name.to_sym
|
67
|
+
cl = ( self.clusters[name] ||= ClusterChef::Cluster.new(name, attrs) )
|
68
|
+
cl.configure(&block)
|
69
|
+
cl
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Return cluster if it's defined. Otherwise, search ClusterChef.cluster_path
|
74
|
+
# for an eponymous file, load it, and return the cluster it defines.
|
75
|
+
#
|
76
|
+
# Raises an error if a matching file isn't found, or if loading that file
|
77
|
+
# doesn't define the requested cluster.
|
78
|
+
#
|
79
|
+
# @return [ClusterChef::Cluster] the requested cluster
|
80
|
+
def self.load_cluster(cluster_name)
|
81
|
+
raise ArgumentError, "Please supply a cluster name" if cluster_name.to_s.empty?
|
82
|
+
return clusters[cluster_name] if clusters[cluster_name]
|
83
|
+
|
84
|
+
cluster_file = cluster_filenames[cluster_name] or die("Couldn't find a definition for #{cluster_name} in cluster_path: #{cluster_path.inspect}")
|
85
|
+
|
86
|
+
Chef::Log.info("Loading cluster #{cluster_file}")
|
87
|
+
|
88
|
+
require cluster_file
|
89
|
+
unless clusters[cluster_name] then die("#{cluster_file} was supposed to have the definition for the #{cluster_name} cluster, but didn't") end
|
90
|
+
|
91
|
+
clusters[cluster_name]
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Map from cluster name to file name
|
96
|
+
#
|
97
|
+
# @return [Hash] map from cluster name to file name
|
98
|
+
def self.cluster_filenames
|
99
|
+
return @cluster_filenames if @cluster_filenames
|
100
|
+
@cluster_filenames = {}
|
101
|
+
cluster_path.each do |cp_dir|
|
102
|
+
Dir[ File.join(cp_dir, '*.rb') ].each do |filename|
|
103
|
+
cluster_name = File.basename(filename).gsub(/\.rb$/, '')
|
104
|
+
@cluster_filenames[cluster_name] ||= filename
|
105
|
+
end
|
106
|
+
end
|
107
|
+
@cluster_filenames
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Utility to die with an error message.
|
112
|
+
# If the last arg is an integer, use it as the exit code.
|
113
|
+
#
|
114
|
+
def self.die *strings
|
115
|
+
exit_code = strings.last.is_a?(Integer) ? strings.pop : -1
|
116
|
+
strings.each{|str| ui.warn str }
|
117
|
+
exit exit_code
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Utility to turn an error into a warning
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# ClusterChef.safely do
|
125
|
+
# ClusterChef.fog_connection.associate_address(self.fog_server.id, address)
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
def self.safely
|
129
|
+
begin
|
130
|
+
yield
|
131
|
+
rescue StandardError => boom
|
132
|
+
ui.info( boom )
|
133
|
+
Chef::Log.error( boom )
|
134
|
+
Chef::Log.error( boom.backtrace.join("\n") )
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
Binary file
|
data/rspec.watchr
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
def run_spec(file)
|
4
|
+
unless File.exist?(file)
|
5
|
+
puts "#{file} does not exist"
|
6
|
+
return
|
7
|
+
end
|
8
|
+
|
9
|
+
puts "Running #{file}"
|
10
|
+
system "rspec #{file}"
|
11
|
+
puts
|
12
|
+
end
|
13
|
+
|
14
|
+
watch("spec/.*/*_spec\.rb") do |match|
|
15
|
+
run_spec match[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
watch("lib/(.*)\.rb") do |match|
|
19
|
+
file = %{spec/#{match[1]}_spec.rb}
|
20
|
+
run_spec file if File.exists?(file)
|
21
|
+
end
|
22
|
+
|
23
|
+
# watch('lib/cluster_chef/cookbook_munger\.rb') do |match|
|
24
|
+
# system match[0]
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# watch('lib/cluster_chef/cookbook_munger/.*\.erb') do |match|
|
28
|
+
# system 'lib/cluster_chef/cookbook_munger.rb'
|
29
|
+
# end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
require CLUSTER_CHEF_DIR("lib/cluster_chef")
|
4
|
+
|
5
|
+
describe ClusterChef::Cluster do
|
6
|
+
describe 'discover!' do
|
7
|
+
let(:cluster){ get_example_cluster(:monkeyballs) }
|
8
|
+
|
9
|
+
it 'enumerates chef nodes' do
|
10
|
+
cluster.discover!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
require CLUSTER_CHEF_DIR("lib/cluster_chef")
|
4
|
+
|
5
|
+
describe ClusterChef::Facet do
|
6
|
+
let(:cluster){ ClusterChef.cluster(:gibbon) }
|
7
|
+
let(:facet){
|
8
|
+
cluster.facet(:namenode) do
|
9
|
+
instances 5
|
10
|
+
end
|
11
|
+
}
|
12
|
+
|
13
|
+
describe 'slicing' do
|
14
|
+
it 'has servers' do
|
15
|
+
facet.indexes.should == [0, 1, 2, 3, 4]
|
16
|
+
facet.valid_indexes.should == [0, 1, 2, 3, 4]
|
17
|
+
facet.server(3){ name(:bob) }
|
18
|
+
svrs = facet.servers
|
19
|
+
svrs.length.should == 5
|
20
|
+
svrs.map{|svr| svr.name }.should == ["gibbon-namenode-0", "gibbon-namenode-1", "gibbon-namenode-2", :bob, "gibbon-namenode-4"]
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'servers have bogosity if out of range' do
|
24
|
+
facet.server(69).should be_bogus
|
25
|
+
facet.servers.select(&:bogus?).map(&:facet_index).should == [69]
|
26
|
+
facet.indexes.should == [0, 1, 2, 3, 4, 69]
|
27
|
+
facet.valid_indexes.should == [0, 1, 2, 3, 4]
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns all on nil or "", but [] means none' do
|
31
|
+
facet.server(69)
|
32
|
+
facet.slice('' ).map(&:facet_index).should == [0, 1, 2, 3, 4, 69]
|
33
|
+
facet.slice(nil).map(&:facet_index).should == [0, 1, 2, 3, 4, 69]
|
34
|
+
facet.slice([] ).map(&:facet_index).should == []
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'slice returns all by default' do
|
38
|
+
facet.server(69)
|
39
|
+
facet.slice().map(&:facet_index).should == [0, 1, 2, 3, 4, 69]
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'with an array returns specified indexes (bogus or not) in sorted order' do
|
43
|
+
facet.server(69)
|
44
|
+
facet.slice( [3, 1, 0] ).map(&:facet_index).should == [0, 1, 3]
|
45
|
+
facet.slice( [3, 1, 69, 0] ).map(&:facet_index).should == [0, 1, 3, 69]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'with an array does not create new dummy servers' do
|
49
|
+
facet.server(69)
|
50
|
+
facet.slice( [3, 1, 69, 0, 75, 123] ).map(&:facet_index).should == [0, 1, 3, 69]
|
51
|
+
facet.has_server?(75).should be_false
|
52
|
+
facet.has_server?(69).should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'with a string, converts to intervals' do
|
56
|
+
facet.slice('1' ).map(&:facet_index).should == [1]
|
57
|
+
facet.slice('5' ).map(&:facet_index).should == []
|
58
|
+
facet.slice('1-1' ).map(&:facet_index).should == [1]
|
59
|
+
facet.slice('0-1' ).map(&:facet_index).should == [0,1]
|
60
|
+
facet.slice('0-1,3-4').map(&:facet_index).should == [0,1,3,4]
|
61
|
+
facet.slice('0-1,69' ).map(&:facet_index).should == [0,1,69]
|
62
|
+
facet.slice('0-2,1-3').map(&:facet_index).should == [0,1,2,3]
|
63
|
+
facet.slice('3-1' ).map(&:facet_index).should == []
|
64
|
+
facet.slice('2-5' ).map(&:facet_index).should == [2,3,4]
|
65
|
+
facet.slice(1).map(&:facet_index).should == [1]
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require CLUSTER_CHEF_DIR("lib/cluster_chef")
|
3
|
+
|
4
|
+
describe ClusterChef::ServerSlice do
|
5
|
+
before do
|
6
|
+
@slice = ClusterChef.slice('webserver_demo')
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'attributes' do
|
10
|
+
it 'security groups' do
|
11
|
+
@slice.security_groups.keys.sort.should == [
|
12
|
+
"default",
|
13
|
+
"webserver_demo", "webserver_demo-awesome_website", "webserver_demo-dbnode", "webserver_demo-esnode",
|
14
|
+
"webserver_demo-redis_client", "webserver_demo-redis_server",
|
15
|
+
"webserver_demo-webnode", "nfs_client", "ssh"
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require CLUSTER_CHEF_DIR("lib/cluster_chef")
|
3
|
+
|
4
|
+
describe ClusterChef::Server do
|
5
|
+
include_context 'dummy_chef'
|
6
|
+
|
7
|
+
ClusterChef::Server.class_eval do
|
8
|
+
def chef_node
|
9
|
+
Chef::Node.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
ClusterChef::DryRunnable.class_eval do
|
14
|
+
def unless_dry_run
|
15
|
+
puts "Not doing that"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
before do
|
20
|
+
ClusterChef::Server.stub!(:chef_node).and_return( "HI" )
|
21
|
+
Chef::Config.stub!(:validation_key).and_return("I_AM_VALID")
|
22
|
+
|
23
|
+
foo = ClusterChef::Server.new(ClusterChef::Facet.new(ClusterChef::Cluster.new('hi'),'there'),0)
|
24
|
+
puts foo.inspect
|
25
|
+
puts foo.chef_node
|
26
|
+
@cluster = get_example_cluster('webserver_demo')
|
27
|
+
@cluster.resolve!
|
28
|
+
@facet = @cluster.facet(:dbnode)
|
29
|
+
@server = @facet.server(0)
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'volumes' do
|
33
|
+
describe '#composite_volumes' do
|
34
|
+
it 'assembles cluster, facet and server volumes' do
|
35
|
+
@server.composite_volumes.length.should == 5
|
36
|
+
@cluster.volumes.length.should == 4
|
37
|
+
@facet.volumes.length.should == 1
|
38
|
+
@server.volumes.length.should == 1
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'composites server attributes onto a volume defined in the facet' do
|
42
|
+
vol = @server.composite_volumes[:data]
|
43
|
+
vol.to_hash.should == {
|
44
|
+
:name => :data,
|
45
|
+
:tags => {},
|
46
|
+
:snapshot_id => "snap-d9c1edb1",
|
47
|
+
:size => 50,
|
48
|
+
:keep => true,
|
49
|
+
:device => "/dev/sdi",
|
50
|
+
:mount_point => "/data/db",
|
51
|
+
:mount_options => "defaults,nouuid,noatime",
|
52
|
+
:fs_type => "xfs",
|
53
|
+
:availability_zone => "us-east-1d"
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'makes block_device_mapping for non-ephemeral storage' do
|
58
|
+
vol = @server.composite_volumes[:data]
|
59
|
+
vol.block_device_mapping.should == {
|
60
|
+
"DeviceName" => "/dev/sdi",
|
61
|
+
"Ebs.SnapshotId" => "snap-d9c1edb1",
|
62
|
+
"Ebs.VolumeSize" => 50,
|
63
|
+
"Ebs.DeleteOnTermination" => "false"
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'skips block_device_mapping for non-ephemeral storage if volume id is present' do
|
68
|
+
vol = @facet.server(1).composite_volumes[:data]
|
69
|
+
vol.block_device_mapping.should be_nil
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'launch' do
|
76
|
+
describe '#fog_description_for_launch' do
|
77
|
+
it 'has right attributes' do
|
78
|
+
|
79
|
+
hsh = @server.fog_description_for_launch
|
80
|
+
hsh.delete(:user_data)
|
81
|
+
hsh.should == {
|
82
|
+
:image_id => "ami-08f40561",
|
83
|
+
:flavor_id => "m1.large",
|
84
|
+
:groups => ["webserver_demo-redis_client", "webserver_demo-dbnode", "default", "ssh", "nfs_client", "webserver_demo"],
|
85
|
+
:key_name => :webserver_demo,
|
86
|
+
:tags => {:cluster=>:webserver_demo, :facet=>:dbnode, :index=>0},
|
87
|
+
:block_device_mapping => [
|
88
|
+
{"DeviceName"=>"/dev/sdi", "Ebs.SnapshotId"=>"snap-d9c1edb1", "Ebs.VolumeSize"=>50, "Ebs.DeleteOnTermination"=>"false"},
|
89
|
+
{"DeviceName"=>"/dev/sdb", "VirtualName"=>"ephemeral0"},
|
90
|
+
{"DeviceName"=>"/dev/sdc", "VirtualName"=>"ephemeral1"},
|
91
|
+
{"DeviceName"=>"/dev/sdd", "VirtualName"=>"ephemeral2"},
|
92
|
+
{"DeviceName"=>"/dev/sde", "VirtualName"=>"ephemeral3"},
|
93
|
+
],
|
94
|
+
:availability_zone => "us-east-1d",
|
95
|
+
:monitoring => nil
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'has right user_data' do
|
100
|
+
hsh = @server.fog_description_for_launch
|
101
|
+
user_data_hsh = JSON.parse( hsh[:user_data] )
|
102
|
+
user_data_hsh.keys.should == ["chef_server", "validation_client_name", "validation_key", "attributes"]
|
103
|
+
user_data_hsh["attributes"].keys.sort.should == [
|
104
|
+
"cluster_name", "facet_name", "facet_index",
|
105
|
+
"node_name",
|
106
|
+
"webnode_count",
|
107
|
+
]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require CLUSTER_CHEF_DIR("lib/cluster_chef")
|
4
|
+
|
5
|
+
describe "cluster_chef" do
|
6
|
+
describe 'successfuly runs example' do
|
7
|
+
|
8
|
+
describe 'webserver_demo:' do
|
9
|
+
before :all do
|
10
|
+
@cluster = get_example_cluster(:webserver_demo)
|
11
|
+
@cluster.resolve!
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'loads successfuly' do
|
15
|
+
@cluster.should be_a(ClusterChef::Cluster)
|
16
|
+
@cluster.name.should == :webserver_demo
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'cluster is right' do
|
20
|
+
@cluster.to_hash.should == {
|
21
|
+
:name => :webserver_demo,
|
22
|
+
:run_list => ["role[base_role]", "role[chef_client]", "role[ssh]", "role[nfs_client]", "role[big_package]", "role[webserver_demo_cluster]"],
|
23
|
+
:chef_attributes => { :webnode_count => 6 },
|
24
|
+
:facet_name => "webserver_demo_cluster",
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'defaults cluster' do
|
29
|
+
defaults_cluster = ClusterChef.cluster(:defaults)
|
30
|
+
cloud_hash = defaults_cluster.cloud.to_hash
|
31
|
+
[:security_groups, :user_data].each{|k| cloud_hash.delete k }
|
32
|
+
cloud_hash.should == {
|
33
|
+
:availability_zones => ['us-east-1d'],
|
34
|
+
:region => "us-east-1",
|
35
|
+
:flavor => "m1.small",
|
36
|
+
:image_name => "lucid",
|
37
|
+
:backing => "ebs",
|
38
|
+
:disable_api_termination => false,
|
39
|
+
:public_ip => false,
|
40
|
+
:bootstrap_distro => "ubuntu10.04-cluster_chef",
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'cluster cloud is right' do
|
45
|
+
cloud_hash = @cluster.cloud.to_hash
|
46
|
+
[:security_groups, :user_data].each{|k| cloud_hash.delete k }
|
47
|
+
cloud_hash.should == {
|
48
|
+
:availability_zones => ['us-east-1d'],
|
49
|
+
:region => "us-east-1",
|
50
|
+
:flavor => "t1.micro",
|
51
|
+
:image_name => "maverick",
|
52
|
+
:backing => "instance",
|
53
|
+
:disable_api_termination => false,
|
54
|
+
:public_ip => false,
|
55
|
+
:bootstrap_distro => "ubuntu10.04-cluster_chef",
|
56
|
+
:keypair => :webserver_demo,
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'facet cloud is right' do
|
61
|
+
cloud_hash = @cluster.facet(:webnode).cloud.to_hash
|
62
|
+
[:security_groups, :user_data].each{|k| cloud_hash.delete k }
|
63
|
+
cloud_hash.should == {
|
64
|
+
:backing => "ebs",
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'webnode facets are right' do
|
69
|
+
@cluster.facets.length.should == 3
|
70
|
+
fct = @cluster.facet(:webnode)
|
71
|
+
fct.to_hash.should == {
|
72
|
+
:name => :webnode,
|
73
|
+
:run_list => ["role[nginx]", "role[redis_client]", "role[mysql_client]", "role[elasticsearch_client]", "role[awesome_website]", "role[webserver_demo_webnode]"],
|
74
|
+
:chef_attributes => {:split_testing=>{:group=>"A"}},
|
75
|
+
:facet_role => "webserver_demo_webnode",
|
76
|
+
:instances => 6,
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'dbnode facets are right' do
|
81
|
+
fct = @cluster.facet(:dbnode)
|
82
|
+
fct.to_hash.should == {
|
83
|
+
:name => :dbnode,
|
84
|
+
:run_list => ["role[mysql_server]", "role[redis_client]", "role[webserver_demo_dbnode]" ],
|
85
|
+
:chef_attributes => {},
|
86
|
+
:facet_role => "webserver_demo_dbnode",
|
87
|
+
:instances => 2,
|
88
|
+
}
|
89
|
+
fct.cloud.flavor.should == 'c1.xlarge'
|
90
|
+
fct.server(0).cloud.flavor.should == 'm1.large'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'esnode facets are right' do
|
94
|
+
fct = @cluster.facet(:esnode)
|
95
|
+
fct.to_hash.should == {
|
96
|
+
:name => :esnode,
|
97
|
+
:run_list => ["role[nginx]", "role[redis_server]", "role[elasticsearch_data_esnode]", "role[elasticsearch_http_esnode]", "role[webserver_demo_esnode]"],
|
98
|
+
:chef_attributes => {},
|
99
|
+
:facet_role => "webserver_demo_esnode",
|
100
|
+
:instances => 1,
|
101
|
+
}
|
102
|
+
fct.cloud.flavor.should == 'm1.large'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'cluster security groups are right' do
|
106
|
+
gg = @cluster.security_groups
|
107
|
+
gg.keys.should == ['default', 'ssh', 'nfs_client', 'webserver_demo']
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'facet webnode security groups are right' do
|
111
|
+
gg = @cluster.facet(:webnode).security_groups
|
112
|
+
gg.keys.sort.should == ["default", "webserver_demo", "webserver_demo-awesome_website", "webserver_demo-redis_client", "webserver_demo-webnode", "nfs_client", "ssh"]
|
113
|
+
gg['webserver_demo-awesome_website'].range_authorizations.should == [[80..80, "0.0.0.0/0", "tcp"], [443..443, "0.0.0.0/0", "tcp"]]
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'facet dbnode security groups are right' do
|
117
|
+
gg = @cluster.facet(:dbnode).security_groups
|
118
|
+
gg.keys.sort.should == ["default", "webserver_demo", "webserver_demo-dbnode", "webserver_demo-redis_client", "nfs_client", "ssh"]
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'facet esnode security groups are right' do
|
122
|
+
gg = @cluster.facet(:esnode).security_groups
|
123
|
+
gg.keys.sort.should == ["default", "webserver_demo", "webserver_demo-esnode", "webserver_demo-redis_server", "nfs_client", "ssh"]
|
124
|
+
gg['webserver_demo-redis_server'][:name].should == "webserver_demo-redis_server"
|
125
|
+
gg['webserver_demo-redis_server'][:description].should == "cluster_chef generated group webserver_demo-redis_server"
|
126
|
+
gg['webserver_demo-redis_server'].group_authorizations.should == [['webserver_demo-redis_client', nil]]
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'has servers' do
|
130
|
+
@cluster.servers.map(&:fullname).should == [
|
131
|
+
"webserver_demo-dbnode-0", "webserver_demo-dbnode-1",
|
132
|
+
"webserver_demo-esnode-0",
|
133
|
+
"webserver_demo-webnode-0", "webserver_demo-webnode-1", "webserver_demo-webnode-2", "webserver_demo-webnode-3", "webserver_demo-webnode-4", "webserver_demo-webnode-5"
|
134
|
+
]
|
135
|
+
end
|
136
|
+
|
137
|
+
describe 'resolving servers gets right' do
|
138
|
+
before do
|
139
|
+
@server = @cluster.slice(:webnode, 5).first
|
140
|
+
@server.cloud.stub!(:validation_key).and_return("I_AM_VALID")
|
141
|
+
@server.resolve!
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'attributes' do
|
145
|
+
@server.to_hash.should == {
|
146
|
+
:name => 'webserver_demo-webnode-5',
|
147
|
+
:run_list => ["role[base_role]", "role[chef_client]", "role[ssh]", "role[nfs_client]", "role[big_package]", "role[webserver_demo_cluster]", "role[nginx]", "role[redis_client]", "role[mysql_client]", "role[elasticsearch_client]", "role[awesome_website]", "role[webserver_demo_webnode]"],
|
148
|
+
:instances => 6,
|
149
|
+
:chef_attributes => {
|
150
|
+
:split_testing => {:group=>"B"},
|
151
|
+
:webnode_count => 6,
|
152
|
+
:node_name => "webserver_demo-webnode-5",
|
153
|
+
:cluster_name => :webserver_demo, :facet_name => :webnode, :facet_index => 5,
|
154
|
+
},
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'security groups' do
|
159
|
+
@server.security_groups.keys.sort.should == ['default', 'webserver_demo', 'webserver_demo-awesome_website', 'webserver_demo-redis_client', 'webserver_demo-webnode', 'nfs_client', 'ssh']
|
160
|
+
end
|
161
|
+
it 'run list' do
|
162
|
+
@server.run_list.should == ["role[base_role]", "role[chef_client]", "role[ssh]", "role[nfs_client]", "role[big_package]", "role[webserver_demo_cluster]", "role[nginx]", "role[redis_client]", "role[mysql_client]", "role[elasticsearch_client]", "role[awesome_website]", "role[webserver_demo_webnode]"]
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'user_data' do
|
166
|
+
@server.cloud.user_data.should == {
|
167
|
+
"chef_server" => "https://api.opscode.com/organizations/infochimps",
|
168
|
+
"validation_client_name" => "chef-validator",
|
169
|
+
"validation_key" => "I_AM_VALID",
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'cloud settings' do
|
174
|
+
hsh = @server.cloud.to_hash
|
175
|
+
hsh.delete(:security_groups)
|
176
|
+
hsh.delete(:user_data)
|
177
|
+
hsh.should == {
|
178
|
+
:availability_zones => ["us-east-1c"],
|
179
|
+
:region => "us-east-1",
|
180
|
+
:flavor => "t1.micro",
|
181
|
+
:image_name => "maverick",
|
182
|
+
:backing => "ebs",
|
183
|
+
:disable_api_termination => false,
|
184
|
+
:public_ip => false,
|
185
|
+
:bootstrap_distro => "ubuntu10.04-cluster_chef",
|
186
|
+
:keypair => :webserver_demo,
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|