You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by mf...@redhat.com on 2011/06/14 13:42:07 UTC
Initial vSphere support
Hi,
This is an initial draft of VMWare vSphere driver for Deltacloud API.
I spend a lot of time debugging issues with 'spherical' gem and after
some time I gave up (eg. I was not able to call PowerOnVM_Task, etc..).
Then I found a gem called 'rbvmomi'[1] developed by regular VMWare employee.
This gem was claimed to work 'only' with 1.9 but it works nice on my
1.8.7 installation.
List of supported things:
- Hardware profiles (there are 4 predefined: small, medium, large and x-large)
Also there is a 'unknown' profile for VM's launched manually using vSphere GUI.
- Listing of images (will list VMs marked as 'template')
- Get single image (it's fast, since I'm using find_vm instead of filtering
whole collection)
- Listing of instances
- Get single instance (same as above)
- Instance operations: start, stop, destroy and reboot are working
flawlessly
- Create instance is working from predefined template. You can specify
a hardware profile and name, which will be used then as an 'id'.
Mostly because find_vm() method search for an instance name instead of
uuid.
List of unsupported/not-working things:
- Instance has *no* image information, because it's imposible to get
information about whot template was used to 'deploy' virtual machine.
If it's a problem, it can be solved by YAML/JSON local mapping, but I would
rather avoid this.
- Create instance is 'slow' (mean it takes like 50 seconds to launch a new
instance. Again this could be solved using some local mapper, but fist I want
to know if it's necessary or not.
How to use this driver:
- You will need vSphere installed and SOAP API enabled on some URL.
- Then:
API_PROVIDER="vsphere.provider.com" ./bin/deltacloudd -i vsphere
NOTE: 'http' and 'sdk' are not needed in PROVIDER
All other things should work as ussual.
-- Michal
[1] https://github.com/rlane/rbvmomi (look to examples directory)
[PATCH core] Initial VSphere driver support for Deltacloud API
Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>
Signed-off-by: Michal fojtik <mf...@redhat.com>
---
server/config/drivers.yaml | 7 +
.../deltacloud/drivers/vsphere/vsphere_driver.rb | 333 ++++++++++++++++++++
2 files changed, 340 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..9e123e4
--- /dev/null
+++ b/server/lib/deltacloud/drivers/vsphere/vsphere_driver.rb
@@ -0,0 +1,333 @@
+# 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'
+require 'json'
+
+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 = [ execute_on_vm(credentials, opts[:id]) ]
+ 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
+
+ # 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 = [ execute_on_vm(credentials, opts[:id]) ]
+ raise 'Not Found' if machine_vms.compact.empty?
+ 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
+ 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 )
+ )
+ 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
+ end
+ Instance::new(
+ :id => opts[:name],
+ :name => opts[:name],
+ :owner_id => credentials.user,
+ :realm_id => dc.name,
+ :state => 'RUNNING',
+ :instance_profile => InstanceProfile::new('default'),
+ :actions => instance_actions_for( 'RUNNING' )
+ )
+ end
+
+ def execute_on_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
+
+ # Reboot an instance, given its id.
+ def reboot_instance(credentials, id)
+ execute_on_vm(credentials, id).Reboot
+ end
+
+ # Start an instance, given its id.
+ def start_instance(credentials, id)
+ execute_on_vm(credentials, id).PowerOnVM_Task.wait_for_completion
+ end
+
+ # Stop an instance, given its id.
+ def stop_instance(credentials, id)
+ execute_on_vm(credentials, id).PowerOffVM_Task.wait_for_completion
+ end
+
+ # Destroy an instance, given its id. Note that this will destory all
+ # instance data.
+ def destroy_instance(credentials, id)
+ execute_on_vm(credentials, id).Destroy_Task.wait_for_completion
+ end
+
+ #######
+ private
+ #######
+
+ def new_client(credentials)
+ RbVmomi::VIM.connect(:host => host_endpoint, :user => credentials.user, :password => credentials.password, :insecure => true)
+ end
+
+ def host_endpoint
+ endpoint = (Thread.current[:provider] || ENV['API_PROVIDER'])
+ endpoint || Deltacloud::Drivers::driver_config[:vsphere][:entrypoints]['default']['default']
+ end
+
+ # Given an instance of the class representing an instance, generate a
+ # unique, reversable ID that can be used to refer to that
+ # instance asynchronously.
+ def generate_instance_id(host, inst)
+ # use the instance's UUID -- you can search by uuid later to deref it.
+ end
+
+ # Given a host instance, and a unique instance ID, get an instance of the
+ # class representing that VM instance.
+ def dereference_instance_id(host, id)
+ # use Spherical's find_by_uuid method to get a hold of an instance
+ 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 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
+
+ exceptions do
+
+ on /Not\ Found/ do
+ status 404
+ end
+
+ on /RbVmomi/ do
+ status 502
+ end
+
+ end
+
+ end
+
+end
--
1.7.4.1