fakes3 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -8,25 +8,38 @@ PATH
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
+ archive-tar-minitar (0.5.2)
11
12
  aws-s3 (0.6.2)
12
13
  builder
13
14
  mime-types
14
15
  xml-simple
15
- builder (2.1.2)
16
- mime-types (1.16)
17
- right_aws (2.0.0)
18
- right_http_connection (>= 1.2.1)
19
- right_http_connection (1.2.4)
20
- thor (0.14.4)
21
- xml-simple (1.0.12)
16
+ builder (3.0.0)
17
+ columnize (0.3.6)
18
+ linecache19 (0.5.12)
19
+ ruby_core_source (>= 0.1.4)
20
+ mime-types (1.18)
21
+ right_aws (3.0.4)
22
+ right_http_connection (>= 1.2.5)
23
+ right_http_connection (1.3.0)
24
+ ruby-debug-base19 (0.11.25)
25
+ columnize (>= 0.3.1)
26
+ linecache19 (>= 0.5.11)
27
+ ruby_core_source (>= 0.1.4)
28
+ ruby-debug19 (0.11.6)
29
+ columnize (>= 0.3.1)
30
+ linecache19 (>= 0.5.11)
31
+ ruby-debug-base19 (>= 0.11.19)
32
+ ruby_core_source (0.1.5)
33
+ archive-tar-minitar (>= 0.5.2)
34
+ thor (0.14.6)
35
+ xml-simple (1.1.1)
22
36
 
23
37
  PLATFORMS
24
38
  ruby
25
39
 
26
40
  DEPENDENCIES
27
41
  aws-s3
28
- builder
29
42
  bundler (>= 1.0.0)
30
43
  fakes3!
31
44
  right_aws
32
- thor
45
+ ruby-debug19
data/README.md CHANGED
@@ -3,7 +3,10 @@ FakeS3 is a lightweight server that responds to the same calls Amazon S3 respond
3
3
  It is extremely useful for testing of S3 in a sandbox environment without actually
4
4
  making calls to Amazon, which not only require network, but also cost you precious dollars.
5
5
 
6
- For now there is a basic file store backend.
6
+ The goal of Fake S3 is to minimize runtime dependencies and be more of a
7
+ development tool to test S3 calls in your code rather than a production server
8
+ looking to duplicate S3 functionality. Trying RiakCS, ParkPlace/Boardwalk, or
9
+ Ceph might be a place to start if that is your goal.
7
10
 
8
11
  FakeS3 doesn't support all of the S3 command set, but the basic ones like put, get,
9
12
  list, copy, and make bucket are supported. More coming soon.
@@ -20,20 +23,20 @@ To run a fakes3 server, you just specify a root and a port.
20
23
 
21
24
  Take a look at the test cases to see client example usage. For now, FakeS3 is
22
25
  mainly tested with s3cmd, aws-s3 gem, and right_aws. There are plenty more
23
- libraries out there, and please do mention other clients.
26
+ libraries out there, and please do mention if other clients work or not.
24
27
 
25
28
  ## Running Tests
26
- In order to run the tests add the following line to your /etc/hosts:
27
29
 
28
- 127.0.0.1 s3.localhost
29
-
30
- Then start the test server using
30
+ Start the test server using
31
31
 
32
32
  rake test_server
33
33
 
34
-
35
34
  Then in another terminal window run
36
35
 
37
36
  rake test
38
37
 
39
38
  It is a TODO to get this to be just one command
39
+
40
+ ## More Information
41
+
42
+ Check out the wiki https://github.com/jubos/fake-s3/wiki
data/Rakefile CHANGED
@@ -9,6 +9,7 @@ Rake::TestTask.new(:test) do |t|
9
9
  t.ruby_opts << '-I.'
10
10
  end
11
11
 
12
+ desc "Run the test_server"
12
13
  task :test_server do |t|
13
14
  system("bundle exec bin/fakes3 --port 10453 --root test_root")
14
15
  end
data/lib/fakes3/bucket.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'builder'
2
+ require 'thread'
2
3
  require 'fakes3/s3_object'
4
+ require 'fakes3/red_black_tree'
3
5
 
4
6
  module FakeS3
5
7
  class Bucket
@@ -8,10 +10,52 @@ module FakeS3
8
10
  def initialize(name,creation_date,objects)
9
11
  @name = name
10
12
  @creation_date = creation_date
11
- @objects = []
13
+ @objects = SortedSet.new
14
+ @objects = RedBlackTree.new
12
15
  objects.each do |obj|
13
- @objects << obj
16
+ @objects.add(obj)
14
17
  end
18
+ @mutex = Mutex.new
19
+ end
20
+
21
+ def <<(object)
22
+ # Unfortunately have to synchronize here since the RB Tree is not thread
23
+ # safe. Probably can get finer granularity if performance is important
24
+ @mutex.synchronize do
25
+ exists = @objects.search(object.name)
26
+ if exists.nil?
27
+ @objects.add(object.name,object)
28
+ end
29
+ end
30
+ end
31
+
32
+ def delete(object_name)
33
+ @objects.delete_via_key(object_name)
34
+ end
35
+
36
+ def query_for_range(options)
37
+ marker = options[:marker]
38
+ prefix = options[:prefix]
39
+ max_keys = options[:max_keys] || 1000
40
+ delimiter = options[:delimiter]
41
+
42
+ matches = []
43
+ is_truncated = false
44
+
45
+ @mutex.synchronize do
46
+ matches, is_truncated = @objects.search_for_range(
47
+ marker,prefix,max_keys,delimiter)
48
+ end
49
+
50
+ bq = BucketQuery.new
51
+ bq.bucket = self
52
+ bq.marker = marker
53
+ bq.prefix = prefix
54
+ bq.max_keys = max_keys
55
+ bq.delimiter = delimiter
56
+ bq.matches = matches
57
+ bq.is_truncated = is_truncated
58
+ return bq
15
59
  end
16
60
 
17
61
  end
@@ -0,0 +1,11 @@
1
+ module FakeS3
2
+ class BucketQuery
3
+ attr_accessor :prefix,:matches,:marker,:max_keys,
4
+ :delimiter,:bucket,:is_truncated
5
+
6
+ # Syntactic sugar
7
+ def is_truncated?
8
+ @is_truncated
9
+ end
10
+ end
11
+ end
@@ -112,9 +112,9 @@ module FakeS3
112
112
  return obj
113
113
  end
114
114
 
115
- def store_object(bucket,object,request)
115
+ def store_object(bucket,object_name,request)
116
116
  begin
117
- filename = File.join(@root,bucket,object)
117
+ filename = File.join(@root,bucket.name,object_name)
118
118
  FileUtils.mkdir_p(filename)
119
119
 
120
120
  metadata_dir = File.join(filename,SHUCK_METADATA_DIR)
@@ -124,6 +124,7 @@ module FakeS3
124
124
  metadata = File.join(filename,SHUCK_METADATA_DIR,"metadata")
125
125
 
126
126
  md5 = Digest::MD5.new
127
+ # TODO put a tmpfile here first and mv it over at the end
127
128
 
128
129
  File.open(content,'wb') do |f|
129
130
  request.body do |chunk|
@@ -139,9 +140,14 @@ module FakeS3
139
140
  File.open(metadata,'w') do |f|
140
141
  f << YAML::dump(metadata_struct)
141
142
  end
143
+
142
144
  obj = S3Object.new
145
+ obj.name = object_name
143
146
  obj.md5 = metadata_struct[:md5]
144
147
  obj.content_type = metadata_struct[:content_type]
148
+
149
+ # TODO need a semaphore here since bucket is not probably not thread safe
150
+ bucket << obj
145
151
  return obj
146
152
  rescue
147
153
  puts $!
@@ -149,5 +155,17 @@ module FakeS3
149
155
  return nil
150
156
  end
151
157
  end
158
+
159
+ def delete_object(bucket,object_name,request)
160
+ begin
161
+ filename = File.join(@root,bucket.name,object_name)
162
+ FileUtils.rm_rf(filename)
163
+ bucket.delete(object_name)
164
+ rescue
165
+ puts $!
166
+ $!.backtrace.each { |line| puts line }
167
+ return nil
168
+ end
169
+ end
152
170
  end
153
171
  end
@@ -0,0 +1,395 @@
1
+ # Algorithm based on "Introduction to Algorithms" by Cormen and others
2
+ # Retrieved from
3
+ # https://github.com/headius/redblack/blob/master/red_black_tree.rb
4
+ #
5
+ # Modified by Curtis Spencer
6
+ class RedBlackTree
7
+ class Node
8
+ attr_accessor :color
9
+ attr_accessor :key
10
+ attr_accessor :left
11
+ attr_accessor :right
12
+ attr_accessor :parent
13
+ attr_accessor :value
14
+
15
+ RED = :red
16
+ BLACK = :black
17
+ COLORS = [RED, BLACK].freeze
18
+
19
+ def initialize(key, value, color = RED)
20
+ raise ArgumentError, "Bad value for color parameter" unless COLORS.include?(color)
21
+ @color = color
22
+ @key = key
23
+ @value = value
24
+ @left = @right = @parent = NilNode.instance
25
+ end
26
+
27
+ def black?
28
+ return color == BLACK
29
+ end
30
+
31
+ def red?
32
+ return color == RED
33
+ end
34
+ end
35
+
36
+ class NilNode < Node
37
+ class << self
38
+ private :new
39
+ @instance = nil
40
+
41
+ # it's not thread safe
42
+ def instance
43
+ if @instance.nil?
44
+ @instance = new
45
+
46
+ def instance
47
+ return @instance
48
+ end
49
+ end
50
+
51
+ return @instance
52
+ end
53
+ end
54
+
55
+ def initialize
56
+ self.color = BLACK
57
+ self.key = 0
58
+ self.left = nil
59
+ self.right = nil
60
+ self.parent = nil
61
+ end
62
+
63
+ def nil?
64
+ return true
65
+ end
66
+ end
67
+
68
+ include Enumerable
69
+
70
+ attr_accessor :root
71
+ attr_accessor :size
72
+
73
+ def initialize
74
+ self.root = NilNode.instance
75
+ self.size = 0
76
+ end
77
+
78
+ def add(key,value)
79
+ insert(Node.new(key,value))
80
+ end
81
+
82
+ def insert(x)
83
+ insert_helper(x)
84
+
85
+ x.color = Node::RED
86
+ while x != root && x.parent.color == Node::RED
87
+ if x.parent == x.parent.parent.left
88
+ y = x.parent.parent.right
89
+ if !y.nil? && y.color == Node::RED
90
+ x.parent.color = Node::BLACK
91
+ y.color = Node::BLACK
92
+ x.parent.parent.color = Node::RED
93
+ x = x.parent.parent
94
+ else
95
+ if x == x.parent.right
96
+ x = x.parent
97
+ left_rotate(x)
98
+ end
99
+ x.parent.color = Node::BLACK
100
+ x.parent.parent.color = Node::RED
101
+ right_rotate(x.parent.parent)
102
+ end
103
+ else
104
+ y = x.parent.parent.left
105
+ if !y.nil? && y.color == Node::RED
106
+ x.parent.color = Node::BLACK
107
+ y.color = Node::BLACK
108
+ x.parent.parent.color = Node::RED
109
+ x = x.parent.parent
110
+ else
111
+ if x == x.parent.left
112
+ x = x.parent
113
+ right_rotate(x)
114
+ end
115
+ x.parent.color = Node::BLACK
116
+ x.parent.parent.color = Node::RED
117
+ left_rotate(x.parent.parent)
118
+ end
119
+ end
120
+ end
121
+ root.color = Node::BLACK
122
+ end
123
+
124
+ alias << insert
125
+
126
+ def delete(z)
127
+ y = (z.left.nil? || z.right.nil?) ? z : successor(z)
128
+ x = y.left.nil? ? y.right : y.left
129
+ x.parent = y.parent
130
+
131
+ if y.parent.nil?
132
+ self.root = x
133
+ else
134
+ if y == y.parent.left
135
+ y.parent.left = x
136
+ else
137
+ y.parent.right = x
138
+ end
139
+ end
140
+
141
+ z.key = y.key if y != z
142
+
143
+ if y.color == Node::BLACK
144
+ delete_fixup(x)
145
+ end
146
+
147
+ self.size -= 1
148
+ return y
149
+ end
150
+
151
+ def minimum(x = root)
152
+ while !x.left.nil?
153
+ x = x.left
154
+ end
155
+ return x
156
+ end
157
+
158
+ def maximum(x = root)
159
+ while !x.right.nil?
160
+ x = x.right
161
+ end
162
+ return x
163
+ end
164
+
165
+ def successor(x)
166
+ if !x.right.nil?
167
+ return minimum(x.right)
168
+ end
169
+ y = x.parent
170
+ while !y.nil? && x == y.right
171
+ x = y
172
+ y = y.parent
173
+ end
174
+ return y
175
+ end
176
+
177
+ def predecessor(x)
178
+ if !x.left.nil?
179
+ return maximum(x.left)
180
+ end
181
+ y = x.parent
182
+ while !y.nil? && x == y.left
183
+ x = y
184
+ y = y.parent
185
+ end
186
+ return y
187
+ end
188
+
189
+ def inorder_walk(x = root)
190
+ x = self.minimum
191
+ while !x.nil?
192
+ yield x.key
193
+ x = successor(x)
194
+ end
195
+ end
196
+
197
+ alias each inorder_walk
198
+
199
+ def reverse_inorder_walk(x = root)
200
+ x = self.maximum
201
+ while !x.nil?
202
+ yield x.key
203
+ x = predecessor(x)
204
+ end
205
+ end
206
+
207
+ alias reverse_each reverse_inorder_walk
208
+
209
+ def search(key, x = root)
210
+ while !x.nil? && x.key != key
211
+ key < x.key ? x = x.left : x = x.right
212
+ end
213
+ return x
214
+ end
215
+
216
+ # Search for a successor even if the key doesn't exist
217
+ def search_for_possible_successor(key, x = root)
218
+ node = search(key)
219
+ pseudo = false
220
+ if node.class == NilNode
221
+ insert(Node.new(key,nil))
222
+ node = search(key)
223
+ pseudo = true
224
+ end
225
+
226
+ succ = successor(node)
227
+ delete(node) if pseudo
228
+ return nil if !succ
229
+ return succ
230
+ end
231
+
232
+ def delete_via_key(key)
233
+ node = search(key)
234
+ return if node.class == NilNode
235
+ delete(node)
236
+ end
237
+
238
+ # Return array of matches and a boolean to denote whether truncation occurred
239
+ def search_for_range(marker,prefix,max_keys,delimiter)
240
+ succ = nil
241
+ if marker
242
+ succ = search_for_possible_successor(marker)
243
+ else
244
+ succ = self.minimum
245
+ end
246
+
247
+ return [],false if succ.class == NilNode
248
+
249
+ keys_count = 0
250
+ keys = []
251
+ is_truncated = false
252
+
253
+ loop do
254
+ if !prefix or succ.key.index(prefix) == 0
255
+ keys_count += 1
256
+ if keys_count <= max_keys
257
+ keys << succ.value
258
+ else
259
+ is_truncated = true
260
+ break
261
+ end
262
+ end
263
+ succ = successor(succ)
264
+ if succ.class == NilNode
265
+ break
266
+ end
267
+ end
268
+
269
+ return keys,is_truncated
270
+ end
271
+
272
+ def empty?
273
+ return self.root.nil?
274
+ end
275
+
276
+ def black_height(x = root)
277
+ height = 0
278
+ while !x.nil?
279
+ x = x.left
280
+ height +=1 if x.nil? || x.black?
281
+ end
282
+ return height
283
+ end
284
+
285
+ private
286
+
287
+ def left_rotate(x)
288
+ raise "x.right is nil!" if x.right.nil?
289
+ y = x.right
290
+ x.right = y.left
291
+ y.left.parent = x if !y.left.nil?
292
+ y.parent = x.parent
293
+ if x.parent.nil?
294
+ self.root = y
295
+ else
296
+ if x == x.parent.left
297
+ x.parent.left = y
298
+ else
299
+ x.parent.right = y
300
+ end
301
+ end
302
+ y.left = x
303
+ x.parent = y
304
+ end
305
+
306
+ def right_rotate(x)
307
+ raise "x.left is nil!" if x.left.nil?
308
+ y = x.left
309
+ x.left = y.right
310
+ y.right.parent = x if !y.right.nil?
311
+ y.parent = x.parent
312
+ if x.parent.nil?
313
+ self.root = y
314
+ else
315
+ if x == x.parent.left
316
+ x.parent.left = y
317
+ else
318
+ x.parent.right = y
319
+ end
320
+ end
321
+ y.right = x
322
+ x.parent = y
323
+ end
324
+
325
+ def insert_helper(z)
326
+ y = NilNode.instance
327
+ x = root
328
+ while !x.nil?
329
+ y = x
330
+ z.key < x.key ? x = x.left : x = x.right
331
+ end
332
+ z.parent = y
333
+ if y.nil?
334
+ self.root = z
335
+ else
336
+ z.key < y.key ? y.left = z : y.right = z
337
+ end
338
+ self.size += 1
339
+ end
340
+
341
+ def delete_fixup(x)
342
+ while x != root && x.color == Node::BLACK
343
+ if x == x.parent.left
344
+ w = x.parent.right
345
+ if w.color == Node::RED
346
+ w.color = Node::BLACK
347
+ x.parent.color = Node::RED
348
+ left_rotate(x.parent)
349
+ w = x.parent.right
350
+ end
351
+ if w.left.color == Node::BLACK && w.right.color == Node::BLACK
352
+ w.color = Node::RED
353
+ x = x.parent
354
+ else
355
+ if w.right.color == Node::BLACK
356
+ w.left.color = Node::BLACK
357
+ w.color = Node::RED
358
+ right_rotate(w)
359
+ w = x.parent.right
360
+ end
361
+ w.color = x.parent.color
362
+ x.parent.color = Node::BLACK
363
+ w.right.color = Node::BLACK
364
+ left_rotate(x.parent)
365
+ x = root
366
+ end
367
+ else
368
+ w = x.parent.left
369
+ if w.color == Node::RED
370
+ w.color = Node::BLACK
371
+ x.parent.color = Node::RED
372
+ right_rotate(x.parent)
373
+ w = x.parent.left
374
+ end
375
+ if w.right.color == Node::BLACK && w.left.color == Node::BLACK
376
+ w.color = Node::RED
377
+ x = x.parent
378
+ else
379
+ if w.left.color == Node::BLACK
380
+ w.right.color = Node::BLACK
381
+ w.color = Node::RED
382
+ left_rotate(w)
383
+ w = x.parent.left
384
+ end
385
+ w.color = x.parent.color
386
+ x.parent.color = Node::BLACK
387
+ w.left.color = Node::BLACK
388
+ right_rotate(x.parent)
389
+ x = root
390
+ end
391
+ end
392
+ end
393
+ x.color = Node::BLACK
394
+ end
395
+ end
@@ -1,5 +1,11 @@
1
1
  module FakeS3
2
2
  class S3Object
3
+ include Comparable
3
4
  attr_accessor :name,:size,:creation_date,:md5,:io,:content_type
5
+
6
+ # Sort by the object's name
7
+ def <=>(object)
8
+ @name <=> object.name
9
+ end
4
10
  end
5
11
  end
data/lib/fakes3/server.rb CHANGED
@@ -1,21 +1,27 @@
1
1
  require 'webrick'
2
2
  require 'fakes3/file_store'
3
3
  require 'fakes3/xml_adapter'
4
+ require 'fakes3/bucket_query'
5
+ require 'fakes3/unsupported_operation'
4
6
 
5
7
  module FakeS3
6
8
  class Request
7
9
  CREATE_BUCKET = "CREATE_BUCKET"
8
10
  LIST_BUCKETS = "LIST_BUCKETS"
9
11
  LS_BUCKET = "LS_BUCKET"
12
+ HEAD = "HEAD"
10
13
  STORE = "STORE"
11
14
  COPY = "COPY"
12
15
  GET = "GET"
13
16
  GET_ACL = "GET_ACL"
14
17
  SET_ACL = "SET_ACL"
15
18
  MOVE = "MOVE"
16
- DELETE = "DELETE"
19
+ DELETE_OBJECT = "DELETE_OBJECT"
20
+ DELETE_BUCKET = "DELETE_BUCKET"
17
21
 
18
- attr_accessor :bucket,:object,:type,:src_bucket,:src_object,:method,:webrick_request,:path,:is_path_style
22
+ attr_accessor :bucket,:object,:type,:src_bucket,
23
+ :src_object,:method,:webrick_request,
24
+ :path,:is_path_style,:query,:http_verb
19
25
 
20
26
  def inspect
21
27
  puts "-----Inspect FakeS3 Request"
@@ -26,6 +32,7 @@ module FakeS3
26
32
  puts "Object: #{@object}"
27
33
  puts "Src Bucket: #{@src_bucket}"
28
34
  puts "Src Object: #{@src_object}"
35
+ puts "Query: #{@query}"
29
36
  puts "-----Done"
30
37
  end
31
38
  end
@@ -51,8 +58,15 @@ module FakeS3
51
58
  bucket_obj = @store.get_bucket(s_req.bucket)
52
59
  if bucket_obj
53
60
  response.status = 200
54
- response.body = XmlAdapter.bucket(bucket_obj)
55
61
  response['Content-Type'] = "application/xml"
62
+ query = {
63
+ :marker => s_req.query["marker"] ? s_req.query["marker"].to_s : nil,
64
+ :prefix => s_req.query["prefix"] ? s_req.query["prefix"].to_s : nil,
65
+ :max_keys => s_req.query["max_keys"] ? s_req.query["max_keys"].to_s : nil,
66
+ :delimiter => s_req.query["delimiter"] ? s_req.query["delimiter"].to_s : nil
67
+ }
68
+ bq = bucket_obj.query_for_range(query)
69
+ response.body = XmlAdapter.bucket_query(bq)
56
70
  else
57
71
  response.status = 404
58
72
  response.body = XmlAdapter.error_no_such_bucket(s_req.bucket)
@@ -98,19 +112,23 @@ module FakeS3
98
112
  end
99
113
  end
100
114
  response['Content-Length'] = File::Stat.new(real_obj.io.path).size
101
- response.body = real_obj.io
115
+ if s_req.http_verb == 'HEAD'
116
+ response.body = ""
117
+ else
118
+ response.body = real_obj.io
119
+ end
102
120
  end
103
121
  end
104
122
 
105
123
  def do_PUT(request,response)
106
124
  s_req = normalize_request(request)
107
125
 
108
-
109
126
  case s_req.type
110
127
  when Request::COPY
111
128
  @store.copy_object(s_req.src_bucket,s_req.src_object,s_req.bucket,s_req.object)
112
129
  when Request::STORE
113
- real_obj = @store.store_object(s_req.bucket,s_req.object,s_req.webrick_request)
130
+ bucket_obj = @store.get_bucket(s_req.bucket)
131
+ real_obj = @store.store_object(bucket_obj,s_req.object,s_req.webrick_request)
114
132
  response['Etag'] = real_obj.md5
115
133
  when Request::CREATE_BUCKET
116
134
  @store.create_bucket(s_req.bucket)
@@ -126,11 +144,47 @@ module FakeS3
126
144
  end
127
145
 
128
146
  def do_DELETE(request,response)
129
- p request
147
+ s_req = normalize_request(request)
148
+
149
+ case s_req.type
150
+ when Request::DELETE_OBJECT
151
+ bucket_obj = @store.get_bucket(s_req.bucket)
152
+ @store.delete_object(bucket_obj,s_req.object,s_req.webrick_request)
153
+ end
154
+
155
+ response.status = 204
156
+ response.body = ""
130
157
  end
131
158
 
132
159
  private
133
160
 
161
+ def normalize_delete(webrick_req,s_req)
162
+ path = webrick_req.path
163
+ path_len = path.size
164
+ query = webrick_req.query
165
+ if path == "/" and s_req.is_path_style
166
+ # Probably do a 404 here
167
+ else
168
+ if s_req.is_path_style
169
+ elems = path[1,path_len].split("/")
170
+ s_req.bucket = elems[0]
171
+ else
172
+ elems = path.split("/")
173
+ end
174
+
175
+ if elems.size == 0
176
+ raise UnsupportedOperation
177
+ elsif elems.size == 1
178
+ s_req.type = Request::DELETE_BUCKET
179
+ s_req.query = query
180
+ else
181
+ s_req.type = Request::DELETE_OBJECT
182
+ object = elems[1,elems.size].join('/')
183
+ s_req.object = object
184
+ end
185
+ end
186
+ end
187
+
134
188
  def normalize_get(webrick_req,s_req)
135
189
  path = webrick_req.path
136
190
  path_len = path.size
@@ -150,6 +204,7 @@ module FakeS3
150
204
  s_req.type = Request::LIST_BUCKETS
151
205
  elsif elems.size == 1
152
206
  s_req.type = Request::LS_BUCKET
207
+ s_req.query = query
153
208
  else
154
209
  if query["acl"] == ""
155
210
  s_req.type = Request::GET_ACL
@@ -219,11 +274,15 @@ module FakeS3
219
274
  s_req.is_path_style = false
220
275
  end
221
276
 
277
+ s_req.http_verb = webrick_req.request_method
278
+
222
279
  case webrick_req.request_method
223
280
  when 'PUT'
224
281
  normalize_put(webrick_req,s_req)
225
- when 'GET'
282
+ when 'GET','HEAD'
226
283
  normalize_get(webrick_req,s_req)
284
+ when 'DELETE'
285
+ normalize_delete(webrick_req,s_req)
227
286
  else
228
287
  raise "Unknown Request"
229
288
  end
@@ -0,0 +1,4 @@
1
+ module FakeS3
2
+ class UnsupportedOperation < RuntimeError
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module FakeS3
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -28,9 +28,10 @@ module FakeS3
28
28
  #<Error>
29
29
  # <Code>NoSuchKey</Code>
30
30
  # <Message>The resource you requested does not exist</Message>
31
- # <Resource>/mybucket/myfoto.jpg</Resource>
31
+ # <Resource>/mybucket/myfoto.jpg</Resource>
32
32
  # <RequestId>4442587FB7D0A2F9</RequestId>
33
- #</Error>
33
+ #</Error>
34
+ #
34
35
  def self.error_no_such_bucket(name)
35
36
  output = ""
36
37
  xml = Builder::XmlMarkup.new(:target => output)
@@ -72,6 +73,54 @@ module FakeS3
72
73
  output
73
74
  end
74
75
 
76
+ # A bucket query gives back the bucket along with contents
77
+ # <Contents>
78
+ #<Key>Nelson</Key>
79
+ # <LastModified>2006-01-01T12:00:00.000Z</LastModified>
80
+ # <ETag>&quot;828ef3fdfa96f00ad9f27c383fc9ac7f&quot;</ETag>
81
+ # <Size>5</Size>
82
+ # <StorageClass>STANDARD</StorageClass>
83
+ # <Owner>
84
+ # <ID>bcaf161ca5fb16fd081034f</ID>
85
+ # <DisplayName>webfile</DisplayName>
86
+ # </Owner>
87
+ # </Contents>
88
+
89
+ def self.append_objects_to_list_bucket_result(lbr,objects)
90
+ return if objects.nil? or objects.size == 0
91
+
92
+ objects.each do |s3_object|
93
+ lbr.Contents { |contents|
94
+ contents.Key(s3_object.name)
95
+ contents.LastModifed(s3_object.creation_date)
96
+ contents.ETag("\"#{s3_object.md5}\"")
97
+ contents.Size(s3_object.size)
98
+ contents.StorageClass("STANDARD")
99
+
100
+ contents.Owner { |owner|
101
+ owner.ID("abc")
102
+ owner.DisplayName("You")
103
+ }
104
+ }
105
+ end
106
+ end
107
+
108
+ def self.bucket_query(bucket_query)
109
+ output = ""
110
+ bucket = bucket_query.bucket
111
+ xml = Builder::XmlMarkup.new(:target => output)
112
+ xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
113
+ xml.ListBucketResult(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |lbr|
114
+ lbr.Name(bucket.name)
115
+ lbr.Prefix(bucket_query.prefix)
116
+ lbr.Marker(bucket_query.marker)
117
+ lbr.MaxKeys(bucket_query.max_keys)
118
+ lbr.IsTruncated(bucket_query.is_truncated?)
119
+ append_objects_to_list_bucket_result(lbr,bucket_query.matches)
120
+ }
121
+ output
122
+ end
123
+
75
124
  # ACL xml
76
125
  def self.acl(object = nil)
77
126
  output = ""
@@ -8,7 +8,7 @@ class RightAWSCommandsTest < Test::Unit::TestCase
8
8
  def setup
9
9
  @s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX',
10
10
  {:multi_thread => false, :server => 'localhost',
11
- :port => 10453, :protocol => 'http',:logger => Logger.new("/dev/null") })
11
+ :port => 10453, :protocol => 'http',:logger => Logger.new("/dev/null"),:no_subdomains => true })
12
12
  end
13
13
 
14
14
  def teardown
@@ -15,44 +15,44 @@ class S3CommandsTest < Test::Unit::TestCase
15
15
  end
16
16
 
17
17
  def test_create_bucket
18
- bucket = Bucket.create("mybucket")
18
+ bucket = Bucket.create("ruby_aws_s3")
19
19
  assert_not_nil bucket
20
20
  end
21
21
 
22
22
  def test_store
23
- bucket = Bucket.create("mybucket")
24
- S3Object.store("hello","world","mybucket")
23
+ bucket = Bucket.create("ruby_aws_s3")
24
+ S3Object.store("hello","world","ruby_aws_s3")
25
25
 
26
26
  output = ""
27
- obj = S3Object.stream("hello","mybucket") do |chunk|
27
+ obj = S3Object.stream("hello","ruby_aws_s3") do |chunk|
28
28
  output << chunk
29
29
  end
30
30
  assert_equal "world", output
31
31
  end
32
32
 
33
33
  def test_large_store
34
- bucket = Bucket.create("mybucket")
34
+ bucket = Bucket.create("ruby_aws_s3")
35
35
  buffer = ""
36
36
  500000.times do
37
37
  buffer << "#{(rand * 100).to_i}"
38
38
  end
39
39
 
40
40
  buf_len = buffer.length
41
- S3Object.store("big",buffer,"mybucket")
41
+ S3Object.store("big",buffer,"ruby_aws_s3")
42
42
 
43
43
  output = ""
44
- S3Object.stream("big","mybucket") do |chunk|
44
+ S3Object.stream("big","ruby_aws_s3") do |chunk|
45
45
  output << chunk
46
46
  end
47
47
  assert_equal buf_len,output.size
48
48
  end
49
49
 
50
50
  def test_multi_directory
51
- bucket = Bucket.create("mybucket")
52
- S3Object.store("dir/myfile/123.txt","recursive","mybucket")
51
+ bucket = Bucket.create("ruby_aws_s3")
52
+ S3Object.store("dir/myfile/123.txt","recursive","ruby_aws_s3")
53
53
 
54
54
  output = ""
55
- obj = S3Object.stream("dir/myfile/123.txt","mybucket") do |chunk|
55
+ obj = S3Object.stream("dir/myfile/123.txt","ruby_aws_s3") do |chunk|
56
56
  output << chunk
57
57
  end
58
58
  assert_equal "recursive", output
@@ -66,4 +66,62 @@ class S3CommandsTest < Test::Unit::TestCase
66
66
  assert_equal AWS::S3::NoSuchBucket,$!.class
67
67
  end
68
68
  end
69
+
70
+ def test_find_object
71
+ bucket = Bucket.create('find_bucket')
72
+ obj_name = 'short'
73
+ S3Object.store(obj_name,'short_text','find_bucket')
74
+ short = S3Object.find(obj_name,"find_bucket")
75
+ assert_not_nil(short)
76
+ assert_equal(short.value,'short_text')
77
+ end
78
+
79
+ def test_find_non_existent_object
80
+ bucket = Bucket.create('find_bucket')
81
+ obj_name = 'doesnotexist'
82
+ assert_raise AWS::S3::NoSuchKey do
83
+ should_throw = S3Object.find(obj_name,"find_bucket")
84
+ end
85
+
86
+ # Try something higher in the alphabet
87
+ assert_raise AWS::S3::NoSuchKey do
88
+ should_throw = S3Object.find("zzz","find_bucket")
89
+ end
90
+ end
91
+
92
+ def test_exists?
93
+ bucket = Bucket.create('ruby_aws_s3')
94
+ obj_name = 'dir/myfile/exists.txt'
95
+ S3Object.store(obj_name,'exists','ruby_aws_s3')
96
+ assert S3Object.exists?(obj_name, 'ruby_aws_s3')
97
+ assert !S3Object.exists?('dir/myfile/doesnotexist.txt','ruby_aws_s3')
98
+ end
99
+
100
+ def test_delete
101
+ bucket = Bucket.create("ruby_aws_s3")
102
+ S3Object.store("something_to_delete","asdf","ruby_aws_s3")
103
+ something = S3Object.find("something_to_delete","ruby_aws_s3")
104
+ S3Object.delete("something_to_delete","ruby_aws_s3")
105
+ end
106
+
107
+ def test_larger_lists
108
+ Bucket.create("ruby_aws_s3_many")
109
+ (0..100).each do |i|
110
+ ('a'..'z').each do |letter|
111
+ name = "#{letter}#{i}"
112
+ S3Object.store(name,"asdf","ruby_aws_s3_many")
113
+ end
114
+ end
115
+
116
+ bucket = Bucket.find("ruby_aws_s3_many")
117
+ assert_equal(bucket.objects.first.key,"a0")
118
+ assert_equal(bucket.size,1000)
119
+ end
120
+
121
+ # Copying an object
122
+ #S3Object.copy 'headshot.jpg', 'headshot2.jpg', 'photos'
123
+
124
+ # Renaming an object
125
+ #S3Object.rename 'headshot.jpg', 'portrait.jpg', 'photos'
126
+
69
127
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Curtis Spencer
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-04-13 00:00:00 -07:00
17
+ date: 2012-04-16 00:00:00 -06:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -103,11 +103,14 @@ files:
103
103
  - fakes3.gemspec
104
104
  - lib/fakes3.rb
105
105
  - lib/fakes3/bucket.rb
106
+ - lib/fakes3/bucket_query.rb
106
107
  - lib/fakes3/cli.rb
107
108
  - lib/fakes3/file_store.rb
108
109
  - lib/fakes3/rate_limitable_file.rb
110
+ - lib/fakes3/red_black_tree.rb
109
111
  - lib/fakes3/s3_object.rb
110
112
  - lib/fakes3/server.rb
113
+ - lib/fakes3/unsupported_operation.rb
111
114
  - lib/fakes3/version.rb
112
115
  - lib/fakes3/xml_adapter.rb
113
116
  - test/local_s3_cfg