etna 0.1.37 → 0.1.38

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.
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