|
module ForemanXen
|
|
class Xenserver < ComputeResource
|
|
validates :url, :user, :password, :presence => true
|
|
|
|
def provided_attributes
|
|
super.merge(
|
|
:uuid => :reference,
|
|
:mac => :mac
|
|
)
|
|
end
|
|
|
|
def capabilities
|
|
[:build]
|
|
end
|
|
|
|
def find_vm_by_uuid(uuid)
|
|
client.servers.get(uuid)
|
|
rescue Fog::XenServer::RequestFailed => e
|
|
Foreman::Logging.exception("Failed retrieving xenserver vm by uuid #{uuid}", e)
|
|
raise(ActiveRecord::RecordNotFound) if e.message.include?('HANDLE_INVALID')
|
|
raise(ActiveRecord::RecordNotFound) if e.message.include?('VM.get_record: ["SESSION_INVALID"')
|
|
raise e
|
|
end
|
|
|
|
# we default to destroy the VM's storage as well.
|
|
def destroy_vm(ref, args = {})
|
|
logger.info "destroy_vm: #{ref} #{args}"
|
|
find_vm_by_uuid(ref).destroy
|
|
rescue ActiveRecord::RecordNotFound
|
|
true
|
|
end
|
|
|
|
def self.model_name
|
|
ComputeResource.model_name
|
|
end
|
|
|
|
def max_cpu_count
|
|
## 16 is a max number of cpus per vm according to XenServer doc
|
|
[hypervisor.host_cpus.size, 16].min
|
|
end
|
|
|
|
def max_memory
|
|
xenserver_max_doc = 128 * 1024 * 1024 * 1024
|
|
[hypervisor.metrics.memory_total.to_i, xenserver_max_doc].min
|
|
rescue => e
|
|
logger.error "unable to figure out free memory, guessing instead due to:#{e}"
|
|
16 * 1024 * 1024 * 1024
|
|
end
|
|
|
|
def test_connection(options = {})
|
|
super
|
|
errors[:url].empty? && errors[:user].empty? && errors[:password].empty? && hypervisor
|
|
rescue => e
|
|
begin
|
|
disconnect
|
|
rescue
|
|
nil
|
|
end
|
|
errors[:base] << e.message
|
|
end
|
|
|
|
def available_hypervisors
|
|
read_from_cache('available_hypervisors', 'available_hypervisors!')
|
|
end
|
|
|
|
def available_hypervisors!
|
|
store_in_cache('available_hypervisors') do
|
|
hosts = client.hosts
|
|
hosts.sort_by(&:name)
|
|
end
|
|
end
|
|
|
|
def new_nic(attr = {})
|
|
client.networks.new attr
|
|
end
|
|
|
|
def new_volume(attr = {})
|
|
client.storage_repositories.new attr
|
|
end
|
|
|
|
def storage_pools
|
|
read_from_cache('storage_pools', 'storage_pools!')
|
|
end
|
|
|
|
def storage_pools!
|
|
store_in_cache('storage_pools') do
|
|
results = []
|
|
storages = client.storage_repositories.select { |sr| sr.type != 'udev' && sr.type != 'iso' }
|
|
storages.each do |sr|
|
|
subresults = {}
|
|
found = false
|
|
|
|
available_hypervisors.each do |host|
|
|
next unless sr.reference == host.suspend_image_sr
|
|
found = true
|
|
subresults[:name] = sr.name
|
|
subresults[:display_name] = sr.name + '(' + host.hostname + ')'
|
|
subresults[:uuid] = sr.uuid
|
|
break
|
|
end
|
|
unless found
|
|
subresults[:name] = sr.name
|
|
subresults[:display_name] = sr.name
|
|
subresults[:uuid] = sr.uuid
|
|
end
|
|
results.push(subresults)
|
|
end
|
|
results.sort_by! { |item| item[:display_name] }
|
|
end
|
|
end
|
|
|
|
def interfaces
|
|
client.interfaces
|
|
rescue
|
|
[]
|
|
end
|
|
|
|
def networks
|
|
read_from_cache('networks', 'networks!')
|
|
end
|
|
|
|
def networks!
|
|
store_in_cache('networks') do
|
|
client.networks.sort_by(&:name)
|
|
end
|
|
end
|
|
|
|
def templates
|
|
read_from_cache('templates', 'templates!')
|
|
end
|
|
|
|
def templates!
|
|
store_in_cache('templates') do
|
|
client.servers.templates.sort_by(&:name)
|
|
end
|
|
end
|
|
|
|
def custom_templates
|
|
read_from_cache('custom_templates', 'custom_templates!')
|
|
end
|
|
|
|
def custom_templates!
|
|
store_in_cache('custom_templates') do
|
|
get_templates(client.servers.custom_templates)
|
|
end
|
|
end
|
|
|
|
def builtin_templates
|
|
read_from_cache('builtin_templates', 'builtin_templates!')
|
|
end
|
|
|
|
def builtin_templates!
|
|
store_in_cache('builtin_templates') do
|
|
get_templates(client.servers.builtin_templates)
|
|
end
|
|
end
|
|
|
|
def associated_host(vm)
|
|
associate_by('mac', vm.interfaces.map(&:mac).map { |mac| Net::Validations.normalize_mac(mac) })
|
|
end
|
|
|
|
def find_snapshots_for_vm(vm)
|
|
return [] if vm.snapshots.empty?
|
|
tmps = begin
|
|
client.servers.templates.select(&:is_a_snapshot)
|
|
rescue
|
|
[]
|
|
end
|
|
retval = []
|
|
tmps.each do |snapshot|
|
|
retval << snapshot if vm.snapshots.include?(snapshot.reference)
|
|
end
|
|
retval
|
|
end
|
|
|
|
def find_snapshots
|
|
tmps = begin
|
|
client.servers.templates.select(&:is_a_snapshot)
|
|
rescue
|
|
[]
|
|
end
|
|
tmps.sort_by(&:name)
|
|
end
|
|
|
|
def new_vm(attr = {})
|
|
test_connection
|
|
return unless errors.empty?
|
|
opts = vm_instance_defaults.merge(attr.to_hash).symbolize_keys
|
|
|
|
[:networks, :volumes].each do |collection|
|
|
nested_attrs = opts.delete("#{collection}_attributes".to_sym)
|
|
opts[collection] = nested_attributes_for(collection, nested_attrs) if nested_attrs
|
|
end
|
|
opts.reject! { |_, v| v.nil? }
|
|
client.servers.new opts
|
|
end
|
|
|
|
def create_vm(args = {})
|
|
custom_template_name = args[:custom_template_name].to_s
|
|
builtin_template_name = args[:builtin_template_name].to_s
|
|
|
|
if builtin_template_name != '' && custom_template_name != ''
|
|
logger.info "custom_template_name: #{custom_template_name}"
|
|
logger.info "builtin_template_name: #{builtin_template_name}"
|
|
raise 'you can select at most one template type'
|
|
end
|
|
begin
|
|
logger.info "create_vm(): custom_template_name: #{custom_template_name}"
|
|
logger.info "create_vm(): builtin_template_name: #{builtin_template_name}"
|
|
vm = custom_template_name != '' ? create_vm_from_custom(args) : create_vm_from_builtin(args)
|
|
vm.set_attribute('name_description', 'Provisioned by Foreman')
|
|
vm.set_attribute('VCPUs_max', args[:vcpus_max])
|
|
vm.set_attribute('VCPUs_at_startup', args[:vcpus_max])
|
|
vm.reload
|
|
return vm
|
|
rescue => e
|
|
logger.info e
|
|
logger.info e.backtrace.join("\n")
|
|
return false
|
|
end
|
|
end
|
|
|
|
def create_vm_from_custom(args)
|
|
mem_max = args[:memory_max]
|
|
mem_min = args[:memory_min]
|
|
|
|
host = get_hypervisor_host(args)
|
|
|
|
logger.info "create_vm_from_builtin: host : #{host.name}"
|
|
|
|
raise 'Memory max cannot be lower than Memory min' if mem_min.to_i > mem_max.to_i
|
|
|
|
template = client.custom_templates.select { |t| t.name == args[:custom_template_name] }.first
|
|
vm = template.clone args[:name]
|
|
vm.affinity = host
|
|
|
|
vm.provision
|
|
|
|
begin
|
|
vm.vifs.first.destroy
|
|
rescue
|
|
nil
|
|
end
|
|
|
|
create_network(vm, args)
|
|
|
|
args['xenstore']['vm-data']['ifs']['0']['mac'] = vm.vifs.first.mac
|
|
xenstore_data = xenstore_hash_flatten(args['xenstore'])
|
|
|
|
vm.set_attribute('xenstore_data', xenstore_data)
|
|
if vm.memory_static_max.to_i < mem_max.to_i
|
|
vm.set_attribute('memory_static_max', mem_max)
|
|
vm.set_attribute('memory_dynamic_max', mem_max)
|
|
vm.set_attribute('memory_dynamic_min', mem_min)
|
|
vm.set_attribute('memory_static_min', mem_min)
|
|
else
|
|
vm.set_attribute('memory_static_min', mem_min)
|
|
vm.set_attribute('memory_dynamic_min', mem_min)
|
|
vm.set_attribute('memory_dynamic_max', mem_max)
|
|
vm.set_attribute('memory_static_max', mem_max)
|
|
end
|
|
|
|
disks = vm.vbds.select { |vbd| vbd.type == 'Disk' }
|
|
disks.sort! { |a, b| a.userdevice <=> b.userdevice }
|
|
i = 0
|
|
disks.each do |vbd|
|
|
vbd.vdi.set_attribute('name-label', "#{args[:name]}_#{i}")
|
|
i += 1
|
|
end
|
|
vm
|
|
end
|
|
|
|
def create_vm_from_builtin(args)
|
|
mem_max = args[:memory_max]
|
|
mem_min = args[:memory_min]
|
|
|
|
host = get_hypervisor_host(args)
|
|
|
|
logger.info "create_vm_from_builtin: host : #{host.name}"
|
|
|
|
builtin_template_name = args[:builtin_template_name]
|
|
builtin_template_name = builtin_template_name.to_s
|
|
|
|
storage_repository = client.storage_repositories.find { |sr| sr.uuid == (args[:VBDs][:sr_uuid]).to_s }
|
|
|
|
gb = 1_073_741_824 # 1gb in bytes
|
|
size = args[:VBDs][:physical_size].to_i * gb
|
|
vdi = client.vdis.create :name => "#{args[:name]}-disk1",
|
|
:storage_repository => storage_repository,
|
|
:description => "#{args[:name]}-disk_1",
|
|
:virtual_size => size.to_s
|
|
|
|
other_config = {}
|
|
if builtin_template_name != ''
|
|
template = client.servers.builtin_templates.find { |tmp| tmp.name == args[:builtin_template_name] }
|
|
other_config = template.other_config
|
|
other_config.delete 'disks'
|
|
other_config.delete 'default_template'
|
|
other_config['mac_seed'] = SecureRandom.uuid
|
|
end
|
|
vm = client.servers.new :name => args[:name],
|
|
:affinity => host,
|
|
:pv_bootloader => '',
|
|
:hvm_boot_params => { :order => 'dnc' },
|
|
:other_config => other_config,
|
|
:memory_static_max => mem_max,
|
|
:memory_static_min => mem_min,
|
|
:memory_dynamic_max => mem_max,
|
|
:memory_dynamic_min => mem_min
|
|
|
|
vm.save :auto_start => false
|
|
client.vbds.create :vm => vm, :vdi => vdi
|
|
|
|
create_network(vm, args)
|
|
|
|
if args[:xstools] == '1'
|
|
# Add xs-tools ISO to newly created VMs
|
|
dvd_vdi = client.vdis.find { |isovdi| isovdi.name == 'xs-tools.iso' }
|
|
vbdconnectcd = {
|
|
'vdi' => dvd_vdi,
|
|
'vm' => vm.reference,
|
|
'userdevice' => '1',
|
|
'mode' => 'RO',
|
|
'type' => 'cd',
|
|
'other_config' => {},
|
|
'qos_algorithm_type' => '',
|
|
'qos_algorithm_params' => {}
|
|
}
|
|
vm.vbds = client.vbds.create vbdconnectcd
|
|
vm.reload
|
|
end
|
|
|
|
vm.provision
|
|
vm.set_attribute('HVM_boot_policy', 'BIOS order')
|
|
vm.reload
|
|
vm
|
|
end
|
|
|
|
def console(uuid)
|
|
vm = find_vm_by_uuid(uuid)
|
|
raise 'VM is not running!' unless vm.ready?
|
|
|
|
console = vm.service.consoles.find { |c| c.vm && c.vm.reference == vm.reference && c.protocol == 'rfb' }
|
|
raise "No console for vm #{vm.name}" if console.nil?
|
|
|
|
session_ref = (vm.service.instance_variable_get :@connection).instance_variable_get :@credentials
|
|
full_url = "#{console.location}&session_id=#{session_ref}"
|
|
tunnel = VNCTunnel.new full_url
|
|
tunnel.start
|
|
logger.info 'VNCTunnel started'
|
|
WsProxy.start(
|
|
:host => tunnel.host,
|
|
:host_port => tunnel.port,
|
|
:password => ''
|
|
).merge(
|
|
:type => 'vnc',
|
|
:name => vm.name
|
|
)
|
|
|
|
rescue Error => e
|
|
logger.warn e
|
|
raise e
|
|
end
|
|
|
|
def hypervisor
|
|
client.hosts.first
|
|
end
|
|
|
|
protected
|
|
|
|
def client
|
|
@client ||= ::Fog::Compute.new(
|
|
:provider => 'XenServer',
|
|
:xenserver_url => url,
|
|
:xenserver_username => user,
|
|
:xenserver_password => password,
|
|
:xenserver_redirect_to_master => true
|
|
)
|
|
end
|
|
|
|
def disconnect
|
|
client.terminate if @client
|
|
@client = nil
|
|
end
|
|
|
|
def vm_instance_defaults
|
|
super.merge({})
|
|
end
|
|
|
|
private
|
|
|
|
def create_network(vm, args)
|
|
net = client.networks.find { |n| n.name == args[:VIFs][:print] }
|
|
net_config = {
|
|
'mac_autogenerated' => 'True',
|
|
'vm' => vm.reference,
|
|
'network' => net.reference,
|
|
'mac' => '',
|
|
'device' => '0',
|
|
'mtu' => '0',
|
|
'other_config' => {},
|
|
'qos_algorithm_type' => 'ratelimit',
|
|
'qos_algorithm_params' => {}
|
|
}
|
|
vm.vifs = client.vifs.create net_config
|
|
vm.reload
|
|
end
|
|
|
|
def xenstore_hash_flatten(nested_hash, key = nil, keychain = nil, out_hash = {})
|
|
nested_hash.each do |k, v|
|
|
if v.is_a? Hash
|
|
xenstore_hash_flatten(v, k, "#{keychain}#{k}/", out_hash)
|
|
else
|
|
out_hash["#{keychain}#{k}"] = v
|
|
end
|
|
end
|
|
out_hash
|
|
end
|
|
|
|
def get_templates(templates)
|
|
tmps = templates.select { |t| !t.is_a_snapshot }
|
|
tmps.sort_by(&:name)
|
|
end
|
|
|
|
def get_hypervisor_host(args)
|
|
return client.hosts.first unless args[:hypervisor_host] != ''
|
|
client.hosts.find { |host| host.name == args[:hypervisor_host] }
|
|
end
|
|
|
|
def read_from_cache(key, fallback)
|
|
value = Rails.cache.fetch(cache_key + key) { public_send(fallback) }
|
|
value
|
|
end
|
|
|
|
def store_in_cache(key)
|
|
value = yield
|
|
Rails.cache.write(cache_key + key, value)
|
|
value
|
|
end
|
|
|
|
def cache_key
|
|
"computeresource_#{id}/"
|
|
end
|
|
end
|
|
end
|