You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by mf...@redhat.com on 2011/08/12 14:42:55 UTC

[PATCH core] Rabbit: Added initial support for sub-collections

From: Michal Fojtik <mf...@redhat.com>


Signed-off-by: Michal fojtik <mf...@redhat.com>
---
 server/lib/sinatra/rabbit.rb |   93 ++++++++++++++-
 server/server.rb             |  263 ++++++++++++++++++++++-------------------
 2 files changed, 228 insertions(+), 128 deletions(-)

diff --git a/server/lib/sinatra/rabbit.rb b/server/lib/sinatra/rabbit.rb
index 70691d5..fc7ed00 100644
--- a/server/lib/sinatra/rabbit.rb
+++ b/server/lib/sinatra/rabbit.rb
@@ -56,7 +56,8 @@ module Sinatra
       def initialize(coll, name, opts, &block)
         @name = name.to_sym
         opts = STANDARD[@name].merge(opts) if standard?
-        @collection = coll
+        @path_generator = opts[:path_generator]
+        @collection, @standard = coll, opts[:standard]
         raise "No method for operation #{name}" unless opts[:method]
         @method = opts[:method].to_sym
         @member = opts[:member]
@@ -71,7 +72,7 @@ module Sinatra
       end
 
       def standard?
-        STANDARD.keys.include?(name)
+        STANDARD.keys.include?(name) || @standard
       end
 
       def form?
@@ -120,8 +121,17 @@ module Sinatra
         end
       end
 
+      def member?
+        if standard?
+          @member || STANDARD[name][:member]
+        else
+          @member
+        end
+      end
+
       def path(args = {})
-        if @member
+        return @path_generator.call(self) if @path_generator
+        if member?
           if standard?
             "#{@collection.name}/:id"
           else
@@ -137,6 +147,11 @@ module Sinatra
       end
 
       def generate
+        # NOTE: To debug all generated routes uncomment this line.
+        # It will print out all defined routes by Rabbit to the console
+        #
+        # puts "#{@method.to_s.upcase}(#{name}): #{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{path}"
+
         ::Sinatra::Application.send(@method, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{path}", {}, &@control)
         # Set up some Rails-like URL helpers
         if name == :index
@@ -184,12 +199,12 @@ module Sinatra
     end
 
     class Collection
-      attr_reader :name, :operations
+      attr_reader :name, :operations, :subcollections
 
       def initialize(name, &block)
         @name = name
         @description = ""
-        @operations = {}
+        @operations, @subcollections = {}, {}
         @global = false
         instance_eval(&block) if block_given?
         generate_documentation
@@ -197,6 +212,10 @@ module Sinatra
         generate_options
       end
 
+      def subcollection?
+        self.class == SubCollection
+      end
+
       # Set/Return description for collection
       # If first parameter is not present, full description will be
       # returned.
@@ -271,13 +290,21 @@ module Sinatra
         @operations[name] = Operation.new(self, name, opts, &block)
       end
 
+      def subcollection(name, opts={}, &block)
+        if subcollections.keys.include?(name)
+          raise DuplicateOperationException::new(500, "DuplicateSubcollection", "Subcollection #{name} is already defined", [])
+        end
+        subcollections[name] = SubCollection.new(self, name, opts, &block)
+        subcollections[name].generate
+      end
+
       def generate
         operations.values.reject { |op| op.member }.each { |o| o.generate }
         operations.values.select { |op| op.member }.each { |o| o.generate }
         app = ::Sinatra::Application
         collname = name # Work around Ruby's weird scoping/capture
         app.send(:define_method, "#{name.to_s.singularize}_url") do |id|
-            api_url_for "#{collname}/#{id}", :full
+          api_url_for "#{collname}/#{id}", :full
         end
         if index_op = operations[:index]
           app.send(:define_method, "#{name}_url") do
@@ -293,6 +320,60 @@ module Sinatra
       end
     end
 
+    class SubCollection < Collection
+
+      attr_accessor :parent
+
+      def initialize(parent, name, opts={}, &block)
+        self.parent = parent
+        super(name, &block)
+      end
+
+      def operation(name, opts = {}, &block)
+        if @operations.keys.include?(name)
+          raise DuplicateOperationException::new(500, "DuplicateOperation", "Operation #{name} is already defined", [])
+        end
+        # Preserve self as local variable to workaround Ruby namespace
+        # weirdness
+        c = self
+        path_generator = Proc.new do |obj|
+          if obj.member?
+            if obj.standard?
+              "#{parent.name}/:#{parent.name.to_s.singularize}/:#{c.name.to_s.singularize}"
+            else
+              "#{parent.name}/:#{parent.name.to_s.singularize}/:#{c.name.to_s.singularize}/#{name}"
+            end
+          else
+            if obj.form?
+              "#{parent.name}/:id/:#{parent.name.to_s.singularize}/#{obj.name}"
+            else
+              "#{parent.name}/:#{parent.name.to_s.singularize}"
+            end
+          end
+        end
+        opts.merge!({
+          :path_generator => path_generator
+        })
+        @operations[name] = Operation.new(self, name, opts, &block)
+      end
+
+      def generate
+        operations.values.reject { |op| op.member }.each { |o| o.generate }
+        operations.values.select { |op| op.member }.each { |o| o.generate }
+        app = ::Sinatra::Application
+        collname = name # Work around Ruby's weird scoping/capture
+        app.send(:define_method, "#{parent.name.to_s}_#{name.to_s.singularize}_url") do |id, subid|
+          api_url_for "#{collname}/#{id}/#{subid}", :full
+        end
+        if index_op = operations[:index]
+          app.send(:define_method, "#{parent.name.to_s}_#{name}_url") do
+            api_url_for index_op.path.gsub(/\/\?$/,''), :full
+          end
+        end
+      end
+
+    end
+
     def collections
       @collections ||= {}
     end
diff --git a/server/server.rb b/server/server.rb
index 51802e1..641127e 100644
--- a/server/server.rb
+++ b/server/server.rb
@@ -743,140 +743,159 @@ collection :keys do
 
 end
 
-#create a new blob using PUT - streams through deltacloud
-put "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do
-  if(env["BLOB_SUCCESS"]) #ie got a 200ok after putting blob
-    content_type = env["CONTENT_TYPE"]
-    content_type ||=  ""
-    @blob = driver.blob(credentials, {:id => params[:blob],
-                                      'bucket' => params[:bucket]})
-    respond_to do |format|
-      format.xml { haml :"blobs/show" }
-      format.html { haml :"blobs/show" }
-      format.json { convert_to_json(:blob, @blob) }
-    end
-  elsif(env["BLOB_FAIL"])
-    report_error(500) #OK?
-  else # small blobs - < 112kb dont hit the streaming monkey patch - use 'normal' create_blob
-       # also, if running under webrick don't hit the streaming patch (Thin specific)
-    bucket_id = params[:bucket]
-    blob_id = params[:blob]
-    temp_file = Tempfile.new("temp_blob_file")
-    temp_file.write(env['rack.input'].read)
-    temp_file.flush
-    content_type = env['CONTENT_TYPE'] || ""
-    blob_data = {:tempfile => temp_file, :type => content_type}
-    user_meta = BlobHelper::extract_blob_metadata_hash(request.env)
-    @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta)
-    temp_file.delete
-    respond_to do |format|
-      format.xml { haml :"blobs/show" }
-      format.html { haml :"blobs/show"}
-    end
-  end
-end
+collection :buckets do
+  description "Cloud Storage buckets - aka buckets|directories|folders"
 
-#create a new blob using html interface - NON STREAMING (i.e. browser POST http form data)
-post "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket" do
-  bucket_id = params[:bucket]
-  blob_id = params['blob']
-  blob_data = params['blob_data']
-  user_meta = {}
-  #metadata from params (i.e., passed by http form post, e.g. browser)
-  max = params[:meta_params]
-  if(max)
-    (1..max.to_i).each do |i|
-      key = params[:"meta_name#{i}"]
-      key = "HTTP_X_Deltacloud_Blobmeta_#{key}"
-      value = params[:"meta_value#{i}"]
-      user_meta[key] = value
+  subcollection :blobs do
+    description "Blobs associated with given bucket"
+
+    operation :show do
+      description "Display blob"
+      control do
+        @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]})
+        if @blob
+          respond_to do |format|
+            format.xml { haml :"blobs/show" }
+            format.html { haml :"blobs/show" }
+            format.json { convert_to_json(:blob, @blob) }
+          end
+        else
+          report_error(404)
+        end
+      end
     end
-  end
-  @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta)
-  respond_to do |format|
-    format.xml { haml :"blobs/show" }
-    format.html { haml :"blobs/show"}
-  end
-end
 
-#delete a blob
-delete "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do
-  bucket_id = params[:bucket]
-  blob_id = params[:blob]
-  driver.delete_blob(credentials, bucket_id, blob_id)
-  status 204
-  respond_to do |format|
-    format.xml
-    format.json
-    format.html { redirect(bucket_url(bucket_id)) }
-  end
-end
+    operation :create do
+      description "Create new blob"
+      control do
+        bucket_id = params[:bucket]
+        blob_id = params['blob']
+        blob_data = params['blob_data']
+        user_meta = {}
+        #metadata from params (i.e., passed by http form post, e.g. browser)
+        max = params[:meta_params]
+        if(max)
+          (1..max.to_i).each do |i|
+            key = params[:"meta_name#{i}"]
+            key = "HTTP_X_Deltacloud_Blobmeta_#{key}"
+            value = params[:"meta_value#{i}"]
+            user_meta[key] = value
+          end
+        end
+        @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta)
+        respond_to do |format|
+          format.xml { haml :"blobs/show" }
+          format.html { haml :"blobs/show"}
+        end
+      end
+    end
 
-#get blob metadata
-head "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do
-  @blob_id = params[:blob]
-  @blob_metadata = driver.blob_metadata(credentials, {:id => params[:blob], 'bucket' => params[:bucket]})
-  if @blob_metadata
-      @blob_metadata.each do |k,v|
-        headers["X-Deltacloud-Blobmeta-#{k}"] = v
+    operation :destroy do
+      description "Destroy bucket"
+      control do
+        bucket_id = params[:bucket]
+        blob_id = params[:blob]
+        driver.delete_blob(credentials, bucket_id, blob_id)
+        status 204
+        respond_to do |format|
+          format.xml
+          format.json
+          format.html { redirect(bucket_url(bucket_id)) }
+        end
       end
-      status 204
-      respond_to do |format|
-        format.xml
-        format.json
+    end
+
+    operation :stream, :member => true, :standard => true, :method => :put do
+      description "Get blob metadata"
+      control do
+        if(env["BLOB_SUCCESS"]) #ie got a 200ok after putting blob
+          content_type = env["CONTENT_TYPE"]
+          content_type ||=  ""
+          @blob = driver.blob(credentials, {:id => params[:blob],
+                                            'bucket' => params[:bucket]})
+          respond_to do |format|
+            format.xml { haml :"blobs/show" }
+            format.html { haml :"blobs/show" }
+            format.json { convert_to_json(:blob, @blob) }
+          end
+        elsif(env["BLOB_FAIL"])
+          report_error(500) #OK?
+        else # small blobs - < 112kb dont hit the streaming monkey patch - use 'normal' create_blob
+          # also, if running under webrick don't hit the streaming patch (Thin specific)
+          bucket_id = params[:bucket]
+          blob_id = params[:blob]
+          temp_file = Tempfile.new("temp_blob_file")
+          temp_file.write(env['rack.input'].read)
+          temp_file.flush
+          content_type = env['CONTENT_TYPE'] || ""
+          blob_data = {:tempfile => temp_file, :type => content_type}
+          user_meta = BlobHelper::extract_blob_metadata_hash(request.env)
+          @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta)
+          temp_file.delete
+          respond_to do |format|
+            format.xml { haml :"blobs/show" }
+            format.html { haml :"blobs/show"}
+          end
+        end
       end
-   else
-    report_error(404)
-  end
-end
+    end
 
-#update blob metadata
-post "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do
-  meta_hash = BlobHelper::extract_blob_metadata_hash(request.env)
-  success = driver.update_blob_metadata(credentials, {'bucket'=>params[:bucket], :id =>params[:blob], 'meta_hash' => meta_hash})
-  if(success)
-    meta_hash.each do |k,v|
-      headers["X-Deltacloud-Blobmeta-#{k}"] = v
+    operation :metadata, :member => true, :standard => true, :method => :head do
+      description "Get blob metadata"
+      control do
+        @blob_id = params[:blob]
+        @blob_metadata = driver.blob_metadata(credentials, {:id => params[:blob], 'bucket' => params[:bucket]})
+        if @blob_metadata
+          @blob_metadata.each do |k,v|
+            headers["X-Deltacloud-Blobmeta-#{k}"] = v
+          end
+          status 204
+          respond_to do |format|
+            format.xml
+            format.json
+          end
+        else
+          report_error(404)
+        end
+      end
     end
-    status 204
-    respond_to do |format|
-      format.xml
-      format.json
+
+    operation :update, :member => true, :method => :post do
+      description "Update blob metadata"
+      control do
+        meta_hash = BlobHelper::extract_blob_metadata_hash(request.env)
+        success = driver.update_blob_metadata(credentials, {'bucket'=>params[:bucket], :id =>params[:blob], 'meta_hash' => meta_hash})
+        if(success)
+          meta_hash.each do |k,v|
+            headers["X-Deltacloud-Blobmeta-#{k}"] = v
+          end
+          status 204
+          respond_to do |format|
+            format.xml
+            format.json
+          end
+        else
+          report_error(404) #FIXME is this the right error code?
+        end
+      end
     end
-  else
-    report_error(404) #FIXME is this the right error code?
-  end
-end
 
-#Get a particular blob's particulars (not actual blob data)
-get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do
-  @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]})
-  if @blob
-    respond_to do |format|
-      format.xml { haml :"blobs/show" }
-      format.html { haml :"blobs/show" }
-      format.json { convert_to_json(:blob, @blob) }
+    operation :content, :member => true, :method => :get do
+      description "Download blob content"
+      control do
+        @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]})
+        if @blob
+          params['content_length'] = @blob.content_length
+          params['content_type'] = @blob.content_type
+          params['content_disposition'] = "attachment; filename=#{@blob.id}"
+          BlobStream.call(env, credentials, params)
+        else
+          report_error(404)
+        end
       end
-  else
-      report_error(404)
-  end
-end
+    end
 
-#get the content of a particular blob
-get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob/content" do
-  @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]})
-  if @blob
-    params['content_length'] = @blob.content_length
-    params['content_type'] = @blob.content_type
-    params['content_disposition'] = "attachment; filename=#{@blob.id}"
-    BlobStream.call(env, credentials, params)
-  else
-    report_error(404)
   end
-end
-
-collection :buckets do
-  description "Cloud Storage buckets - aka buckets|directories|folders"
 
   operation :new do
     description "A form to create a new bucket resource"
-- 
1.7.4.1