From: marios marios@redhat.com
--- .../drivers/terremark/terremark_driver.rb | 261 ++++++++++++++++++++ server/lib/drivers.rb | 11 +- 2 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 server/lib/deltacloud/drivers/terremark/terremark_driver.rb
diff --git a/server/lib/deltacloud/drivers/terremark/terremark_driver.rb b/server/lib/deltacloud/drivers/terremark/terremark_driver.rb new file mode 100644 index 0000000..173a174 --- /dev/null +++ b/server/lib/deltacloud/drivers/terremark/terremark_driver.rb @@ -0,0 +1,261 @@ +# Copyright (C) 2010 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# This driver uses the fog library (Geemus - Wesley Beary) to talk to terremark... see +# http://github.com/geemus/fog +# see terremark vcloud express api at: +# https://community.vcloudexpress.terremark.com/en-us/product_docs/w/wiki/d-co... +# +# 02 May 2010 +# +require 'deltacloud/base_driver' +require 'fog' +require 'excon' +require 'nokogiri' + +module Deltacloud + module Drivers + module Terremark + +class TerremarkDriver < Deltacloud::BaseDriver + + feature :instances, :user_name + +#-- +# Vapp State Map... for use with convert_instance (get an integer back from terremark) +#-- +VAPP_STATE_MAP = { "0" => "PENDING", "1" => "PENDING", "2" => "STOPPED", "4" => "RUNNING" } + +#-- +# HARDWARE PROFILES +#-- + define_hardware_profile 'default' do + cpu [1,2,4,8] + memory [512, 1024, 2048, 4096, 8192] + storage (1..500).to_a + end + #storage_disks [1..15] + +#-- +# IMAGES +#-- +#aka "vapp_templates" + def images(credentials, opts=nil) + image_list = [] + terremark_client = new_client(credentials) + vdc_id = terremark_client.default_vdc_id + catalogItems = terremark_client.get_catalog(vdc_id).body['CatalogItems'] + catalogItems.each{ |catalog_item| + current_item_id = catalog_item['href'].split('/').last + current_item = terremark_client.get_catalog_item(current_item_id).body['Entity'] + if(current_item['type'] == 'application/vnd.vmware.vcloud.vAppTemplate+xml') + image_list << convert_image(current_item, credentials.user) + end + } #end of catalogItems.each + image_list = filter_on( image_list, :id, opts ) + image_list = filter_on( image_list, :architecture, opts ) + image_list = filter_on( image_list, :owner_id, opts ) + image_list + end + +#-- +# REALMS +#-- +#only one realm... everything in US? + def realms(credentials, opts=nil) + [Realm.new( { + :id=>"US-Miami", + :name=>"United States - Miami", + :state=> "AVAILABLE" + } )] + end + +#-- +# INSTANCES +#-- +#aka vApps + def instances(credentials, opts=nil) + instances = [] + terremark_client = new_client(credentials) + vdc_items = terremark_client.get_vdc(terremark_client.default_vdc_id()).body['ResourceEntities'] + vdc_items.each{|current_item| + if(current_item['type'] == 'application/vnd.vmware.vcloud.vApp+xml') + vapp_id = current_item['href'].split('/').last + vapp = terremark_client.get_vapp(vapp_id) + instances << convert_instance(vapp, terremark_client, credentials.user) + end + }#end vdc_items.each + instances = filter_on( instances, :id, opts ) + instances + end + +#-- +# FINITE STATE MACHINE +#-- +#by default new instance --> powered_off + 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(:end) .on( :destroy ) + end + + +#-- +# CREATE INSTANCE +#-- +#launch a vapp template. Needs a name, ram, no. cpus, id of vapp_template + def create_instance(credentials, image_id, opts) + new_vapp = nil + vapp_opts = {} #assemble options to pass to Fog::Terremark::Real.instantiate_vapp_template + terremark_hwp = hardware_profiles(credentials, {:name => 'default'}).first #sanity check values against default + name = opts['name'] #name could be nil or length 0 or too long + name = "inst#{Time.now.to_i}" if (name.nil? || (name.length == 0)) + name = name.slice(0..13) #name < 15 chars (says terremark) + unless ( (terremark_hwp.include?(:cpu, opts[:hwp_cpu].to_i)) && + (terremark_hwp.include?(:memory, opts[:hwp_memory].to_i)) ) then + raise Deltacloud::Validation::Failure.new(Deltacloud::Validation::Param.new(["cpu"]), "Error with cpu and/or memory values. you said cpu->#{opts[:hwp_cpu]} and mem->#{opts[:hwp_memory]}") + end + vapp_opts['cpus'] = opts[:hwp_cpu] + vapp_opts['memory'] = opts[:hwp_memory] + terremark_client = new_client(credentials) +####### +#FIXME# what happens if there is an issue getting the new vapp id? (eg even though created succesfully) +####### + vapp_id = terremark_client.instantiate_vapp_template(name, image_id, vapp_opts).body['href'].split('/').last + new_vapp = terremark_client.get_vapp(vapp_id) + return convert_instance(new_vapp, terremark_client, credentials.user) #return an Instance object + end + +#-- +# REBOOT INSTANCE +#-- + def reboot_instance(credentials, id) + terremark_client = new_client(credentials) + terremark_client.power_reset(id) + end + +#-- +# START INSTANCE +#-- +def start_instance(credentials, id) + terremark_client = new_client(credentials) + terremark_client.power_on(id) +end + +#-- +# STOP INSTANCE +#-- +def stop_instance(credentials, id) + terremark_client = new_client(credentials) + terremark_client.power_shutdown(id) +end + +#-- +# DESTROY INSTANCE +#-- +#shuts down... in terremark need to do a futher delete to get rid of a vapp entirely +def destroy_instance(credentials, id) + terremark_client = new_client(credentials) + terremark_client.delete_vapp(id) +end + +#-- +# PRIVATE METHODS: +#-- + + private + +#-- +# CONVERT IMAGE +#-- +#gets a vapp_template from a catalog and makes it a Image + def convert_image(catalog_vapp_template, account_name) + name = catalog_vapp_template['name'] + #much fudging ensues + #arch = name.scan(/(36|24).bit/).first + #k enuf o'that now! + arch = "n/a" #Leaving out entirely as we don't get one from terremark (could parse but its a fudge) + Image.new( { + :id => catalog_vapp_template['href'].split('/').last, + :name => catalog_vapp_template['name'], + :architecture => arch, + :owner_id => account_name, + :description => catalog_vapp_template['name'] + }) + end + +#-- +# CONVERT INSTANCE +#-- + def convert_instance(vapp, terremark_client, account_name) + vapp_private_ip = vapp.body['IpAddress'] + vapp_public_ip = terremark_client.get_public_ips(terremark_client.default_vdc_id).body['PublicIpAddresses'].first['name']#get_public_address(terremark_client, vapp_private_ip) + vapp_status = vapp.body['status'] + current_state = VAPP_STATE_MAP[vapp_status] #status == 0->BEING_CREATED 2->OFF 4->ON + profile = InstanceProfile.new("default") + name = vapp.body['name'] + if current_state != "PENDING" #can only grab this stuff after instance is created + profile.cpu = vapp.body['VirtualHardware']['cpu'] + profile.memory = vapp.body['VirtualHardware']['ram'] +####### +#FIXME# could be more that one disk... but for now capture only first +####### + disk = ((vapp.body['VirtualHardware']['disks'].first.to_i) / 1024 / 1024).to_s + profile.storage = disk +####### +#FIXME# this is a hack, shouldn't place this info next to name as some clients may rely on name field... probably will introduce +####### a new field in the API for this (e.g. description/text field... human readable) + #name = "#{name} - [ #{vapp.body['OperatingSystem']['Description']} ]" + end + Instance.new( { + :id => vapp.body['href'].split('/').last, + :owner_id => "#{account_name}", + #:image_id => "n/a", #cant get this... see https://community.vcloudexpress.terremark.com/en-us/discussion_forums/f/60/t... + :name => name, + :realm_id => "US-Miami", + :state => current_state, + :actions => instance_actions_for(current_state), + :public_addresses => vapp_public_ip, + :private_addresses => vapp_private_ip, + :instance_profile => profile + } ) + end + +#-- +# NEW CLIENT +#-- +#use supplied credentials to make a new client for talking to terremark + def new_client(credentials) + #Fog constructor expecting credentials[:terremark_password] and credentials[:terremark_username] + terremark_credentials = {:terremark_vcloud_username => "#{credentials.user}", :terremark_vcloud_password => "#{credentials.password}" } + terremark_client = Fog::Terremark::Vcloud.new(terremark_credentials) + vdc_id = terremark_client.default_vdc_id + if (vdc_id.nil?) + raise DeltaCloud::AuthException.new + end + terremark_client + end + + +end + + end + end +end diff --git a/server/lib/drivers.rb b/server/lib/drivers.rb index 1add916..269fe75 100644 --- a/server/lib/drivers.rb +++ b/server/lib/drivers.rb @@ -5,6 +5,7 @@ DRIVERS = { :rhevm => { :name => "RHEVM" }, :rimu => { :name => "Rimu", :class => "RimuHostingDriver"}, :opennebula => { :name => "Opennebula", :class => "OpennebulaDriver" }, + :terremark => { :name => "Terremark"}, :mock => { :name => "Mock" } }
@@ -27,15 +28,7 @@ def driver_mock_source_name end
def driver - - begin - require driver_source_name - rescue LoadError => e - gem_name = e.message.match(/ -- (.+)$/).to_a.last - gem_name = "amazon-ec2" if gem_name.eql?('AWS') - $stderr.puts "ERROR: Please install required gem first. (gem install #{gem_name})" - exit 1 - end + require driver_source_name
if Sinatra::Application.environment.eql? :test require driver_mock_source_name