class MCollective::Client

Helpers for writing clients that can talk to agents, do discovery and so forth

Attributes

options[RW]
stats[RW]

Public Class Methods

new(configfile) click to toggle source
# File lib/mcollective/client.rb, line 8
def initialize(configfile)
    @config = Config.instance
    @config.loadconfig(configfile) unless @config.configured

    @connection = PluginManager["connector_plugin"]
    @security = PluginManager["security_plugin"]

    @security.initiated_by = :client
    @options = nil
    @subscriptions = {}

    @connection.connect
end

Public Instance Methods

collective() click to toggle source

Returns the configured main collective if no specific collective is specified as options

# File lib/mcollective/client.rb, line 24
def collective
    if @options[:collective].nil?
        @config.main_collective
    else
        @options[:collective]
    end
end
disconnect() click to toggle source

Disconnects cleanly from the middleware

# File lib/mcollective/client.rb, line 33
def disconnect
    Log.debug("Disconnecting from the middleware")
    @connection.disconnect
end
discover(filter, timeout) click to toggle source

Performs a discovery of nodes matching the filter passed returns an array of nodes

# File lib/mcollective/client.rb, line 101
def discover(filter, timeout)
    begin
        hosts = []
        Timeout.timeout(timeout) do
            reqid = sendreq("ping", "discovery", filter)
            Log.debug("Waiting #{timeout} seconds for discovery replies to request #{reqid}")

            loop do
                msg = receive(reqid)
                Log.debug("Got discovery reply from #{msg[:senderid]}")
                hosts << msg[:senderid]
            end
        end
    rescue Timeout::Error => e
        hosts.sort
    rescue Exception => e
        raise
    end
end
discovered_req(body, agent, options=false) { |resp| ... } click to toggle source

Performs a discovery and then send a request, performs the passed block for each response

times = discovered_req("status", "mcollectived", options, client) {|resp|
   pp resp
}

It returns a hash of times and timeouts for discovery and total run is taken from the options hash which in turn is generally built using MCollective::Optionparser

# File lib/mcollective/client.rb, line 173
def discovered_req(body, agent, options=false)
    stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}

    options = @options unless options

    STDOUT.sync = true

    print("Determining the amount of hosts matching filter for #{options[:disctimeout]} seconds .... ")

    begin
        discovered_hosts = discover(options[:filter], options[:disctimeout])
        discovered = discovered_hosts.size
        hosts_responded = []
        hosts_not_responded = discovered_hosts

        stat[:discoverytime] = Time.now.to_f - stat[:starttime]

        puts("#{discovered}\n\n")
    rescue Interrupt
        puts("Discovery interrupted.")
        exit!
    end

    raise("No matching clients found") if discovered == 0

    begin
        Timeout.timeout(options[:timeout]) do
            reqid = sendreq(body, agent, options[:filter])

            (1..discovered).each do |c|
                resp = receive(reqid)

                hosts_responded << resp[:senderid]
                hosts_not_responded.delete(resp[:senderid]) if hosts_not_responded.include?(resp[:senderid])

                yield(resp)
            end
        end
    rescue Interrupt => e
    rescue Timeout::Error => e
    end

    stat[:totaltime] = Time.now.to_f - stat[:starttime]
    stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
    stat[:responses] = hosts_responded.size
    stat[:responsesfrom] = hosts_responded
    stat[:noresponsefrom] = hosts_not_responded
    stat[:discovered] = discovered

    @stats = stat
    return stat
end
display_stats(stats, options=false, caption="stomp call summary") click to toggle source

Prints out the stats returns from req and #discovered_req in a nice way

# File lib/mcollective/client.rb, line 227
def display_stats(stats, options=false, caption="stomp call summary")
    options = @options unless options

    if options[:verbose]
        puts("\n---- #{caption} ----")

        if stats[:discovered]
            puts("           Nodes: #{stats[:discovered]} / #{stats[:responses]}")
        else
            puts("           Nodes: #{stats[:responses]}")
        end

        printf("      Start Time: %s\n", Time.at(stats[:starttime]))
        printf("  Discovery Time: %.2fms\n", stats[:discoverytime] * 1000)
        printf("      Agent Time: %.2fms\n", stats[:blocktime] * 1000)
        printf("      Total Time: %.2fms\n", stats[:totaltime] * 1000)

    else
        if stats[:discovered]
            printf("\nFinished processing %d / %d hosts in %.2f ms\n\n", stats[:responses], stats[:discovered], stats[:blocktime] * 1000)
        else
            printf("\nFinished processing %d hosts in %.2f ms\n\n", stats[:responses], stats[:blocktime] * 1000)
        end
    end

    if stats[:noresponsefrom].size > 0
        puts("\nNo response from:\n")

        stats[:noresponsefrom].each do |c|
            puts if c % 4 == 1
            printf("%30s", c)
        end

        puts
    end
end
receive(requestid = nil) click to toggle source

Blocking call that waits for ever for a message to arrive.

If you give it a requestid this means you’ve previously send a request with that ID and now you just want replies that matches that id, in that case the current connection will just ignore all messages not directed at it and keep waiting for more till it finds a matching message.

# File lib/mcollective/client.rb, line 77
def receive(requestid = nil)
    msg = nil

    begin
        msg = @connection.receive

        msg = @security.decodemsg(msg)

        msg[:senderid] = Digest::MD5.hexdigest(msg[:senderid]) if ENV.include?("MCOLLECTIVE_ANON")

        raise(MsgDoesNotMatchRequestID, "Message reqid #{requestid} does not match our reqid #{msg[:requestid]}") if msg[:requestid] != requestid
    rescue SecurityValidationFailed => e
        Log.warn("Ignoring a message that did not pass security validations")
        retry
    rescue MsgDoesNotMatchRequestID => e
        Log.debug("Ignoring a message for some other client")
        retry
    end

    msg
end
req(body, agent, options=false, waitfor=0) { |resp| ... } click to toggle source

Send a request, performs the passed block for each response

times = req(“status”, “mcollectived”, options, client) {|resp|

pp resp

}

It returns a hash of times and timeouts for discovery and total run is taken from the options hash which in turn is generally built using MCollective::Optionparser

# File lib/mcollective/client.rb, line 129
def req(body, agent, options=false, waitfor=0)
    stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}

    options = @options unless options

    STDOUT.sync = true

    hosts_responded = 0

    begin
        Timeout.timeout(options[:timeout]) do
            reqid = sendreq(body, agent, options[:filter])

            loop do
                resp = receive(reqid)

                hosts_responded += 1

                yield(resp)

                break if (waitfor != 0 && hosts_responded >= waitfor)
            end
        end
    rescue Interrupt => e
    rescue Timeout::Error => e
    end

    stat[:totaltime] = Time.now.to_f - stat[:starttime]
    stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
    stat[:responses] = hosts_responded
    stat[:noresponsefrom] = []

    @stats = stat
    return stat
end
sendreq(msg, agent, filter = {}) click to toggle source

Sends a request and returns the generated request id, doesn’t wait for responses and doesn’t execute any passed in code blocks for responses

# File lib/mcollective/client.rb, line 40
def sendreq(msg, agent, filter = {})
    target = Util.make_target(agent, :command, collective)

    reqid = Digest::MD5.hexdigest("#{@config.identity}-#{Time.now.to_f.to_s}-#{target}")

    # Security plugins now accept an agent and collective, ones written for <= 1.1.4 dont
    # but we still want to support them, try to call them in a compatible way if they
    # dont support the new arguments
    begin
        req = @security.encoderequest(@config.identity, target, msg, reqid, filter, agent, collective)
    rescue ArgumentError
        req = @security.encoderequest(@config.identity, target, msg, reqid, filter)
    end

    Log.debug("Sending request #{reqid} to #{target}")

    unless @subscriptions.include?(agent)
        topic = Util.make_target(agent, :reply, collective)
        Log.debug("Subscribing to #{topic}")

        Util.subscribe(topic)
        @subscriptions[agent] = 1
    end

    Timeout.timeout(2) do
        @connection.send(target, req)
    end

    reqid
end