fuel-astute/lib/astute/cobbler.rb
Georgy Kibardin 0e93c8b6c8 Increase xml rpc timeout
With hundreds of nodes Cobbler sync cannot fit default 30 secods
timeout. Cobbler performance is going to be investigated in the next
release. By now lets just increase the timeout.

Change-Id: Ief8ff93fc808549e8d729040512a266b0c09383d
Closes-Bug: #1608700
(cherry picked from commit f030161d19)
2016-09-16 07:56:55 +00:00

345 lines
12 KiB
Ruby

# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
require 'xmlrpc/client'
module Astute
module Provision
class CobblerError < RuntimeError; end
class Cobbler
attr_reader :remote
def initialize(o={})
Astute.logger.debug("Cobbler options:\n#{o.pretty_inspect}")
if (match = /^http:\/\/([^:]+?):?(\d+)?(\/.+)/.match(o['url']))
host = match[1]
port = match[2] || '80'
path = match[3]
else
host = o['host'] || 'localhost'
port = o['port'] || '80'
path = o['path'] || '/cobbler_api'
end
@username = o['username'] || 'cobbler'
@password = o['password'] || 'cobbler'
Astute.logger.debug("Connecting to cobbler with: host: #{host} port: #{port} path: #{path}")
@remote = XMLRPC::Client.new(host, path, port)
@remote.timeout = 120
Astute.logger.debug("Cobbler initialize with username: #{@username}, password: #{@password}")
end
def token
remote.call('login', @username, @password)
end
def item_from_hash(what, name, data, opts = {})
options = {
:item_preremove => true,
}.merge!(opts)
cobsh = Cobsh.new(data.merge({'what' => what, 'name' => name}))
cobblerized = cobsh.cobblerized
Astute.logger.debug("Creating/editing item from hash:\n#{cobsh.pretty_inspect}")
remove_item(what, name) if options[:item_preremove]
# get existent item id or create new one
item_id = get_item_id(what, name)
# defining all item options
cobblerized.each do |opt, value|
next if opt == 'interfaces'
Astute.logger.debug("Setting #{what} #{name} opt: #{opt}=#{value}")
remote.call('modify_item', what, item_id, opt, value, token)
end
# defining system interfaces
if what == 'system' && cobblerized.has_key?('interfaces')
Astute.logger.debug("Defining system interfaces #{name} #{cobblerized['interfaces']}")
remote.call('modify_system', item_id, 'modify_interface',
cobblerized['interfaces'], token)
end
# save item into cobbler database
Astute.logger.debug("Saving #{what} #{name}")
remote.call('save_item', what, item_id, token)
end
def remove_item(what, name, recursive=true)
remote.call('remove_item', what, name, token, recursive) if item_exists(what, name)
end
def remove_system(name)
remove_item('system', name)
end
def item_exists(what, name)
remote.call('has_item', what, name)
end
def items_by_criteria(what, criteria)
remote.call('find_items', what, criteria)
end
def system_by_mac(mac)
items_by_criteria('system', {"mac_address" => mac})[0]
end
def system_exists?(name)
item_exists('system', name)
end
def get_item_id(what, name)
if item_exists(what, name)
item_id = remote.call('get_item_handle', what, name, token)
else
item_id = remote.call('new_item', what, token)
remote.call('modify_item', what, item_id, 'name', name, token)
end
item_id
end
def sync
remote.call('sync', token)
rescue Net::ReadTimeout, XMLRPC::FaultException => e
retries ||= 0
retries += 1
raise e if retries > 2
Astute.logger.warn("Cobbler problem. Try to repeat: #{retries} attempt")
sleep 10
retry
end
def power(name, action)
options = {"systems" => [name], "power" => action}
remote.call('background_power_system', options, token)
end
def power_on(name)
power(name, 'on')
end
def power_off(name)
power(name, 'off')
end
def power_reboot(name)
power(name, 'reboot')
end
def event_status(event_id)
remote.call('get_task_status', event_id)
end
def netboot(name, state)
state = ['on', 'yes', true, 'true', 1, '1'].include?(state)
if system_exists?(name)
system_id = get_item_id('system', name)
else
raise CobblerError, "System #{name} not found."
end
remote.call('modify_system', system_id, 'netboot_enabled', state, token)
remote.call('save_system', system_id, token, 'edit')
end
end
class Cobsh < ::Hash
ALIASES = {
'ks_meta' => ['ksmeta'],
'mac_address' => ['mac'],
'ip_address' => ['ip'],
}
# these fields can be get from the cobbler code
# you can just import cobbler.item_distro.FIELDS
# or cobbler.item_system.FIELDS
FIELDS = {
'system' => {
'fields' => [
'name', 'owners', 'profile', 'image', 'status', 'kernel_options',
'kernel_options_post', 'ks_meta', 'enable_gpxe', 'proxy',
'netboot_enabled', 'kickstart', 'comment', 'server',
'virt_path', 'virt_type', 'virt_cpus', 'virt_file_size',
'virt_disk_driver', 'virt_ram', 'virt_auto_boot', 'power_type',
'power_address', 'power_user', 'power_pass', 'power_id',
'hostname', 'gateway', 'name_servers', 'name_servers_search',
'ipv6_default_device', 'ipv6_autoconfiguration', 'mgmt_classes',
'mgmt_parameters', 'boot_files', 'fetchable_files',
'template_files', 'redhat_management_key', 'redhat_management_server',
'repos_enabled', 'ldap_enabled', 'ldap_type', 'monit_enabled',
],
'interfaces_fields' => [
'mac_address', 'mtu', 'ip_address', 'interface_type',
'interface_master', 'bonding_opts', 'bridge_opts',
'management', 'static', 'netmask', 'dhcp_tag', 'dns_name',
'static_routes', 'virt_bridge', 'ipv6_address', 'ipv6_secondaries',
'ipv6_mtu', 'ipv6_static_routes', 'ipv6_default_gateway'
],
'special' => ['interfaces', 'interfaces_extra']
},
'profile' => {
'fields' => [
'name', 'owners', 'distro', 'parent', 'enable_gpxe',
'enable_menu', 'kickstart', 'kernel_options', 'kernel_options_post',
'ks_meta', 'proxy', 'repos', 'comment', 'virt_auto_boot',
'virt_cpus', 'virt_file_size', 'virt_disk_driver',
'virt_ram', 'virt_type', 'virt_path', 'virt_bridge',
'dhcp_tag', 'server', 'name_servers', 'name_servers_search',
'mgmt_classes', 'mgmt_parameters', 'boot_files', 'fetchable_files',
'template_files', 'redhat_management_key', 'redhat_management_server'
]
},
'distro' => {
'fields' => ['name', 'owners', 'kernel', 'initrd', 'kernel_options',
'kernel_options_post', 'ks_meta', 'arch', 'breed',
'os_version', 'comment', 'mgmt_classes', 'boot_files',
'fetchable_files', 'template_files', 'redhat_management_key',
'redhat_management_server']
}
}
def initialize(h)
Astute.logger.debug("Cobsh is initialized with:\n#{h.pretty_inspect}")
raise CobblerError, "Cobbler hash must have 'name' key" unless h.has_key? 'name'
raise CobblerError, "Cobbler hash must have 'what' key" unless h.has_key? 'what'
raise CobblerError, "Unsupported 'what' value" unless FIELDS.has_key? h['what']
h.each{|k, v| store(k, v)}
end
def cobblerized
Astute.logger.debug("Cobblerizing hash:\n#{pretty_inspect}")
ch = {}
ks_meta = ''
kernel_options = ''
each do |k, v|
k = aliased(k)
if ch.has_key?(k) && ch[k] == v
next
elsif ch.has_key?(k)
raise CobblerError, "Wrong cobbler data: #{k} is duplicated"
end
# skiping not valid item options
unless valid_field?(k)
Astute.logger.warn("Key #{k} is not valid. Will be skipped.")
next
end
ks_meta = serialize_cobbler_parameter(v) if 'ks_meta' == k
kernel_options = serialize_cobbler_parameter(v) if 'kernel_options' == k
# special handling for system interface fields
# which are the only objects in cobbler that will ever work this way
if k == 'interfaces'
ch.store('interfaces', cobblerized_interfaces)
next
end
# here we convert interfaces_extra options into ks_meta format
if k == 'interfaces_extra'
ks_meta << cobblerized_interfaces_extra
next
end
ch.store(k, v)
end # each do |k, v|
ch.store('ks_meta', ks_meta.strip) unless ks_meta.strip.empty?
ch.store('kernel_options', kernel_options.strip) unless kernel_options.strip.empty?
ch
end
def serialize_cobbler_parameter(param)
serialized_param = ''
if param.kind_of?(Hash)
param.each do |ks_meta_key, ks_meta_value|
serialized_param << " #{ks_meta_key}=#{serialize_cobbler_value(ks_meta_value)}"
end
elsif param.kind_of?(String)
param
else
raise CobblerError, "Wrong param format. It must be Hash or String: '#{param}'"
end
serialized_param
end
def serialize_cobbler_value(value)
if value.kind_of?(Hash) || value.kind_of?(Array)
return "\"#{value.to_json.gsub('"', '\"')}\""
end
value
end
def aliased(k)
# converting 'foo-bar' keys into 'foo_bar' keys
k1 = k.gsub(/-/,'_')
# converting orig keys into alias keys
# example: 'ksmeta' into 'ks_meta'
k2 = ALIASES.each_key.select{|ak| ALIASES[ak].include?(k1)}[0] || k1
Astute.logger.debug("Key #{k} aliased with #{k2}") if k != k2
k2
end
def valid_field?(k)
(FIELDS[fetch('what')]['fields'].include?(k) or
(FIELDS[fetch('what')]['special'] or []).include?(k))
end
def valid_interface_field?(k)
(FIELDS[fetch('what')]['interfaces_fields'] or []).include?(k)
end
def cobblerized_interfaces
interfaces = {}
fetch('interfaces').each do |iname, ihash|
ihash.each do |iopt, ivalue|
iopt = aliased(iopt)
if interfaces.has_key?("#{iopt}-#{iname}")
raise CobblerError, "Wrong interface cobbler data: #{iopt} is duplicated"
end
unless valid_interface_field?(iopt)
Astute.logger.debug("Interface key #{iopt} is not valid. Skipping")
next
end
Astute.logger.debug("Defining interfaces[#{iopt}-#{iname}] = #{ivalue}")
interfaces["#{iopt}-#{iname}"] = ivalue
end
end
interfaces
end
def cobblerized_interfaces_extra
# here we just want to convert interfaces_extra into ks_meta
interfaces_extra_str = ""
fetch('interfaces_extra').each do |iname, iextra|
iextra.each do |k, v|
Astute.logger.debug("Adding into ks_meta interface_extra_#{iname}_#{k}=#{v}")
interfaces_extra_str << " interface_extra_#{iname}_#{k}=#{v}"
end
end
interfaces_extra_str
end
end
end
end