postjob 0.5.11 → 0.5.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/postjob/cli/cron.rb +24 -0
  3. data/lib/postjob/cli/db.rb +1 -2
  4. data/lib/postjob/cli/events.rb +2 -2
  5. data/lib/postjob/cli/heartbeat.rb +2 -2
  6. data/lib/postjob/cli/helpers.rb +28 -0
  7. data/lib/postjob/cli/hosts.rb +32 -15
  8. data/lib/postjob/cli/job.rb +2 -0
  9. data/lib/postjob/cli/ps.rb +4 -26
  10. data/lib/postjob/cli/queues.rb +66 -0
  11. data/lib/postjob/cli/run.rb +19 -6
  12. data/lib/postjob/cli/sessions.rb +5 -4
  13. data/lib/postjob/host.rb +26 -5
  14. data/lib/postjob/migrations/001_helpers.sql +19 -0
  15. data/lib/postjob/migrations/007_job_results.sql +0 -26
  16. data/lib/postjob/migrations/012_hosts.sql +48 -5
  17. data/lib/postjob/migrations/013_worker_sessions.sql +12 -1
  18. data/lib/postjob/migrations/013a_checkout_runnable.sql +47 -5
  19. data/lib/postjob/migrations/016_sessions_functions.sql +5 -3
  20. data/lib/postjob/migrations/017_zombie_check.sql +64 -18
  21. data/lib/postjob/migrations/018_heartbeat.sql +36 -3
  22. data/lib/postjob/migrations/021_cron_jobs.sql +12 -11
  23. data/lib/postjob/migrations.rb +1 -1
  24. data/lib/postjob/queue/notifications.rb +15 -7
  25. data/lib/postjob/queue.rb +21 -8
  26. data/lib/postjob/runner.rb +1 -1
  27. data/lib/postjob/worker_session.rb +9 -5
  28. data/lib/postjob.rb +62 -26
  29. data/lib/tools/heartbeat.rb +2 -1
  30. data/spec/postjob/events/job_event_spec.rb +2 -2
  31. data/spec/postjob/worker_session_spec.rb +1 -1
  32. data/spec/postjob/zombie_spec.rb +54 -0
  33. data/spec/spec_helper.rb +2 -0
  34. data/spec/support/test_helper.rb +3 -8
  35. metadata +12 -9
  36. data/spec/postjob/events/zombie_event_spec.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e26be7454c3a0ddb3d26d67b4e51918e8e16e306
4
- data.tar.gz: f837746f80bba5cebbdd0fea7c7ad7c26eacbc5b
3
+ metadata.gz: 175e2a30ec560a1728f3b3a62750bd5afb38df3d
4
+ data.tar.gz: 54328faf7c7966c21743575ab8d7a9c94b66180d
5
5
  SHA512:
6
- metadata.gz: 361995efdeda5d1ca51de93c0807e37737581c32f53c25f20dc45c50c65dc008260abccc0d421776d59186997071fd2aa14cc23828f95145de6536bcf7859589
7
- data.tar.gz: f374453e87325c9ddb05d4b1cad18f20d26b3a6c64a596de23ab471bc6bbc57017b20df5c589944e1cc90994ad717e4d1e57b7fa8ec6ab953ef48e6ec0a33adc
6
+ metadata.gz: 98960758ed5262988f82ac8cb046752be425b15bd91b2355e575f69007d43b7161580d2796af4d4bd6ebcea71d87f955d442f061ad43ba720efb02a2da172584
7
+ data.tar.gz: 5f1bdd2b015c706cddc7e5874bcbfada7ebc4d3c22f34511d6f5d14eb0c3f28261eebd834670cbd1e68920776b1132a20482d7c0bc58e4a13d55edbb175ee996
@@ -1,4 +1,28 @@
1
+ # rubocop:disable Lint/HandleExceptions
2
+
1
3
  module Postjob::CLI
4
+ # Lists cron jobs in the system
5
+ def cron(limit: "30", queue: nil)
6
+ limit = Integer(limit)
7
+ queue = queue.split(",") if queue
8
+
9
+ connect_to_database!
10
+
11
+ query = ps_query(limit: limit, queue: queue, tags: nil)
12
+ query = query.where("cron_interval IS NOT NULL")
13
+ # query = query
14
+ print_results query: query
15
+ end
16
+
17
+ def cron_top(limit: "30", queue: nil)
18
+ loop do
19
+ system "clear"
20
+ cron limit: limit, queue: queue
21
+ sleep 1
22
+ end
23
+ rescue Interrupt
24
+ end
25
+
2
26
  # Enqueues a cron workflow
3
27
  def cron_enqueue(workflow, *args, interval:, queue: "ruby", tags: nil)
4
28
  interval = Integer(interval)
@@ -8,8 +8,7 @@ module Postjob::CLI
8
8
 
9
9
  raise "No settings in this database schema" unless ::Postjob::Queue.settings?
10
10
 
11
- records = "SELECT name,value FROM postjob.settings ORDER BY name"
12
- tp Simple::SQL.all(records, into: Hash)
11
+ Simple::SQL.print "SELECT name,value FROM postjob.settings ORDER BY name"
13
12
  end
14
13
 
15
14
  def db_migrate
@@ -37,7 +37,7 @@ module Postjob::CLI
37
37
  # Example:
38
38
  #
39
39
  # postjob events
40
- def events(limit: "100")
40
+ def events(limit: "30")
41
41
  expect! limit => /\A\d+\z/
42
42
  limit = Integer(limit)
43
43
 
@@ -49,7 +49,7 @@ module Postjob::CLI
49
49
  end
50
50
 
51
51
  # Show up-to-date events information once per second
52
- def events_top(limit: "100")
52
+ def events_top(limit: "30")
53
53
  loop do
54
54
  system "clear"
55
55
  events(limit: limit)
@@ -31,7 +31,7 @@ module Postjob::CLI
31
31
  public
32
32
 
33
33
  # Show the latest heartbeat events
34
- def heartbeat(limit: "100")
34
+ def heartbeat(limit: "30")
35
35
  expect! limit => /\A\d+\z/
36
36
  limit = Integer(limit)
37
37
 
@@ -44,7 +44,7 @@ module Postjob::CLI
44
44
  end
45
45
 
46
46
  # Show up-to-date heartbeat information once per second
47
- def heartbeat_top(limit: "100")
47
+ def heartbeat_top(limit: "30")
48
48
  loop do
49
49
  system "clear"
50
50
  heartbeat(limit: limit)
@@ -0,0 +1,28 @@
1
+
2
+
3
+ module Postjob::CLI
4
+ private
5
+
6
+ def parse_ids(*ids)
7
+ ids.map do |s|
8
+ s = s.gsub(/.*\./, "")
9
+ Integer(s)
10
+ end.uniq
11
+ end
12
+
13
+ def print_results(query:, on_empty: nil, title: nil)
14
+ if title
15
+ puts "\n=== #{title} =======================================\n\n"
16
+ end
17
+
18
+ records = Simple::SQL.print(query, into: Hash)
19
+
20
+ if records.total_count && records.total_count > records.length
21
+ logger.warn "Output limited up to limit #{records.length}. Use the --limit=<NN> command line option for a different limit."
22
+ end
23
+
24
+ if records.empty? && on_empty
25
+ logger.warn(on_empty)
26
+ end
27
+ end
28
+ end
@@ -10,21 +10,21 @@ module Postjob::CLI
10
10
  sql = <<-SQL
11
11
  SELECT
12
12
  hosts.id,
13
- hosts.attributes,
14
13
  hosts.created_at,
15
- heartbeat.attributes AS heartbeat,
16
- heartbeat.created_at AS heartbeat_created_at
14
+ hosts.status,
15
+ heartbeats.attributes AS heartbeat,
16
+ heartbeats.created_at AS heartbeat_created_at,
17
+ hosts.attributes
17
18
  FROM postjob.hosts hosts
18
19
  LEFT JOIN (
19
20
  SELECT
20
- worker_sessions.host_id,
21
- MAX(events.id) AS event_id
22
- FROM postjob.worker_sessions
23
- LEFT JOIN postjob.events events ON events.worker_session_id=worker_sessions.id
24
- WHERE events.name = 'heartbeat'
25
- GROUP BY worker_sessions.host_id
26
- ) q ON q.host_id=hosts.id
27
- LEFT JOIN events heartbeat ON heartbeat.id=event_id
21
+ host_id,
22
+ attributes,
23
+ created_at,
24
+ rank() OVER (PARTITION BY host_id ORDER BY created_at DESC) AS rank
25
+ FROM postjob.events events
26
+ WHERE name='heartbeat'
27
+ ) heartbeats ON heartbeats.host_id=hosts.id AND rank = 1
28
28
  SQL
29
29
 
30
30
  scope = Simple::SQL::Scope.new(sql)
@@ -42,7 +42,7 @@ module Postjob::CLI
42
42
  # Example:
43
43
  #
44
44
  # postjob hosts
45
- def hosts(limit: "100")
45
+ def hosts(limit: "30")
46
46
  expect! limit => /\A\d+\z/
47
47
  limit = Integer(limit)
48
48
 
@@ -54,9 +54,7 @@ module Postjob::CLI
54
54
  end
55
55
 
56
56
  # Show up-to-date hosts information once per second
57
- #
58
- #
59
- def hosts_top(limit: "100")
57
+ def hosts_top(limit: "30")
60
58
  loop do
61
59
  system "clear"
62
60
  hosts(limit: limit)
@@ -64,4 +62,23 @@ module Postjob::CLI
64
62
  end
65
63
  rescue Interrupt
66
64
  end
65
+
66
+ # resets the host id
67
+ def host_reset
68
+ Postjob::Host.clear_storage
69
+ end
70
+
71
+ # Set the host to shutdown state
72
+ def host_shutdown
73
+ connect_to_database!
74
+
75
+ Simple::SQL.ask "UPDATE postjob.hosts SET status='shutdown' WHERE id=$1::uuid", ::Postjob.host_id
76
+ end
77
+
78
+ # Set the host to running again
79
+ def host_restart
80
+ connect_to_database!
81
+
82
+ Simple::SQL.ask "UPDATE postjob.hosts SET status='running' WHERE id=$1::uuid", ::Postjob.host_id
83
+ end
67
84
  end
@@ -87,6 +87,8 @@ module Postjob::CLI
87
87
  public
88
88
 
89
89
  def registry
90
+ require "table_print"
91
+
90
92
  workflows_with_versions = Postjob::Registry.workflows.keys.reject { |k| k[1] == "" }
91
93
  workflows_with_versions = workflows_with_versions.sort_by { |name, _version| name }
92
94
 
@@ -29,7 +29,7 @@ module Postjob::CLI
29
29
  max_attempts,
30
30
  cron_interval AS cron,
31
31
  CASE
32
- WHEN is_sticky THEN COALESCE(substring(sticky_host_id::varchar for 6) || '...', 'yes')
32
+ WHEN is_sticky THEN COALESCE(substring(sticky_host_id::varchar for 9) || '...', 'yes')
33
33
  ELSE 'no'
34
34
  END AS sticky,
35
35
  is_greedy AS greedy,
@@ -64,7 +64,7 @@ module Postjob::CLI
64
64
  #
65
65
  # For a listing of all jobs in the system use ps:full, see 'postjob help ps:full'
66
66
  # for details.
67
- def ps(*ids, limit: "100", tags: nil, queue: nil, only_root: nil)
67
+ def ps(*ids, limit: "30", tags: nil, queue: nil, only_root: nil)
68
68
  expect! limit => /\A\d+\z/
69
69
  limit = Integer(limit)
70
70
  queue = queue.split(",") if queue
@@ -85,7 +85,7 @@ module Postjob::CLI
85
85
  print_results query: query
86
86
  end
87
87
 
88
- def ps_full(*ids, limit: "100", tags: nil, queue: nil)
88
+ def ps_full(*ids, limit: "30", tags: nil, queue: nil)
89
89
  queue = queue.split(",") if queue
90
90
 
91
91
  connect_to_database!
@@ -101,7 +101,7 @@ module Postjob::CLI
101
101
  end
102
102
 
103
103
  # Show up-to-date information once per second
104
- def ps_top(*ids, limit: "100", tags: nil, full: false, queue: nil, only_root: false)
104
+ def ps_top(*ids, limit: "30", tags: nil, full: false, queue: nil, only_root: false)
105
105
  loop do
106
106
  system "clear"
107
107
  if full
@@ -159,26 +159,4 @@ module Postjob::CLI
159
159
  pp job
160
160
  end
161
161
  end
162
-
163
- private
164
-
165
- def parse_ids(*ids)
166
- ids.map do |s|
167
- s = s.gsub(/.*\./, "")
168
- Integer(s)
169
- end.uniq
170
- end
171
-
172
- def print_results(query:, on_empty: nil)
173
- records = Simple::SQL.all(query, into: Hash)
174
- tp records
175
-
176
- if records.total_count > records.length
177
- logger.warn "Output limited up to limit #{records.length}. Use the --limit=<NN> command line option for a different limit."
178
- end
179
-
180
- if records.empty? && on_empty
181
- logger.warn(on_empty)
182
- end
183
- end
184
162
  end
@@ -0,0 +1,66 @@
1
+ # rubocop:disable Metrics/MethodLength
2
+ # rubocop:disable Lint/HandleExceptions
3
+
4
+ module Postjob::CLI
5
+ private
6
+
7
+ def queues_query(time_name:)
8
+ sql = <<-SQL
9
+ SELECT
10
+ queue,
11
+ status,
12
+ iff(status = 'ready', waiting, processing) AS "#{time_name}"
13
+ FROM (
14
+ SELECT
15
+ queue,
16
+ status,
17
+ COUNT(*),
18
+ AVG (now() at time zone 'utc' - jobs.created_at) AS waiting,
19
+ AVG (jobs.updated_at - jobs.created_at) AS processing
20
+ FROM postjob.postjobs jobs
21
+ WHERE jobs.root_id=jobs.id
22
+ AND (jobs.next_run_at IS NULL OR jobs.next_run_at < (now() at time zone 'utc'))
23
+ GROUP BY queue, status
24
+ ORDER BY queue, status
25
+ ) sq
26
+ SQL
27
+
28
+ scope = Simple::SQL::Scope.new(sql)
29
+ scope
30
+ end
31
+
32
+ public
33
+
34
+ # Show hosts status
35
+ #
36
+ # This command lists all worker_sessions currently in the system.
37
+ #
38
+ # Example:
39
+ #
40
+ # postjob hosts
41
+ def queues
42
+ connect_to_database!
43
+
44
+ query = queues_query(time_name: "waiting for..")
45
+ query = query.where(status: %w(ready))
46
+ print_results query: query, title: "Waiting jobs"
47
+
48
+ query = queues_query(time_name: "processing since..")
49
+ query = query.where(status: %w(sleep processing err))
50
+ print_results query: query, title: "Processing jobs"
51
+
52
+ query = queues_query(time_name: "processing time")
53
+ query = query.where(status: %w(failed ok timeout))
54
+ print_results query: query, title: "Finished jobs"
55
+ end
56
+
57
+ # # Show up-to-date hosts information once per second
58
+ def queues_top
59
+ loop do
60
+ system "clear"
61
+ queues
62
+ sleep 1
63
+ end
64
+ rescue Interrupt
65
+ end
66
+ end
@@ -1,3 +1,6 @@
1
+ # rubocop:disable Metrics/PerceivedComplexity
2
+ # rubocop:disable Metrics/ParameterLists
3
+
1
4
  module Postjob::CLI
2
5
  # Run a single job
3
6
  def step
@@ -10,21 +13,31 @@ module Postjob::CLI
10
13
  #
11
14
  # Parameters:
12
15
  #
13
- # - count=<count> maximum number of jobs to process. Default: unlimited.
14
- # - queue run only the specified queue.
15
- # - quiet don't show progress.
16
+ # - --count=<count> maximum number of jobs to process. Default: unlimited.
17
+ # - --queue=queue1,queue2,queue3 run only the specified queues.
18
+ # - --heartbeat=no don't start heartbeat process.
19
+ # - --quiet don't show progress.
16
20
  #
17
- def run(count: nil, queue: nil, quiet: false, fast: false)
21
+ def run(count: nil, queue: "ruby", quiet: false, fast: false, host_id: nil, heartbeat: true)
22
+ expect! Integer(host_id, 16) => 1..0xffffffff if host_id
18
23
  count = Integer(count) if count
19
- queue = queue.split(",") if queue
24
+
25
+ expect! heartbeat => [ "yes", "no" ] if heartbeat.is_a?(String)
26
+ heartbeat = %w(yes true).include?(heartbeat) if heartbeat.is_a?(String)
27
+ expect! heartbeat => [ true, false ]
20
28
 
21
29
  Postjob.fast_mode = (fast ? true : false)
22
30
 
23
31
  connect_to_database!
24
32
 
33
+ if host_id
34
+ Postjob::Host.host_id = "%08x-0000-0000-0000-000000000000" % Integer(host_id, 16)
35
+ Postjob.logger.info "Using host_id: #{Postjob::Host.host_id}"
36
+ end
37
+
25
38
  logger.success "Starting runner with pid #{$$}"
26
39
 
27
- processed = Postjob.run(count: count, queue: queue) do |job_id|
40
+ processed = Postjob.run(count: count, queues: queue.split(","), heartbeat: heartbeat) do |job_id|
28
41
  logger.info "Processed job w/id #{job_id}" if job_id
29
42
  STDERR.print "." unless quiet
30
43
  end
@@ -10,7 +10,8 @@ module Postjob::CLI
10
10
  sql = <<-SQL
11
11
  SELECT
12
12
  worker_sessions.id,
13
- worker_sessions.host_id,
13
+ (substring(worker_sessions.host_id::varchar for 9) || '...') AS host_id,
14
+ array_to_string(worker_sessions.queues, ', ') AS queues,
14
15
  worker_sessions.client_socket,
15
16
  worker_sessions.workflows,
16
17
  worker_sessions.created_at,
@@ -42,7 +43,7 @@ module Postjob::CLI
42
43
  SQL
43
44
 
44
45
  scope = Simple::SQL::Scope.new(sql)
45
-
46
+ scope = scope.where("worker_sessions.id != '00000000-0000-0000-0000-000000000000'::uuid")
46
47
  scope
47
48
  .paginate(per: limit, page: 1)
48
49
  .order_by("heartbeat_created_at DESC NULLS LAST")
@@ -57,7 +58,7 @@ module Postjob::CLI
57
58
  # Example:
58
59
  #
59
60
  # postjob sessions
60
- def sessions(limit: "100")
61
+ def sessions(limit: "30")
61
62
  expect! limit => /\A\d+\z/
62
63
  limit = Integer(limit)
63
64
 
@@ -72,7 +73,7 @@ module Postjob::CLI
72
73
  end
73
74
 
74
75
  # Show up-to-date session information once per second
75
- def sessions_top(limit: "100")
76
+ def sessions_top(limit: "30")
76
77
  loop do
77
78
  system "clear"
78
79
  sessions(limit: limit)
data/lib/postjob/host.rb CHANGED
@@ -2,6 +2,11 @@ require_relative "./record"
2
2
  require "tempfile"
3
3
 
4
4
  class Postjob::Host < Postjob::Record
5
+ attr_reader :id
6
+ attr_reader :attributes
7
+ attr_reader :status
8
+ attr_reader :created_at
9
+
5
10
  class << self
6
11
  def clear_storage
7
12
  @host_id = nil
@@ -9,8 +14,19 @@ class Postjob::Host < Postjob::Record
9
14
  File.unlink(storage_path) if File.exist?(storage_path)
10
15
  end
11
16
 
17
+ UUID_REGEXP = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
18
+
19
+ def host_id=(host_id)
20
+ expect! host_id => UUID_REGEXP
21
+
22
+ return if @host_id == host_id
23
+
24
+ register_host(host_id: host_id)
25
+ @host_id = host_id
26
+ end
27
+
12
28
  def host_id
13
- @host_id ||= atomic_set_and_get(storage_path) { register_host }
29
+ @host_id ||= atomic_set_and_get(storage_path) { register_host(host_id: nil) }
14
30
  end
15
31
 
16
32
  private
@@ -23,8 +39,12 @@ class Postjob::Host < Postjob::Record
23
39
  # the current user. We choose a tmp location for that reason. (A /var location
24
40
  # would be even better - however, our systems do not have a user-writable /var).
25
41
  def storage_path
26
- env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
27
- File.join Dir.tmpdir, "postjob.#{env}.#{Process.uid}.host_id"
42
+ @storage_path ||= begin
43
+ env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
44
+ storage_path = File.join Dir.tmpdir, "postjob.#{env}.#{Process.uid}.host_id"
45
+ Postjob.logger.info "Keeping host identifier in #{storage_path}"
46
+ storage_path
47
+ end
28
48
  end
29
49
 
30
50
  def atomic_set_and_get(path)
@@ -43,10 +63,11 @@ class Postjob::Host < Postjob::Record
43
63
  value
44
64
  end
45
65
 
46
- def register_host
66
+ def register_host(host_id:)
67
+ expect! host_id => [ nil, UUID_REGEXP ]
47
68
  attributes = {}
48
69
  Postjob.logger.debug "registering host w/#{attributes.inspect}"
49
- ::Postjob::Queue.host_register(attributes)
70
+ ::Postjob::Queue.host_register(attributes, host_id: host_id)
50
71
  end
51
72
  end
52
73
  end
@@ -0,0 +1,19 @@
1
+ CREATE OR REPLACE FUNCTION iff(cond BOOLEAN, value anyelement) RETURNS anyelement AS $$
2
+ BEGIN
3
+ IF cond THEN
4
+ RETURN value;
5
+ ELSE
6
+ RETURN NULL;
7
+ END IF;
8
+ END;
9
+ $$ LANGUAGE plpgsql;
10
+
11
+ CREATE OR REPLACE FUNCTION iff(cond BOOLEAN, value anyelement, else_value anyelement) RETURNS anyelement AS $$
12
+ BEGIN
13
+ IF cond THEN
14
+ RETURN value;
15
+ ELSE
16
+ RETURN else_value;
17
+ END IF;
18
+ END;
19
+ $$ LANGUAGE plpgsql;
@@ -149,29 +149,3 @@ BEGIN
149
149
  PERFORM {SCHEMA_NAME}._wakeup_parent_job(p_worker_session_id, job_id);
150
150
  END;
151
151
  $$ LANGUAGE plpgsql;
152
-
153
- CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._set_job_zombie(
154
- job_id BIGINT,
155
- p_fast_mode BOOLEAN) RETURNS VOID AS $$
156
- DECLARE
157
- p_worker_session_id UUID;
158
- BEGIN
159
- p_worker_session_id := {SCHEMA_NAME}._null_uuid();
160
-
161
- PERFORM {SCHEMA_NAME}._reset_job_processing(p_worker_session_id, job_id);
162
-
163
- -- write error info
164
- UPDATE {SCHEMA_NAME}.postjobs
165
- SET
166
- error='Zombie',
167
- error_message='zombie',
168
- error_backtrace=NULL,
169
- failed_attempts=failed_attempts+1,
170
- next_run_at=NULL
171
- WHERE id=job_id;
172
-
173
- -- prepare next run, if any
174
- PERFORM {SCHEMA_NAME}._prepare_rerun(job_id, 'err', p_fast_mode);
175
- PERFORM {SCHEMA_NAME}._wakeup_parent_job(p_worker_session_id, job_id);
176
- END;
177
- $$ LANGUAGE plpgsql;
@@ -1,5 +1,18 @@
1
1
  -- hosts ----------------------------------------------------------------------
2
2
 
3
+ DO $$
4
+ BEGIN
5
+ CREATE TYPE {SCHEMA_NAME}.host_statuses AS ENUM (
6
+ 'running', -- host is running
7
+ 'shutdown', -- host is shutting down
8
+ 'stopped' -- host is not running
9
+ );
10
+ EXCEPTION
11
+ WHEN duplicate_object THEN RAISE DEBUG 'type {SCHEMA_NAME}.host_statuses already exists';
12
+ END;
13
+ $$;
14
+
15
+
3
16
  -- The hosts table records available hosts. hosts are the basis for the
4
17
  -- sticky jobs feature - a sticky job is a job which can only be checked
5
18
  -- out by a specific host.
@@ -7,6 +20,10 @@
7
20
 
8
21
  CREATE TABLE IF NOT EXISTS {SCHEMA_NAME}.hosts (
9
22
  id UUID PRIMARY KEY DEFAULT (gen_random_uuid()), -- UUID identifying a worker **host**
23
+
24
+ -- The workflow status, one of 'running', 'shutdown', 'stopped'
25
+ status {SCHEMA_NAME}.host_statuses NOT NULL DEFAULT 'stopped',
26
+
10
27
  attributes JSONB NOT NULL DEFAULT '{}'::JSONB,
11
28
  created_at timestamp NOT NULL DEFAULT (now() at time zone 'utc')
12
29
  );
@@ -14,6 +31,14 @@ CREATE TABLE IF NOT EXISTS {SCHEMA_NAME}.hosts (
14
31
  CREATE INDEX IF NOT EXISTS hosts_attributes_idx
15
32
  ON {SCHEMA_NAME}.hosts USING GIN (attributes jsonb_path_ops);
16
33
 
34
+ DO $$
35
+ BEGIN
36
+ ALTER TABLE {SCHEMA_NAME}.hosts ADD COLUMN status {SCHEMA_NAME}.host_statuses NOT NULL DEFAULT 'stopped';
37
+ EXCEPTION
38
+ WHEN duplicate_column THEN RAISE DEBUG 'column {SCHEMA_NAME}.hosts.host_statuses already exists';
39
+ END;
40
+ $$;
41
+
17
42
  -- returns _null_host_id ------------------------------------------------------
18
43
 
19
44
  DO $$
@@ -28,15 +53,33 @@ $$ LANGUAGE plpgsql;
28
53
 
29
54
  -- host_register: registers a host --------------------------------------------
30
55
 
31
- CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.host_register(p_attrs JSONB)
56
+ DROP FUNCTION IF EXISTS {SCHEMA_NAME}.host_register(p_attrs JSONB);
57
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.host_register(p_attrs JSONB, v_id UUID)
32
58
  RETURNS UUID
33
59
  AS $$
34
- DECLARE
35
- v_id UUID;
36
60
  BEGIN
37
- v_id := gen_random_uuid();
61
+ IF v_id IS NULL THEN
62
+ v_id := gen_random_uuid();
63
+ END IF;
64
+
38
65
  INSERT INTO {SCHEMA_NAME}.hosts (id, attributes)
39
- VALUES (v_id, p_attrs);
66
+ VALUES (v_id, p_attrs)
67
+ ON CONFLICT(id) DO UPDATE SET attributes=p_attrs;
40
68
  RETURN v_id;
41
69
  END;
42
70
  $$ LANGUAGE plpgsql;
71
+
72
+
73
+ -- wakeup runners after changing hosts
74
+
75
+ -- when a host changes its status to shutdown, all of its runners should
76
+ -- shutdown quickly.
77
+
78
+ BEGIN;
79
+ DROP TRIGGER IF EXISTS _wakeup_runners ON {SCHEMA_NAME}.hosts;
80
+
81
+ CREATE TRIGGER _wakeup_runners AFTER UPDATE
82
+ ON {SCHEMA_NAME}.hosts
83
+ FOR EACH STATEMENT
84
+ EXECUTE PROCEDURE {SCHEMA_NAME}._wakeup_runners();
85
+ COMMIT;
@@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS {SCHEMA_NAME}.worker_sessions (
24
24
  host_id UUID NOT NULL REFERENCES {SCHEMA_NAME}.hosts ON DELETE CASCADE, -- UUID identifying a worker **host**
25
25
  client_socket VARCHAR, -- host:port of connection (from pg_stat_activity)
26
26
  workflows VARCHAR[] NOT NULL, -- array of workflow versions available on that worker
27
+ queues VARCHAR[] NOT NULL, -- array of queue names available on that worker
27
28
  attributes JSONB NOT NULL DEFAULT '{}'::JSONB,
28
29
  created_at timestamp NOT NULL DEFAULT (now() at time zone 'utc')
29
30
  );
@@ -38,7 +39,17 @@ DO $$
38
39
  null_uuid UUID := {SCHEMA_NAME}._null_uuid();
39
40
  BEGIN
40
41
  IF NOT EXISTS (SELECT 1 FROM {SCHEMA_NAME}.worker_sessions WHERE id = null_uuid) THEN
41
- INSERT INTO {SCHEMA_NAME}.worker_sessions(id, host_id, workflows) VALUES(null_uuid, null_uuid, '{}');
42
+ INSERT INTO {SCHEMA_NAME}.worker_sessions(id, host_id, workflows, queues) VALUES(null_uuid, null_uuid, '{}', ARRAY[]::varchar[]);
42
43
  END IF;
43
44
  END;
44
45
  $$ LANGUAGE plpgsql;
46
+
47
+ DO $$
48
+ BEGIN
49
+ ALTER TABLE {SCHEMA_NAME}.worker_sessions ADD COLUMN queues VARCHAR[];
50
+ UPDATE {SCHEMA_NAME}.worker_sessions SET queues = Array['ruby'] WHERE queues IS NULL;
51
+ ALTER TABLE {SCHEMA_NAME}.worker_sessions ALTER COLUMN queues SET NOT NULL;
52
+ EXCEPTION
53
+ WHEN duplicate_column THEN RAISE DEBUG 'column {SCHEMA_NAME}.worker_sessions.queues already exists';
54
+ END;
55
+ $$;