dynflow 1.9.3 → 2.0.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/.github/workflows/bats.yml +50 -0
- data/.github/workflows/release.yml +1 -1
- data/.github/workflows/ruby.yml +27 -46
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -0
- data/Dockerfile +1 -1
- data/Gemfile +7 -8
- data/README.md +4 -4
- data/doc/pages/source/documentation/index.md +4 -4
- data/dynflow.gemspec +2 -3
- data/examples/example_helper.rb +1 -1
- data/examples/execution_plan_chaining.rb +56 -0
- data/examples/remote_executor.rb +5 -8
- data/lib/dynflow/action/format.rb +4 -33
- data/lib/dynflow/debug/telemetry/persistence.rb +1 -1
- data/lib/dynflow/delayed_executors/abstract_core.rb +1 -1
- data/lib/dynflow/delayed_plan.rb +6 -0
- data/lib/dynflow/director.rb +9 -1
- data/lib/dynflow/executors/sidekiq/core.rb +1 -1
- data/lib/dynflow/executors/sidekiq/redis_locking.rb +10 -3
- data/lib/dynflow/extensions/msgpack.rb +4 -0
- data/lib/dynflow/persistence.rb +14 -2
- data/lib/dynflow/persistence_adapters/abstract.rb +9 -1
- data/lib/dynflow/persistence_adapters/sequel.rb +91 -48
- data/lib/dynflow/persistence_adapters/sequel_migrations/025_create_execution_plan_dependencies.rb +22 -0
- data/lib/dynflow/rails/daemon.rb +16 -7
- data/lib/dynflow/testing.rb +1 -1
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world.rb +34 -13
- data/lib/dynflow.rb +0 -1
- data/test/action_test.rb +3 -3
- data/test/bats/helpers/common.bash +67 -0
- data/test/bats/helpers/containers.bash +146 -0
- data/test/bats/setup_suite.bash +46 -0
- data/test/bats/sidekiq-orchestrator.bats +178 -0
- data/test/bats/teardown_suite.bash +16 -0
- data/test/concurrency_control_test.rb +0 -1
- data/test/daemon_test.rb +21 -2
- data/test/extensions_test.rb +3 -3
- data/test/future_execution_test.rb +150 -3
- data/test/persistence_test.rb +70 -3
- data/test/support/dummy_example.rb +4 -0
- data/test/test_helper.rb +19 -4
- data/web/views/show.erb +24 -0
- metadata +15 -17
- data/.github/install_dependencies.sh +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 398d1dc51c8a63beda52acc918da0c570ea91ec8c19090fa2cb7ae704cc4b1a2
|
|
4
|
+
data.tar.gz: 5f1527042cdb29afb348031ced662fb5ac157c4c00ff14fcac73293cba31026c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec917c5c31642bf23ebaf335804e0a151b7ab0a56f8b5d3a5cac23ab63416149844562248bddb93cff293b9d565e02998a958491cdc4f05c94d65859a4d473ef
|
|
7
|
+
data.tar.gz: 80202d14de3d386a5b8d1c54a48542135ab511421f71e08ef533c50c43fb28900d3318798668fa34fabdf80feec6cba4b3c11635d6ea2b766308d8bc2fe8a2f4
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Bats Integration Tests
|
|
2
|
+
|
|
3
|
+
on: [pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
bats:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
DB: postgresql
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v5
|
|
14
|
+
|
|
15
|
+
- name: Install bats
|
|
16
|
+
run: |
|
|
17
|
+
git clone --depth 1 --branch v1.11.0 https://github.com/bats-core/bats-core.git /tmp/bats-core
|
|
18
|
+
sudo /tmp/bats-core/install.sh /usr/local
|
|
19
|
+
bats --version
|
|
20
|
+
|
|
21
|
+
- name: Install podman
|
|
22
|
+
run: |
|
|
23
|
+
sudo apt-get update
|
|
24
|
+
sudo apt-get -y install podman
|
|
25
|
+
podman --version
|
|
26
|
+
|
|
27
|
+
- id: ruby_version
|
|
28
|
+
uses: voxpupuli/ruby-version@v1
|
|
29
|
+
- id: min_ruby
|
|
30
|
+
run: echo "version=$(echo '${{ steps.ruby_version.outputs.versions }}' | jq -r '.[-1]')" >> $GITHUB_OUTPUT
|
|
31
|
+
- name: Setup Ruby
|
|
32
|
+
uses: ruby/setup-ruby@v1
|
|
33
|
+
with:
|
|
34
|
+
# Use the minimum supported Ruby version for rubocop (last in descending list)
|
|
35
|
+
ruby-version: ${{ steps.min_ruby.outputs.version }}
|
|
36
|
+
bundler-cache: true
|
|
37
|
+
|
|
38
|
+
- name: Pull container images
|
|
39
|
+
run: |
|
|
40
|
+
podman pull docker.io/library/postgres:15
|
|
41
|
+
podman pull docker.io/library/redis:7-alpine
|
|
42
|
+
|
|
43
|
+
- name: Run bats tests
|
|
44
|
+
run: bats -x --verbose-run --print-output-on-failure test/bats/
|
|
45
|
+
|
|
46
|
+
- name: Cleanup containers (if tests fail)
|
|
47
|
+
if: always()
|
|
48
|
+
run: |
|
|
49
|
+
podman stop dynflow-test-postgres dynflow-test-redis 2>/dev/null || true
|
|
50
|
+
podman rm -f dynflow-test-postgres dynflow-test-redis 2>/dev/null || true
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
tagRegex: "v(.*)" # Optional. Returns specified group text as tag name. Full tag string is returned if regex is not defined.
|
|
20
20
|
tagRegexGroup: 1 # Optional. Default is 1.
|
|
21
21
|
- name: Checkout the repository
|
|
22
|
-
uses: actions/checkout@
|
|
22
|
+
uses: actions/checkout@v5
|
|
23
23
|
- name: Generate build files
|
|
24
24
|
run: |
|
|
25
25
|
mkdir -p dist
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -15,16 +15,20 @@ env:
|
|
|
15
15
|
jobs:
|
|
16
16
|
rubocop:
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
|
+
outputs:
|
|
19
|
+
ruby_version: ${{ steps.ruby_version.outputs.versions }}
|
|
18
20
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@v5
|
|
22
|
+
- id: ruby_version
|
|
23
|
+
uses: voxpupuli/ruby-version@v1
|
|
24
|
+
- id: min_ruby
|
|
25
|
+
run: echo "version=$(echo '${{ steps.ruby_version.outputs.versions }}' | jq -r '.[-1]')" >> $GITHUB_OUTPUT
|
|
20
26
|
- name: Setup Ruby
|
|
21
27
|
uses: ruby/setup-ruby@v1
|
|
22
28
|
with:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
gem install bundler --version=2.4.22
|
|
27
|
-
bundle install --jobs=3 --retry=3
|
|
29
|
+
# Use the minimum supported Ruby version for rubocop (last in descending list)
|
|
30
|
+
ruby-version: ${{ steps.min_ruby.outputs.version }}
|
|
31
|
+
bundler-cache: true
|
|
28
32
|
- name: Run rubocop
|
|
29
33
|
run: bundle exec rubocop
|
|
30
34
|
|
|
@@ -34,50 +38,30 @@ jobs:
|
|
|
34
38
|
strategy:
|
|
35
39
|
fail-fast: false
|
|
36
40
|
matrix:
|
|
37
|
-
ruby_version:
|
|
38
|
-
- 2.7.0
|
|
39
|
-
- 3.0.0
|
|
40
|
-
- 3.2.0
|
|
41
|
-
- 3.4
|
|
41
|
+
ruby_version: ${{ fromJSON(needs.rubocop.outputs.ruby_version) }}
|
|
42
42
|
concurrent_ruby_ext:
|
|
43
43
|
- 'true'
|
|
44
44
|
- 'false'
|
|
45
45
|
db:
|
|
46
46
|
- postgresql
|
|
47
|
-
- mysql
|
|
48
47
|
- sqlite3
|
|
49
|
-
include:
|
|
50
|
-
- db: postgresql
|
|
51
|
-
conn_string: postgres://postgres@localhost/travis_ci_test
|
|
52
|
-
- db: mysql
|
|
53
|
-
conn_string: mysql2://root@127.0.0.1/travis_ci_test
|
|
54
|
-
- db: sqlite3
|
|
55
|
-
conn_string: sqlite:/
|
|
56
48
|
exclude:
|
|
57
|
-
- db: mysql
|
|
58
|
-
ruby_version: 3.0.0
|
|
59
|
-
- db: mysql
|
|
60
|
-
ruby_version: 3.2.0
|
|
61
|
-
- db: mysql
|
|
62
|
-
ruby_version: 3.4
|
|
63
|
-
- db: mysql
|
|
64
|
-
concurrent_ruby_ext: 'true'
|
|
65
49
|
- db: sqlite3
|
|
66
|
-
ruby_version: 3.0
|
|
50
|
+
ruby_version: '3.0'
|
|
67
51
|
- db: sqlite3
|
|
68
|
-
ruby_version: 3.2
|
|
52
|
+
ruby_version: '3.2'
|
|
69
53
|
- db: sqlite3
|
|
70
|
-
ruby_version: 3.4
|
|
54
|
+
ruby_version: '3.4'
|
|
71
55
|
- db: sqlite3
|
|
72
56
|
concurrent_ruby_ext: 'true'
|
|
73
57
|
- db: postgresql
|
|
74
|
-
ruby_version: 3.0
|
|
58
|
+
ruby_version: '3.0'
|
|
75
59
|
concurrent_ruby_ext: 'true'
|
|
76
60
|
- db: postgresql
|
|
77
|
-
ruby_version: 3.2
|
|
61
|
+
ruby_version: '3.2'
|
|
78
62
|
concurrent_ruby_ext: 'true'
|
|
79
63
|
- db: postgresql
|
|
80
|
-
ruby_version: 3.4
|
|
64
|
+
ruby_version: '3.4'
|
|
81
65
|
concurrent_ruby_ext: 'true'
|
|
82
66
|
|
|
83
67
|
services:
|
|
@@ -86,31 +70,28 @@ jobs:
|
|
|
86
70
|
ports: ['5432:5432']
|
|
87
71
|
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
|
88
72
|
env:
|
|
89
|
-
POSTGRES_DB:
|
|
90
|
-
mariadb:
|
|
91
|
-
image: mariadb:10
|
|
92
|
-
ports: ['3306:3306']
|
|
93
|
-
env:
|
|
94
|
-
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
|
|
95
|
-
MYSQL_DATABASE: travis_ci_test
|
|
73
|
+
POSTGRES_DB: ci_test
|
|
96
74
|
redis:
|
|
97
75
|
image: redis:latest
|
|
98
76
|
ports: ['6379:6379']
|
|
99
77
|
|
|
100
78
|
env:
|
|
101
79
|
DB: ${{ matrix.db }}
|
|
102
|
-
DB_CONN_STRING: ${{ matrix.conn_string }}
|
|
103
80
|
CONCURRENT_RUBY_EXT: "${{ matrix.concurrent_ruby_ext }}"
|
|
104
81
|
|
|
105
82
|
steps:
|
|
106
|
-
- uses: actions/checkout@
|
|
83
|
+
- uses: actions/checkout@v5
|
|
107
84
|
- name: Set up Ruby
|
|
108
|
-
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
109
|
-
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
110
85
|
uses: ruby/setup-ruby@v1
|
|
111
86
|
with:
|
|
112
87
|
ruby-version: ${{ matrix.ruby_version }}
|
|
113
|
-
|
|
114
|
-
run: .github/install_dependencies.sh
|
|
88
|
+
bundler-cache: true
|
|
115
89
|
- name: Run tests
|
|
116
90
|
run: bundle exec rake test
|
|
91
|
+
- name: Upload logs
|
|
92
|
+
uses: actions/upload-artifact@v4
|
|
93
|
+
if: ${{ failure() && contains(matrix.task, 'test') }}
|
|
94
|
+
with:
|
|
95
|
+
name: logs-${{ env.ARTIFACT_SUFFIX }}
|
|
96
|
+
path: test.log
|
|
97
|
+
retention-days: 5
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/Dockerfile
CHANGED
data/Gemfile
CHANGED
|
@@ -4,11 +4,11 @@ source 'https://rubygems.org'
|
|
|
4
4
|
|
|
5
5
|
gemspec
|
|
6
6
|
|
|
7
|
-
group :concurrent_ruby_ext do
|
|
7
|
+
group :concurrent_ruby_ext, optional: ENV.key?('CI') && ENV['CONCURRENT_RUBY_EXT'] != 'true' do
|
|
8
8
|
gem 'concurrent-ruby-ext', '~> 1.1.3'
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
group :pry do
|
|
11
|
+
group :pry, optional: ENV.key?('CI') do
|
|
12
12
|
gem 'pry'
|
|
13
13
|
gem 'pry-byebug'
|
|
14
14
|
end
|
|
@@ -18,14 +18,10 @@ group :sidekiq do
|
|
|
18
18
|
gem 'sidekiq'
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
group :postgresql do
|
|
21
|
+
group :postgresql, optional: ENV.key?('CI') && ENV['DB'] != 'postgresql' do
|
|
22
22
|
gem "pg"
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
group :mysql do
|
|
26
|
-
gem "mysql2"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
25
|
group :lint do
|
|
30
26
|
gem 'theforeman-rubocop', '~> 0.0.4'
|
|
31
27
|
end
|
|
@@ -37,9 +33,12 @@ end
|
|
|
37
33
|
group :rails do
|
|
38
34
|
gem 'daemons'
|
|
39
35
|
gem 'logging'
|
|
40
|
-
gem 'rails', '>=
|
|
36
|
+
gem 'rails', '>= 7', '< 8'
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
group :telemetry do
|
|
44
40
|
gem 'statsd-instrument'
|
|
45
41
|
end
|
|
42
|
+
|
|
43
|
+
local_gemfile = File.join(File.dirname(__FILE__), 'Gemfile.local.rb')
|
|
44
|
+
self.instance_eval(Bundler.read_file(local_gemfile)) if File.exist?(local_gemfile)
|
data/README.md
CHANGED
|
@@ -126,10 +126,10 @@ The Anatomy of Action Class
|
|
|
126
126
|
# every action needs to inherit from Dynflow::Action
|
|
127
127
|
class Action < Dynflow::Action
|
|
128
128
|
|
|
129
|
-
# OPTIONAL: the input format for the execution phase of this action
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
129
|
+
# OPTIONAL: the input format for the execution phase of this action.
|
|
130
|
+
# This is purely documentation - the block is never evaluated and
|
|
131
|
+
# serves only as a reference for developers implementing actions.
|
|
132
|
+
# Input validation is not performed.
|
|
133
133
|
input_format do
|
|
134
134
|
param :id, Integer
|
|
135
135
|
param :name, String
|
|
@@ -203,12 +203,12 @@ class AnAction < Dynflow::Action
|
|
|
203
203
|
end
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
-
This might
|
|
206
|
+
This might be quite handy especially in combination with
|
|
207
207
|
[subscriptions](#subscriptions) functionality.
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
These format definitions are purely for documentation purposes - the blocks are never
|
|
210
|
+
evaluated and serve only as a reference for developers implementing actions.
|
|
211
|
+
Input/output validation is not performed.
|
|
212
212
|
|
|
213
213
|
{% endinfo_block %}
|
|
214
214
|
|
data/dynflow.gemspec
CHANGED
|
@@ -14,14 +14,13 @@ Gem::Specification.new do |s|
|
|
|
14
14
|
s.description = "Ruby workflow/orchestration engine"
|
|
15
15
|
s.license = "MIT"
|
|
16
16
|
|
|
17
|
-
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.files = `git ls-files`.split("\n").reject { |file| file == '.packit.yaml' }
|
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
19
19
|
s.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
s.required_ruby_version = '>=
|
|
21
|
+
s.required_ruby_version = '>= 3.0.0'
|
|
22
22
|
|
|
23
23
|
s.add_dependency "algebrick", '~> 0.7.0'
|
|
24
|
-
s.add_dependency "apipie-params"
|
|
25
24
|
s.add_dependency "concurrent-ruby", '~> 1.1.3'
|
|
26
25
|
s.add_dependency "concurrent-ruby-edge", '~> 0.6.0'
|
|
27
26
|
s.add_dependency "csv", "~> 3.1"
|
data/examples/example_helper.rb
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'example_helper'
|
|
5
|
+
|
|
6
|
+
class DelayedAction < Dynflow::Action
|
|
7
|
+
def plan(should_fail = false)
|
|
8
|
+
plan_self :should_fail => should_fail
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
sleep 5
|
|
13
|
+
raise "Controlled failure" if input[:should_fail]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def rescue_strategy
|
|
17
|
+
Dynflow::Action::Rescue::Fail
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if $PROGRAM_NAME == __FILE__
|
|
22
|
+
world = ExampleHelper.create_world do |config|
|
|
23
|
+
config.auto_rescue = true
|
|
24
|
+
end
|
|
25
|
+
world.action_logger.level = 1
|
|
26
|
+
world.logger.level = 0
|
|
27
|
+
|
|
28
|
+
plan1 = world.trigger(DelayedAction)
|
|
29
|
+
plan2 = world.chain(plan1.execution_plan_id, DelayedAction)
|
|
30
|
+
plan3 = world.chain(plan2.execution_plan_id, DelayedAction)
|
|
31
|
+
plan4 = world.chain(plan2.execution_plan_id, DelayedAction)
|
|
32
|
+
|
|
33
|
+
plan5 = world.trigger(DelayedAction, true)
|
|
34
|
+
plan6 = world.chain(plan5.execution_plan_id, DelayedAction)
|
|
35
|
+
|
|
36
|
+
puts <<-MSG.gsub(/^.*\|/, '')
|
|
37
|
+
|
|
|
38
|
+
| Execution Plan Chaining example
|
|
39
|
+
| ========================
|
|
40
|
+
|
|
|
41
|
+
| This example shows the execution plan chaining functionality of Dynflow, which allows execution plans to wait until another execution plan finishes.
|
|
42
|
+
|
|
|
43
|
+
| Execution plans:
|
|
44
|
+
| #{plan1.id} runs immediately and should run successfully.
|
|
45
|
+
| #{plan2.id} is delayed and should run once #{plan1.id} finishes.
|
|
46
|
+
| #{plan3.id} and #{plan4.id} are delayed and should run once #{plan2.id} finishes.
|
|
47
|
+
|
|
|
48
|
+
| #{plan5.id} runs immediately and is expected to fail.
|
|
49
|
+
| #{plan6.id} should not run at all as its prerequisite failed.
|
|
50
|
+
|
|
|
51
|
+
| Visit #{ExampleHelper::DYNFLOW_URL} to see their status.
|
|
52
|
+
|
|
|
53
|
+
MSG
|
|
54
|
+
|
|
55
|
+
ExampleHelper.run_web_console(world)
|
|
56
|
+
end
|
data/examples/remote_executor.rb
CHANGED
|
@@ -117,17 +117,14 @@ class RemoteExecutorExample
|
|
|
117
117
|
Proc.new { |world| Dynflow::Connectors::Database.new(world) }
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
def run_client
|
|
120
|
+
def run_client(count)
|
|
121
121
|
world = ExampleHelper.create_world do |config|
|
|
122
122
|
config.persistence_adapter = persistence_adapter
|
|
123
123
|
config.executor = false
|
|
124
124
|
config.connector = connector
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
world.trigger(OrchestrateEvented::CreateInfrastructure, true)
|
|
129
|
-
|
|
130
|
-
loop do
|
|
127
|
+
(count || 1000).times do
|
|
131
128
|
start_time = Time.now
|
|
132
129
|
world.trigger(SampleAction).finished.wait
|
|
133
130
|
finished_in = Time.now - start_time
|
|
@@ -150,13 +147,13 @@ if $0 == __FILE__
|
|
|
150
147
|
when 'server'
|
|
151
148
|
puts <<~MSG
|
|
152
149
|
The server is starting…. You can send the work to it by running:
|
|
153
|
-
|
|
150
|
+
|
|
154
151
|
#{$0} client
|
|
155
|
-
|
|
152
|
+
|
|
156
153
|
MSG
|
|
157
154
|
RemoteExecutorExample.run_server
|
|
158
155
|
when 'client'
|
|
159
|
-
RemoteExecutorExample.run_client
|
|
156
|
+
RemoteExecutorExample.run_client(ARGV[1]&.to_i)
|
|
160
157
|
else
|
|
161
158
|
puts "Unknown command #{comment}"
|
|
162
159
|
exit 1
|
|
@@ -1,44 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Dynflow
|
|
4
|
-
# Input/output format validation logic calling
|
|
5
|
-
# input_format/output_format with block acts as a setter for
|
|
6
|
-
# specifying the format. Without a block it acts as a getter
|
|
7
4
|
module Action::Format
|
|
8
|
-
# we don't evaluate tbe block immediatelly, but postpone it till all the
|
|
9
|
-
# action classes are loaded, because we can use them to reference output format
|
|
10
5
|
def input_format(&block)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@input_format_block = block
|
|
14
|
-
when !block && @input_format_block
|
|
15
|
-
return @input_format ||= Apipie::Params::Description.define(&@input_format_block)
|
|
16
|
-
when block && @input_format_block
|
|
17
|
-
raise "The input_format has already been defined in #{self.class}"
|
|
18
|
-
when !block && !@input_format_block
|
|
19
|
-
if superclass.respond_to? :input_format
|
|
20
|
-
superclass.input_format
|
|
21
|
-
else
|
|
22
|
-
raise "The input_format has not been defined yet in #{self.class}"
|
|
23
|
-
end
|
|
24
|
-
end
|
|
6
|
+
# Format definitions are not validated
|
|
7
|
+
# This method is kept for backward compatibility but does nothing
|
|
25
8
|
end
|
|
26
9
|
|
|
27
10
|
def output_format(&block)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@output_format_block = block
|
|
31
|
-
when !block && @output_format_block
|
|
32
|
-
return @output_format ||= Apipie::Params::Description.define(&@output_format_block)
|
|
33
|
-
when block && @output_format_block
|
|
34
|
-
raise "The output_format has already been defined in #{self.class}"
|
|
35
|
-
when !block && !@output_format_block
|
|
36
|
-
if superclass.respond_to? :output_format
|
|
37
|
-
superclass.output_format
|
|
38
|
-
else
|
|
39
|
-
raise "The output_format has not been defined yet in #{self.class}"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
11
|
+
# Format definitions are not validated
|
|
12
|
+
# This method is kept for backward compatibility but does nothing
|
|
42
13
|
end
|
|
43
14
|
end
|
|
44
15
|
end
|
data/lib/dynflow/delayed_plan.rb
CHANGED
|
@@ -31,6 +31,12 @@ module Dynflow
|
|
|
31
31
|
error("Execution plan could not be started before set time (#{@start_before})", 'timeout')
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def failed_dependencies(uuids)
|
|
35
|
+
bullets = uuids.map { |u| "- #{u}" }.join("\n")
|
|
36
|
+
msg = "Execution plan could not be started because some of its prerequisite execution plans failed:\n#{bullets}"
|
|
37
|
+
error(msg, 'failed-dependency')
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
def error(message, history_entry = nil)
|
|
35
41
|
execution_plan.root_plan_step.state = :error
|
|
36
42
|
execution_plan.root_plan_step.error = ::Dynflow::ExecutionPlan::Steps::Error.new(message)
|
data/lib/dynflow/director.rb
CHANGED
|
@@ -114,7 +114,15 @@ module Dynflow
|
|
|
114
114
|
plan = world.persistence.load_delayed_plan(execution_plan_id)
|
|
115
115
|
return if plan.nil? || plan.execution_plan.state != :scheduled
|
|
116
116
|
|
|
117
|
-
if
|
|
117
|
+
if plan.start_before.nil?
|
|
118
|
+
blocker_ids = world.persistence.find_execution_plan_dependencies(execution_plan_id)
|
|
119
|
+
statuses = world.persistence.find_execution_plan_statuses({ filters: { uuid: blocker_ids } })
|
|
120
|
+
failed = statuses.select { |_uuid, status| status[:state] == 'stopped' && status[:result] == 'error' }
|
|
121
|
+
if failed.any?
|
|
122
|
+
plan.failed_dependencies(failed.keys)
|
|
123
|
+
return
|
|
124
|
+
end
|
|
125
|
+
elsif plan.start_before < Time.now.utc()
|
|
118
126
|
plan.timeout
|
|
119
127
|
return
|
|
120
128
|
end
|
|
@@ -13,7 +13,7 @@ Sidekiq.configure_server do |config|
|
|
|
13
13
|
config[:semi_reliable_fetch] = true
|
|
14
14
|
Sidekiq::ReliableFetch.setup_reliable_fetch!(config)
|
|
15
15
|
end
|
|
16
|
-
::Sidekiq.strict_args!(false)
|
|
16
|
+
::Sidekiq.strict_args!(false) if ::Sidekiq.respond_to?(:strict_args!)
|
|
17
17
|
|
|
18
18
|
module Dynflow
|
|
19
19
|
module Executors
|
|
@@ -41,9 +41,7 @@ module Dynflow
|
|
|
41
41
|
def wait_for_orchestrator_lock
|
|
42
42
|
mode = nil
|
|
43
43
|
loop do
|
|
44
|
-
active =
|
|
45
|
-
conn.set(REDIS_LOCK_KEY, @world.id, :ex => REDIS_LOCK_TTL, :nx => true)
|
|
46
|
-
end
|
|
44
|
+
active = try_acquire_orchestrator_lock
|
|
47
45
|
break if active
|
|
48
46
|
if mode.nil?
|
|
49
47
|
mode = :passive
|
|
@@ -54,6 +52,15 @@ module Dynflow
|
|
|
54
52
|
@logger.info('Acquired orchestrator lock, entering active mode.')
|
|
55
53
|
end
|
|
56
54
|
|
|
55
|
+
def try_acquire_orchestrator_lock
|
|
56
|
+
::Sidekiq.redis do |conn|
|
|
57
|
+
conn.set(REDIS_LOCK_KEY, @world.id, :ex => REDIS_LOCK_TTL, :nx => true)
|
|
58
|
+
end
|
|
59
|
+
rescue ::Redis::BaseError => e
|
|
60
|
+
@logger.error("Could not acquire orchestrator lock: #{e}")
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
57
64
|
def reacquire_orchestrator_lock
|
|
58
65
|
case ::Sidekiq.redis { |conn| conn.eval REACQUIRE_SCRIPT, [REDIS_LOCK_KEY], [@world.id] }
|
|
59
66
|
when ACQUIRE_MISSING
|
|
@@ -16,6 +16,10 @@ module Dynflow
|
|
|
16
16
|
::MessagePack::DefaultFactory.register_type(0x00, Time, packer: MessagePack::Time::Packer, unpacker: MessagePack::Time::Unpacker)
|
|
17
17
|
|
|
18
18
|
begin
|
|
19
|
+
# time_with_zone added a deprecation warning in 7.1.0 which we need to account for
|
|
20
|
+
# it was removed again in 7.2.0
|
|
21
|
+
require 'active_support/deprecation'
|
|
22
|
+
require 'active_support/deprecator'
|
|
19
23
|
require 'active_support/time_with_zone'
|
|
20
24
|
unpacker = ->(payload) do
|
|
21
25
|
tv = MessagePack::Timestamp.from_msgpack_ext(payload)
|
data/lib/dynflow/persistence.rb
CHANGED
|
@@ -101,8 +101,16 @@ module Dynflow
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
def
|
|
105
|
-
adapter.
|
|
104
|
+
def find_execution_plan_dependencies(execution_plan_id)
|
|
105
|
+
adapter.find_execution_plan_dependencies(execution_plan_id)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def find_blocked_execution_plans(execution_plan_id)
|
|
109
|
+
adapter.find_blocked_execution_plans(execution_plan_id)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def find_ready_delayed_plans(time)
|
|
113
|
+
adapter.find_ready_delayed_plans(time).map do |plan|
|
|
106
114
|
DelayedPlan.new_from_hash(@world, plan)
|
|
107
115
|
end
|
|
108
116
|
end
|
|
@@ -163,5 +171,9 @@ module Dynflow
|
|
|
163
171
|
def prune_undeliverable_envelopes
|
|
164
172
|
adapter.prune_undeliverable_envelopes
|
|
165
173
|
end
|
|
174
|
+
|
|
175
|
+
def chain_execution_plan(first, second)
|
|
176
|
+
adapter.chain_execution_plan(first, second)
|
|
177
|
+
end
|
|
166
178
|
end
|
|
167
179
|
end
|
|
@@ -72,7 +72,15 @@ module Dynflow
|
|
|
72
72
|
raise NotImplementedError
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
def
|
|
75
|
+
def find_execution_plan_dependencies(execution_plan_id)
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def find_blocked_execution_plans(execution_plan_id)
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_ready_delayed_plans(options = {})
|
|
76
84
|
raise NotImplementedError
|
|
77
85
|
end
|
|
78
86
|
|