ruby-atmos 0.6.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/atmos.rb +35 -0
- data/lib/atmos/object.rb +88 -30
- data/lib/atmos/request.rb +41 -8
- data/lib/atmos/response.rb +116 -0
- data/lib/atmos/rest.rb +37 -146
- data/lib/atmos/store.rb +17 -6
- data/lib/atmos/version.rb +1 -1
- data/test/suite.rb +2 -0
- data/test/test_object_create.rb +16 -1
- data/test/test_object_get.rb +55 -0
- data/test/test_object_misc.rb +1 -1
- data/test/test_object_read.rb +21 -37
- data/test/test_object_update.rb +2 -1
- data/test/{request_test.rb → test_request.rb} +1 -2
- data/test/test_store.rb +1 -1
- metadata +11 -7
data/Rakefile
CHANGED
@@ -45,7 +45,7 @@ spec = Gem::Specification.new do |s|
|
|
45
45
|
s.extra_rdoc_files = %w(README COPYING)
|
46
46
|
s.homepage = 'http://www.emc.com/atmos'
|
47
47
|
#s.rubyforge_project = 'atmos'
|
48
|
-
s.files = FileList['Rakefile', 'lib/*/*.rb']
|
48
|
+
s.files = FileList['Rakefile', 'lib/atmos.rb', 'lib/*/*.rb']
|
49
49
|
s.test_files = Dir['test/**/*']
|
50
50
|
s.require_path = 'lib'
|
51
51
|
s.add_dependency('log4r', '>=1.1.9')
|
data/lib/atmos.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'log4r'
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
require 'net/https'
|
6
|
+
require 'time'
|
7
|
+
require 'base64'
|
8
|
+
require 'hmac-sha1'
|
9
|
+
|
10
|
+
|
11
|
+
Log4r::Logger.new('atmos')
|
12
|
+
Log4r::Logger.get('atmos').add(Log4r::StderrOutputter.new 'console')
|
13
|
+
Log4r::Logger.get('atmos').level = Log4r::FATAL
|
14
|
+
#Log4r::Logger.get('atmos').level = Log4r::WARN
|
15
|
+
#Log4r::Logger.get('atmos').level = Log4r::INFO
|
16
|
+
#Log4r::Logger.get('atmos').level = Log4r::DEBUG
|
17
|
+
|
18
|
+
|
19
|
+
$:.unshift(File.dirname(__FILE__))
|
20
|
+
require 'atmos/parser'
|
21
|
+
require 'atmos/version'
|
22
|
+
require 'atmos/util'
|
23
|
+
require 'atmos/exceptions'
|
24
|
+
require 'atmos/request'
|
25
|
+
require 'atmos/response'
|
26
|
+
require 'atmos/rest'
|
27
|
+
require 'atmos/store'
|
28
|
+
require 'atmos/attributes'
|
29
|
+
require 'atmos/object'
|
30
|
+
|
31
|
+
#
|
32
|
+
# This sets the default, but it can still be overridden by setting the parser elsewhere.
|
33
|
+
#
|
34
|
+
Atmos::Parser::parser = (File.exists?(File.join(File.dirname(__FILE__), 'atmos/parser/rexml.rb'))) ? Atmos::Parser::REXML : Atmos::Parser::NOKOGIRI
|
35
|
+
Atmos::LOG.warn("parser: #{Atmos::Parser::parser}")
|
data/lib/atmos/object.rb
CHANGED
@@ -75,23 +75,13 @@ module Atmos
|
|
75
75
|
class Object
|
76
76
|
attr_reader :aoid, :request, :user # :nodoc:
|
77
77
|
|
78
|
-
# Hash-like object containing user access control properties of the object.
|
79
|
-
attr_reader :user_acl
|
80
|
-
|
81
|
-
# Hash-like object containing group access control properties of the object.
|
82
|
-
attr_reader :group_acl
|
83
|
-
|
84
|
-
# Hash-like object containing non-listable metadata associated with the object.
|
85
|
-
attr_reader :metadata
|
86
|
-
|
87
|
-
# Hash-like object containing listable metadata associated with the object.
|
88
|
-
attr_reader :listable_metadata
|
89
|
-
|
90
|
-
# Hash-like object containing read-only system metadata associated with the object.
|
91
|
-
attr_reader :system_metadata
|
92
|
-
|
93
78
|
@request = nil
|
94
79
|
@checksum = nil
|
80
|
+
@user_acl = nil
|
81
|
+
@group_acl = nil
|
82
|
+
@metadata = nil
|
83
|
+
@system_metadata = nil
|
84
|
+
@listable_metadata = nil
|
95
85
|
|
96
86
|
|
97
87
|
#
|
@@ -99,9 +89,10 @@ module Atmos
|
|
99
89
|
# with an Atmos::Store object:
|
100
90
|
#
|
101
91
|
# obj = store.create
|
102
|
-
# obj = store.get(obj_id)
|
92
|
+
# obj = store.get(:id => obj_id)
|
93
|
+
# obj = store.get(:namespace => obj_id)
|
103
94
|
#
|
104
|
-
def initialize(store,
|
95
|
+
def initialize(store, action, options = {})
|
105
96
|
Atmos::LOG.debug("obj.new options: #{options.inspect}")
|
106
97
|
validate_options(options)
|
107
98
|
|
@@ -111,20 +102,85 @@ module Atmos
|
|
111
102
|
@request = Atmos::Request.new(:store => @store)
|
112
103
|
@user = @store.user
|
113
104
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
105
|
+
if (action == :create)
|
106
|
+
|
107
|
+
if (options[:id])
|
108
|
+
raise Atmos::Exceptions::ArgumentException, "You can't specify an id on object creation."
|
109
|
+
end
|
110
|
+
|
111
|
+
Atmos::LOG.debug("Object.new: creating new object")
|
112
|
+
response = @request.do(:create_object, options)
|
113
|
+
@aoid = response.id
|
114
|
+
|
115
|
+
elsif (action == :get)
|
116
|
+
|
117
|
+
Atmos::LOG.debug("Retrieving object: id: #{options[:id]}; namespace: #{options[:namespace]}")
|
118
|
+
response = @request.do(:get_object_info, options)
|
119
|
+
@aoid = Atmos::Parser::response_get_string(response.http_response, '//xmlns:objectId')
|
120
|
+
Atmos::LOG.debug("Retrieved object id for namespace: #{@aoid}")
|
119
121
|
end
|
120
122
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Lazy evaluation of hash-like object containing user access control properties of the object.
|
127
|
+
#
|
128
|
+
def user_acl
|
129
|
+
#Atmos::LOG.warn("user_acl: #{@user_acl.inspect}")
|
130
|
+
if (@user_acl.nil?)
|
131
|
+
@user_acl = Atmos::ACL.new(self, Atmos::ACL::USER)
|
132
|
+
#Atmos::LOG.warn("user_acl: #{@user_acl.inspect}")
|
133
|
+
end
|
134
|
+
|
135
|
+
@user_acl
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
#
|
140
|
+
# Lazy evaluation of hash-like object containing group access control properties of the object.
|
141
|
+
#
|
142
|
+
def group_acl
|
143
|
+
if (@group_acl.nil?)
|
144
|
+
@group_acl = Atmos::ACL.new(self, Atmos::ACL::GROUP)
|
145
|
+
end
|
146
|
+
|
147
|
+
@group_acl
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
#
|
152
|
+
# Lazy evaluation of hash-like object containing non-listable properties of the object.
|
153
|
+
#
|
154
|
+
def metadata
|
155
|
+
if (@metadata.nil?)
|
156
|
+
@metadata = Atmos::Metadata.new(self, Atmos::Metadata::NON_LISTABLE)
|
157
|
+
end
|
158
|
+
|
159
|
+
@metadata
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
#
|
164
|
+
# Lazy evaluation of hash-like object containing listable metadata properties of the object.
|
165
|
+
#
|
166
|
+
def listable_metadata
|
167
|
+
if (@listable_metadata.nil?)
|
168
|
+
@listable_metadata = Atmos::Metadata.new(self, Atmos::Metadata::LISTABLE)
|
169
|
+
end
|
170
|
+
|
171
|
+
@listable_metadata
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
#
|
176
|
+
# Lazy evaluation of Hash-like object containing read-only system metadata associated with the object.
|
177
|
+
#
|
178
|
+
def system_metadata
|
179
|
+
if (@system_metadata.nil?)
|
180
|
+
@system_metadata = Atmos::Metadata.new(self, Atmos::Metadata::SYSTEM)
|
181
|
+
end
|
182
|
+
|
183
|
+
@system_metadata
|
128
184
|
end
|
129
185
|
|
130
186
|
|
@@ -230,7 +286,7 @@ module Atmos
|
|
230
286
|
def validate_options(options)
|
231
287
|
invalid_values = []
|
232
288
|
|
233
|
-
valid_options = [:checksum, :user_acl, :group_acl, :metadata, :listable_metadata, :mimetype, :length, :data].freeze
|
289
|
+
valid_options = [:checksum, :user_acl, :group_acl, :metadata, :listable_metadata, :mimetype, :length, :data, :namespace, :id].freeze
|
234
290
|
invalid_options = options.keys - valid_options
|
235
291
|
raise Atmos::Exceptions::ArgumentException, "Unrecognized options: #{invalid_options.inspect}" if (!invalid_options.empty?)
|
236
292
|
|
@@ -248,6 +304,8 @@ module Atmos
|
|
248
304
|
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
|
249
305
|
when :mimetype
|
250
306
|
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(String))
|
307
|
+
when :namespace
|
308
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(String))
|
251
309
|
when :length
|
252
310
|
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Integer))
|
253
311
|
when :data
|
data/lib/atmos/request.rb
CHANGED
@@ -14,6 +14,43 @@ module Atmos
|
|
14
14
|
@http = options[:store].http
|
15
15
|
@tag = options[:default_tag]
|
16
16
|
end
|
17
|
+
|
18
|
+
|
19
|
+
#
|
20
|
+
# If there is no namespace option provided, we're using the object interface,
|
21
|
+
# otherwise we're using the namespace interface. Determine which it is, so we know
|
22
|
+
# which URI to use.
|
23
|
+
#
|
24
|
+
# If aoid is nil, we're creating a new object regardless.
|
25
|
+
#
|
26
|
+
def get_uri(action, options)
|
27
|
+
|
28
|
+
if (options.has_key?(:namespace) && action.has_key?(:namespace_uri))
|
29
|
+
|
30
|
+
# only two actions need namespace, so if the action doesn't have the
|
31
|
+
# :namespace_url key, we use the regular :object_url.
|
32
|
+
ns = (options[:namespace].start_with?('/')) ? options[:namespace][1..-1] : options[:namespace]
|
33
|
+
uri = action[:namespace_uri].sub(/:namespace/, ns)
|
34
|
+
|
35
|
+
else
|
36
|
+
|
37
|
+
uri = action[:object_uri]
|
38
|
+
|
39
|
+
# only need the following check/substution if the uri requires an id
|
40
|
+
if (!uri.index(':id').nil?)
|
41
|
+
|
42
|
+
if (options.has_key?(:id) && !options[:id].nil?)
|
43
|
+
uri = uri.sub(/:id/, options[:id])
|
44
|
+
else
|
45
|
+
raise Atmos::Exceptions::ArgumentException, "An id is required for this action. This is an internal error."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Atmos::LOG.info("uri: #{uri}")
|
51
|
+
uri
|
52
|
+
end
|
53
|
+
|
17
54
|
|
18
55
|
|
19
56
|
def do(actionname, options = {}, &block)
|
@@ -22,14 +59,10 @@ module Atmos
|
|
22
59
|
raise Atmos::Exceptions::InternalLibraryException,
|
23
60
|
"Invalid REST action: #{actionname}" if (REST[actionname].nil?)
|
24
61
|
|
25
|
-
action
|
26
|
-
uri
|
27
|
-
url
|
28
|
-
verb
|
29
|
-
|
30
|
-
if (options[:id].nil? && !action[:uri].index(':id').nil?)
|
31
|
-
raise Atmos::Exceptions::ArgumentException, "An id is required for this action (#{actionname})."
|
32
|
-
end
|
62
|
+
action = REST[actionname]
|
63
|
+
uri = get_uri(action, options)
|
64
|
+
url = URI::join(@baseurl.to_s, uri.to_s)
|
65
|
+
verb = action[:verb]
|
33
66
|
|
34
67
|
request = (verbs.include?(verb)) ? Net::HTTP.const_get(verb.to_s.capitalize).new(uri) : nil
|
35
68
|
raise Atmos::Exceptions::AtmosException,
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Atmos
|
2
|
+
class Response # :nodoc:
|
3
|
+
attr_reader :http_response
|
4
|
+
|
5
|
+
def initialize(response, action)
|
6
|
+
@http_response = response
|
7
|
+
@action = action
|
8
|
+
|
9
|
+
if (response.kind_of?(Net::HTTPServerError))
|
10
|
+
raise Atmos::Exceptions::ServerException, response.message
|
11
|
+
elsif (response.kind_of?(Net::HTTPClientError))
|
12
|
+
|
13
|
+
raise Atmos::Exceptions::AtmosException, "Atmos got a bad request. This is probably a problem in this library." if (response.kind_of?(Net::HTTPBadRequest))
|
14
|
+
|
15
|
+
Atmos::LOG.debug("#{response.class}")
|
16
|
+
Atmos::LOG.debug("#{response.body}")
|
17
|
+
|
18
|
+
Atmos::Parser::response_check_action_error(@action, response)
|
19
|
+
|
20
|
+
elsif (!response.kind_of?(Net::HTTPSuccess))
|
21
|
+
raise Atmos::Exceptions::NotImplementedException, "This library doesn't handle these kinds of responses: #{response}"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(header)
|
27
|
+
if (!self[header].nil?)
|
28
|
+
self[header].split(',').each do |elt|
|
29
|
+
if (!elt.index('=').nil?)
|
30
|
+
key,val = elt.split('=')
|
31
|
+
yield key.strip, val.strip
|
32
|
+
else
|
33
|
+
yield elt.strip
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def body
|
40
|
+
@http_response.body
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(sym, *args)
|
44
|
+
Atmos::LOG.info("method missing: #{sym}")
|
45
|
+
header = "x-emc-#{sym.id2name.sub(/_/, '-')}"
|
46
|
+
if (REST[@action][:return_headers].include?(header))
|
47
|
+
Atmos::LOG.info("header: #{header}")
|
48
|
+
rv = Atmos::Util.header2hash(header, @http_response[header])
|
49
|
+
Atmos::LOG.debug("rv: #{rv.inspect}")
|
50
|
+
return rv
|
51
|
+
else
|
52
|
+
return super.method_missing(sym, *args)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def delta
|
57
|
+
if (REST[@action][:return_headers].include?('x-emc-delta'))
|
58
|
+
@http_response['x-emc-delta']
|
59
|
+
else
|
60
|
+
raise "A #{@action} request doesn't return a delta."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def policy
|
65
|
+
if (REST[@action][:return_headers].include?('x-emc-policy'))
|
66
|
+
@http_response['x-emc-policy']
|
67
|
+
else
|
68
|
+
raise "A #{@action} request doesn't return a policy description."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def id
|
73
|
+
if (REST[@action][:return_headers].include?('location'))
|
74
|
+
@http_response['location'][@http_response['location'].rindex('/')+1..-1]
|
75
|
+
else
|
76
|
+
raise "A #{@action} request doesn't return an id."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def headers
|
81
|
+
headers = {}
|
82
|
+
@http_response.each do |header, value|
|
83
|
+
headers[header] = value
|
84
|
+
end
|
85
|
+
headers
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](header)
|
89
|
+
@http_response[header]
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
# while (response.kind_of?(Net::HTTPRedirection))
|
100
|
+
|
101
|
+
#From http://railstips.org/blog/archives/2009/03/04/following-redirects-with-nethttp/
|
102
|
+
# rurl = (response['location'].nil?) ? response.body.match(/<a href=\"([^>]+)\">/i)[1] : response['location']
|
103
|
+
|
104
|
+
# puts("Got a redirect \nfrom: #{options[:url]}\n to: #{rurl}")
|
105
|
+
|
106
|
+
# if rurl.start_with?('/')
|
107
|
+
# puts("Got a relative redirect url: #{options[:url]}")
|
108
|
+
# options[:url] = URI.parse("#{url.scheme}://#{url.host}#{redirect_url}")
|
109
|
+
# end
|
110
|
+
|
111
|
+
# options[:redirects] = (options[:redirects].nil?) ? 0 : options[:redirects] += 1
|
112
|
+
# response = self.generic_request(options)
|
113
|
+
# end
|
114
|
+
# raise "Too many redirects (#{options[:redirects]}): #{url}" if (!options[:redirects].nil? && (options[:redirects] > 3))
|
115
|
+
|
116
|
+
end
|
data/lib/atmos/rest.rb
CHANGED
@@ -1,62 +1,62 @@
|
|
1
1
|
module Atmos
|
2
2
|
REST = {
|
3
3
|
:listable_tags => { :verb => :get,
|
4
|
-
:
|
4
|
+
:object_uri => '/rest/objects?listabletags',
|
5
5
|
:required_headers => ['x-emc-tags'],
|
6
6
|
:optional_headers => ['x-emc-token'],
|
7
7
|
:http_response => 200,
|
8
8
|
:return_headers => ['x-emc-listable-tags'],
|
9
9
|
},
|
10
10
|
:set_group_acl => { :verb => :post,
|
11
|
-
:
|
11
|
+
:object_uri => '/rest/objects/:id?acl',
|
12
12
|
:required_headers => ['x-emc-groupacl'],
|
13
13
|
:http_response => 200,
|
14
14
|
},
|
15
15
|
:set_user_acl => { :verb => :post,
|
16
|
-
:
|
16
|
+
:object_uri => '/rest/objects/:id?acl',
|
17
17
|
:required_headers => ['x-emc-useracl'],
|
18
18
|
:http_response => 200,
|
19
19
|
},
|
20
20
|
:set_metadata => { :verb => :post,
|
21
|
-
:
|
21
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
22
22
|
:http_response => 200,
|
23
23
|
:required_headers => ['x-emc-meta'],
|
24
24
|
},
|
25
|
-
:set_listable_metadata => { :verb
|
26
|
-
:
|
25
|
+
:set_listable_metadata => { :verb => :post,
|
26
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
27
27
|
:http_response => 200,
|
28
28
|
:required_headers => ['x-emc-listable-meta'],
|
29
29
|
},
|
30
30
|
:delete_metadata => { :verb => :delete,
|
31
|
-
:
|
31
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
32
32
|
:http_response => 204,
|
33
33
|
:required_headers => ['x-emc-tags'],
|
34
34
|
},
|
35
35
|
:read_object => { :verb => :get,
|
36
|
-
:
|
36
|
+
:object_uri => '/rest/objects/:id',
|
37
37
|
:http_response => 200,
|
38
38
|
:optional_headers => ['Range'],
|
39
39
|
},
|
40
40
|
:update_object => { :verb => :put,
|
41
|
-
:
|
41
|
+
:object_uri => '/rest/objects/:id',
|
42
42
|
:http_response => 200,
|
43
43
|
:required_headers => ['Content-Type', 'Content-Length'],
|
44
44
|
:optional_headers => ['x-emc-useracl', 'x-emc-groupacl', 'Range'],
|
45
45
|
},
|
46
46
|
:trunc_object => { :verb => :put,
|
47
|
-
:
|
47
|
+
:object_uri => '/rest/objects/:id',
|
48
48
|
:http_response => 200,
|
49
49
|
:required_headers => ['Content-Length'],
|
50
50
|
:optional_headers => ['x-emc-useracl', 'x-emc-groupacl', 'Range'],
|
51
51
|
},
|
52
52
|
:get_service_info => { :verb => :get,
|
53
|
-
:
|
53
|
+
:object_uri => '/rest/service',
|
54
54
|
:response => :xml,
|
55
55
|
:http_response => 200,
|
56
56
|
},
|
57
57
|
|
58
58
|
:list_objects => { :verb => :get,
|
59
|
-
:
|
59
|
+
:object_uri => '/rest/objects',
|
60
60
|
:response => :xml,
|
61
61
|
:http_response => 200,
|
62
62
|
:required_headers => ['x-emc-tags', 'Content-Type'],
|
@@ -66,7 +66,8 @@ module Atmos
|
|
66
66
|
},
|
67
67
|
|
68
68
|
:create_object => { :verb => :post,
|
69
|
-
:
|
69
|
+
:namespace_uri => '/rest/namespace/:namespace',
|
70
|
+
:object_uri => '/rest/objects',
|
70
71
|
:http_response => 201,
|
71
72
|
:optional_headers => ['x-emc-wschecksum', 'x-emc-useracl', 'x-emc-groupacl', 'x-emc-meta', 'x-emc-listable-meta'],
|
72
73
|
:required_headers => ['Content-Type', 'Content-Length'],
|
@@ -78,8 +79,14 @@ module Atmos
|
|
78
79
|
'x-emc-listable-meta' => :listable_metadata},
|
79
80
|
},
|
80
81
|
|
82
|
+
:get_object_info => { :verb => :get,
|
83
|
+
:namespace_uri => '/rest/namespace/:namespace?info',
|
84
|
+
:object_uri => '/rest/objects/:id?info',
|
85
|
+
:http_response => 200,
|
86
|
+
:return_headers => ['x-emc-policy'],
|
87
|
+
},
|
81
88
|
:delete_object => { :verb => :delete,
|
82
|
-
:
|
89
|
+
:object_uri => '/rest/objects/:id',
|
83
90
|
:http_response => 204,
|
84
91
|
:required_headers => ['Content-Type'],
|
85
92
|
:return_headers => ['x-emc-delta', 'x-emc-policy'],
|
@@ -88,27 +95,27 @@ module Atmos
|
|
88
95
|
},
|
89
96
|
|
90
97
|
:list_system_metadata=> { :verb => :get,
|
91
|
-
:
|
98
|
+
:object_uri => '/rest/objects/:id?metadata/system',
|
92
99
|
:http_response => 200,
|
93
100
|
:return_headers => ['x-emc-meta'],
|
94
101
|
:errors => { '1003' => { :class => Atmos::Exceptions::NoSuchObjectException ,
|
95
102
|
:message => "Object not found to delete." } },
|
96
103
|
},
|
97
104
|
:list_metadata => { :verb => :get,
|
98
|
-
:
|
105
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
99
106
|
:http_response => 200,
|
100
107
|
:required_headers => ['Content-Type'],
|
101
108
|
:return_headers => ['x-emc-meta', 'x-emc-listable-meta'],
|
102
109
|
},
|
103
110
|
|
104
111
|
:list_tags => { :verb => :get,
|
105
|
-
:
|
112
|
+
:object_uri => '/rest/objects/:id?metadata/tags',
|
106
113
|
:http_response => 200,
|
107
114
|
:return_headers => ['x-emc-tags', 'x-emc-listable-tags'],
|
108
115
|
},
|
109
116
|
|
110
117
|
:list_acl => { :verb => :get,
|
111
|
-
:
|
118
|
+
:object_uri => '/rest/objects/:id?acl',
|
112
119
|
:http_response => 200,
|
113
120
|
:return_headers => ['x-emc-useracl', 'x-emc-groupacl'],
|
114
121
|
},
|
@@ -119,136 +126,20 @@ module Atmos
|
|
119
126
|
|
120
127
|
|
121
128
|
|
122
|
-
:delete_version => { :verb
|
123
|
-
:
|
124
|
-
:http_response
|
125
|
-
},
|
126
|
-
:get_object_info => { :verb => :get,
|
127
|
-
:uri => '/rest/objects/:id?info',
|
128
|
-
:http_response => 200,
|
129
|
+
:delete_version => { :verb => :delete,
|
130
|
+
:object_uri => '/rest/objects/:id?versions',
|
131
|
+
:http_response => 204,
|
129
132
|
},
|
130
|
-
:create_version => { :verb
|
131
|
-
:
|
132
|
-
:http_response
|
133
|
+
:create_version => { :verb => :post,
|
134
|
+
:object_uri => '/rest/objects/:id?versions' ,
|
135
|
+
:http_response => 201,
|
133
136
|
},
|
134
|
-
:list_versions => { :verb
|
135
|
-
|
137
|
+
:list_versions => { :verb => :get,
|
138
|
+
:object_uri => '/rest/objects/:id?versions',
|
139
|
+
:http_response => 200},
|
140
|
+
:restore_version => { :verb => :put,
|
141
|
+
:object_uri => '/rest/objects/:id?versions',
|
142
|
+
:http_response => 200},
|
136
143
|
} # :nodoc:
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
class Response # :nodoc:
|
141
|
-
attr_reader :http_response
|
142
|
-
|
143
|
-
def initialize(response, action)
|
144
|
-
@http_response = response
|
145
|
-
@action = action
|
146
|
-
|
147
|
-
if (response.kind_of?(Net::HTTPServerError))
|
148
|
-
raise Atmos::Exceptions::ServerException, response.message
|
149
|
-
elsif (response.kind_of?(Net::HTTPClientError))
|
150
|
-
|
151
|
-
raise Atmos::Exceptions::AtmosException, "Atmos got a bad request. This is probably a problem in this library." if (response.kind_of?(Net::HTTPBadRequest))
|
152
|
-
|
153
|
-
Atmos::LOG.debug("#{response.class}")
|
154
|
-
Atmos::LOG.debug("#{response.body}")
|
155
|
-
|
156
|
-
Atmos::Parser::response_check_action_error(@action, response)
|
157
|
-
|
158
|
-
elsif (!response.kind_of?(Net::HTTPSuccess))
|
159
|
-
raise Atmos::Exceptions::NotImplementedException, "This library doesn't handle these kinds of responses: #{response}"
|
160
|
-
end
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
def each(header)
|
165
|
-
if (!self[header].nil?)
|
166
|
-
self[header].split(',').each do |elt|
|
167
|
-
if (!elt.index('=').nil?)
|
168
|
-
key,val = elt.split('=')
|
169
|
-
yield key.strip, val.strip
|
170
|
-
else
|
171
|
-
yield elt.strip
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def body
|
178
|
-
@http_response.body
|
179
|
-
end
|
180
|
-
|
181
|
-
def method_missing(sym, *args)
|
182
|
-
Atmos::LOG.info("method missing: #{sym}")
|
183
|
-
header = "x-emc-#{sym.id2name.sub(/_/, '-')}"
|
184
|
-
if (REST[@action][:return_headers].include?(header))
|
185
|
-
Atmos::LOG.info("header: #{header}")
|
186
|
-
rv = Atmos::Util.header2hash(header, @http_response[header])
|
187
|
-
Atmos::LOG.debug("rv: #{rv.inspect}")
|
188
|
-
return rv
|
189
|
-
else
|
190
|
-
return super.method_missing(sym, *args)
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def delta
|
195
|
-
if (REST[@action][:return_headers].include?('x-emc-delta'))
|
196
|
-
@http_response['x-emc-delta']
|
197
|
-
else
|
198
|
-
raise "A #{@action} request doesn't return a delta."
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def policy
|
203
|
-
if (REST[@action][:return_headers].include?('x-emc-policy'))
|
204
|
-
@http_response['x-emc-policy']
|
205
|
-
else
|
206
|
-
raise "A #{@action} request doesn't return a policy description."
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def id
|
211
|
-
if (REST[@action][:return_headers].include?('location'))
|
212
|
-
@http_response['location'][@http_response['location'].rindex('/')+1..-1]
|
213
|
-
else
|
214
|
-
raise "A #{@action} request doesn't return an id."
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def headers
|
219
|
-
headers = {}
|
220
|
-
@http_response.each do |header, value|
|
221
|
-
headers[header] = value
|
222
|
-
end
|
223
|
-
headers
|
224
|
-
end
|
225
|
-
|
226
|
-
def [](header)
|
227
|
-
@http_response[header]
|
228
|
-
end
|
229
|
-
|
230
|
-
end
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
# while (response.kind_of?(Net::HTTPRedirection))
|
238
|
-
|
239
|
-
#From http://railstips.org/blog/archives/2009/03/04/following-redirects-with-nethttp/
|
240
|
-
# rurl = (response['location'].nil?) ? response.body.match(/<a href=\"([^>]+)\">/i)[1] : response['location']
|
241
|
-
|
242
|
-
# puts("Got a redirect \nfrom: #{options[:url]}\n to: #{rurl}")
|
243
|
-
|
244
|
-
# if rurl.start_with?('/')
|
245
|
-
# puts("Got a relative redirect url: #{options[:url]}")
|
246
|
-
# options[:url] = URI.parse("#{url.scheme}://#{url.host}#{redirect_url}")
|
247
|
-
# end
|
248
|
-
|
249
|
-
# options[:redirects] = (options[:redirects].nil?) ? 0 : options[:redirects] += 1
|
250
|
-
# response = self.generic_request(options)
|
251
|
-
# end
|
252
|
-
# raise "Too many redirects (#{options[:redirects]}): #{url}" if (!options[:redirects].nil? && (options[:redirects] > 3))
|
253
144
|
|
254
145
|
end
|
data/lib/atmos/store.rb
CHANGED
@@ -5,9 +5,8 @@ module Atmos
|
|
5
5
|
|
6
6
|
#
|
7
7
|
# Create and validate a connection to an Atmos server.
|
8
|
-
# This API
|
9
|
-
#
|
10
|
-
# against squid 2.7.STABLE9 on ubuntu 10.10.
|
8
|
+
# This API supports both the object interface and the namespace interface.
|
9
|
+
# Proxy support has been tested against squid 2.7.STABLE9 on ubuntu 10.10.
|
11
10
|
#
|
12
11
|
# If you have used Atmos Online this example will look familiar:
|
13
12
|
#
|
@@ -130,9 +129,10 @@ module Atmos
|
|
130
129
|
# * <tt>:mimetype</tt> - defaults to application/octet-stream
|
131
130
|
# * <tt>:data</tt> - a String or an IO stream
|
132
131
|
# * <tt>:length</tt> - a number (requires <tt>:data</tt>)
|
132
|
+
# * <tt>:namespace</tt> - name for namespace interface instead of object interface
|
133
133
|
#
|
134
134
|
def create(options = {})
|
135
|
-
Atmos::Object.new(self,
|
135
|
+
Atmos::Object.new(self, :create, options)
|
136
136
|
end
|
137
137
|
|
138
138
|
|
@@ -143,8 +143,19 @@ module Atmos
|
|
143
143
|
# The blob data is not loaded until
|
144
144
|
# accessed, so it can be progressively downloaded.
|
145
145
|
#
|
146
|
-
|
147
|
-
|
146
|
+
# Mutually exclusive, one required:
|
147
|
+
# * <tt>:id</tt> - the id of the object to retrieve
|
148
|
+
# * <tt>:namespace</tt> - the namespace key of the object to retrieve
|
149
|
+
def get(options)
|
150
|
+
|
151
|
+
# can't both be set, can't both be empty
|
152
|
+
if (!options[:id].nil? && !options[:namespace].nil?)
|
153
|
+
raise Atmos::Exceptions::ArgumentException, "One of :id, :namespace is required, but not both."
|
154
|
+
elsif (options[:id].nil? && options[:namespace].nil?)
|
155
|
+
raise Atmos::Exceptions::ArgumentException, "One of :id, :namespace is required."
|
156
|
+
end
|
157
|
+
|
158
|
+
Atmos::Object.new(self, :get, options)
|
148
159
|
end
|
149
160
|
|
150
161
|
|
data/lib/atmos/version.rb
CHANGED
data/test/suite.rb
CHANGED
@@ -4,6 +4,8 @@ require File.dirname(__FILE__) + '/test_util'
|
|
4
4
|
require File.dirname(__FILE__) + '/test_acl'
|
5
5
|
require File.dirname(__FILE__) + '/test_metadata'
|
6
6
|
require File.dirname(__FILE__) + '/test_object_create'
|
7
|
+
require File.dirname(__FILE__) + '/test_object_get'
|
8
|
+
require File.dirname(__FILE__) + '/test_object_read'
|
7
9
|
require File.dirname(__FILE__) + '/test_object_update'
|
8
10
|
require File.dirname(__FILE__) + '/test_object_misc'
|
9
11
|
require File.dirname(__FILE__) + '/test_store'
|
data/test/test_object_create.rb
CHANGED
@@ -22,7 +22,7 @@ class AtmosObjectCreateTest < Test::Unit::TestCase
|
|
22
22
|
$stdout.flush
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
25
|
+
def test_create_empty_object_via_object_interface
|
26
26
|
obj = @s.create
|
27
27
|
@cleanup.push(obj)
|
28
28
|
|
@@ -33,6 +33,21 @@ class AtmosObjectCreateTest < Test::Unit::TestCase
|
|
33
33
|
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
34
34
|
end
|
35
35
|
|
36
|
+
def test_create_empty_object_via_namespace_interface
|
37
|
+
time = Time.now()
|
38
|
+
time = time.to_i
|
39
|
+
ns = "/foo/bar#{time}"
|
40
|
+
|
41
|
+
obj = @s.create(:namespace => ns)
|
42
|
+
@cleanup.push(obj)
|
43
|
+
|
44
|
+
assert_not_nil(obj)
|
45
|
+
assert_equal({}, obj.metadata)
|
46
|
+
assert_equal({}, obj.listable_metadata)
|
47
|
+
assert_equal({Atmos::Test::Util.user => :full}, obj.user_acl)
|
48
|
+
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
49
|
+
end
|
50
|
+
|
36
51
|
def test_create_object_with_bad_option
|
37
52
|
assert_raise Atmos::Exceptions::ArgumentException do
|
38
53
|
@cleanup.push(@s.create(:tag => ['testtag']))
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'credentials'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'atmos'
|
4
|
+
|
5
|
+
|
6
|
+
class AtmosObjectGetTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def initialize(id)
|
9
|
+
super(id)
|
10
|
+
@cleanup = []
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@s = Atmos::Test::Util.static_store
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
@cleanup.each do |obj|
|
21
|
+
obj.delete
|
22
|
+
end
|
23
|
+
$stdout.flush
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_get_object_via_object_interface
|
27
|
+
obj = @s.create
|
28
|
+
@cleanup.push(obj)
|
29
|
+
|
30
|
+
obj = @s.get(:id => obj.aoid)
|
31
|
+
assert_not_nil(obj)
|
32
|
+
assert_equal({}, obj.metadata)
|
33
|
+
assert_equal({}, obj.listable_metadata)
|
34
|
+
assert_equal({Atmos::Test::Util.user => :full}, obj.user_acl)
|
35
|
+
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_get_object_via_namespace_interface
|
39
|
+
time = Time.now()
|
40
|
+
time = time.to_i#{}"%.6f" % a.to_f #=> "1195480202.282373"
|
41
|
+
|
42
|
+
ns = "/foo/bar#{time}"
|
43
|
+
obj = @s.create(:namespace => ns)
|
44
|
+
obj = @s.get(:namespace => ns)
|
45
|
+
|
46
|
+
assert_not_nil(obj)
|
47
|
+
assert_equal({}, obj.metadata)
|
48
|
+
assert_equal({}, obj.listable_metadata)
|
49
|
+
assert_equal({Atmos::Test::Util.user => :full}, obj.user_acl)
|
50
|
+
assert_equal({Atmos::Test::Util.group => :none}, obj.group_acl)
|
51
|
+
|
52
|
+
obj.delete()
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/test/test_object_misc.rb
CHANGED
@@ -40,7 +40,7 @@ class AtmosObjectMiscTest < Test::Unit::TestCase
|
|
40
40
|
def test_delete_nonexisting_object
|
41
41
|
first = @s.create
|
42
42
|
assert_not_nil(first)
|
43
|
-
second = @s.get(first.aoid)
|
43
|
+
second = @s.get(:id => first.aoid)
|
44
44
|
assert_not_nil(second)
|
45
45
|
first.delete
|
46
46
|
assert_raise Atmos::Exceptions::NoSuchObjectException do
|
data/test/test_object_read.rb
CHANGED
@@ -3,24 +3,24 @@ require 'test/unit'
|
|
3
3
|
require 'atmos'
|
4
4
|
|
5
5
|
|
6
|
-
class
|
6
|
+
class AtmosObjectReadTest < Test::Unit::TestCase
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
def initialize(id)
|
9
|
+
super(id)
|
10
|
+
@cleanup = []
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
def setup
|
14
|
+
@s = Atmos::Test::Util.static_store
|
15
|
+
@md = {'non' => 'listable', 'user' => 'metadata', 'a' => 'b'}
|
16
|
+
@lmd = {'x' => 'a', 'y' => 'b', 'z' => 'c'}
|
17
|
+
data = open(Atmos::Test::Util.small_binary_filename)
|
18
|
+
obj = @s.create(:metadata => @md,
|
19
|
+
:listable_metadata => @lmd,
|
20
|
+
:data => File.new(Atmos::Test::Util.small_binary_filename, "rb"))
|
21
|
+
@cleanup.push(obj)
|
22
|
+
@aoid = obj.aoid
|
23
|
+
end
|
24
24
|
|
25
25
|
|
26
26
|
def teardown
|
@@ -31,14 +31,10 @@ class AtmosObjectCreateTest < Test::Unit::TestCase
|
|
31
31
|
|
32
32
|
|
33
33
|
def test_read_partial_data_as_stream
|
34
|
-
|
35
|
-
|
36
|
-
# it doesn't end up empty when this file is run
|
37
|
-
# as a standalone test
|
34
|
+
obj = @s.get(:id => @aoid)
|
35
|
+
|
38
36
|
file = open(Atmos::Test::Util.small_binary_filename)
|
39
37
|
contents = file.read
|
40
|
-
obj = @s.get(@aoid)
|
41
|
-
obj.update(contents)
|
42
38
|
assert_equal(contents, obj.data)
|
43
39
|
|
44
40
|
f = File.new(Atmos::Test::Util.small_binary_filename, "rb")
|
@@ -57,13 +53,9 @@ class AtmosObjectCreateTest < Test::Unit::TestCase
|
|
57
53
|
|
58
54
|
def test_read_data_as_stream
|
59
55
|
|
60
|
-
# something is very strange here
|
61
|
-
# the object shouldn't end up empty, but it does
|
62
|
-
# it doesn't end up empty when this file is run
|
63
|
-
# as a standalone test
|
64
56
|
file = open(Atmos::Test::Util.small_binary_filename)
|
65
57
|
contents = file.read
|
66
|
-
obj = @s.get(@aoid)
|
58
|
+
obj = @s.get(:id => @aoid)
|
67
59
|
obj.update(contents)
|
68
60
|
assert_equal(contents, obj.data)
|
69
61
|
|
@@ -77,13 +69,9 @@ class AtmosObjectCreateTest < Test::Unit::TestCase
|
|
77
69
|
|
78
70
|
def test_read_partial_data_as_string
|
79
71
|
|
80
|
-
# something is very strange here
|
81
|
-
# the object shouldn't end up empty, but it does
|
82
|
-
# it doesn't end up empty when this file is run
|
83
|
-
# as a standalone test
|
84
72
|
file = open(Atmos::Test::Util.small_binary_filename)
|
85
73
|
contents = file.read
|
86
|
-
obj = @s.get(@aoid)
|
74
|
+
obj = @s.get(:id => @aoid)
|
87
75
|
obj.update(contents)
|
88
76
|
assert_equal(contents, obj.data)
|
89
77
|
|
@@ -97,13 +85,9 @@ class AtmosObjectCreateTest < Test::Unit::TestCase
|
|
97
85
|
end
|
98
86
|
|
99
87
|
def test_read_data_as_string
|
100
|
-
# something is very strange here
|
101
|
-
# the object shouldn't end up empty, but it does
|
102
|
-
# it doesn't end up empty when this file is run
|
103
|
-
# as a standalone test
|
104
88
|
file = open(Atmos::Test::Util.small_binary_filename)
|
105
89
|
contents = file.read
|
106
|
-
obj = @s.get(@aoid)
|
90
|
+
obj = @s.get(:id => @aoid)
|
107
91
|
obj.update(contents)
|
108
92
|
assert_equal(contents, obj.data)
|
109
93
|
|
data/test/test_object_update.rb
CHANGED
@@ -3,7 +3,7 @@ require 'test/unit'
|
|
3
3
|
require 'atmos'
|
4
4
|
|
5
5
|
|
6
|
-
class
|
6
|
+
class AtmosObjectUpdateTest < Test::Unit::TestCase
|
7
7
|
|
8
8
|
def initialize(id)
|
9
9
|
super(id)
|
@@ -14,6 +14,7 @@ class AtmosObjectCreateTest < Test::Unit::TestCase
|
|
14
14
|
@s = Atmos::Test::Util.static_store
|
15
15
|
@obj = @s.create
|
16
16
|
@cleanup.push(@obj)
|
17
|
+
#print "test_object_update.setup(): obj: #{@obj}; objid: #{@obj.aoid}\n"
|
17
18
|
end
|
18
19
|
|
19
20
|
|
@@ -3,7 +3,7 @@ require 'test/unit'
|
|
3
3
|
require File.dirname(__FILE__) + '/../lib/atmos'
|
4
4
|
|
5
5
|
|
6
|
-
class
|
6
|
+
class AtmosRequestTest < Test::Unit::TestCase
|
7
7
|
|
8
8
|
def dont_test_signature_calculation
|
9
9
|
headers = {}
|
@@ -44,7 +44,6 @@ class AtmosUtilTest < Test::Unit::TestCase
|
|
44
44
|
:secret => 'p97mki4AkiAyVi+/7QpdxFAU/jY=',
|
45
45
|
:unsupported => true
|
46
46
|
)
|
47
|
-
print "#{store.server_version}\n"
|
48
47
|
|
49
48
|
end
|
50
49
|
end
|
data/test/test_store.rb
CHANGED
@@ -112,11 +112,11 @@ class AtmosStoreTest < Test::Unit::TestCase
|
|
112
112
|
|
113
113
|
for i in (1...10)
|
114
114
|
obj = @s.create(:listable_metadata => {tag => true})
|
115
|
+
@cleanup.push(obj)
|
115
116
|
end
|
116
117
|
|
117
118
|
i = 0
|
118
119
|
@s.each_object_with_listable_tag(tag) do |obj|
|
119
|
-
@cleanup.push(obj)
|
120
120
|
i += 1
|
121
121
|
end
|
122
122
|
assert_equal(9, i)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-atmos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
-
|
9
|
-
|
10
|
-
version: 0.6.0
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Fleur Dragan
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2012-01-03 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -77,11 +77,13 @@ extra_rdoc_files:
|
|
77
77
|
- COPYING
|
78
78
|
files:
|
79
79
|
- Rakefile
|
80
|
+
- lib/atmos.rb
|
80
81
|
- lib/atmos/attributes.rb
|
81
82
|
- lib/atmos/exceptions.rb
|
82
83
|
- lib/atmos/object.rb
|
83
84
|
- lib/atmos/parser.rb
|
84
85
|
- lib/atmos/request.rb
|
86
|
+
- lib/atmos/response.rb
|
85
87
|
- lib/atmos/rest.rb
|
86
88
|
- lib/atmos/store.rb
|
87
89
|
- lib/atmos/util.rb
|
@@ -91,16 +93,17 @@ files:
|
|
91
93
|
- test/files/dragaf-tiny-from-vsphere.ova
|
92
94
|
- test/files/SmallImageForTest.iso
|
93
95
|
- test/files/something.txt
|
94
|
-
- test/request_test.rb
|
95
96
|
- test/suite.rb
|
96
97
|
- test/suite_noproxy.rb
|
97
98
|
- test/suite_proxy.rb
|
98
99
|
- test/test_acl.rb
|
99
100
|
- test/test_metadata.rb
|
100
101
|
- test/test_object_create.rb
|
102
|
+
- test/test_object_get.rb
|
101
103
|
- test/test_object_misc.rb
|
102
104
|
- test/test_object_read.rb
|
103
105
|
- test/test_object_update.rb
|
106
|
+
- test/test_request.rb
|
104
107
|
- test/test_store.rb
|
105
108
|
- test/test_util.rb
|
106
109
|
- README
|
@@ -150,15 +153,16 @@ test_files:
|
|
150
153
|
- test/files/dragaf-tiny-from-vsphere.ova
|
151
154
|
- test/files/SmallImageForTest.iso
|
152
155
|
- test/files/something.txt
|
153
|
-
- test/request_test.rb
|
154
156
|
- test/suite.rb
|
155
157
|
- test/suite_noproxy.rb
|
156
158
|
- test/suite_proxy.rb
|
157
159
|
- test/test_acl.rb
|
158
160
|
- test/test_metadata.rb
|
159
161
|
- test/test_object_create.rb
|
162
|
+
- test/test_object_get.rb
|
160
163
|
- test/test_object_misc.rb
|
161
164
|
- test/test_object_read.rb
|
162
165
|
- test/test_object_update.rb
|
166
|
+
- test/test_request.rb
|
163
167
|
- test/test_store.rb
|
164
168
|
- test/test_util.rb
|