dynflow 1.1.2 → 1.1.3

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