dopi 0.17.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 +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +322 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +102 -0
- data/LICENSE.txt +177 -0
- data/README.md +309 -0
- data/Rakefile +44 -0
- data/Vagrantfile +64 -0
- data/bin/dopi +4 -0
- data/doc/getting_started.md +247 -0
- data/doc/getting_started_examples/001_hello_world.yaml +17 -0
- data/doc/getting_started_examples/002_connecting_over_ssh.yaml +35 -0
- data/doc/plugins/custom.md +88 -0
- data/doc/plugins/mco/rpc.md +82 -0
- data/doc/plugins/ssh/custom.md +141 -0
- data/doc/plugins/ssh/file_contains.md +37 -0
- data/doc/plugins/ssh/file_deploy.md +52 -0
- data/doc/plugins/ssh/file_exists.md +31 -0
- data/doc/plugins/ssh/file_replace.md +37 -0
- data/doc/plugins/ssh/puppet_agent_run.md +50 -0
- data/doc/plugins/ssh/reboot.md +22 -0
- data/doc/plugins/ssh/wait_for_login.md +53 -0
- data/doc/plugins/winrm/cmd.md +161 -0
- data/doc/plugins/winrm/file_contains.md +39 -0
- data/doc/plugins/winrm/file_exists.md +31 -0
- data/doc/plugins/winrm/powershell.md +27 -0
- data/doc/plugins/winrm/puppet_agent_run.md +49 -0
- data/doc/plugins/winrm/reboot.md +17 -0
- data/doc/plugins/winrm/wait_for_login.md +55 -0
- data/dopi.gemspec +42 -0
- data/lib/dopi/cli/command_add.rb +35 -0
- data/lib/dopi/cli/command_list.rb +19 -0
- data/lib/dopi/cli/command_remove.rb +31 -0
- data/lib/dopi/cli/command_reset.rb +27 -0
- data/lib/dopi/cli/command_run.rb +68 -0
- data/lib/dopi/cli/command_show.rb +109 -0
- data/lib/dopi/cli/command_update.rb +37 -0
- data/lib/dopi/cli/command_validate.rb +27 -0
- data/lib/dopi/cli/global_options.rb +55 -0
- data/lib/dopi/cli/log.rb +33 -0
- data/lib/dopi/cli.rb +57 -0
- data/lib/dopi/command/custom.rb +52 -0
- data/lib/dopi/command/dummy.rb +27 -0
- data/lib/dopi/command/mco/rpc.rb +158 -0
- data/lib/dopi/command/ssh/custom.rb +48 -0
- data/lib/dopi/command/ssh/file_contains.rb +70 -0
- data/lib/dopi/command/ssh/file_deploy.rb +71 -0
- data/lib/dopi/command/ssh/file_exists.rb +54 -0
- data/lib/dopi/command/ssh/file_replace.rb +96 -0
- data/lib/dopi/command/ssh/puppet_agent_run.rb +63 -0
- data/lib/dopi/command/ssh/reboot.rb +50 -0
- data/lib/dopi/command/ssh/wait_for_login.rb +68 -0
- data/lib/dopi/command/winrm/cmd.rb +44 -0
- data/lib/dopi/command/winrm/file_contains.rb +66 -0
- data/lib/dopi/command/winrm/file_exists.rb +51 -0
- data/lib/dopi/command/winrm/powershell.rb +16 -0
- data/lib/dopi/command/winrm/puppet_agent_run.rb +61 -0
- data/lib/dopi/command/winrm/reboot.rb +33 -0
- data/lib/dopi/command/winrm/wait_for_login.rb +49 -0
- data/lib/dopi/command.rb +239 -0
- data/lib/dopi/command_parser/arguments.rb +38 -0
- data/lib/dopi/command_parser/credentials.rb +59 -0
- data/lib/dopi/command_parser/env.rb +37 -0
- data/lib/dopi/command_parser/exec.rb +27 -0
- data/lib/dopi/command_parser/exit_code.rb +73 -0
- data/lib/dopi/command_parser/output.rb +126 -0
- data/lib/dopi/command_set.rb +66 -0
- data/lib/dopi/connector/local.rb +77 -0
- data/lib/dopi/connector/ssh.rb +170 -0
- data/lib/dopi/connector/winrm.rb +167 -0
- data/lib/dopi/error.rb +43 -0
- data/lib/dopi/log.rb +18 -0
- data/lib/dopi/node.rb +70 -0
- data/lib/dopi/plan.rb +99 -0
- data/lib/dopi/pluginmanager.rb +62 -0
- data/lib/dopi/state.rb +226 -0
- data/lib/dopi/state_store.rb +155 -0
- data/lib/dopi/step.rb +227 -0
- data/lib/dopi/step_set.rb +70 -0
- data/lib/dopi/version.rb +3 -0
- data/lib/dopi.rb +165 -0
- data/spec/command_helper.rb +11 -0
- data/spec/fixtures/mco_client.cfg +26 -0
- data/spec/fixtures/plans/fail_on_timeout.yaml +20 -0
- data/spec/fixtures/plans/hello_world.yaml +34 -0
- data/spec/fixtures/plans/non_existing_node.yaml +26 -0
- data/spec/fixtures/plans/test_role_variable.yaml +29 -0
- data/spec/fixtures/puppet/Puppetfile +8 -0
- data/spec/fixtures/puppet/Puppetfile.lock +57 -0
- data/spec/fixtures/puppet/hiera.yaml +6 -0
- data/spec/fixtures/puppet/manifests/site.pp +52 -0
- data/spec/fixtures/test_configuration.yaml +54 -0
- data/spec/fixtures/test_credentials.yaml +11 -0
- data/spec/fixtures/test_deloyed_file.txt +5 -0
- data/spec/fixtures/test_infrastructure.yaml +12 -0
- data/spec/fixtures/test_nodes.yaml +45 -0
- data/spec/fixtures/testenv_plan.yaml +159 -0
- data/spec/integration/dopi/addrun_spec.rb +31 -0
- data/spec/integration/dopi/cli/command_run_spec.rb +38 -0
- data/spec/integration/dopi/cli/global_options_spec.rb +128 -0
- data/spec/integration/dopi/command_spec.rb +66 -0
- data/spec/integration/dopi/fail_check_plans/file_exists_fails.yaml +38 -0
- data/spec/integration/dopi/fail_check_plans/output_parser.yaml +39 -0
- data/spec/integration/dopi/fail_check_plans/powershell_fail.yaml +25 -0
- data/spec/integration/dopi/fail_check_plans/timeout.yaml +29 -0
- data/spec/integration/dopi/fail_check_plans/verify_commands.yaml +33 -0
- data/spec/integration/dopi/failplan.rb +27 -0
- data/spec/integration/dopi/plan.rb +27 -0
- data/spec/integration/dopi/plans/dummy.yaml +29 -0
- data/spec/integration/dopi/plans/max_per_role.yaml +55 -0
- data/spec/integration/dopi/plans/no_timeout.yaml +29 -0
- data/spec/integration/dopi/plans/node_and_role_patterns.yaml +58 -0
- data/spec/integration/dopi/plans/node_by_config.yaml +116 -0
- data/spec/integration/dopi/plans/plugin_defaults.yaml +86 -0
- data/spec/integration/dopi/plans/plugins/mco/rpc.yaml +33 -0
- data/spec/integration/dopi/plans/plugins/ssh/custom.yaml +97 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_contains.yaml +51 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_deploy.yaml +82 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_exists.yaml +69 -0
- data/spec/integration/dopi/plans/plugins/ssh/file_replace.yaml +55 -0
- data/spec/integration/dopi/plans/plugins/ssh/puppet_agent_run.yaml +45 -0
- data/spec/integration/dopi/plans/plugins/ssh/reboot.yaml +43 -0
- data/spec/integration/dopi/plans/plugins/ssh/wait_for_login.yaml +45 -0
- data/spec/integration/dopi/plans/plugins/winrm/cmd.yaml +39 -0
- data/spec/integration/dopi/plans/plugins/winrm/file_contains.yaml +51 -0
- data/spec/integration/dopi/plans/plugins/winrm/file_exists.yaml +69 -0
- data/spec/integration/dopi/plans/plugins/winrm/reboot.yaml +31 -0
- data/spec/integration/dopi/plans/resolve_roles_on_validate.yaml +23 -0
- data/spec/integration/dopi/plans/ssh_parallel.yaml +37 -0
- data/spec/integration/dopi/plans/verify_commands.yaml +49 -0
- data/spec/spec_helper.rb +104 -0
- data/spec/unit/dopi/command/custom_spec.rb +58 -0
- data/spec/unit/dopi/command/mco/rpc_spec.rb +157 -0
- data/spec/unit/dopi/command/ssh/custom_spec.rb +30 -0
- data/spec/unit/dopi/command/ssh/file_deploy_spec.rb +42 -0
- data/spec/unit/dopi/command/ssh/file_replace_spec.rb +35 -0
- data/spec/unit/dopi/command_parser/credentials_spec.rb +53 -0
- data/spec/unit/dopi/command_parser/exit_code_spec.rb +63 -0
- data/spec/unit/dopi/command_parser/output_spec.rb +129 -0
- data/spec/unit/dopi/command_spec.rb +14 -0
- data/spec/unit/dopi/connector/winrm_spec.rb +111 -0
- data/spec/unit/dopi/node_spec.rb +24 -0
- data/spec/unit/dopi/plan_spec.rb +31 -0
- data/spec/unit/dopi/state_spec.rb +109 -0
- data/spec/unit/dopi/step_spec.rb +13 -0
- metadata +448 -0
data/lib/dopi/plan.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This class loads a deployment plan
|
|
3
|
+
#
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'dop_common'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
|
|
9
|
+
module Dopi
|
|
10
|
+
class Plan
|
|
11
|
+
extend Forwardable
|
|
12
|
+
include Dopi::State
|
|
13
|
+
|
|
14
|
+
attr_reader :plan_parser, :version, :context_logger
|
|
15
|
+
|
|
16
|
+
def initialize(plan_parser)
|
|
17
|
+
@version = Dopi::VERSION
|
|
18
|
+
@plan_parser = plan_parser
|
|
19
|
+
|
|
20
|
+
step_sets.each{|step_set| state_add_child(step_set)}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def_delegators :@plan_parser,
|
|
24
|
+
:name,
|
|
25
|
+
:configuration,
|
|
26
|
+
:credentials,
|
|
27
|
+
:max_in_flight,
|
|
28
|
+
:max_per_role,
|
|
29
|
+
:canary_host
|
|
30
|
+
|
|
31
|
+
def run(options = {})
|
|
32
|
+
options_defaults = {
|
|
33
|
+
:run_for_nodes => :all,
|
|
34
|
+
:noop => false,
|
|
35
|
+
:step_set => 'default',
|
|
36
|
+
:node_info => {},
|
|
37
|
+
:run_id => Time.now.strftime('%Y%m%d-%H%M%S'),
|
|
38
|
+
}
|
|
39
|
+
run_options = options_defaults.merge(options)
|
|
40
|
+
|
|
41
|
+
context_log_path = File.join(DopCommon.config.log_dir, "#{run_options[:run_id]}-#{name}")
|
|
42
|
+
node_names = nodes.map{|n| n.name}
|
|
43
|
+
@context_logger = DopCommon::ThreadContextLogger.new(context_log_path, node_names)
|
|
44
|
+
|
|
45
|
+
nodes.each{|node| node.node_info = run_options[:node_info][node.fqdn] || {}}
|
|
46
|
+
step_set = step_sets.find{|s| s.name == run_options[:step_set]}
|
|
47
|
+
raise "Plan: Step set #{run_options[:step_set]} does not exist" if step_set.nil?
|
|
48
|
+
step_set.run(run_options)
|
|
49
|
+
ensure
|
|
50
|
+
@context_logger.cleanup
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The main validation work is done in the dop_common
|
|
54
|
+
# parser. We just add the command plugin parsers
|
|
55
|
+
def valid?
|
|
56
|
+
validity = @plan_parser.valid?
|
|
57
|
+
validity = false unless step_sets.all?{|step_set| step_set.valid? }
|
|
58
|
+
validity
|
|
59
|
+
rescue => e
|
|
60
|
+
DopCommon.config.trace ? Dopi.log.error(e) : Dopi.log.error(e.message)
|
|
61
|
+
Dopi.log.warn("Plan: Can't validate the command plugins because of a previous error")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def nodes
|
|
65
|
+
@nodes ||= parsed_nodes.map do |parsed_node|
|
|
66
|
+
::Dopi::Node.new(parsed_node, self)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def step_sets
|
|
71
|
+
@step_sets ||= parsed_step_sets.map do |parsed_step_set|
|
|
72
|
+
::Dopi::StepSet.new(parsed_step_set, self)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def load_state(state_hash)
|
|
77
|
+
if state_hash[:step_sets].kind_of?(Hash)
|
|
78
|
+
step_sets.each do |step_set|
|
|
79
|
+
step_set_state = state_hash[:step_sets][step_set.name] || []
|
|
80
|
+
step_set.load_state(step_set_state)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def state_hash
|
|
86
|
+
step_sets_hash = {}
|
|
87
|
+
step_sets.each do |step_set|
|
|
88
|
+
step_sets_hash[step_set.name] = step_set.state_hash
|
|
89
|
+
end
|
|
90
|
+
{:step_sets => step_sets_hash}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def_delegator :@plan_parser, :nodes, :parsed_nodes
|
|
96
|
+
def_delegator :@plan_parser, :step_sets, :parsed_step_sets
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This class registers the plugins as they get loaded by ruby
|
|
3
|
+
# and can create instances based on the plugin name
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Dopi
|
|
7
|
+
module PluginManager
|
|
8
|
+
|
|
9
|
+
@plugins = {}
|
|
10
|
+
|
|
11
|
+
def self.<<(plugin_klass)
|
|
12
|
+
plugin_name = get_plugin_name(plugin_klass)
|
|
13
|
+
|
|
14
|
+
raise Dopi::PluginLoaderError,
|
|
15
|
+
"Plugin class #{plugin_klass.to_s} (#{plugin_name}) already loaded" if @plugins[plugin_name]
|
|
16
|
+
|
|
17
|
+
@plugins[plugin_name] = plugin_klass
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.create_instance(plugin_name, *args)
|
|
21
|
+
begin
|
|
22
|
+
@plugins[plugin_name].new(*args)
|
|
23
|
+
rescue Exception => e
|
|
24
|
+
raise PluginLoaderError, "Could not create instance of plugin #{plugin_name}: #{e.message}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.get_plugin_name(plugin_klass)
|
|
29
|
+
plugin_klass.to_s.
|
|
30
|
+
gsub(/::/, '/').
|
|
31
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
|
32
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
|
33
|
+
tr("-", "_").
|
|
34
|
+
downcase
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Expects a regular expression as a plugin filter
|
|
38
|
+
def self.plugin_list(filter = nil)
|
|
39
|
+
if filter
|
|
40
|
+
regexp = Regexp.new(filter)
|
|
41
|
+
@plugins.select{|p| p =~ regexp}
|
|
42
|
+
else
|
|
43
|
+
@plugins
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Expects a regular expression as a plugin filter
|
|
48
|
+
def self.plugin_name_list(filter = nil)
|
|
49
|
+
plugin_list(filter).keys
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Expects a regular expression as a plugin filter
|
|
53
|
+
def self.plugin_klass_list(filter = nil)
|
|
54
|
+
plugin_list(filter).values
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.plugin_klass(plugin_name)
|
|
58
|
+
@plugins[plugin_name]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/dopi/state.rb
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This is a simple hierarchical state tracker which chan keep
|
|
3
|
+
# track of it's state based on it's children
|
|
4
|
+
#
|
|
5
|
+
require 'observer'
|
|
6
|
+
|
|
7
|
+
module Dopi
|
|
8
|
+
module State
|
|
9
|
+
include Observable
|
|
10
|
+
|
|
11
|
+
def to_yaml_properties
|
|
12
|
+
instance_variables - [:@signal_procs]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def state
|
|
16
|
+
@state ||= :ready
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def state_auto_evaluate_children
|
|
20
|
+
@auto_evaluate_children = true if @auto_evaluate_children.nil?
|
|
21
|
+
@auto_evaluate_children
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def state_auto_evaluate_children=(value)
|
|
25
|
+
@auto_evaluate_children = value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def state_children
|
|
29
|
+
@state_children ||= []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def state_add_child(child)
|
|
33
|
+
state_children << child
|
|
34
|
+
child.add_observer(self)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def state_children_ready?
|
|
38
|
+
state_children.any? {|child| child.state_ready?}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def state_children_starting?
|
|
42
|
+
state_children.any? {|child| child.state_starting?}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def state_children_running_noop?
|
|
46
|
+
state_children.any? {|child| child.state_running_noop?}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def state_children_running?
|
|
50
|
+
state_children.any? {|child| child.state_running?}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def state_children_failed?
|
|
54
|
+
state_children.any? {|child| child.state_failed?}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def state_children_done?
|
|
58
|
+
state_children.all? {|child| child.state_done?}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def state_children_partial?
|
|
62
|
+
state_partial? || state_children.any?{|c| c.state_children_partial?}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update_mutex
|
|
66
|
+
@upate_mutex || Mutex.new
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def update(notify_only)
|
|
70
|
+
update_mutex.synchronize do
|
|
71
|
+
unless notify_only
|
|
72
|
+
old_state = @state
|
|
73
|
+
Dopi.log.debug("Checking if state of '#{name}' needs to be updated")
|
|
74
|
+
unless state_children.empty? || !state_auto_evaluate_children
|
|
75
|
+
if state_children_failed? then @state = :failed
|
|
76
|
+
elsif state_children_done? then @state = :done
|
|
77
|
+
elsif state_children_running? then @state = :running
|
|
78
|
+
elsif state_children_running_noop? then @state = :running_noop
|
|
79
|
+
elsif state_children_starting? then @state = :starting
|
|
80
|
+
elsif state_children_ready? then @state = :ready
|
|
81
|
+
end
|
|
82
|
+
if old_state == @state
|
|
83
|
+
state_changed(true)
|
|
84
|
+
else
|
|
85
|
+
state_changed(false)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
state_changed(true)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def state_reset_with_children(force = false)
|
|
95
|
+
state_reset(force) if state_failed? or force
|
|
96
|
+
if state_children_failed? or force
|
|
97
|
+
state_children.each {|child| child.state_reset_with_children(force) }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def state_ready?
|
|
102
|
+
state == :ready
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def state_starting?
|
|
106
|
+
state == :starting
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def state_running?
|
|
110
|
+
state == :running
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def state_running_noop?
|
|
114
|
+
state == :running_noop
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def state_done?
|
|
118
|
+
state == :done
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def state_failed?
|
|
122
|
+
state == :failed
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def state_partial?
|
|
126
|
+
[:failed, :done, :running, :running_noop, :starting, :ready].each do |s|
|
|
127
|
+
return false if state_children.all?{|c| c.state == s}
|
|
128
|
+
end
|
|
129
|
+
return true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def state_start
|
|
133
|
+
return if state == :starting
|
|
134
|
+
raise Dopi::StateTransitionError, "Can't switch to running from #{state.to_s}" unless state == :ready
|
|
135
|
+
@state = :starting
|
|
136
|
+
state_changed
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def state_run
|
|
140
|
+
return if state == :running
|
|
141
|
+
raise Dopi::StateTransitionError, "Can't switch to running from #{state.to_s}" unless state == :ready || state == :starting
|
|
142
|
+
@state = :running
|
|
143
|
+
state_changed
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def state_run_noop
|
|
147
|
+
return if state == :running_noop
|
|
148
|
+
raise Dopi::StateTransitionError, "Can't switch to running_noop from #{state.to_s}" unless state == :ready || state == :starting
|
|
149
|
+
@state = :running_noop
|
|
150
|
+
state_changed
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def state_ready
|
|
154
|
+
return if state == :ready
|
|
155
|
+
raise Dopi::StateTransitionError, "Can't switch to ready from #{state.to_s}" unless state == :running_noop
|
|
156
|
+
@state = :ready
|
|
157
|
+
state_changed
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def state_finish
|
|
161
|
+
return if state == :done
|
|
162
|
+
raise Dopi::StateTransitionError, "Can't switch to done from #{state.to_s}" unless state == :running
|
|
163
|
+
@state = :done
|
|
164
|
+
state_changed
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def state_fail
|
|
168
|
+
return if state == :failed
|
|
169
|
+
raise Dopi::StateTransitionError, "Can't switch to failed from #{state.to_s}" unless state == :running
|
|
170
|
+
@state = :failed
|
|
171
|
+
state_changed
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def state_reset(force = false)
|
|
175
|
+
if force
|
|
176
|
+
state_children.each {|child| child.state_reset(force)}
|
|
177
|
+
@state = :ready
|
|
178
|
+
state_changed
|
|
179
|
+
else
|
|
180
|
+
raise Dopi::StateTransitionError, "Can't switch to ready from #{state.to_s}" unless state == :failed || state == :ready
|
|
181
|
+
if state_children.any?
|
|
182
|
+
state_children.each {|child| child.state_reset unless (child.state_done? || child.state_ready?)}
|
|
183
|
+
else
|
|
184
|
+
@state = :ready
|
|
185
|
+
state_changed
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def state_changed(notify_only = false)
|
|
191
|
+
Dopi.log.debug("State of '#{name}' updated to #{@state.to_s}, notifying observers")
|
|
192
|
+
changed
|
|
193
|
+
notify_observers(notify_only)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def signals
|
|
197
|
+
@signals ||= Hash.new(false)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def reset_signals
|
|
201
|
+
@signals = Hash.new(false)
|
|
202
|
+
state_children.each {|child| child.reset_signals}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def signal_procs
|
|
206
|
+
@signal_procs ||= []
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def on_signal(a_proc)
|
|
210
|
+
signal_procs << a_proc
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def delete_on_signal(a_proc)
|
|
214
|
+
signal_procs.delete(a_proc)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def send_signal(signal)
|
|
218
|
+
unless signals[signal] == true
|
|
219
|
+
signal_procs.each {|p| p.call(signal)}
|
|
220
|
+
state_children.each {|child| child.send_signal(signal)}
|
|
221
|
+
signals[signal] = true
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This is the DOPi state store which persists the state
|
|
3
|
+
# of the steps between and during runs.
|
|
4
|
+
#
|
|
5
|
+
module Dopi
|
|
6
|
+
class StateStore
|
|
7
|
+
|
|
8
|
+
def initialize(plan_name, plan_store)
|
|
9
|
+
@plan_store = plan_store
|
|
10
|
+
@plan_name = plan_name
|
|
11
|
+
@state_store = @plan_store.state_store(plan_name, 'dopi')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def update(options = {})
|
|
15
|
+
if options[:clear]
|
|
16
|
+
clear(options)
|
|
17
|
+
elsif options[:ignore]
|
|
18
|
+
ignore(options)
|
|
19
|
+
else
|
|
20
|
+
update_state(options)
|
|
21
|
+
end
|
|
22
|
+
rescue DopCommon::UnknownVersionError => e
|
|
23
|
+
Dopi.log.warn("The state has an unknown plan version #{e.message}.")
|
|
24
|
+
Dopi.log.warn("Please update with the 'clear' or 'ignore' option")
|
|
25
|
+
rescue => e
|
|
26
|
+
Dopi.log.error("An error occured during update: #{e.message}")
|
|
27
|
+
Dopi.log.error("Please update with the 'clear' or 'ignore' option")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def persist_state(plan)
|
|
31
|
+
@state_store.transaction do
|
|
32
|
+
plan_state = plan.state_hash
|
|
33
|
+
Dopi.log.debug('Persisting plan state:')
|
|
34
|
+
Dopi.log.debug(plan_state)
|
|
35
|
+
@state_store[:state] = plan_state
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def state_hash
|
|
40
|
+
@state_store.transaction(true) do
|
|
41
|
+
@state_store[:state] || {}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def method_missing(m, *args, &block)
|
|
46
|
+
@state_store.send(m, *args, &block)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def clear(options)
|
|
52
|
+
@state_store.transaction do
|
|
53
|
+
Dopi.log.debug("Clearing the state for plan #{@plan_name}")
|
|
54
|
+
ver = @plan_store.show_versions(@plan_name).last
|
|
55
|
+
plan = Dopi::Plan.new(@plan_store.get_plan(@plan_name))
|
|
56
|
+
@state_store[:state] = plan.state_hash
|
|
57
|
+
@state_store[:version] = ver
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ignore(options)
|
|
62
|
+
@state_store.transaction do
|
|
63
|
+
ver = @plan_store.show_versions(@plan_name).last
|
|
64
|
+
Dopi.log.debug("Ignoring update and setting state version of plan #{@plan_name} to #{ver}")
|
|
65
|
+
@state_store[:version] = ver
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def update_state(options)
|
|
70
|
+
@state_store.update do |plan_diff|
|
|
71
|
+
Dopi.log.debug("Updating plan #{@plan_name}. This is the diff:")
|
|
72
|
+
Dopi.log.debug(plan_diff.to_s)
|
|
73
|
+
|
|
74
|
+
plan_diff.each do |patch|
|
|
75
|
+
match ||= update_rule_steps(patch)
|
|
76
|
+
unless match
|
|
77
|
+
Dopi.log.debug("No rule matched, ignoring patch: #{patch.to_s}")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def update_rule_steps(patch)
|
|
84
|
+
match = /^steps\.?(\w*)\[(\d+)\](.*)/.match(patch[1])
|
|
85
|
+
if match
|
|
86
|
+
step_set = match[1].empty? ? 'default' : match[1]
|
|
87
|
+
step_nr = match[2].to_i
|
|
88
|
+
rest = match[3]
|
|
89
|
+
unless update_rule_commands(patch, step_set, step_nr, rest)
|
|
90
|
+
case patch[0]
|
|
91
|
+
when '+' then add_step(step_set, step_nr)
|
|
92
|
+
when '-' then del_step(step_set, step_nr)
|
|
93
|
+
else
|
|
94
|
+
Dopi.log.debug("Step changed, ignoring patch: #{patch.to_s}")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
return true
|
|
98
|
+
end
|
|
99
|
+
return false
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def update_rule_commands(patch, step_set, step_nr, rest)
|
|
103
|
+
match = /^\.commands?\[(\d+)\](.*)/.match(rest)
|
|
104
|
+
if match
|
|
105
|
+
command_nr = match[1]
|
|
106
|
+
if /^\.verify_command/.match(rest)
|
|
107
|
+
Dopi.log.debug("Change in verify_command only, ignoring patch: #{patch.to_s}")
|
|
108
|
+
else
|
|
109
|
+
case patch[0]
|
|
110
|
+
when '+' then add_command(step_set, step_nr)
|
|
111
|
+
when '-' then del_command(step_set, step_nr)
|
|
112
|
+
else
|
|
113
|
+
Dopi.log.debug("Command changed, ignoring patch: #{patch.to_s}")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
return true
|
|
117
|
+
end
|
|
118
|
+
return false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def add_step(step_set, step_nr)
|
|
122
|
+
@state_store[:state][:step_sets][step_set].insert(step_nr, {})
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def del_step(step_set, step_nr)
|
|
126
|
+
@state_store[:state][:step_sets][step_set].delete_at(step_nr)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def add_command(step_set, step_nr, command_nr)
|
|
130
|
+
@state_store[:state][:step_sets][step_set][step_nr].each do |node|
|
|
131
|
+
node.insert(command_nr, {:command_state => :ready})
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def del_command(step_set, step_nr, command_nr)
|
|
136
|
+
@state_store[:state][:step_sets][step_set][step_nr].each do |node|
|
|
137
|
+
node.delete_at(command_nr)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class StateStoreObserver
|
|
144
|
+
|
|
145
|
+
def initialize(plan, state_store)
|
|
146
|
+
@plan = plan
|
|
147
|
+
@state_store = state_store
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def update(notify_only = false)
|
|
151
|
+
@state_store.persist_state(@plan)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
end
|