Created
May 21, 2014 17:11
-
-
Save hassenius/7f8ca7027034cecddcc4 to your computer and use it in GitHub Desktop.
Find Softlayer Monthly Bare Metal Servers based on cpu,memory and/or disk requirements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
################################################################################ | |
# sl_config_finder.rb | |
# ©Copyright IBM Corporation 2014. | |
# | |
# LICENSE: MIT (http://opensource.org/licenses/MIT) | |
################################################################################ | |
require 'rubygems' | |
require 'softlayer_api' | |
require 'optparse' | |
$SL_API_USERNAME = " SET ME PLEASE " | |
$SL_API_KEY = " PLEASE SET ME TOO " | |
class Compute | |
@@compute_catalog = Array.new | |
@@ram_index = Hash.new { |hash, capacity| hash[capacity] = [] } | |
@@cpu_core_index = Hash.new { |hash, cores| hash[cores] = [] } | |
@@disk_index = Hash.new { |hash, disk_capacity| hash[disk_capacity] = [] } | |
@@package_index = Hash.new | |
@possible_packages = Array.new | |
@priority_order = Array.new | |
@desired_cpu_cores = 0 | |
@cpu_cores = 0 | |
@desired_cpu_type = nil | |
@cpu_type = nil | |
@desired_ram = 0 | |
@ram = 0 | |
@desired_disks = 0 | |
@disks = 0 | |
@desired_disk_size = 0 | |
@disk_size = 0 | |
@desired_disk_type = nil | |
@disk_type = nil | |
attr_accessor(:cpu_cores, :desired_cpu_cores, :ram, :desired_ram, :disks, :desired_disks, :possible_packages) | |
def initialize(args = {}) | |
@desired_cpu_cores = args[:cpu_cores] | |
@desired_cpu_type = args[:cpu_type] # => Can be virtual, physical or a GHz value | |
@desired_ram = args[:ram].to_i | |
@desired_disks = args[:min_disks] # => Number of spindels desired. Typically only relevant for dedicated machines | |
@desired_disk_type = args[:disk_type] # getPrivateBlockDeviceTemplateGroups # => Typically most relevant for dedicated machines -- SSD, SAS, or SATA | |
@priority_order = args[:priority] # => Array of order of priority for resources from most to least important. i.e. [cpu,memory,disk] | |
@bias = args[:bias] # => higher/lower/nearest. When looking for resource match, whether to go matching or higher, matching or lower or closest match | |
# Build the compute catalog if it has not been done already | |
build_compute_catalog() if @@compute_catalog.empty? | |
# Cycle through and find closest match for each resource item in order of priority | |
@priority_order.each { |resource| | |
case resource | |
when "cpu" | |
@cpu_cores = find_package_match(@@cpu_core_index, @desired_cpu_cores, @bias) | |
when "memory" | |
# Find closest ram | |
# TODO: Check that @desired_ram is set | |
@ram = find_package_match(@@ram_index, @desired_ram, @bias) | |
when "disk" | |
# Find closest disk | |
## This is more complicated as we have more variables: amount of spindles, total size and type | |
## For now just do amount of spindles | |
# TODO: Check that @desired_disks is set | |
@disks = find_package_match(@@disk_index, @desired_disks, "higher") # makes no sense to bias lower with disks | |
else | |
puts "You didn't tell me a priority" | |
end | |
} | |
end | |
def find_candidate(candidates, desired_number, bias) | |
# Get the closest matching cpu/ram/disk configuration from a list of candidates | |
## Example from CPU, migth be that there are 2,4,6,8,12,16,24,32,40 core configurations available (candidates) | |
## Select the best option for the desired number (i.e. 7) based on a set bias (higher, lower or nearest) | |
## Previous selections (RAM or Disk) will limit the amount of available options available | |
case bias | |
when "higher" | |
return candidates.select{|item| item.to_i > desired_number.to_i}.min | |
when "lower" | |
return candidates.select{|item| item.to_i < desired_number.to_i}.max | |
when "nearest" | |
return candidates.min{|a,b| (desired_number-a).abs <=> (desired_number-b).abs } | |
else | |
puts "Error: Not sure what #{bias} means" | |
end | |
end | |
def find_package_match(index, desired, bias) | |
# Create a new sorted array with the list of all the CPU/RAM/disk options | |
options = index.keys.map(&:to_i).sort | |
# Start out by finding a candidate match for the requested amount | |
if index.key?(desired.to_s) | |
# Exact match available | |
candidate = desired | |
else | |
# find closest match available | |
candidate = find_candidate(options, desired, bias) | |
end | |
# Compile a list of possible packages | |
if @possible_packages == nil | |
# This was the first resource type to be checked, all of our packages are potential final matches at this stage | |
@possible_packages = index[candidate.to_s] | |
else | |
# Check if there are any of the package options in the existing @possible_packages also exists in the options we found in this run | |
## If not select a different option and try again | |
until ((@possible_packages & index[candidate.to_s]).length != 0) | |
# Previous candidates have not been a match, delete current candidate from options list | |
options.delete(candidate) | |
# find new candidate | |
candidate = find_candidate(options, desired, bias) | |
end | |
# Update the list of possible packages | |
@possible_packages = index[candidate.to_s] & @possible_packages | |
end | |
# Return the selected candidate so the caller knows what option we landed on | |
return candidate | |
end | |
def build_compute_catalog() | |
mask = "mask[id,name,description,availableStorageUnits,activeServerItems,activeRamItems,type]" | |
package_types_of_interest = ["BARE_METAL_CPU"] | |
# Pull down the categories list from SoftLayer | |
sl_package_type_client = SoftLayer::Service.new("SoftLayer_Product_Package_Type") | |
package_types = sl_package_type_client.getAllObjects | |
# Check which categories we're interested in, and add the packages from these categories to the list | |
package_types.each { |type| | |
add_packages_to_catalog(type["id"]) if package_types_of_interest.include? type["keyName"] | |
} | |
# Now create the indexes we will use later to search for the specific configuration we want | |
build_indexes | |
end | |
def add_packages_to_catalog(package_type_id) | |
mask = "mask[id,name,description,availableStorageUnits,activeServerItems[id,description,totalPhysicalCoreCount],activeRamItems[id,description,capacity],type]" | |
# Pull down the full list | |
sl_packages = SoftLayer::Service.new("SoftLayer_Product_Package_Type") | |
packages = sl_packages.object_mask(mask).object_with_id(package_type_id).getPackages() | |
@@compute_catalog.concat(packages) | |
end | |
def build_indexes() | |
# Create neccessary indexes (Hashes) for lookups | |
@@compute_catalog.each_index { |index| | |
# Create a Package index with packageID as key | |
@@package_index[@@compute_catalog[index]["id"]] = index | |
# Create a CPU Index with coreCount as Key and packageID as array of values | |
@@compute_catalog[index]["activeServerItems"].each { |coreItem| | |
@@cpu_core_index[coreItem["totalPhysicalCoreCount"].to_s] << @@compute_catalog[index]["id"] | |
} | |
# Create a RAM index with memory size as key and packageID as array of values | |
@@compute_catalog[index]["activeRamItems"].each { |ramItem| | |
@@ram_index[ramItem["capacity"].to_s] << @@compute_catalog[index]["id"] | |
} | |
# Create a disk index with available disk slots as key and packageId as array of values | |
@@disk_index[@@compute_catalog[index]["availableStorageUnits"].to_s] << @@compute_catalog[index]["id"] | |
} | |
end | |
def print_result | |
# Print the packages that match our requirements | |
puts "Desired versus selected specs" | |
puts "CPU: desired - #{@desired_cpu_cores} == selected - #{@cpu_cores}" | |
puts "RAM: desired - #{@desired_ram} == selected - #{@ram}" | |
puts "Disk spindles: desired - #{@desired_disks} == selected - #{@disks}" | |
puts "=========== Possible packages meeting these requirements ==================" | |
@possible_packages.each { |package| | |
puts "===========================================================================" | |
puts "ID: #{package}" | |
puts "Name: #{@@compute_catalog[@@package_index[package]]["name"]}" | |
puts "CPU options" | |
@@compute_catalog[@@package_index[package]]["activeServerItems"].each { |cpu| | |
puts "Name: #{cpu["description"]}" | |
puts "ID: #{cpu["id"]} == Cores: #{cpu["totalPhysicalCoreCount"]}" | |
puts "--------------------------------------------" | |
} | |
puts "\nRam options:" | |
@@compute_catalog[@@package_index[package]]["activeRamItems"].each { |ram| | |
puts "ID: #{ram["id"]} Name: #{ram["description"]}" | |
} | |
puts "===========================================================================" | |
} | |
end | |
end | |
options = {} | |
parser = OptionParser.new do |opts| | |
opts.banner = "Usage: #$0 [options]" | |
opts.on('-c', '--cpu cores', Integer, 'Amount of CPU cores desired') do |cores| | |
options[:cores] = cores | |
end | |
opts.on('-m', '--memory amount', Integer, 'Amount of memory desired in GB') do |memory| | |
options[:memory] = memory; | |
end | |
opts.on('-d', '--disk amount', Integer, 'Amount of disk spindels desired') do |disk| | |
options[:disk] = disk; | |
end | |
opts.on('-p', '--priority pri1,pri2[,pri3]', Array, 'What order to prioritse resources (cpu, memory, disk) when searching') do |priority| | |
options[:priority] = priority; | |
end | |
opts.on('-b', '--bias {higher, lower, nearest}', ["higher", "lower", "nearest"], 'If exact match for given resource is not found, which way to search') do |bias| | |
options[:bias] = bias; | |
end | |
opts.on('-h', '--help', 'Displays Help') do | |
puts opts | |
exit | |
end | |
end | |
begin | |
parser.parse! | |
mandatory = [:cores, :memory, :priority, :bias] | |
missing = mandatory.select{ |param| options[param].nil? } | |
unless missing.empty? | |
puts parser | |
unless options.empty? | |
puts "\n Missing the following options: #{missing.join(', ')}" | |
end | |
exit | |
end | |
rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::InvalidArgument | |
puts parser | |
puts $!.to_s | |
exit | |
end | |
# Disk is the only optional input | |
if defined?(options[:disk]) | |
item = Compute.new(:ram => options[:memory], :cpu_cores => options[:cores], :min_disks => options[:disk], :priority => options[:priority], :bias => options[:bias]) | |
else | |
item = Compute.new(:ram => options[:memory], :cpu_cores => options[:cores], :priority => options[:priority], :bias => options[:bias]) | |
end | |
item.print_result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment