",
7 | :license => "ASL2",
8 | :version => "0.1",
9 | :url => "http://www.devco.net/",
10 | :timeout => 5
11 |
12 | activate_when { File.executable?("/usr/bin/ts") }
13 |
14 | action "add" do
15 | validate :command, :shellsafe
16 |
17 | flags = []
18 | flags << "-n" if request[:no_output]
19 | flags << "-g" if request[:gzip_output]
20 | flags << "-d" if request[:depends_on_previous]
21 | flags << "-L #{request.uniqid}"
22 | flags << "-B"
23 |
24 | reply[:exitcode] = run("/usr/bin/ts %s %s" % [flags.join(" "), request[:command]], :stdout => :ts_jobid, :chomp => true)
25 |
26 | reply[:ts_jobid] = reply[:ts_jobid].to_i
27 | reply[:jobid] = request.uniqid
28 |
29 | case reply[:exitcode]
30 | when 2
31 | reply[:msg] = "Could not enqueue job - the queue is full"
32 | else
33 | reply[:msg] = "Command enqueued with ts job id #{reply[:ts_jobid]}"
34 | end
35 |
36 | reply.fail("Failed to enqueue the command - exit code was %s" % [reply[:exitcode]]) unless reply[:exitcode] == 0
37 | end
38 |
39 | action "query" do
40 | validate :jobid, :shellsafe
41 |
42 | get_queue.each do |job|
43 | if job[:jobid] == request[:jobid]
44 | job.keys.each do |k|
45 | reply[k] = job[k]
46 | end
47 |
48 | if request[:output] && job[:state] == "finished"
49 | reply[:output] = get_job_output(job[:ts_jobid])
50 | else
51 | reply[:output] = "Not Requested or not Available"
52 | end
53 | end
54 | end
55 |
56 | unless reply[:jobid]
57 | reply.fail! "No job found with job id #{request[:jobid]}"
58 | end
59 |
60 | reply.fail("Command failed to run - error level %s" % [reply[:error_level]]) unless reply[:error_level] == 0
61 | end
62 |
63 | action "get_queue" do
64 | reply[:queue] = get_queue
65 | end
66 |
67 | def get_job_output(ts_jobid)
68 | output = ""
69 | run("/usr/bin/ts -c #{ts_jobid}", :stdout => output, :chomp => true)
70 |
71 | output
72 | end
73 |
74 | def get_queue
75 | queue = ""
76 | run("/usr/bin/ts", :stdout => queue, :chomp => true)
77 |
78 | jobs = []
79 |
80 | queue.split("\n").each do |line|
81 | if line =~ /(\d+)\s+(\w+)\s+.+?\s+(-*\d+)\s+([\.\d]+)\/([\.\d]+)\/([\.\d]+)\s+\[(.+)\](.+)/
82 | jobs << {:ts_jobid => $1, :state => $2, :error_level => Integer($3), :run_time => Float($4),
83 | :user_time => Float($5), :system_time => Float($6), :jobid => $7, :command => $8}
84 | elsif line =~ /(\d+)\s+(\w+).+?\s+\[(.+)\](.+)/
85 | jobs << {:ts_jobid => $1, :state => $2, :error_level => 0, :run_time => 0,
86 | :user_time => 0, :system_time => 0, :jobid => $3, :command => $4}
87 | end
88 | end
89 |
90 | return jobs
91 | end
92 | end
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/agent/libvirt/README.md:
--------------------------------------------------------------------------------
1 | What?
2 | =====
3 |
4 | Basic management of Libvirt Hypervisors and domains
5 |
6 | Usage?
7 | ======
8 |
9 | An mco application is included that wraps arond the basic capabilities of the agent
10 | for full details see _mco virt --help_
11 |
12 | You need the ruby libvirt bindings installed, tested with version 0.3.0
13 |
14 | Hypervisor / Domain Information:
15 | -----------------------
16 |
17 | % mco virt info
18 |
19 | kvm1.xx.net
20 | Max VCPUs: 16
21 | Secrets: 0
22 | Type: QEMU
23 | Version: 12001
24 | Active Domains: ["dev2_devco", "dev3_devco", "dev4_devco", "dev5_devco"]
25 | MHz: 1297
26 | Active Networks: 1
27 | Inactive Domains: 0
28 | Inactive Domains: []
29 | Inactive Storage Pools: 0
30 | Sockets: 1
31 | Active Domains: 4
32 | Active Storage Pools: 1
33 | Cores: 2
34 | Model: x86_64
35 | Inactive Interfaces: 0
36 | Numa Nodes: 1
37 | URI: qemu:///system
38 | Node Devices: 49
39 | Active Interfaces: 2
40 | Memory: 8063656
41 | Network Filters: 15
42 | Free Memory: 3993661440
43 | CPUs: 2
44 | Inactive Networks: 0
45 | Threads: 1
46 |
47 |
48 |
49 | % mco virt info dev2_devco
50 |
51 | kvm1.xx.net
52 | UUID: ca74dc32-0f09-7265-b67e-151b4fb5dd90
53 | State Code: 1
54 | Autostart: false
55 | OS Type: 0
56 | VCPUs: 1
57 | Snapshots: []
58 | Max Memory: 524288
59 | Persistent: true
60 | Number of Snapshots: 0
61 | CPU Time: 5594920000000
62 | Memory: 524288
63 | Current Snapshot: false
64 | State: Running
65 | Managed Save: false
66 |
67 |
68 | Manage a Domain:
69 | ----------------
70 |
71 |
72 | % mco virt stop dev4_devco
73 |
74 | kvm1.xx.net
75 | State: 5
76 | State: Shut off
77 |
78 |
79 | Other available actions are:
80 |
81 | * start
82 | * stop (needs acpid in the domain)
83 | * reboot (needs acpid in the domain)
84 | * suspend
85 | * resume
86 | * destroy
87 |
88 | Create a Domain:
89 | ----------------
90 |
91 | This requires you to have created the XML that describes the domain
92 | on the node already. The _permanent_ argument is optional and is the
93 | difference between _virsh define_ and _virsh create_.
94 |
95 |
96 | % mco virt define dev4 /srv/kvm/etc/dev4.xml permanent
97 |
98 | State Code: 1
99 | State: Running
100 |
101 |
102 | Undefine a Domain:
103 | ------------------
104 |
105 | This undefines a domain, you can optionally destroy the domain before
106 | undefining it else the request will fail.
107 |
108 |
109 | % mco virt undefine dev4 destroy
110 |
111 |
112 | List all Domains:
113 | -----------------
114 |
115 |
116 | % mco virt domains
117 |
118 | xen1.xx.net: Domain-0, devco_net
119 | kvm1.xx.net: dev2_devco, dev3_devco, dev4_devco, dev5_devco
120 | xen5.xx.net: Domain-0, dev1_devco
121 |
122 |
123 | Find a Domain:
124 | --------------
125 |
126 | Searches for a domain based on a ruby pattern:
127 |
128 |
129 | % mco virt find devco
130 |
131 | xen1.xx.net: devco_net
132 | kvm1.xx.net: dev2_devco, dev3_devco, dev4_devco, dev5_devco
133 | xen5.xx.net: dev1_devco
134 |
135 |
136 |
137 | Todo?
138 | ====
139 |
140 | * More stats so that full feature auto provisioning can be built
141 |
142 | Contact?
143 | ========
144 |
145 | R.I.Pienaar / rip@devco.net / http://devco.net / @ripienaar
146 |
--------------------------------------------------------------------------------
/agent/ts/agent/ts.ddl:
--------------------------------------------------------------------------------
1 | metadata :name => "Task Scheduler Agent",
2 | :description => "An agent to create and manage jobs for Task Scheduler",
3 | :author => "R.I.Pienaar ",
4 | :license => "ASL2",
5 | :version => "0.1",
6 | :url => "http://www.devco.net/",
7 | :timeout => 5
8 |
9 | action "add", :description => "Schedules a command to be run" do
10 | input :command,
11 | :prompt => "Command",
12 | :description => "Unix Command to Schedule",
13 | :type => :string,
14 | :validation => '^.+$',
15 | :optional => false,
16 | :maxlength => 150
17 |
18 | input :no_output,
19 | :prompt => "Do not record output",
20 | :description => "Set to false to surpress recording the command output",
21 | :type => :boolean,
22 | :optional => true
23 |
24 | input :gzip_output,
25 | :prompt => "Compress Output",
26 | :description => "Enable this to store the command output compressed with gzip",
27 | :type => :boolean,
28 | :optional => true
29 |
30 | input :depends_on_previous,
31 | :prompt => "Dependant on previous command",
32 | :description => "Only run this command if the previous one completed succesfully",
33 | :type => :boolean,
34 | :optional => true
35 |
36 | output :exitcode,
37 | :description => "The exitcode from the ts binary",
38 | :display_as => "TS Exit Code"
39 |
40 | output :ts_jobid,
41 | :description => "The TS specific job id",
42 | :display_as => "TS Job ID"
43 |
44 | output :jobid,
45 | :description => "The ID that identifies this job uniquely across all machines",
46 | :display_as => "Job ID"
47 |
48 | output :msg,
49 | :description => "Job status",
50 | :display_as => "Status"
51 | end
52 |
53 | action "query", :description => "Query the status of a previously queued command" do
54 | display :always
55 |
56 | input :jobid,
57 | :prompt => "Job ID",
58 | :description => "The MCollective Job ID",
59 | :type => :string,
60 | :validation => '^.+$',
61 | :optional => false,
62 | :maxlength => 33
63 |
64 | output :output,
65 | :description => "Textual output from the unix command",
66 | :display_as => "Output"
67 |
68 | output :ts_jobid,
69 | :description => "Per Machine TS Job ID",
70 | :display_as => "TS Job ID"
71 |
72 | output :state,
73 | :description => "Textual representation of the job state",
74 | :display_as => "Job State State"
75 |
76 | output :error_level,
77 | :description => "Unix exit code for the command",
78 | :display_as => "Exit Code"
79 |
80 | output :run_time,
81 | :description => "Total run time for the command",
82 | :display_as => "Run Time"
83 |
84 | output :user_time,
85 | :description => "User time spent running the command",
86 | :display_as => "User Time"
87 |
88 | output :system_time,
89 | :description => "System time spent running the command",
90 | :display_as => "System Time"
91 |
92 | output :jobid,
93 | :description => "Collective wide unique job ID",
94 | :display_as => "Job ID"
95 |
96 | output :command,
97 | :description => "The command that was run",
98 | :display_as => "Command"
99 | end
100 |
101 | action "get_queue", :description => "Retrieves the entire job queue" do
102 | display :always
103 |
104 | output :queue,
105 | :description => "The complete Job Queue",
106 | :display_as => "Queue"
107 | end
108 |
--------------------------------------------------------------------------------
/discovery/puppetdb/discovery/puppetdb.rb:
--------------------------------------------------------------------------------
1 | require 'net/http'
2 | require 'net/https'
3 |
4 | # Proof of Concept PuppetDB integration for mcollective discovery
5 | # capable of supporting class, fact and identity filters.
6 | #
7 | # Final incorporation into mcollective would depend on the
8 | # http://projects.puppetlabs.com/issues/14763 being closed
9 | #
10 | # There are some hard coded hostnames etc here, so just a POC
11 | module MCollective
12 | class Discovery
13 | class Puppetdb
14 | def self.discover(filter, timeout, limit=0, client=nil)
15 | http = Net::HTTP.new('puppetdb.xx.net', 443)
16 | http.use_ssl = true
17 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
18 |
19 | found = []
20 |
21 | filter.keys.each do |key|
22 | case key
23 | when "identity"
24 | identity_search(filter["identity"], http, found)
25 |
26 | when "cf_class"
27 | class_search(filter["cf_class"], http, found)
28 |
29 | when "fact"
30 | fact_search(filter["fact"], http, found)
31 | end
32 | end
33 |
34 | # filters are combined so we get the intersection of values across
35 | # all matches found using fact, agent and identity filters
36 | found = found.inject(found[0]){|x, y| x & y}
37 |
38 | found.flatten.map do |node|
39 | if node =~ /^(.+?)\.\w+\.net/
40 | $1
41 | else
42 | node
43 | end
44 | end
45 | end
46 |
47 | def self.fact_search(filter, http, found)
48 | return if filter.empty?
49 |
50 | selected_hosts = []
51 |
52 | filter.each do |fact|
53 | raise "Can only do == matches using the PuppetDB discovery" unless fact[:operator] == "=="
54 |
55 | query = ["and", ["=", ["fact", fact[:fact]], fact[:value]]]
56 |
57 | resp, data = http.get("/nodes?query=%s" % URI.escape(query.to_json), {"accept" => "application/json"})
58 | raise "Failed to retrieve nodes from PuppetDB: %s: %s" % [resp.code, resp.message] unless resp.code == "200"
59 |
60 | found << JSON.parse(data)
61 | end
62 | end
63 |
64 | def self.class_search(filter, http, found)
65 | return if filter.empty?
66 |
67 | selected_hosts = []
68 |
69 | filter.each do |klass|
70 | klass = klass.split("::").map{|i| i.capitalize}.join("::")
71 | raise "Can not do regular expression matches for classes using the PuppetDB discovery method" if regexy_string(klass).is_a?(Regexp)
72 |
73 | query = ["and", ["=", "type", "Class"], ["=", "title", klass]]
74 |
75 | resp, data = http.get("/resources?query=%s" % URI.escape(query.to_json), {"accept" => "application/json"})
76 | raise "Failed to retrieve nodes from PuppetDB: %s: %s" % [resp.code, resp.message] unless resp.code == "200"
77 |
78 | found << JSON.parse(data).map{|found| found["certname"]}
79 | end
80 | end
81 |
82 | def self.identity_search(filter, http, found)
83 | return if filter.empty?
84 |
85 | resp, data = http.get("/nodes", {"accept" => "application/json"})
86 | raise "Failed to retrieve nodes from PuppetDB: %s: %s" % [resp.code, resp.message] unless resp.code == "200"
87 |
88 | all_hosts = JSON.parse(data)
89 | selected_hosts = []
90 |
91 | filter.each do |identity|
92 | identity = regexy_string(identity)
93 |
94 | if identity.is_a?(Regexp)
95 | selected_hosts << all_hosts.grep(identity)
96 | else
97 | selected_hosts << identity if all_hosts.include?(identity)
98 | end
99 | end
100 |
101 | found << selected_hosts
102 | end
103 |
104 | def self.regexy_string(string)
105 | if string.match("^/")
106 | Regexp.new(string.gsub("\/", ""))
107 | else
108 | string
109 | end
110 | end
111 | end
112 | end
113 | end
114 |
--------------------------------------------------------------------------------
/connector/redis/discovery/redis.rb:
--------------------------------------------------------------------------------
1 | module MCollective
2 | class Discovery
3 | class Redis
4 | require 'redis'
5 |
6 | class << self
7 | def discover(filter, timeout, limit=0, client=nil)
8 | config = Config.instance
9 |
10 | host = config.pluginconf.fetch("redis.host", "localhost")
11 | port = Integer(config.pluginconf.fetch("redis.port", "6379"))
12 | db = Integer(config.pluginconf.fetch("redis.db", "1"))
13 | password = config.pluginconf.fetch("redis.password", nil)
14 | max_age = Integer(config.pluginconf.fetch("redis.max_age", 1800))
15 |
16 | redis_opts = {:host => host, :port => port, :db => db}
17 | unless @password.nil?
18 | redis_opts.store(:password, @password)
19 | end
20 |
21 | @redis = ::Redis.new(redis_opts)
22 |
23 | found = [collective_hostlist(client.options[:collective], max_age)]
24 |
25 | filter.keys.each do |key|
26 | case key
27 | when "fact"
28 | fact_search(filter["fact"], found, max_age, client.options[:collective])
29 |
30 | when "cf_class"
31 | find_in_zlist("class", found, max_age, filter[key])
32 |
33 | when "agent"
34 | find_in_zlist("agent", found, max_age, filter[key])
35 |
36 | when "identity"
37 | identity_search(filter["identity"], found, max_age, client.options[:collective])
38 | end
39 | end
40 |
41 | # filters are combined so we get the intersection of values across
42 | # all matches found using fact, agent and identity filters
43 | found.inject(found[0]){|x, y| x & y}
44 | end
45 |
46 | def fact_search(filter, found, max_age, collective)
47 | return if filter.empty?
48 |
49 | hosts = collective_hostlist(collective, max_age)
50 | facts = {}
51 |
52 | hosts.each do |host|
53 | facts[host] = @redis.hgetall("mcollective::facts::#{host}")
54 | end
55 |
56 | filter.each do |f|
57 | matched_hosts = []
58 |
59 | fact = f[:fact]
60 | value = f[:value]
61 |
62 | hosts.each do |host|
63 | matched_hosts << host if facts[host].include?(fact) && facts[host][fact].match(regexy_string(value))
64 | end
65 |
66 | found << matched_hosts
67 | end
68 | end
69 |
70 | def identity_search(filter, found, max_age, collective)
71 | return if filter.empty?
72 |
73 | hosts = collective_hostlist(collective, max_age)
74 |
75 | filter.each do |match|
76 | found << hosts.grep(regexy_string(match))
77 | end
78 | end
79 |
80 | def find_in_zlist(key_type, found, max_age, filter)
81 | return if filter.empty?
82 |
83 | prefix = "mcollective::%s" % key_type
84 | oldest = Time.now.utc.to_i - max_age
85 |
86 | members = @redis.keys.grep(/^#{prefix}/).map do |key|
87 | key.match(/^#{prefix}::(.+)$/)[1]
88 | end
89 |
90 | filter.each do |matcher|
91 | discovered = []
92 |
93 | matched = members.grep(regexy_string(matcher))
94 |
95 | matched.each do |member|
96 | discovered.concat @redis.zrange("#{prefix}::#{member}", 0, oldest)
97 | end
98 |
99 | found << discovered
100 | end
101 | end
102 |
103 | def collective_hostlist(collective, max_age)
104 | now = Time.now.utc.to_i
105 | oldest = now - max_age
106 |
107 | @redis.zrangebyscore("mcollective::collective::#{collective}", oldest, now)
108 | end
109 |
110 | def regexy_string(string)
111 | if string.match("^/")
112 | Regexp.new(string.gsub("\/", ""))
113 | else
114 | string
115 | end
116 | end
117 | end
118 | end
119 | end
120 | end
121 |
--------------------------------------------------------------------------------
/agent/rndc/agent/rndc.ddl:
--------------------------------------------------------------------------------
1 | metadata :name => "rndc",
2 | :description => "SimpleRPC RNDC Agent",
3 | :author => "R.I.Pienaar ",
4 | :license => "ASL2.0",
5 | :version => "0.2",
6 | :url => "http://www.devco.net/",
7 | :timeout => 5
8 |
9 | ["reload", "freeze", "thaw"].each do |act|
10 | action act, :description => "#{act.capitalize} a zone or all zones" do
11 | input :zone,
12 | :prompt => "Zone",
13 | :description => "Zone to act on",
14 | :type => :string,
15 | :validation => '^.+$',
16 | :optional => true,
17 | :maxlength => 100
18 |
19 | output :out,
20 | :description => "STDOUT output",
21 | :display_as => "Output"
22 |
23 | output :err,
24 | :description => "STDERR output",
25 | :display_as => "Error"
26 | end
27 | end
28 |
29 | ["refresh", "retransfer", "notify", "sign"].each do |act|
30 | action act, :description => "#{act.capitalize} a zone" do
31 | input :zone,
32 | :prompt => "Zone",
33 | :description => "Zone to act on",
34 | :type => :string,
35 | :validation => '^.+$',
36 | :optional => false,
37 | :maxlength => 100
38 |
39 | output :out,
40 | :description => "STDOUT output",
41 | :display_as => "Output"
42 |
43 | output :err,
44 | :description => "STDERR output",
45 | :display_as => "Error"
46 | end
47 | end
48 |
49 | action "reconfig", :description => "Reloads the server configuration" do
50 | output :out,
51 | :description => "STDOUT output",
52 | :display_as => "Output"
53 |
54 | output :err,
55 | :description => "STDERR output",
56 | :display_as => "Error"
57 | end
58 |
59 | action "querylog", :description => "Toggles the server wide querylog" do
60 | output :out,
61 | :description => "STDOUT output",
62 | :display_as => "Output"
63 |
64 | output :err,
65 | :description => "STDERR output",
66 | :display_as => "Error"
67 | end
68 |
69 | action "flush", :description => "Flushes all of the server's caches." do
70 | output :out,
71 | :description => "STDOUT output",
72 | :display_as => "Output"
73 |
74 | output :err,
75 | :description => "STDERR output",
76 | :display_as => "Error"
77 | end
78 |
79 | action "status", :description => "Gather server status information" do
80 | display :always
81 |
82 | output :debug_level,
83 | :description => "Active debug level",
84 | :display_as => "Debug Level"
85 |
86 | output :version,
87 | :description => "Server Version",
88 | :display_as => "Version"
89 |
90 | output :soa_queries_in_progress,
91 | :description => "Active SOA queries",
92 | :display_as => "SOA Queries in Progress"
93 |
94 | output :worker_threads,
95 | :description => "Number of Worker Threads",
96 | :display_as => "Worker Threads"
97 |
98 | output :recursive_clients,
99 | :description => "Recursive Clients",
100 | :display_as => "Recursive Clients"
101 |
102 | output :xfers_running,
103 | :description => "Active transfers",
104 | :display_as => "Xfers Running"
105 |
106 | output :number_of_zones,
107 | :description => "Number of zones",
108 | :display_as => "Zones"
109 |
110 | output :xfers_deferred,
111 | :description => "Number of Xfers deferred",
112 | :display_as => "Xfers Deferred"
113 |
114 | output :tcp_clients,
115 | :description => "TCP Clients",
116 | :display_as => "TCP Clients"
117 |
118 | output :cpus_found,
119 | :description => "Number of CPUs Found",
120 | :display_as => "CPUs"
121 | end
122 |
--------------------------------------------------------------------------------
/agent/eximng/web/eximweb.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 |
3 | require 'rubygems'
4 | require 'sinatra'
5 | require 'mcollective'
6 | require 'lib/exim.rb'
7 | require 'cgi'
8 |
9 | class EximWeb < Sinatra::Base
10 | def initialize
11 | @exim = Exim.new
12 | super
13 | end
14 |
15 | set :static, true
16 | set :public, "public"
17 |
18 | helpers do
19 | include Rack::Utils
20 | alias_method :h, :escape_html
21 |
22 | def sanitize_email(email)
23 | email.gsub!(/^, "")
24 | email.gsub!(/>$/, "")
25 |
26 | if email == ""
27 | email = "postmaster"
28 | end
29 |
30 | return email
31 | end
32 |
33 | def display_result(result)
34 | if result.is_a?(String)
35 | result.split("\n").map{|r| h(r)}.join("
")
36 | elsif result.is_a?(Numeric)
37 | result.to_s
38 | else
39 | "" + h(JSON.pretty_generate(result)) + "
"
40 | end
41 | end
42 |
43 | def label_for_code(code)
44 | case code
45 | when 0
46 | 'ok'
47 | when 1
48 | 'aborted'
49 | when 2
50 | 'unknown action'
51 | when 3
52 | 'missing data'
53 | when 4
54 | 'invalid data'
55 | when 5
56 | 'unknown error'
57 | end
58 | end
59 | end
60 |
61 | get '/mailq' do
62 | @mailq = @exim.mailq
63 |
64 | erb :mailq_view
65 | end
66 |
67 | get '/mailq/thaw/:id' do
68 | @action = "thaw"
69 | @ddl = @exim.ddl
70 | @results = []
71 |
72 | unless params[:id] =~ /^\w+-\w+-\w+$/
73 | @error = "#{params[:id]} is not a valid message id"
74 | else
75 | @results = @exim.thaw(params[:id])
76 | end
77 |
78 | erb :generic_result_view
79 | end
80 |
81 | get '/mailq/freeze/:id' do
82 | @action = "freeze"
83 | @ddl = @exim.ddl
84 | @results = []
85 |
86 | unless params[:id] =~ /^\w+-\w+-\w+$/
87 | @error = "#{params[:id]} is not a valid message id"
88 | else
89 | @results = @exim.freeze(params[:id])
90 | end
91 |
92 | erb :generic_result_view
93 | end
94 |
95 | get '/mailq/run' do
96 | @action = "runq"
97 | @ddl = @exim.ddl
98 | @results = @exim.runq
99 |
100 | erb :generic_result_view
101 | end
102 |
103 | get '/mailq/delete/frozen' do
104 | @action = "rmfrozen"
105 | @ddl = @exim.ddl
106 | @results = @exim.rmfrozen
107 |
108 | erb :generic_result_view
109 | end
110 |
111 | get '/mailq/delete/bounces' do
112 | @action = "rmbounces"
113 | @ddl = @exim.ddl
114 | @results = @exim.rmbounces
115 |
116 | erb :generic_result_view
117 | end
118 |
119 | get '/mailq/delete/:id' do
120 | @action = "rm"
121 | @ddl = @exim.ddl
122 | @results = []
123 |
124 | unless params[:id] =~ /^\w+-\w+-\w+$/
125 | @error = "#{params[:id]} is not a valid message id"
126 | else
127 | @results = @exim.rm(params[:id])
128 | end
129 |
130 | erb :generic_result_view
131 | end
132 |
133 | get '/mailq/retrymsg/:id' do
134 | @action = "retrymsg"
135 | @ddl = @exim.ddl
136 | @results = []
137 |
138 | unless params[:id] =~ /^\w+-\w+-\w+$/
139 | @error = "#{params[:id]} is not a valid message id"
140 | else
141 | @results = @exim.retrymsg(params[:id])
142 | end
143 |
144 | erb :generic_result_view
145 | end
146 |
147 | get '/exiwhat' do
148 | @action = "exiwhat"
149 | @ddl = @exim.ddl
150 | @results = @exim.exiwhat
151 | erb :generic_result_view
152 | end
153 |
154 | get '/size' do
155 | @action = "size"
156 | @ddl = @exim.ddl
157 | @results = @exim.size
158 | erb :generic_result_view
159 | end
160 | end
161 |
162 | EximWeb.run!
163 |
--------------------------------------------------------------------------------
/scripts/package-updater.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 |
3 | # A simple script that uses the new batch mode available in
4 | # mcollective 1.3.3 and newer to do package updates:
5 | #
6 | # pacakge-updater.rb --batch 10
7 | #
8 | # This will give you the chance to clean yum cache everywhere
9 | # and then it will:
10 | #
11 | # - use the checkupdates action to get a list of available
12 | # updates on all machines
13 | # - present you with a menu of available updates
14 | # - the package you picked will be updated in batches of 10
15 | # machines at a time
16 | #
17 | # This loops until nothing is left to update
18 | #
19 | # While mco 1.3.2 has batching support there's been some refinements
20 | # that this script relies on, do not use it on older mcollectives
21 | #
22 | # R.I.Pienaar / rip@devco.net / @ripienaar / http://devco.net/
23 |
24 | require 'mcollective'
25 |
26 | include MCollective::RPC
27 |
28 | STDOUT.sync = true
29 | STDERR.sync = true
30 |
31 | def err(msg)
32 | STDERR.puts "EEE> #{msg}"
33 | end
34 |
35 | def msg(msg)
36 | puts ">>>> #{msg}"
37 | end
38 |
39 | def ask(msg)
40 | print "#{msg} (y/n) "
41 |
42 | ans = STDIN.gets.chomp.downcase
43 |
44 | ans == "y"
45 | end
46 |
47 | def get_updates_due(agent)
48 | updates = {}
49 |
50 | agent.reset
51 |
52 | msg "Checking for updates on #{agent.discover.size} servers"
53 |
54 | agent.checkupdates(:batch_size => 0) do |r, s|
55 | begin
56 | outdated = [s[:data][:outdated_packages]].compact.flatten
57 |
58 | unless outdated.empty?
59 | msg "Found %d updates for %s" % [outdated.size, s[:sender]]
60 |
61 | outdated.each do |pkg|
62 | name = pkg[:package]
63 |
64 | updates[name] ||= []
65 | updates[name] << s[:sender]
66 | end
67 | else
68 | msg "Found no updates for %s" % [ s[:sender] ]
69 | end
70 | rescue => e
71 | err("Failed to parse data: #{e}: #{r.pretty_inspect}")
72 | end
73 | end
74 |
75 | updates
76 | end
77 |
78 | def print_list(updates)
79 | updates.keys.sort.each_with_index do |pkg, idx|
80 | puts "%3d> %s on %d hosts" % [idx, pkg, updates[pkg].size]
81 | end
82 |
83 | puts
84 | puts " r> Refresh updates list"
85 | puts " q> Quit"
86 | end
87 |
88 | def update_pkg(updates, selection, agent)
89 | pkg = updates.keys.sort[selection]
90 |
91 | puts
92 |
93 | msg "Updating %s on %d servers in batches of %d" % [pkg, updates[pkg].size, agent.batch_size]
94 |
95 | agent.discover :hosts => updates[pkg]
96 |
97 | versions = {}
98 |
99 | agent.update(:package => pkg).each_with_index do |resp, i|
100 | puts if i == 0
101 |
102 | status = resp[:data][:properties]
103 |
104 | if resp[:statuscode] == 0
105 | if status.include?(:version)
106 | version = "#{status[:version]}-#{status[:release]}"
107 | elsif status.include?(:ensure)
108 | version = status[:ensure].to_s
109 | end
110 |
111 | versions.include?(version) ? versions[version] += 1 : versions[version] = 1
112 |
113 | printf("%-40s version = %s-%s\n", resp[:sender], status[:name], version)
114 | else
115 | printf("%-40s error = %s\n", resp[:sender], resp[:statusmsg])
116 | end
117 | end
118 |
119 | puts
120 | msg "Versions: %s" % [ versions.keys.sort.map {|s| "#{versions[s]} * #{s}" }.join(", ") ]
121 | puts
122 |
123 | if versions.keys.size == 1
124 | return pkg
125 | else
126 | err "Some updates of #{pkg} failed, got %d versions" % [ versions.keys.size ]
127 | end
128 | end
129 |
130 | @agent = rpcclient("package")
131 |
132 | if @agent.batch_size == 0
133 | exit unless ask("Are you sure you wish to continue without specifying a batch size using --batch?")
134 | end
135 |
136 | printrpc(@agent.yum_clean(:batch_size => 0)) if ask("Would you like to clear the yum cache everywhere?")
137 |
138 | updates_due = get_updates_due(@agent)
139 |
140 | until updates_due.empty?
141 | begin
142 | print_list(updates_due)
143 |
144 | puts
145 | print "Pick a package to update: "
146 |
147 | choice = STDIN.gets.chomp.downcase
148 |
149 | if choice == "r"
150 | updates_due = get_updates_due(@agent)
151 | next
152 | elsif choice == "q"
153 | exit
154 | end
155 |
156 | pkg = Integer(choice)
157 | updates_due.delete(update_pkg(updates_due, pkg, @agent))
158 | rescue Interrupt
159 | exit
160 | rescue SystemExit
161 | raise
162 | rescue => e
163 | err "#{e.class}: #{e}"
164 | err e.backtrace.pretty_inspect
165 | retry
166 | end
167 | end
168 |
--------------------------------------------------------------------------------
/agent/libvirt/application/virt.rb:
--------------------------------------------------------------------------------
1 | class MCollective::Application::Virt"
6 | usage "Usage: mco virt xml "
7 | usage "Usage: mco virt find "
8 | usage "Usage: mco virt [stop|start|reboot|suspend|resume|destroy] "
9 | usage "Usage: mco virt domains"
10 | usage "Usage: mco virt define [permanent]"
11 | usage "Usage: mco virt undefine [destroy]"
12 |
13 | def post_option_parser(configuration)
14 | configuration[:command] = ARGV.shift if ARGV.size > 0
15 | configuration[:domain] = ARGV.shift if ARGV.size > 0
16 | end
17 |
18 | def validate_configuration(configuration)
19 | raise "Please specify a command, see --help for details" unless configuration[:command]
20 |
21 | if ["xml", "stop", "start", "suspend", "resume", "destroy", "find"].include?(configuration[:command])
22 | raise "%s requires a domain name, see --help for details" % [configuration[:command]] unless configuration[:domain]
23 | end
24 | end
25 |
26 | def undefine_command
27 | configuration[:destroy] = ARGV.shift if ARGV.size > 0
28 |
29 | args = {:domain => configuration[:domain]}
30 | args[:destroy] = true if configuration[:destroy] =~ /^dest/
31 |
32 | printrpc virtclient.undefinedomain(args)
33 | end
34 |
35 | def define_command
36 | configuration[:xmlfile] = ARGV.shift if ARGV.size > 0
37 | configuration[:perm] = ARGV.shift if ARGV.size > 0
38 |
39 | raise "Need a XML file to define an instance" unless configuration[:xmlfile]
40 |
41 | args = {}
42 | if File.exist?(configuration[:xmlfile])
43 | args[:xml] = File.read(configuration[:xmlfile])
44 | else
45 | args[:xmlfile] = configuration[:xmlfile]
46 | end
47 |
48 | args[:permanent] = true if configuration[:perm].to_s =~ /^perm/
49 | args[:domain] = configuration[:domain]
50 |
51 | printrpc virtclient.definedomain(args)
52 | end
53 |
54 | def info_command
55 | if configuration[:domain]
56 | printrpc virtclient.domaininfo(:domain => configuration[:domain])
57 | else
58 | printrpc virtclient.hvinfo
59 | end
60 | end
61 |
62 | def xml_command
63 | printrpc virtclient.domainxml(:domain => configuration[:domain])
64 | end
65 |
66 | def domains_command
67 | virtclient.hvinfo.each do |r|
68 | if r[:statuscode] == 0
69 | domains = r[:data][:active_domains] << r[:data][:inactive_domains]
70 |
71 | puts "%30s: %s" % [r[:sender], domains.flatten.sort.join(", ")]
72 | else
73 | puts "%30s: %s" % [r[:sender], r[:statusmsg]]
74 | end
75 | end
76 |
77 | puts
78 | end
79 |
80 | def reboot_command
81 | printrpc virtclient.reboot(:domain => configuration[:domain])
82 | end
83 |
84 | def start_command
85 | printrpc virtclient.create(:domain => configuration[:domain])
86 | end
87 |
88 | def stop_command
89 | printrpc virtclient.shutdown(:domain => configuration[:domain])
90 | end
91 |
92 | def suspend_command
93 | printrpc virtclient.suspend(:domain => configuration[:domain])
94 | end
95 |
96 | def resume_command
97 | printrpc virtclient.resume(:domain => configuration[:domain])
98 | end
99 |
100 | def destroy_command
101 | printrpc virtclient.destroy(:domain => configuration[:domain])
102 | end
103 |
104 | def find_command
105 | pattern = Regexp.new(configuration[:domain])
106 |
107 | virtclient.hvinfo.each do |r|
108 | if r[:statuscode] == 0
109 | domains = r[:data][:active_domains] << r[:data][:inactive_domains]
110 | matched = domains.flatten.grep pattern
111 |
112 | if matched.size > 0
113 | puts "%30s: %s" % [r[:sender], matched.sort.join(", ")]
114 | end
115 | else
116 | puts "%30s: %s" % [r[:sender], r[:statusmsg]]
117 | end
118 | end
119 |
120 | puts
121 | end
122 |
123 | def virtclient
124 | @client ||= rpcclient("libvirt")
125 | end
126 |
127 | def main
128 | cmd = configuration[:command] + "_command"
129 |
130 | if respond_to?(cmd)
131 | send(cmd)
132 | else
133 | raise "Support for #{configuration[:command]} has not yet been implimented"
134 | end
135 | end
136 | end
137 |
--------------------------------------------------------------------------------
/agent/bench/agent/bench.ddl:
--------------------------------------------------------------------------------
1 | metadata :name => "bench",
2 | :description => "Manage multiple mcollectived instances on a single node",
3 | :author => "R.I.Pienaar",
4 | :license => "ASL 2.0",
5 | :version => "0.6",
6 | :url => "http://devco.net/",
7 | :timeout => 30
8 |
9 | action "destroy", :description => "Stop and destroy all members" do
10 | display :always
11 |
12 | output :instance_count,
13 | :description => "Number of managed instances",
14 | :display_as => "Instances"
15 |
16 | output :instances_running,
17 | :description => "Number of instances currently running",
18 | :display_as => "Running"
19 |
20 | output :instances_stopped,
21 | :description => "Number of instances currently stopped",
22 | :display_as => "Stopped"
23 |
24 | summarize do
25 | aggregate sum(:instance_count, :format => "Total Instances: %d")
26 | end
27 | end
28 |
29 | action "create_members", :description => "Create new member servers" do
30 | input :count,
31 | :prompt => "Instance Count",
32 | :description => "Number of instances to create",
33 | :type => :number,
34 | :optional => false
35 |
36 | input :activemq_host,
37 | :prompt => "ActiveMQ host",
38 | :description => "Connect to a specific ActiveMQ host",
39 | :type => :string,
40 | :validation => '^.+$',
41 | :maxlength => 50,
42 | :optional => true
43 |
44 | output :status,
45 | :description => "Command exit code",
46 | :display_as => "Exit Code"
47 |
48 | output :instance_count,
49 | :description => "Number of managed instances",
50 | :display_as => "Instances"
51 |
52 | output :instances_running,
53 | :description => "Number of instances currently running",
54 | :display_as => "Running"
55 |
56 | output :instances_stopped,
57 | :description => "Number of instances currently stopped",
58 | :display_as => "Stopped"
59 |
60 | summarize do
61 | aggregate sum(:instance_count, :format => "Total Instances: %d")
62 | end
63 | end
64 |
65 | action "list", :description => "Names of known member servers" do
66 | display :always
67 |
68 | output :members,
69 | :description => "Known collective members",
70 | :display_as => "Members"
71 | end
72 |
73 | action "status", :description => "Status of all known member servers" do
74 | display :always
75 |
76 | output :members,
77 | :description => "Known collective members",
78 | :display_as => "Members"
79 |
80 | output :instance_count,
81 | :description => "Number of managed instances",
82 | :display_as => "Instances"
83 |
84 | output :instances_running,
85 | :description => "Number of instances currently running",
86 | :display_as => "Running"
87 |
88 | output :instances_stopped,
89 | :description => "Number of instances currently stopped",
90 | :display_as => "Stopped"
91 |
92 | summarize do
93 | aggregate sum(:instance_count, :format => "Total Instances: %d")
94 | aggregate sum(:instances_running, :format => "Total Running Instances: %d")
95 | aggregate sum(:instances_stopped, :format => "Total Stopped Instances: %d")
96 | end
97 | end
98 |
99 | action "start", :description => "Start all known member servers" do
100 | display :always
101 |
102 | output :instance_count,
103 | :description => "Number of managed instances",
104 | :display_as => "Instances"
105 |
106 | output :instances_running,
107 | :description => "Number of instances currently running",
108 | :display_as => "Running"
109 |
110 | output :instances_stopped,
111 | :description => "Number of instances currently stopped",
112 | :display_as => "Stopped"
113 |
114 | summarize do
115 | aggregate sum(:instance_count, :format => "Total Instances: %d")
116 | aggregate sum(:instances_running, :format => "Total Running Instances: %d")
117 | aggregate sum(:instances_stopped, :format => "Total Stopped Instances: %d")
118 | end
119 | end
120 |
121 | action "stop", :description => "Stop all known member servers" do
122 | display :always
123 |
124 | output :instance_count,
125 | :description => "Number of managed instances",
126 | :display_as => "Instances"
127 |
128 | output :instances_running,
129 | :description => "Number of instances currently running",
130 | :display_as => "Running"
131 |
132 | output :instances_stopped,
133 | :description => "Number of instances currently stopped",
134 | :display_as => "Stopped"
135 |
136 | summarize do
137 | aggregate sum(:instance_count, :format => "Total Instances: %d")
138 | aggregate sum(:instances_running, :format => "Total Running Instances: %d")
139 | aggregate sum(:instances_stopped, :format => "Total Stopped Instances: %d")
140 | end
141 | end
142 |
--------------------------------------------------------------------------------
/agent/eximng/application/exim.rb:
--------------------------------------------------------------------------------
1 | class MCollective::Application::Exim"
5 | usage "mco exim [retry|markdelivered|freeze|thaw|giveup|rm] "
6 | usage "mco exim [addrecipient|markdelivered] "
7 | usage "mco exim setsender "
8 | usage "mco exim [mailq|size]"
9 | usage "mco exim test "
10 | usage "mco exim exigrep pattern"
11 |
12 | VALID_COMMANDS = ["mailq", "size", "summary", "exiwhat", "rmbounces", "rmfrozen", "runq", "addrecipient", "markdelivered", "setsender", "retry", "freeze", "thaw", "giveup", "rm", "delivermatching", "exigrep", "test"]
13 | MSGID_REQ_COMMANDS = ["setsender", "retry", "markdelivered", "freeze", "thaw", "giveup", "rm"]
14 | RECIP_OPT_COMMANDS = ["markdelivered"]
15 | RECIP_REQ_COMMANDS = ["addrecipient"]
16 |
17 | option :limit_sender,
18 | :description => "Match sender pattern",
19 | :arguments => ["--match-sender SENDER", "--limit-sender"],
20 | :required => false
21 |
22 | option :limit_recipient,
23 | :description => "Match recipient pattern",
24 | :arguments => ["--match-recipient RECIPIENT", "--limit-recipient"],
25 | :required => false
26 |
27 | option :limit_younger_than,
28 | :description => "Match younger than seconds",
29 | :arguments => ["--match-younger SECONDS", "--limit-younger"],
30 | :required => false
31 |
32 | option :limit_older_than,
33 | :description => "Match older than seconds",
34 | :arguments => ["--match-older SECONDS", "--limit-older"],
35 | :required => false
36 |
37 | option :limit_frozen_only,
38 | :description => "Match only frozen messages",
39 | :arguments => ["--match-frozen", "--limit-frozen"],
40 | :type => :bool,
41 | :required => false
42 |
43 | option :limit_unfrozen_only,
44 | :description => "Match only active messages",
45 | :arguments => ["--match-active", "--limit-active"],
46 | :type => :bool,
47 | :required => false
48 |
49 | def post_option_parser(configuration)
50 | configuration[:command] = ARGV.shift if ARGV.size > 0
51 |
52 | if MSGID_REQ_COMMANDS.include?(configuration[:command])
53 | if ARGV.size > 0
54 | configuration[:message_id] = ARGV[0]
55 | else
56 | raise "#{configuration[:command]} requires a message id"
57 | end
58 | end
59 |
60 | if RECIP_REQ_COMMANDS.include?(configuration[:command])
61 | if ARGV.size == 2
62 | configuration[:message_id] = ARGV[0]
63 | configuration[:recipient] = ARGV[1]
64 | else
65 | raise "#{configuration[:command]} requires a message id and recipient"
66 | end
67 | end
68 |
69 | if RECIP_OPT_COMMANDS.include?(configuration[:command])
70 | if ARGV.size == 2
71 | configuration[:recipient] = ARGV[1]
72 | end
73 | end
74 | end
75 |
76 | def validate_configuration(configuration)
77 | raise "Please specify a command, see --help for details" unless configuration[:command]
78 |
79 | raise "Unknown command #{configuration[:command]}, see --help for full help" unless VALID_COMMANDS.include?(configuration[:command])
80 |
81 | if configuration.include?(:message_id)
82 | raise "Invalid message id format for id #{configuration[:message_id]}" unless configuration[:message_id] =~ /^\w+-\w+-\w+$/
83 | end
84 | end
85 |
86 | def exigrep_command(util)
87 | if ARGV.empty?
88 | raise("The exigrep command requires a pattern")
89 | else
90 | configuration[:pattern] = ARGV.first
91 | end
92 |
93 | puts util.exigrep(configuration)
94 | end
95 |
96 | def test_command(util)
97 | if ARGV.empty?
98 | raise("The test command requires an address")
99 | else
100 | configuration[:address] = ARGV.first
101 | end
102 |
103 | puts util.test(configuration)
104 | end
105 |
106 | def runq_command(util)
107 | unless ARGV.empty?
108 | configuration[:pattern] = ARGV.first
109 | end
110 |
111 | puts util.runq(configuration)
112 | end
113 |
114 | def setsender_command(util)
115 | if ARGV.size == 2
116 | configuration[:sender] = ARGV.first
117 | else
118 | raise "Please supply a sender"
119 | end
120 |
121 | puts util.setsender(configuration)
122 | end
123 |
124 | def main
125 | MCollective::Util.loadclass("MCollective::Util::EximNG")
126 |
127 | mc = rpcclient("eximng", :options => options)
128 | util = MCollective::Util::EximNG.new(mc)
129 |
130 | cmd = "#{configuration[:command]}_command"
131 |
132 | # if there are local foo_command methods, use that to
133 | # render foo, else use M::U::EximNG#foo else fail
134 | if respond_to?(cmd)
135 | send(cmd, util)
136 | elsif util.respond_to?(configuration[:command])
137 | puts util.send(configuration[:command], configuration)
138 | else
139 | raise "Support for #{configuration[:command]} has not yet been implimented"
140 | end
141 |
142 | puts
143 |
144 | mc.disconnect
145 | end
146 | end
147 |
--------------------------------------------------------------------------------
/agent/bench/README.md:
--------------------------------------------------------------------------------
1 | What?
2 | =====
3 |
4 | A MCollective agent that can be deployed into an existing mcollective
5 | setup and be used to start up multiple instances of mcollective on
6 | each node.
7 |
8 | The purpose is to assist in scale testing middleware, given 100 VMs
9 | you could comfortably start up to 1000 or even 1500 mcollective
10 | instances.
11 |
12 |
13 | Creating Instances?
14 | -------------------
15 |
16 | Prior to creating instances we need to be sure there are no previous
17 | instances running:
18 |
19 | $ mco rpc bench destroy
20 | Discovering hosts using the mc method for 2 second(s) .... 1
21 |
22 | * [ ============================================================> ] 1 / 1
23 |
24 |
25 | master.example.com
26 | Instances: 0
27 | Running: 0
28 | Stopped: 0
29 |
30 | Summary of Instances:
31 |
32 | Total Instances: 0
33 |
34 | Finished processing 1 / 1 hosts in 75.92 ms
35 |
36 | You're now ready to create new instances which will share libdir,
37 | brokers etc all with your original mcollectived:
38 |
39 | $ mco rpc bench create_members count=10
40 | Discovering hosts using the mc method for 2 second(s) .... 1
41 |
42 | * [ ============================================================> ] 1 / 1
43 |
44 | Summary of Instances:
45 |
46 | Total Instances: 10
47 |
48 | Finished processing 1 / 1 hosts in 94.48 ms
49 |
50 | I recommend you test against a different ActiveMQ instance though so that
51 | when you reach the capacity limit of your ActiveMQ instance under test you
52 | can use the first one to destroy the bench collective:
53 |
54 | $ mco rpc bench create_members count=10 activemq_host=another.host.example.net
55 |
56 | Instance Status?
57 | ----------------
58 |
59 | You can figure out the status of your bench instances:
60 |
61 | $ mco rpc bench status
62 | Discovering hosts using the mc method for 2 second(s) .... 1
63 |
64 | * [ ============================================================> ] 1 / 1
65 |
66 |
67 | master.example.com
68 | Instances: 10
69 | Running: 0
70 | Stopped: 10
71 | Members: [{:name=>"master.example.com-2", :pid=>nil},
72 | {:name=>"master.example.com-8", :pid=>nil},
73 | {:name=>"master.example.com-1", :pid=>nil},
74 | {:name=>"master.example.com-7", :pid=>nil},
75 | {:name=>"master.example.com-6", :pid=>nil},
76 | {:name=>"master.example.com-3", :pid=>nil},
77 | {:name=>"master.example.com-5", :pid=>nil},
78 | {:name=>"master.example.com-9", :pid=>nil},
79 | {:name=>"master.example.com-4", :pid=>nil},
80 | {:name=>"master.example.com-0", :pid=>nil}]
81 |
82 | Summary of Instances:
83 |
84 | Total Instances: 10
85 |
86 | Summary of Running:
87 |
88 | Total Running Instances: 0
89 |
90 | Summary of Stopped:
91 |
92 | Total Stopped Instances: 10
93 |
94 | Finished processing 1 / 1 hosts in 84.50 ms
95 |
96 | Here they are all created but none of them are running.
97 |
98 | Starting the instances?
99 | -----------------------
100 |
101 | Starting instances take a while because we sleep a bit between starting each
102 | one...
103 |
104 | $ mco rpc bench start
105 | Discovering hosts using the mc method for 2 second(s) .... 1
106 |
107 | * [ ============================================================> ] 1 / 1
108 |
109 |
110 | master.example.com
111 | Instances: 10
112 | Running: 10
113 | Stopped: 0
114 |
115 | Summary of Instances:
116 |
117 | Total Instances: 10
118 |
119 | Summary of Running:
120 |
121 | Total Running Instances: 10
122 |
123 | Summary of Stopped:
124 |
125 | Total Stopped Instances: 0
126 |
127 | Finished processing 1 / 1 hosts in 9627.61 ms
128 |
129 | If you set them up to run against the same ActiveMQ you should be able to
130 | 'mco ping' them, if on another ActiveMQ you should create a specific client.cfg
131 | file that points to that ActiveMQ at which point you should be able to ping the
132 | new nodes:
133 |
134 | $ mco ping
135 | master.example.com-2 time=333.55 ms
136 | master.example.com-1 time=364.56 ms
137 | master.example.com time=368.11 ms
138 | master.example.com-7 time=369.92 ms
139 | master.example.com-8 time=371.39 ms
140 | master.example.com-6 time=372.80 ms
141 | master.example.com-3 time=378.14 ms
142 | master.example.com-0 time=379.62 ms
143 | master.example.com-9 time=381.37 ms
144 | master.example.com-5 time=382.68 ms
145 | master.example.com-4 time=383.93 ms
146 |
147 | Here we share a single ActiveMQ so both the original and the bench instances show
148 | up
149 |
150 | Stopping the test?
151 | ------------------
152 |
153 | Simply destroy the bench suite to get back where you started:
154 |
155 |
156 | $ mco rpc bench destroy
157 | Discovering hosts using the mc method for 2 second(s) .... 1
158 |
159 | * [ ============================================================> ] 1 / 1
160 |
161 |
162 | master.example.com
163 | Instances: 0
164 | Running: 0
165 | Stopped: 0
166 |
167 | Summary of Instances:
168 |
169 | Total Instances: 0
170 |
171 | Finished processing 1 / 1 hosts in 75.92 ms
172 |
--------------------------------------------------------------------------------
/application/plot/application/plot.rb:
--------------------------------------------------------------------------------
1 | module MCollective
2 | class Application::Plot "Sets a title for the X axis",
35 | :arguments => ["--xtitle [TITLE]"]
36 |
37 | option :y_title,
38 | :description => "Sets a title for the Y axis",
39 | :arguments => ["--ytitle [TITLE]"]
40 |
41 |
42 | option :title,
43 | :description => "Sets the graph title",
44 | :arguments => ["--title [TITLE]"]
45 |
46 | option :buckets,
47 | :description => "How many buckets to group nodes into",
48 | :arguments => ["--buckets [COUNT]"],
49 | :default => 20,
50 | :type => Integer
51 |
52 | option :width,
53 | :description => "Set the graph width in characters",
54 | :arguments => ["--width [WIDTH]"],
55 | :default => 78,
56 | :type => Integer
57 |
58 | option :height,
59 | :description => "Set the graph width in characters",
60 | :arguments => ["--height [HEIGHT]"],
61 | :default => 24,
62 | :type => Integer
63 |
64 | def post_option_parser(configuration)
65 | raise "Please specify a data plugin, query and field to plot" unless ARGV.size >= 2
66 |
67 | if ARGV.size == 2
68 | configuration[:datasource] = ARGV.shift
69 | configuration[:field] = ARGV.shift
70 | elsif ARGV.size == 3
71 | configuration[:datasource] = ARGV.shift
72 | configuration[:query] = ARGV.shift
73 | configuration[:field] = ARGV.shift
74 | end
75 | end
76 |
77 | def validate_configuration(configuration)
78 | raise "Cannot find the 'gnuplot' executable" unless configuration[:gnuplot] = which("gnuplot")
79 | end
80 |
81 | def data_for_field(results, field)
82 | bucket_count = configuration[:buckets]
83 |
84 | buckets = Array.new(bucket_count + 1) { 0 }
85 | values = []
86 |
87 | results.each do |result|
88 | if result[:statuscode] == 0
89 | begin
90 | values << Float(result[:data][field])
91 | rescue => e
92 | raise "Cannot interpret data item '%s': %s" % [result[:data][field], e.to_s]
93 | end
94 | end
95 | end
96 |
97 | raise "No usable data results were found" if values.empty?
98 |
99 | min = values.min
100 | max = values.max
101 |
102 | bucket_size = (max - min) / Float(bucket_count)
103 |
104 | unless max == min
105 | values.each do |value|
106 | bucket = (value - min) / bucket_size
107 | buckets[bucket] += 1
108 | end
109 | end
110 |
111 | range = Array.new(bucket_count + 1) {|i| Integer(min + (i * bucket_size))}
112 |
113 | [range, buckets]
114 | end
115 |
116 | def which (bin)
117 | if Util.windows?
118 | all = [bin, bin + '.exe']
119 | else
120 | all = [bin]
121 | end
122 |
123 | all.each do |exec|
124 | if which_helper(exec)
125 | return which_helper(exec)
126 | end
127 | end
128 |
129 | return nil
130 | end
131 |
132 | def which_helper(bin)
133 | return bin if File::executable?(bin)
134 |
135 | ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
136 | candidate = File::join(dir, bin.strip)
137 | return candidate if File::executable?(candidate)
138 | end
139 | return nil
140 | end
141 |
142 | def main
143 | client = rpcclient("rpcutil")
144 |
145 | args = {:source => configuration[:datasource]}
146 | args[:query] = configuration[:query] if configuration[:query]
147 |
148 | ddl = DDL.new("%s_data" % configuration[:datasource], :data)
149 |
150 | x, data = data_for_field(client.get_data(args), configuration[:field].to_sym)
151 |
152 | plot = StringIO.new
153 |
154 | plot.puts 'set title "%s"' % configuration.fetch(:title, ddl.meta[:description])
155 | plot.puts 'set terminal dumb %d %d' % [configuration[:width], configuration[:height]]
156 | plot.puts 'set key off'
157 | plot.puts 'set ylabel "%s"' % configuration.fetch(:y_title, "Nodes")
158 | plot.puts 'set xlabel "%s"' % configuration.fetch(:x_title, ddl.dataquery_interface[:output][configuration[:field].to_sym][:display_as])
159 | plot.puts "plot '-' with lines"
160 |
161 | x.each_with_index do |v, i|
162 | plot.puts "%s %s" % [v, data[i]]
163 | end
164 |
165 | output = ""
166 |
167 | begin
168 | IO::popen(configuration[:gnuplot], "w+") do |io|
169 | io.write plot.string
170 | io.close_write
171 | output = io.read
172 | end
173 | rescue => e
174 | raise "Could not plot results: %s" % e.to_s
175 | end
176 |
177 | puts output
178 |
179 | halt client.stats
180 | end
181 | end
182 | end
183 |
--------------------------------------------------------------------------------
/agent/rndc/README.md:
--------------------------------------------------------------------------------
1 | RNDC AGENT
2 | ===========
3 |
4 | SimpleRPC RNDC Agent
5 |
6 | Author: R.I.Pienaar
7 | Version: 0.1
8 | License: ASL2.0
9 | Timeout: 5
10 | Home Page: http://www.devco.net/
11 |
12 |
13 |
14 | ACTIONS:
15 | ========
16 | * freeze
17 | * notify
18 | * querylog
19 | * reconfig
20 | * refresh
21 | * reload
22 | * retransfer
23 | * sign
24 | * status
25 | * thaw
26 |
27 | _freeze_ action:
28 | --------------
29 | Freeze a zone or all zones
30 |
31 | INPUT:
32 | zone:
33 | Description: Zone to act on
34 | Prompt: Zone
35 | Type: string
36 | Validation: ^.+$
37 | Length: 100
38 |
39 |
40 | OUTPUT:
41 | err:
42 | Description: STDERR output
43 | Display As: Error
44 |
45 | out:
46 | Description: STDOUT output
47 | Display As: Output
48 |
49 | _notify_ action:
50 | --------------
51 | Notify a zone
52 |
53 | INPUT:
54 | zone:
55 | Description: Zone to act on
56 | Prompt: Zone
57 | Type: string
58 | Validation: ^.+$
59 | Length: 100
60 |
61 |
62 | OUTPUT:
63 | err:
64 | Description: STDERR output
65 | Display As: Error
66 |
67 | out:
68 | Description: STDOUT output
69 | Display As: Output
70 |
71 | _querylog_ action:
72 | ----------------
73 | Toggles the server wide querylog
74 |
75 |
76 | OUTPUT:
77 | err:
78 | Description: STDERR output
79 | Display As: Error
80 |
81 | out:
82 | Description: STDOUT output
83 | Display As: Output
84 |
85 | _reconfig_ action:
86 | ----------------
87 | Reloads the server configuration
88 |
89 |
90 | OUTPUT:
91 | err:
92 | Description: STDERR output
93 | Display As: Error
94 |
95 | out:
96 | Description: STDOUT output
97 | Display As: Output
98 |
99 | _refresh_ action:
100 | ---------------
101 | Refresh a zone
102 |
103 | INPUT:
104 | zone:
105 | Description: Zone to act on
106 | Prompt: Zone
107 | Type: string
108 | Validation: ^.+$
109 | Length: 100
110 |
111 |
112 | OUTPUT:
113 | err:
114 | Description: STDERR output
115 | Display As: Error
116 |
117 | out:
118 | Description: STDOUT output
119 | Display As: Output
120 |
121 | _reload_ action:
122 | --------------
123 | Reload a zone or all zones
124 |
125 | INPUT:
126 | zone:
127 | Description: Zone to act on
128 | Prompt: Zone
129 | Type: string
130 | Validation: ^.+$
131 | Length: 100
132 |
133 |
134 | OUTPUT:
135 | err:
136 | Description: STDERR output
137 | Display As: Error
138 |
139 | out:
140 | Description: STDOUT output
141 | Display As: Output
142 |
143 | _retransfer_ action:
144 | ------------------
145 | Retransfer a zone
146 |
147 | INPUT:
148 | zone:
149 | Description: Zone to act on
150 | Prompt: Zone
151 | Type: string
152 | Validation: ^.+$
153 | Length: 100
154 |
155 |
156 | OUTPUT:
157 | err:
158 | Description: STDERR output
159 | Display As: Error
160 |
161 | out:
162 | Description: STDOUT output
163 | Display As: Output
164 |
165 | _sign_ action:
166 | ------------
167 | Sign a zone
168 |
169 | INPUT:
170 | zone:
171 | Description: Zone to act on
172 | Prompt: Zone
173 | Type: string
174 | Validation: ^.+$
175 | Length: 100
176 |
177 |
178 | OUTPUT:
179 | err:
180 | Description: STDERR output
181 | Display As: Error
182 |
183 | out:
184 | Description: STDOUT output
185 | Display As: Output
186 |
187 | _status_ action:
188 | --------------
189 | Gather server status information
190 |
191 |
192 | OUTPUT:
193 | cpus_found:
194 | Description: Number of CPUs Found
195 | Display As: CPUs
196 |
197 | debug_level:
198 | Description: Active debug level
199 | Display As: Debug Level
200 |
201 | number_of_zones:
202 | Description: Number of zones
203 | Display As: Zones
204 |
205 | recursive_clients:
206 | Description: Recursive Clients
207 | Display As: Recursive Clients
208 |
209 | soa_queries_in_progress:
210 | Description: Active SOA queries
211 | Display As: SOA Queries in Progress
212 |
213 | tcp_clients:
214 | Description: TCP Clients
215 | Display As: TCP Clients
216 |
217 | version:
218 | Description: Server Version
219 | Display As: Version
220 |
221 | worker_threads:
222 | Description: Number of Worker Threads
223 | Display As: Worker Threads
224 |
225 | xfers_deferred:
226 | Description: Number of Xfers deferred
227 | Display As: Xfers Deferred
228 |
229 | xfers_running:
230 | Description: Active transfers
231 | Display As: Xfers Running
232 |
233 | _thaw_ action:
234 | ------------
235 | Thaw a zone or all zones
236 |
237 | INPUT:
238 | zone:
239 | Description: Zone to act on
240 | Prompt: Zone
241 | Type: string
242 | Validation: ^.+$
243 | Length: 100
244 |
245 |
246 | OUTPUT:
247 | err:
248 | Description: STDERR output
249 | Display As: Error
250 |
251 | out:
252 | Description: STDOUT output
253 | Display As: Output
254 |
255 |
--------------------------------------------------------------------------------
/agent/bench/agent/bench.rb:
--------------------------------------------------------------------------------
1 | module MCollective
2 | module Agent
3 | class Bench member, :pid => get_member_pid(member)}
77 | end
78 |
79 | instances_stats
80 | end
81 |
82 | action "start" do
83 | start_all_members
84 | instances_stats
85 | end
86 |
87 | action "stop" do
88 | stop_all_members
89 | instances_stats
90 | end
91 |
92 | def instances_stats
93 | reply[:instance_count] = get_members.size
94 |
95 | reply[:instances_running] = get_members.map do |member|
96 | get_member_pid(member)
97 | end.compact.size
98 |
99 | reply[:instances_stopped] = reply[:instance_count] - reply[:instances_running]
100 | end
101 |
102 | def start_all_members
103 | get_members.each do |member|
104 | start_member(member)
105 | sleep 0.2
106 | end
107 | end
108 |
109 | def stop_all_members
110 | get_members.each do |member|
111 | stop_member(member)
112 | end
113 | end
114 |
115 | def stop_member(identity)
116 | pid = member_running?(identity)
117 | if pid
118 | Log.info("Stopping collective member #{identity} with pid #{pid}")
119 | ::Process.kill(2, Integer(pid))
120 | FileUtils.rm(get_member_pid(identity)) rescue nil
121 | end
122 | end
123 |
124 | def start_member(identity)
125 | reply.fail! "You need to create a collective first using rake create" if get_members.size == 0
126 |
127 | return true if member_running?(identity)
128 |
129 | memberdir = member_path(identity)
130 | pid = pid_path(identity)
131 | libdir = @config.libdir.join(":")
132 | config = File.join(memberdir, "etc", "server.cfg")
133 |
134 | cmd = "#{@ruby} -I #{libdir} #{@mcollectived} --config #{config} --pidfile #{pid}"
135 |
136 | Log.info("Starting member #{identity} with #{cmd} in #{memberdir}")
137 |
138 | status = run(cmd, :cwd => memberdir)
139 | end
140 |
141 | def member_running?(identity)
142 | pid = get_member_pid(identity)
143 |
144 | return false unless pid
145 |
146 | if File.directory?("/proc")
147 | return Integer(pid) if File.exist?("/proc/#{pid}")
148 | end
149 |
150 | false
151 | end
152 |
153 | def get_member_pid(identity)
154 | pidfile = pid_path(identity)
155 |
156 | return nil unless File.exist?(pidfile)
157 |
158 | Integer(File.read(pidfile))
159 | end
160 |
161 | def get_members
162 | Dir.entries(@collectivedir).reject{|f| f.start_with?(".")}
163 | rescue
164 | []
165 | end
166 |
167 | def pid_path(identity)
168 | File.join(@pidsdir, "#{identity}.pid")
169 | end
170 |
171 | def member_path(identity)
172 | File.join(@collectivedir, identity)
173 | end
174 |
175 | def create_member(identity, logfile, brokerhost)
176 | memberdir = member_path(identity)
177 | memberconfig = File.join(memberdir, "etc", "server.cfg")
178 |
179 | FileUtils.mkdir_p(File.join(memberdir, "etc"))
180 |
181 | Log.info("Creating member %s in %s" % [identity, memberdir])
182 |
183 | render_config(@server_config, memberconfig, identity, logfile, brokerhost)
184 | end
185 |
186 | def render_config(source, dest, identity, log, brokerhost)
187 | source_lines = File.readlines(source)
188 |
189 | File.open(dest, "w") do |f|
190 | source_lines.each do |line|
191 | if line =~ /^identity/
192 | line = "identity = %s" % identity
193 | elsif line =~ /^logfile/
194 | line = "logfile = %s" % log
195 | elsif line =~ /plugin.activemq.pool.1.host/
196 | line = "plugin.activemq.pool.1.host = %s" % brokerhost
197 | end
198 |
199 | f.puts line
200 | end
201 |
202 | f.puts "plugin.bench.activate_agent = false"
203 | end
204 | end
205 | end
206 | end
207 | end
208 |
--------------------------------------------------------------------------------
/agent/collective/agent/collective.ddl:
--------------------------------------------------------------------------------
1 | metadata :name => "collective",
2 | :description => "Manage multiple mcollectived instances on a single node",
3 | :author => "R.I.Pienaar",
4 | :license => "ASL 2.0",
5 | :version => "0.6",
6 | :url => "http://devco.net/",
7 | :timeout => 120
8 |
9 | action "destroy", :description => "Stop and destroy all members" do
10 | display :always
11 |
12 | output :instance_count,
13 | :description => "Number of managed instances",
14 | :display_as => "Instances"
15 |
16 | output :instances_running,
17 | :description => "Number of instances currently running",
18 | :display_as => "Running"
19 |
20 | output :instances_stopped,
21 | :description => "Number of instances currently stopped",
22 | :display_as => "Stopped"
23 | end
24 |
25 | action "clone_source_repo", :description => "Clone a git repo to use as source for the collective" do
26 | input :gitrepo,
27 | :prompt => "Git Repository",
28 | :description => "Any valid git repository path",
29 | :type => :string,
30 | :validation => '^.+$',
31 | :optional => false,
32 | :maxlength => 120
33 |
34 | input :branch,
35 | :prompt => "Branch",
36 | :description => "Branch to check out",
37 | :type => :string,
38 | :validation => '^.+$',
39 | :optional => false,
40 | :maxlength => 50
41 |
42 | output :status,
43 | :description => "Command exit code",
44 | :display_as => "Exit Code"
45 | end
46 |
47 | action "create_members", :description => "Create new member servers" do
48 | input :count,
49 | :prompt => "Instance Count",
50 | :description => "Number of instances to create",
51 | :type => :number,
52 | :optional => false
53 |
54 | input :version,
55 | :prompt => "Version",
56 | :description => "Git tag to check out",
57 | :type => :string,
58 | :validation => '^.+$',
59 | :optional => false,
60 | :maxlength => 50
61 |
62 | input :colllective,
63 | :prompt => "Main Collective",
64 | :description => "The main collective the instances will belong to",
65 | :type => :string,
66 | :validation => '^\w+$',
67 | :optional => false,
68 | :maxlength => 20
69 |
70 | input :subcollective,
71 | :prompt => "Subcollectives",
72 | :description => "Comma seperated list of sub collectives",
73 | :type => :string,
74 | :validation => '^[\w,]+$',
75 | :optional => false,
76 | :maxlength => 100
77 |
78 | input :server,
79 | :prompt => "ActiveMQ Server",
80 | :description => "ActiveMQ broker to connect to",
81 | :type => :string,
82 | :validation => '^[\w\.,]+$',
83 | :optional => false,
84 | :maxlength => 50
85 |
86 | input :port,
87 | :prompt => "ActiveMQ Port",
88 | :description => "ActiveMQ broker port to connect to",
89 | :type => :integer,
90 | :validation => '^\d+$',
91 | :optional => false,
92 | :maxlength => 20
93 |
94 | input :user,
95 | :prompt => "ActiveMQ User",
96 | :description => "User to use when connecting to the broker",
97 | :type => :string,
98 | :validation => '^.+$',
99 | :optional => false,
100 | :maxlength => 20
101 |
102 | input :password,
103 | :prompt => "ActiveMQ Password",
104 | :description => "Password to use when connecting to the broker",
105 | :type => :string,
106 | :validation => '^.+$',
107 | :optional => false,
108 | :maxlength => 20
109 |
110 | output :status,
111 | :description => "Command exit code",
112 | :display_as => "Exit Code"
113 |
114 | output :instance_count,
115 | :description => "Number of managed instances",
116 | :display_as => "Instances"
117 |
118 | output :instances_running,
119 | :description => "Number of instances currently running",
120 | :display_as => "Running"
121 |
122 | output :instances_stopped,
123 | :description => "Number of instances currently stopped",
124 | :display_as => "Stopped"
125 | end
126 |
127 | action "list", :description => "Names of known member servers" do
128 | display :always
129 |
130 | output :members,
131 | :description => "Known collective members",
132 | :display_as => "Members"
133 | end
134 |
135 | action "status", :description => "Status of all known member servers" do
136 | display :always
137 |
138 | output :members,
139 | :description => "Known collective members",
140 | :display_as => "Members"
141 |
142 | output :instance_count,
143 | :description => "Number of managed instances",
144 | :display_as => "Instances"
145 |
146 | output :instances_running,
147 | :description => "Number of instances currently running",
148 | :display_as => "Running"
149 |
150 | output :instances_stopped,
151 | :description => "Number of instances currently stopped",
152 | :display_as => "Stopped"
153 | end
154 |
155 | action "start", :description => "Start all known member servers" do
156 | display :always
157 |
158 | output :instance_count,
159 | :description => "Number of managed instances",
160 | :display_as => "Instances"
161 |
162 | output :instances_running,
163 | :description => "Number of instances currently running",
164 | :display_as => "Running"
165 |
166 | output :instances_stopped,
167 | :description => "Number of instances currently stopped",
168 | :display_as => "Stopped"
169 | end
170 |
171 | action "stop", :description => "Stop all known member servers" do
172 | display :always
173 |
174 | output :instance_count,
175 | :description => "Number of managed instances",
176 | :display_as => "Instances"
177 |
178 | output :instances_running,
179 | :description => "Number of instances currently running",
180 | :display_as => "Running"
181 |
182 | output :instances_stopped,
183 | :description => "Number of instances currently stopped",
184 | :display_as => "Stopped"
185 | end
186 |
--------------------------------------------------------------------------------
/agent/eximng/web/public/js/bootstrap-modal.js:
--------------------------------------------------------------------------------
1 | /* =========================================================
2 | * bootstrap-modal.js v1.3.0
3 | * http://twitter.github.com/bootstrap/javascript.html#modal
4 | * =========================================================
5 | * Copyright 2011 Twitter, Inc.
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================= */
19 |
20 |
21 | !function( $ ){
22 |
23 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
24 | * ======================================================= */
25 |
26 | var transitionEnd
27 |
28 | $(document).ready(function () {
29 |
30 | $.support.transition = (function () {
31 | var thisBody = document.body || document.documentElement
32 | , thisStyle = thisBody.style
33 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
34 | return support
35 | })()
36 |
37 | // set CSS transition event type
38 | if ( $.support.transition ) {
39 | transitionEnd = "TransitionEnd"
40 | if ( $.browser.webkit ) {
41 | transitionEnd = "webkitTransitionEnd"
42 | } else if ( $.browser.mozilla ) {
43 | transitionEnd = "transitionend"
44 | } else if ( $.browser.opera ) {
45 | transitionEnd = "oTransitionEnd"
46 | }
47 | }
48 |
49 | })
50 |
51 |
52 | /* MODAL PUBLIC CLASS DEFINITION
53 | * ============================= */
54 |
55 | var Modal = function ( content, options ) {
56 | this.settings = $.extend({}, $.fn.modal.defaults)
57 | this.$element = $(content)
58 | .delegate('.close', 'click.modal', $.proxy(this.hide, this))
59 |
60 | if ( options ) {
61 | $.extend( this.settings, options )
62 |
63 | if ( options.show ) {
64 | this.show()
65 | }
66 | }
67 |
68 | return this
69 | }
70 |
71 | Modal.prototype = {
72 |
73 | toggle: function () {
74 | return this[!this.isShown ? 'show' : 'hide']()
75 | }
76 |
77 | , show: function () {
78 | var that = this
79 | this.isShown = true
80 | this.$element.trigger('show')
81 |
82 | escape.call(this)
83 | backdrop.call(this, function () {
84 | that.$element
85 | .appendTo(document.body)
86 | .show()
87 |
88 | if ($.support.transition && that.$element.hasClass('fade')) {
89 | that.$element[0].offsetWidth // force reflow
90 | }
91 |
92 | that.$element
93 | .addClass('in')
94 | .trigger('shown')
95 | })
96 |
97 | return this
98 | }
99 |
100 | , hide: function (e) {
101 | e && e.preventDefault()
102 |
103 | var that = this
104 | this.isShown = false
105 |
106 | escape.call(this)
107 |
108 | this.$element
109 | .trigger('hide')
110 | .removeClass('in')
111 |
112 | function removeElement () {
113 | that.$element
114 | .hide()
115 | .trigger('hidden')
116 |
117 | backdrop.call(that)
118 | }
119 |
120 | $.support.transition && this.$element.hasClass('fade') ?
121 | this.$element.one(transitionEnd, removeElement) :
122 | removeElement()
123 |
124 | return this
125 | }
126 |
127 | }
128 |
129 |
130 | /* MODAL PRIVATE METHODS
131 | * ===================== */
132 |
133 | function backdrop ( callback ) {
134 | var that = this
135 | , animate = this.$element.hasClass('fade') ? 'fade' : ''
136 | if ( this.isShown && this.settings.backdrop ) {
137 | var doAnimate = $.support.transition && animate
138 |
139 | this.$backdrop = $('')
140 | .appendTo(document.body)
141 |
142 | if ( this.settings.backdrop != 'static' ) {
143 | this.$backdrop.click($.proxy(this.hide, this))
144 | }
145 |
146 | if ( doAnimate ) {
147 | this.$backdrop[0].offsetWidth // force reflow
148 | }
149 |
150 | this.$backdrop.addClass('in')
151 |
152 | doAnimate ?
153 | this.$backdrop.one(transitionEnd, callback) :
154 | callback()
155 |
156 | } else if ( !this.isShown && this.$backdrop ) {
157 | this.$backdrop.removeClass('in')
158 |
159 | function removeElement() {
160 | that.$backdrop.remove()
161 | that.$backdrop = null
162 | }
163 |
164 | $.support.transition && this.$element.hasClass('fade')?
165 | this.$backdrop.one(transitionEnd, removeElement) :
166 | removeElement()
167 | } else if ( callback ) {
168 | callback()
169 | }
170 | }
171 |
172 | function escape() {
173 | var that = this
174 | if ( this.isShown && this.settings.keyboard ) {
175 | $(document).bind('keyup.modal', function ( e ) {
176 | if ( e.which == 27 ) {
177 | that.hide()
178 | }
179 | })
180 | } else if ( !this.isShown ) {
181 | $(document).unbind('keyup.modal')
182 | }
183 | }
184 |
185 |
186 | /* MODAL PLUGIN DEFINITION
187 | * ======================= */
188 |
189 | $.fn.modal = function ( options ) {
190 | var modal = this.data('modal')
191 |
192 | if (!modal) {
193 |
194 | if (typeof options == 'string') {
195 | options = {
196 | show: /show|toggle/.test(options)
197 | }
198 | }
199 |
200 | return this.each(function () {
201 | $(this).data('modal', new Modal(this, options))
202 | })
203 | }
204 |
205 | if ( options === true ) {
206 | return modal
207 | }
208 |
209 | if ( typeof options == 'string' ) {
210 | modal[options]()
211 | } else if ( modal ) {
212 | modal.toggle()
213 | }
214 |
215 | return this
216 | }
217 |
218 | $.fn.modal.Modal = Modal
219 |
220 | $.fn.modal.defaults = {
221 | backdrop: false
222 | , keyboard: false
223 | , show: true
224 | }
225 |
226 |
227 | /* MODAL DATA- IMPLEMENTATION
228 | * ========================== */
229 |
230 | $(document).ready(function () {
231 | $('body').delegate('[data-controls-modal]', 'click', function (e) {
232 | e.preventDefault()
233 | var $this = $(this).data('show', true)
234 | $('#' + $this.attr('data-controls-modal')).modal( $this.data() )
235 | })
236 | })
237 |
238 | }( window.jQuery || window.ender );
--------------------------------------------------------------------------------
/connector/redis/redis.rb:
--------------------------------------------------------------------------------
1 | require 'redis'
2 | require 'ostruct'
3 |
4 | module MCollective
5 | module Connector
6 | # A basic connector for mcollective using Redis.
7 | #
8 | # It is not aimed at large deployments more aimed as a getting
9 | # starter / testing style setup which would be easier for new
10 | # users to evaluate mcollective
11 | #
12 | # It supports direct addressing and sub collectives
13 | #
14 | # We'd also add a registration plugin for it and a discovery
15 | # plugin which means we can give a very solid fast first-user
16 | # experience using this
17 | #
18 | # Configure it with:
19 | #
20 | # plugin.redis.host = localhost
21 | # plugin.redis.port = 6379
22 | # plugin.redis.db = 1
23 | # plugin.redis.password = someStrongPassword (this is optional)
24 | class Redis @host, :port => @port, :db => @db}
75 | unless @password.nil?
76 | redis_opts.store(:password, @password)
77 | end
78 |
79 | Log.debug("Connecting to redis: %s" % redis_opts.inspect)
80 |
81 | @receiver_redis = ::Redis.new(redis_opts)
82 | @receiver_queue = ThreadsafeQueue.new
83 | @receiver_thread = nil
84 |
85 | @sender_redis = ::Redis.new(redis_opts)
86 | @sender_queue = ThreadsafeQueue.new
87 | @sender_thread = nil
88 |
89 | start_sender_thread
90 | end
91 |
92 | def subscribe(agent, type, collective)
93 | unless @subscribed
94 | if PluginManager["security_plugin"].initiated_by == :client
95 | @sources << "mcollective::reply::%s::%d" % [@config.identity, $$]
96 | else
97 | @config.collectives.each do |collective|
98 | @sources << "%s::server::direct::%s" % [collective, @config.identity]
99 | @sources << "%s::server::agents" % collective
100 | end
101 | end
102 |
103 | @subscribed = true
104 | start_receiver_thread(@sources)
105 | end
106 | end
107 |
108 | def unsubscribe(agent, type, collective); end
109 | def disconnect; end
110 |
111 | def receive
112 | msg = @receiver_queue.pop
113 | Message.new(msg[:body], msg, :headers => msg[:headers])
114 | end
115 |
116 | def publish(msg)
117 | Log.debug("About to publish to the sender queue")
118 |
119 | target = {:name => nil, :headers => {}, :name => nil}
120 |
121 | if msg.type == :direct_request
122 | msg.discovered_hosts.each do |node|
123 | target[:name] = "%s::server::direct::%s" % [msg.collective, node]
124 | target[:headers]["reply-to"] = "mcollective::reply::%s::%d" % [@config.identity, $$]
125 |
126 | Log.debug("Sending a direct message to Redis target '#{target[:name]}' with headers '#{target[:headers].inspect}'")
127 |
128 | @sender_queue << {:channel => target[:name],
129 | :body => msg.payload,
130 | :headers => target[:headers],
131 | :command => :publish}
132 | end
133 | else
134 | if msg.type == :reply
135 | target[:name] = msg.request.headers["reply-to"]
136 |
137 | elsif msg.type == :request
138 | target[:name] = "%s::server::agents" % msg.collective
139 | target[:headers]["reply-to"] = "mcollective::reply::%s::%d" % [@config.identity, $$]
140 | end
141 |
142 |
143 | Log.debug("Sending a broadcast message to Redis target '#{target[:name]}' with headers '#{target[:headers].inspect}'")
144 |
145 | @sender_queue << {:channel => target[:name],
146 | :body => msg.payload,
147 | :headers => target[:headers],
148 | :command => :publish}
149 | end
150 | end
151 |
152 | def start_receiver_thread(sources)
153 | @receiver_thread = Thread.new do
154 | begin
155 | @receiver_redis.subscribe(@sources) do |on|
156 | on.subscribe do |channel, subscriptions|
157 | Log.debug("Subscribed to %s" % channel)
158 | end
159 |
160 | on.message do |channel, message|
161 | begin
162 | Log.debug("Got a message on %s: %s" % [channel, message])
163 |
164 | @receiver_queue << YAML.load(message)
165 | rescue => e
166 | Log.warn("Failed to receive from the receiver source: %s: %s" % [e.class, e.to_s])
167 | end
168 | end
169 | end
170 | rescue Exception => e
171 | Log.warn("The receiver thread lost connection to the Redis server: %s: %s" % [e.class, e.to_s])
172 | sleep 0.2
173 | retry
174 | end
175 | end
176 |
177 | Log.debug("Started receiver_thread %s" % @receiver_thread.inspect)
178 | end
179 |
180 | def start_sender_thread
181 | @sender_thread = Thread.new do
182 | Log.debug("Starting sender thread")
183 |
184 | loop do
185 | begin
186 | msg = @sender_queue.pop
187 |
188 | case msg[:command]
189 | when :publish
190 | encoded = {:body => msg[:body], :headers => msg[:headers]}.to_yaml
191 | @sender_redis.publish(msg[:channel], encoded)
192 |
193 | when :proc
194 | msg[:proc].call(@sender_redis)
195 | end
196 | rescue Exception => e
197 | Log.warn("Could not publish message to redis: %s: %s" % [e.class, e.to_s])
198 | sleep 0.2
199 | retry
200 | end
201 | end
202 | end
203 |
204 | Log.debug("Started sender_thread %s" % @sender_thread.inspect)
205 | end
206 | end
207 | end
208 | end
209 |
--------------------------------------------------------------------------------
/mc-irb/mc-irb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Simple IRB shell for mcollective
4 | #
5 | # mc-irb nrpe
6 | # Determining the amount of hosts matching filter for 2 seconds .... 47
7 | # >> rpc :runcommand, :command => "check_disks"
8 | #
9 | # * [ ============================================================> ] 47 / 47
10 | #
11 | #
12 | # dev1.your.net Request Aborted
13 | # CRITICAL
14 | # Output: DISK CRITICAL - free space: / 176 MB (4% inode=86%);
15 | # Exit Code: 2
16 | # Performance Data: /=3959MB;3706;3924;0;4361 /boot=26MB;83;88;0;98 /dev/shm=0MB;217;230;0;256
17 | #
18 | # => true
19 | # >> mchelp
20 | #
21 | # => true
22 | # >> rpc(:runcommand, :command => "check_disks") do |resp|
23 | # ?> puts resp[:sender] + ": " + resp[:data][:output]
24 | # >> end
25 | #
26 | # * [ ============================================================> ] 47 / 47
27 | #
28 | # dev1.your.net: DISK OK
29 | #
30 | # => true
31 | # >>
32 | #
33 | # You can access the agent variable via @agent from where you can do the usual manipulation of filters etc,
34 | # if you wish to switch to a different agent mid run just do newagent("some_other_agent")
35 | #
36 | # If you install the Bond gem you'll get some DDL assisted completion in the rpc method
37 | require 'rubygems'
38 | require 'irb'
39 |
40 | def consolize &block
41 | yield
42 |
43 | IRB.setup(nil)
44 | irb = IRB::Irb.new
45 | IRB.conf[:MAIN_CONTEXT] = irb.context
46 | irb.context.evaluate("require 'irb/completion'", 0)
47 |
48 | install_alias_method :help, :mc_irb_help, IRB::ExtendCommandBundle::OVERRIDE_ALL
49 | install_alias_method :mc?, :mc_irb_help, IRB::ExtendCommandBundle::OVERRIDE_ALL
50 |
51 | begin
52 | require 'bond'
53 | Bond.start
54 |
55 | Bond.complete(:method => "rpc") do |e|
56 | begin
57 | if e.argument == 1
58 | if e.arguments.last == "?"
59 | puts "\n\nActions for #{@agent_name}:\n"
60 |
61 | @agent.ddl.actions.each do |action|
62 | puts "%20s - %s" % [ ":#{action}", @agent.ddl.action_interface(action)[:description] ]
63 | end
64 |
65 | print "\n" + e.line
66 | end
67 |
68 | @agent.ddl.actions
69 |
70 | elsif e.argument > 1
71 | action = eval(e.arguments[0]).to_s
72 | ddl = @agent.ddl.action_interface(action)
73 |
74 | if e.arguments.last == "?"
75 | puts "\n\nArguments for #{action}:\n"
76 | ddl[:input].keys.each do |input|
77 | puts "%20s - %s" % [ ":#{input}", ddl[:input][input][:description] ]
78 | end
79 |
80 | print "\n" + e.line
81 | end
82 |
83 | [ddl[:input].keys, :verbose].flatten
84 | end
85 | rescue Exception
86 | []
87 | end
88 | end
89 | rescue Exception
90 | end
91 |
92 | trap("SIGINT") do
93 | irb.signal_handle
94 | end
95 | catch(:IRB_EXIT) do
96 | irb.eval_input
97 | end
98 | end
99 |
100 | def mchelp
101 | system("mc-rpc --agent-help #{@agent_name}|less")
102 | true
103 | end
104 |
105 | def rpc(method_name, *args, &block)
106 | unless block_given?
107 | if args.size > 0
108 | args = args.first
109 | else
110 | args = {}
111 | end
112 |
113 | if args[:verbose]
114 | args.delete(:verbose)
115 |
116 | printrpc(@agent.send(method_name, args), :verbose => true)
117 | printrpcstats
118 | else
119 | printrpc @agent.send(method_name, args)
120 | printrpcstats
121 | end
122 |
123 | else
124 | @agent.send(method_name, args.first).each do |resp|
125 | yield resp
126 | end
127 |
128 | printrpcstats
129 | end
130 |
131 | true
132 | rescue MCollective::DDLValidationError => e
133 | puts "Request did not pass DDL validation: #{e}"
134 | end
135 |
136 | def print_filter
137 | puts "Active Filter matched #{discover.size} hosts:"
138 | puts "\tIdentity: #{@agent.filter['identity'].pretty_inspect}"
139 | puts "\t Classes: #{@agent.filter['cf_class'].pretty_inspect}"
140 | puts "\t Facts: #{@agent.filter['fact'].pretty_inspect}"
141 | puts "\t Agents: #{@agent.filter['agent'].pretty_inspect}"
142 |
143 | discover.size > 0 ? true : false
144 | end
145 |
146 | def newagent(agent)
147 | @agent_name = agent
148 |
149 | @options[:filter]["agent"] = []
150 | @agent = rpcclient(@agent_name, :options => @options)
151 |
152 | discover
153 |
154 | @agent.progress = true
155 |
156 | print_filter
157 | end
158 |
159 | def identity_filter(*args)
160 | @agent.identity_filter(*args)
161 |
162 | print_filter
163 | end
164 |
165 | def fact_filter(*args)
166 | @agent.fact_filter(*args)
167 |
168 | print_filter
169 | end
170 |
171 | def agent_filter(*args)
172 | @agent.agent_filter(*args)
173 |
174 | print_filter
175 | end
176 |
177 | def class_filter(*args)
178 | @agent.class_filter(*args)
179 |
180 | print_filter
181 | end
182 |
183 | def reset_filter
184 | @agent.reset_filter
185 |
186 | print_filter
187 | end
188 |
189 | def reset
190 | @agent.reset
191 |
192 | print_filter
193 | end
194 |
195 | def discover
196 | @agent.discover
197 | end
198 |
199 | def mc_irb_help
200 | puts < for a list of actions or
229 | arguments, do simple : to get completion on action names and
230 | arguments without description of each
231 |
232 | EOF
233 | true
234 | end
235 |
236 | consolize do
237 | require 'mcollective'
238 |
239 |
240 | include MCollective::RPC
241 |
242 | @options = rpcoptions
243 |
244 | unless ARGV.size == 1
245 | puts "Please specify an agent name on the command line"
246 | exit 1
247 | end
248 |
249 | puts "The Marionette Collective Interactive Ruby Shell version #{MCollective.version}"
250 |
251 | puts
252 | newagent(ARGV[0])
253 | end
254 |
--------------------------------------------------------------------------------
/agent/eximng/sbin/eximctl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 |
3 | require 'mcollective'
4 | require 'rdialog'
5 | require 'tempfile'
6 |
7 | include MCollective::RPC
8 |
9 | class CancelPressed < RuntimeError; end
10 | class InvalidAddress < RuntimeError; end
11 |
12 | class EximUI
13 | def initialize(exim)
14 | @exim = exim
15 | @dialog = RDialog.new
16 | @dialog.backtitle = "Exim Collective"
17 | @dialog.itemhelp = true
18 |
19 | @tools = Array.new
20 | @tools << ["1", "Show mail queue", "mailq"]
21 | @tools << ["2", "Show mail queue summary", "summary"]
22 | @tools << ["3", "Show current exim activity", "exiwhat"]
23 | @tools << ["4", "Test an address", "test"]
24 | @tools << ["5", "Retry mail delivery", "retry"]
25 | @tools << ["6", "Add a recipient", "addrecipient"]
26 | @tools << ["7", "Edit the sender of a message", "setsender"]
27 | @tools << ["8", "Mark all recipients as delivered", "markalldelivered"]
28 | @tools << ["9", "Mark a recipient as delivered", "markrecipdelivered"]
29 | @tools << ["0", "Freeze message", "freeze"]
30 | @tools << ["a", "UnFreeze message", "thaw"]
31 | @tools << ["b", "Give up on a message (with bounce message)", "giveup"]
32 | @tools << ["c", "Removes a message", "rm"]
33 | @tools << ["d", "Remove all postmaster originated message", "rmbounces"]
34 | @tools << ["e", "Remove all frozen message", "rmfrozen"]
35 | @tools << ["f", "Do a normal queue run", "runq"]
36 | @tools << ["g", "Deliver all messages matching a string", "delivermatching"]
37 | @tools << ["h", "Re-discover agents", "discover"]
38 | @tools << ["x", "Exit", "quit"]
39 | end
40 |
41 | def quit_command(title)
42 | exit
43 | end
44 |
45 | # Does a discovery
46 | def discover_command(title)
47 | infobox("Doing discovery", title)
48 | @exim.mc.reset
49 | found = @exim.mc.discover
50 | textbox("Found exim agents on the following hosts:\n\n" + found.join("\n"), "Discovered Hosts")
51 | end
52 |
53 | def test_command(title)
54 | address = ask("Email address to test:", title)
55 | textbox(@exim.test(:address => address), title)
56 | end
57 |
58 | def retry_command(title)
59 | act_on_msgid(title) do |msgid|
60 | textbox(@exim.retry(:message_id => msgid), title)
61 | end
62 | end
63 |
64 | def addrecipient_command(title)
65 | act_on_msgid(title) do |msgid|
66 | recipient = ask("Recipient:", title)
67 | textbox(@exim.addrecipient(:message_id => msgid, :recipient => recipient), title)
68 | end
69 | end
70 |
71 | def setsender_command(title)
72 | act_on_msgid(title) do |msgid|
73 | sender = ask("Sender:", title)
74 | textbox(@exim.setsender(:message_id => msgid, :sender => sender), title)
75 | end
76 | end
77 |
78 | def markalldelivered_command(title)
79 | act_on_msgid(title) do |msgid|
80 | textbox(@exim.markdelivered(:message_id => msgid), title)
81 | end
82 | end
83 |
84 | def freeze_command(title)
85 | act_on_msgid(title) do |msgid|
86 | textbox(@exim.freeze(:message_id => msgid), title)
87 | end
88 | end
89 |
90 | def thaw_command(title)
91 | act_on_msgid(title) do |msgid|
92 | textbox(@exim.thaw(:message_id => msgid), title)
93 | end
94 | end
95 |
96 | def markrecipdelivered_command(title)
97 | act_on_msgid(title) do |msgid|
98 | recipient = ask("Recipient:", title)
99 | textbox(@exim.markdelivered(:message_id => msgid, :recipient => recipient), title)
100 | end
101 | end
102 |
103 | def giveup_command(title)
104 | act_on_msgid(title) do |msgid|
105 | textbox(@exim.giveup(:message_id => msgid), title)
106 | end
107 | end
108 |
109 | def rm_command(title)
110 | act_on_msgid(title) do |msgid|
111 | textbox(@exim.rm(:message_id => msgid), title)
112 | end
113 | end
114 |
115 | def delivermatching_command(title)
116 | pattern = ask("Pattern:", title)
117 | textbox(@exim.runq(:pattern => pattern), title)
118 | end
119 |
120 | def run
121 | discover_command("Discovering agents")
122 |
123 | while (result = menu(@tools)) do
124 | begin
125 | tool = gettool(result, @tools)
126 |
127 | cmd = "#{tool[1]}_command"
128 |
129 | if respond_to?(cmd)
130 | send(cmd, tool[0])
131 | elsif @exim.respond_to?(tool[1])
132 | textbox(@exim.send(tool[1], {}), tool[0])
133 | else
134 | textbox("Support for #{tool[1]} has not yet been implimented", "Error")
135 | end
136 | rescue InvalidAddress => e
137 | textbox("Invalid email address entered", "Error")
138 | rescue CancelPressed => e
139 | # go back to the menu if cancel is pressed
140 | rescue SystemExit
141 | exit
142 | rescue Exception => e
143 | textbox("Unhandled exception: \n #{e.class}: #{e}\n" + e.backtrace.join("\n "), "Error")
144 | end
145 | end
146 | rescue CancelPressed
147 | end
148 |
149 | def act_on_msgid(title, &blk)
150 | if choice = choosemsg(title)
151 | yield(choice)
152 | else
153 | @dialog.msgbox("The mail queue is empty, nothing to operate on")
154 | end
155 | end
156 |
157 | # Choose a message from a list of the current mailq, returns the msgid
158 | def choosemsg(title)
159 | choices = []
160 |
161 | @exim.mc.mailq do |r, s|
162 | s[:data][:mailq].each do |message|
163 | recipients = message[:recipients].join(' ')[0,30]
164 | frozen = "*** frozen *** " if message[:frozen]
165 | choices << [message[:msgid], "From: #{message[:sender]} To: #{recipients} #{frozen}"]
166 | end
167 | end
168 |
169 | msgid = choose(choices, title)
170 | end
171 |
172 | # wrappers to save typing
173 | def choose(items, title)
174 | @dialog.title = "\"#{title}\""
175 | res = @dialog.menu(title, items)
176 |
177 | raise CancelPressed unless res
178 |
179 | res
180 | end
181 |
182 | # Show the string in a dialog box with a specified title
183 | def textbox(msg, title)
184 | Tempfile.open("exim") do |tmp|
185 | tmp.write("\n" + msg.to_s)
186 | tmp.close
187 |
188 | title.sub!(/"/, "\\\"")
189 |
190 | @dialog.title = "\"#{title}\""
191 | @dialog.textbox(tmp.path)
192 | end
193 | end
194 |
195 | # Displays some text while you do something in the background
196 | def infobox(msg, title, height = 5, width = 40)
197 | @dialog.title = "\"#{title}\""
198 | @dialog.infobox("\n" + msg, height, width)
199 | end
200 |
201 | # Presents the list of tools in a menu
202 | def menu(tools)
203 | items = Array.new
204 |
205 | tools.each do |t|
206 | items << [ t[0], t[1] ]
207 | end
208 |
209 | selected = choose(items, "Choose an operation")
210 | end
211 |
212 | # Ask the user for something, return the entered text
213 | def ask(what, title)
214 | @dialog.title = "\"#{title}\""
215 |
216 | res = @dialog.inputbox(what)
217 |
218 | raise CancelPressed.new unless res
219 |
220 | res
221 | end
222 |
223 | def gettool(choice, tools)
224 | tools.each do |t|
225 | return [t[1], t[2]] if t[0] == choice
226 | end
227 |
228 | return nil
229 | end
230 | end
231 |
232 | mc = rpcclient("eximng")
233 | mc.progress = false
234 |
235 | MCollective::Util.loadclass("MCollective::Util::EximNG")
236 | exim = MCollective::Util::EximNG.new(mc)
237 | eximui = EximUI.new(exim)
238 |
239 | eximui.run
240 |
--------------------------------------------------------------------------------
/agent/collective/agent/collective.rb:
--------------------------------------------------------------------------------
1 | module MCollective
2 | module Agent
3 | class Collective :out, :stderr => :err, :chomp => true, :cwd => @basedir)
62 |
63 | reply.fail! "Failed to run '#{command}'" unless reply[:status] == 0
64 | end
65 |
66 | action "list" do
67 | reply[:members] = get_members
68 | end
69 |
70 | action "status" do
71 | members = get_members
72 |
73 | reply[:instance_count] = members.size
74 | reply[:members] = []
75 | members.each do |member|
76 | reply[:members] << {:name => member, :pid => get_member_pid(member)}
77 | end
78 |
79 | instances_stats
80 | end
81 |
82 | action "start" do
83 | start_all_members
84 | instances_stats
85 | end
86 |
87 | action "stop" do
88 | stop_all_members
89 | instances_stats
90 | end
91 |
92 | def instances_stats
93 | reply[:instance_count] = get_members.size
94 |
95 | reply[:instances_running] = get_members.map do |member|
96 | get_member_pid(member)
97 | end.compact.size
98 |
99 | reply[:instances_stopped] = reply[:instance_count] - reply[:instances_running]
100 | end
101 |
102 | def start_all_members
103 | get_members.each do |member|
104 | start_member(member)
105 | sleep 0.5
106 | end
107 | end
108 |
109 | def stop_all_members
110 | get_members.each do |member|
111 | stop_member(member)
112 | end
113 | end
114 |
115 | def stop_member(identity)
116 | pid = member_running?(identity)
117 | if pid
118 | Log.info("Stopping collective member #{identity} with pid #{pid}")
119 | ::Process.kill(2, Integer(pid))
120 | FileUtils.rm(get_member_pid(identity)) rescue nil
121 | end
122 | end
123 |
124 | def start_member(identity)
125 | reply.fail! "You need to create a collective first using rake create" if get_members.size == 0
126 |
127 | return true if member_running?(identity)
128 |
129 | memberdir = member_path(identity)
130 | pid = pid_path(identity)
131 | libdir = File.join(memberdir, "lib")
132 | config = File.join(memberdir, "etc", "server.cfg")
133 |
134 | cmd = "ruby -I #{libdir} bin/mcollectived --config #{config} --pidfile #{pid}"
135 | Log.info("Starting member #{identity} with #{cmd} in #{memberdir}")
136 |
137 | status = run("ruby -I #{libdir} bin/mcollectived --config #{config} --pidfile #{pid}", :cwd => memberdir)
138 | end
139 |
140 | def member_running?(identity)
141 | pid = get_member_pid(identity)
142 |
143 | return false unless pid
144 |
145 | if File.directory?("/proc")
146 | return Integer(pid) if File.exist?("/proc/#{pid}")
147 | end
148 |
149 | false
150 | end
151 |
152 | def get_member_pid(identity)
153 | pidfile = pid_path(identity)
154 |
155 | return nil unless File.exist?(pidfile)
156 |
157 | Integer(File.read(pidfile))
158 | end
159 |
160 | def get_members
161 | Dir.entries(@collectivedir).reject{|f| f.start_with?(".")}
162 | rescue
163 | []
164 | end
165 |
166 | def pid_path(identity)
167 | File.join(@pidsdir, "#{identity}.pid")
168 | end
169 |
170 | def member_path(identity)
171 | File.join(@collectivedir, identity)
172 | end
173 |
174 | def create_member(identity, version, collective, subcollectives, stompserver, stompport, stompuser, stomppass)
175 | raise "Cannot find server.cfg template in #{@templatedir}" unless File.exist?(File.join(@templatedir, "server.cfg.erb"))
176 |
177 | memberdir = member_path(identity)
178 | membertemplate = File.join(@templatedir, "server.cfg.erb")
179 | memberconfig = File.join(memberdir, "etc", "server.cfg")
180 |
181 | Log.info("Creating member %s in %s using templates in %s" % [identity, memberdir, @templatedir])
182 |
183 | out = ""; err = ""
184 | cmd = "git clone -q file:///%s %s" % [@clonedir, memberdir]
185 | status = run(cmd, :stdout => out, :stderr => err, :chomp => true)
186 | raise("Cloning member #{identity} failed while running #{cmd}: #{err}") unless status == 0
187 |
188 | out = ""; err = ""
189 | cmd = "git checkout -q %s" % version
190 | status = run(cmd, :stdout => out, :stderr => err, :chomp => true, :cwd => memberdir)
191 | raise("Cloning member #{identity} failed while running #{cmd}: #{err}") unless status == 0
192 |
193 | render_template(membertemplate, memberconfig, binding)
194 | FileUtils.cp(get_random_template_file("classes"), File.join(memberdir, "etc", "classes.txt"))
195 | FileUtils.cp(get_random_template_file("facts"), File.join(memberdir, "etc", "facts.yaml"))
196 | FileUtils.cp_r(File.join(@pluginsdir, "."), File.join(memberdir, "plugins", "mcollective"))
197 | end
198 |
199 | def get_random_template_file(type)
200 | files = Dir.entries(File.join(@templatedir, type)).reject{|f| f.start_with?(".")}
201 |
202 | File.join(@templatedir, type, files[rand(files.size)])
203 | end
204 |
205 | def render_template(template, output, scope)
206 | tmpl = File.read(template)
207 | erb = ERB.new(tmpl, 0, "<>")
208 | File.open(output, "w") do |f|
209 | f.puts erb.result(scope)
210 | end
211 | end
212 | end
213 | end
214 | end
215 |
--------------------------------------------------------------------------------
/agent/eximng/web/public/js/bootstrap-twipsy.js:
--------------------------------------------------------------------------------
1 | /* ==========================================================
2 | * bootstrap-twipsy.js v1.3.0
3 | * http://twitter.github.com/bootstrap/javascript.html#twipsy
4 | * Adapted from the original jQuery.tipsy by Jason Frame
5 | * ==========================================================
6 | * Copyright 2011 Twitter, Inc.
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | * ========================================================== */
20 |
21 |
22 | !function( $ ) {
23 |
24 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
25 | * ======================================================= */
26 |
27 | var transitionEnd
28 |
29 | $(document).ready(function () {
30 |
31 | $.support.transition = (function () {
32 | var thisBody = document.body || document.documentElement
33 | , thisStyle = thisBody.style
34 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
35 | return support
36 | })()
37 |
38 | // set CSS transition event type
39 | if ( $.support.transition ) {
40 | transitionEnd = "TransitionEnd"
41 | if ( $.browser.webkit ) {
42 | transitionEnd = "webkitTransitionEnd"
43 | } else if ( $.browser.mozilla ) {
44 | transitionEnd = "transitionend"
45 | } else if ( $.browser.opera ) {
46 | transitionEnd = "oTransitionEnd"
47 | }
48 | }
49 |
50 | })
51 |
52 |
53 | /* TWIPSY PUBLIC CLASS DEFINITION
54 | * ============================== */
55 |
56 | var Twipsy = function ( element, options ) {
57 | this.$element = $(element)
58 | this.options = options
59 | this.enabled = true
60 | this.fixTitle()
61 | }
62 |
63 | Twipsy.prototype = {
64 |
65 | show: function() {
66 | var pos
67 | , actualWidth
68 | , actualHeight
69 | , placement
70 | , $tip
71 | , tp
72 |
73 | if (this.getTitle() && this.enabled) {
74 | $tip = this.tip()
75 | this.setContent()
76 |
77 | if (this.options.animate) {
78 | $tip.addClass('fade')
79 | }
80 |
81 | $tip
82 | .remove()
83 | .css({ top: 0, left: 0, display: 'block' })
84 | .prependTo(document.body)
85 |
86 | pos = $.extend({}, this.$element.offset(), {
87 | width: this.$element[0].offsetWidth
88 | , height: this.$element[0].offsetHeight
89 | })
90 |
91 | actualWidth = $tip[0].offsetWidth
92 | actualHeight = $tip[0].offsetHeight
93 | placement = _.maybeCall(this.options.placement, this.$element[0])
94 |
95 | switch (placement) {
96 | case 'below':
97 | tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}
98 | break
99 | case 'above':
100 | tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}
101 | break
102 | case 'left':
103 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}
104 | break
105 | case 'right':
106 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}
107 | break
108 | }
109 |
110 | $tip
111 | .css(tp)
112 | .addClass(placement)
113 | .addClass('in')
114 | }
115 | }
116 |
117 | , setContent: function () {
118 | var $tip = this.tip()
119 | $tip.find('.twipsy-inner')[this.options.html ? 'html' : 'text'](this.getTitle())
120 | $tip[0].className = 'twipsy'
121 | }
122 |
123 | , hide: function() {
124 | var that = this
125 | , $tip = this.tip()
126 |
127 | $tip.removeClass('in')
128 |
129 | function removeElement () {
130 | $tip.remove()
131 | }
132 |
133 | $.support.transition && this.$tip.hasClass('fade') ?
134 | $tip.bind(transitionEnd, removeElement) :
135 | removeElement()
136 | }
137 |
138 | , fixTitle: function() {
139 | var $e = this.$element
140 | if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
141 | $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
142 | }
143 | }
144 |
145 | , getTitle: function() {
146 | var title
147 | , $e = this.$element
148 | , o = this.options
149 |
150 | this.fixTitle()
151 |
152 | if (typeof o.title == 'string') {
153 | title = $e.attr(o.title == 'title' ? 'data-original-title' : o.title)
154 | } else if (typeof o.title == 'function') {
155 | title = o.title.call($e[0])
156 | }
157 |
158 | title = ('' + title).replace(/(^\s*|\s*$)/, "")
159 |
160 | return title || o.fallback
161 | }
162 |
163 | , tip: function() {
164 | if (!this.$tip) {
165 | this.$tip = $('').html('')
166 | }
167 | return this.$tip
168 | }
169 |
170 | , validate: function() {
171 | if (!this.$element[0].parentNode) {
172 | this.hide()
173 | this.$element = null
174 | this.options = null
175 | }
176 | }
177 |
178 | , enable: function() {
179 | this.enabled = true
180 | }
181 |
182 | , disable: function() {
183 | this.enabled = false
184 | }
185 |
186 | , toggleEnabled: function() {
187 | this.enabled = !this.enabled
188 | }
189 |
190 | }
191 |
192 |
193 | /* TWIPSY PRIVATE METHODS
194 | * ====================== */
195 |
196 | var _ = {
197 |
198 | maybeCall: function ( thing, ctx ) {
199 | return (typeof thing == 'function') ? (thing.call(ctx)) : thing
200 | }
201 |
202 | }
203 |
204 |
205 | /* TWIPSY PLUGIN DEFINITION
206 | * ======================== */
207 |
208 | $.fn.twipsy = function (options) {
209 | $.fn.twipsy.initWith.call(this, options, Twipsy, 'twipsy')
210 | return this
211 | }
212 |
213 | $.fn.twipsy.initWith = function (options, Constructor, name) {
214 | var twipsy
215 | , binder
216 | , eventIn
217 | , eventOut
218 |
219 | if (options === true) {
220 | return this.data(name)
221 | } else if (typeof options == 'string') {
222 | twipsy = this.data(name)
223 | if (twipsy) {
224 | twipsy[options]()
225 | }
226 | return this
227 | }
228 |
229 | options = $.extend({}, $.fn[name].defaults, options)
230 |
231 | function get(ele) {
232 | var twipsy = $.data(ele, name)
233 |
234 | if (!twipsy) {
235 | twipsy = new Constructor(ele, $.fn.twipsy.elementOptions(ele, options))
236 | $.data(ele, name, twipsy)
237 | }
238 |
239 | return twipsy
240 | }
241 |
242 | function enter() {
243 | var twipsy = get(this)
244 | twipsy.hoverState = 'in'
245 |
246 | if (options.delayIn == 0) {
247 | twipsy.show()
248 | } else {
249 | twipsy.fixTitle()
250 | setTimeout(function() {
251 | if (twipsy.hoverState == 'in') {
252 | twipsy.show()
253 | }
254 | }, options.delayIn)
255 | }
256 | }
257 |
258 | function leave() {
259 | var twipsy = get(this)
260 | twipsy.hoverState = 'out'
261 | if (options.delayOut == 0) {
262 | twipsy.hide()
263 | } else {
264 | setTimeout(function() {
265 | if (twipsy.hoverState == 'out') {
266 | twipsy.hide()
267 | }
268 | }, options.delayOut)
269 | }
270 | }
271 |
272 | if (!options.live) {
273 | this.each(function() {
274 | get(this)
275 | })
276 | }
277 |
278 | if (options.trigger != 'manual') {
279 | binder = options.live ? 'live' : 'bind'
280 | eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus'
281 | eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'
282 | this[binder](eventIn, enter)[binder](eventOut, leave)
283 | }
284 |
285 | return this
286 | }
287 |
288 | $.fn.twipsy.Twipsy = Twipsy
289 |
290 | $.fn.twipsy.defaults = {
291 | animate: true
292 | , delayIn: 0
293 | , delayOut: 0
294 | , fallback: ''
295 | , placement: 'above'
296 | , html: false
297 | , live: false
298 | , offset: 0
299 | , title: 'title'
300 | , trigger: 'hover'
301 | }
302 |
303 | $.fn.twipsy.elementOptions = function(ele, options) {
304 | return $.metadata ? $.extend({}, options, $(ele).metadata()) : options
305 | }
306 |
307 | }( window.jQuery || window.ender );
--------------------------------------------------------------------------------
/agent/libvirt/agent/libvirt.rb:
--------------------------------------------------------------------------------
1 | module MCollective
2 | module Agent
3 | class Libvirt e
44 | reply.fail! "Could not load hvm info: #{e}"
45 | ensure
46 | close(conn)
47 | end
48 | end
49 |
50 | action "domaininfo" do
51 | validate :domain, String
52 |
53 | conn = connect
54 |
55 | begin
56 | has_domain?(request[:domain], conn)
57 |
58 | domain = conn.lookup_domain_by_name(request[:domain])
59 | info = domain.info
60 |
61 | reply[:autostart] = domain.autostart?
62 | reply[:vcpus] = info.nr_virt_cpu
63 | reply[:memory] = info.memory
64 | reply[:max_memory] = info.max_mem
65 | reply[:cputime] = info.cpu_time
66 | reply[:state] = info.state
67 | reply[:state_description] = virtstates[info.state]
68 | reply[:uuid] = domain.uuid
69 | reply[:persistent] = domain.persistent?
70 |
71 | # not on all versions of libvirt
72 | begin
73 | reply[:has_current_snapshot] = domain.has_current_snapshot?
74 | reply[:has_managed_save] = domain.has_managed_save?
75 | reply[:snapshots] = domain.list_snapshots
76 | reply[:num_of_snapshots] = domain.num_of_snapshots
77 | rescue
78 | end
79 | rescue Exception => e
80 | reply.fail! "Could not load domain %s: %s" % [request[:domain], e]
81 | ensure
82 | domain.free if domain
83 | close(conn)
84 | end
85 | end
86 |
87 | action "domainxml" do
88 | validate :domain, String
89 |
90 | conn = connect
91 |
92 | begin
93 | has_domain?(request[:domain], conn)
94 |
95 | domain = conn.lookup_domain_by_name(request[:domain])
96 | reply[:xml] = domain.xml_desc
97 | rescue Exception => e
98 | reply.fail! "Could not load domain %s: %s" % [request[:domain], e]
99 | ensure
100 | domain.free if domain
101 |
102 | close(conn)
103 | end
104 | end
105 |
106 | action "definedomain" do
107 | validate :xmlfile, String if request.include?[:xmlfile]
108 | validate :xml, String if request.include?[:xml]
109 | validate :domain, String
110 |
111 | reply.fail!("Can't find XML file defining instance") unless File.exist?(request[:xmlfile])
112 |
113 | begin
114 | conn = connect
115 |
116 | if request[:xmlfile]
117 | xml = File.read(request[:xmlfile])
118 | elsif request[:xml]
119 | xml = File.read(request[:xml])
120 | else
121 | reply.fail!("Need either xmlfile or xml parameters to define a domain")
122 | end
123 |
124 |
125 | if request[:permanent]
126 | conn.define_domain_xml(xml)
127 | else
128 | conn.create_domain_xml(xml)
129 | end
130 |
131 | domain = conn.lookup_domain_by_name(request[:domain])
132 | reply[:state] = domain.info.state
133 | reply[:state_description] = virtstates[reply[:state]]
134 | rescue Exception => e
135 | reply.fail! "Could not define domain %s: %s" % [request[:domain], e]
136 | ensure
137 | domain.free if domain
138 |
139 | close(conn)
140 | end
141 | end
142 |
143 | action "undefinedomain" do
144 | validate :domain, String
145 |
146 | begin
147 | conn = connect
148 |
149 | has_domain?(request[:domain], conn)
150 |
151 | domain = conn.lookup_domain_by_name(request[:domain])
152 |
153 | if request[:destroy] && domain.active?
154 | Log.info("Attempting to destroy domain %s on request of %s" % [request[:domain], request.caller])
155 | domain.destroy
156 | end
157 |
158 | Log.info("Attempting to undefine domain %s on request of %s" % [request[:domain], request.caller])
159 |
160 | domain.undefine
161 |
162 | reply[:status] = request[:domain] + " undefined"
163 | rescue Exception => e
164 | reply.fail! "Could not undefine domain %s: %s" % [request[:domain], e]
165 | ensure
166 | domain.free if domain
167 | close(conn)
168 | end
169 | end
170 |
171 | [:destroy, :shutdown, :suspend, :resume, :create, :reboot].each do |act|
172 | action act do
173 | validate :domain, String
174 |
175 | reply[:state] = domain_action(request[:domain], act)
176 | reply[:state_description] = virtstates[reply[:state]]
177 | end
178 | end
179 |
180 | alias :start_action :create_action
181 |
182 | private
183 | def connect
184 | url = @config.pluginconf["libvirt.url"] || "qemu:///system"
185 |
186 | conn = ::Libvirt::open(url)
187 |
188 | raise "Could not connect to hypervisor" if conn.closed?
189 |
190 | conn
191 | end
192 |
193 | def close(conn)
194 | if conn && !conn.closed?
195 | conn.close
196 | end
197 | end
198 |
199 | def domains(conn)
200 | domains = []
201 | conn.list_domains.each do |id|
202 | domain = conn.lookup_domain_by_id(id)
203 | domains << domain.name
204 | domain.free
205 | end
206 |
207 | domains << conn.list_defined_domains
208 |
209 | domains.flatten.sort
210 | end
211 |
212 | def has_domain?(domain, conn)
213 | raise "Unknown domain #{request[:domain]}" unless domains(conn).include?(request[:domain])
214 | end
215 |
216 | def virtstates
217 | {0 => "No state",
218 | 1 => "Running",
219 | 2 => "Blocked on resource",
220 | 3 => "Paused",
221 | 4 => "Shutting down",
222 | 5 => "Shut off",
223 | 6 => "Crashed"}
224 | end
225 |
226 | def domain_action(name, action)
227 | conn = connect
228 |
229 | begin
230 | has_domain?(request[:domain], conn)
231 |
232 | domain = conn.lookup_domain_by_name(name)
233 | domain.send(action.to_sym)
234 |
235 | return domain.info.state
236 | rescue Exception => e
237 | reply.fail! "Could not #{action} domain %s : %s" % [request[:domain], e]
238 | ensure
239 | domain.free if domain
240 | close(conn)
241 | end
242 | end
243 | end
244 | end
245 | end
246 |
--------------------------------------------------------------------------------
/agent/eximng/README.md:
--------------------------------------------------------------------------------
1 | What?
2 | =====
3 |
4 | A rework of my now ancient Exim agent for MCollective. The old agent predated
5 | SimpleRPC so it was a protocol on it's own, this new agent is fully SimpleRPC
6 | based.
7 |
8 | This version will handle bigger spools better - it will still be slow to fetch an
9 | entire spool with 10s of thousands of mails on it but you now have the ability to
10 | dig into the spool using the usual _exigrep_ features. The filters are done server
11 | side so should be much more efficient than before.
12 |
13 | A new Dialog interface was written that provides a lot of the same functionality
14 | minux some of the finer grained matching options. The Dialog interface is particularly
15 | good for commands like retry, rm, giveup etc where you need to operate on a message id
16 | as it will give you a convenient chooser to pick messages to operate on. It requires
17 | the _rdialog_ gem to be installed
18 |
19 | I am toying with a web interface but it's still a work in progress. Some of this code
20 | is not particularly DRY I will refactor once I have the worklow/approach of the web
21 | app down 100% and will then look at common points
22 |
23 | Usage?
24 | ======
25 |
26 | The included application plugin does most of what is needed in general use:
27 |
28 | % mco exim --help
29 |
30 | MCollective Exim Manager
31 |
32 | Usage: mco exim [mailq|size|summary|exiwhat|rmbounces|rmfrozen|runq]
33 | Usage: mco exim runq
34 | Usage: mco exim [retry|markdelivered|freeze|thaw|giveup|rm]
35 | Usage: mco exim [addrecipient|markdelivered]
36 | Usage: mco exim setsender
37 | Usage: mco exim [mailq|size]
38 | Usage: mco exim test
39 | Usage: mco exim exigrep pattern
40 |
41 | --match-sender, --limit-sender SENDER Match sender pattern
42 | --match-recipient, --limit-recipient RECIPIENT Match recipient pattern
43 | --match-younger, --limit-younger Match younger than seconds
44 | --match-older, --limit-older SECONDS Match older than seconds
45 | --match-frozen, --limit-frozen Match only frozen messages
46 | --match-active, --limit-active Match only active messages
47 |
48 | Retrieve the mail queue
49 | -----------------------
50 |
51 | The mail queue can be retrieved in whole or in part by using filters, the display will
52 | match what the built in mailq application would show so you can pipe the network wide
53 | mail queue into whatever existing scripts you have.
54 |
55 | $ mco exim mailq
56 |
57 | $ mco exim mailq --limit-frozen
58 |
59 | $ mco exim mailq --limit-sender foo.com
60 |
61 | TODO: delivery status of individual recipients on a message with many recipients is not
62 | handled, we simply don't return delivered addresses.
63 |
64 | Mail queue sizes
65 | ----------------
66 |
67 | You can get a quick summary of mail queue sizes for your entire network:
68 |
69 | $ mco exim size
70 |
71 | mx1.your.net: total mail: 12 matched mail: 12 frozen mail: 0
72 | mx2.your.net: total mail: 21 matched mail: 21 frozen mail: 16
73 | mx3.your.net: total mail: 25 matched mail: 25 frozen mail: 3
74 | mx4.your.net: total mail: 31 matched mail: 31 frozen mail: 18
75 | -- -- --
76 | 89 89 37
77 |
78 | As with the mail queue command you can use the standard matchers to limit:
79 |
80 | % mco exim size --limit-recipient foo.com
81 |
82 | mx1.your.net: total mail: 12 matched mail: 1 frozen mail: 0
83 | mx2.your.net: total mail: 21 matched mail: 14 frozen mail: 14
84 | mx3.your.net: total mail: 25 matched mail: 0 frozen mail: 0
85 | mx4.your.net: total mail: 31 matched mail: 15 frozen mail: 15
86 | -- -- --
87 | 89 30 29
88 |
89 | Here the total mail still represent all mail on the server but the matched and
90 | frozen is limited to mail destined for foo.com
91 |
92 | The mail queue summary is your standard _exiqsumm_ output but for the entire network,
93 | as it's already a simple summary no matching is allowed:
94 |
95 | % mco exim summary
96 | Count Volume Oldest Newest Domain
97 | ----- ------ ------ ------ ------
98 |
99 | 3 34KB 22h 18h 229-94.webeventstadium.com
100 | 1 1843 17h 17h alsg-italia.org
101 | 1 2048 14h 14h auditcomconsulting.cz
102 | .
103 | .
104 | .
105 | ---------------------------------------------------------------
106 | 121 2419KB 9d 43m TOTAL
107 |
108 | Managing the mail queue and its contents
109 | ----------------------------------------
110 |
111 | You can remove, edit, freeze and thaw messages on the queue.
112 |
113 | Most of these actions are limited to single messages or the entire queue
114 | we hope to add matchers to retry, freeze, thaw and rm so that these actions
115 | can be taken on all messages matching recipient, sender etc.
116 |
117 | Common uses:
118 |
119 | Do a forced retry on a single message:
120 |
121 | % mco exim retry 1R6TJa-0000fA-5Z
122 |
123 | mx1.your.net: Message 1R6TJa-0000fA-5Z has been retried
124 | mx2.your.net: No message matching 1R6TJa-0000fA-5Z
125 |
126 | Mark a message as delivered, the exim queue daemon will remove it later:
127 |
128 | % mco exim markdelivered 1R6TJa-0000fA-5Z
129 |
130 | Mark a single recipient on a message as delivered:
131 |
132 | % mco exim markdelivered 1R6TJa-0000fA-5Z foo@example.com
133 |
134 | Freeze and thaw a message:
135 |
136 | % mco exim freeze 1R6TJa-0000fA-5Z
137 | % mco exim thaw 1R6TJa-0000fA-5Z
138 |
139 | Stop trying to deliver a message and create a non delivery report:
140 |
141 | % mco exim giveup 1R6TJa-0000fA-5Z
142 |
143 | Removes a message without creating a non delivery report:
144 |
145 | % mco exim rm 1R6TJa-0000fA-5Z
146 |
147 | Add a recipient to a message:
148 |
149 | % mco exim addrecipient 1R6TJa-0000fA-5Z foo@example.com
150 |
151 | mx1.your.net: foo@example.com has been added to message 1R6TJa-0000fA-5Z
152 | mx2.your.net: No message matching 1R6TJa-0000fA-5Z
153 |
154 | Edit the sender of a message:
155 |
156 | % mco exim setsender 1R6TJa-0000fA-5Z foo@example.com
157 |
158 | Remove all frozen mail from the queue:
159 |
160 | % mco exim rmfrozen
161 |
162 | mx1.your.net: 5 messages deleted
163 | mx2.your.net: 8 messages deleted
164 | mx3.your.net: 3 messages deleted
165 |
166 | Remove all mail with postmaster as sender:
167 |
168 | % mco exim rmbounces
169 |
170 | mx1.your.net: 5 messages deleted
171 | mx2.your.net: 8 messages deleted
172 | mx3.your.net: 3 messages deleted
173 |
174 | Do a normal background queue run:
175 |
176 | % mco exim runq
177 |
178 | Do a queue run for messages matching example.com:
179 |
180 | % mco exim runq example.com
181 |
182 | mx1.your.net: Delivery for pattern example.com has been scheduled
183 | mx2.your.net: Delivery for pattern example.com has been scheduled
184 | mx3.your.net: Delivery for pattern example.com has been scheduled
185 |
186 | Server Activity
187 | ---------------
188 |
189 | You can figure out what all your servers are doing using exiwhat, this agent will run
190 | exiwhat and so let you get a simple network wide view:
191 |
192 | % mco exim exiwhat
193 |
194 | mx1.your.net
195 | 1865 daemon: -q1h, listening for SMTP on port 25 (IPv6 and IPv4)
196 | 12285 handling incoming connection from (94.102.224.6) [94.102.224.6]
197 | 12290 handling incoming connection from 93-86-76-68.dynamic.isp.telekom.rs (discus) [93.86.76.68]
198 |
199 | mx2.your.net
200 | 2688 daemon: -q1h, listening for SMTP on port 25 (IPv6 and IPv4)
201 | 15593 handling incoming connection from (outgoing-26.annoyoushypobenthos.info) [209.222.114.228]
202 | 15655 handling incoming connection from (mqja.com) [88.250.93.236]
203 |
204 | You can also do a network wide _exigrep_ to find all log lines relating some activity, you should
205 | use this with caution as on busy servers the files can be huge and it can put considerable load
206 | on your servers, just like when you use exigrep on the CLI.
207 |
208 | % mco exim exigrep foo.org.uk
209 |
210 | mx1.your.net
211 | Matches: +++ 1R7DXr-0006vV-Pa has not completed +++
212 | 2011-09-23 22:44:31 1R7DXr-0006vV-Pa <= <> R=1R5kqf-0006ro-TT U=exim P=local S=6323 T="Mail delivery failed: returning message to sender"
213 | 2011-09-24 07:41:17 1R7DXr-0006vV-Pa == foo.org.uk.606774.david@host117.enginereliable.com R=dnslookup T=remote_smtp defer (-18): Remote host host117.enginereliable.com [205.204.86.119] closed connection in response to end of data
214 | 2011-09-24 11:32:28 1R7DXr-0006vV-Pa == foo.org.uk.606774.david@host117.enginereliable.com R=dnslookup T=remote_smtp defer (-53): retry time not reached for any host
215 |
216 |
217 | You can quickly verify how your servers will route mail to a specific address:
218 |
219 | % mco exim test foo@gmail.com
220 |
221 | mx1.your.net
222 | Route: foo@gmail.com
223 | router = dnslookup, transport = remote_smtp
224 | host gmail-smtp-in.l.google.com [74.125.91.27] MX=5
225 | host alt1.gmail-smtp-in.l.google.com [209.85.143.27] MX=10
226 | host alt2.gmail-smtp-in.l.google.com [209.85.227.27] MX=20
227 | host alt3.gmail-smtp-in.l.google.com [74.125.79.26] MX=30
228 | host alt4.gmail-smtp-in.l.google.com [74.125.39.26] MX=40
229 |
230 |
--------------------------------------------------------------------------------