├── agent ├── eximng │ ├── web │ │ ├── .gitignore │ │ ├── serverlist.yaml.dist │ │ ├── views │ │ │ ├── mailq_view.erb │ │ │ ├── generic_result_view.erb │ │ │ └── layout.erb │ │ ├── lib │ │ │ └── exim.rb │ │ ├── public │ │ │ └── js │ │ │ │ ├── bootstrap-dropdown.js │ │ │ │ ├── bootstrap-tabs.js │ │ │ │ ├── bootstrap-popover.js │ │ │ │ ├── bootstrap-alerts.js │ │ │ │ ├── bootstrap-scrollspy.js │ │ │ │ ├── bootstrap-modal.js │ │ │ │ └── bootstrap-twipsy.js │ │ └── eximweb.rb │ ├── validator │ │ ├── exim_msgid_validator.rb │ │ └── exim_msgid_validator.ddl │ ├── data │ │ ├── domain_mailq_data.ddl │ │ └── domain_mailq_data.rb │ ├── application │ │ └── exim.rb │ ├── sbin │ │ └── eximctl │ └── README.md ├── urltest │ ├── TODO │ ├── COPYING │ ├── README.md │ ├── agent │ │ ├── urltest.rb │ │ └── urltest.ddl │ └── application │ │ └── urltest.rb ├── angelianotify │ └── README ├── collective │ ├── collective_plugins │ │ ├── templates │ │ │ ├── classes │ │ │ │ ├── classes-3.txt │ │ │ │ ├── classes-2.txt │ │ │ │ └── classes-1.txt │ │ │ ├── facts │ │ │ │ ├── facts-3.yaml │ │ │ │ ├── facts-1.yaml │ │ │ │ └── facts-2.yaml │ │ │ └── server.cfg.erb │ │ └── plugins │ │ │ ├── agents │ │ │ ├── stomputil.ddl │ │ │ └── stomputil.rb │ │ │ └── security │ │ │ └── none.rb │ ├── README.md │ └── agent │ │ ├── collective.ddl │ │ └── collective.rb ├── cap │ ├── README.md │ └── application │ │ └── cap.rb ├── haproxy │ └── agent │ │ ├── haproxy.rb │ │ └── haproxy.ddl ├── rndc │ ├── agent │ │ ├── rndc.rb │ │ └── rndc.ddl │ └── README.md ├── vcsmgr │ ├── vcsmgr.rb │ └── vcsmgr.ddl ├── ts │ └── agent │ │ ├── ts.rb │ │ └── ts.ddl ├── libvirt │ ├── README.md │ ├── application │ │ └── virt.rb │ └── agent │ │ └── libvirt.rb └── bench │ ├── agent │ ├── bench.ddl │ └── bench.rb │ └── README.md ├── data └── augeas_match │ └── data │ ├── augeas_match_data.rb │ └── augeas_match_data.ddl ├── discovery ├── puppetdb │ └── discovery │ │ ├── puppetdb.ddl │ │ └── puppetdb.rb └── ec2 │ ├── ec2.ddl │ ├── README.md │ └── ec2.rb ├── README.markdown ├── connector └── redis │ ├── discovery │ ├── redis.ddl │ └── redis.rb │ ├── registration │ └── redis.rb │ └── redis.rb ├── registration └── mcollective-registration-receiver.rb ├── application └── plot │ ├── README.md │ └── application │ └── plot.rb ├── scripts └── package-updater.rb └── mc-irb └── mc-irb /agent/eximng/web/.gitignore: -------------------------------------------------------------------------------- 1 | serverlist.yaml 2 | -------------------------------------------------------------------------------- /agent/urltest/TODO: -------------------------------------------------------------------------------- 1 | Rewrite to use - http://curb.rubyforge.org/ 2 | -------------------------------------------------------------------------------- /agent/eximng/web/serverlist.yaml.dist: -------------------------------------------------------------------------------- 1 | --- 2 | - mx1.example.com 3 | - mx2.example.com 4 | -------------------------------------------------------------------------------- /agent/angelianotify/README: -------------------------------------------------------------------------------- 1 | This agent has been moved to https://github.com/ripienaar/angelia 2 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/classes/classes-3.txt: -------------------------------------------------------------------------------- 1 | common 2 | mysql::server 3 | mysql 4 | roles::db_server 5 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/classes/classes-2.txt: -------------------------------------------------------------------------------- 1 | common 2 | apache 3 | apache::service 4 | roles::web_server 5 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/classes/classes-1.txt: -------------------------------------------------------------------------------- 1 | common 2 | apache 3 | apache::service 4 | mysql::server 5 | mysql 6 | roles::dev_server 7 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/facts/facts-3.yaml: -------------------------------------------------------------------------------- 1 | cluster: c 2 | customer: bar 3 | environment: staging 4 | operatingsystem: CentOS 5 | architecture: i386 6 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/facts/facts-1.yaml: -------------------------------------------------------------------------------- 1 | cluster: a 2 | customer: foo 3 | environment: production 4 | operatingsystem: CentOS 5 | architecture: i386 6 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/facts/facts-2.yaml: -------------------------------------------------------------------------------- 1 | cluster: b 2 | customer: foo 3 | environment: development 4 | operatingsystem: CentOS 5 | architecture: x86_64 6 | -------------------------------------------------------------------------------- /agent/cap/README.md: -------------------------------------------------------------------------------- 1 | A mco plugin that uses discovery to feed host lists 2 | to capistrano: 3 | 4 | mco cap invoke -W dom0=kvm1 USER=root COMMAND="service mcollective restart" 5 | 6 | Above will discover all nodes matching _dom0=kvm1_ and restart mcollective on them as the root user. 7 | -------------------------------------------------------------------------------- /data/augeas_match/data/augeas_match_data.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Data 3 | class Augeas_match_data "Exim Message ID", 2 | :description => "Validates that a string is a Exim Message ID", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "1.0", 6 | :url => "http://devco.net/", 7 | :timeout => 1 8 | -------------------------------------------------------------------------------- /agent/collective/README.md: -------------------------------------------------------------------------------- 1 | What? 2 | ===== 3 | 4 | A MCollective agent that can be used to manage a number of instances of 5 | mcollective running on a node. 6 | 7 | It is effectively a distributed version of the mcollective-collective-builder 8 | which when ran on 100 machines can be used to start 1000 mcollective instances 9 | all communicating to custom sets of brokers, collectives and sub collectives 10 | -------------------------------------------------------------------------------- /agent/cap/application/cap.rb: -------------------------------------------------------------------------------- 1 | class MCollective::Application::Cap "puppetdb", 2 | :description => "PuppetDB based discovery", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "0.1", 6 | :url => "http://marionette-collective.org/", 7 | :timeout => 0 8 | 9 | discovery do 10 | capabilities [:identity, :classes, :facts] 11 | end 12 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | What is it? 2 | =========== 3 | 4 | A collection of plugins for The Marionette Collective. 5 | 6 | Most of the plugins that used to be here have been moved 7 | to Puppet Labs and can be found documented at: 8 | 9 | http://projects.puppetlabs.com/projects/mcollective-plugins/wiki/Wiki 10 | 11 | And the code lives at: 12 | 13 | https://github.com/puppetlabs/mcollective-plugins 14 | 15 | If you have any qyestions about these plugins please contact 16 | 17 | R.I.Pienaar 18 | -------------------------------------------------------------------------------- /connector/redis/discovery/redis.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "redis", 2 | :description => "Redis based discovery to compliment the Redis connector and registration", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "0.1", 6 | :url => "http://marionette-collective.org/", 7 | :timeout => 0 8 | 9 | discovery do 10 | capabilities [:classes, :facts, :identity, :agents] 11 | end 12 | -------------------------------------------------------------------------------- /agent/urltest/COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2012 R.I.Pienaar 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /data/augeas_match/data/augeas_match_data.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "Augeas Match", 2 | :description => "Allows agents and discovery to do Augeas match lookups", 3 | :author => "R.I.Pienaar ", 4 | :license => "Apache License, Version 2.0", 5 | :version => "1.1", 6 | :url => "http://marionette-collective.org/", 7 | :timeout => 2 8 | 9 | dataquery :description => "Match data using Augeas" do 10 | input :query, 11 | :prompt => "Matcher", 12 | :description => "Valid Augeas match expression", 13 | :type => :string, 14 | :validation => /.+/, 15 | :maxlength => 150 16 | 17 | output :size, 18 | :description => "The amont of records matched", 19 | :display_as => "Matched" 20 | end 21 | -------------------------------------------------------------------------------- /agent/eximng/data/domain_mailq_data.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "domain_mailq_data", 2 | :description => "Checks the mailq for mail to a certain domain", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "0.1", 6 | :url => "http:/devco.net/", 7 | :timeout => 1 8 | 9 | dataquery :description => "Counts the mail destined for a certain domain" do 10 | input :query, 11 | :prompt => "Domain", 12 | :description => "Domain to check the mail queue for", 13 | :type => :string, 14 | :validation => :shellsafe, 15 | :optional => false, 16 | :maxlength => 50 17 | 18 | output :size, 19 | :description => "Amount of mail matching the domain in the spool", 20 | :display_as => "Size", 21 | :default => 0 22 | end 23 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/server.cfg.erb: -------------------------------------------------------------------------------- 1 | topicprefix = /topic/ 2 | collectives = <%= [collective, subcollectives].flatten.join(',') %> 3 | main_collective = <%= collective %> 4 | libdir = <%= memberdir %>/plugins 5 | logfile = <%= File.join(@logsdir, "#{identity}.log") %> 6 | loglevel = debug 7 | daemonize = 1 8 | identity = <%= identity %> 9 | 10 | # Plugins 11 | securityprovider = none 12 | 13 | direct_addressing = yes 14 | direct_addressing_threshold = 5 15 | 16 | connector = activemq 17 | plugin.activemq.pool.size = <%= stompserver.size %> 18 | 19 | <% stompserver.each_with_index do |server, idx| %> 20 | plugin.activemq.pool.<%= idx + 1 %>.host = <%= server %> 21 | plugin.activemq.pool.<%= idx + 1 %>.port = <%= stompport %> 22 | plugin.activemq.pool.<%= idx + 1 %>.user = <%= stompuser %> 23 | plugin.activemq.pool.<%= idx + 1 %>.password = <%= stomppass %> 24 | <% end %> 25 | 26 | factsource = yaml 27 | plugin.yaml = <%= memberdir %>/etc/facts.yaml 28 | 29 | classesfile = <%= memberdir %>/etc/classes.txt 30 | -------------------------------------------------------------------------------- /agent/haproxy/agent/haproxy.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Haproxy 2 | 3 | 4 | Sender 5 | Recipients 6 | Age 7 | Size 8 | Actions 9 | 10 | 11 | 12 | <% @mailq.each do |message| %> 13 | 14 | 15 | <% if message[:frozen] %> 16 | Frozen 17 | <% end %> 18 | <%= sanitize_email message[:sender] %> 19 | 20 | <%= message[:recipients].map {|r| h r}.join("
") %> 21 | <%= message[:age] %> 22 | <%= message[:size] %> 23 | 24 | retry 25 | <% if message[:frozen] %> 26 | thaw 27 | <% else %> 28 | freeze 29 | <% end %> 30 | delete 31 | 32 | 33 | <% end %> 34 | 35 | 36 | -------------------------------------------------------------------------------- /discovery/ec2/ec2.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "ec2", 2 | :description => "EC2 Instance data based discovery", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "0.1", 6 | :url => "http://marionette-collective.org/", 7 | :timeout => 10 8 | 9 | usage <<-END_OF_USAGE 10 | A discovery plugin that uses fog.io to discover against the EC2 API. 11 | 12 | In order to configure it you need a standard ~/.fog file that sets 13 | up your EC2 credentials etc. 14 | 15 | You can set plugin.ec2.region in the MCollective configuration to choose 16 | your region else it will use eu-west-1 17 | 18 | This plugin maps instance attributes to facts, instance security group 19 | names to classes and instance tags are turned into facts with the tag_ 20 | prefixed. 21 | 22 | The discovered names will be the instance private dns name and your 23 | mcollective identities should match. 24 | 25 | All nodes in eu-west: 26 | 27 | $ mco ping --dm=ec -F availability_zone=/eu-west/ 28 | 29 | All nodes with the tag cluster=charlie: 30 | 31 | $ mco ping --dm=ec2 -F tag_cluster=charlie 32 | 33 | All nodes in the 'mcollective' security group: 34 | 35 | $ mco ping --dm=ec2 -C mcollective 36 | 37 | Regular expressions are supported where sensible. 38 | END_OF_USAGE 39 | 40 | discovery do 41 | capabilities [:classes, :facts, :identity] 42 | end 43 | -------------------------------------------------------------------------------- /agent/eximng/data/domain_mailq_data.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Data 3 | class Domain_mailq_data out) 17 | 18 | shell.runcommand 19 | 20 | raise("Command #{command} failed with status #{status} and error: #{err}") unless shell.status.exitstatus == 0 21 | 22 | return out.chomp 23 | end 24 | 25 | def parse_mailq_output(output) 26 | messages = [] 27 | msg = nil 28 | 29 | output << "\n" 30 | 31 | output.each do |line| 32 | line.chomp! 33 | 34 | if line =~ /^\s*(.+?)\s+(.+?)\s+(.+-.+-.+) (<.*>)/ 35 | msg = {} 36 | msg[:recipients] = Array.new 37 | msg[:frozen] = false 38 | 39 | msg[:age] = $1 40 | msg[:size] = $2 41 | msg[:msgid] = $3 42 | msg[:sender] = $4 43 | 44 | msg[:frozen] = true if line =~ /frozen/ 45 | elsif line =~ /\s+(\S+?)@(.+)/ and msg 46 | msg[:recipients] << "#{$1}@#{$2}" 47 | elsif line =~ /^$/ && msg 48 | messages << msg 49 | msg = nil 50 | end 51 | end 52 | 53 | messages 54 | end 55 | end 56 | end 57 | end 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/plugins/agents/stomputil.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "STOMP Connector Utility Agent", 2 | :description => "Various helpers and useful actions for the STOMP connector", 3 | :author => "R.I.Pienaar ", 4 | :license => "Apache v 2.0", 5 | :version => "1.1", 6 | :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", 7 | :timeout => 20 8 | 9 | action "peer_info", :description => "Get STOMP Connection Peer" do 10 | display :always 11 | 12 | output :protocol, 13 | :description => "IP Protocol in use", 14 | :display_as => "Protocol" 15 | 16 | output :destport, 17 | :description => "Destination Port", 18 | :display_as => "Port" 19 | 20 | output :desthost, 21 | :description => "Destination Host", 22 | :display_as => "Host" 23 | 24 | output :destaddr, 25 | :description => "Destination Address", 26 | :display_as => "Address" 27 | end 28 | 29 | action "reconnect", :description => "Re-creates the connection to the STOMP network" do 30 | display :always 31 | 32 | output :restarted, 33 | :description => "Did the restart complete succesfully?", 34 | :display_as => "Restarted" 35 | end 36 | 37 | action "subscription_info", :description => "Get a list of all subscriptions" do 38 | display :always 39 | 40 | output :subscriptions, 41 | :description => "Hash of subscription related information", 42 | :display_as => "Subscriptions" 43 | end 44 | -------------------------------------------------------------------------------- /agent/eximng/web/lib/exim.rb: -------------------------------------------------------------------------------- 1 | class Exim 2 | include MCollective::RPC 3 | 4 | def initialize 5 | @exim = rpcclient("eximng") 6 | @exim.instance_eval { undef :freeze } 7 | @exim.progress = false 8 | 9 | if File.exist?("serverlist.yaml") 10 | @exim.discover :hosts => YAML.parse_file("serverlist.yaml") 11 | else 12 | raise "A yaml file with server names is needed in serverlist.yaml" 13 | end 14 | end 15 | 16 | def mailq 17 | mailq = [] 18 | 19 | @exim.mailq do |r, s| 20 | begin 21 | mailq.concat s[:data][:mailq] 22 | rescue 23 | end 24 | end 25 | 26 | return mailq 27 | end 28 | 29 | def size 30 | @exim.size 31 | end 32 | 33 | def rm(msgid) 34 | @exim.rm(:msgid => msgid) 35 | end 36 | 37 | def thaw(msgid) 38 | @exim.thaw(:msgid => msgid) 39 | end 40 | 41 | def freeze(msgid) 42 | @exim.freeze(:msgid => msgid) 43 | end 44 | 45 | def retrymsg(msgid) 46 | @exim.retrymsg(:msgid => msgid) 47 | end 48 | 49 | def rmfrozen 50 | @exim.rmfrozen 51 | end 52 | 53 | def rmbounces 54 | @exim.rmbounces 55 | end 56 | 57 | def exiwhat 58 | @exim.exiwhat 59 | end 60 | 61 | def runq(pattern=nil) 62 | if pattern 63 | @exim.runq 64 | else 65 | @exim.runq(:pattern => pattern) 66 | end 67 | end 68 | 69 | def ddl 70 | @exim.ddl 71 | end 72 | 73 | 74 | def client 75 | @exim 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/plugins/security/none.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Security 3 | # A YAML based security plugin that doesnt actually secure anything 4 | # it just does the needed serialization. 5 | # 6 | # You should *NOT* use this for every day running, this exist mostly 7 | # to make development and testing a bit less painful 8 | class None < Base 9 | require 'etc' 10 | require 'yaml' 11 | 12 | # Decodes a message by unserializing all the bits etc, it also validates 13 | # it as valid using the psk etc 14 | def decodemsg(msg) 15 | YAML.load(msg.payload) 16 | end 17 | 18 | # Encodes a reply 19 | def encodereply(sender, msg, requestid, requestcallerid=nil) 20 | YAML.dump(create_reply(requestid, sender, msg)) 21 | end 22 | 23 | # Encodes a request msg 24 | def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) 25 | request = create_request(requestid, filter, msg, @initiated_by, target_agent, target_collective, ttl) 26 | 27 | YAML.dump(request) 28 | end 29 | 30 | # Checks the md5 hash in the request body against our psk, the request sent for validation 31 | # should not have been deserialized already 32 | def validrequest?(req) 33 | @stats.validated 34 | return true 35 | end 36 | 37 | def callerid 38 | "user=#{Etc.getlogin}" 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdown 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 | var d = 'a.menu, .dropdown-toggle' 24 | 25 | function clearMenus() { 26 | $(d).parent('li').removeClass('open') 27 | } 28 | 29 | $(function () { 30 | $('html').bind("click", clearMenus) 31 | $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' ) 32 | }) 33 | 34 | /* DROPDOWN PLUGIN DEFINITION 35 | * ========================== */ 36 | 37 | $.fn.dropdown = function ( selector ) { 38 | return this.each(function () { 39 | $(this).delegate(selector || d, 'click', function (e) { 40 | var li = $(this).parent('li') 41 | , isActive = li.hasClass('open') 42 | 43 | clearMenus() 44 | !isActive && li.toggleClass('open') 45 | return false 46 | }) 47 | }) 48 | } 49 | 50 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /agent/urltest/README.md: -------------------------------------------------------------------------------- 1 | What? 2 | ===== 3 | 4 | A simple MCollective agent to do a banchmark of a specied URL. 5 | 6 | Usage? 7 | ------ 8 | 9 |
10 | % mco urltest http://www.devco.net/
11 | 
12 |  * [ ============================================================> ] 11 / 11
13 | 
14 |       Tester Location DNS      Connect    Pre-xfer   Start-xfer Total      Bytes Fetched
15 |                node3: 5.0086   0.0066     0.7277     0.7272     6.9230     101859
16 |                node8: 5.0127   0.0054     0.8764     0.8761     7.1109     101859
17 |           middleware: 5.0063   0.0044     0.8985     0.8982     7.1212     101859
18 |                node7: 5.0097   0.0082     0.9753     0.9749     7.1797     101859
19 |                node4: 5.0070   0.0069     0.8708     0.8702     7.2758     101859
20 |                node1: 5.0083   0.0077     1.0395     1.0386     7.2877     101859
21 |                node9: 5.0127   0.0061     1.1421     1.1419     7.3066     101859
22 |                node5: 5.0041   0.0052     0.9964     0.9959     7.3191     101859
23 |                node0: 5.0433   0.0051     0.9715     0.9712     7.3205     101859
24 |                node6: 5.0084   0.0050     0.9896     0.9892     7.3213     101859
25 |                node2: 5.0126   0.0063     0.9806     0.9799     7.3277     101859
26 | 
27 | Summary:
28 | 
29 |       DNS lookup time: min: 5.0041 max: 5.0433 avg: 5.0122 sdev: 0.0107
30 |      TCP connect time: min: 0.0044 max: 0.0082 avg: 0.0061 sdev: 0.0012
31 |    Time to first byte: min: 0.7277 max: 1.1421 avg: 0.9517 sdev: 0.1070
32 |    HTTP Responce time: min: 0.7272 max: 1.1419 avg: 0.9512 sdev: 0.1070
33 |      Total time taken: min: 6.9230 max: 7.3277 avg: 7.2267 sdev: 0.1296
34 | 
35 | 36 | Contact? 37 | -------- 38 | 39 | R.I.Pienaar / rip@devco.net / @ripienaar / http://devco.net/ 40 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/plugins/agents/stomputil.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Stomputil "STOMP Connector Utility Agent", 5 | :description => "Various helpers and useful actions for the STOMP connector", 6 | :author => "R.I.Pienaar ", 7 | :license => "Apache v 2.0", 8 | :version => "1.1", 9 | :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", 10 | :timeout => 20 11 | 12 | # Get the Stomp connection peer information 13 | action "peer_info" do 14 | peer = PluginManager["connector_plugin"].connection.socket.peeraddr 15 | 16 | reply[:protocol] = peer[0] 17 | reply[:destport] = peer[1] 18 | reply[:desthost] = peer[2] 19 | reply[:destaddr] = peer[3] 20 | end 21 | 22 | action "reconnect" do 23 | PluginManager["connector_plugin"].disconnect 24 | 25 | sleep 0.5 26 | 27 | PluginManager["connector_plugin"].connect 28 | 29 | ::Process.kill("USR1", $$) 30 | 31 | logger.info("Reconnected to middleware and reloaded all agents") 32 | 33 | reply[:restarted] = 1 34 | end 35 | 36 | action "subscription_info" do 37 | reply[:subscriptions] = PluginManager["connector_plugin"].connection.instance_variable_get("@subscriptions") 38 | end 39 | 40 | private 41 | def get_pid(process) 42 | pid = `pidof #{process}`.chomp.grep(/\d+/) 43 | 44 | pid.first 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /agent/urltest/agent/urltest.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'socket' 3 | 4 | module MCollective 5 | module Agent 6 | class Urltest "haproxy", 2 | :description => "Manage HAProxy through it's socket interface", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL2.0", 5 | :version => "0.1", 6 | :url => "http://devco.net", 7 | :timeout => 1 8 | 9 | requires :mcollective => "2.2.1" 10 | 11 | action "enable", :description => "Enables a server for a specific backend" do 12 | input :backend, 13 | :prompt => "Backend", 14 | :description => "Backend name", 15 | :type => :string, 16 | :validation => '^[a-zA-Z\d_]+$', 17 | :optional => false, 18 | :maxlength => 20 19 | 20 | input :server, 21 | :prompt => "Server", 22 | :description => "Server name", 23 | :type => :string, 24 | :validation => '^[a-zA-Z\d_]+$', 25 | :optional => false, 26 | :maxlength => 20 27 | 28 | output :status, 29 | :description => "Message from HAProxy if any", 30 | :display_as => "Status", 31 | :default => "OK" 32 | end 33 | 34 | action "disable", :description => "Disables a server for a specific backend" do 35 | input :backend, 36 | :prompt => "Backend", 37 | :description => "Backend name", 38 | :type => :string, 39 | :validation => '^[a-zA-Z\d_]+$', 40 | :optional => false, 41 | :maxlength => 20 42 | 43 | input :server, 44 | :prompt => "Server", 45 | :description => "Server name", 46 | :type => :string, 47 | :validation => '^[a-zA-Z\d_]+$', 48 | :optional => false, 49 | :maxlength => 20 50 | 51 | output :status, 52 | :description => "Message from HAProxy if any", 53 | :display_as => "Status", 54 | :default => "OK" 55 | end 56 | -------------------------------------------------------------------------------- /discovery/ec2/README.md: -------------------------------------------------------------------------------- 1 | What? 2 | ===== 3 | 4 | A discovery plugin that uses fog.io to discover against the EC2 API. 5 | 6 | In order to configure it you need a standard ~/.fog file that sets 7 | up your EC2 credentials etc. 8 | 9 | You can set plugin.ec2.region in the MCollective configuration to choose 10 | your region else it will use eu-west-1 11 | 12 | This plugin maps instance attributes to facts, instance security group 13 | names to classes and instance tags are turned into facts with the tag_ 14 | prefixed. 15 | 16 | The discovered names will be the instance private dns name and your 17 | mcollective identities should match. 18 | 19 | All nodes in eu-west: 20 | 21 | $ mco ping --dm=ec2 -F availability_zone=/eu-west/ 22 | 23 | All nodes with the tag cluster=charlie: 24 | 25 | $ mco ping --dm=ec2 -F tag_cluster=charlie 26 | 27 | All nodes in the 'mcollective' security group: 28 | 29 | $ mco ping --dm=ec2 -C mcollective 30 | 31 | Regular expressions are supported where sensible. 32 | 33 | Currently for a given node this is an example of the data that you can 34 | discover using the fact-like syntax: 35 | 36 | {"flavor_id"=>"m1.medium", 37 | "image_id"=>"ami-230b1b57", 38 | "state"=>"running", 39 | "monitoring"=>false, 40 | "availability_zone"=>"eu-west-1c", 41 | "placement_group"=>nil, 42 | "tenancy"=>"default", 43 | "id"=>"i-f4e9b0b9", 44 | "private_dns_name"=>"ip-10-34-192-235.eu-west-1.compute.internal", 45 | "dns_name"=>"ec2-54-228-117-74.eu-west-1.compute.amazonaws.com", 46 | "reason"=>nil, 47 | "key_name"=>"rip_aws", 48 | "ami_launch_index"=>9, 49 | "created_at"=>2013-06-28 10:11:57 UTC, 50 | "kernel_id"=>"aki-71665e05", 51 | "private_ip_address"=>"10.34.192.235", 52 | "public_ip_address"=>"54.228.117.74", 53 | "root_device_type"=>"ebs", 54 | "client_token"=>"oatGN1372414316920", 55 | "tag_cluster"=>"charlie"}] 56 | 57 | 58 | Contact? 59 | -------- 60 | 61 | R.I.Pienaar / rip@devco.net / @ripienaar / http://devco.net/ 62 | 63 | -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-tabs.js: -------------------------------------------------------------------------------- 1 | /* ======================================================== 2 | * bootstrap-tabs.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#tabs 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 | function activate ( element, container ) { 24 | container.find('.active').removeClass('active') 25 | element.addClass('active') 26 | } 27 | 28 | function tab( e ) { 29 | var $this = $(this) 30 | , href = $this.attr('href') 31 | , $ul = $this.closest('ul') 32 | , $controlled 33 | 34 | if (/^#\w+/.test(href)) { 35 | e.preventDefault() 36 | 37 | if ($this.hasClass('active')) { 38 | return 39 | } 40 | 41 | $href = $(href) 42 | 43 | activate($this.parent('li'), $ul) 44 | activate($href, $href.parent()) 45 | } 46 | } 47 | 48 | 49 | /* TABS/PILLS PLUGIN DEFINITION 50 | * ============================ */ 51 | 52 | $.fn.tabs = $.fn.pills = function ( selector ) { 53 | return this.each(function () { 54 | $(this).delegate(selector || '.tabs li > a, .pills > li > a', 'click', tab) 55 | }) 56 | } 57 | 58 | $(document).ready(function () { 59 | $('body').tabs('ul[data-tabs] li > a, ul[data-pills] > li > a') 60 | }) 61 | 62 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /agent/urltest/application/urltest.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module MCollective 3 | class Application::Urltest options) 28 | 29 | results = tester.perftest(:url => configuration[:url]) 30 | 31 | puts Util.colorize(:bold, " Tester Location DNS Connect Pre-xfer Start-xfer Total Bytes Fetched") 32 | 33 | results.sort_by{|r| r[:data][:totaltime] rescue 0}.each do |result| 34 | res = result[:data] 35 | 36 | puts "%20s: %.4f %.4f %.4f %.4f %.4f %d" % [ res[:testerlocation], res[:lookuptime], res[:connectime], res[:prexfertime], res[:startxfer], res[:totaltime], res[:bytesfetched]] 37 | end 38 | 39 | puts 40 | 41 | puts Util.colorize(:bold, "Summary:") 42 | puts 43 | 44 | puts " DNS lookup time: %s" % stats_for_field(results, :lookuptime) 45 | puts " TCP connect time: %s" % stats_for_field(results, :connectime) 46 | puts " Time to first byte: %s" % stats_for_field(results, :prexfertime) 47 | puts " HTTP Responce time: %s" % stats_for_field(results, :startxfer) 48 | puts " Total time taken: %s" % stats_for_field(results, :totaltime) 49 | 50 | puts 51 | 52 | halt tester.stats 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /agent/rndc/agent/rndc.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Rndc "rndc", 5 | :description => "SimpleRPC RNDC Agent", 6 | :author => "R.I.Pienaar ", 7 | :license => "ASL2.0", 8 | :version => "0.2", 9 | :url => "http://www.devco.net/", 10 | :timeout => 5 11 | 12 | def startup_hook 13 | @rndc = "/usr/sbin/rndc" 14 | end 15 | 16 | ["reload", "freeze", "thaw"].each do |cmd| 17 | action cmd do 18 | optional_zone_command(cmd) 19 | end 20 | end 21 | 22 | ["refresh", "retransfer", "notify", "sign"].each do |cmd| 23 | action cmd do 24 | zone_command(cmd) 25 | end 26 | end 27 | 28 | ["reconfig", "querylog", "flush"].each do |cmd| 29 | action cmd do 30 | rndc(cmd) 31 | end 32 | end 33 | 34 | action "status" do 35 | out = "" 36 | err = "" 37 | 38 | ret = run("#{@rndc} status", :stdout => out, :err => err, :chomp => true) 39 | 40 | reply.fail! "Failed to get rndc status: #{err}" if ret > 0 41 | 42 | out.lines.each do |line| 43 | if line =~ /^(.+?): (.+)/ 44 | val = $2 45 | var = $1.gsub(" ", "_").downcase.to_sym 46 | reply[var] = val 47 | end 48 | end 49 | end 50 | 51 | private 52 | def zone_command(command) 53 | validate :zone, :shellsafe 54 | 55 | rndc(command, request[:zone]) 56 | end 57 | 58 | def optional_zone_command(command) 59 | if request.include?(:zone) 60 | zone_command(command) 61 | else 62 | rndc(command) 63 | end 64 | end 65 | 66 | def rndc(command, options=nil) 67 | cmd = [@rndc, command, options].flatten.compact.join(" ") 68 | 69 | Log.info("Running #{cmd}") 70 | 71 | status = run(cmd, :stdout => :out, :stderr => :err, :chomp => true) 72 | 73 | reply.fail! "#{cmd} returned #{status}" if status > 0 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /connector/redis/registration/redis.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Registration 3 | # A registration plugin that sends in all the metadata we have for a node 4 | # to redis, this will only work with the Redis connector and no other 5 | # connector 6 | # 7 | # Metadata being sent: 8 | # 9 | # - all facts 10 | # - all agents 11 | # - all classes (if applicable) 12 | # - the configured identity 13 | # - the list of collectives the nodes belong to 14 | # - last seen time 15 | # 16 | # Keys will be set to expire (2 * registration interval) + 2 17 | class Redis [], 20 | :facts => {}, 21 | :classes => [], 22 | :collectives => []} 23 | 24 | identity = Config.instance.identity 25 | 26 | cfile = Config.instance.classesfile 27 | 28 | if File.exist?(cfile) 29 | data[:classes] = File.readlines(cfile).map {|i| i.chomp} 30 | end 31 | 32 | data[:identity] = Config.instance.identity 33 | data[:agentlist] = Agents.agentlist 34 | data[:facts] = PluginManager["facts_plugin"].get_facts 35 | data[:collectives] = Config.instance.collectives.sort 36 | 37 | commit = lambda do |redis| 38 | begin 39 | time = Time.now.utc.to_i 40 | 41 | redis.multi do 42 | data[:collectives].each {|c| redis.zadd "mcollective::collective::#{c}", time, data[:identity]} 43 | data[:agentlist].each {|a| redis.zadd "mcollective::agent::#{a}", time, data[:identity]} 44 | data[:classes].each {|c| redis.zadd "mcollective::class::#{c}", time, data[:identity]} 45 | 46 | redis.del "mcollective::facts::#{data[:identity]}" 47 | redis.hmset "mcollective::facts::#{data[:identity]}", data[:facts].map{|k, v| [k.to_s, v.to_s]}.flatten 48 | end 49 | rescue => e 50 | Log.error("%s: %s: %s" % [e.backtrace.first, e.class, e.to_s]) 51 | end 52 | end 53 | 54 | PluginManager["connector_plugin"].sender_queue << {:command => :proc, :proc => commit} 55 | nil 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /agent/urltest/agent/urltest.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "urltest", 2 | :description => "Agent that connects to a URL and returns some statistics", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "2.0.1", 6 | :url => "https://github.com/ripienaar/mc-plugins", 7 | :timeout => 60 8 | 9 | requires :mcollective => "2.2.1" 10 | 11 | action "perftest", :description => "Perform URL test" do 12 | display :always 13 | 14 | input :url, 15 | :prompt => "URL", 16 | :description => "The URL to test, only http is supported", 17 | :type => :string, 18 | :validation => '^http:\/\/', 19 | :optional => false, 20 | :maxlength => 120 21 | 22 | output :lookuptime, 23 | :description => "Time to perform DNS lookup", 24 | :display_as => "DNS lookup time" 25 | 26 | output :connectime, 27 | :description => "Time to open TCP connection", 28 | :display_as => "TCP connect time" 29 | 30 | output :prexfertime, 31 | :description => "Time between socket open and first reply", 32 | :display_as => "Pre transfer time" 33 | 34 | output :startxfer, 35 | :description => "Time between between sending the request and receiving reply", 36 | :display_as => "Request wait time" 37 | 38 | output :bytesfetched, 39 | :description => "Size of the reply in bytes", 40 | :display_as => "Bytes" 41 | 42 | output :totaltime, 43 | :description => "Total test time", 44 | :display_as => "Total time" 45 | 46 | output :testerlocation, 47 | :description => "Location where the server is based", 48 | :display_as => "Tested from" 49 | 50 | summarize do 51 | aggregate average(:lookuptime, :format => "Average DNS lookup time: %0.6f") 52 | aggregate average(:connectime, :format => "Average TCP connect time: %0.6f") 53 | aggregate average(:prexfertime, :format => "Average time to first byte: %0.6f") 54 | aggregate average(:startxfer, :format => "Average HTTP response time: %0.6f") 55 | aggregate average(:totaltime, :format => "Average total test time: %0.6f") 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /agent/eximng/web/views/generic_result_view.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @action_ddl = @ddl.action_interface(@action) 3 | @ok_count = 0 4 | @error_count = 0 5 | @results.each do |r| 6 | @ok_count += 1 if r[:statuscode] == 0 7 | @error_count += 1 if r[:statuscode] > 0 8 | end 9 | %> 10 | 11 | <% if @error_count > 0 && @ok_count == 0 %> 12 |
13 | <% elsif @error_count > 0 && @ok_count > 0 %> 14 |
15 | <% elsif @error_count == 0 && @ok_count == 0 %> 16 |
17 | <% else %> 18 |
19 | <% end %> 20 |

<%= @ok_count %> successful and <%= @error_count %> failed responses

21 |
22 | 23 | <% if @ok_count > 0 %> 24 |
Successful Responses
25 | 26 | 27 | 28 | 29 | <% @action_ddl[:output].keys.sort.each do |field| %> 30 | 31 | <% end %> 32 | 33 | 34 | 35 | <% @results.select{|r| r[:statuscode] == 0}.each do |result| %> 36 | 37 | 38 | <% @action_ddl[:output].keys.sort.each do |field| %> 39 | 42 | <% end %> 43 | 44 | <% end %> 45 | 46 |
Sender<%= @action_ddl[:output][field][:display_as] %>
<%= result[:sender] %> 40 | <%= display_result result[:data][field] %> 41 |
47 | <% end %> 48 | 49 | <% if @error_count > 0 %> 50 |
Failed Responses
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | <% @results.select{|r| r[:statuscode] > 0}.each do |err| %> 60 | 61 | 62 | 63 | 64 | <% end %> 65 | 66 |
SenderError
<%= label_for_code err[:statuscode] %> <%= err[:sender] %><%= err[:statusmsg] %>
67 | <% end %> 68 | 69 | <% unless request.referer == "/" %> 70 | Back 71 | <% end %> 72 | -------------------------------------------------------------------------------- /agent/vcsmgr/vcsmgr.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Vcsmgr "Version Control System Manager", 7 | :description => "Manages checkouts of code managed by VCS tools", 8 | :author => "R.I.Pienaar ", 9 | :license => "Apache 2", 10 | :version => "0.1", 11 | :url => "http://mcollective-plugins.googlecode.com/", 12 | :timeout => 90 13 | 14 | def startup_hook 15 | @svn = @config.pluginconf["vcsmgr.svn"] || "/usr/bin/svn" 16 | end 17 | 18 | action "svn_info" do 19 | validate :path, :shellsafe 20 | 21 | reply.data.merge!(svn_info(request[:path])) 22 | end 23 | 24 | action "svn_update" do 25 | validate :path, :shellsafe 26 | 27 | svn_validcheckout?(request[:path]) 28 | 29 | svnout = %x[#{@svn} up #{request[:path]}].chomp 30 | svnexit = $?.exitstatus 31 | 32 | reply.fail "svn up failed with exit code #{svnexit}" unless svnexit == 0 33 | 34 | reply[:svnout] = svnout 35 | 36 | reply.data.merge!(svn_info(request[:path])) 37 | end 38 | 39 | private 40 | def svn_validcheckout?(path) 41 | raise "Path doesn't exist: #{path}" unless File.exist?(path) 42 | raise "Path doesn't look like a svn checkout" unless File.exist?([path, ".svn"].join(File::SEPARATOR)) 43 | end 44 | 45 | def svn_info(path) 46 | svn_validcheckout?(path) 47 | 48 | cmd = "#{@svn} info #{path}" 49 | info = YAML.load(%x[#{@svn} info #{path}]) 50 | response = {} 51 | 52 | {"Last Changed Author" => :author, 53 | "URL" => :url, 54 | "Repository Root" => :root, 55 | "Revision" => :revision, 56 | "Last Changed Date" => :date, 57 | "Path" => :path}.each do |k,v| 58 | response[v] = info[k] || "" 59 | end 60 | 61 | response 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /registration/mcollective-registration-receiver.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'mcollective' 4 | 5 | module MCollective 6 | class Util::RegistrationDaemon 7 | def initialize 8 | oparser = Optionparser.new 9 | options = oparser.parse 10 | 11 | @config = Config.instance 12 | @config.loadconfig(options[:config]) 13 | 14 | @connector = PluginManager["connector_plugin"] 15 | @connector.connect 16 | 17 | queue = Config.instance.pluginconf["registration_daemon_queue"] || "/queue/mcollective.registration" 18 | Log.info("Subscribing to #{queue} for new events") 19 | 20 | @connector.connection.subscribe(queue) 21 | 22 | Util.loadclass("MCollective::Agent::Registration") 23 | 24 | @agent = Agent::Registration.new 25 | 26 | Log.info("MCollective Registration daemon started") 27 | end 28 | 29 | def daemonize 30 | fork do 31 | Process.setsid 32 | exit if fork 33 | Dir.chdir('/tmp') 34 | STDIN.reopen('/dev/null') 35 | STDOUT.reopen('/dev/null', 'a') 36 | STDERR.reopen('/dev/null', 'a') 37 | 38 | yield 39 | end 40 | end 41 | 42 | def run 43 | if Config.instance.daemonize 44 | daeminize { receive_loop } 45 | else 46 | receive_loop 47 | end 48 | end 49 | 50 | def receive_loop 51 | loop do 52 | begin 53 | msg = @connector.connection.receive 54 | 55 | start_time = Time.now 56 | 57 | registration_data = JSON.load(msg.body) 58 | 59 | raise RPCAborted, "Did not receive a FQDN fact" unless registration_data["facts"].include?("fqdn") 60 | 61 | sender = registration_data["facts"]["fqdn"] 62 | registration_msg = {:senderid => sender, :body => registration_data} 63 | 64 | begin 65 | @agent.handlemsg(registration_msg, @connector) 66 | rescue Exception => e 67 | raise RPCAborted, "registration raised an unexpected exception: #{e.class}: #{e}" 68 | end 69 | 70 | Log.info("Processed registration data from %s in %.2f seconds" % [sender, (Time.now - start_time).to_f]) 71 | 72 | rescue RPCAborted 73 | Log.warn("Failed to handle registration data: #{e}") 74 | rescue Interrupt 75 | Log.info("Exiting on interrupt signal") 76 | exit 77 | rescue Exception => e 78 | Log.error("Unexpected Exception: #{e.class}: #{e}") 79 | sleep 1 80 | end 81 | end 82 | end 83 | end 84 | end 85 | 86 | receiver = MCollective::Util::RegistrationDaemon.new 87 | 88 | receiver.run 89 | -------------------------------------------------------------------------------- /agent/vcsmgr/vcsmgr.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "Version Control System Manager", 2 | :description => "Manages checkouts of code managed by VCS tools", 3 | :author => "R.I.Pienaar ", 4 | :license => "Apache 2", 5 | :version => "0.1", 6 | :url => "http://mcollective-plugins.googlecode.com/", 7 | :timeout => 90 8 | 9 | action "svn_update", :description => "Performs an SVN update on an existing checkout" do 10 | display :always 11 | 12 | input :path, 13 | :prompt => "Path to update", 14 | :description => "The directory containing the SVN checkout", 15 | :type => :string, 16 | :validation => '^.+$', 17 | :optional => false, 18 | :maxlength => 150 19 | 20 | output :svnout, 21 | :description => "Output from SVN", 22 | :display_as => "Output" 23 | 24 | output :author, 25 | :description => "Latest Author", 26 | :display_as => "Author" 27 | 28 | output :url, 29 | :description => "Repository URL", 30 | :display_as => "URL" 31 | 32 | output :root, 33 | :description => "Repository root", 34 | :display_as => "Root" 35 | 36 | output :revision, 37 | :description => "Current Revision", 38 | :display_as => "Revision" 39 | 40 | output :date, 41 | :description => "Latest Date", 42 | :display_as => "Date" 43 | 44 | output :path, 45 | :description => "Path to the repository", 46 | :display_as => "Path" 47 | end 48 | 49 | action "svn_info", :description => "Get revision info for an existing SVN checkout" do 50 | display :always 51 | 52 | input :path, 53 | :prompt => "Path to update", 54 | :description => "The directory containing the SVN checkout", 55 | :type => :string, 56 | :validation => '^.+$', 57 | :optional => false, 58 | :maxlength => 150 59 | 60 | output :author, 61 | :description => "Latest Author", 62 | :display_as => "Author" 63 | 64 | output :url, 65 | :description => "Repository URL", 66 | :display_as => "URL" 67 | 68 | output :root, 69 | :description => "Repository root", 70 | :display_as => "Root" 71 | 72 | output :revision, 73 | :description => "Current Revision", 74 | :display_as => "Revision" 75 | 76 | output :date, 77 | :description => "Latest Date", 78 | :display_as => "Date" 79 | 80 | output :path, 81 | :description => "Path to the repository", 82 | :display_as => "Path" 83 | end 84 | -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-popover.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-popover.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#popover 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 | var Popover = function ( element, options ) { 24 | this.$element = $(element) 25 | this.options = options 26 | this.enabled = true 27 | this.fixTitle() 28 | } 29 | 30 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TWIPSY.js 31 | ========================================= */ 32 | 33 | Popover.prototype = $.extend({}, $.fn.twipsy.Twipsy.prototype, { 34 | 35 | setContent: function () { 36 | var $tip = this.tip() 37 | $tip.find('.title')[this.options.html ? 'html' : 'text'](this.getTitle()) 38 | $tip.find('.content p')[this.options.html ? 'html' : 'text'](this.getContent()) 39 | $tip[0].className = 'popover' 40 | } 41 | 42 | , getContent: function () { 43 | var content 44 | , $e = this.$element 45 | , o = this.options 46 | 47 | if (typeof this.options.content == 'string') { 48 | content = $e.attr(o.content) 49 | } else if (typeof this.options.content == 'function') { 50 | content = this.options.content.call(this.$element[0]) 51 | } 52 | return content 53 | } 54 | 55 | , tip: function() { 56 | if (!this.$tip) { 57 | this.$tip = $('
') 58 | .html('

') 59 | } 60 | return this.$tip 61 | } 62 | 63 | }) 64 | 65 | 66 | /* POPOVER PLUGIN DEFINITION 67 | * ======================= */ 68 | 69 | $.fn.popover = function (options) { 70 | if (typeof options == 'object') options = $.extend({}, $.fn.popover.defaults, options) 71 | $.fn.twipsy.initWith.call(this, options, Popover, 'popover') 72 | return this 73 | } 74 | 75 | $.fn.popover.defaults = $.extend({} , $.fn.twipsy.defaults, { content: 'data-content', placement: 'right'}) 76 | 77 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-alerts.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alerts.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 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 | /* ALERT CLASS DEFINITION 52 | * ====================== */ 53 | 54 | var Alert = function ( content, selector ) { 55 | this.$element = $(content) 56 | .delegate(selector || '.close', 'click', this.close) 57 | } 58 | 59 | Alert.prototype = { 60 | 61 | close: function (e) { 62 | var $element = $(this).parent('.alert-message') 63 | 64 | e && e.preventDefault() 65 | $element.removeClass('in') 66 | 67 | function removeElement () { 68 | $element.remove() 69 | } 70 | 71 | $.support.transition && $element.hasClass('fade') ? 72 | $element.bind(transitionEnd, removeElement) : 73 | removeElement() 74 | } 75 | 76 | } 77 | 78 | 79 | /* ALERT PLUGIN DEFINITION 80 | * ======================= */ 81 | 82 | $.fn.alert = function ( options ) { 83 | 84 | if ( options === true ) { 85 | return this.data('alert') 86 | } 87 | 88 | return this.each(function () { 89 | var $this = $(this) 90 | 91 | if ( typeof options == 'string' ) { 92 | return $this.data('alert')[options]() 93 | } 94 | 95 | $(this).data('alert', new Alert( this )) 96 | 97 | }) 98 | } 99 | 100 | $(document).ready(function () { 101 | new Alert($('body'), '.alert-message[data-alert] .close') 102 | }) 103 | 104 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-scrollspy.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-scrollspy.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#scrollspy 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 | var $window = $(window) 24 | 25 | function ScrollSpy( topbar, selector ) { 26 | var processScroll = $.proxy(this.processScroll, this) 27 | this.$topbar = $(topbar) 28 | this.selector = selector || 'li > a' 29 | this.refresh() 30 | this.$topbar.delegate(this.selector, 'click', processScroll) 31 | $window.scroll(processScroll) 32 | this.processScroll() 33 | } 34 | 35 | ScrollSpy.prototype = { 36 | 37 | refresh: function () { 38 | this.targets = this.$topbar.find(this.selector).map(function () { 39 | var href = $(this).attr('href') 40 | return /^#\w/.test(href) && $(href).length ? href : null 41 | }) 42 | 43 | this.offsets = $.map(this.targets, function (id) { 44 | return $(id).offset().top 45 | }) 46 | } 47 | 48 | , processScroll: function () { 49 | var scrollTop = $window.scrollTop() + 10 50 | , offsets = this.offsets 51 | , targets = this.targets 52 | , activeTarget = this.activeTarget 53 | , i 54 | 55 | for (i = offsets.length; i--;) { 56 | activeTarget != targets[i] 57 | && scrollTop >= offsets[i] 58 | && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) 59 | && this.activateButton( targets[i] ) 60 | } 61 | } 62 | 63 | , activateButton: function (target) { 64 | this.activeTarget = target 65 | 66 | this.$topbar 67 | .find(this.selector).parent('.active') 68 | .removeClass('active') 69 | 70 | this.$topbar 71 | .find(this.selector + '[href="' + target + '"]') 72 | .parent('li') 73 | .addClass('active') 74 | } 75 | 76 | } 77 | 78 | /* SCROLLSPY PLUGIN DEFINITION 79 | * =========================== */ 80 | 81 | $.fn.scrollSpy = function( options ) { 82 | var scrollspy = this.data('scrollspy') 83 | 84 | if (!scrollspy) { 85 | return this.each(function () { 86 | $(this).data('scrollspy', new ScrollSpy( this, options )) 87 | }) 88 | } 89 | 90 | if ( options === true ) { 91 | return scrollspy 92 | } 93 | 94 | if ( typeof options == 'string' ) { 95 | scrollspy[options]() 96 | } 97 | 98 | return this 99 | } 100 | 101 | $(document).ready(function () { 102 | $('body').scrollSpy('[data-scrollspy] li > a') 103 | }) 104 | 105 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /application/plot/README.md: -------------------------------------------------------------------------------- 1 | GNU Plotter for MCollective Data 2 | ================================ 3 | 4 | A small tool to plot data that can be gathered via MCollective data plugins. 5 | 6 | Example 7 | ------- 8 | 9 | If you have the Puppet agent install on your infrastructure you can use this 10 | application to examine the behavior of your Puppet infrastructure. 11 | 12 | $ mco plot resource config_retrieval_time --np 13 | 14 | Information about Puppet managed resources 15 | Nodes 16 | 4 ++------*--------+-------+--------+-------+-------+--------+------++ 17 | + * + + + + + + + 18 | 3.5 ++ * * ++ 19 | | * * | 20 | 3 ++ * * * ++ 21 | | * * * * | 22 | | * * * * | 23 | 2.5 ++ * * * * ++ 24 | | * * * * | 25 | 2 ++ * ** * * **** ++ 26 | | * * * * * | 27 | 1.5 ++ * * * * * ++ 28 | | ** * * * | 29 | 1 ++ * ********* * * * ++ 30 | | * * * * | 31 | | * * * * | 32 | 0.5 ++ * * * * ++ 33 | + + + + + * + * + * *+ + 34 | 0 ++------+--------+-------+--------+----*********--+------*-+------++ 35 | 0 5 10 15 20 25 30 35 40 36 | Config Retrieval Time 37 | 38 | This shows you that the time to retrieve the configuration from my master is 39 | generally fast but there are some slowdown of nodes taking > 25 seconds. 40 | 41 | We can interogate the network and ask it which machines this is: 42 | 43 | $ mco find -S "resource().config_retrieval_time > 25" 44 | dev2.example.net 45 | . 46 | . 47 | . 48 | 49 | This shows how you can first view and then dig into the graph and find nodes 50 | matching it. 51 | 52 | To see what data you can plot use the *mco plugin doc* application: 53 | 54 | $ mco plugin doc 55 | . 56 | . 57 | Data Queries: 58 | agent Meta data about installed MColletive Agents 59 | augeas_match Allows agents and discovery to do Augeas match lookups 60 | domain_mailq Checks the mailq for mail to a certain domain 61 | fstat Retrieve file stat data for a given file 62 | nrpe Checks the exit codes of executed Nrpe commands 63 | puppet Information about Puppet agent state 64 | resource Information about Puppet managed resources 65 | sysctl Retrieve values for a given sysctl 66 | 67 | Any numeric data in these data sources can be plotted, see *mco plugin doc 68 | data/puppet* to get details about a specific plugin. 69 | -------------------------------------------------------------------------------- /discovery/ec2/ec2.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | class Discovery 3 | class Ec2 4 | require 'fog' 5 | 6 | class << self 7 | def discover(filter, timeout, limit=0, client=nil) 8 | config = Config.instance 9 | 10 | region = config.pluginconf.fetch("ec2.region", "eu-west-1") 11 | connection = Fog::Compute.new({:provider => 'AWS', :region => region}) 12 | servers = simplify_servers(connection.servers) 13 | 14 | found = [] 15 | 16 | filter.keys.each do |key| 17 | case key 18 | when "fact" 19 | fact_search(filter["fact"], servers, found) 20 | 21 | when "cf_class" 22 | class_search(filter["cf_class"], servers, found) 23 | 24 | when "identity" 25 | identity_search(filter["identity"], servers, found) 26 | end 27 | end 28 | 29 | # filters are combined so we get the intersection of values across 30 | # all matches found using fact, agent and identity filters 31 | found.inject(found[0]){|x, y| x & y} 32 | end 33 | 34 | def fact_search(filter, servers, found) 35 | return if filter.empty? 36 | 37 | matched = [] 38 | 39 | filter.each do |f| 40 | fact = f[:fact] 41 | value = f[:value] 42 | operator = f[:operator] 43 | 44 | servers.each do |server| 45 | case operator 46 | when "==" 47 | matched << server["private_dns_name"] if server[fact] == value 48 | when "=~" 49 | matched << server["private_dns_name"] if server[fact] =~ regexy_string(value) 50 | when "!=" 51 | matched << server["private_dns_name"] unless server[fact] == value 52 | else 53 | raise "Cannot perform '%s' matches for facts using the ec2 discovery method" % f[:operator] 54 | end 55 | end 56 | end 57 | 58 | found << matched 59 | end 60 | 61 | def class_search(filter, servers, found) 62 | return if filter.empty? 63 | 64 | matched = [] 65 | 66 | filter.each do |f| 67 | servers.each do |server| 68 | [server["groups"]].flatten.each do |group| 69 | matched << server["private_dns_name"] if group.match(regexy_string(f)) 70 | end 71 | end 72 | end 73 | 74 | found << matched.compact 75 | end 76 | 77 | def identity_search(filter, servers, found) 78 | return if filter.empty? 79 | 80 | matched = [] 81 | 82 | filter.each do |f| 83 | servers.each do |server| 84 | matched << server["private_dns_name"] if server["private_dns_name"].match(regexy_string(f)) 85 | end 86 | end 87 | 88 | found << matched.compact 89 | end 90 | 91 | def simplify_servers(servers) 92 | servers.select{|s| s.state == "running"}.map do |server| 93 | hsh = {} 94 | 95 | server.attributes.each do |attribute, value| 96 | hsh[attribute.to_s] = value 97 | end 98 | 99 | server.attributes[:tags].each do |tag, value| 100 | hsh["tag_%s" % tag] = value if value 101 | end 102 | 103 | hsh 104 | end 105 | end 106 | 107 | def regexy_string(string) 108 | if string.match("^/") 109 | Regexp.new(string.gsub("\/", "")) 110 | else 111 | string 112 | end 113 | end 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /agent/eximng/web/views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |

Exim Collective

20 | 54 |
55 |
56 |
57 |
58 | 59 |
60 | 68 |
69 | <% if @error %> 70 |
71 | <%= @error %> 72 |
73 | <% end %> 74 | <%= yield %> 75 |
76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /agent/ts/agent/ts.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Ts "Task Scheduler Agent", 5 | :description => "An agent to create and manage jobs for Task Scheduler", 6 | :author => "R.I.Pienaar ", 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!(/^$/, "") 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 = $('