fakes3 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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