dynflow 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.travis.yml +3 -4
- data/Dockerfile +9 -0
- data/Gemfile +6 -0
- data/Rakefile +1 -0
- data/doc/pages/Gemfile +1 -0
- data/doc/pages/Rakefile +1 -0
- data/doc/pages/plugins/alert_block.rb +1 -0
- data/doc/pages/plugins/div_tag.rb +1 -0
- data/doc/pages/plugins/graphviz.rb +1 -0
- data/doc/pages/plugins/plantuml.rb +1 -0
- data/doc/pages/plugins/play.rb +1 -0
- data/doc/pages/plugins/tags.rb +1 -0
- data/doc/pages/plugins/toc.rb +1 -0
- data/docker-compose.yml +41 -0
- data/dynflow.gemspec +1 -0
- data/examples/clock_benchmark.rb +1 -0
- data/examples/example_helper.rb +19 -2
- data/examples/future_execution.rb +2 -1
- data/examples/memory_limit_watcher.rb +1 -0
- data/examples/orchestrate.rb +4 -5
- data/examples/orchestrate_evented.rb +3 -2
- data/examples/remote_executor.rb +68 -0
- data/examples/singletons.rb +4 -3
- data/examples/sub_plan_concurrency_control.rb +2 -1
- data/examples/sub_plans.rb +3 -2
- data/examples/termination.rb +1 -0
- data/lib/dynflow.rb +20 -0
- data/lib/dynflow/action.rb +28 -3
- data/lib/dynflow/action/cancellable.rb +1 -0
- data/lib/dynflow/action/format.rb +1 -0
- data/lib/dynflow/action/missing.rb +1 -0
- data/lib/dynflow/action/polling.rb +3 -1
- data/lib/dynflow/action/progress.rb +1 -0
- data/lib/dynflow/action/rescue.rb +1 -0
- data/lib/dynflow/action/singleton.rb +1 -0
- data/lib/dynflow/action/suspended.rb +9 -2
- data/lib/dynflow/action/timeouts.rb +2 -1
- data/lib/dynflow/action/with_bulk_sub_plans.rb +2 -1
- data/lib/dynflow/action/with_polling_sub_plans.rb +7 -5
- data/lib/dynflow/action/with_sub_plans.rb +1 -0
- data/lib/dynflow/active_job/queue_adapter.rb +1 -0
- data/lib/dynflow/actor.rb +13 -5
- data/lib/dynflow/actors.rb +1 -0
- data/lib/dynflow/actors/execution_plan_cleaner.rb +1 -0
- data/lib/dynflow/clock.rb +27 -47
- data/lib/dynflow/config.rb +11 -2
- data/lib/dynflow/connectors.rb +1 -0
- data/lib/dynflow/connectors/abstract.rb +1 -0
- data/lib/dynflow/connectors/database.rb +1 -0
- data/lib/dynflow/connectors/direct.rb +1 -0
- data/lib/dynflow/coordinator.rb +1 -0
- data/lib/dynflow/coordinator_adapters.rb +1 -0
- data/lib/dynflow/coordinator_adapters/abstract.rb +1 -0
- data/lib/dynflow/coordinator_adapters/sequel.rb +1 -0
- data/lib/dynflow/dead_letter_silencer.rb +2 -0
- data/lib/dynflow/debug/telemetry/persistence.rb +1 -0
- data/lib/dynflow/delayed_executors.rb +1 -0
- data/lib/dynflow/delayed_executors/abstract.rb +1 -0
- data/lib/dynflow/delayed_executors/abstract_core.rb +1 -0
- data/lib/dynflow/delayed_executors/polling.rb +1 -0
- data/lib/dynflow/delayed_plan.rb +1 -0
- data/lib/dynflow/director.rb +80 -15
- data/lib/dynflow/director/execution_plan_manager.rb +17 -3
- data/lib/dynflow/director/flow_manager.rb +1 -0
- data/lib/dynflow/director/{work_queue.rb → queue_hash.rb} +9 -8
- data/lib/dynflow/director/running_steps_manager.rb +55 -18
- data/lib/dynflow/director/sequence_cursor.rb +1 -0
- data/lib/dynflow/director/sequential_manager.rb +12 -2
- data/lib/dynflow/dispatcher.rb +4 -2
- data/lib/dynflow/dispatcher/abstract.rb +1 -0
- data/lib/dynflow/dispatcher/client_dispatcher.rb +6 -4
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +13 -1
- data/lib/dynflow/errors.rb +1 -0
- data/lib/dynflow/execution_history.rb +1 -0
- data/lib/dynflow/execution_plan.rb +3 -2
- data/lib/dynflow/execution_plan/dependency_graph.rb +1 -0
- data/lib/dynflow/execution_plan/hooks.rb +1 -0
- data/lib/dynflow/execution_plan/output_reference.rb +2 -1
- data/lib/dynflow/execution_plan/steps.rb +1 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +10 -5
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +2 -0
- data/lib/dynflow/execution_plan/steps/error.rb +1 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +1 -0
- data/lib/dynflow/execution_plan/steps/plan_step.rb +1 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +1 -0
- data/lib/dynflow/executors.rb +1 -1
- data/lib/dynflow/executors/abstract/core.rb +132 -0
- data/lib/dynflow/executors/parallel.rb +24 -11
- data/lib/dynflow/executors/parallel/core.rb +10 -91
- data/lib/dynflow/executors/parallel/pool.rb +4 -2
- data/lib/dynflow/executors/parallel/worker.rb +2 -1
- data/lib/dynflow/executors/sidekiq/core.rb +121 -0
- data/lib/dynflow/executors/sidekiq/internal_job_base.rb +24 -0
- data/lib/dynflow/executors/sidekiq/orchestrator_jobs.rb +60 -0
- data/lib/dynflow/executors/sidekiq/redis_locking.rb +69 -0
- data/lib/dynflow/executors/sidekiq/serialization.rb +33 -0
- data/lib/dynflow/executors/sidekiq/worker_jobs.rb +42 -0
- data/lib/dynflow/flows.rb +1 -0
- data/lib/dynflow/flows/abstract.rb +1 -0
- data/lib/dynflow/flows/abstract_composed.rb +1 -0
- data/lib/dynflow/flows/atom.rb +1 -0
- data/lib/dynflow/flows/concurrence.rb +1 -0
- data/lib/dynflow/flows/sequence.rb +1 -0
- data/lib/dynflow/logger_adapters.rb +1 -0
- data/lib/dynflow/logger_adapters/abstract.rb +1 -0
- data/lib/dynflow/logger_adapters/delegator.rb +1 -0
- data/lib/dynflow/logger_adapters/formatters.rb +1 -0
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +1 -0
- data/lib/dynflow/logger_adapters/formatters/exception.rb +1 -0
- data/lib/dynflow/logger_adapters/simple.rb +1 -0
- data/lib/dynflow/middleware.rb +1 -0
- data/lib/dynflow/middleware/common/singleton.rb +1 -0
- data/lib/dynflow/middleware/common/transaction.rb +1 -0
- data/lib/dynflow/middleware/register.rb +1 -0
- data/lib/dynflow/middleware/resolver.rb +1 -0
- data/lib/dynflow/middleware/stack.rb +1 -0
- data/lib/dynflow/middleware/world.rb +1 -0
- data/lib/dynflow/persistence.rb +3 -2
- data/lib/dynflow/persistence_adapters.rb +1 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +10 -7
- data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/002_incremental_progress.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/003_parent_action.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/006_fix_data_length.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/007_future_execution.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/009_fix_mysql_data_length.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/010_add_execution_plans_label.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/011_placeholder.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/012_add_delayed_plans_serialized_args.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/017_add_delayed_plan_frozen.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/018_add_uuid_column.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/019_update_mysql_time_precision.rb +48 -0
- data/lib/dynflow/rails.rb +1 -0
- data/lib/dynflow/rails/configuration.rb +6 -3
- data/lib/dynflow/rails/daemon.rb +1 -0
- data/lib/dynflow/round_robin.rb +1 -0
- data/lib/dynflow/semaphores.rb +1 -0
- data/lib/dynflow/semaphores/abstract.rb +1 -0
- data/lib/dynflow/semaphores/aggregating.rb +1 -0
- data/lib/dynflow/semaphores/dummy.rb +1 -0
- data/lib/dynflow/semaphores/stateful.rb +1 -0
- data/lib/dynflow/serializable.rb +13 -4
- data/lib/dynflow/serializer.rb +24 -0
- data/lib/dynflow/serializers.rb +1 -0
- data/lib/dynflow/serializers/abstract.rb +1 -0
- data/lib/dynflow/serializers/noop.rb +1 -0
- data/lib/dynflow/stateful.rb +1 -0
- data/lib/dynflow/telemetry.rb +1 -0
- data/lib/dynflow/telemetry_adapters/abstract.rb +1 -0
- data/lib/dynflow/telemetry_adapters/dummy.rb +1 -0
- data/lib/dynflow/telemetry_adapters/statsd.rb +1 -0
- data/lib/dynflow/testing.rb +1 -0
- data/lib/dynflow/testing/assertions.rb +6 -5
- data/lib/dynflow/testing/dummy_execution_plan.rb +1 -0
- data/lib/dynflow/testing/dummy_executor.rb +19 -2
- data/lib/dynflow/testing/dummy_planned_action.rb +1 -0
- data/lib/dynflow/testing/dummy_step.rb +3 -1
- data/lib/dynflow/testing/dummy_world.rb +9 -0
- data/lib/dynflow/testing/factories.rb +6 -1
- data/lib/dynflow/testing/in_thread_executor.rb +22 -3
- data/lib/dynflow/testing/in_thread_world.rb +9 -0
- data/lib/dynflow/testing/managed_clock.rb +1 -0
- data/lib/dynflow/testing/mimic.rb +1 -0
- data/lib/dynflow/throttle_limiter.rb +1 -0
- data/lib/dynflow/transaction_adapters.rb +1 -0
- data/lib/dynflow/transaction_adapters/abstract.rb +1 -0
- data/lib/dynflow/transaction_adapters/active_record.rb +1 -0
- data/lib/dynflow/transaction_adapters/none.rb +1 -0
- data/lib/dynflow/utils.rb +1 -0
- data/lib/dynflow/utils/indifferent_hash.rb +1 -0
- data/lib/dynflow/utils/priority_queue.rb +1 -0
- data/lib/dynflow/version.rb +2 -1
- data/lib/dynflow/watchers/memory_consumption_watcher.rb +1 -0
- data/lib/dynflow/web.rb +1 -0
- data/lib/dynflow/web/console.rb +1 -0
- data/lib/dynflow/web/console_helpers.rb +1 -0
- data/lib/dynflow/web/filtering_helpers.rb +1 -0
- data/lib/dynflow/web/world_helpers.rb +1 -0
- data/lib/dynflow/web_console.rb +1 -0
- data/lib/dynflow/world.rb +11 -1
- data/lib/dynflow/world/invalidation.rb +7 -1
- data/test/abnormal_states_recovery_test.rb +41 -40
- data/test/action_test.rb +160 -110
- data/test/activejob_adapter_test.rb +1 -0
- data/test/batch_sub_tasks_test.rb +12 -11
- data/test/clock_test.rb +2 -1
- data/test/concurrency_control_test.rb +20 -19
- data/test/coordinator_test.rb +20 -21
- data/test/daemon_test.rb +2 -1
- data/test/dead_letter_silencer_test.rb +9 -7
- data/test/dispatcher_test.rb +2 -1
- data/test/execution_plan_cleaner_test.rb +13 -12
- data/test/execution_plan_hooks_test.rb +3 -2
- data/test/execution_plan_test.rb +33 -32
- data/test/executor_test.rb +533 -489
- data/test/future_execution_test.rb +45 -44
- data/test/memory_cosumption_watcher_test.rb +5 -4
- data/test/middleware_test.rb +55 -54
- data/test/persistence_test.rb +56 -53
- data/test/rescue_test.rb +36 -35
- data/test/round_robin_test.rb +13 -12
- data/test/semaphores_test.rb +31 -30
- data/test/support/code_workflow_example.rb +1 -0
- data/test/support/dummy_example.rb +14 -1
- data/test/support/middleware_example.rb +2 -1
- data/test/support/rails/config/environment.rb +1 -0
- data/test/support/rescue_example.rb +1 -0
- data/test/support/test_execution_log.rb +1 -0
- data/test/test_helper.rb +18 -17
- data/test/testing_test.rb +45 -44
- data/test/utils_test.rb +18 -17
- data/test/web_console_test.rb +1 -0
- data/test/world_test.rb +7 -6
- metadata +13 -4
- data/lib/dynflow/executors/abstract.rb +0 -40
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/web.rb
CHANGED
data/lib/dynflow/web/console.rb
CHANGED
data/lib/dynflow/web_console.rb
CHANGED
data/lib/dynflow/world.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
require 'dynflow/world/invalidation'
|
3
4
|
|
4
5
|
module Dynflow
|
@@ -29,7 +30,12 @@ module Dynflow
|
|
29
30
|
:backup_deleted_plans => @config.backup_deleted_plans,
|
30
31
|
:backup_dir => @config.backup_dir)
|
31
32
|
@coordinator = Coordinator.new(@config.coordinator_adapter)
|
32
|
-
|
33
|
+
if @config.executor
|
34
|
+
@executor = Executors::Parallel.new(self,
|
35
|
+
executor_class: @config.executor,
|
36
|
+
heartbeat_interval: @config.executor_heartbeat_interval,
|
37
|
+
queues_options: @config.queues)
|
38
|
+
end
|
33
39
|
@action_classes = @config.action_classes
|
34
40
|
@auto_rescue = @config.auto_rescue
|
35
41
|
@exit_on_terminate = Concurrent::AtomicBoolean.new(@config.exit_on_terminate)
|
@@ -217,6 +223,10 @@ module Dynflow
|
|
217
223
|
publish_request(Dispatcher::Event[execution_plan_id, step_id, event], done, false)
|
218
224
|
end
|
219
225
|
|
226
|
+
def plan_event(execution_plan_id, step_id, event, time, accepted = Concurrent::Promises.resolvable_future)
|
227
|
+
publish_request(Dispatcher::Event[execution_plan_id, step_id, event, time], accepted, false)
|
228
|
+
end
|
229
|
+
|
220
230
|
def ping(world_id, timeout, done = Concurrent::Promises.resolvable_future)
|
221
231
|
publish_request(Dispatcher::Ping[world_id, true], done, false, timeout)
|
222
232
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Dynflow
|
2
3
|
class World
|
3
4
|
module Invalidation
|
@@ -35,8 +36,13 @@ module Dynflow
|
|
35
36
|
with_valid_execution_plan_for_lock(planning_lock) do |plan|
|
36
37
|
plan.steps.values.each { |step| invalidate_step step }
|
37
38
|
|
38
|
-
state = plan.plan_steps.all? { |step| step.state == :success }
|
39
|
+
state = if plan.plan_steps.any? && plan.plan_steps.all? { |step| step.state == :success }
|
40
|
+
:planned
|
41
|
+
else
|
42
|
+
:stopped
|
43
|
+
end
|
39
44
|
plan.update_state(state)
|
45
|
+
|
40
46
|
coordinator.release(planning_lock)
|
41
47
|
execute(plan.id) if plan.state == :planned
|
42
48
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
require_relative 'test_helper'
|
3
4
|
require 'ostruct'
|
4
5
|
|
@@ -64,11 +65,11 @@ module Dynflow
|
|
64
65
|
it 'when no executor is available, marks the plans as paused' do
|
65
66
|
executor_world_2.terminate.wait
|
66
67
|
with_invalidation_while_executing(false) do |plan|
|
67
|
-
plan.state.must_equal :paused
|
68
|
-
plan.result.must_equal :pending
|
68
|
+
_(plan.state).must_equal :paused
|
69
|
+
_(plan.result).must_equal :pending
|
69
70
|
expected_history = [['start execution', executor_world.id],
|
70
71
|
['terminate execution', executor_world.id]]
|
71
|
-
plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
|
72
|
+
_(plan.execution_history.map { |h| [h.name, h.world_id] }).must_equal(expected_history)
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
@@ -103,14 +104,14 @@ module Dynflow
|
|
103
104
|
client_world.invalidate(executor_world.registered_world)
|
104
105
|
expected_locks = ["lock world-invalidation:#{executor_world.id}",
|
105
106
|
"unlock world-invalidation:#{executor_world.id}"]
|
106
|
-
client_world.coordinator.adapter.lock_log.must_equal(expected_locks)
|
107
|
+
_(client_world.coordinator.adapter.lock_log).must_equal(expected_locks)
|
107
108
|
end
|
108
109
|
|
109
110
|
it "prevents from running the consistency checks twice on the same world concurrently" do
|
110
111
|
client_world.invalidate(executor_world.registered_world)
|
111
112
|
expected_locks = ["lock world-invalidation:#{executor_world.id}",
|
112
113
|
"unlock world-invalidation:#{executor_world.id}"]
|
113
|
-
client_world.coordinator.adapter.lock_log.must_equal(expected_locks)
|
114
|
+
_(client_world.coordinator.adapter.lock_log).must_equal(expected_locks)
|
114
115
|
end
|
115
116
|
|
116
117
|
it "handles missing execution plans" do
|
@@ -120,7 +121,7 @@ module Dynflow
|
|
120
121
|
expected_locks = ["lock world-invalidation:#{executor_world.id}",
|
121
122
|
"unlock execution-plan:missing",
|
122
123
|
"unlock world-invalidation:#{executor_world.id}"]
|
123
|
-
client_world.coordinator.adapter.lock_log.must_equal(expected_locks)
|
124
|
+
_(client_world.coordinator.adapter.lock_log).must_equal(expected_locks)
|
124
125
|
end
|
125
126
|
|
126
127
|
it 'releases singleton locks belonging to missing execution plan' do
|
@@ -134,7 +135,7 @@ module Dynflow
|
|
134
135
|
"unlock execution-plan:#{execution_plan_id}",
|
135
136
|
"unlock singleton-action:#{action_class}",
|
136
137
|
"unlock world-invalidation:#{executor_world.id}"]
|
137
|
-
client_world.coordinator.adapter.lock_log.must_equal(expected_locks)
|
138
|
+
_(client_world.coordinator.adapter.lock_log).must_equal(expected_locks)
|
138
139
|
end
|
139
140
|
|
140
141
|
describe 'planning locks' do
|
@@ -148,7 +149,7 @@ module Dynflow
|
|
148
149
|
"unlock execution-plan:#{plan.id}", # planning lock
|
149
150
|
"lock execution-plan:#{plan.id}", # execution lock
|
150
151
|
"unlock world-invalidation:#{client_world.id}"]
|
151
|
-
executor_world.coordinator.adapter.lock_log.must_equal(expected_locks)
|
152
|
+
_(executor_world.coordinator.adapter.lock_log).must_equal(expected_locks)
|
152
153
|
wait_for do
|
153
154
|
plan = client_world_2.persistence.load_execution_plan(plan.id)
|
154
155
|
plan.state == :stopped
|
@@ -167,9 +168,9 @@ module Dynflow
|
|
167
168
|
expected_locks = ["lock world-invalidation:#{client_world.id}",
|
168
169
|
"unlock execution-plan:#{plan.id}",
|
169
170
|
"unlock world-invalidation:#{client_world.id}"]
|
170
|
-
client_world_2.coordinator.adapter.lock_log.must_equal(expected_locks)
|
171
|
+
_(client_world_2.coordinator.adapter.lock_log).must_equal(expected_locks)
|
171
172
|
plan = client_world_2.persistence.load_execution_plan(plan.id)
|
172
|
-
plan.state.must_equal :stopped
|
173
|
+
_(plan.state).must_equal :stopped
|
173
174
|
end
|
174
175
|
|
175
176
|
it 'releases orphaned planning locks without execution plans' do
|
@@ -179,7 +180,7 @@ module Dynflow
|
|
179
180
|
expected_locks = ["lock world-invalidation:#{client_world.id}",
|
180
181
|
"unlock execution-plan:#{uuid}",
|
181
182
|
"unlock world-invalidation:#{client_world.id}"]
|
182
|
-
client_world_2.coordinator.adapter.lock_log.must_equal(expected_locks)
|
183
|
+
_(client_world_2.coordinator.adapter.lock_log).must_equal(expected_locks)
|
183
184
|
end
|
184
185
|
end
|
185
186
|
end
|
@@ -194,10 +195,10 @@ module Dynflow
|
|
194
195
|
it "prevents from running the auto-execution twice" do
|
195
196
|
client_world.auto_execute
|
196
197
|
expected_locks = ["lock auto-execute", "unlock auto-execute"]
|
197
|
-
client_world.coordinator.adapter.lock_log.must_equal(expected_locks)
|
198
|
+
_(client_world.coordinator.adapter.lock_log).must_equal(expected_locks)
|
198
199
|
lock = Coordinator::AutoExecuteLock.new(client_world)
|
199
200
|
client_world.coordinator.acquire(lock)
|
200
|
-
client_world.auto_execute.must_equal []
|
201
|
+
_(client_world.auto_execute).must_equal []
|
201
202
|
end
|
202
203
|
|
203
204
|
it "re-runs the plans that were planned but not executed" do
|
@@ -212,7 +213,7 @@ module Dynflow
|
|
212
213
|
end
|
213
214
|
expected_history = [['start execution', executor_world.id],
|
214
215
|
['finish execution', executor_world.id]]
|
215
|
-
plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
|
216
|
+
_(plan.execution_history.map { |h| [h.name, h.world_id] }).must_equal(expected_history)
|
216
217
|
end
|
217
218
|
|
218
219
|
it "re-runs the plans that were terminated but not re-executed (because no available executor)" do
|
@@ -236,10 +237,10 @@ module Dynflow
|
|
236
237
|
retries = executor_world.auto_execute
|
237
238
|
retries.each(&:wait)
|
238
239
|
plan = client_world.persistence.load_execution_plan(triggered.id)
|
239
|
-
plan.state.must_equal :paused
|
240
|
+
_(plan.state).must_equal :paused
|
240
241
|
expected_history = [['start execution', executor_world.id],
|
241
242
|
['pause execution', executor_world.id]]
|
242
|
-
plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
|
243
|
+
_(plan.execution_history.map { |h| [h.name, h.world_id] }).must_equal(expected_history)
|
243
244
|
end
|
244
245
|
end
|
245
246
|
|
@@ -266,45 +267,45 @@ module Dynflow
|
|
266
267
|
|
267
268
|
it 'performs the validity check on world creation if auto_validity_check enabled' do
|
268
269
|
client_world.coordinator.register_world(invalid_world)
|
269
|
-
client_world.coordinator.find_worlds(false, id: invalid_world.id).wont_be_empty
|
270
|
+
_(client_world.coordinator.find_worlds(false, id: invalid_world.id)).wont_be_empty
|
270
271
|
world_with_auto_validity_check
|
271
|
-
client_world.coordinator.find_worlds(false, id: invalid_world.id).must_be_empty
|
272
|
+
_(client_world.coordinator.find_worlds(false, id: invalid_world.id)).must_be_empty
|
272
273
|
end
|
273
274
|
|
274
275
|
it 'by default, the auto_validity_check is enabled only for executor words' do
|
275
276
|
client_world_config = Config::ForWorld.new(Config.new.tap { |c| c.executor = false }, create_world )
|
276
|
-
client_world_config.auto_validity_check.must_equal false
|
277
|
+
_(client_world_config.auto_validity_check).must_equal false
|
277
278
|
|
278
|
-
executor_world_config = Config::ForWorld.new(Config.new.tap { |c| c.executor =
|
279
|
-
executor_world_config.auto_validity_check.must_equal true
|
279
|
+
executor_world_config = Config::ForWorld.new(Config.new.tap { |c| c.executor = Executors::Parallel::Core }, create_world )
|
280
|
+
_(executor_world_config.auto_validity_check).must_equal true
|
280
281
|
end
|
281
282
|
|
282
283
|
it 'reports the validation status' do
|
283
284
|
client_world.coordinator.register_world(invalid_world)
|
284
285
|
results = client_world.worlds_validity_check
|
285
|
-
client_world.coordinator.find_worlds(false, id: invalid_world.id).must_be_empty
|
286
|
+
_(client_world.coordinator.find_worlds(false, id: invalid_world.id)).must_be_empty
|
286
287
|
|
287
|
-
results[invalid_world.id].must_equal :invalidated
|
288
|
+
_(results[invalid_world.id]).must_equal :invalidated
|
288
289
|
|
289
|
-
results[client_world.id].must_equal :valid
|
290
|
+
_(results[client_world.id]).must_equal :valid
|
290
291
|
end
|
291
292
|
|
292
293
|
it 'allows checking only, without actual invalidation' do
|
293
294
|
client_world.coordinator.register_world(invalid_world)
|
294
295
|
results = client_world.worlds_validity_check(false)
|
295
|
-
client_world.coordinator.find_worlds(false, id: invalid_world.id).wont_be_empty
|
296
|
+
_(client_world.coordinator.find_worlds(false, id: invalid_world.id)).wont_be_empty
|
296
297
|
|
297
|
-
results[invalid_world.id].must_equal :invalid
|
298
|
+
_(results[invalid_world.id]).must_equal :invalid
|
298
299
|
end
|
299
300
|
|
300
301
|
it 'allows to filter the worlds to run the check on' do
|
301
302
|
client_world.coordinator.register_world(invalid_world)
|
302
303
|
client_world.coordinator.register_world(invalid_world_2)
|
303
|
-
client_world.coordinator.find_worlds(false, id: [invalid_world.id, invalid_world_2.id]).size.must_equal 2
|
304
|
+
_(client_world.coordinator.find_worlds(false, id: [invalid_world.id, invalid_world_2.id]).size).must_equal 2
|
304
305
|
|
305
306
|
results = client_world.worlds_validity_check(true, :id => invalid_world.id)
|
306
|
-
results.must_equal(invalid_world.id => :invalidated)
|
307
|
-
client_world.coordinator.find_worlds(false, id: [invalid_world.id, invalid_world_2.id]).size.must_equal 1
|
307
|
+
_(results).must_equal(invalid_world.id => :invalidated)
|
308
|
+
_(client_world.coordinator.find_worlds(false, id: [invalid_world.id, invalid_world_2.id]).size).must_equal 1
|
308
309
|
end
|
309
310
|
end
|
310
311
|
end
|
@@ -327,22 +328,22 @@ module Dynflow
|
|
327
328
|
before do
|
328
329
|
client_world.coordinator.acquire(valid_lock)
|
329
330
|
client_world.coordinator.acquire(invalid_lock)
|
330
|
-
current_locks.must_include(valid_lock)
|
331
|
-
current_locks.must_include(invalid_lock)
|
331
|
+
_(current_locks).must_include(valid_lock)
|
332
|
+
_(current_locks).must_include(invalid_lock)
|
332
333
|
end
|
333
334
|
|
334
335
|
it 'performs the validity check on world creation if auto_validity_check enabled' do
|
335
336
|
world_with_auto_validity_check
|
336
|
-
current_locks.must_include(valid_lock)
|
337
|
-
current_locks.wont_include(invalid_lock)
|
337
|
+
_(current_locks).must_include(valid_lock)
|
338
|
+
_(current_locks).wont_include(invalid_lock)
|
338
339
|
end
|
339
340
|
|
340
341
|
it 'performs the validity check on world creation if auto_validity_check enabled' do
|
341
342
|
invalid_locks = client_world.locks_validity_check
|
342
|
-
current_locks.must_include(valid_lock)
|
343
|
-
current_locks.wont_include(invalid_lock)
|
344
|
-
invalid_locks.must_include(invalid_lock)
|
345
|
-
invalid_locks.wont_include(valid_lock)
|
343
|
+
_(current_locks).must_include(valid_lock)
|
344
|
+
_(current_locks).wont_include(invalid_lock)
|
345
|
+
_(invalid_locks).must_include(invalid_lock)
|
346
|
+
_(invalid_locks).wont_include(valid_lock)
|
346
347
|
end
|
347
348
|
end
|
348
349
|
|
@@ -372,9 +373,9 @@ module Dynflow
|
|
372
373
|
client_world.coordinator.acquire(invalid_lock2)
|
373
374
|
invalid_locks = client_world.coordinator.clean_orphaned_locks
|
374
375
|
# It must invalidate locks which are missing or in paused/stopped
|
375
|
-
invalid_locks.must_include(invalid_lock)
|
376
|
-
invalid_locks.must_include(invalid_lock2)
|
377
|
-
invalid_locks.wont_include(valid_lock)
|
376
|
+
_(invalid_locks).must_include(invalid_lock)
|
377
|
+
_(invalid_locks).must_include(invalid_lock2)
|
378
|
+
_(invalid_locks).wont_include(valid_lock)
|
378
379
|
end
|
379
380
|
end
|
380
381
|
end
|
data/test/action_test.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative 'test_helper'
|
2
|
-
require 'mocha/
|
3
|
+
require 'mocha/minitest'
|
3
4
|
|
4
5
|
module Dynflow
|
5
6
|
describe 'action' do
|
@@ -26,7 +27,7 @@ module Dynflow
|
|
26
27
|
Action.from_hash(action_data.merge(step: step), world)
|
27
28
|
end
|
28
29
|
|
29
|
-
specify { subject.class.name.must_equal 'RenamedAction' }
|
30
|
+
specify { _(subject.class.name).must_equal 'RenamedAction' }
|
30
31
|
specify { assert subject.is_a? Action }
|
31
32
|
end
|
32
33
|
|
@@ -35,16 +36,16 @@ module Dynflow
|
|
35
36
|
smart_action_class = Class.new(Dynflow::Action)
|
36
37
|
smarter_action_class = Class.new(smart_action_class)
|
37
38
|
|
38
|
-
specify { smart_action_class.all_children.must_include smarter_action_class }
|
39
|
-
specify { smart_action_class.all_children.size.must_equal 1 }
|
39
|
+
specify { _(smart_action_class.all_children).must_include smarter_action_class }
|
40
|
+
specify { _(smart_action_class.all_children.size).must_equal 1 }
|
40
41
|
|
41
42
|
describe 'World#subscribed_actions' do
|
42
43
|
event_action_class = Support::CodeWorkflowExample::Triage
|
43
44
|
subscribed_action_class = Support::CodeWorkflowExample::NotifyAssignee
|
44
45
|
|
45
|
-
specify { subscribed_action_class.subscribe.must_equal event_action_class }
|
46
|
-
specify { world.subscribed_actions(event_action_class).must_include subscribed_action_class }
|
47
|
-
specify { world.subscribed_actions(event_action_class).size.must_equal 1 }
|
46
|
+
specify { _(subscribed_action_class.subscribe).must_equal event_action_class }
|
47
|
+
specify { _(world.subscribed_actions(event_action_class)).must_include subscribed_action_class }
|
48
|
+
specify { _(world.subscribed_actions(event_action_class).size).must_equal 1 }
|
48
49
|
end
|
49
50
|
end
|
50
51
|
|
@@ -52,7 +53,7 @@ module Dynflow
|
|
52
53
|
|
53
54
|
let :execution_plan do
|
54
55
|
result = world.trigger(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
55
|
-
result.must_be :planned?
|
56
|
+
_(result).must_be :planned?
|
56
57
|
result.finished.value
|
57
58
|
end
|
58
59
|
|
@@ -65,10 +66,10 @@ module Dynflow
|
|
65
66
|
execution_plan.root_plan_step.action execution_plan
|
66
67
|
end
|
67
68
|
|
68
|
-
specify { presenter.class.must_equal Support::CodeWorkflowExample::IncomingIssues }
|
69
|
+
specify { _(presenter.class).must_equal Support::CodeWorkflowExample::IncomingIssues }
|
69
70
|
|
70
71
|
it 'allows aggregating data from other actions' do
|
71
|
-
presenter.summary.must_equal(assignees: ["John Doe"])
|
72
|
+
_(presenter.summary).must_equal(assignees: ["John Doe"])
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
@@ -78,7 +79,7 @@ module Dynflow
|
|
78
79
|
|
79
80
|
it 'fails when input is not serializable' do
|
80
81
|
klass = Class.new(Dynflow::Action)
|
81
|
-
-> { create_and_plan_action klass, key: Object.new }.must_raise NoMethodError
|
82
|
+
_(-> { create_and_plan_action klass, key: Object.new }).must_raise NoMethodError
|
82
83
|
end
|
83
84
|
|
84
85
|
it 'fails when output is not serializable' do
|
@@ -88,7 +89,7 @@ module Dynflow
|
|
88
89
|
end
|
89
90
|
end
|
90
91
|
action = create_and_plan_action klass, {}
|
91
|
-
-> { run_action action }.must_raise NoMethodError
|
92
|
+
_(-> { run_action action }).must_raise NoMethodError
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
@@ -113,7 +114,56 @@ module Dynflow
|
|
113
114
|
it 'is customizable from an action' do
|
114
115
|
plan = create_and_plan_action ActionWithHumanizedState, {}
|
115
116
|
action = run_action(plan)
|
116
|
-
action.humanized_state.must_equal "waiting"
|
117
|
+
_(action.humanized_state).must_equal "waiting"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'evented action' do
|
122
|
+
include Testing
|
123
|
+
|
124
|
+
class PlanEventedAction < Dynflow::Action
|
125
|
+
def run(event = nil)
|
126
|
+
case event
|
127
|
+
when "ping"
|
128
|
+
output[:status] = 'pinged'
|
129
|
+
when nil
|
130
|
+
plan_event('ping', input[:time])
|
131
|
+
suspend
|
132
|
+
else
|
133
|
+
self.output[:event] = event
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'send planned event' do
|
139
|
+
plan = create_and_plan_action(PlanEventedAction, { time: 0.5 })
|
140
|
+
action = run_action plan
|
141
|
+
|
142
|
+
_(action.output[:status]).must_equal nil
|
143
|
+
_(action.world.clock.pending_pings.first).wont_be_nil
|
144
|
+
_(action.state).must_equal :suspended
|
145
|
+
|
146
|
+
progress_action_time action
|
147
|
+
|
148
|
+
_(action.output[:status]).must_equal 'pinged'
|
149
|
+
_(action.world.clock.pending_pings.first).must_be_nil
|
150
|
+
_(action.state).must_equal :success
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'plans event immediately if no time is given' do
|
154
|
+
plan = create_and_plan_action(PlanEventedAction, { time: nil })
|
155
|
+
action = run_action plan
|
156
|
+
|
157
|
+
_(action.output[:status]).must_equal nil
|
158
|
+
_(action.world.clock.pending_pings.first).must_be_nil
|
159
|
+
_(action.world.executor.events_to_process.first).wont_be_nil
|
160
|
+
_(action.state).must_equal :suspended
|
161
|
+
|
162
|
+
action.world.executor.progress
|
163
|
+
|
164
|
+
_(action.output[:status]).must_equal 'pinged'
|
165
|
+
_(action.world.clock.pending_pings.first).must_be_nil
|
166
|
+
_(action.state).must_equal :success
|
117
167
|
end
|
118
168
|
end
|
119
169
|
|
@@ -222,7 +272,7 @@ module Dynflow
|
|
222
272
|
end
|
223
273
|
end
|
224
274
|
|
225
|
-
describe'without timeout' do
|
275
|
+
describe 'without timeout' do
|
226
276
|
let(:plan) do
|
227
277
|
create_and_plan_action TestPollingAction, { task_args: 'do something' }
|
228
278
|
end
|
@@ -238,37 +288,37 @@ module Dynflow
|
|
238
288
|
it 'initiates the external task' do
|
239
289
|
action = run_action plan
|
240
290
|
|
241
|
-
action.output[:task][:task_id].must_equal 123
|
291
|
+
_(action.output[:task][:task_id]).must_equal 123
|
242
292
|
end
|
243
293
|
|
244
294
|
it 'polls till the task is done' do
|
245
295
|
action = run_action plan
|
246
296
|
|
247
297
|
9.times { progress_action_time action }
|
248
|
-
action.done
|
249
|
-
next_ping(action).wont_be_nil
|
250
|
-
action.state.must_equal :suspended
|
298
|
+
_(action.done?).must_equal false
|
299
|
+
_(next_ping(action)).wont_be_nil
|
300
|
+
_(action.state).must_equal :suspended
|
251
301
|
|
252
302
|
progress_action_time action
|
253
|
-
action.done
|
254
|
-
next_ping(action).must_be_nil
|
255
|
-
action.state.must_equal :success
|
303
|
+
_(action.done?).must_equal true
|
304
|
+
_(next_ping(action)).must_be_nil
|
305
|
+
_(action.state).must_equal :success
|
256
306
|
end
|
257
307
|
|
258
308
|
it 'tries to poll for the old task when resuming' do
|
259
309
|
action = run_action plan
|
260
|
-
action.output[:task][:progress].must_equal 0
|
310
|
+
_(action.output[:task][:progress]).must_equal 0
|
261
311
|
run_action action
|
262
|
-
action.output[:task][:progress].must_equal 10
|
312
|
+
_(action.output[:task][:progress]).must_equal 10
|
263
313
|
end
|
264
314
|
|
265
315
|
it 'invokes the external task again when polling on the old one fails' do
|
266
316
|
action = run_action plan
|
267
317
|
action.world.silence_logger!
|
268
318
|
action.external_service.will_fail
|
269
|
-
action.output[:task][:progress].must_equal 0
|
319
|
+
_(action.output[:task][:progress]).must_equal 0
|
270
320
|
run_action action
|
271
|
-
action.output[:task][:progress].must_equal 0
|
321
|
+
_(action.output[:task][:progress]).must_equal 0
|
272
322
|
end
|
273
323
|
|
274
324
|
it 'tolerates some failure while polling' do
|
@@ -279,15 +329,15 @@ module Dynflow
|
|
279
329
|
TestPollingAction.config.poll_max_retries = 3
|
280
330
|
(1..2).each do |attempt|
|
281
331
|
progress_action_time action
|
282
|
-
action.poll_attempts[:failed].must_equal attempt
|
283
|
-
next_ping(action).wont_be_nil
|
284
|
-
action.state.must_equal :suspended
|
332
|
+
_(action.poll_attempts[:failed]).must_equal attempt
|
333
|
+
_(next_ping(action)).wont_be_nil
|
334
|
+
_(action.state).must_equal :suspended
|
285
335
|
end
|
286
336
|
|
287
337
|
progress_action_time action
|
288
|
-
action.poll_attempts[:failed].must_equal 3
|
289
|
-
next_ping(action).must_be_nil
|
290
|
-
action.state.must_equal :error
|
338
|
+
_(action.poll_attempts[:failed]).must_equal 3
|
339
|
+
_(next_ping(action)).must_be_nil
|
340
|
+
_(action.state).must_equal :error
|
291
341
|
end
|
292
342
|
|
293
343
|
it 'allows increasing poll interval in a time' do
|
@@ -302,8 +352,8 @@ module Dynflow
|
|
302
352
|
progress_action_time action
|
303
353
|
pings << next_ping(action)
|
304
354
|
progress_action_time action
|
305
|
-
(pings[1].when - pings[0].when).must_be_close_to 1
|
306
|
-
(pings[2].when - pings[1].when).must_be_close_to 2
|
355
|
+
_((pings[1].when - pings[0].when)).must_be_close_to 1
|
356
|
+
_((pings[2].when - pings[1].when)).must_be_close_to 2
|
307
357
|
end
|
308
358
|
end
|
309
359
|
|
@@ -324,10 +374,10 @@ module Dynflow
|
|
324
374
|
# we count the number of iterations till the timeout occurs
|
325
375
|
iterations += 1
|
326
376
|
end
|
327
|
-
action.state.must_equal :error
|
377
|
+
_(action.state).must_equal :error
|
328
378
|
# two polls in 2 seconds intervals untill the 5 seconds
|
329
379
|
# timeout appears
|
330
|
-
iterations.must_equal 3
|
380
|
+
_(iterations).must_equal 3
|
331
381
|
end
|
332
382
|
end
|
333
383
|
end
|
@@ -442,9 +492,9 @@ module Dynflow
|
|
442
492
|
|
443
493
|
specify "the sub-plan stores the information about its parent" do
|
444
494
|
sub_plans = execution_plan.sub_plans
|
445
|
-
sub_plans.size.must_equal 2
|
446
|
-
execution_plan.sub_plans_count.must_equal 2
|
447
|
-
sub_plans.each { |sub_plan| sub_plan.caller_execution_plan_id.must_equal execution_plan.id }
|
495
|
+
_(sub_plans.size).must_equal 2
|
496
|
+
_(execution_plan.sub_plans_count).must_equal 2
|
497
|
+
sub_plans.each { |sub_plan| _(sub_plan.caller_execution_plan_id).must_equal execution_plan.id }
|
448
498
|
end
|
449
499
|
|
450
500
|
specify "the parent and sub-plan actions return root_action? properly" do
|
@@ -456,7 +506,7 @@ module Dynflow
|
|
456
506
|
end
|
457
507
|
|
458
508
|
specify "it saves the information about number for sub plans in the output" do
|
459
|
-
execution_plan.entry_action.output.must_equal('total_count' => 2,
|
509
|
+
_(execution_plan.entry_action.output).must_equal('total_count' => 2,
|
460
510
|
'failed_count' => 0,
|
461
511
|
'success_count' => 2,
|
462
512
|
'pending_count' => 0)
|
@@ -464,49 +514,49 @@ module Dynflow
|
|
464
514
|
|
465
515
|
specify "when a sub plan fails, the caller action fails as well" do
|
466
516
|
FailureSimulator.fail_in_child_run = true
|
467
|
-
execution_plan.entry_action.output.must_equal('total_count' => 2,
|
517
|
+
_(execution_plan.entry_action.output).must_equal('total_count' => 2,
|
468
518
|
'failed_count' => 2,
|
469
519
|
'success_count' => 0,
|
470
520
|
'pending_count' => 0)
|
471
|
-
execution_plan.state.must_equal :paused
|
472
|
-
execution_plan.result.must_equal :error
|
521
|
+
_(execution_plan.state).must_equal :paused
|
522
|
+
_(execution_plan.result).must_equal :error
|
473
523
|
end
|
474
524
|
|
475
525
|
describe 'resuming' do
|
476
526
|
specify "resuming the action depends on the resume method definition" do
|
477
527
|
FailureSimulator.fail_in_child_plan = true
|
478
|
-
execution_plan.state.must_equal :paused
|
528
|
+
_(execution_plan.state).must_equal :paused
|
479
529
|
FailureSimulator.fail_in_child_plan = false
|
480
530
|
resumed_plan = world.execute(execution_plan.id).value
|
481
|
-
resumed_plan.entry_action.output[:custom_resume].must_equal true
|
531
|
+
_(resumed_plan.entry_action.output[:custom_resume]).must_equal true
|
482
532
|
end
|
483
533
|
|
484
534
|
specify "by default, when no sub plans were planned successfully, it call create_sub_plans again" do
|
485
535
|
FailureSimulator.fail_in_child_plan = true
|
486
|
-
execution_plan.state.must_equal :paused
|
536
|
+
_(execution_plan.state).must_equal :paused
|
487
537
|
FailureSimulator.fail_in_child_plan = false
|
488
538
|
resumed_plan = world.execute(execution_plan.id).value
|
489
|
-
resumed_plan.state.must_equal :stopped
|
490
|
-
resumed_plan.result.must_equal :success
|
539
|
+
_(resumed_plan.state).must_equal :stopped
|
540
|
+
_(resumed_plan.result).must_equal :success
|
491
541
|
end
|
492
542
|
|
493
543
|
specify "by default, when any sub-plan was planned, it succeeds only when the sub-plans were already finished" do
|
494
544
|
FailureSimulator.fail_in_child_run = true
|
495
|
-
execution_plan.state.must_equal :paused
|
545
|
+
_(execution_plan.state).must_equal :paused
|
496
546
|
sub_plans = execution_plan.sub_plans
|
497
547
|
|
498
548
|
FailureSimulator.fail_in_child_run = false
|
499
549
|
resumed_plan = world.execute(execution_plan.id).value
|
500
|
-
resumed_plan.state.must_equal :paused
|
550
|
+
_(resumed_plan.state).must_equal :paused
|
501
551
|
|
502
552
|
world.execute(sub_plans.first.id).wait
|
503
553
|
resumed_plan = world.execute(execution_plan.id).value
|
504
|
-
resumed_plan.state.must_equal :paused
|
554
|
+
_(resumed_plan.state).must_equal :paused
|
505
555
|
|
506
556
|
sub_plans.drop(1).each { |sub_plan| world.execute(sub_plan.id).wait }
|
507
557
|
resumed_plan = world.execute(execution_plan.id).value
|
508
|
-
resumed_plan.state.must_equal :stopped
|
509
|
-
resumed_plan.result.must_equal :success
|
558
|
+
_(resumed_plan.state).must_equal :stopped
|
559
|
+
_(resumed_plan.result).must_equal :success
|
510
560
|
end
|
511
561
|
|
512
562
|
describe ::Dynflow::Action::WithPollingSubPlans do
|
@@ -520,18 +570,18 @@ module Dynflow
|
|
520
570
|
total = 2
|
521
571
|
FailureSimulator.fail_in_child_plan = true
|
522
572
|
triggered_plan = world.trigger(PollingParentAction, count: total)
|
523
|
-
polling_plan = world.persistence.load_execution_plan(triggered_plan.id)
|
524
573
|
|
525
|
-
|
574
|
+
polling_plan = nil
|
575
|
+
wait_for('the subplans to be spawned') do
|
526
576
|
polling_plan = world.persistence.load_execution_plan(triggered_plan.id)
|
527
577
|
polling_plan.sub_plans_count == total
|
528
578
|
end
|
529
579
|
|
530
580
|
# Moving the clock to make the parent check on sub plans
|
531
|
-
clock.pending_pings.count.must_equal 1
|
581
|
+
_(clock.pending_pings.count).must_equal 1
|
532
582
|
clock.progress
|
533
583
|
|
534
|
-
wait_for
|
584
|
+
wait_for('the parent to realise the sub plans failed') do
|
535
585
|
polling_plan = world.persistence.load_execution_plan(triggered_plan.id)
|
536
586
|
polling_plan.state == :paused
|
537
587
|
end
|
@@ -540,15 +590,15 @@ module Dynflow
|
|
540
590
|
|
541
591
|
world.execute(polling_plan.id) # The actual resume
|
542
592
|
|
543
|
-
wait_for
|
593
|
+
wait_for('new generation of sub plans to be spawned') do
|
544
594
|
polling_plan.sub_plans_count == 2 * total
|
545
595
|
end
|
546
596
|
|
547
597
|
# Move the clock again
|
548
|
-
clock.pending_pings.count.must_equal 1
|
598
|
+
_(clock.pending_pings.count).must_equal 1
|
549
599
|
clock.progress
|
550
600
|
|
551
|
-
wait_for
|
601
|
+
wait_for('everything to finish successfully') do
|
552
602
|
polling_plan = world.persistence.load_execution_plan(triggered_plan.id)
|
553
603
|
polling_plan.state == :stopped && polling_plan.result == :success
|
554
604
|
end
|
@@ -569,9 +619,9 @@ module Dynflow
|
|
569
619
|
end
|
570
620
|
|
571
621
|
# Moving the clock to make the parent check on sub plans
|
572
|
-
clock.pending_pings.count.must_equal 1
|
622
|
+
_(clock.pending_pings.count).must_equal 1
|
573
623
|
clock.progress
|
574
|
-
clock.pending_pings.count.must_equal 0
|
624
|
+
_(clock.pending_pings.count).must_equal 0
|
575
625
|
|
576
626
|
wait_for do # Waiting for the parent to realise the sub plans failed
|
577
627
|
polling_plan = world.persistence.load_execution_plan(triggered_plan.id)
|
@@ -613,8 +663,8 @@ module Dynflow
|
|
613
663
|
end
|
614
664
|
plan.cancel
|
615
665
|
triggered_plan.finished.wait
|
616
|
-
triggered_plan.finished.value.state.must_equal :stopped
|
617
|
-
triggered_plan.finished.value.result.must_equal :success
|
666
|
+
_(triggered_plan.finished.value.state).must_equal :stopped
|
667
|
+
_(triggered_plan.finished.value.result).must_equal :success
|
618
668
|
end
|
619
669
|
|
620
670
|
it "sends the abort event to all actions that are running and support cancelling" do
|
@@ -627,10 +677,10 @@ module Dynflow
|
|
627
677
|
end
|
628
678
|
plan.cancel true
|
629
679
|
triggered_plan.finished.wait
|
630
|
-
triggered_plan.finished.value.state.must_equal :stopped
|
631
|
-
triggered_plan.finished.value.result.must_equal :success
|
680
|
+
_(triggered_plan.finished.value.state).must_equal :stopped
|
681
|
+
_(triggered_plan.finished.value.result).must_equal :success
|
632
682
|
plan.sub_plans.each do |sub_plan|
|
633
|
-
sub_plan.entry_action.output[:aborted].must_equal true
|
683
|
+
_(sub_plan.entry_action.output[:aborted]).must_equal true
|
634
684
|
end
|
635
685
|
end
|
636
686
|
end
|
@@ -645,20 +695,20 @@ module Dynflow
|
|
645
695
|
world.stub :clock, clock do
|
646
696
|
total = 2
|
647
697
|
plan = world.plan(PollingParentAction, count: total)
|
648
|
-
plan.state.must_equal :planned
|
649
|
-
clock.pending_pings.count.must_equal 0
|
698
|
+
_(plan.state).must_equal :planned
|
699
|
+
_(clock.pending_pings.count).must_equal 0
|
650
700
|
world.execute(plan.id)
|
651
701
|
wait_for do
|
652
702
|
plan.sub_plans_count == total &&
|
653
703
|
plan.sub_plans.all? { |sub| sub.result == :success }
|
654
704
|
end
|
655
|
-
clock.pending_pings.count.must_equal 1
|
705
|
+
_(clock.pending_pings.count).must_equal 1
|
656
706
|
clock.progress
|
657
707
|
wait_for do
|
658
708
|
plan = world.persistence.load_execution_plan(plan.id)
|
659
709
|
plan.state == :stopped
|
660
710
|
end
|
661
|
-
clock.pending_pings.count.must_equal 0
|
711
|
+
_(clock.pending_pings.count).must_equal 0
|
662
712
|
end
|
663
713
|
end
|
664
714
|
|
@@ -667,14 +717,14 @@ module Dynflow
|
|
667
717
|
total = 2
|
668
718
|
plan = world.plan(PollingBulkParentAction, count: total)
|
669
719
|
assert_nil plan.entry_action.output[:planning_finished]
|
670
|
-
clock.pending_pings.count.must_equal 0
|
720
|
+
_(clock.pending_pings.count).must_equal 0
|
671
721
|
world.execute(plan.id)
|
672
722
|
wait_for do
|
673
723
|
plan = world.persistence.load_execution_plan(plan.id)
|
674
724
|
plan.entry_action.output[:planning_finished] == 1
|
675
725
|
end
|
676
726
|
# Poll was set during #initiate
|
677
|
-
clock.pending_pings.count.must_equal 1
|
727
|
+
_(clock.pending_pings.count).must_equal 1
|
678
728
|
|
679
729
|
# Wait for the sub plans to finish
|
680
730
|
wait_for do
|
@@ -688,14 +738,14 @@ module Dynflow
|
|
688
738
|
plan = world.persistence.load_execution_plan(plan.id)
|
689
739
|
plan.state == :stopped
|
690
740
|
end
|
691
|
-
plan.entry_action.output[:poll].must_equal 1
|
692
|
-
clock.pending_pings.count.must_equal 0
|
741
|
+
_(plan.entry_action.output[:poll]).must_equal 1
|
742
|
+
_(clock.pending_pings.count).must_equal 0
|
693
743
|
end
|
694
744
|
end
|
695
745
|
|
696
746
|
it 'handles empty sub plans when calculating progress' do
|
697
747
|
action = create_and_plan_action(PollingBulkParentAction, :count => 0)
|
698
|
-
action.run_progress.must_equal 0.1
|
748
|
+
_(action.run_progress).must_equal 0.1
|
699
749
|
end
|
700
750
|
|
701
751
|
describe ::Dynflow::Action::Singleton do
|
@@ -737,58 +787,58 @@ module Dynflow
|
|
737
787
|
|
738
788
|
it 'unlocks the locks after #plan if no #run or #finalize' do
|
739
789
|
plan = world.plan(SingletonAction)
|
740
|
-
plan.state.must_equal :planned
|
790
|
+
_(plan.state).must_equal :planned
|
741
791
|
lock_filter = ::Dynflow::Coordinator::SingletonActionLock
|
742
792
|
.unique_filter plan.entry_action.class.name
|
743
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
793
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
744
794
|
plan = world.execute(plan.id).wait!.value
|
745
|
-
plan.state.must_equal :stopped
|
746
|
-
plan.result.must_equal :success
|
747
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 0
|
795
|
+
_(plan.state).must_equal :stopped
|
796
|
+
_(plan.result).must_equal :success
|
797
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 0
|
748
798
|
end
|
749
799
|
|
750
800
|
it 'unlocks the locks after #finalize' do
|
751
801
|
plan = world.plan(SingletonActionWithFinalize)
|
752
|
-
plan.state.must_equal :planned
|
802
|
+
_(plan.state).must_equal :planned
|
753
803
|
lock_filter = ::Dynflow::Coordinator::SingletonActionLock
|
754
804
|
.unique_filter plan.entry_action.class.name
|
755
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
805
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
756
806
|
plan = world.execute(plan.id).wait!.value
|
757
|
-
plan.state.must_equal :stopped
|
758
|
-
plan.result.must_equal :success
|
759
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 0
|
807
|
+
_(plan.state).must_equal :stopped
|
808
|
+
_(plan.result).must_equal :success
|
809
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 0
|
760
810
|
end
|
761
811
|
|
762
812
|
it 'does not unlock when getting suspended' do
|
763
813
|
plan = world.plan(SuspendedSingletonAction)
|
764
|
-
plan.state.must_equal :planned
|
814
|
+
_(plan.state).must_equal :planned
|
765
815
|
lock_filter = ::Dynflow::Coordinator::SingletonActionLock
|
766
816
|
.unique_filter plan.entry_action.class.name
|
767
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
817
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
768
818
|
future = world.execute(plan.id)
|
769
819
|
wait_for do
|
770
820
|
plan = world.persistence.load_execution_plan(plan.id)
|
771
821
|
plan.state == :running && plan.result == :pending
|
772
822
|
end
|
773
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
823
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
774
824
|
world.event(plan.id, 2, nil)
|
775
825
|
plan = future.wait!.value
|
776
|
-
plan.state.must_equal :stopped
|
777
|
-
plan.result.must_equal :success
|
778
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 0
|
826
|
+
_(plan.state).must_equal :stopped
|
827
|
+
_(plan.result).must_equal :success
|
828
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 0
|
779
829
|
end
|
780
830
|
|
781
831
|
it 'can be triggered only once' do
|
782
832
|
# plan1 acquires the lock in plan phase
|
783
833
|
plan1 = world.plan(SingletonActionWithRun)
|
784
|
-
plan1.state.must_equal :planned
|
785
|
-
plan1.result.must_equal :pending
|
834
|
+
_(plan1.state).must_equal :planned
|
835
|
+
_(plan1.result).must_equal :pending
|
786
836
|
|
787
837
|
# plan2 tries to acquire the lock in plan phase and fails
|
788
838
|
plan2 = world.plan(SingletonActionWithRun)
|
789
|
-
plan2.state.must_equal :stopped
|
790
|
-
plan2.result.must_equal :error
|
791
|
-
plan2.errors.first.message.must_equal 'Action Dynflow::SingletonActionWithRun is already active'
|
839
|
+
_(plan2.state).must_equal :stopped
|
840
|
+
_(plan2.result).must_equal :error
|
841
|
+
_(plan2.errors.first.message).must_equal 'Action Dynflow::SingletonActionWithRun is already active'
|
792
842
|
|
793
843
|
# Simulate some bad things happening
|
794
844
|
plan1.entry_action.send(:singleton_unlock!)
|
@@ -799,42 +849,42 @@ module Dynflow
|
|
799
849
|
# plan1 tries to relock on run
|
800
850
|
# This should fail because the lock was taken by plan3
|
801
851
|
plan1 = world.execute(plan1.id).wait!.value
|
802
|
-
plan1.state.must_equal :paused
|
803
|
-
plan1.result.must_equal :error
|
852
|
+
_(plan1.state).must_equal :paused
|
853
|
+
_(plan1.result).must_equal :error
|
804
854
|
|
805
855
|
# plan3 can finish successfully because it holds the lock
|
806
856
|
plan3 = world.execute(plan3.id).wait!.value
|
807
|
-
plan3.state.must_equal :stopped
|
808
|
-
plan3.result.must_equal :success
|
857
|
+
_(plan3.state).must_equal :stopped
|
858
|
+
_(plan3.result).must_equal :success
|
809
859
|
|
810
860
|
# The lock was released when plan3 stopped
|
811
861
|
lock_filter = ::Dynflow::Coordinator::SingletonActionLock
|
812
862
|
.unique_filter plan3.entry_action.class.name
|
813
|
-
world.coordinator.find_locks(lock_filter).must_be :empty?
|
863
|
+
_(world.coordinator.find_locks(lock_filter)).must_be :empty?
|
814
864
|
end
|
815
865
|
|
816
866
|
it 'cannot be unlocked by another action' do
|
817
867
|
# plan1 doesn't keep its locks
|
818
868
|
plan1 = world.plan(BadAction, true)
|
819
|
-
plan1.state.must_equal :planned
|
869
|
+
_(plan1.state).must_equal :planned
|
820
870
|
lock_filter = ::Dynflow::Coordinator::SingletonActionLock
|
821
871
|
.unique_filter plan1.entry_action.class.name
|
822
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 0
|
872
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 0
|
823
873
|
plan2 = world.plan(BadAction, false)
|
824
|
-
plan2.state.must_equal :planned
|
825
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
874
|
+
_(plan2.state).must_equal :planned
|
875
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
826
876
|
|
827
877
|
# The locks held by plan2 can't be unlocked by plan1
|
828
878
|
plan1.entry_action.singleton_unlock!
|
829
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
879
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
830
880
|
|
831
881
|
plan1 = world.execute(plan1.id).wait!.value
|
832
|
-
plan1.state.must_equal :paused
|
833
|
-
plan1.result.must_equal :error
|
882
|
+
_(plan1.state).must_equal :paused
|
883
|
+
_(plan1.result).must_equal :error
|
834
884
|
|
835
885
|
plan2 = world.execute(plan2.id).wait!.value
|
836
|
-
plan2.state.must_equal :stopped
|
837
|
-
plan2.result.must_equal :success
|
886
|
+
_(plan2.state).must_equal :stopped
|
887
|
+
_(plan2.result).must_equal :success
|
838
888
|
end
|
839
889
|
end
|
840
890
|
end
|