From 13509fced449e1a93c35a1521d6708f10af7624e Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Tue, 16 Oct 2012 21:48:50 -0400 Subject: [PATCH 1/8] Add grant, revoke and list methods for cross tenant object acls implementation. --- lib/fog/hp/models/storage/directories.rb | 5 +- lib/fog/hp/models/storage/directory.rb | 86 ++++++++++++++++++---- lib/fog/hp/storage.rb | 92 ++++++++++++++++++------ 3 files changed, 144 insertions(+), 39 deletions(-) diff --git a/lib/fog/hp/models/storage/directories.rb b/lib/fog/hp/models/storage/directories.rb index f3f70235c..29b462069 100644 --- a/lib/fog/hp/models/storage/directories.rb +++ b/lib/fog/hp/models/storage/directories.rb @@ -58,7 +58,10 @@ module Fog end # set the acl on the directory based on the headers if !(read_header.nil? && write_header.nil?) - directory.acl = connection.header_to_acl(read_header, write_header) + read_acl, write_acl = connection.header_to_perm_acl(read_header, write_header) + # do not want to expose the read_acl and write_acl as writable attributes + directory.instance_variable_set(:@read_acl, read_acl) + directory.instance_variable_set(:@write_acl, write_acl) end directory.files.merge_attributes(options) directory.files.instance_variable_set(:@loaded, true) diff --git a/lib/fog/hp/models/storage/directory.rb b/lib/fog/hp/models/storage/directory.rb index 972acc2a4..4336a1efe 100644 --- a/lib/fog/hp/models/storage/directory.rb +++ b/lib/fog/hp/models/storage/directory.rb @@ -12,15 +12,70 @@ module Fog attribute :bytes, :aliases => 'X-Container-Bytes-Used' attribute :count, :aliases => 'X-Container-Object-Count' - def acl=(new_acl) - if new_acl.nil? - new_acl = "private" + def initialize(attributes = {}) + @read_acl = [] + @write_acl = [] + super + end + + def read_acl + @read_acl + end + + def write_acl + @write_acl + end + + def can_read?(user) + return false if @read_acl.nil? + list_users_with_read.include?(user) + end + + def can_write?(user) + return false if @write_acl.nil? + list_users_with_write.include?(user) + end + + def can_read_write?(user) + can_read?(user) && can_write?(user) + end + + def list_users_with_read + users = [] + users = @read_acl.map {|acl| acl.split(':')[1]} unless @read_acl.nil? + return users + end + + def list_users_with_write + users = [] + users = @write_acl.map {|acl| acl.split(':')[1]} unless @write_acl.nil? + return users + end + + def grant(perm, users=[]) + r_acl, w_acl = connection.perm_to_acl(perm, users) + unless r_acl.nil? + @read_acl = @read_acl + r_acl + @read_acl.uniq! end - valid_acls = ['private', 'public-read', 'public-write', 'public-read-write'] - unless valid_acls.include?(new_acl) - raise ArgumentError.new("acl must be one of [#{valid_acls.join(', ')}]") + unless w_acl.nil? + @write_acl = @write_acl + w_acl + @write_acl.uniq! end - @acl = new_acl + true + end + + def revoke(perm, users=[]) + r_acl, w_acl = connection.perm_to_acl(perm, users) + unless r_acl.nil? + @read_acl = @read_acl - r_acl + @read_acl.uniq! + end + unless w_acl.nil? + @write_acl = @write_acl - w_acl + @write_acl.uniq! + end + true end def destroy @@ -50,18 +105,20 @@ module Fog def public=(new_public) if new_public - @acl = 'public-read' + self.grant("pr") else - @acl = 'private' + self.revoke("pr") end @public = new_public end def public? - if @acl.nil? + if @read_acl.empty? false + elsif @read_acl.include?(".r:*") + true else - @acl == 'public-read' + false end end @@ -70,7 +127,7 @@ module Fog @public_url ||= begin begin response = connection.head_container(key) # escape the key to cover for special char. in container names - url = "#{connection.url}/#{Fog::HP.escape(key)}" + url = connection.public_url(key) rescue Fog::Storage::HP::NotFound => err nil end @@ -135,9 +192,8 @@ module Fog def save requires :key options = {} - if @acl - options.merge!(connection.acl_to_header(@acl)) - end + # write out the acls into the headers before save + options.merge!(connection.perm_acl_to_header(@read_acl, @write_acl)) connection.put_container(key, options) # Added an extra check to see if CDN is enabled for the container if (!connection.cdn.nil? && connection.cdn.enabled?) diff --git a/lib/fog/hp/storage.rb b/lib/fog/hp/storage.rb index 8ba015aad..3c3617f00 100644 --- a/lib/fog/hp/storage.rb +++ b/lib/fog/hp/storage.rb @@ -53,34 +53,80 @@ module Fog "#{@scheme}://#{@host}:#{@port}#{@path}" end - def acl_to_header(acl) + def public_url(container=nil, object=nil) + public_url = nil + unless container.nil? + if object.nil? + # return container public url + public_url = "#{url}/#{Fog::HP.escape(container)}" + else + # return object public url + public_url = "#{url}/#{Fog::HP.escape(container)}/#{Fog::HP.escape(object)}" + end + end + public_url + end + + def perm_to_acl(perm, users=[]) + read_perm_acl = [] + write_perm_acl = [] + valid_public_perms = ['pr', 'pw', 'prw'] + valid_account_perms = ['r', 'w', 'rw'] + valid_perms = valid_public_perms + valid_account_perms + unless valid_perms.include?(perm) + raise ArgumentError.new("permission must be one of [#{valid_perms.join(', ')}]") + end + # tackle the public access differently + if valid_public_perms.include?(perm) + case perm + when "pr" + read_perm_acl = [".r:*",".rlistings"] + when "pw" + write_perm_acl = ["*"] + when "prw" + read_perm_acl = [".r:*",".rlistings"] + write_perm_acl = ["*"] + end + elsif valid_account_perms.include?(perm) + # tackle the user access differently + unless (users.nil? || users.empty?) + # return the correct acls + tenant_id = "*" # this might change later + acl_array = users.map { |u| "#{tenant_id}:#{u}" } + #acl_string = acl_array.join(',') + case perm + when "r" + read_perm_acl = acl_array + when "w" + write_perm_acl = acl_array + when "rw" + read_perm_acl = acl_array + write_perm_acl = acl_array + end + end + end + return read_perm_acl, write_perm_acl + end + + def perm_acl_to_header(read_perm_acl, write_perm_acl) header = {} - case acl - when "private" - header['X-Container-Read'] = "" - header['X-Container-Write'] = "" - when "public-read" - header['X-Container-Read'] = ".r:*,.rlistings" - when "public-write" - header['X-Container-Write'] = "*" - when "public-read-write" - header['X-Container-Read'] = ".r:*,.rlistings" - header['X-Container-Write'] = "*" + if read_perm_acl.nil? && write_perm_acl.nil? + header = {'X-Container-Read' => "", 'X-Container-Write' => ""} + elsif !read_perm_acl.nil? && write_perm_acl.nil? + header = {'X-Container-Read' => "#{read_perm_acl.join(',')}", 'X-Container-Write' => ""} + elsif read_perm_acl.nil? && !write_perm_acl.nil? + header = {'X-Container-Read' => "", 'X-Container-Write' => "#{write_perm_acl.join(',')}"} + elsif !read_perm_acl.nil? && !write_perm_acl.nil? + header = {'X-Container-Read' => "#{read_perm_acl.join(',')}", 'X-Container-Write' => "#{write_perm_acl.join(',')}"} end header end - def header_to_acl(read_header=nil, write_header=nil) - acl = nil - if read_header.nil? && write_header.nil? - acl = nil - elsif !read_header.nil? && read_header.include?(".r:*") && write_header.nil? - acl = "public-read" - elsif !write_header.nil? && write_header.include?("*") && read_header.nil? - acl = "public-write" - elsif !read_header.nil? && read_header.include?(".r:*") && !write_header.nil? && write_header.include?("*") - acl = "public-read-write" - end + def header_to_perm_acl(read_header=nil, write_header=nil) + read_h, write_h = nil + read_h = read_header.split(',') unless read_header.nil? + write_h = write_header.split(',') unless write_header.nil? + return read_h, write_h end def generate_object_temp_url(container, object, expires_secs, method) From 6679654b704293e0121164db31e57a0931315a6b Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Tue, 16 Oct 2012 22:51:59 -0400 Subject: [PATCH 2/8] Add request layer methods for shared container and shared object access. --- .../requests/storage/get_shared_container.rb | 56 +++++++++++++++++++ .../hp/requests/storage/get_shared_object.rb | 46 +++++++++++++++ .../requests/storage/head_shared_container.rb | 44 +++++++++++++++ .../hp/requests/storage/head_shared_object.rb | 38 +++++++++++++ lib/fog/hp/storage.rb | 29 ++++++++++ 5 files changed, 213 insertions(+) create mode 100644 lib/fog/hp/requests/storage/get_shared_container.rb create mode 100644 lib/fog/hp/requests/storage/get_shared_object.rb create mode 100644 lib/fog/hp/requests/storage/head_shared_container.rb create mode 100644 lib/fog/hp/requests/storage/head_shared_object.rb diff --git a/lib/fog/hp/requests/storage/get_shared_container.rb b/lib/fog/hp/requests/storage/get_shared_container.rb new file mode 100644 index 000000000..e3c262c1f --- /dev/null +++ b/lib/fog/hp/requests/storage/get_shared_container.rb @@ -0,0 +1,56 @@ +module Fog + module Storage + class HP + class Real + + # Get details for a shared container + # + # ==== Parameters + # * shared_container_url<~String> - Url of the shared container + # * options<~String>: + # * 'limit'<~String> - Maximum number of objects to return + # * 'marker'<~String> - Only return objects whose name is greater than marker + # * 'prefix'<~String> - Limits results to those starting with prefix + # * 'path'<~String> - Return objects nested in the pseudo path + # + # ==== Returns + # * response<~Excon::Response>: + # * headers<~Hash>: + # * 'X-Container-Object-Count'<~String> - Count of objects in container + # * 'X-Container-Bytes-Used'<~String> - Bytes used + # * 'X-Trans-Id'<~String> - Trans Id + # * body<~Array>: + # * item<~Hash>: + # * 'bytes'<~String> - Size of object + # * 'content_type'<~String> Content-Type of object + # * 'hash'<~String> - Hash of object (etag?) + # * 'last_modified'<~String> - Last modified timestamp + # * 'name'<~String> - Name of object + def get_shared_container(shared_container_url, options = {}) + options = options.reject {|key, value| value.nil?} + # split up the shared container url + uri = URI.parse(shared_container_url) + path = uri.path + + response = shared_request( + :expects => 200, + :method => 'GET', + :path => path, + :query => {'format' => 'json'}.merge!(options) + ) + response + end + + end + + class Mock # :nodoc:all + + def get_shared_container(shared_container_url, options = {}) + + end + + end + + end + end +end diff --git a/lib/fog/hp/requests/storage/get_shared_object.rb b/lib/fog/hp/requests/storage/get_shared_object.rb new file mode 100644 index 000000000..e2f769501 --- /dev/null +++ b/lib/fog/hp/requests/storage/get_shared_object.rb @@ -0,0 +1,46 @@ +module Fog + module Storage + class HP + class Real + + # Get details for a shared object + # + # ==== Parameters + # * shared_object_url<~String> - Url of the shared object + # + def get_shared_object(shared_object_url, &block) + # split up the shared object url + uri = URI.parse(shared_object_url) + path = uri.path + + if block_given? + response = shared_request( + :response_block => block, + :expects => 200, + :method => 'GET', + :path => path + ) + else + response = shared_request({ + :block => block, + :expects => 200, + :method => 'GET', + :path => path + }, false, &block) + end + response + end + + end + + class Mock # :nodoc:all + + def get_shared_object(shared_object_url, &block) + + end + + end + + end + end +end diff --git a/lib/fog/hp/requests/storage/head_shared_container.rb b/lib/fog/hp/requests/storage/head_shared_container.rb new file mode 100644 index 000000000..49029658c --- /dev/null +++ b/lib/fog/hp/requests/storage/head_shared_container.rb @@ -0,0 +1,44 @@ +module Fog + module Storage + class HP + class Real + + # List number of objects and total bytes stored for a shared container + # + # ==== Parameters + # * shared_container_url<~String> - Url of the shared container + # + # ==== Returns + # * response<~Excon::Response>: + # * headers<~Hash>: + # * 'X-Container-Object-Count'<~String> - Count of containers + # * 'X-Container-Bytes-Used'<~String> - Bytes used + def head_shared_container(shared_container_url) + # split up the shared container url + uri = URI.parse(shared_container_url) + path = uri.path + + response = shared_request( + :expects => 204, + :method => 'HEAD', + :path => path, + :query => {'format' => 'json'} + ) + response + end + + end + + class Mock # :nodoc:all + + def head_shared_container(shared_container_url) + response = get_shared_container(shared_container_url) + response.body = nil + response + end + + end + + end + end +end diff --git a/lib/fog/hp/requests/storage/head_shared_object.rb b/lib/fog/hp/requests/storage/head_shared_object.rb new file mode 100644 index 000000000..6faede925 --- /dev/null +++ b/lib/fog/hp/requests/storage/head_shared_object.rb @@ -0,0 +1,38 @@ +module Fog + module Storage + class HP + class Real + + # Get headers for shared object + # + # ==== Parameters + # * * shared_object_url<~String> - Url of the shared object + # + def head_shared_object(shared_object_url) + # split up the shared object url + uri = URI.parse(shared_object_url) + path = uri.path + + response = shared_request({ + :expects => 200, + :method => 'HEAD', + :path => path + }, false) + response + end + + end + + class Mock # :nodoc:all + + def head_shared_object(shared_object_url) + response = get_shared_object(shared_object_url) + response.body = nil + response + end + + end + + end + end +end diff --git a/lib/fog/hp/storage.rb b/lib/fog/hp/storage.rb index 3c3617f00..18304256f 100644 --- a/lib/fog/hp/storage.rb +++ b/lib/fog/hp/storage.rb @@ -21,9 +21,13 @@ module Fog request :get_containers request :get_object request :get_object_temp_url + request :get_shared_container + request :get_shared_object request :head_container request :head_containers request :head_object + request :head_shared_container + request :head_shared_object request :put_container request :put_object @@ -276,6 +280,31 @@ module Fog response end + # this request is used only for get_shared_container and get_shared_object calls + def shared_request(params, parse_json = true, &block) + begin + response = @connection.request(params.merge!({ + :headers => { + 'Content-Type' => 'application/json', + 'X-Auth-Token' => @auth_token + }.merge!(params[:headers] || {}), + :host => @host, + :path => "#{params[:path]}", + }), &block) + rescue Excon::Errors::HTTPStatusError => error + raise case error + when Excon::Errors::NotFound + Fog::Storage::HP::NotFound.slurp(error) + else + error + end + end + if !response.body.empty? && parse_json && response.headers['Content-Type'] =~ %r{application/json} + response.body = MultiJson.decode(response.body) + end + response + end + end end end From 5e5bdd08c17dcf6378a6c0338639106ad8efa63d Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Wed, 17 Oct 2012 15:02:42 -0400 Subject: [PATCH 3/8] Fix response status for mocks. --- lib/fog/hp/requests/storage/head_container.rb | 1 + lib/fog/hp/requests/storage/head_object.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/fog/hp/requests/storage/head_container.rb b/lib/fog/hp/requests/storage/head_container.rb index 8d1d2e969..220864548 100644 --- a/lib/fog/hp/requests/storage/head_container.rb +++ b/lib/fog/hp/requests/storage/head_container.rb @@ -30,6 +30,7 @@ module Fog def head_container(container_name) response = get_container(container_name) response.body = nil + response.status = 204 response end diff --git a/lib/fog/hp/requests/storage/head_object.rb b/lib/fog/hp/requests/storage/head_object.rb index 1115c420a..5cfbdf402 100644 --- a/lib/fog/hp/requests/storage/head_object.rb +++ b/lib/fog/hp/requests/storage/head_object.rb @@ -11,7 +11,7 @@ module Fog # def head_object(container, object) response = request({ - :expects => 200, + :expects => 200, # should be 204 :method => 'HEAD', :path => "#{Fog::HP.escape(container)}/#{Fog::HP.escape(object)}" }, false) @@ -25,6 +25,7 @@ module Fog def head_object(container_name, object_name, options = {}) response = get_object(container_name, object_name, options) response.body = nil + response.status = 200 # should be 204 response end From 7c21f2d3789b46993b5bf89976b6c298cf821bb8 Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Wed, 17 Oct 2012 15:03:10 -0400 Subject: [PATCH 4/8] Add mocks for shared container and objects calls. --- .../requests/storage/get_shared_container.rb | 19 +++++++++++++++ .../hp/requests/storage/get_shared_object.rb | 23 ++++++++++++++++++- .../requests/storage/head_shared_container.rb | 1 + .../hp/requests/storage/head_shared_object.rb | 3 ++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/fog/hp/requests/storage/get_shared_container.rb b/lib/fog/hp/requests/storage/get_shared_container.rb index e3c262c1f..6410f46af 100644 --- a/lib/fog/hp/requests/storage/get_shared_container.rb +++ b/lib/fog/hp/requests/storage/get_shared_container.rb @@ -46,6 +46,25 @@ module Fog class Mock # :nodoc:all def get_shared_container(shared_container_url, options = {}) + response = Excon::Response.new + data = { + 'name' => Fog::Mock.random_letters(10), + 'hash' => Fog::HP::Mock.etag, + 'bytes' => 11, + 'content_type' => "text/plain", + 'last_modified' => Time.now + } + response.status = 200 + response.body = [data] + response.headers = { + 'X-Container-Object-Count' => 1, + 'X-Container-Bytes-Used' => 11, + 'Accept-Ranges' => 'bytes', + 'Content-Type' => "application/json", + 'Content-Length' => 11, + 'X-Trans-Id' => "tx#{Fog::Mock.random_hex(32)}" + } + response end diff --git a/lib/fog/hp/requests/storage/get_shared_object.rb b/lib/fog/hp/requests/storage/get_shared_object.rb index e2f769501..d429f9b04 100644 --- a/lib/fog/hp/requests/storage/get_shared_object.rb +++ b/lib/fog/hp/requests/storage/get_shared_object.rb @@ -36,7 +36,28 @@ module Fog class Mock # :nodoc:all def get_shared_object(shared_object_url, &block) - + response = Excon::Response.new + response.status = 200 + response.headers = { + 'Last-Modified' => Date.today.rfc822, + 'Etag' => Fog::HP::Mock.etag, + 'Accept-Ranges' => 'bytes', + 'Content-Type' => "text/plain", + 'Content-Length' => 11, + 'X-Trans-Id' => "tx#{Fog::Mock.random_hex(32)}" + } + unless block_given? + response.body = "This is a sample text.\n" + else + data = StringIO.new("This is a sample text.\n") + remaining = data.length + while remaining > 0 + chunk = data.read([remaining, Excon::CHUNK_SIZE].min) + block.call(chunk) + remaining -= Excon::CHUNK_SIZE + end + end + response end end diff --git a/lib/fog/hp/requests/storage/head_shared_container.rb b/lib/fog/hp/requests/storage/head_shared_container.rb index 49029658c..cf16a5db1 100644 --- a/lib/fog/hp/requests/storage/head_shared_container.rb +++ b/lib/fog/hp/requests/storage/head_shared_container.rb @@ -34,6 +34,7 @@ module Fog def head_shared_container(shared_container_url) response = get_shared_container(shared_container_url) response.body = nil + response.status = 204 response end diff --git a/lib/fog/hp/requests/storage/head_shared_object.rb b/lib/fog/hp/requests/storage/head_shared_object.rb index 6faede925..9dad09e35 100644 --- a/lib/fog/hp/requests/storage/head_shared_object.rb +++ b/lib/fog/hp/requests/storage/head_shared_object.rb @@ -14,7 +14,7 @@ module Fog path = uri.path response = shared_request({ - :expects => 200, + :expects => 200, # should be 204 :method => 'HEAD', :path => path }, false) @@ -28,6 +28,7 @@ module Fog def head_shared_object(shared_object_url) response = get_shared_object(shared_object_url) response.body = nil + response.status = 200 # should be 204 response end From c87f00392a6882f74d85aa2037ebd67ff2c0d31f Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Fri, 19 Oct 2012 01:29:22 -0400 Subject: [PATCH 5/8] Add a new exception class and handled exceptions in exception messages. --- lib/fog/hp.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/fog/hp.rb b/lib/fog/hp.rb index d4c8df904..36584b64f 100644 --- a/lib/fog/hp.rb +++ b/lib/fog/hp.rb @@ -20,8 +20,13 @@ module Fog data = nil message = nil else - data = MultiJson.decode(error.response.body) - message = data['message'] + begin + data = MultiJson.decode(error.response.body) + message = data['message'] + rescue MultiJson::DecodeError + data = error.response.body #### the body is not in JSON format so just return it as it is + message = data + end end new_error = super(error, message) @@ -33,6 +38,7 @@ module Fog class InternalServerError < ServiceError; end class Conflict < ServiceError; end class NotFound < ServiceError; end + class Forbidden < ServiceError; end class ServiceUnavailable < ServiceError; end class BadRequest < ServiceError From 16b31facd69ba4afc71bca08a6985470b8e50c67 Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Fri, 19 Oct 2012 01:33:20 -0400 Subject: [PATCH 6/8] Add request method for put_shared_object. Add model and collection for shared_directory and shared_file. --- .../hp/models/storage/shared_directories.rb | 49 +++++++++++ lib/fog/hp/models/storage/shared_directory.rb | 28 ++++++ lib/fog/hp/models/storage/shared_file.rb | 57 +++++++++++++ lib/fog/hp/models/storage/shared_files.rb | 60 +++++++++++++ .../hp/requests/storage/put_shared_object.rb | 85 +++++++++++++++++++ lib/fog/hp/storage.rb | 13 ++- 6 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 lib/fog/hp/models/storage/shared_directories.rb create mode 100644 lib/fog/hp/models/storage/shared_directory.rb create mode 100644 lib/fog/hp/models/storage/shared_file.rb create mode 100644 lib/fog/hp/models/storage/shared_files.rb create mode 100644 lib/fog/hp/requests/storage/put_shared_object.rb diff --git a/lib/fog/hp/models/storage/shared_directories.rb b/lib/fog/hp/models/storage/shared_directories.rb new file mode 100644 index 000000000..3a552784d --- /dev/null +++ b/lib/fog/hp/models/storage/shared_directories.rb @@ -0,0 +1,49 @@ +require 'fog/core/collection' +require 'fog/hp/models/storage/shared_directory' + +module Fog + module Storage + class HP + + class SharedDirectories < Fog::Collection + + model Fog::Storage::HP::SharedDirectory + + def head(url) + data = connection.head_shared_container(url) + shared_directory = new(:url => url) + for key, value in data.headers + if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key) + shared_directory.merge_attributes(key => value) + end + end + + shared_directory + rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden + nil + end + + def get(url) + data = connection.get_shared_container(url) + shared_directory = new(:url => url) + for key, value in data.headers + if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key) + shared_directory.merge_attributes(key => value) + end + end + # set the files for the directory + shared_directory.files.instance_variable_set(:@loaded, true) + data.body.each do |file| + shared_directory.files << shared_directory.files.new(file) + end + + shared_directory + rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden + nil + end + + end + + end + end +end diff --git a/lib/fog/hp/models/storage/shared_directory.rb b/lib/fog/hp/models/storage/shared_directory.rb new file mode 100644 index 000000000..de6953edb --- /dev/null +++ b/lib/fog/hp/models/storage/shared_directory.rb @@ -0,0 +1,28 @@ +require 'fog/core/model' +require 'fog/hp/models/storage/shared_files' + +module Fog + module Storage + class HP + + class SharedDirectory < Fog::Model + + identity :url + + attribute :bytes, :aliases => 'X-Container-Bytes-Used' + attribute :count, :aliases => 'X-Container-Object-Count' + + def files + @files ||= begin + Fog::Storage::HP::SharedFiles.new( + :shared_directory => self, + :connection => connection + ) + end + end + + end + + end + end +end diff --git a/lib/fog/hp/models/storage/shared_file.rb b/lib/fog/hp/models/storage/shared_file.rb new file mode 100644 index 000000000..37245f271 --- /dev/null +++ b/lib/fog/hp/models/storage/shared_file.rb @@ -0,0 +1,57 @@ +require 'fog/core/model' + +module Fog + module Storage + class HP + + class SharedFile < Fog::Model + + identity :key, :aliases => 'name' + attribute :url + + attribute :content_length, :aliases => ['bytes', 'Content-Length'], :type => :integer + attribute :content_type, :aliases => ['content_type', 'Content-Type'] + attribute :etag, :aliases => ['hash', 'Etag'] + attribute :last_modified, :aliases => ['last_modified', 'Last-Modified'], :type => :time + + def url + "#{self.collection.shared_directory.url}/#{key}" + end + + def body + attributes[:body] ||= if last_modified + collection.get(identity).body + else + '' + end + end + + def body=(new_body) + attributes[:body] = new_body + end + + def shared_directory + @shared_directory + end + + def save(options = {}) + requires :shared_directory, :key + options['Content-Type'] = content_type if content_type + data = connection.put_shared_object(shared_directory.url, key, body, options) + merge_attributes(data.headers) + self.content_length = Fog::Storage.get_body_size(body) + true + rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden + false + end + + private + + def shared_directory=(new_shared_directory) + @shared_directory = new_shared_directory + end + end + + end + end +end diff --git a/lib/fog/hp/models/storage/shared_files.rb b/lib/fog/hp/models/storage/shared_files.rb new file mode 100644 index 000000000..97894058f --- /dev/null +++ b/lib/fog/hp/models/storage/shared_files.rb @@ -0,0 +1,60 @@ +require 'fog/core/collection' +require 'fog/hp/models/storage/shared_file' + +module Fog + module Storage + class HP + + class SharedFiles < Fog::Collection + + attribute :shared_directory + + model Fog::Storage::HP::SharedFile + + def all + requires :shared_directory + parent = shared_directory.collection.get(shared_directory.url) + if parent + load(parent.files.map {|file| file.attributes}) + else + nil + end + rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden + nil + end + + def get(key, &block) + requires :shared_directory + shared_object_url = "#{shared_directory.url}/#{key}" + data = connection.get_shared_object(shared_object_url, &block) + file_data = data.headers.merge({ + :body => data.body, + :key => key + }) + new(file_data) + rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden + nil + end + + def head(key) + requires :shared_directory + shared_object_url = "#{shared_directory.url}/#{key}" + data = connection.head_shared_object(shared_object_url) + file_data = data.headers.merge({ + :key => key + }) + new(file_data) + rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden + nil + end + + def new(attributes = {}) + requires :shared_directory + super({ :shared_directory => shared_directory }.merge!(attributes)) + end + + end + + end + end +end diff --git a/lib/fog/hp/requests/storage/put_shared_object.rb b/lib/fog/hp/requests/storage/put_shared_object.rb new file mode 100644 index 000000000..cee3a9db0 --- /dev/null +++ b/lib/fog/hp/requests/storage/put_shared_object.rb @@ -0,0 +1,85 @@ +module Fog + module Storage + class HP + class Real + + # Create a new object in a shared container + # + # ==== Parameters + # * shared_container_url<~String> - Shared url for the container + # * object<~String> - Name of the object + # * options<~Hash> - header options + # + def put_shared_object(shared_container_url, object_name, data, options = {}, &block) + # split up the shared object url + uri = URI.parse(shared_container_url) + path = uri.path + + data = Fog::Storage.parse_data(data) + headers = data[:headers].merge!(options) + if block_given? + headers['Transfer-Encoding'] = 'chunked' + headers.delete('Content-Length') + return shared_request( + :request_block => block, + :expects => 201, + :headers => headers, + :method => 'PUT', + :path => "#{path}/#{Fog::HP.escape(object_name)}" + ) + end + if headers.has_key?('Transfer-Encoding') + headers.delete('Content-Length') + end + response = shared_request( + :body => data[:body], + :expects => 201, + :headers => headers, + :method => 'PUT', + :path => "#{path}/#{Fog::HP.escape(object_name)}" + ) + response + end + + end + + class Mock # :nodoc:all + + def put_shared_object(shared_container_url, object_name, data, options = {}, &block) + response = Excon::Response.new + data = Fog::Storage.parse_data(data) + unless data[:body].is_a?(String) + data[:body] = data[:body].read + end + response.status = 201 + object = { + :body => data[:body], + 'Content-Type' => options['Content-Type'] || data[:headers]['Content-Type'], + 'ETag' => Fog::HP::Mock.etag, + 'Key' => object_name, + 'Date' => Fog::Time.now.to_date_header, + 'Content-Length' => options['Content-Length'] || data[:headers]['Content-Length'], + } + + for key, value in options + case key + when 'Cache-Control', 'Content-Disposition', 'Content-Encoding', 'Content-MD5', 'Expires', /^X-Object-Meta-/ + object[key] = value + end + end + + response.headers = { + 'Content-Length' => object['Content-Length'], + 'Content-Type' => object['Content-Type'], + 'ETag' => object['ETag'], + 'Date' => object['Date'] + } + + response + end + + end + + end + end +end diff --git a/lib/fog/hp/storage.rb b/lib/fog/hp/storage.rb index 18304256f..6f964d0a8 100644 --- a/lib/fog/hp/storage.rb +++ b/lib/fog/hp/storage.rb @@ -11,8 +11,12 @@ module Fog model_path 'fog/hp/models/storage' model :directory collection :directories + model :shared_directory + collection :shared_directories model :file collection :files + model :shared_file + collection :shared_files request_path 'fog/hp/requests/storage' request :delete_container @@ -30,6 +34,7 @@ module Fog request :head_shared_object request :put_container request :put_object + request :put_shared_object module Utils @@ -295,12 +300,18 @@ module Fog raise case error when Excon::Errors::NotFound Fog::Storage::HP::NotFound.slurp(error) + when Excon::Errors::Forbidden + Fog::HP::Errors::Forbidden.slurp(error) else error end end if !response.body.empty? && parse_json && response.headers['Content-Type'] =~ %r{application/json} - response.body = MultiJson.decode(response.body) + begin + response.body = MultiJson.decode(response.body) + rescue MultiJson::DecodeError => error + response.body #### the body is not in JSON format so just return it as it is + end end response end From ed11924172c352d5077dc8b8447154cf8d6f92d5 Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Fri, 19 Oct 2012 01:34:14 -0400 Subject: [PATCH 7/8] Refactor common code into separate method. --- lib/fog/hp/models/storage/directories.rb | 48 ++++++++++-------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/lib/fog/hp/models/storage/directories.rb b/lib/fog/hp/models/storage/directories.rb index 29b462069..2fc5115af 100644 --- a/lib/fog/hp/models/storage/directories.rb +++ b/lib/fog/hp/models/storage/directories.rb @@ -15,24 +15,8 @@ module Fog end def head(key, options = {}) - read_header = nil - write_header = nil data = connection.head_container(key) - directory = new(:key => key) - for key, value in data.headers - if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key) - directory.merge_attributes(key => value) - end - if key == 'X-Container-Read' - read_header = value - elsif key == 'X-Container-Write' - write_header = value - end - end - # set the acl on the directory based on the headers - if !(read_header.nil? && write_header.nil?) - directory.acl = connection.header_to_acl(read_header, write_header) - end + directory = create_directory(key, data) # set the cdn state for the directory directory.cdn_enabled? @@ -42,9 +26,27 @@ module Fog end def get(key, options = {}) + data = connection.get_container(key, options) + directory = create_directory(key, data) + # set the files for the directory + directory.files.merge_attributes(options) + directory.files.instance_variable_set(:@loaded, true) + data.body.each do |file| + directory.files << directory.files.new(file) + end + # set the cdn state for the directory + directory.cdn_enabled? + + directory + rescue Fog::Storage::HP::NotFound + nil + end + + private + + def create_directory(key, data) read_header = nil write_header = nil - data = connection.get_container(key, options) directory = new(:key => key) for key, value in data.headers if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key) @@ -63,17 +65,7 @@ module Fog directory.instance_variable_set(:@read_acl, read_acl) directory.instance_variable_set(:@write_acl, write_acl) end - directory.files.merge_attributes(options) - directory.files.instance_variable_set(:@loaded, true) - data.body.each do |file| - directory.files << directory.files.new(file) - end - # set the cdn state for the directory - directory.cdn_enabled? - directory - rescue Fog::Storage::HP::NotFound - nil end end From c4ecfc93577f462cf55cb57ba48ddb80f9fee7ee Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Fri, 19 Oct 2012 01:35:06 -0400 Subject: [PATCH 8/8] Fix mock for put_container to reflect new acl changes. --- lib/fog/hp/requests/storage/put_container.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/fog/hp/requests/storage/put_container.rb b/lib/fog/hp/requests/storage/put_container.rb index b3d4235fe..7bbb95d62 100644 --- a/lib/fog/hp/requests/storage/put_container.rb +++ b/lib/fog/hp/requests/storage/put_container.rb @@ -22,16 +22,16 @@ module Fog class Mock # :nodoc:all def put_container(container_name, options = {}) - acl = options['X-Container-Read'] || 'private' - if !['private', 'public-read'].include?(acl) - #raise Excon::Errors::BadRequest.new('invalid X-Container-Read') - else - self.data[:acls][:container][container_name] = self.class.acls(acl) + read_h = options['X-Container-Read'] || '' + write_h = options['X-Container-Write'] || '' + unless options + read_acl, write_acl = self.class.header_to_perm_acl(read_h, write_h) + self.data[:acls][:container][container_name] = {:read_acl => read_acl, :write_acl => write_acl} end response = Excon::Response.new container = { - :objects => {}, + :objects => {} } if self.data[:containers][container_name] response.status = 202