dynflow 1.3.0 → 1.4.0
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 +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
|