souffle 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +9 -3
- data/README.md +6 -0
- data/bin/{souffle-server → souffle} +0 -0
- data/lib/souffle.rb +8 -8
- data/lib/souffle/application.rb +15 -10
- data/lib/souffle/application/souffle-server.rb +90 -5
- data/lib/souffle/config.rb +88 -59
- data/lib/souffle/daemon.rb +156 -0
- data/lib/souffle/exceptions.rb +29 -17
- data/lib/souffle/http.rb +43 -0
- data/lib/souffle/log.rb +11 -14
- data/lib/souffle/node.rb +91 -53
- data/lib/souffle/node/runlist.rb +16 -18
- data/lib/souffle/node/runlist_item.rb +43 -36
- data/lib/souffle/node/runlist_parser.rb +60 -62
- data/lib/souffle/polling_event.rb +110 -0
- data/lib/souffle/provider.rb +231 -23
- data/lib/souffle/provider/aws.rb +654 -7
- data/lib/souffle/provider/vagrant.rb +42 -5
- data/lib/souffle/provisioner.rb +55 -0
- data/lib/souffle/provisioner/node.rb +157 -0
- data/lib/souffle/provisioner/system.rb +195 -0
- data/lib/souffle/redis_client.rb +8 -0
- data/lib/souffle/redis_mixin.rb +40 -0
- data/lib/souffle/server.rb +42 -8
- data/lib/souffle/ssh_monkey.rb +8 -0
- data/lib/souffle/state.rb +16 -0
- data/lib/souffle/system.rb +139 -37
- data/lib/souffle/template.rb +30 -0
- data/lib/souffle/templates/Vagrantfile.erb +41 -0
- data/lib/souffle/version.rb +6 -0
- data/spec/config_spec.rb +20 -0
- data/spec/log_spec.rb +24 -0
- data/spec/{runlist_parser_spec.rb → node/runlist_parser_spec.rb} +1 -1
- data/spec/{runlist_spec.rb → node/runlist_spec.rb} +1 -1
- data/spec/node_spec.rb +43 -8
- data/spec/provider_spec.rb +56 -0
- data/spec/providers/aws_provider_spec.rb +114 -0
- data/spec/providers/vagrant_provider_spec.rb +22 -0
- data/spec/provisioner_spec.rb +47 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/system_spec.rb +242 -13
- data/spec/template_spec.rb +20 -0
- data/spec/templates/example_template.erb +1 -0
- metadata +125 -30
- data/bin/souffle-worker +0 -7
- data/lib/souffle/application/souffle-worker.rb +0 -46
- data/lib/souffle/providers.rb +0 -2
- data/lib/souffle/worker.rb +0 -14
@@ -1,15 +1,52 @@
|
|
1
|
-
require 'souffle/
|
1
|
+
require 'souffle/template'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
# The Vagrant souffle provider.
|
4
|
-
class Souffle::Provider::Vagrant < Souffle::Provider
|
5
|
+
class Souffle::Provider::Vagrant < Souffle::Provider::Base
|
6
|
+
attr_reader :vagrant_dir
|
5
7
|
|
6
8
|
# Setup the internal Vagrant configuration and object.
|
7
|
-
def
|
9
|
+
def initialize
|
10
|
+
super()
|
11
|
+
@vagrant_dir = @system.try_opt(:vagrant_dir)
|
12
|
+
create_new_vm_group unless current_folder_has_souffle_config?
|
13
|
+
generate_vagrant_config
|
8
14
|
end
|
9
15
|
|
10
|
-
#
|
11
|
-
|
16
|
+
# Creates a system using vagrant as the provider.
|
17
|
+
#
|
18
|
+
# @param [ Souffle::System ] system The system to instantiate.
|
19
|
+
# @param [ String ] tag_prefix The tag prefix to use for the system.
|
20
|
+
def create_system(system, tag_prefix=nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Takes a node definition and begins the provisioning process.
|
24
|
+
#
|
25
|
+
# @param [ Souffle::Node ] node The node to instantiate.
|
26
|
+
# @param [ String ] tag The tag to use for the node.
|
27
|
+
def create_node(node, tag=nil)
|
28
|
+
end
|
12
29
|
|
13
30
|
# Noop.
|
14
31
|
def create_raid; end
|
32
|
+
|
33
|
+
# Checks if the current folder has the souffle configuration object.
|
34
|
+
#
|
35
|
+
# @return [ Boolean ] Whether or not we're in a souffle Vagrant project.
|
36
|
+
def current_folder_has_souffle_config?
|
37
|
+
File.exists? "#{Dir.pwd}/souffle.json"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates a new virtualmachine group.
|
41
|
+
def create_new_vm_group
|
42
|
+
end
|
43
|
+
|
44
|
+
# Generates the multi-vm configuration.
|
45
|
+
def generate_vagrant_config
|
46
|
+
template = Souffle::Template.new('Vagrantfile.erb')
|
47
|
+
temp_binding = OpenStruct.new
|
48
|
+
temp_binding.version = Souffle::VERSION
|
49
|
+
|
50
|
+
template.render(temp_binding)
|
51
|
+
end
|
15
52
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Starts up the base provisioner class with system and node state machines.
|
2
|
+
class Souffle::Provisioner; end
|
3
|
+
|
4
|
+
require 'souffle/provisioner/node'
|
5
|
+
require 'souffle/provisioner/system'
|
6
|
+
|
7
|
+
# Starts up the base provisioner class with system and node state machines.
|
8
|
+
class Souffle::Provisioner
|
9
|
+
attr_reader :provider, :system
|
10
|
+
|
11
|
+
# Creates a new provisioner.
|
12
|
+
def initialize
|
13
|
+
@provider = initialize_provider
|
14
|
+
@provisioner = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates the system object from a hash.
|
18
|
+
#
|
19
|
+
# @param [ Hash ] system_hash The system represented in hash format.
|
20
|
+
def setup_system(system_hash)
|
21
|
+
@system = Souffle::System.from_hash(system_hash)
|
22
|
+
@provider = initialize_provider(
|
23
|
+
cleanup_provider(@system.try_opt[:provider]))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Cleans up the provider name to match the providers we have.
|
27
|
+
#
|
28
|
+
# @param [ String ] provider The name of the provider to use.
|
29
|
+
#
|
30
|
+
# @return [ String ] The cleaned up provider name.
|
31
|
+
def cleanup_provider(provider)
|
32
|
+
lookups = {}
|
33
|
+
Souffle::Provider.constants.each { |k| lookups[k.to_s.downcase] = k.to_s }
|
34
|
+
lookups.fetch([provider.downcase], provider)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sets up the given provider to be used for the creation of a system.
|
38
|
+
#
|
39
|
+
# @param [ String ] provider The system provider to use for provisioning.
|
40
|
+
def initialize_provider(provider=nil)
|
41
|
+
prv = cleanup_provider(Souffle::Config[:provider])
|
42
|
+
Souffle::Provider.const_get(prv).new
|
43
|
+
rescue Souffle::Exceptions::InvalidAwsKeys => e
|
44
|
+
Souffle::Log.error "#{e.message}:\n#{e.backtrace.join("\n")}"
|
45
|
+
rescue Exception
|
46
|
+
raise Souffle::Exceptions::InvalidProvider,
|
47
|
+
"The provider Souffle::Provider::#{prv} does not exist."
|
48
|
+
end
|
49
|
+
|
50
|
+
# Starts the provisioning process keeping a local lookup to the provisioner.
|
51
|
+
def begin_provisioning
|
52
|
+
@provisioner = Souffle::Provisioner::System.new(@system, @provider)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'state_machine'
|
2
|
+
|
3
|
+
# The node provisioning statemachine.
|
4
|
+
class Souffle::Provisioner::Node
|
5
|
+
attr_accessor :time_used
|
6
|
+
|
7
|
+
state_machine :state, :initial => :initializing do
|
8
|
+
after_transition any => :handling_error, :do => :error_handler
|
9
|
+
after_transition any => :creating, :do => :create
|
10
|
+
after_transition :creating => :booting, :do => :boot
|
11
|
+
after_transition :booting => :partitioning_device,
|
12
|
+
:do => :partition_device
|
13
|
+
after_transition :partitioning_device => :installing_mdadm,
|
14
|
+
:do => :setup_mdadm
|
15
|
+
after_transition :installing_mdadm => :initializing_raid,
|
16
|
+
:do => :setup_raid
|
17
|
+
after_transition :initializing_raid => :formatting_device,
|
18
|
+
:do => :format_device
|
19
|
+
after_transition :formatting_device => :ready_to_provision,
|
20
|
+
:do => :ready
|
21
|
+
after_transition any => :provisioning, :do => :provision
|
22
|
+
|
23
|
+
event :reclaimed do
|
24
|
+
transition any => :creating
|
25
|
+
end
|
26
|
+
|
27
|
+
event :initialized do
|
28
|
+
transition :initializing => :creating
|
29
|
+
end
|
30
|
+
|
31
|
+
event :created do
|
32
|
+
transition :creating => :booting
|
33
|
+
end
|
34
|
+
|
35
|
+
event :booted do
|
36
|
+
transition :booting => :partitioning_device
|
37
|
+
end
|
38
|
+
|
39
|
+
event :partitioned_device do
|
40
|
+
transition :partitioning_device => :installing_mdadm
|
41
|
+
end
|
42
|
+
|
43
|
+
event :mdadm_installed do
|
44
|
+
transition :installing_mdadm => :initializing_raid
|
45
|
+
end
|
46
|
+
|
47
|
+
event :raid_initialized do
|
48
|
+
transition :initializing_raid => :formatting_device
|
49
|
+
end
|
50
|
+
|
51
|
+
event :device_formatted do
|
52
|
+
transition :formatting_device => :ready_to_provision
|
53
|
+
end
|
54
|
+
|
55
|
+
event :begin_provision do
|
56
|
+
transition :ready_to_provision => :provisioning
|
57
|
+
end
|
58
|
+
|
59
|
+
event :provisioned do
|
60
|
+
transition :provisioning => :complete
|
61
|
+
end
|
62
|
+
|
63
|
+
event :error_occurred do
|
64
|
+
transition any => :handling_error
|
65
|
+
end
|
66
|
+
|
67
|
+
event :failed do
|
68
|
+
transition any => :handling_failure
|
69
|
+
end
|
70
|
+
|
71
|
+
around_transition do |system, transition, block|
|
72
|
+
start = Time.now
|
73
|
+
block.call
|
74
|
+
system.time_used += Time.now - start
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Creates a new node provisioner state machine.
|
79
|
+
#
|
80
|
+
# @param [ Souffle::Node ] node The node to manage.
|
81
|
+
# @param [ Fixnum ] max_failures The maximum number of failures.
|
82
|
+
def initialize(node, max_failures=3)
|
83
|
+
@time_used = 0
|
84
|
+
@node = node
|
85
|
+
@max_failures = max_failures
|
86
|
+
super() # NOTE: This is here to initialize state_machine.
|
87
|
+
end
|
88
|
+
|
89
|
+
# Creates the node from an api or command.
|
90
|
+
def create
|
91
|
+
Souffle::Log.info "#{@node.log_prefix} Creating a new node..."
|
92
|
+
provider.create_node(@node)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Boots up the node and waits for ssh.
|
96
|
+
def boot
|
97
|
+
Souffle::Log.info "#{@node.log_prefix} Booting node..."
|
98
|
+
provider.boot(@node)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Installs and sets up mdadm.
|
102
|
+
def setup_mdadm
|
103
|
+
Souffle::Log.info "#{@node.log_prefix} Setting up mdadm..."
|
104
|
+
provider.setup_mdadm(@node)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Partitions the soon to be raid device.
|
108
|
+
def partition_device
|
109
|
+
Souffle::Log.info "#{@node.log_prefix} Partitioning the device..."
|
110
|
+
provider.partition(@node)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Formats a device to the configured filesystem.
|
114
|
+
def format_device
|
115
|
+
Souffle::Log.info "#{@node.log_prefix} Formatting the device..."
|
116
|
+
provider.format_device(@node)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Sets up raid to the configured raid-level.
|
120
|
+
def setup_raid
|
121
|
+
Souffle::Log.info "#{@node.log_prefix} Setting up raid..."
|
122
|
+
provider.setup_raid(@node)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Notify the logger when the node is ready for provisioning.
|
126
|
+
def ready
|
127
|
+
Souffle::Log.info "#{@node.log_prefix} Is ready for provisioning..."
|
128
|
+
end
|
129
|
+
|
130
|
+
# Provisions the given node with a chef/chef-solo run.
|
131
|
+
def provision
|
132
|
+
Souffle::Log.info "#{@node.log_prefix} Provisioning node..."
|
133
|
+
provider.provision(@node)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Kills the node entirely.
|
137
|
+
def kill
|
138
|
+
Souffle::Log.info "#{@node.log_prefix} Killing node..."
|
139
|
+
provider.kill(@node)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Kills the node and restarts the creation loop.
|
143
|
+
def kill_and_recreate
|
144
|
+
Souffle::Log.info "#{@node.log_prefix} Recreating node..."
|
145
|
+
provider.kill_and_recreate(@node)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Handles any
|
149
|
+
def error_handler
|
150
|
+
Souffle::Log.info "#{@node.log_prefix} Handling node error..."
|
151
|
+
end
|
152
|
+
|
153
|
+
# Helper function for the node's system provider.
|
154
|
+
def provider
|
155
|
+
@node.system.provisioner.provider
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'state_machine'
|
2
|
+
|
3
|
+
require 'souffle/polling_event'
|
4
|
+
|
5
|
+
# The system provisioning statemachine.
|
6
|
+
class Souffle::Provisioner::System
|
7
|
+
attr_accessor :time_used, :provider
|
8
|
+
|
9
|
+
attr_reader :max_failures
|
10
|
+
|
11
|
+
state_machine :state, :initial => :initializing do
|
12
|
+
after_transition any => :handling_error, :do => :error_handler
|
13
|
+
after_transition :initializing => :creating, :do => :create
|
14
|
+
after_transition :creating => :provisioning, :do => :provision
|
15
|
+
after_transition any => :initializing, :do => :create
|
16
|
+
|
17
|
+
event :initialized do
|
18
|
+
transition :initializing => :creating
|
19
|
+
end
|
20
|
+
|
21
|
+
event :created do
|
22
|
+
transition :creating => :provisioning
|
23
|
+
end
|
24
|
+
|
25
|
+
event :provisioned do
|
26
|
+
transition :provisioning => :complete
|
27
|
+
end
|
28
|
+
|
29
|
+
event :error_occurred do
|
30
|
+
transition any => :handling_error
|
31
|
+
end
|
32
|
+
|
33
|
+
event :creation_halted do
|
34
|
+
transition any => :failed
|
35
|
+
end
|
36
|
+
|
37
|
+
event :reclaimed do
|
38
|
+
transition any => :initializing
|
39
|
+
end
|
40
|
+
|
41
|
+
around_transition do |system, transition, block|
|
42
|
+
start = Time.now
|
43
|
+
block.call
|
44
|
+
system.time_used += Time.now - start
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creates a new system using a specific provider.
|
49
|
+
#
|
50
|
+
# @param [ Souffle::System ] system The system to provision.
|
51
|
+
# @param [ Souffle::Provider::Base ] provider The provider to use.
|
52
|
+
# @param [ Fixnum ] max_failures the maximum number of failures.
|
53
|
+
# @param [ Fixnum ] timeout The maximum time to wait for node creation.
|
54
|
+
def initialize(system, provider, max_failures=3, timeout=500)
|
55
|
+
@failures = 0
|
56
|
+
@system = system
|
57
|
+
@provider = provider
|
58
|
+
@time_used = 0
|
59
|
+
@timeout = timeout
|
60
|
+
@max_failures = max_failures
|
61
|
+
super() # NOTE: This is here to initialize state_machine.
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates the system from an api or command.
|
65
|
+
def create
|
66
|
+
Souffle::Log.info "[#{system_tag}] Creating a new system..."
|
67
|
+
@system.nodes.each do |node|
|
68
|
+
node.provisioner = Souffle::Provisioner::Node.new(node)
|
69
|
+
node.provisioner.initialized
|
70
|
+
end
|
71
|
+
wait_until_created
|
72
|
+
end
|
73
|
+
|
74
|
+
# Provisioning the system.
|
75
|
+
#
|
76
|
+
# @todo We should really have these provisioned with fibers.
|
77
|
+
def provision
|
78
|
+
Souffle::Log.info "[#{system_tag}] Provisioning the system..."
|
79
|
+
@system.rebalance_nodes
|
80
|
+
@system.nodes.each do |node|
|
81
|
+
when_parents_are_complete(node) { node.provisioner.begin_provision }
|
82
|
+
end
|
83
|
+
wait_until_complete
|
84
|
+
end
|
85
|
+
|
86
|
+
# Wait until all of the parent nodes are in a completed state and yield.
|
87
|
+
def when_parents_are_complete(node)
|
88
|
+
total_nodes = node.parents.size
|
89
|
+
if total_nodes == 0
|
90
|
+
yield if block_given?
|
91
|
+
all_complete = true
|
92
|
+
else
|
93
|
+
all_complete = false
|
94
|
+
end
|
95
|
+
timer = EM::PeriodicTimer.new(2) do
|
96
|
+
nodes_complete = node.parents.select do |n|
|
97
|
+
n.provisioner.state == "complete"
|
98
|
+
end.size
|
99
|
+
|
100
|
+
if nodes_complete == total_nodes
|
101
|
+
all_complete = true
|
102
|
+
timer.cancel
|
103
|
+
yield if block_given?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
EM::Timer.new(@timeout) do
|
108
|
+
unless all_complete
|
109
|
+
Souffle::Log.error "[#{system_tag}] Parent creation timeout reached."
|
110
|
+
timer.cancel
|
111
|
+
error_occurred
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Kills the system.
|
117
|
+
def kill_system
|
118
|
+
# @provider.kill(@system.nodes)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Handles the error state and recreates the system
|
122
|
+
def error_handler
|
123
|
+
@failures += 1
|
124
|
+
if @failures >= @max_failures
|
125
|
+
Souffle::Log.error "[#{system_tag}] Complete failure. Halting Creation."
|
126
|
+
creation_halted
|
127
|
+
else
|
128
|
+
err_msg = "[#{system_tag}] Error creating system. "
|
129
|
+
err_msg << "Killing and recreating..."
|
130
|
+
Souffle::Log.error(err_msg)
|
131
|
+
kill_system
|
132
|
+
reclaimed
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Helper function for the system tag.
|
139
|
+
#
|
140
|
+
# @param [ String ] The system or global tag.
|
141
|
+
def system_tag
|
142
|
+
@system.try_opt(:tag)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Wait until all of the nodes are ready to be provisioned and then continue.
|
146
|
+
def wait_until_created
|
147
|
+
total_nodes = @system.nodes.size
|
148
|
+
all_created = false
|
149
|
+
timer = EM::PeriodicTimer.new(2) do
|
150
|
+
nodes_ready = @system.nodes.select do |n|
|
151
|
+
n.provisioner.state == "ready_to_provision"
|
152
|
+
end.size
|
153
|
+
|
154
|
+
if nodes_ready == total_nodes
|
155
|
+
all_created = true
|
156
|
+
timer.cancel
|
157
|
+
created
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
EM::Timer.new(@timeout) do
|
162
|
+
unless all_created
|
163
|
+
Souffle::Log.error "[#{system_tag}] System creation timeout reached."
|
164
|
+
timer.cancel
|
165
|
+
error_occurred
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Wait until all of the nodes are provisioned and then continue.
|
171
|
+
def wait_until_complete
|
172
|
+
total_nodes = @system.nodes.size
|
173
|
+
all_complete = false
|
174
|
+
timer = EM::PeriodicTimer.new(2) do
|
175
|
+
nodes_ready = @system.nodes.select do |n|
|
176
|
+
n.provisioner.state == "complete"
|
177
|
+
end.size
|
178
|
+
|
179
|
+
if nodes_ready == total_nodes
|
180
|
+
all_complete = true
|
181
|
+
timer.cancel
|
182
|
+
created
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
EM::Timer.new(@timeout) do
|
187
|
+
unless all_complete
|
188
|
+
Souffle::Log.error "[#{system_tag}] System provision timeout reached."
|
189
|
+
timer.cancel
|
190
|
+
error_occurred
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|