souffle 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Gemfile +9 -3
  2. data/README.md +6 -0
  3. data/bin/{souffle-server → souffle} +0 -0
  4. data/lib/souffle.rb +8 -8
  5. data/lib/souffle/application.rb +15 -10
  6. data/lib/souffle/application/souffle-server.rb +90 -5
  7. data/lib/souffle/config.rb +88 -59
  8. data/lib/souffle/daemon.rb +156 -0
  9. data/lib/souffle/exceptions.rb +29 -17
  10. data/lib/souffle/http.rb +43 -0
  11. data/lib/souffle/log.rb +11 -14
  12. data/lib/souffle/node.rb +91 -53
  13. data/lib/souffle/node/runlist.rb +16 -18
  14. data/lib/souffle/node/runlist_item.rb +43 -36
  15. data/lib/souffle/node/runlist_parser.rb +60 -62
  16. data/lib/souffle/polling_event.rb +110 -0
  17. data/lib/souffle/provider.rb +231 -23
  18. data/lib/souffle/provider/aws.rb +654 -7
  19. data/lib/souffle/provider/vagrant.rb +42 -5
  20. data/lib/souffle/provisioner.rb +55 -0
  21. data/lib/souffle/provisioner/node.rb +157 -0
  22. data/lib/souffle/provisioner/system.rb +195 -0
  23. data/lib/souffle/redis_client.rb +8 -0
  24. data/lib/souffle/redis_mixin.rb +40 -0
  25. data/lib/souffle/server.rb +42 -8
  26. data/lib/souffle/ssh_monkey.rb +8 -0
  27. data/lib/souffle/state.rb +16 -0
  28. data/lib/souffle/system.rb +139 -37
  29. data/lib/souffle/template.rb +30 -0
  30. data/lib/souffle/templates/Vagrantfile.erb +41 -0
  31. data/lib/souffle/version.rb +6 -0
  32. data/spec/config_spec.rb +20 -0
  33. data/spec/log_spec.rb +24 -0
  34. data/spec/{runlist_parser_spec.rb → node/runlist_parser_spec.rb} +1 -1
  35. data/spec/{runlist_spec.rb → node/runlist_spec.rb} +1 -1
  36. data/spec/node_spec.rb +43 -8
  37. data/spec/provider_spec.rb +56 -0
  38. data/spec/providers/aws_provider_spec.rb +114 -0
  39. data/spec/providers/vagrant_provider_spec.rb +22 -0
  40. data/spec/provisioner_spec.rb +47 -0
  41. data/spec/spec_helper.rb +8 -0
  42. data/spec/system_spec.rb +242 -13
  43. data/spec/template_spec.rb +20 -0
  44. data/spec/templates/example_template.erb +1 -0
  45. metadata +125 -30
  46. data/bin/souffle-worker +0 -7
  47. data/lib/souffle/application/souffle-worker.rb +0 -46
  48. data/lib/souffle/providers.rb +0 -2
  49. data/lib/souffle/worker.rb +0 -14
@@ -0,0 +1,8 @@
1
+ require 'souffle/redis_mixin'
2
+
3
+ class Souffle::Redis
4
+ extend Souffle::RedisMixin
5
+
6
+ # Force initialization of the redis client (@redis).
7
+ init
8
+ end
@@ -0,0 +1,40 @@
1
+ require 'eventmachine'
2
+ require 'redis'
3
+
4
+ # A singleton mixin adapter similar to mixlib/log.
5
+ #
6
+ # @example
7
+ #
8
+ # require 'souffle/redis_mixin'
9
+ #
10
+ # class MyRedis
11
+ # extend Souffle::RedisMixin
12
+ # end
13
+ #
14
+ # MyRedis.set('awesome', 'cool')
15
+ # MyRedis.get('awesome')
16
+ #
17
+ module Souffle::RedisMixin
18
+ attr_reader :redis
19
+
20
+ # Initializes the redis client (uses synchrony if Eventmachine is running).
21
+ def init(*opts)
22
+ if EM.reactor_running?
23
+ @redis ||= Redis.new({ :driver => :synchrony }.merge(*opts))
24
+ else
25
+ @redis ||= Redis.new(*opts)
26
+ end
27
+ end
28
+
29
+ # The singleton redis object, initializes if it doesn't exist.
30
+ def redis
31
+ @redis || init
32
+ end
33
+
34
+ # Pass any other method calls to the underlying redis object created with
35
+ # init. If this method is hit before the call to Souffle::RedisMixin.init
36
+ # has been made, it will call Souffle::RedisMixin.init() with no arguments.
37
+ def method_missing(method_symbol, *args, &blk)
38
+ redis.send(method_symbol, *args, &blk)
39
+ end
40
+ end
@@ -1,14 +1,48 @@
1
- module Souffle
2
- # The souffle server and management daemon.
3
- class Server
1
+ require 'thin'
2
+ require 'eventmachine'
3
+ require 'em-synchrony'
4
+ require 'souffle/http'
4
5
 
5
- # Creates a new souffle server.
6
- def initialize
6
+ # The souffle server and management daemon.
7
+ class Souffle::Server
8
+
9
+ # Creates a new souffle server.
10
+ def initialize
11
+ end
12
+
13
+ # Runs the server.
14
+ def run
15
+ if Souffle::Config[:server]
16
+ EM.synchrony do
17
+ @app = Rack::Builder.new do
18
+ use Rack::Lint
19
+ use Rack::ShowExceptions
20
+ run Rack::Cascade.new([Souffle::Http])
21
+ end.to_app
22
+
23
+ Rack::Handler.get(:thin).run(@app, rack_options)
24
+ end
7
25
  end
26
+ end
27
+
28
+ # Gets the rack options from the configuration.
29
+ #
30
+ # @return [ Hash ] The rack options from Souffle::Config.
31
+ def rack_options
32
+ opts = Hash.new
33
+ Souffle::Config.configuration.each do |k,v|
34
+ if /^rack/ =~ k.to_s
35
+ param = k.to_s.gsub('rack_', '')
8
36
 
9
- # Runs the server.
10
- def run
37
+ case param
38
+ when "environment"
39
+ opts[param.to_sym] = v
40
+ else
41
+ opts[param.capitalize.to_sym] = v
42
+ end
43
+ end
11
44
  end
12
-
45
+ opts
13
46
  end
47
+
14
48
  end
@@ -0,0 +1,8 @@
1
+ require 'em-ssh'
2
+
3
+ # Monkeypatching connect on ssh so it doesn't spam the log.
4
+ class Net::SSH::Authentication::KeyManager
5
+ def use_agent?
6
+ false
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ require 'souffle/redis_client'
2
+
3
+ class Souffle::State
4
+ class << self
5
+
6
+ # The Souffle::State prefix for Redis.
7
+ def prefix
8
+ "souffle_state_"
9
+ end
10
+
11
+ # Returns the current system states.
12
+ def status
13
+ Souffle::Redis.get("#{Souffle::State.prefix}status")
14
+ end
15
+ end
16
+ end
@@ -1,51 +1,153 @@
1
- module Souffle
2
- # A system description with nodes and the statemachine to manage them.
3
- class System
4
- attr_reader :nodes, :root, :provider
5
-
6
- state_machine :state, :initial => :uninitialized do
7
- before_transition :uninitialized => any - :uninitialized,
8
- :do => :initialize_provider
9
-
10
- around_transition do |system, transition, block|
11
- start = Time.now
12
- block.call
13
- system.time_used += Time.now - start
14
- end
15
- end
1
+ require 'set'
16
2
 
17
- # Creates a new souffle system, defaulting to using Vagrant as a provider.
18
- #
19
- # @param [ String ] provider The provider to use for the given system.
20
- def initialize(provider="Vagrant")
21
- initialize_provider(provider)
22
- super() # NOTE: This is here to initialize state_machine.
3
+ # A system description with nodes and the statemachine to manage them.
4
+ class Souffle::System
5
+ attr_reader :nodes
6
+ attr_accessor :options, :provisioner
7
+
8
+ # Creates a new souffle system.
9
+ def initialize
10
+ @nodes = []
11
+ @options = {}
12
+ end
13
+
14
+ # Adds a node to the system tree.
15
+ #
16
+ # @param [ Souffle::Node ] node The node to add into the tree.
17
+ def add(node)
18
+ node.system = self
19
+ @nodes << node
20
+ end
21
+
22
+ # Checks node dependencies and rebalances them accordingly.
23
+ #
24
+ # If a node has no dependencies, it's a root node!
25
+ # If a node has depdendencies, setup the node's parents.
26
+ def rebalance_nodes
27
+ clear_node_heirarchy
28
+ dependent_nodes.each { |n| setup_node_parents(n) }
29
+ end
30
+
31
+ # Clears all parents and children from nodes to prepare to rebalancing.
32
+ def clear_node_heirarchy
33
+ @nodes.each { |n| n.parents = []; n.children = [] }
34
+ end
35
+
36
+ # Finds all of a nodes parent dependencies and setup the parents.
37
+ #
38
+ # @param [ Souffle::Node ] node The node to check and configure.
39
+ def setup_node_parents(node)
40
+ optimal_deps = optimized_node_dependencies(node)
41
+ optimal_deps.each { |n, node_deps| n.add_child(node) }
42
+ end
43
+
44
+ # Gets all of the node dependencies on the system.
45
+ #
46
+ # @param [ Souffle::Node ] node The node to retrieve dependencies for.
47
+ #
48
+ # @return [ Array ] The tuple of [ node, dependency_list ] for the node.
49
+ def dependencies_on_system(node)
50
+ node_dependencies = []
51
+ nodes_except(node).each do |n|
52
+ is_dependant, dep_list = node.depends_on?(n)
53
+ node_dependencies << [n, dep_list] if is_dependant
23
54
  end
55
+ node_dependencies
56
+ end
24
57
 
25
- def initialize_provider(provider)
26
- @provider = Souffle::Provider.const_get(provider.to_sym).new
27
- rescue
28
- raise Souffle::Exceptions::InvalidProvider,
29
- "The provider Souffle::Provider::#{provider} does not exist."
58
+ # Returns a dependency to node list mapping.
59
+ #
60
+ # @return [ Hash ] The mapping of depdencies to nodes.
61
+ def dependency_mapping(node)
62
+ mapping = {}
63
+ dependencies_on_system(node).each do |n, deps|
64
+ deps.each do |dep|
65
+ mapping[dep] ||= []; mapping[dep] << n
66
+ end
30
67
  end
68
+ mapping
69
+ end
31
70
 
32
- # Proxy to the provider setup routine.
33
- def setup_provider
34
- @provider.setup
71
+ # The optimized the node dependencies for the system.
72
+ #
73
+ # @param [ Souffle::Node ] node The node that you want to optimize.
74
+ def optimized_node_dependencies(node)
75
+ deps = Set.new
76
+ dependency_mapping(node).each do |dep, nodes|
77
+ deps << nodes.sort_by { |n| n.weight }.first
35
78
  end
79
+ deps.to_a
80
+ end
81
+
82
+ # Returns the list of all nodes except the given node.
83
+ #
84
+ # @return [ Array ] The list of all nodes except the given node.
85
+ def nodes_except(node)
86
+ @nodes.select { |n| n != node }
87
+ end
88
+
89
+ # Returns the list of all root nodes.
90
+ #
91
+ # @note We use dependencies here to validate whether a node is root here
92
+ # because the parents are only determined after a rebalance is run.
93
+ #
94
+ # @return [ Array ] The list of all root nodes. (Nodes without parents).
95
+ def roots
96
+ @nodes.select { |n| n.dependencies.empty? }
97
+ end
98
+
99
+ # Returns the list of all dependent nodes.
100
+ #
101
+ # @return [ Array ] The list of all dependant nodes.
102
+ def dependent_nodes
103
+ @nodes.select { |n| n.dependencies.any? }
104
+ end
36
105
 
37
- # Adds the root node to the system.
106
+ # Tries to fetch an option parameter otherwise it grabs it from config.
107
+ #
108
+ # @param [ Symbol ] opt The option to try and fetch.
109
+ #
110
+ # @return [ String ] The option return value.
111
+ def try_opt(opt)
112
+ options.fetch(opt, Souffle::Config[opt])
113
+ rescue
114
+ nil
115
+ end
116
+
117
+ class << self
118
+ # Creates a new system from a given hash.
38
119
  #
39
- # @param [ Souffle::Node ] node The node to become to root node.
40
- def root=(node)
41
- @root = node
120
+ # @param [ Hash ] system_hash The hash representation of the system.
121
+ def from_hash(system_hash)
122
+ guarentee_valid_hash(system_hash)
123
+ system_hash[:options] ||= {}
124
+
125
+ sys = Souffle::System.new
126
+ system_hash[:nodes].each do |n|
127
+ n[:options] ||= Hash.new
128
+
129
+ node = Souffle::Node.new
130
+ node.name = n[:name]
131
+ Array(n[:run_list]).each { |rl| node.run_list << rl }
132
+ Array(n[:dependencies]).each { |dep| node.dependencies << dep }
133
+ node.options = system_hash[:options].merge(n[:options])
134
+ node.options[:attributes] ||= Hash.new
135
+ sys.add(node)
136
+ end
137
+ sys
42
138
  end
43
139
 
44
- # Adds a node to the system tree.
140
+ private
141
+
142
+ # Guarentee that the system hash that was passed in is valid.
45
143
  #
46
- # @param [ Souffle::Node ] node The node to add into the tree.
47
- def add(node)
144
+ # @param [ Hash ] system_hash The hash representation of the system.
145
+ def guarentee_valid_hash(system_hash)
146
+ if system_hash.nil? or !system_hash.has_key?(:nodes)
147
+ raise Souffle::Exceptions::InvalidSystemHash,
148
+ "The system hash must have a nodes key with a list of nodes."
149
+ end
48
150
  end
49
-
50
151
  end
152
+
51
153
  end
@@ -0,0 +1,30 @@
1
+ require 'erb'
2
+ require 'tilt'
3
+
4
+ # Template wrapper around the Tilt Template Abstraction Library.
5
+ class Souffle::Template
6
+
7
+ # Creates a new template.
8
+ #
9
+ # @param [ String ] template The name of the template to render.
10
+ def initialize(template)
11
+ @template = Tilt.new(
12
+ File.expand_path("#{Souffle::Template.template_path}/#{template}"))
13
+ end
14
+
15
+ # Renders the template with the given binding.
16
+ #
17
+ # @param [ Object ] binding The binding object for the template.
18
+ #
19
+ # @return [ String ] The rendered template.
20
+ def render(binding)
21
+ @template.render(binding)
22
+ end
23
+
24
+ # Helper pointing to the default templates path.
25
+ #
26
+ # @return [ String ] The path to the Souffle templates.
27
+ def self.template_path
28
+ File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
29
+ end
30
+ end
@@ -0,0 +1,41 @@
1
+ # This file is generated by Souffle (<%= version %>)
2
+
3
+ require 'mixlib/config'
4
+ require 'yajl'
5
+
6
+ class SouffleCfg
7
+ extend Mixlib::Config
8
+
9
+ def self.from_file(filename, parser="ruby")
10
+ send("from_file_#{parser}".to_sym, filename)
11
+ end
12
+
13
+ def self.from_file_ruby(filename)
14
+ self.instance_eval(IO.read(filename), filename, 1)
15
+ end
16
+
17
+ def self.from_file_json(filename)
18
+ self.from_stream_json(IO.read(filename))
19
+ end
20
+
21
+ def self.from_stream_json(input)
22
+ parser = Yajl::Parser.new(:symbolize_keys => true)
23
+ configuration.merge!(parser.parse(input))
24
+ end
25
+
26
+ nodes = []
27
+ end
28
+
29
+ cfg = File.join(File.dirname(__FILE__), 'souffle.json')
30
+ SouffleCfg.from_file(cfg, "json") if File.exists? cfg
31
+
32
+ SouffleCfg[:nodes] = Array(SouffleCfg[:nodes])
33
+
34
+ Vagrant::Config.run do |config|
35
+ SouffleCfg[:nodes].each do |node|
36
+ config.vm_box = config[:vm_box]
37
+
38
+ node.vm.provision :chef_solo do |chef|
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ # An orchestrator for setting up isolated chef-managed systems.
2
+ module Souffle
3
+ # The current souffle version.
4
+ VERSION = "0.0.2"
5
+ end
6
+
data/spec/config_spec.rb CHANGED
@@ -1,6 +1,26 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "Souffle::Config" do
4
+ after(:each) do
5
+ Souffle::Config.configure { |c| c.delete(:random_something) }
6
+ Souffle::Config[:aws_access_key] = ""
7
+ Souffle::Config[:aws_access_secret] = ""
8
+ end
9
+
10
+ it "should have default values for aws, rack and vagrant" do
11
+ %w{ log_level log_location provider
12
+ user group umask aws_access_key aws_access_secret
13
+ rack_host rack_port rack_environment vagrant_dir }.each do |cfg|
14
+ cfg = cfg.to_sym
15
+ Souffle::Config.configuration.keys.include?(cfg).should eql(true)
16
+ end
17
+ end
18
+
19
+ it "should have a proper default vagrant directory" do
20
+ vagrant_dir = "#{ENV['HOME']}/vagrant/vms"
21
+ Souffle::Config[:vagrant_dir].should eql(vagrant_dir)
22
+ end
23
+
4
24
  it "should be able to read from a ruby config file" do
5
25
  config = File.join(File.dirname(__FILE__), 'config', 'example.rb')
6
26
  Souffle::Config.from_file(config)
data/spec/log_spec.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'stringio'
2
+ require 'tempfile'
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+
5
+ describe "Souffle::Log" do
6
+ before(:each) do
7
+ Souffle::Log.reset!
8
+ end
9
+
10
+ it "should ave the ability to write directly to the log" do
11
+ io = StringIO.new
12
+ Souffle::Log.init(io)
13
+ Souffle::Log << "Example Log"
14
+ io.string.should eql("Example Log")
15
+ end
16
+
17
+ it "should have the ability to write to a specific log level" do
18
+ io = StringIO.new
19
+ Souffle::Log.init(io)
20
+ Souffle::Log.level(:info)
21
+ Souffle::Log.info "Awesome"
22
+ (/INFO: Awesome/ =~ io.string).should_not eql(nil)
23
+ end
24
+ end