Class: RightScale::ExecutableSequenceProxy

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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, options = {})
  options = DEFAULT_OPTIONS.merge(options)
  @context = context
  @thread_name = get_thread_name_from_context(context)
  @pid_callback = options[:pid_callback]
  @tag_query_timeout = options[: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

#contextObject (readonly)

(::RightScale::OperationContext) operation context containing bundle



43
44
45
# File 'lib/instance/executable_sequence_proxy.rb', line 43

def context
  @context
end

#inputs_patchObject

(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

#pidObject (readonly)

PID for created process or nil



46
47
48
# File 'lib/instance/executable_sequence_proxy.rb', line 46

def pid
  @pid
end

#thread_nameObject (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

#runObject

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.tags(:timeout=>@tag_query_timeout) do |tags|
    if tags.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=>tags)

      if tags.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