Return-Path: X-Original-To: apmail-incubator-deltacloud-dev-archive@minotaur.apache.org Delivered-To: apmail-incubator-deltacloud-dev-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B73714870 for ; Wed, 15 Jun 2011 13:18:59 +0000 (UTC) Received: (qmail 47362 invoked by uid 500); 15 Jun 2011 13:18:59 -0000 Delivered-To: apmail-incubator-deltacloud-dev-archive@incubator.apache.org Received: (qmail 47345 invoked by uid 500); 15 Jun 2011 13:18:59 -0000 Mailing-List: contact deltacloud-dev-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: deltacloud-dev@incubator.apache.org Delivered-To: mailing list deltacloud-dev@incubator.apache.org Received: (qmail 47337 invoked by uid 99); 15 Jun 2011 13:18:59 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 15 Jun 2011 13:18:59 +0000 X-ASF-Spam-Status: No, hits=-4.6 required=5.0 tests=FILL_THIS_FORM_FRAUD_PHISH,RCVD_IN_DNSWL_HI,SPF_HELO_PASS,SPF_PASS,T_FILL_THIS_FORM_SHORT X-Spam-Check-By: apache.org Received-SPF: pass (athena.apache.org: domain of mfojtik@redhat.com designates 209.132.183.28 as permitted sender) Received: from [209.132.183.28] (HELO mx1.redhat.com) (209.132.183.28) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 15 Jun 2011 13:18:55 +0000 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id p5FDIYXJ008358 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Wed, 15 Jun 2011 09:18:35 -0400 Received: from dhcp-2-126.brq.redhat.com (dhcp-2-126.brq.redhat.com [10.34.2.126]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id p5FDIV4K026520 for ; Wed, 15 Jun 2011 09:18:33 -0400 From: mfojtik@redhat.com To: deltacloud-dev@incubator.apache.org Subject: [PATCH core 1/2] Initial VSphere driver support for Deltacloud API Date: Wed, 15 Jun 2011 15:19:07 +0200 Message-Id: <1308143948-8923-2-git-send-email-mfojtik@redhat.com> In-Reply-To: <1308143948-8923-1-git-send-email-mfojtik@redhat.com> References: <1308143948-8923-1-git-send-email-mfojtik@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.22 From: David Lutterkort Signed-off-by: Michal fojtik --- 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