Chris's Blog

Devops Shokunin

Wrap a unix command for mcollective code example

Comments Off on Wrap a unix command for mcollective code example

Writing plugins to wrap a command in mcollective is very easy.

What is difficult is figuring out all of the conventions that you need to follow.

I’m going to walk you through an example with some tips.

Conventions you need to follow

1) Agents need to go in the agent directory.  You can find it with the following command

sudo find / -name rpcutil.ddl -exec dirname {} \;

2) The file name should be all lowercase with a file mode of 644

test@mcollectivemaster:/usr/share/mcollective/plugins/mcollective/agent$ ls -l wrapit.rb
-rw-r--r-- 1 root root 1127 2012-04-13 19:05 wrapit.rb

3)  The class name needs to match the file name with at least the first letter capitalized.

4) To more easily debug make sure the following two lines are present in your /etc/mcollective/server.cfg so you can find errors in syslog

logger_type = syslog
loglevel = debug

5) Restart mcollective between changes to the agent file

An example agent

module MCollective
  module Agent
    class Wrapit < RPC::Agent
###################################################################
      metadata :name        => "My Agent",
               :description => "Example of how to wrap a command",
               :author      => "Me <me@example.com>",
               :license     => "DWYWI",
               :version     => "0.1",
               :url         => "http://blog.mague.com",
               :timeout     => 10
###################################################################
#functions
 
      def run_command(cmd)
	cmdrun = IO.popen(cmd)
	output = cmdrun.read
	cmdrun.close
	if $?.to_i > 0
	  logger.error "WRAPIT: #{cmd}: #{output}"
	  reply.fail! "ERROR: failed with #{output}"
	end
	output
      end
###################################################################
#actions
 
      action "ping" do
	reply[:info] = run_command("/bin/ping -c 5 #{request[:target]}")
      end
 
###################################################################
    end #class
  end   #agent
end     #module

Mcollective uses the request data structure to receive information from the client and reply to send information back to the client. The actions are methods that the client calls to run.

When running commands be sure to wrap with IO.popen so that you can capture both the output and pass the error code. Do not forget to close the popen or you will leak file handles and cause trouble.

An Example Client

 
#!/usr/bin/env ruby
require 'rubygems' if RUBY_VERSION < "1.9"
require 'mcollective'
 
include MCollective::RPC
 
args = {}
action=""
 
options = rpcoptions do |parser, options|
 
  parser.on('-K', '--targ TARGET', 'Target to test against') do |v|
    args[:target] = v
    end 
 
  parser.on('-S', '--action ACTION', 'action') do |v|
    action = v
  end   
 
end
 
mc = rpcclient("wrapit", :options => options)
#mc.fact_filter "servertype", "/myservertype/"
mc.timeout = 10
mc.progress = false
 
mc.send(action, args).each do |resp|
  if resp[:statusmsg] == "OK"
    puts resp[:sender]
    puts resp[:data][:info]
  else
    puts "ERROR:"
    p resp
  end
end

The RPC includes all kinds of switches so be sure to run your script with -h to make sure none of your arguments overlap as they will be overwritten by the default switches.

You can add your own fact_filters and turn off the progress bar which is useful with web interfaces.

Be sure to check the statusmsg sent back from the agent, so you can catch any errors.

Since developer access is limited in my work environment, I wrote lots of customer agents to allow troubleshooting and deployment to be coordinated. Putting them behind a Sinatra web front end has made life easier for all.

Update:It was kindly pointed out to me by R.I. Pienaar that instead of using popen, there is a built-in run() function that is even more flexible.

cmd_output = []
cmd_error = ""
run ( "/bin/ping -c #{request[:target}",
      :environment => {"MYSETTING" => "ABC123"}
      :cwd         => "/home/testguy",
      :stdout      => cmd_output,
      :sdterr      => cmd_error,
)

Comments are closed.