You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by ma...@redhat.com on 2011/01/21 20:34:25 UTC

[PATCH] Adds ability to specify arbitrary user metadata (KEY-VALUE) for S3, Cloudfiles and Azure, during blob creation.

From: marios <ma...@redhat.com>

---
 server/lib/deltacloud/base_driver/base_driver.rb   |   13 +++++++
 .../lib/deltacloud/drivers/azure/azure_driver.rb   |   17 +++++++---
 server/lib/deltacloud/drivers/ec2/ec2_driver.rb    |   13 +++++--
 .../drivers/rackspace/rackspace_driver.rb          |   23 +++++++++----
 server/lib/deltacloud/models/blob.rb               |    1 +
 server/public/javascripts/application.js           |   35 ++++++++++++++++++++
 server/server.rb                                   |   10 +++++-
 server/views/blobs/new.html.haml                   |   18 +++++++++-
 server/views/blobs/show.html.haml                  |    3 ++
 server/views/blobs/show.xml.haml                   |    4 ++-
 10 files changed, 118 insertions(+), 19 deletions(-)

diff --git a/server/lib/deltacloud/base_driver/base_driver.rb b/server/lib/deltacloud/base_driver/base_driver.rb
index bf06e56..5bae2f1 100644
--- a/server/lib/deltacloud/base_driver/base_driver.rb
+++ b/server/lib/deltacloud/base_driver/base_driver.rb
@@ -252,6 +252,19 @@ module Deltacloud
       end
     end
 
+    #used by blobstore - server.rb prefixes all user metadata keys with "META-"
+    #but each backend blobstore defines their own meta header - e.g. amazon is "x-amz-meta-"
+    def replace_hash_keys(hash, pattern)
+      hash.each do |k, v|
+        if k.match(/^META-/)
+          new_k = k.gsub(/^META-/, pattern)
+          hash[new_k] = v
+          hash.delete(k)
+        end #if
+      end#opts.each
+      return hash
+    end
+
   end
 
 end
diff --git a/server/lib/deltacloud/drivers/azure/azure_driver.rb b/server/lib/deltacloud/drivers/azure/azure_driver.rb
index abbd353..20c6295 100644
--- a/server/lib/deltacloud/drivers/azure/azure_driver.rb
+++ b/server/lib/deltacloud/drivers/azure/azure_driver.rb
@@ -94,16 +94,22 @@ class AzureDriver < Deltacloud::BaseDriver
 #--
 # Create Blob
 #--
+require 'ruby-debug'
   def create_blob(credentials, bucket_id, blob_id, blob_data, opts=nil)
     azure_connect(credentials)
-    #get a handle to the bucket in order to put there
-    the_bucket = WAZ::Blobs::Container.find(bucket_id)
-    the_bucket.store(blob_id, blob_data[:tempfile], blob_data[:type])
+    #insert azure-specific header for user metadata ... x-ms-meta-kEY = VALUE
+    opts = replace_hash_keys(opts, "x-ms-meta-")
+    safely do
+      #get a handle to the bucket in order to put there
+      the_bucket = WAZ::Blobs::Container.find(bucket_id)
+      the_bucket.store(blob_id, blob_data[:tempfile], blob_data[:type], opts)
+    end
     Blob.new( { :id => blob_id,
                 :bucket => bucket_id,
                 :content_lengh => blob_data[:tempfile].length,
                 :content_type => blob_data[:type],
-                :last_modified => ''
+                :last_modified => '',
+                :user_metadata => opts.select{|k,v| k.match(/^x-ms-meta-/)}
             } )
   end
 
@@ -146,7 +152,8 @@ class AzureDriver < Deltacloud::BaseDriver
                  :bucket => bucket,
                  :content_length => waz_blob.metadata[:content_length],
                  :content_type => waz_blob.metadata[:content_type],
-                 :last_modified => waz_blob.metadata[:last_modified]
+                 :last_modified => waz_blob.metadata[:last_modified],
+                 :user_metadata => waz_blob.metadata.select{|k,v| k.to_s.match(/^x_ms_meta_/)}
               })
   end
 
diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
index 7a4b394..db0e1a1 100644
--- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
+++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
@@ -361,18 +361,22 @@ module Deltacloud
           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 = replace_hash_keys(opts, "x-amz-meta-")
+          opts["Content-Type"] = data[:type]
           safely do
             res = s3_client.interface.put(bucket_id, 
                                         blob_id, 
                                         file, 
-                                        {"Content-Type" => data[:type]})
+                                        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 => ''
+                      :last_modified => '',
+                      :user_metadata => opts.select{|k,v| k.match(/^x-amz-meta-/)}
                     }
                   )
         end
@@ -503,7 +507,7 @@ module Deltacloud
             'us-east-1' => 'elasticloadbalancing.us-east-1.amazonaws.com',
             'us-west-1' => 'elasticloadbalancing.us-west-1.amazonaws.com'
           },
-          
+
           's3' => {
             'ap-southeast-1' => 's3.amazonaws.com',
             'eu-west-1' => 's3.amazonaws.com',
@@ -568,7 +572,8 @@ module Deltacloud
             :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
+            :last_modified => s3_object.last_modified,
+            :user_metadata => s3_object.meta_headers
           )  
         end
 
diff --git a/server/lib/deltacloud/drivers/rackspace/rackspace_driver.rb b/server/lib/deltacloud/drivers/rackspace/rackspace_driver.rb
index e40fb9c..9a45d4f 100644
--- a/server/lib/deltacloud/drivers/rackspace/rackspace_driver.rb
+++ b/server/lib/deltacloud/drivers/rackspace/rackspace_driver.rb
@@ -233,16 +233,24 @@ class RackspaceDriver < Deltacloud::BaseDriver
 #--
   def create_blob(credentials, bucket_id, blob_id, blob_data, opts=nil)
     cf = cloudfiles_client(credentials)
-    #must first create the object using cloudfiles_client.create_object
-    #then can write using object.write(data)
-    object = cf.container(bucket_id).create_object(blob_id)
-    #blob_data is a construct with data in .tempfile and content-type in {:type}
-    res = object.write(blob_data[:tempfile], {'Content-Type' => blob_data[:type]})
+    user_meta = opts.select{|k,v| k.match(/^META-/)}
+    #insert ec2-specific header for user metadata ... X-Object-Meta-KEY = VALUE
+    opts = replace_hash_keys(opts, "X-Object-Meta-")
+    opts['Content-Type'] = blob_data[:type]
+    object = nil
+    safely do
+      #must first create the object using cloudfiles_client.create_object
+      #then can write using object.write(data)
+      object = cf.container(bucket_id).create_object(blob_id)
+      #blob_data is a construct with data in .tempfile and content-type in {:type}
+      res = object.write(blob_data[:tempfile], opts)
+    end
     Blob.new( { :id => object.name,
                 :bucket => object.container.name,
                 :content_length => blob_data[:tempfile].length,
                 :content_type => blob_data[:type],
-                :last_modified => ''
+                :last_modified => '',
+                :user_metadata => opts.select{|k,v| k.match(/^X-Object-Meta-/)}
               }
             )
   end
@@ -292,7 +300,8 @@ private
                  :bucket => cf_object.container.name,
                  :content_length => cf_object.bytes,
                  :content_type => cf_object.content_type,
-                 :last_modified => cf_object.last_modified
+                 :last_modified => cf_object.last_modified,
+                 :user_metadata => cf_object.metadata
               })
   end
 
diff --git a/server/lib/deltacloud/models/blob.rb b/server/lib/deltacloud/models/blob.rb
index dfa67fe..0bf473b 100644
--- a/server/lib/deltacloud/models/blob.rb
+++ b/server/lib/deltacloud/models/blob.rb
@@ -24,5 +24,6 @@ class Blob < BaseModel
   attr_accessor :content_type
   attr_accessor :last_modified
   attr_accessor :content
+  attr_accessor :user_metadata
 
 end
diff --git a/server/public/javascripts/application.js b/server/public/javascripts/application.js
index 5ee0f7f..95c9bc2 100644
--- a/server/public/javascripts/application.js
+++ b/server/public/javascripts/application.js
@@ -16,3 +16,38 @@ $(document).ready(function() {
   }
 
 })
+
+function more_fields()
+{
+	//increment the hidden input that captures how many meta_data are passed
+	var meta_params = document.getElementsByName('meta_params')
+    current_number_params = eval(meta_params[0].value)+1
+	meta_params[0].value = current_number_params
+    var new_meta = document.getElementById('metadata_holder').cloneNode(true);
+    new_meta.id = 'metadata_holder' + current_number_params;
+    new_meta.style.display = 'block';
+    var nodes = new_meta.childNodes;
+    for (var i=0;i < nodes.length;i++) {
+        var theName = nodes[i].name;
+        if (theName)
+          nodes[i].name = theName + current_number_params;
+    }
+    var insertHere = document.getElementById('metadata_holder');
+    insertHere.parentNode.insertBefore(new_meta,insertHere);
+}
+
+function less_fields()
+{
+    var meta_params = document.getElementsByName('meta_params')
+	current_val = eval(meta_params[0].value)
+	if (current_val == 0)
+	{
+		return;
+	}
+	else
+	{
+		var theDiv = document.getElementById('metadata_holder'+current_val)
+		theDiv.parentNode.removeChild(theDiv)
+		meta_params[0].value = eval(current_val)-1
+	}
+}
diff --git a/server/server.rb b/server/server.rb
index 8c3b72c..418a497 100644
--- a/server/server.rb
+++ b/server/server.rb
@@ -589,7 +589,15 @@ post '/api/buckets/:bucket' do
   bucket_id = params[:bucket]
   blob_id = params['blob_id']
   blob_data = params['blob_data']
-  @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data )
+  max = params[:meta_params].to_i
+  user_meta = {}
+  (1..max).each do |i|
+    key = params[:"meta_name#{i}"]
+    key = "META-#{key}"
+    value = params[:"meta_value#{i}"]
+    user_meta[key] = value
+  end
+  @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta)
   respond_to do |format|
     format.html { haml :"blobs/show"}
     format.xml { haml :"blobs/show" }
diff --git a/server/views/blobs/new.html.haml b/server/views/blobs/new.html.haml
index feb94e5..a9817d5 100644
--- a/server/views/blobs/new.html.haml
+++ b/server/views/blobs/new.html.haml
@@ -4,7 +4,23 @@
   %label
     Blob Name:
     %input{ :name => 'blob_id', :size => 512}/
+  %label
     Blob Data:
-    %input{ :type => "file", :name => 'blob_data',  :size => 50}/
     %br
+    %input{ :type => "file", :name => 'blob_data', :size => 50}/
+    %br
+    %br
+  %input{ :type => "hidden", :name => "meta_params", :value => "0"}
+  %a{ :href => "javascript:;", :onclick => "more_fields();"} Add Metadata
+  %div{ :id => "metadata_holder", :style => "display: none;"}
+    %label
+      Metadata Name:
+    %input{ :type => "text", :name => "meta_name", :value => ""}/
+    %label
+      Metadata Value:
+    %input{ :type => "text", :name => "meta_value", :value => ""}/
+  %br
+  %a{ :href => "javascript:;", :onclick => "less_fields();"} Less Metadata
+  %br
+  %br
   %input{ :type => :submit, :name => "commit", :value => "create"}/
diff --git a/server/views/blobs/show.html.haml b/server/views/blobs/show.html.haml
index e5c2cee..29025c0 100644
--- a/server/views/blobs/show.html.haml
+++ b/server/views/blobs/show.html.haml
@@ -19,6 +19,9 @@
     %dt Content
     %dd
       =link_to 'Blob content', bucket_url(@blob.bucket) + '/' + @blob.id + '/content'
+    %dt User_Metadata
+    %dd
+      = @blob.user_metadata.inspect
     %dt Delete this Blob
     %dd
       %form{ :action => bucket_url(@blob.bucket) + '/' + @blob.id , :method => :post }
diff --git a/server/views/blobs/show.xml.haml b/server/views/blobs/show.xml.haml
index bc499ca..1b9ea55 100644
--- a/server/views/blobs/show.xml.haml
+++ b/server/views/blobs/show.xml.haml
@@ -1,7 +1,9 @@
 !!! XML
 %blob{:href => bucket_url(@blob.bucket) + '/' + @blob.id, :id => @blob.id}
-  - @blob.attributes.select{ |attr| attr!=:id}.each do |attribute|
+  - @blob.attributes.select{ |attr| (attr!=:id && attr!=:user_metadata) }.each do |attribute|
     - unless attribute == :content
       - haml_tag(attribute, :<) do
         - haml_concat @blob.send(attribute)
+  - @blob.user_metadata.each do |k, v|
+    %user_metadata{:name => k, :value => v}
   %content{:href => bucket_url(@blob.bucket) + '/' + @blob.id + '/content'}
-- 
1.7.3.4


Re: [PATCH] Adds ability to specify arbitrary user metadata (KEY-VALUE) for S3, Cloudfiles and Azure, during blob creation.

Posted by "marios@redhat.com" <ma...@redhat.com>.
On 22/01/11 02:14, David Lutterkort wrote:
> Some comments below:
>> +    def replace_hash_keys(hash, pattern)
>
> Nit pick: what you call 'pattern' is really the replacement.
>
> The Ruby way to do something like this is to define it directly in the
> Hash class, i.e. doing something like
>
>          class Hash
>             def replace_keys(pattern)
>               ...
>             end
>          end
>
> though it might be clearer to have a method Hash.gsub_keys(pattern,
> replacement)
>
cool :) thanks for your comments, I'll rework the patch and send out 
again (including the x-deltacloud-meta headers tweak for passing in the 
metadata values - i'm thinking we can have both mechanisms [params and 
headers] i.e. just add the checking for x-deltacloud-meta headers to the 
code.. i'll see how this works in practice),

marios



>>     end
>>
>>   end
>> diff --git a/server/lib/deltacloud/drivers/azure/azure_driver.rb b/server/lib/deltacloud/drivers/azure/azure_driver.rb
>> index abbd353..20c6295 100644
>> --- a/server/lib/deltacloud/drivers/azure/azure_driver.rb
>> +++ b/server/lib/deltacloud/drivers/azure/azure_driver.rb
>> @@ -94,16 +94,22 @@ class AzureDriver<  Deltacloud::BaseDriver
>>   #--
>>   # Create Blob
>>   #--
>> +require 'ruby-debug'
>>     def create_blob(credentials, bucket_id, blob_id, blob_data, opts=nil)
>
> Default value for opts should be {}, so that replace_keys doesn't barf
> on a nil hash
>
> Other than that, and the issue of how to pass in metadata k/v pairs,
> looks good.
>
> David
>
>


Re: [PATCH] Adds ability to specify arbitrary user metadata (KEY-VALUE) for S3, Cloudfiles and Azure, during blob creation.

Posted by David Lutterkort <lu...@redhat.com>.
Some comments below:

On Fri, 2011-01-21 at 21:34 +0200, marios@redhat.com wrote:
> diff --git a/server/lib/deltacloud/base_driver/base_driver.rb b/server/lib/deltacloud/base_driver/base_driver.rb
> index bf06e56..5bae2f1 100644
> --- a/server/lib/deltacloud/base_driver/base_driver.rb
> +++ b/server/lib/deltacloud/base_driver/base_driver.rb
> @@ -252,6 +252,19 @@ module Deltacloud
>        end
>      end
>  
> +    #used by blobstore - server.rb prefixes all user metadata keys with "META-"
> +    #but each backend blobstore defines their own meta header - e.g. amazon is "x-amz-meta-"
> +    def replace_hash_keys(hash, pattern)

Nit pick: what you call 'pattern' is really the replacement.

> +      hash.each do |k, v|
> +        if k.match(/^META-/)
> +          new_k = k.gsub(/^META-/, pattern)
> +          hash[new_k] = v
> +          hash.delete(k)

Is it really safe in Ruby to modify a hash while iterating over it ?

> +        end #if
> +      end#opts.each
> +      return hash
> +    end
> +

The Ruby way to do something like this is to define it directly in the
Hash class, i.e. doing something like

        class Hash
           def replace_keys(pattern)
             ...
           end
        end

though it might be clearer to have a method Hash.gsub_keys(pattern,
replacement)

>    end
>  
>  end
> diff --git a/server/lib/deltacloud/drivers/azure/azure_driver.rb b/server/lib/deltacloud/drivers/azure/azure_driver.rb
> index abbd353..20c6295 100644
> --- a/server/lib/deltacloud/drivers/azure/azure_driver.rb
> +++ b/server/lib/deltacloud/drivers/azure/azure_driver.rb
> @@ -94,16 +94,22 @@ class AzureDriver < Deltacloud::BaseDriver
>  #--
>  # Create Blob
>  #--
> +require 'ruby-debug'
>    def create_blob(credentials, bucket_id, blob_id, blob_data, opts=nil)

Default value for opts should be {}, so that replace_keys doesn't barf
on a nil hash

Other than that, and the issue of how to pass in metadata k/v pairs,
looks good.

David