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@redhat.com wrote:
From: marios marios@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/60/t/376.aspx
: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