Skip to content
Browse files

Script Broker

A simple broker that executes a script on the node. This provides a
lightweight broker with no external service dependencies. It is suitable
for one-time configuration post os install, but also flexible enough to
setup more complicated long term solutions (e.g. local puppet apply).

Features:

 * Optionally send auxillary data along with the script.
 * Sends the metadata along with the script.
 * Fully configurable where the files land on the node.

Interface:

The script will be called with one parameter: The path to the metadata file.
The metadata file contains all the node metadata (including tags) and the
configuration of the script broker (including the paths to local resources). It
is JSON 'pretty' formatted and should be amiable to parsing.

Example:

  On the Razor Server:

    # cat /root/hello_world.sh
    #!/bin/bash
    set -e
    set -u

    echo "Hello World!"
    echo "Metadata Path: $1"

    # razor broker
    Broker Targets:
     Name =>  hello_world
     Description =>  Hello
     Plugin =>  script
     UUID =>  4kp0o3tchb5NW5pdcV6pRf
     Script =>  /root/hello_world.sh
     Script Path =>  /tmp/razor_script
     Data =>
     Data Path =>  /tmp/razor_script_data
     Metadata Path =>  /tmp/razor_script_metadata
     Log Path =>  /tmp/razor_script.log

  On the Node after the script broker has run:

    # cd /tmp
    # ls
    razor_complete.log  razor_script  razor_script.log  razor_script_metadata
    # cat razor_script.log
    Hello World!
    Metadata Path: /tmp/razor_script_metadata
    # cat /tmp/razor_script_metadata
    {
      "username": "root",
      "password": "electric",
      "metadata": {
        "razor_tags": "ubuntu-precise,cpus_16,virtualbox_vm,OracleCorporation,nics_1,memsize_1015MiB",
        "razor_node_uuid": "49jbMtCv4gn0BLphWfmJ8p",
        "razor_active_model_uuid": "4IfYuiaCQ1fY9D1mQUZ0Vr",
        "razor_model_uuid": "60WqyDfq9yF3HSSpLWutpf",
        "razor_model_name": "ubuntu_precise",
        "razor_model_description": "Ubuntu Precise Model",
        "razor_model_template": "linux_deploy",
        "razor_policy_count": "27"
      },
      "uuid": "49jbMtCv4gn0BLphWfmJ8p",
      "ipaddress": "10.0.1.123",
      "script": "/vagrant/echo_script.sh",
      "script_path": "/tmp/razor_script",
      "data": "",
      "data_path": "/tmp/razor_script_data",
      "metadata_path": "/tmp/razor_script_metadata",
      "log_path": "/tmp/razor_script.log"
    }
  • Loading branch information...
1 parent cf7f00b commit 29f9ab4d4195e7d02d9dcef62e60182a426d21ed @calebcase calebcase committed Mar 26, 2013
Showing with 174 additions and 0 deletions.
  1. +1 −0 Gemfile
  2. +173 −0 lib/project_razor/broker/script.rb
View
1 Gemfile
@@ -8,6 +8,7 @@ gem 'bson_ext'
gem 'require_all'
gem 'json', '>= 1.7.7'
gem 'net-ssh'
+gem 'net-scp'
gem 'mongo'
gem 'pg'
gem 'daemons'
View
173 lib/project_razor/broker/script.rb
@@ -0,0 +1,173 @@
+# Script Broker
+
+require "erb"
+require "net/ssh"
+require "net/scp"
+require 'json'
+require 'stringio'
+
+# Root namespace for ProjectRazor
+module ProjectRazor::BrokerPlugin
+ # Script Error
+ class Error < RuntimeError; end
+ class ScriptError < Error
+ attr :return_code
+ def initialize(return_code)
+ @return_code = return_code
+ end
+ end
+
+ # Root namespace for Script Broker plugin defined in ProjectRazor for node handoff.
+ class Script < ProjectRazor::BrokerPlugin::Base
+ include(ProjectRazor::Logging)
+
+ def initialize(hash)
+ super(hash)
+
+ @hidden = false
+ @plugin = :script
+ @description = "Script Execution"
+ @hidden = false
+ from_hash(hash) if hash
+ @req_metadata_hash = {
+ "@script" => {
+ :default => "",
+ :example => "web-server.sh",
+ :required => true,
+ :description => "Script to be run.",
+ :validation => '.*',
+ },
+ "@script_path" => {
+ :default => "/tmp/razor_script",
+ :example => "/tmp/razor_script",
+ :required => false,
+ :description => "Script data path.",
+ :validation => '.*',
+ },
+ "@data" => {
+ :default => "",
+ :example => "data.tbz",
+ :required => false,
+ :description => "Script data resources.",
+ :validation => '.*',
+ },
+ "@data_path" => {
+ :default => "/tmp/razor_script_data",
+ :example => "/tmp/razor_script_data",
+ :required => false,
+ :description => "Script data path.",
+ :validation => '.*',
+ },
+ "@metadata_path" => {
+ :default => "/tmp/razor_script_metadata",
+ :example => "/tmp/razor_script_metadata",
+ :required => false,
+ :description => "Script metadata path.",
+ :validation => '.*',
+ },
+ "@log_path" => {
+ :default => "/tmp/razor_script.log",
+ :example => "/tmp/razor_script.log",
+ :required => false,
+ :description => "Script log path.",
+ :validation => '.*',
+ },
+ }
+ end
+
+ def print_item_header
+ if @is_template
+ return "Plugin", "Description"
+ else
+ return "Name", "Description", "Plugin", "UUID", "Script", "Script Path", "Data", "Data Path", "Metadata Path", "Log Path"
+ end
+ end
+
+ def print_item
+ if @is_template
+ return @plugin.to_s, @description.to_s
+ else
+ return @name, @user_description, @plugin.to_s, @uuid, @script, @script_path, @data, @data_path, @metadata_path, @log_path
+ end
+ end
+
+ def agent_hand_off(options = {})
+ logger.debug 'Begin hand off.'
+
+ options[:script] = @script
+ options[:script_path] = @script_path
+ options[:data] = @data
+ options[:data_path] = @data_path
+ options[:metadata_path] = @metadata_path
+ options[:log_path] = @log_path
+
+ logger.debug "Options processed: #{options.to_json}"
+
+ @output = ""
+ @attempts = 0
+
+ begin
+ Net::SSH.start(options[:ipaddress], options[:username], {:password => options[:password], :user_known_hosts_file => '/dev/null'}) do |session|
+ scp = Net::SCP.new(session)
+
+ # Install Script
+ logger.debug 'Installing Script...'
+ scp.upload! "#{options[:script]}", "#{options[:script_path]}"
+
+ if not options[:data].empty?
+ # Install Data
+ logger.debug 'Installing Data...'
+ scp.upload! "#{options[:data]}", "#{options[:data_path]}"
+ end
+
+ # Install Metadata
+ logger.debug 'Installing Metadata...'
+ scp.upload! StringIO.new("#{JSON.pretty_generate(options)}"), "#{options[:metadata_path]}"
+
+ # Execute Script
+ logger.debug 'Executing Script...'
+ session.open_channel do |channel|
+ channel.request_pty do |ch, success|
+ logger.error 'Failed to get PTY... this _might_ break your script.' unless success
+ end
+
+ channel.on_request('exit-status') do |ch, data|
+ return_code = data.read_long
+ if return_code != 0
+ raise ScriptError.new(return_code), "Script exited non-zero: #{return_code}"
+ end
+ end
+
+ channel.on_data do |ch, data|
+ @output << data
+ end
+
+ channel.on_extended_data do |ch, type, data|
+ @output << data
+ end
+
+ channel.exec("#{options[:script_path]} #{options[:metadata_path]} 2>&1 | tee #{options[:log_path]}")
+ end
+ end
+ rescue Net::SSH::ConnectionTimeout, Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
+ if @attempts < 3 then
+ @attempts += 1
+ logger.warn "Razor script broker connection timed out (attempt: #{@attempts} of 3): #{e}"
+ sleep(30)
+ retry
+ else
+ logger.error "Razor script broker connection timed out (no more attempts left): #{e}"
+ return :broker_fail
+ end
+ rescue => e
+ logger.error "Razor script broker error: #{e}"
+ logger.error "Razor script broker output:\n---\n#{@output}\n---"
+ return :broker_fail
+ end
+
+ logger.debug "Razor script broker output:\n---\n#{@output}\n---"
+
+ return :broker_success
+ end
+ end
+end

0 comments on commit 29f9ab4

Please sign in to comment.
Something went wrong with that request. Please try again.