Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolr updates #412

Merged
merged 11 commits into from
Feb 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion support/ruby/consolr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,17 @@ Configuration params are searched in these locations --
An example consolr.yml file

```
ipmitool: /usr/bin/ipmitool
runners:
ipmitool: /usr/bin/ipmitool
dangerous_assets:
- "007117"
dangerous_status:
- "Allocated"
```

Consolr will load the runners listed and pick the first runner that says it can
manage the node in question by running the `can_run?` method of the runner.

## Running the tool

```
Expand All @@ -81,6 +85,54 @@ For a full list of actions, look up the help page
$ consolr --help
```

## Runners
Consolr ships with a runner that uses ipmitool to manage servers but there are
cases where you might want to extend it. For instance to support your favorite
IaaS provider or a private cloud. In this case, you need to write a custom
runner and deploy it. The way we do it at Tumblr is to gather our custom runner
into a gem and install that gem along with consolr.

A runner is simply a class that extends the `Consolr::Runners::Runner` base
class and is located in the correct module. A very simple example could look
like:
```
module Consolr
module Runners
class MyRunner < Runner
def initialize
// Set up the runner
end

def can_run? node
// Should return true if this runner can manage `node`
end

def verify node
// Verify that the runner can talk to the node. For instance by pinging
// the ipmi interface.
end

def on node
// issue command to power node on
end

end
end
```
All the methods (`on`, `off`) etc. that can be implemented can be seen in the
`Consolr::Runners::Runner` base class.

In order to package it up as a gem the directory structure should look like

```
.
└── lib
└── consolr
└── runners
└── myrunner.rb
```
You'll also need a gemspec file at the root.

## Mailing list
http://groups.google.com/group/collins-sm

Expand Down
4 changes: 4 additions & 0 deletions support/ruby/consolr/bin/consolr
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ opt_parser = OptionParser.new do |opt|
opt.on('-l', '--log LOG', 'System Event Log (SEL) [list|clear]') { |log| options[:log] = log }
opt.on('-o', '--on', 'turn on node') { options[:on] = true }
opt.on('-r', '--reboot', 'restart node') { options[:reboot] = true }
opt.on('-R', '--soft-reboot', 'restart node gracefully') { options[:soft_reboot] = true }
opt.on('-s', '--sdr', 'Sensor Data Repository (SDR)') { options[:sdr] = true }
opt.on('-t', '--tag ASSET', 'asset tag') { |tag| options[:tag] = tag }
opt.on('-x', '--off', 'turn off node') { options[:off] = true }
opt.on('-X', '--soft-off', 'turn off gracefully (via ACPI)') { options[:soft_off] = true }
opt.on('-S', '--status', 'print asset power status') { options[:status] = true }
opt.on('--runner RUNNER', 'specify the runner to use') { |runner| options[:runner] = runner }

opt.on_tail('-h', '--help', 'help') { puts opt; exit 0 }
opt.on_tail('-v', '--version', 'version') { puts Consolr::VERSION; exit 0 }
Expand Down
94 changes: 56 additions & 38 deletions support/ruby/consolr/lib/consolr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

require 'consolr/version'
require 'collins_auth'
require 'net/ping'
require 'optparse'
require 'yaml'


module Consolr
class Console

Expand All @@ -19,7 +19,7 @@ def initialize
File.readable?(File.expand_path(conf, __FILE__)) and File.size(File.expand_path(conf, __FILE__)) > 0
end

config_params = begin
@config_params = begin
YAML.load(File.open(File.expand_path(config_file, __FILE__)))
rescue TypeError => e
puts "-------"
Expand All @@ -36,33 +36,21 @@ def initialize
puts "------"
exit 1
end

begin
@ipmitool_exec = config_params['ipmitool'] # ipmitool absolute path
rescue Exception => e
puts e
puts "-------"
puts "Ensure that the ipmitool's path (#{@ipmitool_exec}) is given in the consolr.yml file and is correct"
puts "-------"
exit 1
end

#
# Will be ignored for dangerous actions, no matter what, even with --force
begin
@dangerous_assets = config_params['dangerous_assets']
@dangerous_assets = @config_params['dangerous_assets']
rescue Exception => e
puts e
puts "-------"
puts "Dangerous Assets -- #{dangrous}"
puts "Please make sure dangerous_assets exists and is valid."
puts "Supply them in a comma separated list."
puts "-------"
exit 1
end

# Dangerous actions wont be run in these status, override with --force
begin
@dangerous_status = config_params['dangerous_status']
@dangerous_status = @config_params['dangerous_status']
rescue Exception => e
puts e
puts "-------"
Expand All @@ -78,8 +66,6 @@ def initialize
def start options
abort("Please pass either the asset tag or hostname") if options[:tag].nil? and options[:hostname].nil?

abort("Cannot find #{@ipmitool_exec}") unless File.exist?(@ipmitool_exec)

dangerous_body = "Dangerous actions: #{dangerous_actions.join(', ')}\n"\
"Dangerous status: #{dangerous_status.join(', ')} (override with --force)\n"\
"Dangerous assets: #{dangerous_assets.join(', ')} (ignored no matter what, even with --force)"
Expand All @@ -103,11 +89,36 @@ def start options
abort("Please pass either the hostname OR the tag but not both.")
end

if options[:runner].nil?
runners = load_runners(@config_params.fetch('runners', []))
# Default to the ipmitool runner for backwards compatibility
if runners.empty?
require 'consolr/runners/ipmitool'
runners = [Consolr::Runners::Ipmitool.new(@config_params.fetch('ipmitool', {}))]
end
else
runners = load_runners([options[:runner]])
if runners.empty?
abort('Specified runner could not be loaded. Aborting.')
end
end

# match assets like vm-67f5eh, zt-*, etc.
nodes = options[:tag] ? (collins.find :tag => options[:tag]) : (collins.find :hostname => options[:hostname])
@node = nodes.length == 1 ? nodes.first : abort("Found #{nodes.length} assets, aborting.")

abort("Cannot ping IP #{@node.ipmi.address} (#{@node.tag})") unless Net::Ping::External.new(@node.ipmi.address).ping?
# select the first runner that support the node
runner = runners.select {|runner|
runner.can_run? @node
}.first

if runner.nil?
abort("No runners available for node #{@node.hostname} (#{@node.tag})")
end

if not runner.verify @node
abort("Cannot verify asset #{@node.hostname} (#{@node.tag})")
end

selected_dangerous_actions = dangerous_actions.select { |o| options[o] }
if dangerous_assets.include?(@node.tag) and selected_dangerous_actions.any?
Expand All @@ -121,40 +132,47 @@ def start options
case
when options[:console]
puts '--> Opening SOL session (type ~~. to quit)'
puts ipmitool_cmd('sol activate')
puts runner.console @node
when options[:kick]
puts ipmitool_cmd('sol deactivate')
puts runner.kick @node
when options[:identify]
puts ipmitool_cmd('chassis identify')
puts runner.identify @node
when options[:sdr]
puts ipmitool_cmd('sdr elist all')
puts runner.sdr @node
when options[:log] == 'list'
puts ipmitool_cmd('sel list')
puts runner.log_list @node
when options[:log] == 'clear'
puts ipmitool_cmd('sel clear')
puts runner.log_clear @node
when options[:on]
puts ipmitool_cmd('power on')
puts runner.on @node
when options[:off]
puts ipmitool_cmd('power off')
puts runner.off @node
when options[:soft_off]
puts runner.soft_off @node
when options[:reboot]
puts ipmitool_cmd('power cycle')
puts runner.reboot @node
when options[:soft_reboot]
puts runner.soft_reboot @node
when options[:status]
puts runner.status @node
else
begin
raise OptionParser::MissingArgument, "specify an action"
rescue OptionParser::MissingArgument => e
puts e
puts "specify an action"
exit 1
end
end
end

private

def ipmitool_cmd action
system("#{@ipmitool_exec} -I lanplus -H #{@node.ipmi.address} -U #{@node.ipmi.username} -P #{@node.ipmi.password} #{action}")
return $?.exitstatus == 0 ? "SUCCESS" : "FAILED"
def load_runners runners
runners.map {|runner|
begin
require "consolr/runners/#{runner}"
Consolr::Runners.const_get(runner.capitalize).new @config_params.fetch(runner, {})
rescue NameError, LoadError => e
puts "Could not load runner #{runner.capitalize}, skipping."
end
}.compact
end

end

end
82 changes: 82 additions & 0 deletions support/ruby/consolr/lib/consolr/runners/ipmitool.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require 'consolr/runners/runner'

module Consolr
module Runners
class Ipmitool < Runner
def initialize config
@ipmitool = if config.empty?
'/usr/bin/ipmitool'
else
config
end
end

def can_run? node
begin
not (node.ipmi.address.empty? or node.ipmi.username.empty? or node.ipmi.password.empty?)
rescue
false
end
end

def verify node
Net::Ping::External.new(node.ipmi.address).ping?
end

def console node
cmd 'sol activate', node
end

def kick node
cmd 'sol deactivate', node
end

def identify node
cmd 'chassis identify', node
end

def sdr node
cmd 'sdr elist all', node
end

def log_list node
cmd 'sel list', node
end

def log_clear node
cmd 'sel clear', node
end

def on node
cmd 'power on', node
end

def off node
cmd 'power off', node
end

def soft_off node
cmd 'power soft', node
end

def reboot node
cmd 'power cycle', node
end

def soft_reboot node
cmd 'power reset', node
end

def status node
cmd 'power status', node
end

private
def cmd action, node
system("#{@ipmitool} -I lanplus -H #{node.ipmi.address} -U #{node.ipmi.username} -P #{node.ipmi.password} #{action}")
return $?.exitstatus == 0 ? "SUCCESS" : "FAILED"
end
end
end
end

Loading