mirror of
https://github.com/fog/fog.git
synced 2022-11-09 13:51:43 -05:00
9ee029e70a
Templates used to be included in Compute[:vsphere].servers.all, but they've been pruned out for awhile now. This patch adds the functionality back in so that templates may be located for cloning.
260 lines
9.7 KiB
Ruby
260 lines
9.7 KiB
Ruby
require 'digest/sha2'
|
|
|
|
module Fog
|
|
module Compute
|
|
class Vsphere < Fog::Service
|
|
|
|
requires :vsphere_username, :vsphere_password, :vsphere_server
|
|
recognizes :vsphere_port, :vsphere_path, :vsphere_ns
|
|
recognizes :vsphere_rev, :vsphere_ssl, :vsphere_expected_pubkey_hash
|
|
|
|
model_path 'fog/vsphere/models/compute'
|
|
model :server
|
|
collection :servers
|
|
model :datacenter
|
|
collection :datacenters
|
|
model :interface
|
|
collection :interfaces
|
|
model :volume
|
|
collection :volumes
|
|
model :template
|
|
collection :templates
|
|
model :cluster
|
|
collection :clusters
|
|
model :resource_pool
|
|
collection :resource_pools
|
|
model :network
|
|
collection :networks
|
|
model :datastore
|
|
collection :datastores
|
|
model :folder
|
|
collection :folders
|
|
|
|
request_path 'fog/vsphere/requests/compute'
|
|
request :current_time
|
|
request :list_virtual_machines
|
|
request :vm_power_off
|
|
request :vm_power_on
|
|
request :vm_reboot
|
|
request :vm_clone
|
|
request :vm_destroy
|
|
request :vm_migrate
|
|
request :list_datacenters
|
|
request :get_datacenter
|
|
request :list_clusters
|
|
request :get_cluster
|
|
request :list_resource_pools
|
|
request :get_resource_pool
|
|
request :list_networks
|
|
request :get_network
|
|
request :list_datastores
|
|
request :get_datastore
|
|
request :list_templates
|
|
request :get_template
|
|
request :get_folder
|
|
request :list_folders
|
|
request :create_vm
|
|
request :list_vm_interfaces
|
|
request :list_vm_volumes
|
|
request :get_virtual_machine
|
|
request :vm_reconfig_hardware
|
|
request :vm_reconfig_memory
|
|
request :vm_reconfig_cpus
|
|
request :vm_config_vnc
|
|
request :create_folder
|
|
|
|
module Shared
|
|
|
|
attr_reader :vsphere_is_vcenter
|
|
attr_reader :vsphere_rev
|
|
attr_reader :vsphere_server
|
|
attr_reader :vsphere_username
|
|
|
|
protected
|
|
|
|
ATTR_TO_PROP = {
|
|
:id => 'config.instanceUuid',
|
|
:name => 'name',
|
|
:uuid => 'config.uuid',
|
|
:template => 'config.template',
|
|
:parent => 'parent',
|
|
:hostname => 'summary.guest.hostName',
|
|
:operatingsystem => 'summary.guest.guestFullName',
|
|
:ipaddress => 'guest.ipAddress',
|
|
:power_state => 'runtime.powerState',
|
|
:connection_state => 'runtime.connectionState',
|
|
:hypervisor => 'runtime.host',
|
|
:tools_state => 'guest.toolsStatus',
|
|
:tools_version => 'guest.toolsVersionStatus',
|
|
:memory_mb => 'config.hardware.memoryMB',
|
|
:cpus => 'config.hardware.numCPU',
|
|
:overall_status => 'overallStatus',
|
|
:guest_id => 'summary.guest.guestId',
|
|
}
|
|
|
|
def convert_vm_view_to_attr_hash(vms)
|
|
vms = @connection.serviceContent.propertyCollector.collectMultiple(vms,*ATTR_TO_PROP.values.uniq)
|
|
vms.map { |vm| props_to_attr_hash(*vm) }
|
|
end
|
|
|
|
# Utility method to convert a VMware managed object into an attribute hash.
|
|
# This should only really be necessary for the real class.
|
|
# This method is expected to be called by the request methods
|
|
# in order to massage VMware Managed Object References into Attribute Hashes.
|
|
def convert_vm_mob_ref_to_attr_hash(vm_mob_ref)
|
|
return nil unless vm_mob_ref
|
|
|
|
props = vm_mob_ref.collect!(*ATTR_TO_PROP.values.uniq)
|
|
props_to_attr_hash vm_mob_ref, props
|
|
end
|
|
|
|
def props_to_attr_hash vm_mob_ref, props
|
|
# NOTE: Object.tap is in 1.8.7 and later.
|
|
# Here we create the hash object that this method returns, but first we need
|
|
# to add a few more attributes that require additional calls to the vSphere
|
|
# API. The hypervisor name and mac_addresses attributes may not be available
|
|
# so we need catch any exceptions thrown during lookup and set them to nil.
|
|
#
|
|
# The use of the "tap" method here is a convenience, it allows us to update the
|
|
# hash object without explicitly returning the hash at the end of the method.
|
|
Hash[ATTR_TO_PROP.map { |k,v| [k.to_s, props[v]] }].tap do |attrs|
|
|
attrs['id'] ||= vm_mob_ref._ref
|
|
attrs['mo_ref'] = vm_mob_ref._ref
|
|
# The name method "magically" appears after a VM is ready and
|
|
# finished cloning.
|
|
if attrs['hypervisor'].kind_of?(RbVmomi::VIM::HostSystem)
|
|
host = attrs['hypervisor']
|
|
attrs['datacenter'] = Proc.new { parent_attribute(host.path, :datacenter)[1] rescue nil }
|
|
attrs['cluster'] = Proc.new { parent_attribute(host.path, :cluster)[1] rescue nil }
|
|
attrs['hypervisor'] = Proc.new { host.name rescue nil }
|
|
attrs['resource_pool'] = Proc.new {(vm_mob_ref.resourcePool || host.resourcePool).name rescue nil}
|
|
end
|
|
# This inline rescue catches any standard error. While a VM is
|
|
# cloning, a call to the macs method will throw and NoMethodError
|
|
attrs['mac_addresses'] = Proc.new {vm_mob_ref.macs rescue nil}
|
|
# Rescue nil to catch testing while vm_mob_ref isn't reaL??
|
|
attrs['path'] = "/"+attrs['parent'].path.map(&:last).join('/') rescue nil
|
|
attrs['relative_path'] = (attrs['path'].split('/').reject {|e| e.empty?} - ["Datacenters", attrs['datacenter'], "vm"]).join("/") rescue nil
|
|
end
|
|
end
|
|
# returns the parent object based on a type
|
|
# provides both real RbVmomi object and its name.
|
|
# e.g.
|
|
#[Datacenter("datacenter-2"), "dc-name"]
|
|
def parent_attribute path, type
|
|
element = case type
|
|
when :datacenter
|
|
RbVmomi::VIM::Datacenter
|
|
when :cluster
|
|
RbVmomi::VIM::ClusterComputeResource
|
|
when :host
|
|
RbVmomi::VIM::HostSystem
|
|
else
|
|
raise "Unknown type"
|
|
end
|
|
path.select {|x| x[0].is_a? element}.flatten
|
|
rescue
|
|
nil
|
|
end
|
|
|
|
# returns vmware managed obj id string
|
|
def managed_obj_id obj
|
|
obj.to_s.match(/\("([^"]+)"\)/)[1]
|
|
end
|
|
|
|
end
|
|
|
|
class Mock
|
|
|
|
include Shared
|
|
|
|
def initialize(options={})
|
|
require 'rbvmomi'
|
|
@vsphere_username = options[:vsphere_username]
|
|
@vsphere_password = 'REDACTED'
|
|
@vsphere_server = options[:vsphere_server]
|
|
@vsphere_expected_pubkey_hash = options[:vsphere_expected_pubkey_hash]
|
|
@vsphere_is_vcenter = true
|
|
@vsphere_rev = '4.0'
|
|
end
|
|
|
|
end
|
|
|
|
class Real
|
|
|
|
include Shared
|
|
|
|
def initialize(options={})
|
|
require 'rbvmomi'
|
|
@vsphere_username = options[:vsphere_username]
|
|
@vsphere_password = options[:vsphere_password]
|
|
@vsphere_server = options[:vsphere_server]
|
|
@vsphere_port = options[:vsphere_port] || 443
|
|
@vsphere_path = options[:vsphere_path] || '/sdk'
|
|
@vsphere_ns = options[:vsphere_ns] || 'urn:vim25'
|
|
@vsphere_rev = options[:vsphere_rev] || '4.0'
|
|
@vsphere_ssl = options[:vsphere_ssl] || true
|
|
@vsphere_expected_pubkey_hash = options[:vsphere_expected_pubkey_hash]
|
|
@vsphere_must_reauthenticate = false
|
|
|
|
@connection = nil
|
|
# This is a state variable to allow digest validation of the SSL cert
|
|
bad_cert = false
|
|
loop do
|
|
begin
|
|
@connection = RbVmomi::VIM.new :host => @vsphere_server,
|
|
:port => @vsphere_port,
|
|
:path => @vsphere_path,
|
|
:ns => @vsphere_ns,
|
|
:rev => @vsphere_rev,
|
|
:ssl => @vsphere_ssl,
|
|
:insecure => bad_cert
|
|
break
|
|
rescue OpenSSL::SSL::SSLError
|
|
raise if bad_cert
|
|
bad_cert = true
|
|
end
|
|
end
|
|
|
|
if bad_cert then
|
|
validate_ssl_connection
|
|
end
|
|
|
|
# Negotiate the API revision
|
|
if not options[:vsphere_rev]
|
|
rev = @connection.serviceContent.about.apiVersion
|
|
@connection.rev = [ rev, ENV['FOG_VSPHERE_REV'] || '4.1' ].min
|
|
end
|
|
|
|
@vsphere_is_vcenter = @connection.serviceContent.about.apiType == "VirtualCenter"
|
|
@vsphere_rev = @connection.rev
|
|
|
|
authenticate
|
|
end
|
|
|
|
private
|
|
|
|
def authenticate
|
|
begin
|
|
@connection.serviceContent.sessionManager.Login :userName => @vsphere_username,
|
|
:password => @vsphere_password
|
|
rescue RbVmomi::VIM::InvalidLogin => e
|
|
raise Fog::Vsphere::Errors::ServiceError, e.message
|
|
end
|
|
end
|
|
|
|
# Verify a SSL certificate based on the hashed public key
|
|
def validate_ssl_connection
|
|
pubkey = @connection.http.peer_cert.public_key
|
|
pubkey_hash = Digest::SHA2.hexdigest(pubkey.to_s)
|
|
expected_pubkey_hash = @vsphere_expected_pubkey_hash
|
|
if pubkey_hash != expected_pubkey_hash then
|
|
raise Fog::Vsphere::Errors::SecurityError, "The remote system presented a public key with hash #{pubkey_hash} but we're expecting a hash of #{expected_pubkey_hash || '<unset>'}. If you are sure the remote system is authentic set vsphere_expected_pubkey_hash: <the hash printed in this message> in ~/.fog"
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|