etna 0.1.37 → 0.1.38
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/etna/application.rb +73 -9
- data/lib/etna/clients/metis/workflows.rb +4 -3
- data/lib/etna/clients/metis/workflows/ingest_metis_data_workflow.rb +31 -0
- data/lib/etna/clients/metis/workflows/metis_upload_workflow.rb +1 -1
- data/lib/etna/controller.rb +18 -13
- data/lib/etna/filesystem.rb +63 -0
- data/lib/etna/logger.rb +4 -0
- data/lib/etna/route.rb +60 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7c665ee736c06bf1b566467985638cd41a5b3536f848fe2971174aab26ba3a4
|
4
|
+
data.tar.gz: 22bd58b6dd1a4902ee91f6588f53cf56a4bec9db5b42d9236ed9cd7c4310a2f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04ad878b6f9593a06b65b7652ba3b6da9b0a2aa4de57981ef91247b19eb606e5018cc01e45d033ccdaf5b755cb92149e92a1d26218c47fce6c1a77085dce300e
|
7
|
+
data.tar.gz: 26134c1553f33e59e13f5ec737bd11b0bd8e62e1f54c3b1c5963560d1078cb00d5ce0255c01bddae86dc19f9bb38ae5a0dfc6b9d2845e955e55df0299ad1f456
|
data/lib/etna/application.rb
CHANGED
@@ -60,12 +60,61 @@ module Etna::Application
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
# This will cause metrics to persist to a file.
|
64
|
+
# NOTE -- /tmp/metrics.bin should be a persistent mount when using this.
|
65
|
+
# You will still need to export metrics in the text format for the node_exporter on the host machine to
|
66
|
+
# export them to prometheus. Ensure that the /tmp/metrics.bin is on a named volume or a bind mount, either is fine.
|
67
|
+
def enable_job_metrics!
|
68
|
+
require 'prometheus'
|
69
|
+
Prometheus::Client.config.data_store = Prometheus::Client::DataStores::DirectFileStore.new({
|
70
|
+
dir: "/tmp/metrics.bin"
|
71
|
+
})
|
72
|
+
end
|
73
|
+
|
63
74
|
def setup_yabeda
|
75
|
+
application = self.id
|
76
|
+
Yabeda.configure do
|
77
|
+
default_tag :application, application
|
78
|
+
|
79
|
+
group :etna do
|
80
|
+
histogram :response_time do
|
81
|
+
comment "Time spent by a controller returning a response"
|
82
|
+
unit :seconds
|
83
|
+
tags [:controller, :action, :user_hash, :project_name]
|
84
|
+
buckets [0.01, 0.1, 0.3, 0.5, 1, 5]
|
85
|
+
end
|
86
|
+
|
87
|
+
counter :visits do
|
88
|
+
comment "Counts visits to the controller"
|
89
|
+
tags [:controller, :action, :user_hash, :project_name]
|
90
|
+
end
|
91
|
+
|
92
|
+
counter :rollbar_errors do
|
93
|
+
comment "Counts errors detected by and sent to rollbar"
|
94
|
+
end
|
95
|
+
|
96
|
+
gauge :last_command_completion do
|
97
|
+
comment "Unix time of last time command was completed"
|
98
|
+
tags [:command, :status, :application]
|
99
|
+
end
|
100
|
+
|
101
|
+
histogram :command_runtime do
|
102
|
+
comment "Time spent processing a given command"
|
103
|
+
tags [:command, :status, :application]
|
104
|
+
unit :seconds
|
105
|
+
buckets [0.1, 1, 5, 60, 300, 1500]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
64
110
|
Yabeda.configure!
|
65
111
|
end
|
66
112
|
|
113
|
+
# Writes all metrics currently gathered to a text format prometheus file. If /tmp/metrics.prom is bind mounted
|
114
|
+
# to the host directed bound to the node_exporter's file exporter directory, these will be exported to
|
115
|
+
# prometheus. Combine this enable_job_metrics! for maximum effect.
|
67
116
|
def write_job_metrics(name)
|
68
|
-
node_metrics_dir =
|
117
|
+
node_metrics_dir = "/tmp/metrics.prom"
|
69
118
|
::FileUtils.mkdir_p(node_metrics_dir)
|
70
119
|
|
71
120
|
tmp_file = ::File.join(node_metrics_dir, "#{name}.prom.$$")
|
@@ -126,17 +175,32 @@ module Etna::Application
|
|
126
175
|
end
|
127
176
|
|
128
177
|
def run_command(config, *args, &block)
|
178
|
+
application = self.id
|
179
|
+
status = 'success'
|
180
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
129
181
|
cmd, cmd_args, cmd_kwds = find_command(*args)
|
130
|
-
cmd.setup(config)
|
131
182
|
|
132
|
-
|
133
|
-
|
134
|
-
|
183
|
+
begin
|
184
|
+
cmd.setup(config)
|
185
|
+
if block_given?
|
186
|
+
return unless yield [cmd, cmd_args]
|
187
|
+
end
|
135
188
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
189
|
+
cmd.execute(*cmd.fill_in_missing_params(cmd_args), **cmd_kwds)
|
190
|
+
rescue => e
|
191
|
+
Rollbar.error(e)
|
192
|
+
status = 'failed'
|
193
|
+
raise
|
194
|
+
ensure
|
195
|
+
if Yabeda.configured?
|
196
|
+
tags = { command: cmd.class.name, status: status, application: application }
|
197
|
+
dur = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
198
|
+
|
199
|
+
Yabeda.etna.last_command_completion.set(tags, Time.now.to_i)
|
200
|
+
Yabeda.etna.command_runtime.measure(tags, dur)
|
201
|
+
write_job_metrics("run_command")
|
202
|
+
end
|
203
|
+
end
|
140
204
|
end
|
141
205
|
end
|
142
206
|
|
@@ -1,3 +1,4 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
3
|
-
require_relative
|
1
|
+
require_relative "./workflows/metis_download_workflow"
|
2
|
+
require_relative "./workflows/metis_upload_workflow"
|
3
|
+
require_relative "./workflows/sync_metis_data_workflow"
|
4
|
+
require_relative "./workflows/ingest_metis_data_workflow"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "fileutils"
|
3
|
+
require "tempfile"
|
4
|
+
|
5
|
+
module Etna
|
6
|
+
module Clients
|
7
|
+
class Metis
|
8
|
+
class IngestMetisDataWorkflow < Struct.new(:metis_filesystem, :ingest_filesystem, :logger, keyword_init: true)
|
9
|
+
# Since we are doing manual triage of files,
|
10
|
+
# do not automatically copy directory trees.
|
11
|
+
# srcs must be a list of full paths to files.
|
12
|
+
def copy_files(srcs)
|
13
|
+
srcs.each do |src|
|
14
|
+
next unless ingest_filesystem.exist?(src)
|
15
|
+
|
16
|
+
logger&.info("Copying file #{src} (#{Etna::Formatting.as_size(ingest_filesystem.stat(src).size)})")
|
17
|
+
|
18
|
+
# For ingestion triage, just copy over the exact path + filename.
|
19
|
+
copy_file(dest: src, src: src)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def copy_file(dest:, src:)
|
24
|
+
ingest_filesystem.with_readable(src, "r") do |file|
|
25
|
+
metis_filesystem.do_streaming_upload(file, dest, file.size)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/etna/controller.rb
CHANGED
@@ -19,25 +19,30 @@ module Etna
|
|
19
19
|
@logger.warn(request_msg(line))
|
20
20
|
end
|
21
21
|
|
22
|
+
def handle_error(e)
|
23
|
+
case e
|
24
|
+
when Etna::Error
|
25
|
+
Rollbar.error(e)
|
26
|
+
@logger.error(request_msg("Exiting with #{e.status}, #{e.message}"))
|
27
|
+
return failure(e.status, error: e.message)
|
28
|
+
else
|
29
|
+
Rollbar.error(e)
|
30
|
+
@logger.error(request_msg('Caught unspecified error'))
|
31
|
+
@logger.error(request_msg(e.message))
|
32
|
+
e.backtrace.each do |trace|
|
33
|
+
@logger.error(request_msg(trace))
|
34
|
+
end
|
35
|
+
return failure(500, error: 'Server error.')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
22
39
|
def response(&block)
|
23
40
|
return instance_eval(&block) if block_given?
|
24
|
-
|
25
41
|
return send(@action) if @action
|
26
42
|
|
27
|
-
|
28
43
|
[501, {}, ['This controller is not implemented.']]
|
29
|
-
rescue Etna::Error => e
|
30
|
-
Rollbar.error(e)
|
31
|
-
@logger.error(request_msg("Exiting with #{e.status}, #{e.message}"))
|
32
|
-
return failure(e.status, error: e.message)
|
33
44
|
rescue Exception => e
|
34
|
-
|
35
|
-
@logger.error(request_msg('Caught unspecified error'))
|
36
|
-
@logger.error(request_msg(e.message))
|
37
|
-
e.backtrace.each do |trace|
|
38
|
-
@logger.error(request_msg(trace))
|
39
|
-
end
|
40
|
-
return failure(500, error: 'Server error.')
|
45
|
+
handle_error(e)
|
41
46
|
end
|
42
47
|
|
43
48
|
def require_params(*params)
|
data/lib/etna/filesystem.rb
CHANGED
@@ -3,6 +3,8 @@ require 'fileutils'
|
|
3
3
|
require 'open3'
|
4
4
|
require 'securerandom'
|
5
5
|
require 'concurrent-ruby'
|
6
|
+
require 'net/sftp'
|
7
|
+
require 'net/ssh'
|
6
8
|
|
7
9
|
module Etna
|
8
10
|
# A class that encapsulates opening / reading file system entries that abstracts normal file access in order
|
@@ -50,6 +52,11 @@ module Etna
|
|
50
52
|
::FileUtils.mv(src, dest)
|
51
53
|
end
|
52
54
|
|
55
|
+
def stat(src)
|
56
|
+
raise "stat not supported by #{self.class.name}" unless self.class == Filesystem
|
57
|
+
::File.stat(src)
|
58
|
+
end
|
59
|
+
|
53
60
|
class EmptyIO < StringIO
|
54
61
|
def write(*args)
|
55
62
|
# Do nothing -- always leave empty
|
@@ -369,7 +376,59 @@ module Etna
|
|
369
376
|
end
|
370
377
|
end
|
371
378
|
|
379
|
+
class SftpFilesystem < Filesystem
|
380
|
+
def initialize(host:, username:, password: nil, port: 22, **args)
|
381
|
+
@username = username
|
382
|
+
@password = password
|
383
|
+
@host = host
|
384
|
+
@port = port
|
385
|
+
end
|
386
|
+
|
387
|
+
def ssh
|
388
|
+
@ssh ||= Net::SSH.start(@host, @username, password: @password)
|
389
|
+
end
|
390
|
+
|
391
|
+
def sftp
|
392
|
+
@sftp ||= begin
|
393
|
+
conn = Net::SFTP::Session.new(ssh)
|
394
|
+
conn.loop { conn.opening? }
|
395
|
+
|
396
|
+
conn
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def with_readable(src, opts = 'r', &block)
|
401
|
+
sftp.file.open(src, opts, &block)
|
402
|
+
end
|
403
|
+
|
404
|
+
def ls(dir)
|
405
|
+
sftp.dir.entries(dir)
|
406
|
+
end
|
407
|
+
|
408
|
+
def exist?(src)
|
409
|
+
begin
|
410
|
+
sftp.file.open(src)
|
411
|
+
rescue Net::SFTP::StatusException
|
412
|
+
return false
|
413
|
+
end
|
414
|
+
return true
|
415
|
+
end
|
416
|
+
|
417
|
+
def stat(src)
|
418
|
+
sftp.file.open(src).stat
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
372
422
|
class Mock < Filesystem
|
423
|
+
class MockStat
|
424
|
+
def initialize
|
425
|
+
end
|
426
|
+
|
427
|
+
def size
|
428
|
+
0
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
373
432
|
def initialize(&new_io)
|
374
433
|
@files = {}
|
375
434
|
@dirs = {}
|
@@ -438,6 +497,10 @@ module Etna
|
|
438
497
|
def exist?(src)
|
439
498
|
@files.include?(src) || @dirs.include?(src)
|
440
499
|
end
|
500
|
+
|
501
|
+
def stat(src)
|
502
|
+
@files[src].respond_to?(:stat) ? @files[src].stat : MockStat.new
|
503
|
+
end
|
441
504
|
end
|
442
505
|
end
|
443
506
|
end
|
data/lib/etna/logger.rb
CHANGED
data/lib/etna/route.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'date'
|
3
|
+
|
1
4
|
module Etna
|
2
5
|
class Route
|
3
6
|
attr_reader :name
|
@@ -58,6 +61,63 @@ module Etna
|
|
58
61
|
end
|
59
62
|
|
60
63
|
def call(app, request)
|
64
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
65
|
+
|
66
|
+
try_yabeda(request) do |tags|
|
67
|
+
Yabeda.etna.visits.increment(tags)
|
68
|
+
end
|
69
|
+
|
70
|
+
begin
|
71
|
+
process_call(app, request)
|
72
|
+
ensure
|
73
|
+
try_yabeda(request) do |tags|
|
74
|
+
dur = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
75
|
+
Yabeda.etna.response_time.measure(tags, dur)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def hash_user_email(email)
|
81
|
+
secret = Etna::Application.instance.config(:user_hash_secret) || 'notsosecret'
|
82
|
+
digest = email + secret + Date.today.to_s
|
83
|
+
|
84
|
+
if @name
|
85
|
+
digest += @name.to_s
|
86
|
+
else
|
87
|
+
digest += @route.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
Digest::MD5.hexdigest(digest)
|
91
|
+
end
|
92
|
+
|
93
|
+
def try_yabeda(request, &block)
|
94
|
+
if @action
|
95
|
+
controller, action = @action.split('#')
|
96
|
+
elsif @name
|
97
|
+
controller = "none"
|
98
|
+
action = @name
|
99
|
+
else
|
100
|
+
controller = "none"
|
101
|
+
action = @route
|
102
|
+
end
|
103
|
+
|
104
|
+
params = request.env['rack.request.params']
|
105
|
+
user = request.env['etna.user']
|
106
|
+
user_hash = user ? hash_user_email(user.email) : 'unknown'
|
107
|
+
project_name = "unknown"
|
108
|
+
|
109
|
+
if params && (params.include?(:project_name) || params.include?('project_name'))
|
110
|
+
project_name = params[:project_name] || params['project_name']
|
111
|
+
end
|
112
|
+
|
113
|
+
begin
|
114
|
+
block.call({ controller: controller, action: action, user_hash: user_hash, project_name: project_name })
|
115
|
+
rescue => e
|
116
|
+
raise e unless Etna::Application.instance.environment == :production
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_call(app, request)
|
61
121
|
update_params(request)
|
62
122
|
|
63
123
|
unless authorized?(request)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: etna
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.38
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Saurabh Asthana
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: net-sftp
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.0.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.0.0
|
97
111
|
description: See summary
|
98
112
|
email: Saurabh.Asthana@ucsf.edu
|
99
113
|
executables:
|
@@ -144,6 +158,7 @@ files:
|
|
144
158
|
- lib/etna/clients/metis/client.rb
|
145
159
|
- lib/etna/clients/metis/models.rb
|
146
160
|
- lib/etna/clients/metis/workflows.rb
|
161
|
+
- lib/etna/clients/metis/workflows/ingest_metis_data_workflow.rb
|
147
162
|
- lib/etna/clients/metis/workflows/metis_download_workflow.rb
|
148
163
|
- lib/etna/clients/metis/workflows/metis_upload_workflow.rb
|
149
164
|
- lib/etna/clients/metis/workflows/sync_metis_data_workflow.rb
|