dynflow 1.1.2 → 1.1.3

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: bd598d9b0729e31768151b6079b88fad1fd1ee9336dae678f65abd2787aeec85
4
- data.tar.gz: 305b54dde96ab5521e04cc1574f7169e46ea89b3fe0d4e0ae30f8a5b220a7d51
3
+ metadata.gz: f162fe5b456a60898fad5ea4f3eec20ee1301f4adc8e4939946b03962b0ce12d
4
+ data.tar.gz: 591f53a8eafe2f94f61c5988fdc02d39ee8405726522a458394cfaa5dd3a260e
5
5
  SHA512:
6
- metadata.gz: f2f30544726a096da979f6f07d0b5ca9620cfbd9c99fd95092d132b3b9a7004447f3dc6aed28bf61df9db373970236ce83a43b1e60d2f623851dc951e441ce6e
7
- data.tar.gz: cd392df7f4e7aba1f41ae7bb6c903347ae2505562b866150045f6689e7729c59ac712f972b3cfc0f754064b53895a8dd92c147d6037e9946fbcbf69cea468d62
6
+ metadata.gz: 41345060fa6037bd1d10f9f6c3515ffe495b131e71744a8820d112f0547f21426b3829de522d1d4571cc4beecfde12f350f080f83fa62ec1be67652a976ef49d
7
+ data.tar.gz: 7ecec4743683c885c4e98caf978d1cd75d0647da11d88baab5b051fffa0192ee727d6feef1d61fbc1906f874f412f5486a9eb516e5a7aeded9c343de2f6df25d
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency "multi_json"
22
22
  s.add_dependency "apipie-params"
23
23
  s.add_dependency "algebrick", '~> 0.7.0'
24
- s.add_dependency "concurrent-ruby", '~> 1.0'
24
+ s.add_dependency "concurrent-ruby", '~> 1.0.0'
25
25
  s.add_dependency "concurrent-ruby-edge", '~> 0.2.0'
26
26
  s.add_dependency "sequel", '>= 4.0.0'
27
27
 
@@ -251,6 +251,18 @@ module Dynflow
251
251
  def self.lock_id(action_class)
252
252
  'singleton-action:' + action_class
253
253
  end
254
+
255
+ def self.valid_owner_ids(coordinator)
256
+ lock_ids = coordinator.find_locks(:class => self.name).map(&:owner_id)
257
+ plans = coordinator.adapter.find_execution_plans(:uuid => lock_ids)
258
+ plans = Hash[*plans.map { |plan| [plan[:uuid], plan[:state]] }.flatten]
259
+ lock_ids.select { |id| plans.key?(id) && !%w(stopped paused).include?(plans[id]) }
260
+ .map { |id| 'execution-plan:' + id }
261
+ end
262
+
263
+ def self.valid_classes
264
+ [self]
265
+ end
254
266
  end
255
267
 
256
268
  class ExecutionLock < LockByWorld
@@ -367,7 +379,7 @@ module Dynflow
367
379
  end
368
380
 
369
381
  def clean_orphaned_locks
370
- cleanup_classes = [LockByWorld]
382
+ cleanup_classes = [LockByWorld, SingletonActionLock]
371
383
  ret = []
372
384
  cleanup_classes.each do |cleanup_class|
373
385
  valid_owner_ids = cleanup_class.valid_owner_ids(self)
@@ -23,6 +23,10 @@ module Dynflow
23
23
  def find_records(record)
24
24
  raise NotImplementedError
25
25
  end
26
+
27
+ def find_execution_plans(filter_options)
28
+ raise NotImplementedError
29
+ end
26
30
  end
27
31
  end
28
32
  end
@@ -28,6 +28,10 @@ module Dynflow
28
28
  def find_records(filter_options)
29
29
  @sequel_adapter.find_coordinator_records(filters: filter_options)
30
30
  end
31
+
32
+ def find_execution_plans(filter_options)
33
+ @sequel_adapter.find_execution_plans(filters: filter_options)
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -45,6 +45,7 @@ module Dynflow
45
45
  invalidated_worlds = world.perform_validity_checks
46
46
  world.auto_execute
47
47
  world.post_initialization if invalidated_worlds > 0
48
+ config.run_post_executor_init_hooks(world)
48
49
  end
49
50
  end
50
51
  end
@@ -39,8 +39,9 @@ module Dynflow
39
39
  self.lazy_initialization = !::Rails.env.production?
40
40
  self.rake_tasks_with_executor = %w(db:migrate db:seed)
41
41
 
42
- @on_init = []
43
- @on_executor_init = []
42
+ @on_init = []
43
+ @on_executor_init = []
44
+ @post_executor_init = []
44
45
  end
45
46
 
46
47
  # Action related info such as exceptions raised inside the actions' methods
@@ -65,6 +66,14 @@ module Dynflow
65
66
  source.each { |init| init.call(world) }
66
67
  end
67
68
 
69
+ def post_executor_init(&block)
70
+ @post_executor_init << block
71
+ end
72
+
73
+ def run_post_executor_init_hooks(world)
74
+ @post_executor_init.each { |init| init.call(world) }
75
+ end
76
+
68
77
  def initialize_world(world_class = ::Dynflow::World)
69
78
  world_class.new(world_config)
70
79
  end
@@ -142,12 +142,12 @@ module Dynflow
142
142
 
143
143
  def log_memory_limit_exceeded(current_memory, memory_limit)
144
144
  message = "Memory level exceeded, registered #{current_memory} bytes, which is greater than #{memory_limit} limit."
145
- world.dynflow_logger.error(message)
145
+ world.logger.error(message)
146
146
  end
147
147
 
148
148
  def log_memory_within_limit(current_memory, memory_limit)
149
149
  message = "Memory level OK, registered #{current_memory} bytes, which is less than #{memory_limit} limit."
150
- world.dynflow_logger.debug(message)
150
+ world.logger.debug(message)
151
151
  end
152
152
 
153
153
  private
@@ -4,7 +4,7 @@ module Dynflow
4
4
  # assert that +assert_actioned_plan+ was planned by +action+ with arguments +plan_input+
5
5
  # alternatively plan-input can be asserted with +block+
6
6
  def assert_action_planned_with(action, planned_action_class, *plan_input, &block)
7
- found_classes = assert_action_planed(action, planned_action_class)
7
+ found_classes = assert_action_planned(action, planned_action_class)
8
8
  found = found_classes.select do |a|
9
9
  if plan_input.empty?
10
10
  block.call a.plan_input
@@ -1,3 +1,3 @@
1
1
  module Dynflow
2
- VERSION = '1.1.2'.freeze
2
+ VERSION = '1.1.3'.freeze
3
3
  end
@@ -82,13 +82,13 @@ module Dynflow
82
82
  logger.error "unexpected error when invalidating execution plan #{execution_lock.execution_plan_id}, skipping"
83
83
  end
84
84
  coordinator.release(execution_lock)
85
- coordinator.release_by_owner(execution_lock.execution_plan_id)
85
+ coordinator.release_by_owner(execution_lock.id)
86
86
  return
87
87
  end
88
88
  unless plan.valid?
89
89
  logger.error "invalid plan #{plan.id}, skipping"
90
90
  coordinator.release(execution_lock)
91
- coordinator.release_by_owner(execution_lock.execution_plan_id)
91
+ coordinator.release_by_owner(execution_lock.id)
92
92
  return
93
93
  end
94
94
  yield plan
@@ -122,6 +122,20 @@ module Dynflow
122
122
  "unlock world-invalidation:#{executor_world.id}"]
123
123
  client_world.coordinator.adapter.lock_log.must_equal(expected_locks)
124
124
  end
125
+
126
+ it 'releases singleton locks belonging to missing execution plan' do
127
+ execution_plan_id = 'missing'
128
+ action_class = 'ActionClass'
129
+ locks = [Coordinator::ExecutionLock.new(executor_world, "missing", nil, nil),
130
+ Coordinator::SingletonActionLock.new(action_class, execution_plan_id)]
131
+ locks.each { |lock| executor_world.coordinator.acquire lock }
132
+ client_world.invalidate(executor_world.registered_world)
133
+ expected_locks = ["lock world-invalidation:#{executor_world.id}",
134
+ "unlock execution-plan:#{execution_plan_id}",
135
+ "unlock singleton-action:#{action_class}",
136
+ "unlock world-invalidation:#{executor_world.id}"]
137
+ client_world.coordinator.adapter.lock_log.must_equal(expected_locks)
138
+ end
125
139
  end
126
140
  end
127
141
 
@@ -291,6 +305,37 @@ module Dynflow
291
305
  invalid_locks.wont_include(valid_lock)
292
306
  end
293
307
  end
308
+
309
+ describe 'with singleton action locks' do
310
+ def plan_in_state(state)
311
+ plan = executor_world.persistence.load_execution_plan(trigger_waiting_action.id)
312
+ step = plan.steps.values.last
313
+ wait_for do
314
+ executor_world.persistence.load_step(step.execution_plan_id, step.id, executor_world).state == :suspended
315
+ end
316
+ plan.state = state if plan.state != state
317
+ plan.save
318
+ plan
319
+ end
320
+
321
+ let(:valid_plan) { plan_in_state :running }
322
+ let(:invalid_plan) { plan_in_state :stopped }
323
+ let(:valid_lock) { Coordinator::SingletonActionLock.new('MyClass1', valid_plan.id) }
324
+ let(:invalid_lock) { Coordinator::SingletonActionLock.new('MyClass2', 'plan-id') }
325
+ let(:invalid_lock2) { Coordinator::SingletonActionLock.new('MyClass3', invalid_plan.id) }
326
+
327
+ it 'unlocks orphaned singleton action locks' do
328
+ executor_world
329
+ client_world.coordinator.acquire(valid_lock)
330
+ client_world.coordinator.acquire(invalid_lock)
331
+ client_world.coordinator.acquire(invalid_lock2)
332
+ invalid_locks = client_world.coordinator.clean_orphaned_locks
333
+ # It must invalidate locks which are missing or in paused/stopped
334
+ invalid_locks.must_include(invalid_lock)
335
+ invalid_locks.must_include(invalid_lock2)
336
+ invalid_locks.wont_include(valid_lock)
337
+ end
338
+ end
294
339
  end
295
340
  end
296
341
  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.1.2
4
+ version: 1.1.3
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: 2018-11-05 00:00:00.000000000 Z
12
+ date: 2018-11-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '1.0'
62
+ version: 1.0.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '1.0'
69
+ version: 1.0.0
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: concurrent-ruby-edge
72
72
  requirement: !ruby/object:Gem::Requirement