Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

moving meta-cookbooks/cluster_chef specs into their own deal

  • Loading branch information...
commit 470440e6f9f79b2ba7267b6e89948f1455ee5e53 1 parent c20bdb0
Philip (flip) Kromer authored
View
25 .watchr
@@ -11,14 +11,14 @@ def run_spec(file)
puts
end
-# watch("spec/.*/*_spec\.rb") do |match|
-# run_spec match[0]
-# end
-#
-# watch("lib/(.*)\.rb") do |match|
-# file = %{spec/#{match[1]}_spec.rb}
-# run_spec file if File.exists?(file)
-# end
+watch("spec/.*/*_spec\.rb") do |match|
+ run_spec match[0]
+end
+
+watch("lib/(.*)\.rb") do |match|
+ file = %{spec/#{match[1]}_spec.rb}
+ run_spec file if File.exists?(file)
+end
# watch('lib/cluster_chef/cookbook_munger\.rb') do |match|
# system match[0]
@@ -27,12 +27,3 @@ end
# watch('lib/cluster_chef/cookbook_munger/.*\.erb') do |match|
# system 'lib/cluster_chef/cookbook_munger.rb'
# end
-
-watch("spec/.*/discovery_spec\.rb") do |match|
- run_spec match[0]
-end
-
-watch("meta-cookbooks/provides_service/libraries/discovery\.rb") do |match|
- file = %{spec/cluster_chef/discovery_spec.rb}
- run_spec file if File.exists?(file)
-end
View
92 README-contents.md
@@ -79,20 +79,52 @@ For example, I am user `mrflip` and my organization is `infochimps`, so my tree
#
# Systems
#
+
+ #
+ # A server is something that (typically all, but at least most of)
+ # runs a daemon, opens ports, has logs, has directories, etc.
+ #
+ # if there's only one contender for the title of
+ #
[:apache, :server],
[:cassandra, :server],
- [:chef, :client],
- [:cron, :daemon],
- [:cluster_chef, :dashboard],
- # [:dash_dash, :dashboard],
+ [:mongodb, :server],
+ [:mysql, :server],
+ [:nfs, :server],
+ [:nginx, :server],
+ [:ntp, :server],
+ [:redis, :server],
+ [:statsd, :server],
+ [:zabbix, :server],
+ [:zookeeper, :server],
+ [:apt_cacher, :server],
+ [:dashpot, :server], # not dashboard
+ [:resque, :server],
+ [:openssh, :server],
+
+ # !not extra server: an announcement some cassandra servers make!
+ [:cassandra, :seed],
+ [:elasticsearch, :seed],
+ # similarly, a mysql-master is also a mysql-server
+ [:mysql, :master],
+ [:mysql, :slave ],
+ [:redis, :master],
+ [:redis, :slave ],
+
+ # where there are lots of server-y things
+ # give them their system-specific natural name.
+ # If there are multiple server-y names, avoid if possible giving any of them the subsytem name 'server'...
+
+ [:chef, :expander],
+ [:chef, :server], # ... but don't fight city hall: if its name is server go with server
+ [:chef, :solr],
+ [:chef, :webui],
[:elasticsearch, :datanode],
[:elasticsearch, :httpnode],
- [:flume, :client],
[:flume, :master],
[:ganglia, :master],
- [:ganglia, :monitor],
[:graphite, :carbon],
- [:graphite, :dashboard],
+ [:graphite, :dashboard],
[:graphite, :whisper],
[:hadoop, :datanode],
[:hadoop, :hdfs_fuse],
@@ -103,32 +135,32 @@ For example, I am user `mrflip` and my organization is `infochimps`, so my tree
[:hbase, :master],
[:hbase, :regionserver],
[:hbase, :stargate],
- [:mongodb, :server],
- [:mysql, :server],
+ [:jenkins, :master],
+ [:jenkins, :worker],
+ [:resque, :worker],
+
+ #
+ # A 'client' means 'I install all the stuff to let you *use* some other
+ # component. We're not even sure if these announce.
+ #
[:nfs, :client],
- [:nfs, :server],
- [:nginx, :server],
- [:ntp, :server],
- [:redis, :server],
- [:resque, :dashboard],
- [:openssh, :daemon],
- [:statsd, :server],
- [:zabbix, :monitor],
- [:zabbix, :server],
- [:zookeeper, :server],
+ [:redis, :client],
- [:apt_cacher, :server],
- [:bluepill, :monitor],
- [:goliath, :app],
- [:unicorn, :app],
- [:resque, :worker],
+ # An 'agent' is a thing that runs and uses that thing. A client is not
+ # necessarily an agent.
+ [:flume, :agent],
+ [:zabbix, :agent],
+ [:cron, :agent],
+ [:ganglia, :agent],
+ [:bluepill, :agent],
+
+ # The chef-client daemon is in this terminology properly an 'agent' -- if it
+ # conformed to the style guide, chef_client would install the runnable, and
+ # chef_agent would be the daemon which periodically contacts the chef_server
+ # and converges the machine.
+ [:chef, :client],
- [:chef, :dashboard],
- [:chef, :expander],
- [:chef, :server],
- [:chef, :solr],
- [:jenkins, :master],
- [:jenkins, :worker],
+__________________________________________________________________________
#
# Discovery / preparation
View
7 chefignore
@@ -32,3 +32,10 @@ a.out
*/.bzr/*
*/.hg/*
*/.svn/*
+
+## Don't send rspecs up in cookbook
+.watchr
+.rspec
+spec/*
+spec/fixtures/*
+
View
3  meta-cookbooks/cluster_chef/.rspec
@@ -0,0 +1,3 @@
+--color
+--format documentation
+--drb
View
20 meta-cookbooks/cluster_chef/.watchr
@@ -0,0 +1,20 @@
+# -*- ruby -*-
+
+def run_spec(file)
+ unless File.exist?(file)
+ Watchr.debug "#{file} does not exist"
+ return
+ end
+
+ Watchr.debug "Running #{file}"
+ system "rspec #{file}"
+end
+
+watch("spec/.*_spec\.rb") do |match|
+ run_spec match[0]
+end
+
+watch("libraries/(.*)\.rb") do |match|
+ file = %{spec/#{match[1]}_spec.rb}
+ run_spec file if File.exists?(file)
+end
View
15 meta-cookbooks/cluster_chef/Gemfile
@@ -0,0 +1,15 @@
+source "http://rubygems.org"
+
+gem 'chef', "~> 0.10.4"
+
+# Add dependencies to develop your gem here.
+# Include everything needed to run rake, tests, features, etc.
+group :development do
+ gem 'bundler', "~> 1"
+ gem 'yard', "~> 0.6.7"
+ gem 'jeweler', "~> 1.6.4"
+ gem 'rspec', "~> 2.7.0"
+ gem 'watchr', "~> 0.7"
+ # gem 'ruby-fsevent', "~> 0.2"
+ # gem 'rev'
+end
View
18 meta-cookbooks/cluster_chef/libraries/aspect.rb
@@ -61,19 +61,17 @@ def lint!
end
def lint_flavor
- self.class.allowed_flavors.include?(self.flavor) ? [] : ["Unexpected #{klass_handle} flavor #{flavor.inspect}"]
+ self.class.allowed_flavors.include?(self.flavor) ? [] : ["Unexpected #{self.class.handle} flavor #{flavor.inspect}"]
end
- # simple handle for class
- # @example
- # foo = ClusterChef::FooAspect
- # foo.klass_handle # :foo
- def klass_handle() self.class.klass_handle ; end
-
module ClassMethods
include AttrStruct::ClassMethods
include ClusterChef::NodeUtils
+ def register!
+ ClusterChef::Component.has_aspect(self)
+ end
+
#
# Extract attributes matching the given pattern.
#
@@ -110,9 +108,9 @@ def rsrc_matches(rsrc_clxn, resource_name, cookbook_name)
end
# strip off module part and '...Aspect' from class name
- # @example ClusterChef::FooAspect.klass_handle # :foo
- def klass_handle
- @klass_handle ||= self.name.to_s.gsub(/.*::(\w+)Aspect\z/,'\1').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase.to_sym
+ # @example ClusterChef::FooAspect.handle # :foo
+ def handle
+ @handle ||= self.name.to_s.gsub(/.*::(\w+)Aspect\z/,'\1').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase.to_sym
end
end
View
5 meta-cookbooks/cluster_chef/libraries/attr_struct.rb
@@ -119,9 +119,8 @@ def store_into_node(node, a, b=nil)
module ClassMethods
def dsl_attr(name, validation)
name = name.to_sym
- p name
- define_method(name) do |*args|
- set_or_return(name, *args, validation)
+ define_method(name) do |val=nil|
+ set_or_return(name, val, validation)
end
end
end
View
13 meta-cookbooks/cluster_chef/libraries/cluster_chef.rb
@@ -1,12 +1,17 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+
+# $LOAD_PATH.unshift(File.expand_path('../../../lib'), File.dirname(__FILE__))
+# require 'cluster_chef/dsl_object'
#
# Dependencies for cluster_chef libraries
#
require File.expand_path('attr_struct.rb', File.dirname(__FILE__))
-require File.expand_path('node_utils.rb', File.dirname(__FILE__))
-require File.expand_path('aspect.rb', File.dirname(__FILE__))
-require File.expand_path('aspects.rb', File.dirname(__FILE__))
-require File.expand_path('discovery.rb', File.dirname(__FILE__))
+require File.expand_path('node_utils.rb', File.dirname(__FILE__))
+require File.expand_path('component.rb', File.dirname(__FILE__))
+require File.expand_path('aspect.rb', File.dirname(__FILE__))
+require File.expand_path('aspects.rb', File.dirname(__FILE__))
+require File.expand_path('discovery.rb', File.dirname(__FILE__))
# FIXME -- remove:
View
29 meta-cookbooks/cluster_chef/libraries/component.rb
@@ -13,12 +13,12 @@ class Component < Struct.new(
attr_reader :subsys # subsystem name: eg +:server+ or +:datanode+
attr_reader :node # node this component belongs to
- def initialize(node, sys, subsys=nil, hsh={})
+ def initialize(node, sys, subsys, hsh={})
super()
@node = node
@sys = sys
@subsys = subsys
- self.name = subsys ? "#{sys}_#{subsys}".to_sym : sys.to_sym
+ self.name = "#{sys}_#{subsys}".to_sym
self.timestamp = ClusterChef::NodeUtils.timestamp
merge!(hsh)
end
@@ -37,7 +37,7 @@ def fullname
# A segmented name for the component
def self.fullname(realm, sys, subsys=nil)
- subsys ? "#{realm}-#{sys}-#{subsys}".to_s : "#{realm}-#{sys}"
+ "#{realm}-#{sys}-#{subsys}".to_s
end
#
@@ -91,10 +91,9 @@ def self.aspect_types
end
# add this class to the list of registered aspects
- def self.register_aspect(klass)
- aspect_name = klass.klass_handle
- self.aspect_types[aspect_name] = klass
- dsl_attr(aspect_name, :kind_of => [Mash, klass])
+ def self.has_aspect(klass)
+ self.aspect_types[klass.handle] = klass
+ dsl_attr(klass.handle, :kind_of => [Mash, klass])
end
#
@@ -108,8 +107,6 @@ def self.register_aspect(klass)
# subsys hash, the subsys hash's value wins (see +:user+ in the
# example below).
#
- # If subsys is nil, just returns the direct node hash.
- #
# @example
# node.to_hash
# # { :hadoop => {
@@ -127,12 +124,10 @@ def self.register_aspect(klass)
def node_info
unless node[sys] then Chef::Log.warn("no system data in component '#{name}', node '#{node}'") ; return Mash.new ; end
hsh = Mash.new(node[sys].to_hash)
- if subsys
- if node[sys][subsys]
- hsh.merge!(node[sys][subsys])
- else
- Chef::Log.warn("no subsystem data in component '#{name}', node '#{node}'")
- end
+ if node[sys][subsys]
+ hsh.merge!(node[sys][subsys])
+ else
+ Chef::Log.warn("no subsystem data in component '#{name}', node '#{node}'")
end
hsh
end
@@ -144,9 +139,5 @@ def node_attr(attr, required=nil)
node_info[attr]
end
- def self.has_aspect(aspect, klass)
- @aspect_types ||= {}
- @aspect_types[aspect] = klass
- end
end
end
View
11 meta-cookbooks/cluster_chef/libraries/discovery.rb
@@ -26,7 +26,7 @@ module Discovery
# @option opts [String] :realm Offer the component within this realm -- by
# default, the current node's cluster
#
- def announce(sys, subsys=nil, opts={})
+ def announce(sys, subsys, opts={})
opts = Mash.new(opts)
opts[:realm] ||= node[:cluster_name]
component = Component.new(run_context, sys, subsys, opts)
@@ -43,7 +43,7 @@ def announce(sys, subsys=nil, opts={})
# discover_all(:cassandra, :seeds, 'bukkit') # all cassandra seeds for 'bukkit' cluster
#
# @return [ClusterChef::Component] component from server to most recently-announce
- def discover_all(sys, subsys=nil, realm=nil)
+ def discover_all(sys, subsys, realm=nil)
realm ||= discovery_realm(sys,subsys)
component_name = ClusterChef::Component.fullname(realm, sys, subsys)
#
@@ -62,20 +62,17 @@ def discover_all(sys, subsys=nil, realm=nil)
# discover(:redis, :server, 'uploader') # redis server for 'uploader' realm
#
# @return [ClusterChef::Component] component from server to most recently-announce
- def discover(sys, subsys=nil, realm=nil)
+ def discover(sys, subsys, realm=nil)
discover_all(sys, subsys, realm).last or raise("Cannot find '#{component_name}'")
end
def discovery_realm(sys, subsys=nil)
- return node[:discovers][sys][subsys] if (node[:discovers][sys][subsys].is_a? String rescue false)
- return node[:discovers][sys] if (node[:discovers][sys].is_a? String rescue false)
- node[:cluster_name]
+ node[:discovers][sys][subsys] rescue node[:cluster_name]
end
def node_components(server)
server[:announces].map do |name, hsh|
realm, sys, subsys = name.split("-", 3)
- subsys = nil if (subsys.to_s == "")
hsh[:realm] = realm
ClusterChef::Component.new(server, sys, subsys, hsh)
end
View
436 meta-cookbooks/cluster_chef/spec/discovery_spec.rb
@@ -0,0 +1,436 @@
+require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
+require CLUSTER_CHEF_DIR("libraries/cluster_chef.rb")
+
+CHEF_RESOURCE_CLXN = JSON.parse(File.read(CLUSTER_CHEF_DIR('spec/fixtures/chef_resources-el_ridiculoso-aqui-0.json')))
+
+
+DummyNode.class_eval do
+ include ClusterChef::Discovery
+ include ClusterChef::NodeUtils
+ public :discover_all_nodes
+end
+
+describe ClusterChef do
+ include ClusterChef::SpecHelper
+
+ describe ClusterChef::Discovery do
+
+ context '#node_info' do
+ it 'returns a mash' do
+ chef_server_component.node_info.should be_a(Mash)
+ end
+ it 'extracts the node attribute tree' do
+ chef_server_component.node_info.should == Mash.new({ :user => 'chef', :port => 4000, :server => { :port => 4000 }, :webui => { :port => 4040, :user => 'www-data' } })
+ end
+ it 'overrides system attrs with subsystem attrs' do
+ chef_webui_component.node_info.should == Mash.new({ :user => 'www-data', :port => 4040, :server => { :port => 4000 }, :webui => { :port => 4040, :user => 'www-data' } })
+ end
+ it 'warns but does not fail if system is missing' do
+ Chef::Log.should_receive(:warn).with("no system data in component 'mxyzptlk_shazbot', node 'node[el_ridiculoso-aqui-0]'")
+ comp = ClusterChef::Component.new(dummy_node, :mxyzptlk, :shazbot)
+ comp.node_info.should == Mash.new
+ end
+ it 'warns but does not fail if subsystem is missing' do
+ Chef::Log.should_receive(:warn).with("no subsystem data in component 'chef_zod', node 'node[el_ridiculoso-aqui-0]'")
+ comp = ClusterChef::Component.new(dummy_node, :chef, :zod)
+ comp.node_info.should == Mash.new({ :user => 'chef', :server => { :port => 4000 }, :webui => { :port => 4040, :user => 'www-data' } })
+ end
+ end
+
+ context '.announce' do
+ it 'returns the announced component' do
+ component = recipe.announce(:chef, :server)
+ component.should be_a(ClusterChef::Component)
+ component.fullname.should == 'el_ridiculoso-chef-server'
+ end
+ end
+
+ context '.discover_all_nodes' do
+ before(:each) do
+ dummy_recipe.stub!(:search).
+ with(:node, 'announces:el_ridiculoso-hadoop-datanode').
+ and_return( all_nodes.values_at('el_ridiculoso-aqui-0', 'el_ridiculoso-pequeno-0') )
+ dummy_recipe.stub!(:search).
+ with(:node, 'announces:el_ridiculoso-hadoop-tasktracker').
+ and_return( all_nodes.values_at('el_ridiculoso-aqui-0', 'el_ridiculoso-pequeno-0') )
+ dummy_recipe.stub!(:search).
+ with(:node, 'announces:el_ridiculoso-redis-server').
+ and_return( all_nodes.values_at('el_ridiculoso-aqui-0') )
+ dummy_recipe.stub!(:search).
+ with(:node, 'announces:cocina-chef-client').
+ and_return( all_nodes.values )
+ end
+ it 'finds nodes matching the request, sorted by timestamp' do
+ result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-datanode")
+ result.map{|nd| nd.name }.should == ['el_ridiculoso-pequeno-0', 'el_ridiculoso-aqui-0']
+ end
+
+ it 'replaces itself with a current copy in the search results' do
+ result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-datanode")
+ result.map{|nd| nd.name }.should == ['el_ridiculoso-pequeno-0', 'el_ridiculoso-aqui-0']
+ result[1].should have_key(:nfs)
+ end
+ it 'finds current node if it has announced (even when the server\'s copy has not)' do
+ result = dummy_recipe.discover_all_nodes("el_ridiculoso-redis-server")
+ result.map{|nd| nd.name }.should == ['el_ridiculoso-aqui-0']
+ result[0].should have_key(:nfs)
+ end
+ it 'does not find current node if it has not announced (even when the server\'s copy has announced)' do
+ result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-tasktracker")
+ result.map{|nd| nd.name }.should == ['el_ridiculoso-pequeno-0']
+ end
+ it 'when no server found warns and returns an empty hash' do
+ dummy_recipe.should_receive(:search).
+ with(:node, 'announces:el_ridiculoso-hadoop-mxyzptlk').and_return([])
+ Chef::Log.should_receive(:warn).with("No node announced for 'el_ridiculoso-hadoop-mxyzptlk'")
+ result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-mxyzptlk")
+ result.should == []
+ end
+ end
+
+ end
+
+ describe ClusterChef::Component do
+
+ context 'registering aspects' do
+ it 'adds a set-or-return accessor' do
+ klass = Class.new(ClusterChef::Component)
+ klass.dsl_attr(:foo, :kind_of => Integer)
+ obj = klass.new(dummy_node, :hadoop, :namenode)
+ obj.foo(3).should == 3
+ obj.foo.should == 3
+ end
+
+ it 'sees all the registered aspects' do
+ ClusterChef::Component.aspect_types.should == Mash.new({
+ :port => ClusterChef::PortAspect, :dashboard => ClusterChef::DashboardAspect, :daemon => ClusterChef::DaemonAspect,
+ :log => ClusterChef::LogAspect, :directory => ClusterChef::DirectoryAspect,
+ :exported => ClusterChef::ExportedAspect
+ })
+ end
+ end
+
+ context '.harvest_aspects' do
+ end
+
+ # context 'Disco' do
+ # context 'works on a complex example' do
+ #
+ # it('daemon') do
+ # subject[:daemon].should == [
+ # ClusterChef::DaemonAspect.new("hadoop_datanode", 'hadoop_datanode', 'start') ]
+ # end
+ # it('port') do
+ # subject[:port].should == [
+ # ClusterChef::PortAspect.new("dash_port", :dash, "50075"),
+ # ClusterChef::PortAspect.new("ipc_port", :ipc, "50020"),
+ # ClusterChef::PortAspect.new("jmx_dash_port", :jmx_dash, "8006"),
+ # ClusterChef::PortAspect.new("port", :port, "50010"),
+ # ]
+ # end
+ # it('dashboard') do
+ # subject[:dashboard].should == [
+ # ClusterChef::DashboardAspect.new("dash", :http_dash, "http://33.33.33.12:50075/"),
+ # ClusterChef::DashboardAspect.new("jmx_dash", :jmx_dash, "http://33.33.33.12:8006/"),
+ # ]
+ # end
+ # it('log') do
+ # subject[:log].should == [
+ # ClusterChef::LogAspect.new("log", :log, "/hadoop/log")
+ # ]
+ # end
+ # it('directory') do
+ # subject[:directory].should == [
+ # ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/hadoop/conf"),
+ # ClusterChef::DirectoryAspect.new("data", :data, ["/mnt1/hadoop/hdfs/data", "/mnt2/hadoop/hdfs/data"] ),
+ # ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/hadoop"),
+ # ClusterChef::DirectoryAspect.new("log", :log, "/hadoop/log"),
+ # ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/hadoop"),
+ # ClusterChef::DirectoryAspect.new("tmp", :tmp, "/hadoop/tmp"),
+ # ]
+ # end
+ # it('exported') do
+ # subject[:exported].should == [
+ # ClusterChef::ExportedAspect.new("confs", :confs, ["/etc/hadoop/conf/core-site.xml", "/etc/hadoop/conf/hdfs-site.xml", "/etc/hadoop/conf/mapred-site.xml" ]),
+ # ClusterChef::ExportedAspect.new("jars", :jars, ["/usr/lib/hadoop/hadoop-core.jar","/usr/lib/hadoop/hadoop-examples.jar", "/usr/lib/hadoop/hadoop-test.jar", "/usr/lib/hadoop/hadoop-tools.jar" ]),
+ # ]
+ # end
+ #
+ # end
+ # end
+
+ end
+
+ describe ClusterChef::Aspect do
+ let(:foo_aspect){ Struct.new('FooAspect', :name, :description){ include ClusterChef::Aspect } }
+ after(:each) do
+ Struct.send(:remove_const, :FooAspect) if defined?(Struct::FooAspect)
+ ClusterChef::Component.aspect_types.delete(:foo)
+ ClusterChef::Component.aspect_types.should_not include(:foo)
+ end
+
+ it 'can register itself' do
+ ClusterChef::Component.aspect_types.should_not include(foo_aspect)
+ foo_aspect.register!
+ ClusterChef::Component.aspect_types.should include(:foo)
+ ClusterChef::Component.aspect_types.values.should include(foo_aspect)
+ end
+
+ it 'knows its handle' do
+ foo_aspect.handle.should == :foo
+ end
+
+ it 'passes the node to each aspect in turn' do
+ foo_aspect.register!
+ rc = Chef::RunContext.new(Chef::Node.new, [])
+ foo_aspect.should_receive(:harvest).with(rc, :billy, :bob, {})
+ .harvest_all(rc, :billy, :bob, {})
+ end
+
+ end
+
+ # describe :PortAspect do
+ # it 'is harvested by Aspects.harvest_all' do
+ # aspects = ClusterChef::Aspect.harvest_all(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
+ # aspects[:port].should_not be_empty
+ # aspects[:port].each{|asp| asp.should be_a(ClusterChef::PortAspect) }
+ # end
+ # it 'harvests any "*_port" attributes' do
+ # port_aspects = ClusterChef::PortAspect.harvest(chef_context, :hadoop, :datanode, chef_node[:hadoop][:datanode])
+ # port_aspects.should == [
+ # ClusterChef::PortAspect.new("dash_port", :dash, "50075"),
+ # ClusterChef::PortAspect.new("ipc_port", :ipc, "50020"),
+ # ClusterChef::PortAspect.new("jmx_dash_port", :jmx_dash, "8006"),
+ # ClusterChef::PortAspect.new("port", :port, "50010"),
+ # ]
+ # end
+ # # context '#addrs' do
+ # # it 'can be marked :critical, :open, :closed or :ignore'
+ # # it 'marks first private interface open by default'
+ # # it 'marks other interfaces closed by default'
+ # # end
+ # # context '#flavor' do
+ # # it 'accepts a defined flavor'
+ # # end
+ # # context '#monitors' do
+ # # it 'accepts an arbitrary hash'
+ # # end
+ # end
+ #
+ # describe :DashboardAspect do
+ # it 'is harvested by Aspects.harvest_all' do
+ # aspects = ClusterChef::Aspect.harvest_all(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
+ # aspects[:dashboard].should_not be_empty
+ # aspects[:dashboard].each{|asp| asp.should be_a(ClusterChef::DashboardAspect) }
+ # end
+ # it 'harvests any "dash_port" attributes' do
+ # dashboard_aspects = ClusterChef::DashboardAspect.harvest(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
+ # dashboard_aspects.should == [
+ # ClusterChef::DashboardAspect.new("dash", :http_dash, "http://33.33.33.12:50070/"),
+ # ClusterChef::DashboardAspect.new("jmx_dash", :jmx_dash, "http://33.33.33.12:8004/"),
+ # ]
+ # end
+ # it 'by default harvests the url from the private_ip and dash_port'
+ # it 'lets me set the URL with an explicit template'
+ # end
+ #
+ # describe :DaemonAspect do
+ # it 'is harvested by Aspects.harvest_all' do
+ # aspects = ClusterChef::Aspect.harvest_all(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
+ # aspects[:daemon].should_not be_empty
+ # aspects[:daemon].each{|asp| asp.should be_a(ClusterChef::DaemonAspect) }
+ # end
+ # it 'harvests its associated service resource' do
+ # info = Mash.new(chef_node[:zookeeper].to_hash).merge(chef_node[:zookeeper][:server])
+ # daemon_aspects = ClusterChef::DaemonAspect.harvest(chef_context, :zookeeper, :server, info)
+ # daemon_aspects.should == [
+ # ClusterChef::DaemonAspect.new("zookeeper", "zookeeper", 'stop'),
+ # ]
+ # end
+ #
+ # it 'harvesting many' do
+ # # rl = chef_node.run_list.map{|s| s.to_s.gsub(/(?:\Arecipe|role)\[([^:]+?)(?:::(.+))?\]\z/, '\1') }.compact.uniq.map(&:to_sym)
+ # chef_context.node.recipes.map{|x| x.split(/::/) }.uniq.each do |sys, subsys|
+ # info = Mash.new(chef_node[sys])
+ # daemon_aspects = ClusterChef::DaemonAspect.harvest(chef_context, sys, subsys, info)
+ # end
+ # end
+ # # context '#run_state' do
+ # # it 'harvests the :run_state attribute'
+ # # it 'can be set explicitly'
+ # # it 'only accepts :start, :stop or :nothing'
+ # # end
+ # # context '#boot_state' do
+ # # it 'harvests the :boot_state attribute'
+ # # it 'can be set explicitly'
+ # # it 'only accepts :enable, :disable or nil'
+ # # end
+ # # context '#pattern' do
+ # # it 'harvests the :pattern attribute from the associated service resource'
+ # # it 'is not settable explicitly'
+ # # end
+ # # context '#limits' do
+ # # it 'accepts an arbitrary hash'
+ # # it 'harvests the :limits hash'
+ # # end
+ # end
+ #
+ # describe :LogAspect do
+ # it 'is harvested by Aspects.harvest_all' do
+ # aspects = ClusterChef::Aspect.harvest_all(chef_context, :flume, :node, chef_node[:flume])
+ # aspects[:log].should_not be_empty
+ # aspects[:log].each{|asp| asp.should be_a(ClusterChef::LogAspect) }
+ # end
+ # it 'harvests any "log_dir" attributes' do
+ # log_aspects = ClusterChef::LogAspect.harvest(chef_context, :flume, :node, chef_node[:flume])
+ # log_aspects.should == [
+ # ClusterChef::LogAspect.new("log", :log, "/var/log/flume"),
+ # ]
+ # end
+ # # context '#flavor' do
+ # # it 'accepts :http, :log4j, or :rails'
+ # # end
+ # end
+ #
+ # describe :DirectoryAspect do
+ # it 'is harvested by Aspects.harvest_all' do
+ # aspects = ClusterChef::Aspect.harvest_all(chef_context, :zookeeper, :server, chef_node[:zookeeper])
+ # aspects[:directory].should_not be_empty
+ # aspects[:directory].each{|asp| asp.should be_a(ClusterChef::DirectoryAspect) }
+ # end
+ # it 'harvests attributes ending with "_dir"' do
+ # directory_aspects = ClusterChef::DirectoryAspect.harvest(chef_context, :flume, :node, chef_node[:flume])
+ # directory_aspects.should == [
+ # ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/flume/conf"),
+ # ClusterChef::DirectoryAspect.new("data", :data, "/data/db/flume"),
+ # ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/flume"),
+ # ClusterChef::DirectoryAspect.new("log", :log, "/var/log/flume"),
+ # ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/flume"),
+ # ]
+ # end
+ # it 'harvests plural directory sets ending with "_dirs"' do
+ # hadoop_namenode = Mash.new(chef_node[:hadoop].to_hash).merge(chef_node[:hadoop][:namenode])
+ # directory_aspects = ClusterChef::DirectoryAspect.harvest(chef_context, :hadoop, :namenode, hadoop_namenode)
+ # directory_aspects.should == [
+ # ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/hadoop/conf"),
+ # ClusterChef::DirectoryAspect.new("data", :data, ["/mnt1/hadoop/hdfs/name", "/mnt2/hadoop/hdfs/name"]),
+ # ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/hadoop"),
+ # ClusterChef::DirectoryAspect.new("log", :log, "/hadoop/log"),
+ # ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/hadoop"),
+ # ClusterChef::DirectoryAspect.new("tmp", :tmp, "/hadoop/tmp"),
+ # ]
+ # end
+ # it 'harvests non-standard dirs' do
+ # chef_node[:flume][:foo_dirs] = ['/var/foo/flume', '/var/bar/flume']
+ # directory_aspects = ClusterChef::DirectoryAspect.harvest(chef_context, :flume, :node, chef_node[:flume])
+ # directory_aspects.should == [
+ # ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/flume/conf"),
+ # ClusterChef::DirectoryAspect.new("data", :data, "/data/db/flume"),
+ # ClusterChef::DirectoryAspect.new("foo", :foo, ["/var/foo/flume", "/var/bar/flume"]),
+ # ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/flume"),
+ # ClusterChef::DirectoryAspect.new("log", :log, "/var/log/flume"),
+ # ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/flume"),
+ # ]
+ # end
+ # it 'finds its associated resource' do
+ # end
+ # context 'permissions' do
+ # it 'finds its mode / owner / group from the associated respo'
+ # end
+ #
+ # # context '#flavor' do
+ # # def good_flavors() [:home, :conf, :log, :tmp, :pid, :data, :lib, :journal, :cache] ; end
+ # # it "accepts #{good_flavors}"
+ # # end
+ # # context '#limits' do
+ # # it 'accepts an arbitrary hash'
+ # # end
+ # end
+ #
+ # describe :ExportedAspect do
+ # context '#files' do
+ # it 'is harvested by Aspects.harvest_all' do
+ # aspects = ClusterChef::Aspect.harvest_all(chef_context, :zookeeper, :server, chef_node[:zookeeper])
+ # aspects[:exported].should_not be_empty
+ # aspects[:exported].each{|asp| asp.should be_a(ClusterChef::ExportedAspect) }
+ # end
+ # it 'harvests attributes beginning with "exported_"' do
+ # exported_aspects = ClusterChef::ExportedAspect.harvest(chef_context, :zookeeper, :server, chef_node[:zookeeper])
+ # exported_aspects.should == [
+ # ClusterChef::ExportedAspect.new("jars", :jars, ["/usr/lib/zookeeper/zookeeper.jar"])
+ # ]
+ # end
+ # it 'harvests multiple examples' do
+ # exported_aspects = ClusterChef::ExportedAspect.harvest(chef_context, :hbase, :master, chef_node[:hbase])
+ # exported_aspects.should == [
+ # ClusterChef::ExportedAspect.new("confs", :confs, ["/etc/hbase/conf/hbase-default.xml", "/etc/hbase/conf/hbase-site.xml"]),
+ # ClusterChef::ExportedAspect.new("jars", :jars, ["/usr/lib/hbase/hbase-0.90.1-cdh3u0.jar", "/usr/lib/hbase/hbase-0.90.1-cdh3u0-tests.jar"])
+ # ]
+ # end
+ # end
+ # end
+
+ # describe :CookbookAspect do
+ # end
+ #
+ # describe :CronAspect do
+ # end
+
+
+ # __________________________________________________________________________
+ #
+ # Utils
+ #
+
+ describe ClusterChef::AttrStruct do
+ let(:car_class){ Struct.new(:name, :model, :doors, :engine){ include ClusterChef::AttrStruct } }
+ let(:engine_class){ Struct.new(:name, :displacement, :cylinders){ include ClusterChef::AttrStruct } }
+ let(:chevy_350){ engine_class.new('chevy', 350, 8) }
+ let(:hot_rod){ car_class.new('39 ford', 'tudor', 2, chevy_350) }
+
+ context '#to_hash' do
+ it('succeeds'){ chevy_350.to_hash.should == { 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8} }
+ it('nests'){ hot_rod.to_hash.should == { "name" => "39 ford", "model" => "tudor", "doors" => 2, "engine"=> { 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8} } }
+ it('is a Hash'){ hot_rod.to_hash.class.should == Hash }
+ end
+
+ context '#to_mash' do
+ it('succeeds') do
+ msh = chevy_350.to_mash
+ msh.should == Mash.new({ 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8})
+ msh['name'].should == 'chevy'
+ msh[:name ].should == 'chevy'
+ end
+ it('nests'){ hot_rod.to_mash.should == Mash.new({ "name" => "39 ford", "model" => "tudor", "doors" => 2, "engine"=> { 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8} }) }
+ it('is a Mash'){ hot_rod.to_mash.class.should == Mash }
+ end
+
+ context 'stores into node' do
+ it 'loads the node from its fixture' do
+ node_json.keys.sort.should == ["apt", "apt_cacher", "aws", "block_device", "chef_environment", "chef_packages", "chef_server", "chef_type", "cloud", "cluster_chef", "cluster_name", "cluster_size", "command", "cpu", "current_user", "discovery", "dmi", "domain", "end", "etc", "facet_index", "facet_name", "filesystem", "firewall", "flume", "fqdn", "ganglia", "groups", "hadoop", "hbase", "hostname", "install_from", "ipaddress", "java", "jruby", "kernel", "languages", "lsb", "macaddress", "memory", "mountable_volumes", "name", "network", "nfs", "node_name", "nodejs", "ntp", "os", "os_version", "pig", "pkg_sets", "platform", "platform_version", "python", "recipes", "redis", "resque", "rstats", "run_list", "runit", "server_tuning", "tags", "thrift", "users", "value_for_platform", "virtualbox", "virtualization", "zookeeper"]
+ chef_node.name.should == 'el_ridiculoso-aqui-0'
+ chef_node[:cloud][:public_ipv4].should == "10.0.2.15"
+ end
+
+ it 'into variable as directed' do
+ hot_rod.store_into_node(chef_node, 'car')
+ chef_node[:car][:model].should == 'tudor'
+ chef_node[:car].should be_a(Chef::Node::Attribute)
+ end
+ end
+
+ context '#dsl_attr' do
+ it 'adds a set-or-return accessor' do
+ klass = Struct.new(:hi){ include ClusterChef::AttrStruct ; dsl_attr(:foo, :kind_of => Integer) }
+ obj = klass.new('yo!')
+ obj.foo(3).should == 3
+ obj.foo.should == 3
+ end
+ end
+ end
+
+ describe :AttrTemplateString do
+ # * (:any attr:) (:node_name:) (:private_ip:) public_ip cluster facet facet_idx
+ end
+
+end
View
0  spec/fixtures/chef_node-el_ridiculoso-aqui-0.json → ...spec/fixtures/chef_node-el_ridiculoso-aqui-0.json
File renamed without changes
View
0  ...fixtures/chef_resources-el_ridiculoso-aqui-0.json → ...fixtures/chef_resources-el_ridiculoso-aqui-0.json
File renamed without changes
View
16 meta-cookbooks/cluster_chef/spec/spec_helper.rb
@@ -0,0 +1,16 @@
+require 'rspec'
+
+CLUSTER_CHEF_DIR = File.expand_path(File.dirname(__FILE__)+'/..') unless defined?(CLUSTER_CHEF_DIR)
+def CLUSTER_CHEF_DIR(*paths) File.join(CLUSTER_CHEF_DIR, *paths); end
+
+require 'chef/node'
+require 'chef/resource_collection'
+require 'chef/providers'
+require 'chef/resources'
+require 'chef/mixin/params_validate'
+
+Dir[CLUSTER_CHEF_DIR("spec/spec_helper/*.rb")].each {|f| require f}
+
+# Configure rspec
+RSpec.configure do |config|
+end
View
103 meta-cookbooks/cluster_chef/spec/spec_helper/dummy_chef.rb
@@ -0,0 +1,103 @@
+
+class DummyNode < Mash
+ attr_accessor :cookbook_collection
+ attr_accessor :name
+
+ def initialize(name, *args, &block)
+ self.name = name
+ super(*args, &block)
+ end
+
+ def to_s
+ "node[#{name}]"
+ end
+end
+
+class DummyRecipe
+ attr_accessor :name, :node, :run_context
+ def initialize(name, run_context)
+ self.name = name
+ self.node = run_context.node
+ self.run_context = run_context
+ end
+end
+
+module ClusterChef
+ module SpecHelper
+
+ def self.included(base)
+base.class_eval do
+let(:dummy_node) do
+ DummyNode.new('el_ridiculoso-aqui-0', {
+ :cluster_name => 'el_ridiculoso',
+ :nfs => {
+ :server => { :user => 'nfsd', :port => 111 },
+ },
+ :chef => { :user => 'chef',
+ :server => { :port => 4000 },
+ :webui => { :port => 4040, :user => 'www-data' },
+ },
+ :announces => {
+ 'cocina-chef-client' => { :timestamp => '20110907' },
+ 'el_ridiculoso-hadoop-namenode' => { :timestamp => '20110907' },
+ 'el_ridiculoso-hadoop-datanode' => { :timestamp => '20110907' },
+ 'el_ridiculoso-redis-server' => { :timestamp => '20110907' },
+ }
+ })
+end
+
+let(:all_nodes) do
+ Mash.new({
+ 'el_ridiculoso-cocina-0' => DummyNode.new('el_ridiculoso-cocina-0', :announces => {
+ 'cocina:chef.server' => { :timestamp => '20110902' },
+ 'cocina:chef.client' => { :timestamp => '20110902' },
+ 'cocina:rabbitmq.server' => { :timestamp => '20110902' },
+ 'cocina:couchdb.server' => { :timestamp => '20110902' },
+ } ),
+ 'el_ridiculoso-pequeno-0' => DummyNode.new('el_ridiculoso-pequeno-0', :announces => {
+ 'cocina:chef.client' => { :timestamp => '20110903' },
+ 'el_ridiculoso-hadoop-datanode' => { :timestamp => '20110903' },
+ 'el_ridiculoso-hadoop-tasktracker' => { :timestamp => '20110903' },
+ } ),
+ # note that this *does* announce tasktracker and *does not* announce redis-server
+ # and lacks all the attributes on the actual node
+ 'el_ridiculoso-aqui-0' => DummyNode.new('el_ridiculoso-aqui-0', :announces => {
+ 'cocina:chef.client' => { :timestamp => '20110903' },
+ 'el_ridiculoso-hadoop-namenode' => { :timestamp => '20110905' },
+ 'el_ridiculoso-hadoop-datanode' => { :timestamp => '20110905' },
+ 'el_ridiculoso-hadoop-tasktracker' => { :timestamp => '20110905' },
+ } ),
+ })
+end
+
+let(:node_json){ JSON.parse(File.read(CLUSTER_CHEF_DIR('spec/fixtures/chef_node-el_ridiculoso-aqui-0.json'))) }
+let(:chef_node) do
+ recipes = node_json.delete('recipes')
+ nd = Chef::Node.new ; nd.consume_attributes(node_json)
+ nd.name(node_json["name"]) ; nd.chef_environment(node_json["chef_environment"])
+ nd.recipes = recipes
+ nd
+end
+
+let(:chef_context) do
+ rc = Chef::RunContext.new(chef_node, [])
+ rc.resource_collection = CHEF_RESOURCE_CLXN
+ rc
+end
+
+let(:dummy_context) do
+ rc = Chef::RunContext.new(dummy_node, [])
+ rc.resource_collection = CHEF_RESOURCE_CLXN
+ rc
+end
+
+let(:recipe){ DummyRecipe.new(:hadoop, chef_context) }
+let(:dummy_recipe){ DummyRecipe.new(:hadoop, dummy_context) }
+
+let(:chef_server_component){ ClusterChef::Component.new(dummy_node, :chef, :server) }
+let(:chef_webui_component ){ ClusterChef::Component.new(dummy_node, :chef, :webui) }
+
+end
+ end
+end
+end
View
532 spec/cluster_chef/discovery_spec.rb
@@ -1,532 +0,0 @@
-require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-
-require 'chef/node'
-require 'chef/resource_collection'
-require 'chef/mixin/params_validate'
-require CLUSTER_CHEF_DIR("meta-cookbooks/cluster_chef/libraries/discovery.rb")
-
-# $: << '/Users/flip/ics/repos/awesome_print/lib'
-require 'ap' # FIXME: remove
-
-CHEF_RESOURCE_CLXN = JSON.parse(File.read(CLUSTER_CHEF_DIR('spec/fixtures/chef_resources-el_ridiculoso-aqui-0.json')))
-
-describe ClusterChef do
-
- let(:node_json){ JSON.parse(File.read(CLUSTER_CHEF_DIR('spec/fixtures/chef_node-el_ridiculoso-aqui-0.json'))) }
- let(:chef_node) do
- recipes = node_json.delete('recipes')
- nd = Chef::Node.new ; nd.consume_attributes(node_json)
- nd.name(node_json["name"]) ; nd.chef_environment(node_json["chef_environment"])
- nd.recipes = recipes
- nd
- end
-
- let(:chef_context) do
- rc = Chef::RunContext.new(chef_node, [])
- rc.resource_collection = CHEF_RESOURCE_CLXN
- rc
- end
-
- class DummyNode < Mash
- attr_accessor :cookbook_collection
- attr_accessor :name
-
- def initialize(name, *args, &block)
- self.name = name
- super(*args, &block)
- end
-
- def to_s
- "node[#{name}]"
- end
- end
-
- class DummyRecipe
- include ClusterChef::Discovery
- include ClusterChef::NodeUtils
- #
- attr_accessor :name, :node, :run_context
- def initialize(name, run_context)
- self.name = name
- self.node = run_context.node
- self.run_context = run_context
- end
- public :discover_all_nodes
- end
-
- let(:dummy_node) do
- DummyNode.new('el_ridiculoso-aqui-0', {
- :cluster_name => 'el_ridiculoso',
- :nfs => { :user => 'nfsd', :port => 111 },
- :chef => { :user => 'chef',
- :server => { :port => 4000 },
- :webui => { :port => 4040, :user => 'www-data' },
- },
- :announces => {
- 'cocina-chef-client' => { :timestamp => '20110907' },
- 'el_ridiculoso-hadoop-namenode' => { :timestamp => '20110907' },
- 'el_ridiculoso-hadoop-datanode' => { :timestamp => '20110907' },
- 'el_ridiculoso-redis-server' => { :timestamp => '20110907' },
- }
- })
- end
-
- let(:dummy_context) do
- rc = Chef::RunContext.new(dummy_node, [])
- rc.resource_collection = CHEF_RESOURCE_CLXN
- rc
- end
-
- let(:recipe){ DummyRecipe.new(:hadoop, chef_context) }
- let(:dummy_recipe){ DummyRecipe.new(:hadoop, dummy_context) }
-
- describe ClusterChef::Discovery do
-
- context '#node_info' do
- def component_info(sys, subsys=nil)
- comp = ClusterChef::Component.new(dummy_node, sys, subsys)
- comp.node_info
- end
- it 'returns a mash' do
- component_info(:chef, :server).should be_a(Mash)
- end
- it 'extracts just the system tree given only system (simple tree)' do
- component_info(:nfs).should == Mash.new({ :user => 'nfsd', :port => 111 })
- end
- it 'extracts just the system tree given only system (complex tree)' do
- component_info(:chef).should == Mash.new({ :user => 'chef', :server => { :port => 4000 }, :webui => { :port => 4040, :user => 'www-data' } })
- end
- it 'extracts the node attribute tree given system and subsystem' do
- component_info(:chef, :server).should == Mash.new({ :user => 'chef', :port => 4000, :server => { :port => 4000 }, :webui => { :port => 4040, :user => 'www-data' } })
- end
- it 'overrides system attrs with subsystem attrs' do
- component_info(:chef, :webui).should == Mash.new({ :user => 'www-data', :port => 4040, :server => { :port => 4000 }, :webui => { :port => 4040, :user => 'www-data' } })
- end
- it 'warns but does not fail if system is missing' do
- Chef::Log.should_receive(:warn).with("no system data in component 'mxyzptlk', node 'node[el_ridiculoso-aqui-0]'")
- component_info(:mxyzptlk).should == Mash.new
- end
- it 'warns but does not fail if subsystem is missing' do
- Chef::Log.should_receive(:warn).with("no subsystem data in component 'chef_zod', node 'node[el_ridiculoso-aqui-0]'")
- component_info(:chef, :zod).should == Mash.new({ :user => 'chef', :server => { :port => 4000 }, :webui => { :port => 4040, :user => 'www-data' } })
- end
- end
-
- context '.announce' do
- it 'returns the announced component' do
- component = recipe.announce(:chef, :server)
- component.should be_a(ClusterChef::Component)
- component.fullname.should == 'el_ridiculoso-chef-server'
- end
- end
-
- let(:all_nodes) do
- Mash.new({
- 'el_ridiculoso-cocina-0' => DummyNode.new('el_ridiculoso-cocina-0', :announces => {
- 'cocina:chef.server' => { :timestamp => '20110902' },
- 'cocina:chef.client' => { :timestamp => '20110902' },
- 'cocina:rabbitmq.server' => { :timestamp => '20110902' },
- 'cocina:couchdb.server' => { :timestamp => '20110902' },
- } ),
- 'el_ridiculoso-pequeno-0' => DummyNode.new('el_ridiculoso-pequeno-0', :announces => {
- 'cocina:chef.client' => { :timestamp => '20110903' },
- 'el_ridiculoso-hadoop-datanode' => { :timestamp => '20110903' },
- 'el_ridiculoso-hadoop-tasktracker' => { :timestamp => '20110903' },
- } ),
- # note that this *does* announce tasktracker and *does not* announce redis-server
- # and lacks all the attributes on the actual node
- 'el_ridiculoso-aqui-0' => DummyNode.new('el_ridiculoso-aqui-0', :announces => {
- 'cocina:chef.client' => { :timestamp => '20110903' },
- 'el_ridiculoso-hadoop-namenode' => { :timestamp => '20110905' },
- 'el_ridiculoso-hadoop-datanode' => { :timestamp => '20110905' },
- 'el_ridiculoso-hadoop-tasktracker' => { :timestamp => '20110905' },
- } ),
- })
- end
-
- context '.discover_all_nodes' do
- before(:each) do
- dummy_recipe.stub!(:search).
- with(:node, 'announces:el_ridiculoso-hadoop-datanode').
- and_return( all_nodes.values_at('el_ridiculoso-aqui-0', 'el_ridiculoso-pequeno-0') )
- dummy_recipe.stub!(:search).
- with(:node, 'announces:el_ridiculoso-hadoop-tasktracker').
- and_return( all_nodes.values_at('el_ridiculoso-aqui-0', 'el_ridiculoso-pequeno-0') )
- dummy_recipe.stub!(:search).
- with(:node, 'announces:el_ridiculoso-redis-server').
- and_return( all_nodes.values_at('el_ridiculoso-aqui-0') )
- dummy_recipe.stub!(:search).
- with(:node, 'announces:cocina-chef-client').
- and_return( all_nodes.values )
- end
- it 'finds nodes matching the request, sorted by timestamp' do
- result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-datanode")
- result.map{|nd| nd.name }.should == ['el_ridiculoso-pequeno-0', 'el_ridiculoso-aqui-0']
- end
-
- it 'replaces itself with a current copy in the search results' do
- result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-datanode")
- result.map{|nd| nd.name }.should == ['el_ridiculoso-pequeno-0', 'el_ridiculoso-aqui-0']
- result[1].should have_key(:nfs)
- end
- it 'finds current node if it has announced (even when the server\'s copy has not)' do
- result = dummy_recipe.discover_all_nodes("el_ridiculoso-redis-server")
- result.map{|nd| nd.name }.should == ['el_ridiculoso-aqui-0']
- result[0].should have_key(:nfs)
- end
- it 'does not find current node if it has not announced (even when the server\'s copy has announced)' do
- result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-tasktracker")
- result.map{|nd| nd.name }.should == ['el_ridiculoso-pequeno-0']
- end
- it 'when no server found warns and returns an empty hash' do
- dummy_recipe.should_receive(:search).
- with(:node, 'announces:el_ridiculoso-hadoop-mxyzptlk').and_return([])
- Chef::Log.should_receive(:warn).with("No node announced for 'el_ridiculoso-hadoop-mxyzptlk'")
- result = dummy_recipe.discover_all_nodes("el_ridiculoso-hadoop-mxyzptlk")
- result.should == []
- end
- end
- end
-
- describe ClusterChef::Component do
-
- context 'registering aspects' do
- it 'adds a set-or-return accessor' do
- klass = Class.new(ClusterChef::Component)
- klass.dsl_attr(:foo, :kind_of => Integer)
- obj = klass.new(dummy_node, :hadoop, :namenode)
- obj.foo(3).should == 3
- obj.foo.should == 3
- end
- end
-
- context 'Disco' do
- context 'works on a complex example' do
-
- it('daemon') do
- subject[:daemon].should == [
- ClusterChef::DaemonAspect.new("hadoop_datanode", 'hadoop_datanode', 'start') ]
- end
- it('port') do
- subject[:port].should == [
- ClusterChef::PortAspect.new("dash_port", :dash, "50075"),
- ClusterChef::PortAspect.new("ipc_port", :ipc, "50020"),
- ClusterChef::PortAspect.new("jmx_dash_port", :jmx_dash, "8006"),
- ClusterChef::PortAspect.new("port", :port, "50010"),
- ]
- end
- it('dashboard') do
- subject[:dashboard].should == [
- ClusterChef::DashboardAspect.new("dash", :http_dash, "http://33.33.33.12:50075/"),
- ClusterChef::DashboardAspect.new("jmx_dash", :jmx_dash, "http://33.33.33.12:8006/"),
- ]
- end
- it('log') do
- subject[:log].should == [
- ClusterChef::LogAspect.new("log", :log, "/hadoop/log")
- ]
- end
- it('directory') do
- subject[:directory].should == [
- ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/hadoop/conf"),
- ClusterChef::DirectoryAspect.new("data", :data, ["/mnt1/hadoop/hdfs/data", "/mnt2/hadoop/hdfs/data"] ),
- ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/hadoop"),
- ClusterChef::DirectoryAspect.new("log", :log, "/hadoop/log"),
- ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/hadoop"),
- ClusterChef::DirectoryAspect.new("tmp", :tmp, "/hadoop/tmp"),
- ]
- end
- it('exported') do
- subject[:exported].should == [
- ClusterChef::ExportedAspect.new("confs", :confs, ["/etc/hadoop/conf/core-site.xml", "/etc/hadoop/conf/hdfs-site.xml", "/etc/hadoop/conf/mapred-site.xml" ]),
- ClusterChef::ExportedAspect.new("jars", :jars, ["/usr/lib/hadoop/hadoop-core.jar","/usr/lib/hadoop/hadoop-examples.jar", "/usr/lib/hadoop/hadoop-test.jar", "/usr/lib/hadoop/hadoop-tools.jar" ]),
- ]
- end
-
- end
- end
-
- end
-
- describe ClusterChef::Aspect do
- let(:foo_aspect){ Struct.new('FooAspect', :name, :description){ include ClusterChef::Aspect } }
- after(:each) do
- Struct.send(:remove_const, :FooAspect) if defined?(Struct::FooAspect)
- ClusterChef::Aspect.registered.delete(:foo)
- ClusterChef::Aspect.registered.should_not include(:foo)
- end
-
- it 'can register itself' do
- ClusterChef::Aspect.registered.should_not include(foo_aspect)
- foo_aspect.register!
- ClusterChef::Aspect.registered.should include(:foo)
- ClusterChef::Aspect.registered.values.should include(foo_aspect)
- end
-
- it 'enumerates all registered aspects' do
- ClusterChef::Aspect.registered.should == Mash.new({
- :port => ClusterChef::PortAspect, :dashboard => ClusterChef::DashboardAspect, :daemon => ClusterChef::DaemonAspect,
- :log => ClusterChef::LogAspect, :directory => ClusterChef::DirectoryAspect,
- :exported => ClusterChef::ExportedAspect
- })
- end
-
- it 'knows its handle' do
- foo_aspect.klass_handle.should == :foo
- end
-
- context '.harvest_all' do
- it 'passes the node to each aspect in turn' do
- foo_aspect.register!
- rc = Chef::RunContext.new(Chef::Node.new, [])
- foo_aspect.should_receive(:harvest).with(rc, :billy, :bob, {})
- ClusterChef::Aspect.harvest_all(rc, :billy, :bob, {})
- end
- end
- end
-
- describe :PortAspect do
- it 'is harvested by Aspects.harvest_all' do
- aspects = ClusterChef::Aspect.harvest_all(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
- aspects[:port].should_not be_empty
- aspects[:port].each{|asp| asp.should be_a(ClusterChef::PortAspect) }
- end
- it 'harvests any "*_port" attributes' do
- port_aspects = ClusterChef::PortAspect.harvest(chef_context, :hadoop, :datanode, chef_node[:hadoop][:datanode])
- port_aspects.should == [
- ClusterChef::PortAspect.new("dash_port", :dash, "50075"),
- ClusterChef::PortAspect.new("ipc_port", :ipc, "50020"),
- ClusterChef::PortAspect.new("jmx_dash_port", :jmx_dash, "8006"),
- ClusterChef::PortAspect.new("port", :port, "50010"),
- ]
- end
- # context '#addrs' do
- # it 'can be marked :critical, :open, :closed or :ignore'
- # it 'marks first private interface open by default'
- # it 'marks other interfaces closed by default'
- # end
- # context '#flavor' do
- # it 'accepts a defined flavor'
- # end
- # context '#monitors' do
- # it 'accepts an arbitrary hash'
- # end
- end
-
- describe :DashboardAspect do
- it 'is harvested by Aspects.harvest_all' do
- aspects = ClusterChef::Aspect.harvest_all(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
- aspects[:dashboard].should_not be_empty
- aspects[:dashboard].each{|asp| asp.should be_a(ClusterChef::DashboardAspect) }
- end
- it 'harvests any "dash_port" attributes' do
- dashboard_aspects = ClusterChef::DashboardAspect.harvest(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
- dashboard_aspects.should == [
- ClusterChef::DashboardAspect.new("dash", :http_dash, "http://33.33.33.12:50070/"),
- ClusterChef::DashboardAspect.new("jmx_dash", :jmx_dash, "http://33.33.33.12:8004/"),
- ]
- end
- it 'by default harvests the url from the private_ip and dash_port'
- it 'lets me set the URL with an explicit template'
- end
-
- describe :DaemonAspect do
- it 'is harvested by Aspects.harvest_all' do
- aspects = ClusterChef::Aspect.harvest_all(chef_context, :hadoop, :namenode, chef_node[:hadoop][:namenode])
- aspects[:daemon].should_not be_empty
- aspects[:daemon].each{|asp| asp.should be_a(ClusterChef::DaemonAspect) }
- end
- it 'harvests its associated service resource' do
- info = Mash.new(chef_node[:zookeeper].to_hash).merge(chef_node[:zookeeper][:server])
- daemon_aspects = ClusterChef::DaemonAspect.harvest(chef_context, :zookeeper, :server, info)
- daemon_aspects.should == [
- ClusterChef::DaemonAspect.new("zookeeper", "zookeeper", 'stop'),
- ]
- end
-
- it 'harvesting many' do
- # rl = chef_node.run_list.map{|s| s.to_s.gsub(/(?:\Arecipe|role)\[([^:]+?)(?:::(.+))?\]\z/, '\1') }.compact.uniq.map(&:to_sym)
- chef_context.node.recipes.map{|x| x.split(/::/) }.uniq.each do |sys, subsys|
- info = Mash.new(chef_node[sys])
- daemon_aspects = ClusterChef::DaemonAspect.harvest(chef_context, sys, subsys, info)
- end
- end
- # context '#run_state' do
- # it 'harvests the :run_state attribute'
- # it 'can be set explicitly'
- # it 'only accepts :start, :stop or :nothing'
- # end
- # context '#boot_state' do
- # it 'harvests the :boot_state attribute'
- # it 'can be set explicitly'
- # it 'only accepts :enable, :disable or nil'
- # end
- # context '#pattern' do
- # it 'harvests the :pattern attribute from the associated service resource'
- # it 'is not settable explicitly'
- # end
- # context '#limits' do
- # it 'accepts an arbitrary hash'
- # it 'harvests the :limits hash'
- # end
- end
-
- describe :LogAspect do
- it 'is harvested by Aspects.harvest_all' do
- aspects = ClusterChef::Aspect.harvest_all(chef_context, :flume, :node, chef_node[:flume])
- aspects[:log].should_not be_empty
- aspects[:log].each{|asp| asp.should be_a(ClusterChef::LogAspect) }
- end
- it 'harvests any "log_dir" attributes' do
- log_aspects = ClusterChef::LogAspect.harvest(chef_context, :flume, :node, chef_node[:flume])
- log_aspects.should == [
- ClusterChef::LogAspect.new("log", :log, "/var/log/flume"),
- ]
- end
- # context '#flavor' do
- # it 'accepts :http, :log4j, or :rails'
- # end
- end
-
- describe :DirectoryAspect do
- it 'is harvested by Aspects.harvest_all' do
- aspects = ClusterChef::Aspect.harvest_all(chef_context, :zookeeper, :server, chef_node[:zookeeper])
- aspects[:directory].should_not be_empty
- aspects[:directory].each{|asp| asp.should be_a(ClusterChef::DirectoryAspect) }
- end
- it 'harvests attributes ending with "_dir"' do
- directory_aspects = ClusterChef::DirectoryAspect.harvest(chef_context, :flume, :node, chef_node[:flume])
- directory_aspects.should == [
- ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/flume/conf"),
- ClusterChef::DirectoryAspect.new("data", :data, "/data/db/flume"),
- ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/flume"),
- ClusterChef::DirectoryAspect.new("log", :log, "/var/log/flume"),
- ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/flume"),
- ]
- end
- it 'harvests plural directory sets ending with "_dirs"' do
- hadoop_namenode = Mash.new(chef_node[:hadoop].to_hash).merge(chef_node[:hadoop][:namenode])
- directory_aspects = ClusterChef::DirectoryAspect.harvest(chef_context, :hadoop, :namenode, hadoop_namenode)
- directory_aspects.should == [
- ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/hadoop/conf"),
- ClusterChef::DirectoryAspect.new("data", :data, ["/mnt1/hadoop/hdfs/name", "/mnt2/hadoop/hdfs/name"]),
- ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/hadoop"),
- ClusterChef::DirectoryAspect.new("log", :log, "/hadoop/log"),
- ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/hadoop"),
- ClusterChef::DirectoryAspect.new("tmp", :tmp, "/hadoop/tmp"),
- ]
- end
- it 'harvests non-standard dirs' do
- chef_node[:flume][:foo_dirs] = ['/var/foo/flume', '/var/bar/flume']
- directory_aspects = ClusterChef::DirectoryAspect.harvest(chef_context, :flume, :node, chef_node[:flume])
- directory_aspects.should == [
- ClusterChef::DirectoryAspect.new("conf", :conf, "/etc/flume/conf"),
- ClusterChef::DirectoryAspect.new("data", :data, "/data/db/flume"),
- ClusterChef::DirectoryAspect.new("foo", :foo, ["/var/foo/flume", "/var/bar/flume"]),
- ClusterChef::DirectoryAspect.new("home", :home, "/usr/lib/flume"),
- ClusterChef::DirectoryAspect.new("log", :log, "/var/log/flume"),
- ClusterChef::DirectoryAspect.new("pid", :pid, "/var/run/flume"),
- ]
- end
- it 'finds its associated resource' do
- end
- context 'permissions' do
- it 'finds its mode / owner / group from the associated respo'
- end
-
- # context '#flavor' do
- # def good_flavors() [:home, :conf, :log, :tmp, :pid, :data, :lib, :journal, :cache] ; end
- # it "accepts #{good_flavors}"
- # end
- # context '#limits' do
- # it 'accepts an arbitrary hash'
- # end
- end
-
- describe :ExportedAspect do
- context '#files' do
- it 'is harvested by Aspects.harvest_all' do
- aspects = ClusterChef::Aspect.harvest_all(chef_context, :zookeeper, :server, chef_node[:zookeeper])
- aspects[:exported].should_not be_empty
- aspects[:exported].each{|asp| asp.should be_a(ClusterChef::ExportedAspect) }
- end
- it 'harvests attributes beginning with "exported_"' do
- exported_aspects = ClusterChef::ExportedAspect.harvest(chef_context, :zookeeper, :server, chef_node[:zookeeper])
- exported_aspects.should == [
- ClusterChef::ExportedAspect.new("jars", :jars, ["/usr/lib/zookeeper/zookeeper.jar"])
- ]
- end
- it 'harvests multiple examples' do
- exported_aspects = ClusterChef::ExportedAspect.harvest(chef_context, :hbase, :master, chef_node[:hbase])
- exported_aspects.should == [
- ClusterChef::ExportedAspect.new("confs", :confs, ["/etc/hbase/conf/hbase-default.xml", "/etc/hbase/conf/hbase-site.xml"]),
- ClusterChef::ExportedAspect.new("jars", :jars, ["/usr/lib/hbase/hbase-0.90.1-cdh3u0.jar", "/usr/lib/hbase/hbase-0.90.1-cdh3u0-tests.jar"])
- ]
- end
- end
- end
-
- # describe :CookbookAspect do
- # end
- #
- # describe :CronAspect do
- # end
-
-
- # __________________________________________________________________________
- #
- # Utils
- #
-
- describe ClusterChef::AttrStruct do
- let(:car_class){ Struct.new(:name, :model, :doors, :engine){ include ClusterChef::AttrStruct } }
- let(:engine_class){ Struct.new(:name, :displacement, :cylinders){ include ClusterChef::AttrStruct } }
- let(:chevy_350){ engine_class.new('chevy', 350, 8) }
- let(:hot_rod){ car_class.new('39 ford', 'tudor', 2, chevy_350) }
-
- context '#to_hash' do
- it('succeeds'){ chevy_350.to_hash.should == { 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8} }
- it('nests'){ hot_rod.to_hash.should == { "name" => "39 ford", "model" => "tudor", "doors" => 2, "engine"=> { 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8} } }
- it('is a Hash'){ hot_rod.to_hash.class.should == Hash }
- end
-
- context '#to_mash' do
- it('succeeds') do
- msh = chevy_350.to_mash
- msh.should == Mash.new({ 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8})
- msh['name'].should == 'chevy'
- msh[:name ].should == 'chevy'
- end
- it('nests'){ hot_rod.to_mash.should == Mash.new({ "name" => "39 ford", "model" => "tudor", "doors" => 2, "engine"=> { 'name' => 'chevy', 'displacement' => 350, 'cylinders' => 8} }) }
- it('is a Mash'){ hot_rod.to_mash.class.should == Mash }
- end
-
- context 'stores into node' do
- it 'loads the node from its fixture' do
- node_json.keys.should == ["chef_type", "name", "chef_environment", "languages", "kernel", "os", "os_version", "virtualization", "hostname", "fqdn", "domain", "network", "ipaddress", "macaddress", "virtualbox", "chef_packages", "etc", "current_user", "dmi", "cloud", "command", "lsb", "platform", "platform_version", "memory", "block_device", "filesystem", "cpu", "node_name", "cluster_name", "facet_name", "facet_index", "chef_server", "nfs", "recipes", "pkg_sets", "server_tuning", "java", "apt", "mountable_volumes", "hadoop", "hbase", "zookeeper", "flume", "end", "tags", "value_for_platform", "runit", "provides_service", "cluster_chef", "apt_cacher", "ntp", "users", "firewall", "thrift", "python", "install_from", "groups", "cluster_size", "ganglia", "redis", "resque", "pig", "rstats", "nodejs", "jruby", "aws", "run_list"]
- chef_node.name.should == 'el_ridiculoso-aqui-0'
- chef_node[:cloud][:public_ipv4].should == "10.0.2.15"
- end
-
- it 'into variable as directed' do
- hot_rod.store_into_node(chef_node, 'car')
- chef_node[:car][:model].should == 'tudor'
- chef_node[:car].should be_a(Chef::Node::Attribute)
- end
- end
-
- context '#dsl_attr' do
- it 'adds a set-or-return accessor' do
- klass = Struct.new(:hi){ include AttrStruct ; dsl_attr(:foo, :kind_of => Integer) }
- obj = klass.new('yo!')
- obj.foo(3).should == 3
- obj.foo.should == 3
- end
- end
- end
-
- describe :AttrTemplateString do
- # * (:any attr:) (:node_name:) (:private_ip:) public_ip cluster facet facet_idx
- end
-
-end
Please sign in to comment.
Something went wrong with that request. Please try again.