Ack'd but I had to add the storage field from nil to a bogus value, since seems that conductor does not manage it well.
On Fri, Jun 17, 2011 at 11:33:33AM +0200, [email protected] wrote: > From: David Lutterkort <[email protected]> > > > Signed-off-by: Michal fojtik <[email protected]> > --- > server/config/drivers.yaml | 7 + > .../deltacloud/drivers/vsphere/vsphere_driver.rb | 340 > ++++++++++++++++++++ > 2 files changed, 347 insertions(+), 0 deletions(-) > create mode 100644 server/lib/deltacloud/drivers/vsphere/vsphere_driver.rb > > diff --git a/server/config/drivers.yaml b/server/config/drivers.yaml > index 20b2857..4d06d93 100644 > --- a/server/config/drivers.yaml > +++ b/server/config/drivers.yaml > @@ -46,3 +46,10 @@ > eu-west-1: ec2.eu-west-1.amazonaws.com > us-east-1: ec2.us-east-1.amazonaws.com > :name: EC2 > +:vsphere: > + :name: VSphere > + :username: Login Name > + :password: Password > + :entrypoints: > + default: > + default: "https://vsphere.provider.com/sdk" > diff --git a/server/lib/deltacloud/drivers/vsphere/vsphere_driver.rb > b/server/lib/deltacloud/drivers/vsphere/vsphere_driver.rb > new file mode 100644 > index 0000000..52835c9 > --- /dev/null > +++ b/server/lib/deltacloud/drivers/vsphere/vsphere_driver.rb > @@ -0,0 +1,340 @@ > +# Licensed to the Apache Software Foundation (ASF) under one or more > +# contributor license agreements. See the NOTICE file distributed with > +# this work for additional information regarding copyright ownership. The > +# ASF licenses this file to you 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 'deltacloud/base_driver' > +require 'rbvmomi' > + > +module Deltacloud::Drivers::VSphere > + > + class VSphereDriver < Deltacloud::BaseDriver > + > + # Set of predefined hardware profiles > + define_hardware_profile('small') do > + cpu 1 > + memory 256 > + architecture 'x86_64' > + end > + > + define_hardware_profile('medium') do > + cpu 1 > + memory 512 > + architecture 'x86_64' > + end > + > + define_hardware_profile('large') do > + cpu 2 > + memory 1024 > + architecture 'x86_64' > + end > + > + define_hardware_profile('x-large') do > + cpu 4 > + memory 2048 > + architecture 'x86_64' > + end > + > + # Since user can launch own instance using vSphere tools > + # with customized properties, threat this hardware profile as > + # unknown > + define_hardware_profile('unknown') do > + # NOTE: Memory and CPU should be set properly > + architecture 'x86_64' > + end > + > + # Configure instance state machine > + define_instance_states do > + start.to(:pending) .on( :create ) > + pending.to(:stopped) .automatically > + stopped.to(:running) .on( :start ) > + running.to(:running) .on( :reboot ) > + running.to(:shutting_down) .on( :stop ) > + shutting_down.to(:stopped) .automatically > + stopped.to(:finish) .on( :destroy ) > + end > + > + > + # List all images, across all datacenters. Note: Deltacloud API does not > + # yet support filtering images by realm. > + def images(credentials, opts=nil) > + cloud = new_client(credentials) > + img_arr = [] > + > + # Skip traversing through all instances in all datacenters when ID > + # attribute is set > + safely do > + if opts[:id] > + template_vms = [ find_vm(credentials, opts[:id]) ].compact > + else > + template_vms = list_virtual_machines(credentials).select { |vm| > vm.summary.config[:template] } > + end > + > + img_arr = template_vms.collect do |image| > + # Since all calls to vm are threaten as SOAP calls, reduce them > using > + # local variable. > + config = image.summary.config > + instance_state = convert_state(:instance, > image.summary.runtime[:powerState]) > + properties = { > + :name => config[:name], > + :full_name => config[:guestFullName] > + } > + image_state = convert_state(:image, > image.summary.runtime[:powerState]) > + Image.new( > + :id => properties[:name], > + :name => properties[:name], > + :architecture => 'x86_64', # FIXME: I'm not sure if all > templates/VM's in vSphere are x86_64 > + :owner_id => credentials.user, > + :description => properties[:full_name], > + :state => image_state > + ) > + end > + end > + > + img_arr = filter_on( img_arr, :architecture, opts ) > + img_arr.sort_by{|e| [e.owner_id, e.name]} > + end > + > + def create_image(credentials, opts={}) > + vsphere = new_client(credentials) > + safely do > + find_vm(credentials, opts[:id]).MarkAsTemplate > + end > + image(credentials, :id => opts[:id]) > + end > + > + # List all datacenters managed by the vSphere or vCenter entrypoint. > + def realms(credentials, opts=nil) > + vsphere = new_client(credentials) > + safely do > + rootFolder = vsphere.serviceInstance.content.rootFolder > + rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).collect do |dc| > + Realm.new( > + :id => dc.name, > + :name => dc.name, > + :limit => :unlimited, > + :state => convert_state(:datacenter, dc.configStatus) > + ) > + end > + end > + end > + > + # List all running instances, across all datacenters. DeltaCloud API does > + # not yet support filtering instances by realm. > + def instances(credentials, opts=nil) > + cloud = new_client(credentials) > + inst_arr, machine_vms = [], [] > + safely do > + if opts[:id] > + machine_vms = [ find_vm(credentials, opts[:id]) ].compact > + else > + machine_vms = list_virtual_machines(credentials).select { |vm| > !vm.summary.config[:template] } > + end > + end > + realm_id = realms(credentials).first.id > + safely do > + inst_arr = machine_vms.collect do |vm| > + # Since all calls to vm are threaten as SOAP calls, reduce them > using > + # local variable. > + config = vm.summary.config > + next unless config > + next unless vm.summary.storage > + properties = { > + :memory => config[:memorySizeMB], > + :cpus => config[:numCpu], > + :storage => vm.summary.storage[:unshared], > + :name => config[:name], > + :full_name => config[:guestFullName] > + } > + instance_state = convert_state(:instance, > vm.summary.runtime[:powerState]) > + instance_profile = InstanceProfile::new(match_hwp_id(:memory => > properties[:memory].to_s, :cpus => properties[:cpus].to_s), > + :hwp_cpu => > properties[:cpus], > + :hwp_memory => > properties[:memory], > + :hwp_storage => > properties[:storage]) > + instance_address = vm.guest[:net].empty? ? vm.macs.values.first : > vm.guest[:net].first[:ipAddress].first > + Instance.new( > + :id => properties[:name], > + :name => properties[:name], > + :owner_id => credentials.user, > + :description => properties[:full_name], > + :realm_id => realm_id, > + :state => instance_state, > + :public_addresses => instance_address, > + :private_addresses => [], > + :instance_profile => instance_profile, > + :actions => instance_actions_for( instance_state ), > + :create_image => true > + ) > + end > + end > + filter_on( inst_arr, :state, opts ) > + end > + > + > + def create_instance(credentials, image_id, opts) > + vsphere = new_client(credentials) > + safely do > + rootFolder = vsphere.serviceInstance.content.rootFolder > + # FIXME: This will consume first datacenter and ignore 'realm' > property > + dc = > rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).collect.first > + vm = dc.find_vm(image_id) > + resource_pool = dc.hostFolder.childEntity.collect.first.resourcePool > + relocateSpec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => > resource_pool) > + # NOTE: 'powerOn' attribute will force machine to start after clone > operation > + # 'template' attribute will mark VM as a template if set to > true > + spec = RbVmomi::VIM.VirtualMachineCloneSpec( > + :location => relocateSpec, > + :powerOn => true, > + :template => false, > + :config => RbVmomi::VIM.VirtualMachineConfigSpec( > + :memoryMB => 256, > + :numCPUs => 1 > + ) > + ) > + # NOTE: This operation may take a very long time (about 1m) to > complete > + puts "Cloning template #{image_id} to #{opts[:name]}..." > + vm.CloneVM_Task(:folder => vm.parent, :name => opts[:name], :spec => > spec).wait_for_completion > + puts "Cloning complete!" > + # Since task itself is not returning anything, construct Instance > from things we already have > + Instance::new( > + :id => opts[:name], > + :name => opts[:name], > + :owner_id => credentials.user, > + :realm_id => dc.name, > + :state => 'PENDING', > + :instance_profile => InstanceProfile::new('default'), > + :actions => instance_actions_for( 'PENDING' ) > + ) > + end > + end > + > + # Reboot an instance, given its id. > + def reboot_instance(credentials, id) > + find_vm(credentials, id).ResetVM_Task > + end > + > + # Start an instance, given its id. > + def start_instance(credentials, id) > + find_vm(credentials, id).PowerOnVM_Task > + end > + > + # Stop an instance, given its id. > + def stop_instance(credentials, id) > + find_vm(credentials, id).PowerOffVM_Task > + end > + > + # Destroy an instance, given its id. Note that this will destory all > + # instance data. > + def destroy_instance(credentials, id) > + find_vm(credentials, id).Destroy_Task.wait_for_completion > + end > + > + exceptions do > + > + on /InvalidLogin/ do > + status 401 > + end > + > + on /RbVmomi::Fault/ do > + status 502 > + end > + > + end > + > + def valid_credentials?(credentials) > + begin > + RbVmomi::VIM.connect(:host => host_endpoint, :user => > credentials.user, :password => credentials.password, :insecure => true) > + return true > + rescue > + return false > + end > + end > + > + ####### > + private > + ####### > + > + def new_client(credentials) > + safely do > + RbVmomi::VIM.connect(:host => host_endpoint, :user => > credentials.user, :password => credentials.password, :insecure => true) > + end > + end > + > + def host_endpoint > + endpoint = (Thread.current[:provider] || ENV['API_PROVIDER']) > + endpoint || > Deltacloud::Drivers::driver_config[:vsphere][:entrypoints]['default']['default'] > + end > + > + def list_virtual_machines(credentials) > + vsphere = new_client(credentials) > + vms = [] > + rootFolder = vsphere.serviceInstance.content.rootFolder > + rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).each do |dc| > + vms += dc.vmFolder.childEntity.collect do |ent| > + if ent.class.name == 'RbVmomi::VIM::Folder' > + ent.childEntity.grep(RbVmomi::VIM::VirtualMachine).collect > + else > + ent > + end > + end > + end > + vms.flatten.compact > + end > + > + def find_vm(credentials, name) > + vsphere = new_client(credentials) > + safely do > + rootFolder = vsphere.serviceInstance.content.rootFolder > + dc = > rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).collect.first > + dc.find_vm(name) > + end > + end > + > + def convert_state(object, state) > + new_state = '' > + if object == :image > + new_state = case state > + when 'poweredOff' then 'AVAILABLE' > + when 'poweredOn' then 'UNAVAILABLE' > + end > + end > + if object == :instance > + new_state = case state > + when 'poweredOff' then 'STOPPED' > + when 'poweredOn' then 'RUNNING' > + else 'PENDING' > + end > + end > + if object == :datacenter > + new_state = case state > + when 'gray', 'green' then 'AVAILABLE' > + else 'UNAVAILABLE' > + end > + end > + new_state > + end > + > + # Match hardware profile ID against given properties > + def match_hwp_id(prop) > + return 'small' if prop[:memory] == '256' and prop[:cpus] == '1' > + return 'medium' if prop[:memory] == '512' and prop[:cpus] == '1' > + return 'large' if prop[:memory] == '1024' and prop[:cpus] == '2' > + return 'x-large' if prop[:memory] == '2048' and prop[:cpus] == '4' > + 'unknown' > + end > + > + > + end > + > +end > -- > 1.7.4.1 >
