├── README ├── clients ├── generic_chefresource.rb ├── mc-cluster-ssh ├── mc-xen ├── mc-xen-balancer └── mc-xmpp-bridge ├── etc └── xen-mappings.csv └── plugins ├── agents ├── chef-package.rb ├── chef-resource.rb ├── chef-service.rb ├── chef.rb ├── smith.rb ├── svnagent.ddl ├── svnagent.rb ├── xenagent.ddl └── xenagent.rb └── util └── actionpolicy.rb /README: -------------------------------------------------------------------------------- 1 | My mcollective stuff 2 | 3 | Agents : 4 | - xenagent : to get information from xen dom0s, and migrate VMs. Needs the xen gem 5 | located at http://github.com/rottenbytes/ruby-xen 6 | - smith : just a toy 7 | - chef-service : handle services using chef provider 8 | - chef-package : handle packages using chef provider 9 | - chef-resource : generic handler for a chef resource 10 | 11 | Clients : 12 | - mc-xen : search for dom0 & domUs 13 | - mc-xen-balancer : a load balancer, vmware RDS inspired, proof of concept 14 | 15 | Etc : 16 | - xen-mappings.csv : used by mc-xen-balancer 17 | -------------------------------------------------------------------------------- /clients/generic_chefresource.rb: -------------------------------------------------------------------------------- 1 | require 'mcollective' 2 | include MCollective::RPC 3 | 4 | mc = rpcclient("chefresource") 5 | mc.progress = false 6 | 7 | r = [ { "action" => "restart" }, { "supports" => {:status => true } } ] 8 | 9 | mc.handle(:resourcename => "cron", :resourcetype => "service", :resourceactions => r).each do |resp| 10 | puts resp[:sender] + " => " + resp[:status].inspect 11 | end 12 | -------------------------------------------------------------------------------- /clients/mc-cluster-ssh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # You need cssh installed for this one ! 4 | # By nico 5 | 6 | require 'mcollective' 7 | include MCollective::RPC 8 | 9 | ssh_clients = "" 10 | 11 | oparser = MCollective::Optionparser.new({}, "filter") 12 | 13 | options = oparser.parse{|parser, options| 14 | parser.define_head "Find hosts matching criteria" 15 | parser.banner = "Usage: mc-find-hosts [options] [filters]" 16 | } 17 | 18 | client = MCollective::Client.new(options[:config]) 19 | client.options = options 20 | 21 | stats = client.req("ping", "discovery") do |resp| 22 | ssh_clients+=resp[:senderid]+" " 23 | end 24 | 25 | `cssh #{ssh_clients}` 26 | -------------------------------------------------------------------------------- /clients/mc-xen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'mcollective' 4 | include MCollective::RPC 5 | 6 | options = rpcoptions do |parser, options| 7 | parser.define_head "Xen Client" 8 | parser.banner = "Usage: [options] [filters]" 9 | 10 | parser.on('-a', '--action ACTION', 'action to run') do |v| 11 | options[:action] = v 12 | end 13 | 14 | parser.on('-d', '--domu NAME', 'domU name to search') do |v| 15 | options[:name] = v 16 | end 17 | end 18 | 19 | unless options.include?(:action) 20 | puts "You must pass at least an action" 21 | exit! 1 22 | end 23 | 24 | if options[:action] == "find" then 25 | unless options.include?(:name) 26 | puts("You need to specify a domU to find with --domu|-d") 27 | exit! 1 28 | end 29 | end 30 | 31 | mc = rpcclient("xenagent") 32 | mc.progress = false 33 | client = mc.client 34 | 35 | if options[:action] == "find" then 36 | mc.find(:name => options[:name]).each do |resp| 37 | printf("%-25s: %s\n", resp[:sender], resp[:data]) 38 | end 39 | end 40 | 41 | if options[:action] == "list" then 42 | mc.list.each do |resp| 43 | printf("%-25s\n", resp[:sender]) 44 | # drop domain 0 from list 45 | resp[:data][:slices].delete("Domain-0") 46 | if resp[:data][:slices] != [] then 47 | resp[:data][:slices].each { |d| 48 | puts "\t #{d}" 49 | } 50 | else 51 | puts "\t no domU running" 52 | end 53 | puts "" 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /clients/mc-xen-balancer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A (wannabe) load balancer for Xen host 4 | # based on mcollective, and inspired by VmWare features 5 | 6 | require 'mcollective' 7 | require 'csv' 8 | 9 | include MCollective::RPC 10 | 11 | mc = rpcclient("xenagent") 12 | mc.progress = false 13 | 14 | @config = { :interval => 30, # polling interval 15 | :load_threshold => 0.4, # cpu max load 16 | :daemonize => false, # do we go in background ? 17 | :max_over => 1, # max MINUTES where load is over threshold. 18 | # this should be _at least_ interval*2 19 | :debug => true, # being verbose 20 | :max_vm_per_host => 10, # try not to go over 9000^Wthis many VMs per host 21 | :max_load_candidate => 0.8, # if we reach this load we're 22 | # no more eligible as a host for vms 23 | :host_mapping => "/etc/mcollective/xen-mappings.csv" # the hypervisor name / IP mapping 24 | } 25 | 26 | @load_counter = {} 27 | @domu_times = {} 28 | @domu_counter = {} 29 | @hypervisors = {} 30 | 31 | def load_hypervisors() 32 | CSV.read(@config[:host_mapping]).find_all { |r| 33 | @hypervisors[r[0].to_s]=r[1].to_s 34 | } 35 | end 36 | 37 | def debug(msg) 38 | if @config[:debug] then 39 | puts "[+] " + msg 40 | end 41 | end 42 | 43 | def log(msg) 44 | puts msg 45 | end 46 | 47 | def choose_from_times(hostname) 48 | if !@domu_times_used.empty? then 49 | highest = @domu_times_used.sort_by { |v| v[1] } 50 | debug("VM key : " + highest[-1][0]) 51 | debug("Time consumed in a run (interval is "+@config[:interval].to_s+"s) : " + highest[-1][1].to_s) 52 | domu_name = highest[-1][0].to_s.sub(/^#{hostname}-/,"") 53 | else 54 | nil 55 | end 56 | end 57 | 58 | loop do 59 | load_hypervisors() 60 | 61 | mc.stat.each do |resp| 62 | # clean up time consumption at each loop 63 | @domu_times_used = {} 64 | # know how many VMs each host has (may be used when migrating) 65 | @domu_counter[resp[:sender]]=resp[:data][:slices].count 66 | 67 | debug(resp[:sender] + " : " + resp[:data][:load].to_s + " load and " + @domu_counter[resp[:sender]].to_s + " slice(s) running") 68 | 69 | # Do we hit the limit ? 70 | if resp[:data][:load] > @config[:load_threshold] then 71 | if @load_counter[resp[:sender]] then 72 | @load_counter[resp[:sender]] +=1 73 | else # init/reset it, we want load spikes to be consecutives 74 | @load_counter[resp[:sender]] = 1 75 | end 76 | else # if no reinit counter 77 | @load_counter[resp[:sender]] = 0 78 | debug("init/reset load counter for " + resp[:sender]) 79 | end 80 | 81 | # store & calculate domU time consumption 82 | if !resp[:data][:times].empty? then 83 | resp[:data][:times].each_pair { |k,v| 84 | v=v.to_f 85 | key=resp[:sender]+"-"+k 86 | # does not have a value for this domU on this dom0 87 | if !@domu_times.has_key?(key) then 88 | @domu_times[key]=v 89 | @domu_times_used[key]=0 90 | debug("added #{k} on #{resp[:sender]} with 0 CPU time (registered #{@domu_times[key]} as a reference)") 91 | else # we have one, calculate time consumption 92 | @domu_times_used[key]=v-@domu_times[key] 93 | @domu_times[key]=v 94 | debug("updated #{k} on #{resp[:sender]} with #{@domu_times_used[key]} CPU time eaten (registered #{@domu_times[key]} as a reference)") 95 | end 96 | } 97 | else 98 | debug("#{resp[:sender]} has no slices consuming CPU time") 99 | end 100 | end # End of mc.stat loop 101 | 102 | # Time for analysis & decision 103 | @load_counter.each_pair { |k,v| 104 | # Yes, this machine has reached the limit 105 | if v > (@config[:max_over]*60)/@config[:interval] then 106 | debug("#{k} has #{v} threshold overload") 107 | debug("Time to see if we can migrate a VM from #{k}") 108 | vm=choose_from_times(k) 109 | # now look for a new home 110 | if vm != nil 111 | # Let's exclude those that don't match config criterias 112 | # first, max vms : 113 | host_list=@domu_counter.map { |d| if d[1] < @config[:max_vm_per_host] then d[0] end } 114 | host_list.delete(nil) # drop nil 115 | host_list.delete(k) # drop ourself 116 | if !host_list.empty? and @config[:debug] then 117 | host_list.each { |h| 118 | debug("#{h} is a candidate for being a host (step 1 : max VMs)") 119 | } 120 | end 121 | # next, max load 122 | load_excluded = @load_counter.map { |l| if l[1] > @config[:max_load_candidate] then l[0] end } 123 | host_list -= load_excluded 124 | if !host_list.empty? and @config[:debug] then 125 | host_list.each { |h| 126 | debug("#{h} is a candidate for being a host (step 2 : max load)") 127 | } 128 | 129 | # there is at least 1 host, so let's sort by load and take the first one 130 | new_host = @load_counter.sort_by { |h| h[1] }[0][0] 131 | if @hypervisors.has_key? new_host then 132 | new_host_ip = @hypervisors[new_host] 133 | else 134 | puts "FIXME : implement DNS resolution" 135 | exit! 136 | end 137 | 138 | log("trying to migrate #{vm} from #{k} to #{new_host} (#{new_host_ip})") 139 | # We create a new client, with filters 140 | mc_migration = rpcclient("xenagent") 141 | mc_migration.progress = false 142 | mc_migration.verbose = false 143 | mc_migration.fact_filter("hostname",k) 144 | result = mc_migration.migrate(:slice => vm, :newhost => new_host_ip) 145 | if result[0][:data][:result] == true then 146 | log("Successfully migrated #{vm} !") 147 | else 148 | log("Failed to migrate #{vm}. You should take a look") 149 | end 150 | 151 | # VM migrated, reset load_counter 152 | @load_counter[k]=0 153 | # drop times values 154 | @domu_times.delete(vm) 155 | @domu_times_used.delete(vm) 156 | else 157 | debug("no more candidates, doing nothing...") 158 | end 159 | else 160 | debug("could not guess the name of the VM to migrate, weird") 161 | exit! 1 162 | end 163 | end 164 | } 165 | 166 | 167 | 168 | debug("sleeping for " + @config[:interval].to_s + " seconds") 169 | log("") 170 | sleep(@config[:interval]) 171 | end 172 | 173 | -------------------------------------------------------------------------------- /clients/mc-xmpp-bridge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'mcollective' 4 | include MCollective::RPC 5 | require 'rubygems' 6 | require 'jabber/bot' 7 | 8 | bot = Jabber::Bot.new( 9 | :jabber_id => "bot@jabber.qualigaz.com/Mcollective", 10 | :password => "bot", 11 | :master => "nico@jabber.qualigaz.com", 12 | :is_public => true 13 | ) 14 | 15 | bot.add_command( 16 | :syntax => 'find', 17 | :description => 'find hosts', 18 | :regex => /^find$/, 19 | :is_public => true 20 | ) { 21 | hosts = "\n---- hosts ----\n" 22 | mc=rpcclient("discovery") 23 | mc.progress=false 24 | mc.ping.each { |resp| 25 | hosts += resp[:sender].to_s+"\n" 26 | } 27 | 28 | hosts 29 | } 30 | 31 | bot.add_command( 32 | :syntax => 'facts [hostname]', 33 | :description => 'shows facts', 34 | :regex => /^facts\s.*$/, 35 | :is_public => true 36 | ) { |sender, argument| 37 | 38 | } 39 | 40 | 41 | bot.connect 42 | 43 | -------------------------------------------------------------------------------- /etc/xen-mappings.csv: -------------------------------------------------------------------------------- 1 | hypervisor2,10.0.0.2 2 | hypervisor3,10.0.0.3 3 | -------------------------------------------------------------------------------- /plugins/agents/chef-package.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | # An agent that uses chef provider to manage packages 4 | # Based on the puppet version oneh 5 | class Package "SimpleRPC Agent For Package Management", 7 | :description => "Agent To Manage Packages", 8 | :author => "Nicolas Szalay ", 9 | :license => "GPLv2", 10 | :version => "1.0", 11 | :url => "http://www.rottenbytes.info/", 12 | :timeout => 180 13 | 14 | ["install", "upgrade", "remove", "purge"].each do |act| 15 | action act do 16 | validate :package, :shellsafe 17 | do_pkg_action(request[:package], act.to_sym) 18 | end 19 | end 20 | 21 | action "apt_update" do 22 | reply.fail! "Cannot find apt-get at /usr/bin/apt-get" unless File.exist?("/usr/bin/apt-get") 23 | reply[:output] = %x[/usr/bin/apt-get update] 24 | reply[:exitcode] = $?.exitstatus 25 | 26 | reply.fail! "apt-get update failed, exit code was #{reply[:exitcode]}" unless reply[:exitcode] == 0 27 | end 28 | 29 | 30 | private 31 | def do_pkg_action(package, action) 32 | begin 33 | require 'chef' 34 | require 'chef/client' 35 | require 'chef/run_context' 36 | 37 | Chef::Config[:solo] = true 38 | Chef::Config[:log_level] = :debug 39 | Chef::Log.level(:debug) 40 | client = Chef::Client.new 41 | client.run_ohai 42 | client.build_node 43 | 44 | run_context = Chef::RunContext.new(client.node, Chef::CookbookCollection.new(Chef::CookbookLoader.new("/tmp"))) 45 | recipe = Chef::Recipe.new("adhoc", "default", run_context) 46 | resource = recipe.send(:package, package) 47 | resource.send("action",action) 48 | 49 | Log.instance.debug("Doing '#{action}' for package '#{package}'") 50 | status=Chef::Runner.new(run_context).converge 51 | 52 | reply["status"] = status 53 | rescue Exception => e 54 | reply.fail e.to_s 55 | end 56 | end 57 | 58 | end 59 | end 60 | end 61 | 62 | # vi:tabstop=4:expandtab:ai:filetype=ruby 63 | -------------------------------------------------------------------------------- /plugins/agents/chef-resource.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | # An agent that uses Opscode to manage resources 4 | # Original credit goes to R.I. Pienaar 5 | class Chefresource "SimpleRPC Chef Resource Agent", 7 | :description => "Generic resource management", 8 | :author => "Nicolas Szalay ", 9 | :license => "BSD", 10 | :version => "1.0", 11 | :url => "https://github.com/rottenbytes/mcollective", 12 | :timeout => 60 13 | 14 | # Does the actual work with the chef provider and sets appropriate reply options 15 | action "handle" do 16 | validate :resourcetype, String 17 | validate :resourcename, String 18 | 19 | require 'chef' 20 | require 'chef/client' 21 | require 'chef/run_context' 22 | 23 | begin 24 | Chef::Config[:solo] = true 25 | Chef::Config[:log_level] = :debug 26 | Chef::Log.level(:debug) 27 | client = Chef::Client.new 28 | client.run_ohai 29 | client.build_node 30 | 31 | run_context = Chef::RunContext.new(client.node, Chef::CookbookCollection.new(Chef::CookbookLoader.new)) 32 | recipe = Chef::Recipe.new("adhoc", "default", run_context) 33 | # create the resource 34 | resource = recipe.send(request[:resourcetype].to_sym, request[:resourcename]) 35 | # insert action, attribute, whatever supported by your resource type 36 | request[:resourceactions].each { |action| 37 | action.each_pair { |k,v| 38 | resource.send(k,v) 39 | } 40 | } 41 | Log.instance.debug("Converging for resource #{request[:resourcetype]} '#{request[:resourcename]}'") 42 | status=Chef::Runner.new(run_context).converge 43 | 44 | reply["status"] = status 45 | rescue Exception => e 46 | reply.fail "#{e}" 47 | end 48 | end 49 | 50 | end 51 | end 52 | end 53 | 54 | # vi:tabstop=4:expandtab:ai:filetype=ruby 55 | -------------------------------------------------------------------------------- /plugins/agents/chef-service.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | # An agent that uses Opscode to manage services 4 | # Made from the puppet version 5 | # Original credit goes to R.I. Pienaar 6 | class Service "SimpleRPC Service Agent, Chef version", 8 | :description => "Agent to manage services", 9 | :author => "Nicolas Szalay ", 10 | :license => "BSD", 11 | :version => "1.0", 12 | :url => "https://github.com/rottenbytes/mcollective", 13 | :timeout => 60 14 | 15 | ["stop", "start", "restart"].each do |act| 16 | action act do 17 | do_service_action(act) 18 | end 19 | end 20 | 21 | private 22 | 23 | # Does the actual work with the chef provider and sets appropriate reply options 24 | def do_service_action(action) 25 | validate :service, String 26 | 27 | require 'chef' 28 | require 'chef/client' 29 | require 'chef/run_context' 30 | 31 | begin 32 | Chef::Config[:solo] = true 33 | Chef::Config[:log_level] = :debug 34 | Chef::Log.level(:debug) 35 | client = Chef::Client.new 36 | client.run_ohai 37 | client.build_node 38 | 39 | run_context = Chef::RunContext.new(client.node, Chef::CookbookCollection.new(Chef::CookbookLoader.new("/tmp"))) 40 | recipe = Chef::Recipe.new("adhoc", "default", run_context) 41 | resource = recipe.send(:service, request[:service]) 42 | resource.send("action",action) 43 | resource.send("supports", {:status => true } ) 44 | 45 | Log.instance.debug("Doing '#{action}' for service '#{request[:service]}'") 46 | status=Chef::Runner.new(run_context).converge 47 | 48 | reply["status"] = status 49 | rescue Exception => e 50 | reply.fail "#{e}" 51 | end 52 | end 53 | end 54 | end 55 | end 56 | 57 | # vi:tabstop=4:expandtab:ai:filetype=ruby 58 | -------------------------------------------------------------------------------- /plugins/agents/chef.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | # An agent to manage the Chef Daemon 4 | # 5 | # Many bits taken from the puppet agent from R.I. Pienaar 6 | # 7 | # Configuration Options: 8 | # chef.client - Where to find the chef client, defaults to /usr/sbin/chef-client 9 | # chef.pidfile - Where to find the chef client pid file 10 | class Chef "SimpleRPC Chef Client Agent", 12 | :description => "Agent to manage the chef client", 13 | :author => "Nicolas Szalay", 14 | :license => "Apache License 2.0", 15 | :version => "1.0", 16 | :url => "http://www.rottenbytes.info", 17 | :timeout => 30 18 | 19 | def startup_hook 20 | @pidfile = @config.pluginconf["chef.pidfile"] || "/var/run/chef/client.pid" 21 | @client = @config.pluginconf["chef.client"] || "/usr/bin/chef-client" 22 | end 23 | 24 | action "status" do 25 | reply[:running] = 0 26 | if File.exists?(@pidfile) then 27 | reply[:running] = 1 28 | end 29 | end 30 | 31 | action "runonce" do 32 | Log.debug("=> running #{@client}") 33 | reply[:stdout] = "" 34 | reply[:stderr] = "" 35 | reply[:status] = run(@client, :stdout => reply[:stdout], :stderr => reply[:stderr]) 36 | end 37 | 38 | end # end of class Chef 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /plugins/agents/smith.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Smith < RPC::Agent 4 | action "report" do 5 | reply[:libdir] = @config.libdir 6 | reply[:agents] = Agents.agentlist 7 | end 8 | 9 | action "updateagent" do 10 | validate :name, String 11 | validate :source, String 12 | validate :method, String 13 | validate :reload, /(true|false)/ 14 | 15 | update_methods = [ "http", "file"] 16 | 17 | if !update_methods.include? request[:method] then 18 | reply.fail ":method must be one of " + update_methods.join(", ") 19 | else 20 | destination = @config.libdir.to_s+"/mcollective/agent/"+request[:name]+".rb" 21 | reply[:destination] = destination 22 | end 23 | 24 | case request[:method] 25 | when "http": 26 | require "rubygems" 27 | require 'open-uri' 28 | # Get the file from http (request[:source]) to its destination 29 | fp=open(destination,"w") 30 | 31 | fp.write(open(request[:source]).read) 32 | fp.close 33 | 34 | # is there a way to control this ?? 35 | reply[:result] = true 36 | 37 | when "file": 38 | require 'ftools' 39 | # copy the file from request[:source] to the right place 40 | begin File.syscopy(request[:source],destination) 41 | reply[:result] = true 42 | rescue 43 | reply.fail "could not copy file while updating !" 44 | end 45 | end # end of case 46 | 47 | if request[:reload] == "true" then 48 | Log.instance.debug("Reload requested after update of an agent (#{request[:name]})") 49 | ::Process.kill("USR1", $$) 50 | end 51 | end # end of updateagent action/method 52 | 53 | action "deleteagent" do 54 | validate :name, String 55 | validate :reload, /(true|false)/ 56 | 57 | agentfile = @config.libdir.to_s+"/mcollective/agent/"+request[:name]+".rb" 58 | 59 | if File.exists?(agentfile) then 60 | # delete the file 61 | File.delete(agentfile) 62 | end 63 | 64 | if request[:reload] == "true" then 65 | Log.instance.debug("Reload requested after deletion of an agent (#{request[:name]})") 66 | ::Process.kill("USR1", $$) 67 | end 68 | end # end of deleteagent action/methodd 69 | 70 | end # end of class/class/module 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /plugins/agents/svnagent.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "svnagent", 2 | :description => "Agent to manage SVN repos", 3 | :author => "Nicolas Szalay "BSD", 5 | :version => "0.1", 6 | :url => "http://www.rottenbytes.info/", 7 | :timeout => 30 8 | 9 | action "update", :description => "Updates the repo in the specified path" do 10 | input :path, 11 | :prompt => "path", 12 | :description => "full path of the repository", 13 | :type => :string, 14 | :validation => '^[a-zA-Z\-_\d]+$', 15 | :optional => false 16 | 17 | output "data", 18 | :description => "The repository revision number extracted", 19 | :display_as => "Revision number" 20 | end 21 | 22 | action "getrevision", :description => "get the revision of the specified path" do 23 | input :path, 24 | :prompt => "path", 25 | :description => "full path of the repository", 26 | :type => :string, 27 | :validation => '^[a-zA-Z\-_\d]+$', 28 | :optional => false 29 | 30 | output "revision", 31 | :description => "The repository revision number extracted", 32 | :display_as => "Revision number" 33 | end 34 | -------------------------------------------------------------------------------- /plugins/agents/svnagent.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Svnagent < RPC::Agent 4 | metadata :name => "svnagent", 5 | :description => "Agent to manage svn repos", 6 | :author => "Nicolas Szalay", 7 | :license => "BSD", 8 | :version => "0.1", 9 | :url => "http://www.rottenbytes.info/", 10 | :timeout => 60 11 | 12 | def startup_hook 13 | @timeout = 60 14 | end 15 | 16 | action "update" do 17 | validate :path, String 18 | 19 | revision=`cd #{request[:path]} && svn up` 20 | # send back revision number 21 | reply[:data]=revision.chomp.split(" ")[-1].gsub(".","") 22 | end 23 | 24 | action "getrevision" do 25 | validate :path, String 26 | 27 | # accomodate english & french locales 28 | revision=`cd #{request[:path]} && svn info | grep R.vision | head -1` 29 | reply[:revision]=revision.chomp.split(" ")[-1] 30 | end 31 | 32 | # end of class Svnagent 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /plugins/agents/xenagent.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "xenagent", 2 | :description => "Agent to manage xen", 3 | :author => "Nicolas Szalay "BSD", 5 | :version => "0.1", 6 | :url => "http://www.rottenbytes.info/", 7 | :timeout => 30 8 | 9 | action "list", :description => "Find all dom0 and displays associated domUs" do 10 | end 11 | 12 | action "find", :description => "Finds the specified domU" do 13 | input :name, 14 | :prompt => "domU name", 15 | :description => "The domU name", 16 | :type => :string, 17 | :validation => '^[a-zA-Z\-_\d]+$', 18 | :optional => false 19 | end 20 | 21 | action "stat", :description => "returns load avg & domu times" do 22 | output "slices", 23 | :description => "The list of domUs", 24 | :display_as => "Slices" 25 | 26 | output "load", 27 | :description => "The dom0 load", 28 | :display_as => "dom0 load" 29 | 30 | output "times", 31 | :description => "The domUs times", 32 | :display_as => "Times" 33 | end 34 | 35 | action "migrate", :description => "Migrate a domU to another dom0" do 36 | input :name, 37 | :prompt => "domU name", 38 | :description => "The domU name", 39 | :type => :string, 40 | :validation => '^[a-zA-Z\-_\d]+$', 41 | :optional => false 42 | 43 | input :newhost, 44 | :prompt => "dom0 name", 45 | :description => "The destination dom0", 46 | :type => :string, 47 | :validation => '^[a-zA-Z\-_\d]+$', 48 | :optional => false 49 | end 50 | 51 | action "create", :description => "Starts the specified domU" do 52 | input :name, 53 | :prompt => "domU name", 54 | :description => "The domU to start", 55 | :type => :string, 56 | :validation => '^[a-zA-Z\-_\d]+$', 57 | :optional => false 58 | end 59 | 60 | action "destroy", :description => "Destroys the specified domU" do 61 | input :name, 62 | :prompt => "domU name", 63 | :description => "The domU to destroy", 64 | :type => :string, 65 | :validation => '^[a-zA-Z\-_\d]+$', 66 | :optional => false 67 | end 68 | 69 | -------------------------------------------------------------------------------- /plugins/agents/xenagent.rb: -------------------------------------------------------------------------------- 1 | require 'xen' 2 | 3 | module MCollective 4 | module Agent 5 | class Xenagent < RPC::Agent 6 | metadata :name => "xenagent", 7 | :description => "Agent to manage xen", 8 | :author => "Nicolas Szalay", 9 | :license => "BSD", 10 | :version => "0.1", 11 | :url => "http://www.rottenbytes.info/", 12 | :timeout => 30 13 | 14 | def startup_hook 15 | # Increase timeout because migration can be longer than the default one 16 | # Yes, it is quite bad 17 | @timeout = 30 18 | end 19 | 20 | # Basic echo server, kept for tests 21 | action "echo" do 22 | validate :msg, String 23 | 24 | reply.data = request[:msg] 25 | end 26 | 27 | action "find" do 28 | validate :name, String 29 | 30 | x=Xen::XenServer.new 31 | if x.has? request[:name] then 32 | reply.data = "Present" 33 | else 34 | reply.data = "Absent" 35 | end 36 | end 37 | 38 | action "list" do 39 | x=Xen::XenServer.new 40 | reply[:slices] = x.slices 41 | end 42 | 43 | 44 | action "stat" do 45 | x=Xen::XenServer.new 46 | # We don't care about domain 0 47 | slices = x.slices 48 | slices.delete("Domain-0") 49 | reply[:slices] = slices 50 | # UGLY -- this is purely linux -- need to be fixed 51 | reply[:load] = open("/proc/loadavg","r").readline.split()[0].to_f 52 | reply[:times] = {} 53 | if !slices.empty? then 54 | slices.each { |s| reply[:times][s]=x.get(s).time } 55 | end 56 | end 57 | 58 | action "migrate" do 59 | validate :slice, String 60 | validate :newhost, :ipv4address 61 | 62 | x=Xen::XenServer.new 63 | result=x.migrate(request[:slice],request[:newhost]) 64 | reply[:result]=result 65 | end 66 | 67 | action "create" do 68 | validate :name, String 69 | 70 | x=Xen::XenServer.new 71 | result=x.create(request[:name]) 72 | reply[:result]=result 73 | end 74 | 75 | action "destroy" do 76 | validate :name, String 77 | 78 | x=Xen::XenServer.new 79 | result=x.destroy(request[:name]) 80 | reply[:result]=result 81 | end 82 | # end of class 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /plugins/util/actionpolicy.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Util 3 | class ActionPolicy 4 | def self.authorize(request) 5 | unless request.caller == "uid=0" 6 | raise("You must be root to access to #{request.agent}::#{request.action}") 7 | end 8 | end 9 | end 10 | end 11 | end 12 | 13 | 14 | --------------------------------------------------------------------------------