stellar_core_commander 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,10 +21,6 @@ module StellarCoreCommander
21
21
  Base64.strict_decode64(base64_string)
22
22
  end
23
23
 
24
- def base58
25
- Stellar::Util::Base58.stellar
26
- end
27
-
28
24
  extend self
29
25
  end
30
- end
26
+ end
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'set'
2
3
  require 'securerandom'
3
4
 
4
5
  module StellarCoreCommander
@@ -6,15 +7,41 @@ module StellarCoreCommander
6
7
  class DockerProcess < Process
7
8
  include Contracts
8
9
 
10
+ attr_reader :docker_core_image
11
+ attr_reader :docker_state_image
12
+
13
+ Contract({
14
+ docker_state_image: String,
15
+ docker_core_image: String,
16
+ docker_pull: Bool
17
+ } => Any)
18
+ def initialize(params)
19
+ @docker_state_image = params[:docker_state_image]
20
+ @docker_core_image = params[:docker_core_image]
21
+ @docker_pull = params[:docker_pull]
22
+ super
23
+ end
24
+
9
25
  Contract None => Num
10
26
  def required_ports
11
27
  3
12
28
  end
13
29
 
30
+ Contract None => Any
31
+ def launch_heka_container
32
+ $stderr.puts "launching heka container #{heka_container_name}"
33
+ docker %W(run
34
+ --name #{heka_container_name}
35
+ --net container:#{container_name}
36
+ --volumes-from #{container_name}
37
+ -d stellar/heka
38
+ )
39
+ end
40
+
14
41
  Contract None => Any
15
42
  def launch_state_container
16
- $stderr.puts "launching state container #{state_container_name}"
17
- docker %W(run --name #{state_container_name} -p #{postgres_port}:5432 --env-file stellar-core.env -d stellar/stellar-core-state)
43
+ $stderr.puts "launching state container #{state_container_name} from image #{docker_state_image}"
44
+ docker %W(run --name #{state_container_name} -p #{postgres_port}:5432 --env-file stellar-core.env -d #{docker_state_image})
18
45
  raise "Could not create state container" unless $?.success?
19
46
  end
20
47
 
@@ -25,6 +52,13 @@ module StellarCoreCommander
25
52
  raise "Could not drop db: #{database_name}" unless $?.success?
26
53
  end
27
54
 
55
+ Contract None => Any
56
+ def shutdown_heka_container
57
+ return true unless heka_container_running?
58
+ docker %W(rm -f -v #{heka_container_name})
59
+ raise "Could not stop heka container: #{heka_container_name}" unless $?.success?
60
+ end
61
+
28
62
  Contract None => Any
29
63
  def write_config
30
64
  IO.write("#{working_dir}/stellar-core.env", config)
@@ -35,66 +69,75 @@ module StellarCoreCommander
35
69
  write_config
36
70
  end
37
71
 
38
- Contract None => nil
39
- def run
40
- raise "already running!" if running?
41
- setup
72
+ Contract None => Any
73
+ def launch_process
42
74
  launch_state_container
43
75
  launch_stellar_core
76
+ launch_heka_container if atlas
44
77
  end
45
78
 
46
79
  Contract None => Bool
47
80
  def running?
48
- docker %W(inspect #{container_name})
49
- $?.success?
81
+ container_running? container_name
82
+ end
83
+
84
+ Contract None => Bool
85
+ def heka_container_running?
86
+ container_running? heka_container_name
50
87
  end
51
88
 
52
89
  Contract None => Bool
53
90
  def state_container_running?
54
- docker %W(inspect #{state_container_name})
55
- $?.success?
91
+ container_running? state_container_name
56
92
  end
57
93
 
58
94
  Contract None => Any
59
95
  def shutdown
60
96
  return true unless running?
97
+ docker %W(stop #{container_name})
98
+ docker %W(exec #{container_name} rm -rf /history)
61
99
  docker %W(rm -f #{container_name})
62
100
  end
63
101
 
64
102
  Contract None => Any
65
103
  def cleanup
66
104
  database.disconnect
105
+ dump_database
106
+ dump_logs
107
+ dump_cores
108
+ dump_scp_state
109
+ dump_info
110
+ dump_metrics
67
111
  shutdown
68
112
  shutdown_state_container
69
- rm_working_dir
113
+ shutdown_heka_container if atlas
70
114
  end
71
115
 
72
116
  Contract None => Any
73
- def dump_database
74
- Dir.chdir(working_dir) do
75
- host_args = "-H tcp://#{docker_host}:#{docker_port}" if host
76
- `docker #{host_args} exec #{state_container_name} pg_dump -U #{database_user} --clean --no-owner #{database_name}`
77
- end
78
- end
79
-
80
- Contract None => Sequel::Database
81
- def database
82
- @database ||= Sequel.postgres(database_name, host: docker_host, port: postgres_port, user: database_user, password: database_password)
117
+ def dump_logs
118
+ docker ["logs", container_name]
83
119
  end
84
120
 
85
- Contract None => String
86
- def database_name
87
- "stellar"
121
+ Contract None => Any
122
+ def dump_cores
123
+ docker %W(run --volumes-from #{container_name} --rm -e MODE=local #{docker_core_image} /utils/core_file_processor.py)
124
+ docker %W(cp #{container_name}:/cores .)
88
125
  end
89
126
 
90
- Contract None => String
91
- def database_user
92
- "postgres"
127
+ Contract None => Any
128
+ def dump_database
129
+ fname = "#{working_dir}/database-#{Time.now.to_i}-#{rand 100000}.sql"
130
+ $stderr.puts "dumping database to #{fname}"
131
+ host_args = "-H tcp://#{docker_host}:#{docker_port}" if host
132
+ sql = `docker #{host_args} exec #{state_container_name} pg_dump -U #{database_user} --clean --no-owner --no-privileges #{database_name}`
133
+ File.open(fname, 'w') {|f| f.write(sql) }
134
+ fname
93
135
  end
94
136
 
95
137
  Contract None => String
96
- def database_password
138
+ def default_database_url
97
139
  @database_password ||= SecureRandom.hex
140
+ "postgres://postgres:#{@database_password}@#{docker_host}:#{postgres_port}/stellar"
98
141
  end
99
142
 
100
143
  Contract None => Num
@@ -112,6 +155,11 @@ module StellarCoreCommander
112
155
  "scc-state-#{idname}"
113
156
  end
114
157
 
158
+ Contract None => String
159
+ def heka_container_name
160
+ "scc-heka-#{idname}"
161
+ end
162
+
115
163
  Contract None => String
116
164
  def docker_host
117
165
  return host if host
@@ -124,44 +172,167 @@ module StellarCoreCommander
124
172
  docker_host
125
173
  end
126
174
 
175
+ Contract None => Bool
176
+ def docker_pull?
177
+ @docker_pull
178
+ end
179
+
180
+ Contract None => ArrayOf[String]
181
+ def aws_credentials_volume
182
+ if use_s3 and File.exists?("#{ENV['HOME']}/.aws")
183
+ ["-v", "#{ENV['HOME']}/.aws:/root/.aws:ro"]
184
+ else
185
+ []
186
+ end
187
+ end
188
+
189
+ Contract None => Bool
190
+ def use_s3
191
+ if @use_s3
192
+ true
193
+ else
194
+ if host and (@quorum.size > 1)
195
+ $stderr.puts "WARNING: multi-peer with remote docker host, but no s3; history will not be shared"
196
+ end
197
+ false
198
+ end
199
+ end
200
+
201
+ Contract None => ArrayOf[String]
202
+ def shared_history_volume
203
+ if use_s3
204
+ []
205
+ else
206
+ dir = File.expand_path("#{working_dir}/../history-archives")
207
+ Dir.mkdir(dir) unless File.exists?(dir)
208
+ ["-v", "#{dir}:/history"]
209
+ end
210
+ end
211
+
212
+ Contract None => String
213
+ def history_get_command
214
+ cmds = Set.new
215
+ localget = "cp /history/%s/{0} {1}"
216
+ s3get = "aws s3 --region #{@s3_history_region} cp #{@s3_history_prefix}/%s/{0} {1}"
217
+ @quorum.each do |q|
218
+ if q == @name
219
+ next
220
+ end
221
+ if SPECIAL_PEERS.has_key? q
222
+ cmds.add SPECIAL_PEERS[q][:get]
223
+ elsif use_s3
224
+ cmds.add s3get
225
+ else
226
+ cmds.add localget
227
+ end
228
+ end
229
+
230
+ if cmds.size == 0
231
+ if use_s3
232
+ cmds.add s3get
233
+ else
234
+ cmds.add localget
235
+ end
236
+ end
237
+
238
+ if cmds.size != 1
239
+ raise "Conflicting get commands: #{cmds.to_a.inspect}"
240
+ end
241
+ <<-EOS.strip_heredoc
242
+ HISTORY_GET=#{cmds.to_a.first}
243
+ EOS
244
+ end
245
+
246
+ Contract None => String
247
+ def history_put_commands
248
+ if has_special_peers?
249
+ ""
250
+ else
251
+ if use_s3
252
+ <<-EOS.strip_heredoc
253
+ HISTORY_PUT=aws s3 --region #{@s3_history_region} cp {0} #{@s3_history_prefix}/%s/{1}
254
+ EOS
255
+ else
256
+ <<-EOS.strip_heredoc
257
+ HISTORY_PUT=cp {0} /history/%s/{1}
258
+ HISTORY_MKDIR=mkdir -p /history/%s/{0}
259
+ EOS
260
+ end
261
+ end
262
+ end
263
+
264
+ def prepare
265
+ $stderr.puts "preparing #{idname} (dir:#{working_dir})"
266
+ return unless docker_pull?
267
+ docker %W(pull #{docker_state_image})
268
+ docker %W(pull #{docker_core_image})
269
+ docker %W(pull stellar/heka)
270
+ end
271
+
272
+ def crash
273
+ docker %W(exec #{container_name} pkill -ABRT stellar-core)
274
+ end
275
+
127
276
  private
128
277
  def launch_stellar_core
129
278
  $stderr.puts "launching stellar-core container #{container_name}"
130
- docker %W(run
279
+ docker (%W(run
131
280
  --name #{container_name}
132
281
  --net host
133
282
  --volumes-from #{state_container_name}
283
+ ) + aws_credentials_volume + shared_history_volume + %W(
134
284
  --env-file stellar-core.env
135
- -d stellar/stellar-core
136
- /run main fresh forcescp
137
- )
285
+ -d #{docker_core_image}
286
+ /start #{@name} fresh #{"forcescp" if @forcescp}
287
+ ))
138
288
  raise "Could not create stellar-core container" unless $?.success?
139
289
  end
140
290
 
141
291
  Contract None => String
142
292
  def config
293
+ (
143
294
  <<-EOS.strip_heredoc
144
295
  POSTGRES_PASSWORD=#{database_password}
145
296
 
146
- main_POSTGRES_PORT=#{postgres_port}
147
- main_PEER_PORT=#{peer_port}
148
- main_HTTP_PORT=#{http_port}
149
- main_PEER_SEED=#{identity.seed}
150
- main_VALIDATION_SEED=#{identity.seed}
297
+ ENVIRONMENT=scc
298
+ CLUSTER_NAME=#{recipe_name}
299
+ HOSTNAME=#{idname}
300
+
301
+ #{@name}_POSTGRES_PORT=#{postgres_port}
302
+ #{@name}_PEER_PORT=#{peer_port}
303
+ #{@name}_HTTP_PORT=#{http_port}
304
+ #{@name}_PEER_SEED=#{identity.seed}
305
+ #{"#{@name}_VALIDATION_SEED=#{identity.seed}" if @validate}
151
306
 
152
307
  #{"MANUAL_CLOSE=true" if manual_close?}
153
308
 
154
- QUORUM_THRESHOLD=#{threshold}
309
+ ARTIFICIALLY_GENERATE_LOAD_FOR_TESTING=true
310
+ #{"ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true" if @accelerate_time}
311
+ #{"CATCHUP_COMPLETE=true" if @catchup_complete}
155
312
 
156
- PREFERRED_PEERS=#{peers}
157
- QUORUM_SET=#{quorum}
313
+ #{"ATLAS_ADDRESS=" + atlas if atlas}
158
314
 
159
- HISTORY_PEERS=["main"]
315
+ METRICS_INTERVAL=#{atlas_interval}
160
316
 
161
- HISTORY_GET=cp history/%s/{0} {1}
162
- HISTORY_PUT=cp {0} history/%s/{1}
163
- HISTORY_MKDIR=mkdir -p history/%s/{0}
317
+ #{"COMMANDS=[\"ll?level=debug\"]" if @debug}
318
+
319
+ FAILURE_SAFETY=0
320
+ UNSAFE_QUORUM=true
321
+
322
+ PREFERRED_PEERS=#{peer_connections}
323
+ VALIDATORS=#{quorum}
324
+
325
+ HISTORY_PEERS=#{peer_names}
326
+
327
+ NETWORK_PASSPHRASE="#{network_passphrase}"
164
328
  EOS
329
+ ) + history_get_command + history_put_commands
330
+ end
331
+
332
+ def recipe_name
333
+ File.basename($opts[:recipe], '.rb')
334
+ rescue TypeError
335
+ 'recipe_name_not_found'
165
336
  end
166
337
 
167
338
  def docker_port
@@ -183,5 +354,10 @@ module StellarCoreCommander
183
354
  def docker(args)
184
355
  run_cmd "docker", docker_args + args
185
356
  end
357
+
358
+ def container_running?(name)
359
+ docker ['inspect', '-f', '{{.Name}} running: {{.State.Running}}', name]
360
+ $?.success?
361
+ end
186
362
  end
187
363
  end
@@ -4,12 +4,15 @@ module StellarCoreCommander
4
4
  include Contracts
5
5
 
6
6
  attr_reader :pid
7
- attr_reader :wait
8
7
 
9
8
  def initialize(params)
9
+ raise "`host` param is unsupported on LocalProcess, please use `-p docker` for this recipe." if params[:host]
10
+ $stderr.puts "Warning: Ignoring `atlas` param since LocalProcess doesn't support this." if params[:atlas]
11
+
10
12
  super
11
13
  @stellar_core_bin = params[:stellar_core_bin]
12
- raise "`host` param is unsupported on LocalProcess, please use `-p docker` for this recipe." if params[:host]
14
+ @database_url = params[:database].try(:strip)
15
+
13
16
  setup_working_dir
14
17
  end
15
18
 
@@ -21,7 +24,8 @@ module StellarCoreCommander
21
24
 
22
25
  Contract None => Any
23
26
  def initialize_history
24
- run_cmd "./stellar-core", ["--newhist", "main"]
27
+ Dir.mkdir(history_dir) unless File.exists?(history_dir)
28
+ run_cmd "./stellar-core", ["--newhist", @name.to_s]
25
29
  raise "Could not initialize history" unless $?.success?
26
30
  end
27
31
 
@@ -48,19 +52,22 @@ module StellarCoreCommander
48
52
  IO.write("#{@working_dir}/stellar-core.cfg", config)
49
53
  end
50
54
 
55
+ Contract None => String
56
+ def history_dir
57
+ File.expand_path("#{working_dir}/../history-archives")
58
+ end
59
+
51
60
  Contract None => Any
52
61
  def setup
53
62
  write_config
54
- create_database
63
+ create_database unless @keep_database
55
64
  initialize_history
56
65
  initialize_database
57
66
  end
58
67
 
59
68
  Contract None => Num
60
- def run
61
- raise "already running!" if running?
62
- setup
63
- forcescp
69
+ def launch_process
70
+ forcescp if @forcescp
64
71
  launch_stellar_core
65
72
  end
66
73
 
@@ -84,52 +91,50 @@ module StellarCoreCommander
84
91
  ::Process.kill "KILL", @pid
85
92
  end
86
93
 
87
- @wait.value.success?
94
+ @wait_value == 0
88
95
  end
89
96
 
90
97
  Contract None => Any
91
98
  def cleanup
92
99
  database.disconnect
100
+ dump_database
101
+ dump_scp_state
102
+ dump_info
103
+ dump_metrics
93
104
  shutdown
94
- drop_database
95
- rm_working_dir
105
+ drop_database unless @keep_database
96
106
  end
97
107
 
98
108
  Contract None => Any
99
109
  def dump_database
100
- Dir.chdir(@working_dir) do
101
- `pg_dump #{database_name} --clean --no-owner`
102
- end
103
- end
104
-
105
-
106
- Contract None => Sequel::Database
107
- def database
108
- @database ||= Sequel.postgres(database_name)
110
+ fname = "#{working_dir}/database-#{Time.now.to_i}-#{rand 100000}.sql"
111
+ $stderr.puts "dumping database to #{fname}"
112
+ sql = `pg_dump #{database_name} --clean --no-owner --no-privileges`
113
+ File.open(fname, 'w') {|f| f.write(sql) }
114
+ fname
109
115
  end
110
116
 
111
117
  Contract None => String
112
- def database_name
113
- "stellar_core_tmp_#{idname}"
118
+ def default_database_url
119
+ "postgres:///#{idname}"
114
120
  end
115
121
 
116
- Contract None => String
117
- def dsn
118
- "postgresql://dbname=#{database_name}"
122
+ def crash
123
+ `kill -ABRT #{@pid}`
119
124
  end
120
125
 
121
126
  private
122
127
  def launch_stellar_core
123
128
  Dir.chdir @working_dir do
124
- sin, sout, serr, wait = Open3.popen3("./stellar-core")
125
-
126
- # throwaway stdout, stderr (the logs will record any output)
127
- Thread.new{ until (line = sout.gets).nil? ; end }
128
- Thread.new{ until (line = serr.gets).nil? ; end }
129
-
130
- @wait = wait
131
- @pid = wait.pid
129
+ @pid = ::Process.spawn("./stellar-core",
130
+ :out => "stdout.txt",
131
+ :err => "stderr.txt")
132
+ @wait = Thread.new {
133
+ @wait_value = ::Process.wait(@pid);
134
+ $stderr.puts "stellar-core process exited: #{@wait_value}"
135
+ }
132
136
  end
137
+ @pid
133
138
  end
134
139
 
135
140
  Contract None => String
@@ -139,25 +144,61 @@ module StellarCoreCommander
139
144
  RUN_STANDALONE=false
140
145
  HTTP_PORT=#{http_port}
141
146
  PUBLIC_HTTP_PORT=false
142
- PEER_SEED="#{@identity.seed}"
143
- VALIDATION_SEED="#{@identity.seed}"
147
+ NODE_SEED="#{@identity.seed}"
148
+ #{"NODE_IS_VALIDATOR=true" if @validate}
149
+
150
+ ARTIFICIALLY_GENERATE_LOAD_FOR_TESTING=true
151
+ #{"ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true" if @accelerate_time}
152
+ #{"CATCHUP_COMPLETE=true" if @catchup_complete}
144
153
 
145
154
  DATABASE="#{dsn}"
146
- PREFERRED_PEERS=#{peers}
155
+ PREFERRED_PEERS=#{peer_connections}
147
156
 
148
157
  #{"MANUAL_CLOSE=true" if manual_close?}
158
+ #{"COMMANDS=[\"ll?level=debug\"]" if @debug}
159
+
160
+ FAILURE_SAFETY=0
161
+ UNSAFE_QUORUM=true
162
+
163
+ NETWORK_PASSPHRASE="#{network_passphrase}"
149
164
 
150
165
  [QUORUM_SET]
151
- THRESHOLD=#{threshold}
152
166
  VALIDATORS=#{quorum}
153
167
 
154
- [HISTORY.main]
155
- get="cp history/main/{0} {1}"
156
- put="cp {0} history/main/{1}"
157
- mkdir="mkdir -p history/main/{0}"
168
+ #{history_sources}
158
169
  EOS
159
170
  end
160
171
 
172
+ Contract Symbol => String
173
+ def one_history_source(n)
174
+ dir = "#{history_dir}/#{n}"
175
+ if n == @name
176
+ <<-EOS.strip_heredoc
177
+ [HISTORY.#{n}]
178
+ get="cp #{dir}/{0} {1}"
179
+ put="cp {0} #{dir}/{1}"
180
+ mkdir="mkdir -p #{dir}/{0}"
181
+ EOS
182
+ else
183
+ name = n.to_s
184
+ get = "cp #{history_dir}/%s/{0} {1}"
185
+ if SPECIAL_PEERS.has_key? n
186
+ name = SPECIAL_PEERS[n][:name]
187
+ get = SPECIAL_PEERS[n][:get]
188
+ end
189
+ get.sub!('%s', name)
190
+ <<-EOS.strip_heredoc
191
+ [HISTORY.#{name}]
192
+ get="#{get}"
193
+ EOS
194
+ end
195
+ end
196
+
197
+ Contract None => String
198
+ def history_sources
199
+ @quorum.map {|n| one_history_source n}.join("\n")
200
+ end
201
+
161
202
  def setup_working_dir
162
203
  if @stellar_core_bin.blank?
163
204
  search = `which stellar-core`.strip