dynflow 1.6.10 → 1.7.0

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: 79387ebdc9ad0bbae88897885da885483c4d57e08af9be69f6f2369be67bbe5b
4
- data.tar.gz: aa5413ff3913decab84dc70e86f94f4293acfcc16340e7ad6216abf9943335b1
3
+ metadata.gz: b1fd4cb9db86befb0bfc96cd5c98f2ca9aa0f6f40249712cbbb8b250b9b9a142
4
+ data.tar.gz: 218d8a4ce5ec717f24ca4d5d730a5c0620f65ab00e6a35e0595a18876f4b9a31
5
5
  SHA512:
6
- metadata.gz: 99132ff502c108b47595d211caba78c3034adb05390937269814d09a8da67c546b9bc2e7a2c776bcba0596d76ee50b7229c7497aad77e2d2483c43351d4a65a5
7
- data.tar.gz: 52e2bb51d1fdc668c8837646635c37c58c65761d5ef3812294838dfacc54dfc48ae26550bd132a077dfc8579ee4fa86787fe17d27f45389a6a536328276de897
6
+ metadata.gz: 80b8644418108a107c2a73481fb793647ac6ce40f9789b4efa064970bd6cccd54579ce95ff0b5a4710f033e4defaa7d0fd50ec96d9c51e91666adcd875cc9cc4
7
+ data.tar.gz: 6a28661cdfeff6555c970df957f10f28d4c23baf977b1212cf3253bac973cef967b1c40be7b77dbaa3fe6811e484be319a04417a0895dd81a32edd534ce58975
@@ -35,10 +35,9 @@ jobs:
35
35
  fail-fast: false
36
36
  matrix:
37
37
  ruby_version:
38
- - 2.5.0
39
- - 2.6.0
40
38
  - 2.7.0
41
39
  - 3.0.0
40
+ - 3.2.0
42
41
  concurrent_ruby_ext:
43
42
  - 'true'
44
43
  - 'false'
@@ -54,30 +53,23 @@ jobs:
54
53
  - db: sqlite3
55
54
  conn_string: sqlite:/
56
55
  exclude:
57
- - db: mysql
58
- ruby_version: 2.5.0
59
- - db: mysql
60
- ruby_version: 2.6.0
61
56
  - db: mysql
62
57
  ruby_version: 3.0.0
58
+ - db: mysql
59
+ ruby_version: 3.2.0
63
60
  - db: mysql
64
61
  concurrent_ruby_ext: 'true'
65
- - db: sqlite3
66
- ruby_version: 2.5.0
67
- - db: sqlite3
68
- ruby_version: 2.6.0
69
62
  - db: sqlite3
70
63
  ruby_version: 3.0.0
64
+ - db: sqlite3
65
+ ruby_version: 3.2.0
71
66
  - db: sqlite3
72
67
  concurrent_ruby_ext: 'true'
73
68
  - db: postgresql
74
- ruby_version: 2.5.0
75
- concurrent_ruby_ext: 'true'
76
- - db: postgresql
77
- ruby_version: 2.6.0
69
+ ruby_version: 3.0.0
78
70
  concurrent_ruby_ext: 'true'
79
71
  - db: postgresql
80
- ruby_version: 3.0.0
72
+ ruby_version: 3.2.0
81
73
  concurrent_ruby_ext: 'true'
82
74
 
83
75
  services:
@@ -179,6 +179,12 @@ module Dynflow
179
179
  @output_chunks ||= world.persistence.load_output_chunks(@execution_plan_id, @id)
180
180
  end
181
181
 
182
+ def drop_output_chunks!
183
+ @pending_output_chunks = []
184
+ @output_chunks = []
185
+ world.persistence.delete_output_chunks(@execution_plan_id, @id)
186
+ end
187
+
182
188
  def caller_action
183
189
  phase! Present
184
190
  return nil if @caller_action_id
@@ -56,12 +56,20 @@ module Dynflow
56
56
  adapter.load_output_chunks(execution_plan_id, action_id)
57
57
  end
58
58
 
59
+ def delete_output_chunks(execution_plan_id, action_id)
60
+ adapter.delete_output_chunks(execution_plan_id, action_id)
61
+ end
62
+
59
63
  def find_execution_plans(options)
60
64
  adapter.find_execution_plans(options).map do |execution_plan_hash|
61
65
  ExecutionPlan.new_from_hash(execution_plan_hash, @world)
62
66
  end
63
67
  end
64
68
 
69
+ def find_execution_plan_statuses(options)
70
+ adapter.find_execution_plan_statuses(options)
71
+ end
72
+
65
73
  def find_execution_plan_counts(options)
66
74
  adapter.find_execution_plan_counts(options)
67
75
  end
@@ -46,6 +46,10 @@ module Dynflow
46
46
  filter(:execution_plan, options[:filters]).count
47
47
  end
48
48
 
49
+ def find_execution_plan_statuses(options)
50
+ raise NotImplementedError
51
+ end
52
+
49
53
  # @param filters [Hash{ String => Object }] filters to determine
50
54
  # what to delete
51
55
  # @param batch_size the size of the chunks to iterate over when
@@ -104,6 +108,18 @@ module Dynflow
104
108
  raise NotImplementedError
105
109
  end
106
110
 
111
+ def save_output_chunks(execution_plan_id, action_id, chunks)
112
+ raise NotImplementedError
113
+ end
114
+
115
+ def load_output_chunks(execution_plan_id, action_id)
116
+ raise NotImplementedError
117
+ end
118
+
119
+ def delete_output_chunks(execution_plan_id, action_id)
120
+ raise NotImplementedError
121
+ end
122
+
107
123
  # for debug purposes
108
124
  def to_hash
109
125
  raise NotImplementedError
@@ -78,6 +78,16 @@ module Dynflow
78
78
  filter(:execution_plan, table(:execution_plan), options[:filters]).count
79
79
  end
80
80
 
81
+ def find_execution_plan_statuses(options)
82
+ plans = filter(:execution_plan, table(:execution_plan), options[:filters])
83
+ .select(:uuid, :state, :result)
84
+
85
+ plans.each_with_object({}) do |current, acc|
86
+ uuid = current.delete(:uuid)
87
+ acc[uuid] = current
88
+ end
89
+ end
90
+
81
91
  def delete_execution_plans(filters, batch_size = 1000, backup_dir = nil)
82
92
  count = 0
83
93
  filter(:execution_plan, table(:execution_plan), filters).each_slice(batch_size) do |plans|
@@ -190,6 +200,10 @@ module Dynflow
190
200
  load_records :output_chunk, { execution_plan_uuid: execution_plan_id, action_id: action_id }, [:timestamp, :kind, :chunk]
191
201
  end
192
202
 
203
+ def delete_output_chunks(execution_plan_id, action_id)
204
+ filter(:output_chunk, table(:output_chunk), { execution_plan_uuid: execution_plan_id, action_id: action_id }).delete
205
+ end
206
+
193
207
  def connector_feature!
194
208
  unless @additional_responsibilities[:connector]
195
209
  raise "The sequel persistence adapter connector feature used but not enabled in additional_features"
@@ -393,11 +407,11 @@ module Dynflow
393
407
  hash = if record[:data].nil?
394
408
  SERIALIZABLE_COLUMNS.fetch(what, []).each do |key|
395
409
  key = key.to_sym
396
- record[key] = MessagePack.unpack((record[key])) unless record[key].nil?
410
+ record[key] = MessagePack.unpack(record[key].to_s) unless record[key].nil?
397
411
  end
398
412
  record
399
413
  else
400
- MessagePack.unpack(record[:data])
414
+ MessagePack.unpack(record[:data].to_s)
401
415
  end
402
416
  Utils.indifferent_hash(hash)
403
417
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Dynflow
3
- VERSION = '1.6.10'
3
+ VERSION = '1.7.0'
4
4
  end
data/test/action_test.rb CHANGED
@@ -889,5 +889,48 @@ module Dynflow
889
889
  end
890
890
  end
891
891
  end
892
+
893
+ describe 'output chunks' do
894
+ include ::Dynflow::Testing::Factories
895
+
896
+ class OutputChunkAction < ::Dynflow::Action
897
+ def run(event = nil)
898
+ output[:counter] ||= 0
899
+ case event
900
+ when nil
901
+ output_chunk("Chunk #{output[:counter]}")
902
+ output[:counter] += 1
903
+ suspend
904
+ when :exit
905
+ return
906
+ end
907
+ end
908
+
909
+ def finalize
910
+ drop_output_chunks!
911
+ end
912
+ end
913
+
914
+ it 'collects and drops output chunks' do
915
+ action = create_and_plan_action(OutputChunkAction)
916
+ _(action.pending_output_chunks).must_equal nil
917
+
918
+ action = run_action(action)
919
+ _(action.pending_output_chunks.count).must_equal 1
920
+
921
+ action = run_action(action)
922
+ _(action.pending_output_chunks.count).must_equal 2
923
+
924
+ action = run_action(action, :exit)
925
+ _(action.pending_output_chunks.count).must_equal 2
926
+
927
+ persistence = mock()
928
+ persistence.expects(:delete_output_chunks).with(action.execution_plan_id, action.id)
929
+ action.world.stubs(:persistence).returns(persistence)
930
+
931
+ action = finalize_action(action)
932
+ _(action.pending_output_chunks.count).must_equal 0
933
+ end
934
+ end
892
935
  end
893
936
  end
@@ -159,6 +159,46 @@ module Dynflow
159
159
  end
160
160
  end
161
161
 
162
+ describe '#def find_execution_plan_statuses' do
163
+ before do
164
+ # the tests expect clean field
165
+ adapter.delete_execution_plans({})
166
+ end
167
+
168
+ it 'supports filtering' do
169
+ prepare_and_save_plans
170
+ if adapter.ordering_by.include?('state')
171
+ loaded_plans = adapter.find_execution_plan_statuses(filters: { label: ['test1'] })
172
+ _(loaded_plans).must_equal({ 'plan1' => { state: 'paused', result: nil} })
173
+
174
+ loaded_plans = adapter.find_execution_plan_statuses(filters: { state: ['paused'] })
175
+ _(loaded_plans).must_equal({"plan1"=>{:state=>"paused", :result=>nil},
176
+ "plan3"=>{:state=>"paused", :result=>nil},
177
+ "plan4"=>{:state=>"paused", :result=>nil}})
178
+
179
+ loaded_plans = adapter.find_execution_plan_statuses(filters: { state: ['stopped'] })
180
+ _(loaded_plans).must_equal({"plan2"=>{:state=>"stopped", :result=>nil}})
181
+
182
+ loaded_plans = adapter.find_execution_plan_statuses(filters: { state: [] })
183
+ _(loaded_plans).must_equal({})
184
+
185
+ loaded_plans = adapter.find_execution_plan_statuses(filters: { state: ['stopped', 'paused'] })
186
+ _(loaded_plans).must_equal({"plan1"=>{:state=>"paused", :result=>nil},
187
+ "plan2"=>{:state=>"stopped", :result=>nil},
188
+ "plan3"=>{:state=>"paused", :result=>nil}, "plan4"=>{:state=>"paused", :result=>nil}})
189
+
190
+ loaded_plans = adapter.find_execution_plan_statuses(filters: { 'state' => ['stopped', 'paused'] })
191
+ _(loaded_plans).must_equal({"plan1"=>{:state=>"paused", :result=>nil},
192
+ "plan2"=>{:state=>"stopped", :result=>nil},
193
+ "plan3"=>{:state=>"paused", :result=>nil},
194
+ "plan4"=>{:state=>"paused", :result=>nil}})
195
+
196
+ loaded_plans = adapter.find_execution_plan_statuses(filters: { label: ['test1'], :delayed => true })
197
+ _(loaded_plans).must_equal({})
198
+ end
199
+ end
200
+ end
201
+
162
202
  describe '#def find_execution_plan_counts' do
163
203
  before do
164
204
  # the tests expect clean field
@@ -331,6 +371,20 @@ module Dynflow
331
371
  _(plans.first[:execution_plan_uuid]).must_equal 'plan1'
332
372
  end
333
373
  end
374
+
375
+ describe '#delete_output_chunks' do
376
+ it 'deletes output chunks' do
377
+ prepare_plans_with_actions
378
+
379
+ adapter.save_output_chunks('plan1', 1, [{chunk: "Hello", timestamp: Time.now}, {chunk: "Bye", timestamp: Time.now}])
380
+ chunks = adapter.load_output_chunks('plan1', 1)
381
+ _(chunks.length).must_equal 2
382
+ deleted = adapter.delete_output_chunks('plan1', 1)
383
+ _(deleted).must_equal 2
384
+ chunks = adapter.load_output_chunks('plan1', 1)
385
+ _(chunks.length).must_equal 0
386
+ end
387
+ end
334
388
  end
335
389
 
336
390
  describe Dynflow::PersistenceAdapters::Sequel do
@@ -464,6 +518,27 @@ module Dynflow
464
518
  loaded_plan = adapter.load_execution_plan(plan[:id])
465
519
  assert_equal_attributes!(plan_data, loaded_plan)
466
520
  end
521
+
522
+ it 'does not leak Sequel blobs' do
523
+ db = adapter.send(:db)
524
+ # Prepare records for saving
525
+ plan = prepare_plans.first
526
+
527
+ value = 'a' * 1000
528
+
529
+ adata = action_data.merge({:output => { :key => value }})
530
+ plan_record = adapter.send(:prepare_record, :execution_plan, plan.merge(:uuid => plan[:id]))
531
+ action_record = adapter.send(:prepare_record, :action, adata.dup)
532
+
533
+ # Insert the records
534
+ db[:dynflow_execution_plans].insert plan_record.merge(:uuid => plan[:id])
535
+ db[:dynflow_actions].insert action_record.merge(:execution_plan_uuid => plan[:id], :id => adata[:id])
536
+
537
+ # Load the saved records
538
+ loaded_action = adapter.load_action(plan[:id], adata[:id])
539
+ _(loaded_action[:output][:key].class).must_equal String
540
+ _(loaded_action[:output][:key]).must_equal value
541
+ end
467
542
  end
468
543
  end
469
544
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.10
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Necas
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-03-01 00:00:00.000000000 Z
12
+ date: 2023-05-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -678,7 +678,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
678
678
  - !ruby/object:Gem::Version
679
679
  version: '0'
680
680
  requirements: []
681
- rubygems_version: 3.3.20
681
+ rubygems_version: 3.4.12
682
682
  signing_key:
683
683
  specification_version: 4
684
684
  summary: DYNamic workFLOW engine