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.
Files changed (46) hide show
  1. data/.gitignore +51 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +63 -0
  4. data/Gemfile +18 -0
  5. data/LICENSE +201 -0
  6. data/README.md +332 -0
  7. data/Rakefile +92 -0
  8. data/TODO.md +8 -0
  9. data/VERSION +1 -0
  10. data/chefignore +41 -0
  11. data/cluster_chef.gemspec +115 -0
  12. data/clusters/website_demo.rb +65 -0
  13. data/config/client.rb +59 -0
  14. data/lib/cluster_chef/chef_layer.rb +297 -0
  15. data/lib/cluster_chef/cloud.rb +409 -0
  16. data/lib/cluster_chef/cluster.rb +118 -0
  17. data/lib/cluster_chef/compute.rb +144 -0
  18. data/lib/cluster_chef/cookbook_munger/README.md.erb +47 -0
  19. data/lib/cluster_chef/cookbook_munger/licenses.yaml +16 -0
  20. data/lib/cluster_chef/cookbook_munger/metadata.rb.erb +23 -0
  21. data/lib/cluster_chef/cookbook_munger.rb +588 -0
  22. data/lib/cluster_chef/deprecated.rb +33 -0
  23. data/lib/cluster_chef/discovery.rb +158 -0
  24. data/lib/cluster_chef/dsl_object.rb +123 -0
  25. data/lib/cluster_chef/facet.rb +144 -0
  26. data/lib/cluster_chef/fog_layer.rb +134 -0
  27. data/lib/cluster_chef/private_key.rb +110 -0
  28. data/lib/cluster_chef/role_implications.rb +49 -0
  29. data/lib/cluster_chef/security_group.rb +103 -0
  30. data/lib/cluster_chef/server.rb +265 -0
  31. data/lib/cluster_chef/server_slice.rb +259 -0
  32. data/lib/cluster_chef/volume.rb +93 -0
  33. data/lib/cluster_chef.rb +137 -0
  34. data/notes/aws_console_screenshot.jpg +0 -0
  35. data/rspec.watchr +29 -0
  36. data/spec/cluster_chef/cluster_spec.rb +13 -0
  37. data/spec/cluster_chef/facet_spec.rb +70 -0
  38. data/spec/cluster_chef/server_slice_spec.rb +19 -0
  39. data/spec/cluster_chef/server_spec.rb +112 -0
  40. data/spec/cluster_chef_spec.rb +193 -0
  41. data/spec/spec_helper/dummy_chef.rb +25 -0
  42. data/spec/spec_helper.rb +50 -0
  43. data/spec/test_config.rb +20 -0
  44. data/tasks/chef_config.rb +38 -0
  45. data/tasks/jeweler_use_alt_branch.rb +47 -0
  46. 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
@@ -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