One problem I ran into: when I created a new instance (IIRC, from image
3), the API isn't able to show that to me - it bombs with 'ERROR:
private method `split' called for nil:NilClass' and shows an empty web
page
On Mon, 2010-05-03 at 01:04 +0100, mandreou(a)redhat.com wrote:
From: marios <marios(a)redhat.com>
---
.../drivers/terremark/terremark_driver.rb | 271 ++++++++++++++++++++
server/lib/drivers.rb | 1 +
2 files changed, 272 insertions(+), 0 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..b7ff9bd
--- /dev/null
+++ b/server/lib/deltacloud/drivers/terremark/terremark_driver.rb
@@ -0,0 +1,271 @@
+require 'deltacloud/base_driver'
+path = File.dirname(__FILE__)
+$: << path
+begin
+ require 'fog'
+ require 'excon'
+ require 'nokogiri'
+rescue LoadError
+ puts "ERROR: please ensure fog, excon and nokogiri gems installed. ('gem
install fog', 'gem install excon' , 'gem install nokogiri')"
+ exit(1)
+end
I don't like doing this kind of idiot-proofing all over the code. Just
take out catching the LoadError, and we'll need to fix deltacloudd to
produce more understandable errors when loading fails.
+#--
+# IMAGES
+#--
+#aka "vapp_templates"
+ def images(credentials, opts=nil)
+ image_list = []
+ safely do
+ 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
+ end #end of safely do
+ 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
As mentioned in the other mail, the problem here is that the API
currently forces you to make n+1 calls to Terremark. Leave that as is
for now, and we'll address it separately once the driver has been
committed.
+#--
+# INSTANCES
+#--
+#aka vApps
+ def instances(credentials, opts=nil)
+ instances = []
+ safely do
+ 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
+ end #end safely do
+ instances = filter_on( instances, :id, opts )
+ instances
+ end
This has the same issue as the images call, and we should fix it up in
the same way. There's no way we can do 1 + 1000 calls to the backend
here.
+#--
+# 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..14) #name max 15 chars
+ 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)
+ safely do
+#######
+#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
+ end
The above is also an issue with reliability; again, this can be
committed as is, since it requires an API change to allow us to return
VM details as an option.
+ 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
The arch we report should either be 'i386' or 'x86_64'. Also,
shouldn't
the regex above be /(32|64)\.bit/ ?
+ arch ||= "n/a"
Just leave the architecture out if we don't get one from the backend.
+#--
+# 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_status == "0" || vapp_status == "1") ?
"PENDING" : (vapp_status == "2" ? "STOPPED" :
"RUNNING" )) #status == 0->BEING_CREATED 2->OFF 4->ON
This would be clearer if you used a hash, i.e. definea constant in the
class
VAPP_STATE_MAP = { "0" => "PENDING", "1" =>
"PENDING", "2" => "STOPPED", "4" =>
"RUNNING" }
and set 'current_state = VAPP_STATE_MAP[vapp_status]'. Might even be
worth a small helper method so that you can check for invalid/unexpected
states and raise an error.
+ 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/6...
+ :name => vapp.body['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 => InstanceProfile.new("default")
+ } )
+ 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}" }
+ safely do
+ terremark_client = Fog::Terremark::Vcloud.new(terremark_credentials)
+ vdc_id = terremark_client.default_vdc_id
+ if (vdc_id.nil?)
+ raise DeltaCloud::AuthException.new
+ end
+ return terremark_client
+ end
+ end
+
+ def safely(&block)
+ begin
+ block.call
+# rescue Excon::Errors.status_error => e #this is what you get from the fog
requests if :expects status code is off
+# raise Deltacloud::AuthException.new
+ rescue Exception => e
+ puts "ERROR: #{e.message}"
+ end
+ end
I know we have 'safely' all over the code base, but it has to die.
Rescuing any exception and then just printing is a gigantic bug. It
really, really needs to go.
Given the maturity of the code base, we should only ever catch
exceptions we know how to handle, and either raise another exception or
really address the exception, not print it. Just remove any use of
'safely' from the driver, and let stuff blow up in its full glory.
Handling 'weird' errors, i.e. errors we have no idea what to do about
belongs into server.rb; for example, for XML clients, we'd want to
return an apporpriate 500 response.
David