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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 484c950fffbafcf5132df923bc0ef88a79faa6c285ccde653805aac526e4a97e
4
- data.tar.gz: f513b75b048e816acc494c09cb0eccbc2c38cda02c2723266fe7bd98c8595f3a
3
+ metadata.gz: e7c665ee736c06bf1b566467985638cd41a5b3536f848fe2971174aab26ba3a4
4
+ data.tar.gz: 22bd58b6dd1a4902ee91f6588f53cf56a4bec9db5b42d9236ed9cd7c4310a2f0
5
5
  SHA512:
6
- metadata.gz: 6ad0e99925dc6110c8f80f4a1e1417d610b28838497f09afe8be1c69a65fec95a7d549af987aaccde2b42480347bf336838be5398ef580d3024476206b4d611d
7
- data.tar.gz: 44eb05596c635e7ab96df7971fed6b24bfee7bd545238e173e3d1b57bdcfe7b30db5deea355d2711b192d026d8fb72afc32cbcbcf4a83ef92848184433b7a813
6
+ metadata.gz: 04ad878b6f9593a06b65b7652ba3b6da9b0a2aa4de57981ef91247b19eb606e5018cc01e45d033ccdaf5b755cb92149e92a1d26218c47fce6c1a77085dce300e
7
+ data.tar.gz: 26134c1553f33e59e13f5ec737bd11b0bd8e62e1f54c3b1c5963560d1078cb00d5ce0255c01bddae86dc19f9bb38ae5a0dfc6b9d2845e955e55df0299ad1f456
@@ -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 = config(:node_metrics_dir) || "/tmp/metrics.prom"
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
- if block_given?
133
- return unless yield [cmd, cmd_args]
134
- end
183
+ begin
184
+ cmd.setup(config)
185
+ if block_given?
186
+ return unless yield [cmd, cmd_args]
187
+ end
135
188
 
136
- cmd.execute(*cmd.fill_in_missing_params(cmd_args), **cmd_kwds)
137
- rescue => e
138
- Rollbar.error(e)
139
- raise
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 './workflows/metis_download_workflow'
2
- require_relative './workflows/metis_upload_workflow'
3
- require_relative './workflows/sync_metis_data_workflow'
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
@@ -30,7 +30,7 @@ module Etna
30
30
  project_name: project_name,
31
31
  bucket_name: bucket_name,
32
32
  folder_path: dir,
33
- ))
33
+ )) unless dir == "."
34
34
 
35
35
  authorize_response = metis_client.authorize_upload(AuthorizeUploadRequest.new(
36
36
  project_name: project_name,
@@ -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
- Rollbar.error(e)
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)
@@ -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
@@ -40,6 +40,10 @@ module Etna
40
40
  error(trace)
41
41
  end
42
42
 
43
+ if defined? Yabeda
44
+ Yabeda.etna.rollbar_errors.increment({}, 1) rescue nil
45
+ end
46
+
43
47
  Rollbar.error(e)
44
48
  end
45
49
 
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.37
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-05-27 00:00:00.000000000 Z
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