| OLD | NEW |
| (Empty) |
| 1 # Copyright 2010 Google Inc. | |
| 2 # | |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a | |
| 4 # copy of this software and associated documentation files (the | |
| 5 # "Software"), to deal in the Software without restriction, including | |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- | |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit | |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- | |
| 9 # lowing conditions: | |
| 10 # | |
| 11 # The above copyright notice and this permission notice shall be included | |
| 12 # in all copies or substantial portions of the Software. | |
| 13 # | |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| 20 # IN THE SOFTWARE. | |
| 21 | |
| 22 """ | |
| 23 Provides basic mocks of core storage service classes, for unit testing: | |
| 24 ACL, Key, Bucket, Connection, and StorageUri. We implement a subset of | |
| 25 the interfaces defined in the real boto classes, but don't handle most | |
| 26 of the optional params (which we indicate with the constant "NOT_IMPL"). | |
| 27 """ | |
| 28 | |
| 29 import copy | |
| 30 import boto | |
| 31 | |
| 32 NOT_IMPL = None | |
| 33 | |
| 34 | |
| 35 class MockAcl(object): | |
| 36 | |
| 37 def __init__(self, parent=NOT_IMPL): | |
| 38 pass | |
| 39 | |
| 40 def startElement(self, name, attrs, connection): | |
| 41 pass | |
| 42 | |
| 43 def endElement(self, name, value, connection): | |
| 44 pass | |
| 45 | |
| 46 def to_xml(self): | |
| 47 return '<mock_ACL_XML/>' | |
| 48 | |
| 49 | |
| 50 class MockKey(object): | |
| 51 | |
| 52 def __init__(self, bucket=None, name=None): | |
| 53 self.bucket = bucket | |
| 54 self.name = name | |
| 55 self.data = None | |
| 56 self.size = None | |
| 57 self.content_encoding = None | |
| 58 self.content_type = None | |
| 59 self.last_modified = 'Wed, 06 Oct 2010 05:11:54 GMT' | |
| 60 | |
| 61 def get_contents_as_string(self, headers=NOT_IMPL, | |
| 62 cb=NOT_IMPL, num_cb=NOT_IMPL, | |
| 63 torrent=NOT_IMPL, | |
| 64 version_id=NOT_IMPL): | |
| 65 return self.data | |
| 66 | |
| 67 def get_contents_to_file(self, fp, headers=NOT_IMPL, | |
| 68 cb=NOT_IMPL, num_cb=NOT_IMPL, | |
| 69 torrent=NOT_IMPL, | |
| 70 version_id=NOT_IMPL, | |
| 71 res_download_handler=NOT_IMPL): | |
| 72 fp.write(self.data) | |
| 73 | |
| 74 def get_file(self, fp, headers=NOT_IMPL, cb=NOT_IMPL, num_cb=NOT_IMPL, | |
| 75 torrent=NOT_IMPL, version_id=NOT_IMPL, | |
| 76 override_num_retries=NOT_IMPL): | |
| 77 fp.write(self.data) | |
| 78 | |
| 79 def _handle_headers(self, headers): | |
| 80 if not headers: | |
| 81 return | |
| 82 if 'Content-Encoding' in headers: | |
| 83 self.content_encoding = headers['Content-Encoding'] | |
| 84 if 'Content-Type' in headers: | |
| 85 self.content_type = headers['Content-Type'] | |
| 86 | |
| 87 def open_read(self, headers=NOT_IMPL, query_args=NOT_IMPL, | |
| 88 override_num_retries=NOT_IMPL): | |
| 89 pass | |
| 90 | |
| 91 def set_contents_from_file(self, fp, headers=None, replace=NOT_IMPL, | |
| 92 cb=NOT_IMPL, num_cb=NOT_IMPL, | |
| 93 policy=NOT_IMPL, md5=NOT_IMPL, | |
| 94 res_upload_handler=NOT_IMPL): | |
| 95 self.data = fp.read() | |
| 96 self.size = len(self.data) | |
| 97 self._handle_headers(headers) | |
| 98 | |
| 99 def set_contents_from_string(self, s, headers=NOT_IMPL, replace=NOT_IMPL, | |
| 100 cb=NOT_IMPL, num_cb=NOT_IMPL, policy=NOT_IMPL, | |
| 101 md5=NOT_IMPL, reduced_redundancy=NOT_IMPL): | |
| 102 self.data = copy.copy(s) | |
| 103 self.size = len(s) | |
| 104 self._handle_headers(headers) | |
| 105 | |
| 106 def set_contents_from_filename(self, filename, headers=None, replace=NOT_IMP
L, | |
| 107 cb=NOT_IMPL, num_cb=NOT_IMPL, | |
| 108 policy=NOT_IMPL, md5=NOT_IMPL, | |
| 109 res_upload_handler=NOT_IMPL): | |
| 110 fp = open(filename, 'rb') | |
| 111 self.set_contents_from_file(fp, headers, replace, cb, num_cb, | |
| 112 policy, md5, res_upload_handler) | |
| 113 fp.close() | |
| 114 | |
| 115 def copy(self, dst_bucket_name, dst_key, metadata=NOT_IMPL, | |
| 116 reduced_redundancy=NOT_IMPL, preserve_acl=NOT_IMPL): | |
| 117 dst_bucket = self.bucket.connection.get_bucket(dst_bucket_name) | |
| 118 return dst_bucket.copy_key(dst_key, self.bucket.name, | |
| 119 self.name, metadata) | |
| 120 | |
| 121 | |
| 122 class MockBucket(object): | |
| 123 | |
| 124 def __init__(self, connection=None, name=None, key_class=NOT_IMPL): | |
| 125 self.name = name | |
| 126 self.keys = {} | |
| 127 self.acls = {name: MockAcl()} | |
| 128 self.connection = connection | |
| 129 | |
| 130 def copy_key(self, new_key_name, src_bucket_name, | |
| 131 src_key_name, metadata=NOT_IMPL, src_version_id=NOT_IMPL, | |
| 132 storage_class=NOT_IMPL, preserve_acl=NOT_IMPL): | |
| 133 new_key = self.new_key(key_name=new_key_name) | |
| 134 src_key = mock_connection.get_bucket( | |
| 135 src_bucket_name).get_key(src_key_name) | |
| 136 new_key.data = copy.copy(src_key.data) | |
| 137 new_key.size = len(new_key.data) | |
| 138 return new_key | |
| 139 | |
| 140 def get_acl(self, key_name='', headers=NOT_IMPL, version_id=NOT_IMPL): | |
| 141 if key_name: | |
| 142 # Return ACL for the key. | |
| 143 return self.acls[key_name] | |
| 144 else: | |
| 145 # Return ACL for the bucket. | |
| 146 return self.acls[self.name] | |
| 147 | |
| 148 def new_key(self, key_name=None): | |
| 149 mock_key = MockKey(self, key_name) | |
| 150 self.keys[key_name] = mock_key | |
| 151 self.acls[key_name] = MockAcl() | |
| 152 return mock_key | |
| 153 | |
| 154 def delete_key(self, key_name, headers=NOT_IMPL, | |
| 155 version_id=NOT_IMPL, mfa_token=NOT_IMPL): | |
| 156 if key_name not in self.keys: | |
| 157 raise boto.exception.StorageResponseError(404, 'Not Found') | |
| 158 del self.keys[key_name] | |
| 159 | |
| 160 def get_all_keys(self, headers=NOT_IMPL): | |
| 161 return self.keys.itervalues() | |
| 162 | |
| 163 def get_key(self, key_name, headers=NOT_IMPL, version_id=NOT_IMPL): | |
| 164 # Emulate behavior of boto when get_key called with non-existent key. | |
| 165 if key_name not in self.keys: | |
| 166 return None | |
| 167 return self.keys[key_name] | |
| 168 | |
| 169 def list(self, prefix='', delimiter=NOT_IMPL, marker=NOT_IMPL, | |
| 170 headers=NOT_IMPL): | |
| 171 # Return list instead of using a generator so we don't get | |
| 172 # 'dictionary changed size during iteration' error when performing | |
| 173 # deletions while iterating (e.g., during test cleanup). | |
| 174 result = [] | |
| 175 for k in self.keys.itervalues(): | |
| 176 if not prefix: | |
| 177 result.append(k) | |
| 178 elif k.name.startswith(prefix): | |
| 179 result.append(k) | |
| 180 return result | |
| 181 | |
| 182 def set_acl(self, acl_or_str, key_name='', headers=NOT_IMPL, | |
| 183 version_id=NOT_IMPL): | |
| 184 # We only handle setting ACL XML here; if you pass a canned ACL | |
| 185 # the get_acl call will just return that string name. | |
| 186 if key_name: | |
| 187 # Set ACL for the key. | |
| 188 self.acls[key_name] = acl_or_str | |
| 189 else: | |
| 190 # Set ACL for the bucket. | |
| 191 self.acls[self.name] = acl_or_str | |
| 192 | |
| 193 | |
| 194 class MockConnection(object): | |
| 195 | |
| 196 def __init__(self, aws_access_key_id=NOT_IMPL, | |
| 197 aws_secret_access_key=NOT_IMPL, is_secure=NOT_IMPL, | |
| 198 port=NOT_IMPL, proxy=NOT_IMPL, proxy_port=NOT_IMPL, | |
| 199 proxy_user=NOT_IMPL, proxy_pass=NOT_IMPL, | |
| 200 host=NOT_IMPL, debug=NOT_IMPL, | |
| 201 https_connection_factory=NOT_IMPL, | |
| 202 calling_format=NOT_IMPL, | |
| 203 path=NOT_IMPL, provider=NOT_IMPL, | |
| 204 bucket_class=NOT_IMPL): | |
| 205 self.buckets = {} | |
| 206 | |
| 207 def create_bucket(self, bucket_name, headers=NOT_IMPL, location=NOT_IMPL, | |
| 208 policy=NOT_IMPL): | |
| 209 if bucket_name in self.buckets: | |
| 210 raise boto.exception.StorageCreateError( | |
| 211 409, 'BucketAlreadyOwnedByYou', 'bucket already exists') | |
| 212 mock_bucket = MockBucket(name=bucket_name, connection=self) | |
| 213 self.buckets[bucket_name] = mock_bucket | |
| 214 return mock_bucket | |
| 215 | |
| 216 def delete_bucket(self, bucket, headers=NOT_IMPL): | |
| 217 if bucket not in self.buckets: | |
| 218 raise boto.exception.StorageResponseError(404, 'NoSuchBucket', | |
| 219 'no such bucket') | |
| 220 del self.buckets[bucket] | |
| 221 | |
| 222 def get_bucket(self, bucket_name, validate=NOT_IMPL, headers=NOT_IMPL): | |
| 223 if bucket_name not in self.buckets: | |
| 224 raise boto.exception.StorageResponseError(404, 'NoSuchBucket', | |
| 225 'Not Found') | |
| 226 return self.buckets[bucket_name] | |
| 227 | |
| 228 def get_all_buckets(self, headers=NOT_IMPL): | |
| 229 return self.buckets.itervalues() | |
| 230 | |
| 231 | |
| 232 # We only mock a single provider/connection. | |
| 233 mock_connection = MockConnection() | |
| 234 | |
| 235 | |
| 236 class MockBucketStorageUri(object): | |
| 237 | |
| 238 def __init__(self, scheme, bucket_name=None, object_name=None, | |
| 239 debug=NOT_IMPL): | |
| 240 self.scheme = scheme | |
| 241 self.bucket_name = bucket_name | |
| 242 self.object_name = object_name | |
| 243 if self.bucket_name and self.object_name: | |
| 244 self.uri = ('%s://%s/%s' % (self.scheme, self.bucket_name, | |
| 245 self.object_name)) | |
| 246 elif self.bucket_name: | |
| 247 self.uri = ('%s://%s/' % (self.scheme, self.bucket_name)) | |
| 248 else: | |
| 249 self.uri = ('%s://' % self.scheme) | |
| 250 | |
| 251 def __repr__(self): | |
| 252 """Returns string representation of URI.""" | |
| 253 return self.uri | |
| 254 | |
| 255 def acl_class(self): | |
| 256 return MockAcl | |
| 257 | |
| 258 def canned_acls(self): | |
| 259 return boto.provider.Provider('aws').canned_acls | |
| 260 | |
| 261 def clone_replace_name(self, new_name): | |
| 262 return MockBucketStorageUri(self.scheme, self.bucket_name, new_name) | |
| 263 | |
| 264 def connect(self, access_key_id=NOT_IMPL, secret_access_key=NOT_IMPL): | |
| 265 return mock_connection | |
| 266 | |
| 267 def create_bucket(self, headers=NOT_IMPL, location=NOT_IMPL, | |
| 268 policy=NOT_IMPL): | |
| 269 return self.connect().create_bucket(self.bucket_name) | |
| 270 | |
| 271 def delete_bucket(self, headers=NOT_IMPL): | |
| 272 return self.connect().delete_bucket(self.bucket_name) | |
| 273 | |
| 274 def delete_key(self, validate=NOT_IMPL, headers=NOT_IMPL, | |
| 275 version_id=NOT_IMPL, mfa_token=NOT_IMPL): | |
| 276 self.get_bucket().delete_key(self.object_name) | |
| 277 | |
| 278 def equals(self, uri): | |
| 279 return self.uri == uri.uri | |
| 280 | |
| 281 def get_acl(self, validate=NOT_IMPL, headers=NOT_IMPL, version_id=NOT_IMPL): | |
| 282 return self.get_bucket().get_acl(self.object_name) | |
| 283 | |
| 284 def get_all_buckets(self, headers=NOT_IMPL): | |
| 285 return self.connect().get_all_buckets() | |
| 286 | |
| 287 def get_all_keys(self, validate=NOT_IMPL, headers=NOT_IMPL): | |
| 288 return self.get_bucket().get_all_keys(self) | |
| 289 | |
| 290 def get_bucket(self, validate=NOT_IMPL, headers=NOT_IMPL): | |
| 291 return self.connect().get_bucket(self.bucket_name) | |
| 292 | |
| 293 def get_key(self, validate=NOT_IMPL, headers=NOT_IMPL, | |
| 294 version_id=NOT_IMPL): | |
| 295 return self.get_bucket().get_key(self.object_name) | |
| 296 | |
| 297 def is_file_uri(self): | |
| 298 return False | |
| 299 | |
| 300 def is_cloud_uri(self): | |
| 301 return True | |
| 302 | |
| 303 def names_container(self): | |
| 304 return not self.object_name | |
| 305 | |
| 306 def names_singleton(self): | |
| 307 return self.object_name | |
| 308 | |
| 309 def new_key(self, validate=NOT_IMPL, headers=NOT_IMPL): | |
| 310 bucket = self.get_bucket() | |
| 311 return bucket.new_key(self.object_name) | |
| 312 | |
| 313 def set_acl(self, acl_or_str, key_name='', validate=NOT_IMPL, | |
| 314 headers=NOT_IMPL, version_id=NOT_IMPL): | |
| 315 self.get_bucket().set_acl(acl_or_str, key_name) | |
| OLD | NEW |