From: Sang-Min Park <[email protected]>
---
server/config/drivers.yaml | 4 +
.../drivers/eucalyptus/eucalyptus_driver.rb | 640 ++++++++++++++++++++
server/server.rb | 4 +-
3 files changed, 646 insertions(+), 2 deletions(-)
create mode 100644
server/lib/deltacloud/drivers/eucalyptus/eucalyptus_driver.rb
diff --git a/server/config/drivers.yaml b/server/config/drivers.yaml
index f28234c..e21ef47 100644
--- a/server/config/drivers.yaml
+++ b/server/config/drivers.yaml
@@ -28,6 +28,10 @@
:name: Rackspace
:azure:
:name: Azure
+:eucalyptus:
+ :name: Eucalyptus
+ :username: Access Key ID
+ :password: Secret Access Key
:ec2:
:entrypoints:
s3:
diff --git a/server/lib/deltacloud/drivers/eucalyptus/eucalyptus_driver.rb
b/server/lib/deltacloud/drivers/eucalyptus/eucalyptus_driver.rb
new file mode 100644
index 0000000..1b88bca
--- /dev/null
+++ b/server/lib/deltacloud/drivers/eucalyptus/eucalyptus_driver.rb
@@ -0,0 +1,640 @@
+# Copyright (C) 2009-2011 Eucalyptus Systems, Inc.
+#
+# Copyright (C) 2009, 2010 Red Hat, Inc.
+#
+# 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 'aws'
+
+class Instance
+ attr_accessor :keyname
+ attr_accessor :authn_error
+
+ def authn_feature_failed?
+ return true unless authn_error.nil?
+ end
+
+end
+
+module Deltacloud
+ module Drivers
+ module Eucalyptus
+ class EucalyptusDriver < Deltacloud::BaseDriver
+
+ def supported_collections
+ DEFAULT_COLLECTIONS + [ :keys, :buckets ]
+ end
+
+ feature :instances, :user_data
+ feature :instances, :authentication_key
+ feature :instances, :security_group
+ feature :instances, :instance_count
+ feature :images, :owner_id
+ feature :buckets, :bucket_location
+ #feature :instances, :register_to_load_balancer # not supported by
Eucalyptus 2.0.3
+
+ DEFAULT_REGION = 'us-east-1'
+
+ define_hardware_profile('m1.small') do
+ cpu 1
+ memory 128
+ storage 2
+ architecture 'x86_64'
+ end
+
+ define_hardware_profile('c1.medium') do
+ cpu 1
+ memory 256
+ storage 5
+ architecture 'x86_64'
+ end
+
+ define_hardware_profile('m1.large') do
+ cpu 2
+ memory 512
+ storage 10
+ architecture 'x86_64'
+ end
+
+ define_hardware_profile('m1.xlarge') do
+ cpu 2
+ memory 1024
+ storage 20
+ architecture 'x86_64'
+ end
+
+ define_hardware_profile('c1.xlarge') do
+ cpu 4
+ memory 2048
+ storage 20
+ architecture 'x86_64'
+ end
+
+ define_instance_states do
+ start.to( :pending ) .automatically
+ pending.to( :running ) .automatically
+ pending.to( :stopping ) .on( :stop )
+ pending.to( :stopped ) .automatically
+ stopped.to( :running ) .on( :start )
+ running.to( :running ) .on( :reboot )
+ running.to( :stopping ) .on( :stop )
+ shutting_down.to( :stopped ) .automatically
+ stopped.to( :finish ) .automatically
+ end
+
+ def images(credentials, opts={})
+ ec2 = new_client(credentials)
+ img_arr = []
+ opts ||= {}
+ if opts[:id]
+ safely do
+ img_arr = ec2.describe_images(opts[:id]).collect do |image|
+ convert_image(image)
+ end
+ end
+ return img_arr
+ end
+ owner_id = opts[:owner_id] || "self"
+ safely do
+ img_arr = ec2.describe_images_by_owner(owner_id).collect do |image|
+ convert_image(image)
+ end
+ end
+ #img_arr = filter_on( img_arr, :architecture, opts )
+ img_arr.sort_by { |e| [e.owner_id, e.name] }
+ end
+
+ def realms(credentials, opts={})
+ ec2 = new_client(credentials)
+ zone_id = opts ? opts[:id] : nil
+ safely do
+ return ec2.describe_availability_zones(zone_id).collect do |realm|
+ convert_realm(realm)
+ end
+ end
+ end
+
+ def instances(credentials, opts={})
+ ec2 = new_client(credentials)
+ inst_arr = []
+ safely do
+ inst_arr = ec2.describe_instances.collect do |instance|
+ convert_instance(instance) if instance
+ end.flatten
+ end
+ filter_on( inst_arr, :state, opts )
+ end
+
+ def create_instance(credentials, image_id, opts={})
+ ec2 = new_client(credentials)
+ instance_options = {}
+ instance_options.merge!(:user_data => opts[:user_data]) if
opts[:user_data]
+ instance_options.merge!(:key_name => opts[:keyname]) if
opts[:keyname]
+ instance_options.merge!(:availability_zone => opts[:realm_id]) if
opts[:realm_id]
+ instance_options.merge!(:instance_type => opts[:hwp_id]) if
opts[:hwp_id] && opts[:hwp_id].length > 0
+ instance_options.merge!(:group_ids => opts[:security_group]) if
opts[:security_group]
+ instance_options.merge!(
+ :min_count => opts[:instance_count],
+ :max_count => opts[:instance_count]
+ ) if opts[:instance_count] and opts[:instance_count].length!=0
+ safely do
+ new_instance = convert_instance(ec2.launch_instances(image_id,
instance_options).first)
+ # TODO: Rework this to use client_id for name instead of tag
+ # Tags should be keept as an optional feature
+ #if opts[:name]
+ # tag_instance(credentials, new_instance, opts[:name])
+ #end
+ # Register Instance to Load Balancer if load_balancer_id is set.
+ # This parameter is a feature parameter
+ #if opts[:load_balancer_id] and opts[:load_balancer_id].length>0
+ # elb = new_client(credentials, :elb)
+ #
elb.register_instances_with_load_balancer(opts[:load_balancer_id],
[new_instance.id])
+ #end
+ new_instance
+ end
+ end
+
+ def run_on_instance(credentials, opts={})
+ target = instance(credentials, :id => opts[:id])
+ param = {}
+ param[:credentials] = {
+ :username => 'root', # Default for EC2 Linux instances
+ }
+ param[:port] = opts[:port] || '22'
+ param[:ip] = target.public_addresses
+ param[:private_key] = (opts[:private_key].length > 1) ?
opts[:private_key] : nil
+ safely do
+ Deltacloud::Runner.execute(opts[:cmd], param)
+ end
+ end
+
+ def reboot_instance(credentials, instance_id)
+ ec2 = new_client(credentials)
+ if ec2.reboot_instances([instance_id])
+ instance(credentials, instance_id)
+ else
+ raise Deltacloud::BackendError.new(500, "Instance", "Instance
reboot failed", "")
+ end
+ end
+
+ def destroy_instance(credentials, instance_id)
+ ec2 = new_client(credentials)
+ instance_id = instance_id
+ if ec2.terminate_instances([instance_id])
+ #untag_instance(credentials, instance_id)
+ instance(credentials, instance_id)
+ else
+ raise Deltacloud::BackendError.new(500, "Instance", "Instance
cannot be terminated", "")
+ end
+ end
+
+ alias :stop_instance :destroy_instance
+
+ def keys(credentials, opts={})
+ ec2 = new_client(credentials)
+ opts ||= {}
+ safely do
+ ec2.describe_key_pairs(opts[:id] || nil).collect do |key|
+ convert_key(key)
+ end
+ end
+ end
+
+ def create_key(credentials, opts={})
+ ec2 = new_client(credentials)
+ safely do
+ convert_key(ec2.create_key_pair(opts[:key_name]))
+ end
+ end
+
+ def destroy_key(credentials, opts={})
+ ec2 = new_client(credentials)
+ original_key = key(credentials, opts)
+ safely do
+ ec2.delete_key_pair(original_key.id)
+ original_key= original_key.state = "DELETED"
+ end
+ original_key
+ end
+
+ def load_balancer(credentials, opts={})
+ raise Deltacloud::BackendError.new(500, "Loadbalancer",
"Loadbalancer not supported yet", "")
+ end
+
+ def load_balancers(credentials, opts=nil)
+ raise Deltacloud::BackendError.new(500, "Loadbalancer",
"Loadbalancer not supported yet", "")
+ end
+
+ def create_load_balancer(credentials, opts={})
+ raise Deltacloud::BackendError.new(500, "Loadbalancer",
"Loadbalancer not supported yet", "")
+ end
+
+ def destroy_load_balancer(credentials, id)
+ raise Deltacloud::BackendError.new(500, "Loadbalancer",
"Loadbalancer not supported yet", "")
+ end
+
+ def lb_register_instance(credentials, opts={})
+ raise Deltacloud::BackendError.new(500, "Loadbalancer",
"Loadbalancer not supported yet", "")
+ end
+
+ def lb_unregister_instance(credentials, opts={})
+ raise Deltacloud::BackendError.new(500, "Loadbalancer",
"Loadbalancer not supported yet", "")
+ end
+
+
+ def buckets(credentials, opts={})
+ buckets = []
+ safely do
+ s3_client = new_client(credentials, :s3)
+ bucket_list = s3_client.buckets
+
+ bucket_list.each do |current|
+ buckets << convert_bucket(current)
+ end
+ end
+ buckets
+ end
+
+ def create_bucket(credentials, name, opts={})
+ bucket = nil
+ safely do
+ s3_client = new_client(credentials, :s3)
+ bucket_location = opts['location']
+ if bucket_location
+ bucket = Aws::S3::Bucket.create(s3_client, name, true, nil,
:location => bucket_location)
+ else
+ bucket = Aws::S3::Bucket.create(s3_client, name, true)
+ end
+ end
+ convert_bucket(bucket)
+ end
+
+ def delete_bucket(credentials, name, opts={})
+ s3_client = new_client(credentials, :s3)
+ safely do
+ s3_client.interface.delete_bucket(name)
+ end
+ end
+
+ def blobs(credentials, opts = {})
+ s3_client = new_client(credentials, :s3)
+ blobs = []
+ safely do
+ s3_bucket = s3_client.bucket(opts['bucket'])
+ s3_bucket.keys({}, true).each do |s3_object|
+ blobs << convert_object(s3_object)
+ end
+ end
+ blobs = filter_on(blobs, :id, opts)
+ blobs
+ end
+
+ #--
+ # Create Blob
+ #--
+ def create_blob(credentials, bucket_id, blob_id, data = nil, opts = {})
+ s3_client = new_client(credentials, :s3)
+ #data is a construct with the temporary file created by server
@.tempfile
+ #also file[:type] will give us the content-type
+ res = nil
+ # File stream needs to be reopened in binary mode for whatever reason
+ file = File::open(data[:tempfile].path, 'rb')
+ #insert ec2-specific header for user metadata ... x-amz-meta-KEY =
VALUE
+ opts.gsub_keys('HTTP_X_Deltacloud_Blobmeta_', 'x-amz-meta-')
+ opts["Content-Type"] = data[:type]
+ safely do
+ res = s3_client.interface.put(bucket_id,
+ blob_id,
+ file,
+ opts)
+ end
+ #create a new Blob object and return that
+ Blob.new( { :id => blob_id,
+ :bucket => bucket_id,
+ :content_length => data[:tempfile].length,
+ :content_type => data[:type],
+ :last_modified => '',
+ :user_metadata => opts.select{|k,v|
k.match(/^x-amz-meta-/i)}
+ }
+ )
+ end
+
+ #--
+ # Delete Blob
+ #--
+ def delete_blob(credentials, bucket_id, blob_id, opts={})
+ s3_client = new_client(credentials, :s3)
+ safely do
+ s3_client.interface.delete(bucket_id, blob_id)
+ end
+ end
+
+
+ def blob_data(credentials, bucket_id, blob_id, opts={})
+ s3_client = new_client(credentials, :s3)
+ safely do
+ s3_client.interface.get(bucket_id, blob_id) do |chunk|
+ yield chunk
+ end
+ end
+ end
+
+ def storage_volumes(credentials, opts={})
+ ec2 = new_client( credentials )
+ volume_list = (opts and opts[:id]) ? opts[:id] : nil
+ safely do
+ ec2.describe_volumes(volume_list).collect do |volume|
+ convert_volume(volume)
+ end
+ end
+ end
+
+ def create_storage_volume(credentials, opts=nil)
+ ec2 = new_client(credentials)
+ opts ||= {}
+ opts[:snapshot_id] ||= ""# -> this causes signature validation error
with Euca backend
+ opts[:capacity] ||= "1"
+ opts[:realm_id] ||= realms(credentials).first.id
+ safely do
+ convert_volume(ec2.create_volume(opts[:snapshot_id],
opts[:capacity], opts[:realm_id]))
+
+ end
+ end
+
+ def destroy_storage_volume(credentials, opts={})
+ ec2 = new_client(credentials)
+ safely do
+ unless ec2.delete_volume(opts[:id])
+ raise Deltacloud::BackendError.new(500, "StorageVolume", "Cannot
delete storage volume")
+ end
+ storage_volume(credentials, opts[:id])
+ end
+ end
+
+ def attach_storage_volume(credentials, opts={})
+ ec2 = new_client(credentials)
+ safely do
+ convert_volume(ec2.attach_volume(opts[:id], opts[:instance_id],
opts[:device]))
+ end
+ end
+
+ def detach_storage_volume(credentials, opts={})
+ ec2 = new_client(credentials)
+ safely do
+ convert_volume(ec2.detach_volume(opts[:id], opts[:instance_id],
opts[:device], true))
+ end
+ end
+
+ def storage_snapshots(credentials, opts={})
+ ec2 = new_client(credentials)
+ snapshot_list = (opts and opts[:id]) ? opts[:id] : []
+ safely do
+ ec2.describe_snapshots(snapshot_list).collect do |snapshot|
+ convert_snapshot(snapshot)
+ end
+ end
+ end
+
+ def create_storage_snapshot(credentials, opts={})
+ ec2 = new_client(credentials)
+ safely do
+ convert_snapshot(ec2.try_create_snapshot(opts[:volume_id]))
+ end
+ end
+
+ def destroy_storage_snapshot(credentials, opts={})
+ ec2 = new_client(credentials)
+ safely do
+ unless ec2.delete_snapshot(opts[:id])
+ raise Deltacloud::BackendError.new(500, "StorageSnapshot",
"Cannot destroy this snapshot")
+ end
+ end
+ end
+
+ def valid_credentials?(credentials)
+ retval = true
+ begin
+ realms(credentials)
+ rescue Deltacloud::BackendError
+ retval = false
+ end
+ retval
+ end
+
+ private
+
+ def new_client(credentials, type = :ec2)
+ klass = case type
+ when :elb then Aws::Elb
+ when :ec2 then Aws::Ec2
+ when :s3 then Aws::S3
+ end
+ klass.new(credentials.user, credentials.password,
eucalyptus_endpoint)
+#:server => endpoint_for_service(type))
+ end
+
+ def eucalyptus_endpoint
+ endpoint = (Thread.current[:provider] || ENV['API_PROVIDER'])
+ #parse endpoint string into server, port, service, and protocol
+ if endpoint
+ {:server => URI.parse(endpoint).host, :port =>
URI.parse(endpoint).port, :service => URI.parse(endpoint).path, :protocol =>
URI.parse(endpoint).scheme}
+ else
+ { } #EC2_URL env variable will be used by AWS
+ end
+ end
+
+ # shouldn't be called now; eucalyptus doens't support tagging yet
+ def tag_instance(credentials, instance, name)
+ ec2 = new_client(credentials)
+ safely do
+ ec2.create_tag(instance.id, 'name', name)
+ end
+ end
+
+ # shouldn't be called now; eucalyptus doens't support tagging yet
+ def untag_instance(credentials, instance_id)
+ ec2 = new_client(credentials)
+ safely do
+ ec2.delete_tag(instance_id, 'name')
+ end
+ end
+
+ # shouldn't be called now; eucalyptus doens't support tagging yet
+ def delete_unused_tags(credentials, inst_ids)
+ ec2 = new_client(credentials)
+ tags = []
+ safely do
+ tags = ec2.describe_tags('Filter.1.Name' => 'resource-type',
'Filter.1.Value' => 'instance')
+ tags.collect! { |t| t[:aws_resource_id] }
+ inst_ids.each do |inst_id|
+ unless tags.include?(inst_id)
+ ec2.delete_tag(inst_id, 'name')
+ end
+ end
+ end
+ end
+
+ def convert_bucket(s3_bucket)
+ #get blob list:
+ blob_list = []
+ s3_bucket.keys.each do |s3_object|
+ blob_list << s3_object.name
+ end
+
+ #can use AWS::S3::Owner.current.display_name or current.id
+ Bucket.new(
+ :id => s3_bucket.name,
+ :name => s3_bucket.name,
+ :size => s3_bucket.keys.length,
+ :blob_list => blob_list
+ )
+ end
+
+ def convert_object(s3_object)
+ Blob.new(
+ :id => s3_object.name,
+ :bucket => s3_object.bucket.name.to_s,
+ :content_length => s3_object.headers['content-length'],
+ :content_type => s3_object.headers['content-type'],
+ :last_modified => s3_object.last_modified,
+ :user_metadata => s3_object.meta_headers
+ )
+ end
+
+ def convert_realm(realm)
+ Realm.new(
+ :id => realm[:zone_name],
+ :name => realm[:zone_name],
+ :state => realm[:zone_state],
+ :limit => realm[:zone_state].eql?('available') ? :unlimited : 0
+ )
+ end
+
+ def convert_image(image)
+ # There is not support for 'name' for now
+ Image.new(
+ :id => image[:aws_id],
+ :name => image[:aws_name] || image[:aws_id],
+ :description => image[:aws_description] || image[:aws_location],
+ :owner_id => image[:aws_owner],
+ :architecture => image[:aws_architecture],
+ :state => image[:state]
+ )
+ end
+
+ def convert_instance(instance)
+ Instance.new(
+ :id => instance[:aws_instance_id],
+ :name => instance[:aws_image_id],
+ :state => convert_state(instance[:aws_state]),
+ :image_id => instance[:aws_image_id],
+ :owner_id => instance[:aws_owner],
+ :actions =>
instance_actions_for(convert_state(instance[:aws_state])),
+ :key_name => instance[:ssh_key_name],
+ :launch_time => instance[:aws_launch_time],
+ :instance_profile =>
InstanceProfile.new(instance[:aws_instance_type]),
+ :realm_id => instance[:aws_availability_zone],
+ :private_addresses => instance[:private_dns_name],
+ :public_addresses => instance[:dns_name]
+ )
+ end
+
+ def convert_key(key)
+ Key.new(
+ :id => key[:aws_key_name],
+ :fingerprint => key[:aws_fingerprint],
+ :credential_type => :key,
+ :pem_rsa_key => key[:aws_material],
+ :state => "AVAILABLE"
+ )
+ end
+
+ def convert_volume(volume)
+ StorageVolume.new(
+ :id => volume[:aws_id],
+ :created => volume[:aws_created_at],
+ :state => volume[:aws_status] ? volume[:aws_status].upcase :
'unknown',
+ :capacity => volume[:aws_size],
+ :instance_id => volume[:aws_instance_id],
+ :realm_id => volume[:zone],
+ :device => volume[:aws_device],
+ # TODO: the available actions should be tied to the current
+ # volume state
+ :actions => [:attach, :detach, :destroy]
+ )
+ end
+
+ def convert_snapshot(snapshot)
+ StorageSnapshot.new(
+ :id => snapshot[:aws_id],
+ :state => snapshot[:aws_status],
+ :storage_volume_id => snapshot[:aws_volume_id],
+ :created => snapshot[:aws_started_at]
+ )
+ end
+
+ def convert_load_balancer(credentials, loadbalancer)
+ puts loadbalancer.inspect
+ realms = []
+ balancer_realms = loadbalancer[:availability_zones].each do |zone|
+ realms << realm(credentials, zone)
+ end
+ balancer = LoadBalancer.new({
+ :id => loadbalancer[:name],
+ :created_at => loadbalancer[:created_time],
+ :public_addresses => [loadbalancer[:dns_name]],
+ :realms => realms
+ })
+ balancer.listeners = []
+ balancer.instances = []
+ loadbalancer[:listeners].each do |listener|
+ balancer.add_listener(listener)
+ end
+ loadbalancer[:instances].each do |instance|
+ balancer.instances << instance(credentials, :id => instance[:id])
+ end
+ balancer
+ end
+
+ def convert_state(ec2_state)
+ case ec2_state
+ when "terminated"
+ "STOPPED"
+ when "stopped"
+ "STOPPED"
+ when "running"
+ "RUNNING"
+ when "pending"
+ "PENDING"
+ when "shutting-down"
+ "STOPPED"
+ end
+ end
+
+ def catched_exceptions_list
+ {
+ :auth => [], # [ ::Aws::AuthFailure ],
+ :error => [ ::Aws::AwsError ],
+ :glob => [ /AWS::(\w+)/, /Deltacloud::Runner::(\w+)/ ]
+ }
+ end
+
+ end
+ end
+ end
+end
diff --git a/server/server.rb b/server/server.rb
index fe723c5..ff7c988 100644
--- a/server/server.rb
+++ b/server/server.rb
@@ -478,8 +478,8 @@ collection :storage_snapshots do
with_capability :destroy_storage_snapshot
param :id, :string, :required
control do
- driver.create_storage_snapshot(credentials, params)
- redirect(storage_snapshot_url(params[:id]))
+ driver.destroy_storage_snapshot(credentials, params)
+ redirect(storage_snapshots_url)
end
end
--
1.7.1