Class: RightScale::ExecutableSequenceProxy
- Includes:
- EM::Deferrable
- Defined in:
- lib/instance/executable_sequence_proxy.rb
Overview
Bundle sequence proxy, create child process to execute bundle Use right_popen gem to control child process asynchronously
Constant Summary collapse
- DEFAULT_OPTIONS =
{ :tag_query_timeout => 120 }
- AUDIT_CLOSE_TIMEOUT =
Wait up to 20 seconds to process pending audits after child process exited
20
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
(::RightScale::OperationContext) operation context containing bundle.
-
#inputs_patch ⇒ Object
(Hash) Inputs patch to be forwarded to core after each converge.
-
#pid ⇒ Object
readonly
PID for created process or nil.
-
#thread_name ⇒ Object
readonly
Execution thread name or default.
Instance Method Summary collapse
-
#get_thread_name_from_context(context) ⇒ Object
FIX: thread_name should never be nil from the core in future, but temporarily we must supply the default thread_name before if nil.
-
#initialize(context, options = {}) ⇒ ExecutableSequenceProxy
constructor
Initialize sequence.
-
#run ⇒ Object
Run given executable bundle Asynchronous, set deferrable object’s disposition.
Constructor Details
#initialize(context, options = {}) ⇒ ExecutableSequenceProxy
Initialize sequence
Parameters
- context(RightScale::OperationContext)
-
Bundle to be run and associated audit
Options
- :pid_callback(Proc)
-
proc that will be called, passing self, when the PID of the child process becomes known
- :tag_query_timeout(Proc)
-
default 120 – how many seconds to wait for the agent tag query to complete, before giving up and continuing
59 60 61 62 63 64 65 66 67 68 |
# File 'lib/instance/executable_sequence_proxy.rb', line 59 def initialize(context, = {}) = DEFAULT_OPTIONS.merge() @context = context @thread_name = get_thread_name_from_context(context) @pid_callback = [:pid_callback] @tag_query_timeout = [:tag_query_timeout] AuditCookStub.instance.setup_audit_forwarding(@thread_name, context.audit) AuditCookStub.instance.on_close(@thread_name) { @audit_closed = true; check_done } end |
Instance Attribute Details
#context ⇒ Object (readonly)
(::RightScale::OperationContext) operation context containing bundle
43 44 45 |
# File 'lib/instance/executable_sequence_proxy.rb', line 43 def context @context end |
#inputs_patch ⇒ Object
(Hash) Inputs patch to be forwarded to core after each converge
40 41 42 |
# File 'lib/instance/executable_sequence_proxy.rb', line 40 def inputs_patch @inputs_patch end |
#pid ⇒ Object (readonly)
PID for created process or nil
46 47 48 |
# File 'lib/instance/executable_sequence_proxy.rb', line 46 def pid @pid end |
#thread_name ⇒ Object (readonly)
Execution thread name or default.
49 50 51 |
# File 'lib/instance/executable_sequence_proxy.rb', line 49 def thread_name @thread_name end |
Instance Method Details
#get_thread_name_from_context(context) ⇒ Object
FIX: thread_name should never be nil from the core in future, but temporarily we must supply the default thread_name before if nil. in future we should fail execution when thread_name is reliably present and for any reason does not match ::RightScale::AgentConfig.valid_thread_name see also ExecutableSequenceProxy#initialize
Parameters
- bundle(OperationalContext)
-
An operational context
Return
- result(String)
-
Thread name of this context
81 82 83 84 85 86 87 88 89 90 |
# File 'lib/instance/executable_sequence_proxy.rb', line 81 def get_thread_name_from_context(context) thread_name = nil thread_name = context.thread_name if context.respond_to?(:thread_name) Log.warn("Encountered a nil thread name unexpectedly, defaulting to '#{RightScale::AgentConfig.default_thread_name}'") unless thread_name thread_name ||= RightScale::AgentConfig.default_thread_name unless thread_name =~ RightScale::AgentConfig.valid_thread_name raise ArgumentError, "Invalid thread name #{thread_name.inspect}" end thread_name end |
#run ⇒ Object
Run given executable bundle Asynchronous, set deferrable object’s disposition
Return
- true
-
Always return true
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/instance/executable_sequence_proxy.rb', line 97 def run @succeeded = true @context.audit.create_new_section('Querying tags') # update CookState with the latest instance before launching Cook RightScale::AgentTagManager.instance.(:timeout=>@tag_query_timeout) do || if .is_a?(String) # AgentTagManager could give us a String (error message) Log.error("Failed to query tags before running executable sequence: #{tags}") @context.audit.append_error('Could not discover tags due to an error or timeout.') else # or, it could give us anything else -- generally an array) -- which indicates success CookState.update(InstanceState, :startup_tags=>) if .empty? @context.audit.append_info('No tags discovered.') else @context.audit.append_info("Tags discovered: '#{tags.join("', '")}'") end end input_text = "#{MessageEncoder.for_agent(InstanceState.identity).encode(@context.payload)}\n" # TEAL FIX: we have an issue with the Windows EM implementation not # allowing both sockets and named pipes to share the same file/socket # id. sending the input on the command line is a temporary workaround. platform = RightScale::Platform if platform.windows? input_path = File.normalize_path(File.join(platform.filesystem.temp_dir, "rs_executable_sequence#{@thread_name}.txt")) File.open(input_path, "w") { |f| f.write(input_text) } input_text = nil cmd_exe_path = File.normalize_path(ENV['ComSpec']).gsub("/", "\\") ruby_exe_path = File.normalize_path(AgentConfig.ruby_cmd).gsub("/", "\\") input_path = input_path.gsub("/", "\\") cmd = "#{cmd_exe_path} /C type \"#{input_path}\" | #{ruby_exe_path} #{cook_path_and_arguments}" else # WARNING - always ensure cmd is a String, never an Array of command parts. # # right_popen handles single-String arguments using "sh -c #{cmd}" which ensures # we are invoked through a shell which will parse shell config files and ensure that # changes to system PATH, etc are freshened on every converge. # # If we pass cmd as an Array, right_popen uses the Array form of exec without an # intermediate shell, and system config changes will not be picked up. cmd = "#{AgentConfig.ruby_cmd} #{cook_path_and_arguments}" end EM.next_tick do # prepare env vars for child process. environment = { ::RightScale::OptionsBag::OPTIONS_ENV => ::ENV[::RightScale::OptionsBag::OPTIONS_ENV] } # spawn RightScale::RightPopen.popen3_async( cmd, :input => input_text, :target => self, :environment => environment, :stdout_handler => :on_read_stdout, :stderr_handler => :on_read_stderr, :pid_handler => :on_pid, :exit_handler => :on_exit) end end end |