diff --git a/doc/rnc/access_control_policy.rnc b/doc/rnc/access_control_policy.rnc new file mode 100644 index 00000000..c857359e --- /dev/null +++ b/doc/rnc/access_control_policy.rnc @@ -0,0 +1,7 @@ +include "common.rnc" + +start = + element AccessControlPolicy { + element Owner { CanonicalUser } & + element AccessControlList { AccessControlList } + } diff --git a/doc/rnc/bucket_logging_status.rnc b/doc/rnc/bucket_logging_status.rnc new file mode 100644 index 00000000..a7d9a1ef --- /dev/null +++ b/doc/rnc/bucket_logging_status.rnc @@ -0,0 +1,10 @@ +include "common.rnc" + +start = + element BucketLoggingStatus { + element LoggingEnabled { + element TargetBucket { xsd:string } & + element TargetPrefix { xsd:string } & + element TargetGrants { AccessControlList }? + }? + } diff --git a/doc/rnc/common.rnc b/doc/rnc/common.rnc new file mode 100644 index 00000000..79dddbb5 --- /dev/null +++ b/doc/rnc/common.rnc @@ -0,0 +1,26 @@ +namespace xsi = "http://www.w3.org/2001/XMLSchema-instance" + +CanonicalUser = + element ID { xsd:string } & + element DisplayName { xsd:string }? + +StorageClass = "STANDARD" | "REDUCED_REDUNDANCY" | "GLACIER" | "UNKNOWN" + +AccessControlList = + element Grant { + element Grantee { + ( + attribute xsi:type { "AmazonCustomerByEmail" }, + element EmailAddress { xsd:string } + ) | ( + attribute xsi:type { "CanonicalUser" }, + CanonicalUser + ) | ( + attribute xsi:type { "Group" }, + element URI { xsd:string } + ) + } & + element Permission { + "READ" | "WRITE" | "READ_ACP" | "WRITE_ACP" | "FULL_CONTROL" + } + }* diff --git a/doc/rnc/complete_multipart_upload.rnc b/doc/rnc/complete_multipart_upload.rnc new file mode 100644 index 00000000..dee60e54 --- /dev/null +++ b/doc/rnc/complete_multipart_upload.rnc @@ -0,0 +1,7 @@ +start = + element CompleteMultipartUpload { + element Part { + element PartNumber { xsd:int } & + element ETag { xsd:string } + }+ + } diff --git a/doc/rnc/complete_multipart_upload_result.rnc b/doc/rnc/complete_multipart_upload_result.rnc new file mode 100644 index 00000000..6dd9cbeb --- /dev/null +++ b/doc/rnc/complete_multipart_upload_result.rnc @@ -0,0 +1,7 @@ +start = + element CompleteMultipartUploadResult { + element Location { xsd:anyURI }, + element Bucket { xsd:string }, + element Key { xsd:string }, + element ETag { xsd:string } + } diff --git a/doc/rnc/copy_object_result.rnc b/doc/rnc/copy_object_result.rnc new file mode 100644 index 00000000..bf96a8a9 --- /dev/null +++ b/doc/rnc/copy_object_result.rnc @@ -0,0 +1,5 @@ +start = + element CopyObjectResult { + element LastModified { xsd:dateTime }, + element ETag { xsd:string } + } diff --git a/doc/rnc/create_bucket_configuration.rnc b/doc/rnc/create_bucket_configuration.rnc new file mode 100644 index 00000000..53f07d8a --- /dev/null +++ b/doc/rnc/create_bucket_configuration.rnc @@ -0,0 +1,4 @@ +start = + element CreateBucketConfiguration { + element LocationConstraint { xsd:string } + } diff --git a/doc/rnc/delete.rnc b/doc/rnc/delete.rnc new file mode 100644 index 00000000..95214f01 --- /dev/null +++ b/doc/rnc/delete.rnc @@ -0,0 +1,8 @@ +start = + element Delete { + element Quiet { xsd:boolean }? & + element Object { + element Key { xsd:string } & + element VersionId { xsd:string }? + }+ + } diff --git a/doc/rnc/delete_result.rnc b/doc/rnc/delete_result.rnc new file mode 100644 index 00000000..3a63bf78 --- /dev/null +++ b/doc/rnc/delete_result.rnc @@ -0,0 +1,17 @@ +start = + element DeleteResult { + ( + element Deleted { + element Key { xsd:string }, + element VersionId { xsd:string }?, + element DeleteMarker { xsd:boolean }?, + element DeleteMarkerVersionId { xsd:string }? + } | + element Error { + element Key { xsd:string }, + element VersionId { xsd:string }?, + element Code { xsd:string }, + element Message { xsd:string } + } + )* + } diff --git a/doc/rnc/error.rnc b/doc/rnc/error.rnc new file mode 100644 index 00000000..0e352c71 --- /dev/null +++ b/doc/rnc/error.rnc @@ -0,0 +1,11 @@ +start = + element Error { + element Code { xsd:string }, + element Message { xsd:string }, + DebugInfo* + } + +DebugInfo = + element * { + (attribute * { text } | text | DebugInfo)* + } diff --git a/doc/rnc/initiate_multipart_upload_result.rnc b/doc/rnc/initiate_multipart_upload_result.rnc new file mode 100644 index 00000000..8830121f --- /dev/null +++ b/doc/rnc/initiate_multipart_upload_result.rnc @@ -0,0 +1,6 @@ +start = + element InitiateMultipartUploadResult { + element Bucket { xsd:string }, + element Key { xsd:string }, + element UploadId { xsd:string } + } diff --git a/doc/rnc/lifecycle_configuration.rnc b/doc/rnc/lifecycle_configuration.rnc new file mode 100644 index 00000000..b21fc07b --- /dev/null +++ b/doc/rnc/lifecycle_configuration.rnc @@ -0,0 +1,20 @@ +include "common.rnc" + +start = + element LifecycleConfiguration { + element Rule { + element ID { xsd:string }? & + element Prefix { xsd:string } & + element Status { "Enabled" | "Disabled" } & + element Transition { Transition }? & + element Expiration { Expiration }? + }+ + } + +Expiration = + element Days { xsd:int } | + element Date { xsd:dateTime } + +Transition = + Expiration & + element StorageClass { StorageClass } diff --git a/doc/rnc/list_all_my_buckets_result.rnc b/doc/rnc/list_all_my_buckets_result.rnc new file mode 100644 index 00000000..220a34aa --- /dev/null +++ b/doc/rnc/list_all_my_buckets_result.rnc @@ -0,0 +1,12 @@ +include "common.rnc" + +start = + element ListAllMyBucketsResult { + element Owner { CanonicalUser }, + element Buckets { + element Bucket { + element Name { xsd:string }, + element CreationDate { xsd:dateTime } + }* + } + } diff --git a/doc/rnc/list_bucket_result.rnc b/doc/rnc/list_bucket_result.rnc new file mode 100644 index 00000000..1dac2ac8 --- /dev/null +++ b/doc/rnc/list_bucket_result.rnc @@ -0,0 +1,24 @@ +include "common.rnc" + +start = + element ListBucketResult { + element Name { xsd:string }, + element Prefix { xsd:string }, + element Marker { xsd:string }, + element NextMarker { xsd:string }?, + element MaxKeys { xsd:int }, + element EncodingType { xsd:string }?, + element Delimiter { xsd:string }?, + element IsTruncated { xsd:boolean }, + element Contents { + element Key { xsd:string }, + element LastModified { xsd:dateTime }, + element ETag { xsd:string }, + element Size { xsd:long }, + element Owner { CanonicalUser }?, + element StorageClass { StorageClass } + }*, + element CommonPrefixes { + element Prefix { xsd:string } + }* + } diff --git a/doc/rnc/list_multipart_uploads_result.rnc b/doc/rnc/list_multipart_uploads_result.rnc new file mode 100644 index 00000000..6ac1e123 --- /dev/null +++ b/doc/rnc/list_multipart_uploads_result.rnc @@ -0,0 +1,26 @@ +include "common.rnc" + +start = + element ListMultipartUploadsResult { + element Bucket { xsd:string }, + element KeyMarker { xsd:string }, + element UploadIdMarker { xsd:string }, + element NextKeyMarker { xsd:string }, + element NextUploadIdMarker { xsd:string }, + element Delimiter { xsd:string }?, + element Prefix { xsd:string }?, + element MaxUploads { xsd:int }, + element EncodingType { xsd:string }?, + element IsTruncated { xsd:boolean }, + element Upload { + element Key { xsd:string }, + element UploadId { xsd:string }, + element Initiator { CanonicalUser }, + element Owner { CanonicalUser }, + element StorageClass { StorageClass }, + element Initiated { xsd:dateTime } + }*, + element CommonPrefixes { + element Prefix { xsd:string } + }* + } diff --git a/doc/rnc/list_parts_result.rnc b/doc/rnc/list_parts_result.rnc new file mode 100644 index 00000000..21433154 --- /dev/null +++ b/doc/rnc/list_parts_result.rnc @@ -0,0 +1,22 @@ +include "common.rnc" + +start = + element ListPartsResult { + element Bucket { xsd:string }, + element Key { xsd:string }, + element UploadId { xsd:string }, + element Initiator { CanonicalUser }, + element Owner { CanonicalUser }, + element StorageClass { StorageClass }, + element PartNumberMarker { xsd:int }, + element NextPartNumberMarker { xsd:int }, + element MaxParts { xsd:int }, + element EncodingType { xsd:string }?, + element IsTruncated { xsd:boolean }, + element Part { + element PartNumber { xsd:int }, + element LastModified { xsd:dateTime }, + element ETag { xsd:string }, + element Size { xsd:long } + }* + } diff --git a/doc/rnc/list_versions_result.rnc b/doc/rnc/list_versions_result.rnc new file mode 100644 index 00000000..969073f3 --- /dev/null +++ b/doc/rnc/list_versions_result.rnc @@ -0,0 +1,37 @@ +include "common.rnc" + +start = + element ListVersionsResult { + element Name { xsd:string }, + element Prefix { xsd:string }, + element KeyMarker { xsd:string }, + element VersionIdMarker { xsd:string }, + element NextKeyMarker { xsd:string }?, + element NextVersionIdMarker { xsd:string }?, + element MaxKeys { xsd:int }, + element EncodingType { xsd:string }?, + element Delimiter { xsd:string }?, + element IsTruncated { xsd:boolean }, + ( + element Version { + element Key { xsd:string }, + element VersionId { xsd:string }, + element IsLatest { xsd:boolean }, + element LastModified { xsd:dateTime }, + element ETag { xsd:string }, + element Size { xsd:long }, + element Owner { CanonicalUser }?, + element StorageClass { StorageClass } + } | + element DeleteMarker { + element Key { xsd:string }, + element VersionId { xsd:string }, + element IsLatest { xsd:boolean }, + element LastModified { xsd:dateTime }, + element Owner { CanonicalUser }? + } + )*, + element CommonPrefixes { + element Prefix { xsd:string } + }* + } diff --git a/doc/rnc/location_constraint.rnc b/doc/rnc/location_constraint.rnc new file mode 100644 index 00000000..829176ff --- /dev/null +++ b/doc/rnc/location_constraint.rnc @@ -0,0 +1 @@ +start = element LocationConstraint { xsd:string } diff --git a/doc/rnc/versioning_configuration.rnc b/doc/rnc/versioning_configuration.rnc new file mode 100644 index 00000000..87e5d15a --- /dev/null +++ b/doc/rnc/versioning_configuration.rnc @@ -0,0 +1,5 @@ +start = + element VersioningConfiguration { + element Status { "Enabled" | "Suspended" }? & + element MfaDelete { "Enabled" | "Disabled" }? + } diff --git a/swift3/controllers/acl.py b/swift3/controllers/acl.py index 6d41cd6e..471a35c0 100644 --- a/swift3/controllers/acl.py +++ b/swift3/controllers/acl.py @@ -18,7 +18,8 @@ from swift.common.middleware.acl import parse_acl, referrer_allowed from swift3.controllers.base import Controller from swift3.response import HTTPOk, S3NotImplemented, MalformedACLError -from swift3.etree import Element, SubElement, fromstring, tostring +from swift3.etree import Element, SubElement, fromstring, tostring, \ + DocumentInvalid XMLNS_XSI = 'http://www.w3.org/2001/XMLSchema-instance' @@ -97,7 +98,10 @@ def swift_acl_translate(acl, group='', user='', xml=False): ['HTTP_X_CONTAINER_READ', '.']] if xml: # We are working with XML and need to parse it - elem = fromstring(acl) + try: + elem = fromstring(acl, 'AccessControlPolicy') + except DocumentInvalid: + raise MalformedACLError() acl = 'unknown' for grant in elem.findall('./AccessControlList/Grant'): permission = grant.find('./Permission').text diff --git a/swift3/controllers/bucket.py b/swift3/controllers/bucket.py index e5ad92dd..61094448 100644 --- a/swift3/controllers/bucket.py +++ b/swift3/controllers/bucket.py @@ -62,16 +62,16 @@ class BucketController(Controller): objects = loads(resp.body) elem = Element('ListBucketResult') + SubElement(elem, 'Name').text = req.container_name SubElement(elem, 'Prefix').text = req.params.get('prefix') SubElement(elem, 'Marker').text = req.params.get('marker') + SubElement(elem, 'MaxKeys').text = str(max_keys) SubElement(elem, 'Delimiter').text = req.params.get('delimiter') if max_keys > 0 and len(objects) == max_keys + 1: is_truncated = 'true' else: is_truncated = 'false' SubElement(elem, 'IsTruncated').text = is_truncated - SubElement(elem, 'MaxKeys').text = str(max_keys) - SubElement(elem, 'Name').text = req.container_name for o in objects[:max_keys]: if 'subdir' not in o: @@ -82,6 +82,7 @@ class BucketController(Controller): SubElement(contents, 'ETag').text = o['hash'] SubElement(contents, 'Size').text = str(o['bytes']) add_canonical_user(contents, 'Owner', req.user_id) + SubElement(contents, 'StorageClass').text = 'STANDARD' for o in objects[:max_keys]: if 'subdir' in o: diff --git a/swift3/controllers/multi_delete.py b/swift3/controllers/multi_delete.py index 9e155965..85bcc76d 100644 --- a/swift3/controllers/multi_delete.py +++ b/swift3/controllers/multi_delete.py @@ -14,8 +14,10 @@ # limitations under the License. from swift3.controllers.base import Controller -from swift3.etree import Element, SubElement, fromstring, tostring -from swift3.response import HTTPOk, S3NotImplemented, NoSuchKey, ErrorResponse +from swift3.etree import Element, SubElement, fromstring, tostring, \ + DocumentInvalid +from swift3.response import HTTPOk, S3NotImplemented, NoSuchKey, \ + ErrorResponse, MalformedXML class MultiObjectDeleteController(Controller): @@ -28,7 +30,11 @@ class MultiObjectDeleteController(Controller): Handles Delete Multiple Objects. """ def object_key_iter(xml): - elem = fromstring(xml) + try: + elem = fromstring(xml, 'Delete') + except DocumentInvalid: + raise MalformedXML() + for obj in elem.iterchildren('Object'): key = obj.find('./Key').text version = obj.find('./VersionId') diff --git a/swift3/controllers/service.py b/swift3/controllers/service.py index bfcb29e6..1f57149f 100644 --- a/swift3/controllers/service.py +++ b/swift3/controllers/service.py @@ -34,6 +34,11 @@ class ServiceController(Controller): # we don't keep the creation time of a backet (s3cmd doesn't # work without that) so we use something bogus. elem = Element('ListAllMyBucketsResult') + + owner = SubElement(elem, 'Owner') + SubElement(owner, 'ID').text = req.user_id + SubElement(owner, 'DisplayName').text = req.user_id + buckets = SubElement(elem, 'Buckets') for c in containers: bucket = SubElement(buckets, 'Bucket') diff --git a/swift3/etree.py b/swift3/etree.py index e2a2b944..e33fa7c0 100644 --- a/swift3/etree.py +++ b/swift3/etree.py @@ -15,9 +15,21 @@ import lxml.etree from copy import deepcopy +from pkg_resources import resource_stream + +from swift.common.utils import get_logger + +from swift3.exception import S3Exception +from swift3.utils import camel_to_snake +from swift3.cfg import CONF XMLNS_S3 = 'http://s3.amazonaws.com/doc/2006-03-01/' +LOGGER = get_logger(CONF, log_route='swift3') + +class DocumentInvalid(S3Exception, lxml.etree.DocumentInvalid): + pass + def cleanup_namespaces(elem): def remove_ns(tag, ns): @@ -36,10 +48,24 @@ def cleanup_namespaces(elem): cleanup_namespaces(e) -def fromstring(text): +def fromstring(text, root_tag=None): elem = lxml.etree.fromstring(text) cleanup_namespaces(elem) + if root_tag is not None: + # validate XML + try: + path = 'schema/%s.rng' % camel_to_snake(root_tag) + rng = resource_stream(__name__, path) + lxml.etree.RelaxNG(file=rng).assertValid(elem) + except IOError as e: + # Probably, the schema file doesn't exist. + LOGGER.error(e) + raise + except lxml.etree.DocumentInvalid as e: + LOGGER.debug(e) + raise DocumentInvalid(e) + return elem diff --git a/swift3/response.py b/swift3/response.py index baf7bcdd..141ff6dc 100644 --- a/swift3/response.py +++ b/swift3/response.py @@ -19,6 +19,7 @@ from functools import partial from swift.common import swob +from swift3.utils import snake_to_camel from swift3.etree import Element, SubElement, tostring @@ -157,8 +158,7 @@ class ErrorResponse(swob.HTTPException): def _dict_to_etree(self, parent, d): for key, value in d.items(): - tag = key.title().replace('_', '') - tag = re.sub('\W', '', tag) + tag = re.sub('\W', '', snake_to_camel(key)) elem = SubElement(parent, tag) if isinstance(value, (dict, DictMixin)): diff --git a/swift3/schema/access_control_policy.rng b/swift3/schema/access_control_policy.rng new file mode 100644 index 00000000..5308a12f --- /dev/null +++ b/swift3/schema/access_control_policy.rng @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/swift3/schema/bucket_logging_status.rng b/swift3/schema/bucket_logging_status.rng new file mode 100644 index 00000000..27ea1e1d --- /dev/null +++ b/swift3/schema/bucket_logging_status.rng @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/common.rng b/swift3/schema/common.rng new file mode 100644 index 00000000..22319c0e --- /dev/null +++ b/swift3/schema/common.rng @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + STANDARD + REDUCED_REDUNDANCY + GLACIER + UNKNOWN + + + + + + + + + + + AmazonCustomerByEmail + + + + + + + + CanonicalUser + + + + + + Group + + + + + + + + + + READ + WRITE + READ_ACP + WRITE_ACP + FULL_CONTROL + + + + + + + diff --git a/swift3/schema/complete_multipart_upload.rng b/swift3/schema/complete_multipart_upload.rng new file mode 100644 index 00000000..d7ba2569 --- /dev/null +++ b/swift3/schema/complete_multipart_upload.rng @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/complete_multipart_upload_result.rng b/swift3/schema/complete_multipart_upload_result.rng new file mode 100644 index 00000000..47406e1c --- /dev/null +++ b/swift3/schema/complete_multipart_upload_result.rng @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/copy_object_result.rng b/swift3/schema/copy_object_result.rng new file mode 100644 index 00000000..ec0ac95f --- /dev/null +++ b/swift3/schema/copy_object_result.rng @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/swift3/schema/create_bucket_configuration.rng b/swift3/schema/create_bucket_configuration.rng new file mode 100644 index 00000000..0e22a298 --- /dev/null +++ b/swift3/schema/create_bucket_configuration.rng @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/swift3/schema/delete.rng b/swift3/schema/delete.rng new file mode 100644 index 00000000..3417a394 --- /dev/null +++ b/swift3/schema/delete.rng @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/delete_result.rng b/swift3/schema/delete_result.rng new file mode 100644 index 00000000..1e28b3ce --- /dev/null +++ b/swift3/schema/delete_result.rng @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/error.rng b/swift3/schema/error.rng new file mode 100644 index 00000000..a0d61d48 --- /dev/null +++ b/swift3/schema/error.rng @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/initiate_multipart_upload_result.rng b/swift3/schema/initiate_multipart_upload_result.rng new file mode 100644 index 00000000..67d03016 --- /dev/null +++ b/swift3/schema/initiate_multipart_upload_result.rng @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/swift3/schema/lifecycle_configuration.rng b/swift3/schema/lifecycle_configuration.rng new file mode 100644 index 00000000..dd0816e2 --- /dev/null +++ b/swift3/schema/lifecycle_configuration.rng @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + Enabled + Disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/list_all_my_buckets_result.rng b/swift3/schema/list_all_my_buckets_result.rng new file mode 100644 index 00000000..76959d7b --- /dev/null +++ b/swift3/schema/list_all_my_buckets_result.rng @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/list_bucket_result.rng b/swift3/schema/list_bucket_result.rng new file mode 100644 index 00000000..f463fe38 --- /dev/null +++ b/swift3/schema/list_bucket_result.rng @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/list_multipart_uploads_result.rng b/swift3/schema/list_multipart_uploads_result.rng new file mode 100644 index 00000000..2e20c840 --- /dev/null +++ b/swift3/schema/list_multipart_uploads_result.rng @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/list_parts_result.rng b/swift3/schema/list_parts_result.rng new file mode 100644 index 00000000..4cf5a0ce --- /dev/null +++ b/swift3/schema/list_parts_result.rng @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/list_versions_result.rng b/swift3/schema/list_versions_result.rng new file mode 100644 index 00000000..464cfbcc --- /dev/null +++ b/swift3/schema/list_versions_result.rng @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift3/schema/location_constraint.rng b/swift3/schema/location_constraint.rng new file mode 100644 index 00000000..2f3a143b --- /dev/null +++ b/swift3/schema/location_constraint.rng @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/swift3/schema/versioning_configuration.rng b/swift3/schema/versioning_configuration.rng new file mode 100644 index 00000000..3d6d3d12 --- /dev/null +++ b/swift3/schema/versioning_configuration.rng @@ -0,0 +1,25 @@ + + + + + + + + + Enabled + Suspended + + + + + + + Enabled + Disabled + + + + + + + diff --git a/swift3/test/functional/001.out b/swift3/test/functional/001.out index ed8923b2..5ee2c59f 100644 --- a/swift3/test/functional/001.out +++ b/swift3/test/functional/001.out @@ -10,6 +10,10 @@ X-Trans-Id: TXID + + TESTER + TESTER + 5ABCDEFGHIJKLMNOPQRSTUVWXYZ.5-6789 diff --git a/swift3/test/functional/003.out b/swift3/test/functional/003.out index 11ddcad6..54f1a538 100644 --- a/swift3/test/functional/003.out +++ b/swift3/test/functional/003.out @@ -14,12 +14,12 @@ X-Trans-Id: TXID + bucket photos/2006/ + 1000 / false - 1000 - bucket photos/2006/February/ diff --git a/swift3/test/unit/__init__.py b/swift3/test/unit/__init__.py index 64066f6a..e21a45b9 100644 --- a/swift3/test/unit/__init__.py +++ b/swift3/test/unit/__init__.py @@ -76,8 +76,7 @@ class Swift3TestCase(unittest.TestCase): swob.HTTPNoContent, {}, None) def _get_error_code(self, body): - elem = fromstring(body) - self.assertEquals(elem.tag, 'Error') + elem = fromstring(body, 'Error') return elem.find('./Code').text def _test_method_error(self, method, path, response_class, headers={}): diff --git a/swift3/test/unit/test_acl.py b/swift3/test/unit/test_acl.py index 94ec527c..da9e1f39 100644 --- a/swift3/test/unit/test_acl.py +++ b/swift3/test/unit/test_acl.py @@ -29,8 +29,7 @@ class TestSwift3Acl(Swift3TestCase): super(TestSwift3Acl, self).setUp() def _check_acl(self, owner, body): - elem = fromstring(body) - self.assertEquals(elem.tag, 'AccessControlPolicy') + elem = fromstring(body, 'AccessControlPolicy') permission = elem.find('./AccessControlList/Grant/Permission').text self.assertEquals(permission, 'FULL_CONTROL') name = elem.find('./AccessControlList/Grant/Grantee/ID').text diff --git a/swift3/test/unit/test_bucket.py b/swift3/test/unit/test_bucket.py index 3297a777..9c8a2d11 100644 --- a/swift3/test/unit/test_bucket.py +++ b/swift3/test/unit/test_bucket.py @@ -66,8 +66,7 @@ class TestSwift3Bucket(Swift3TestCase): status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200') - elem = fromstring(body) - self.assertEquals(elem.tag, 'ListBucketResult') + elem = fromstring(body, 'ListBucketResult') name = elem.find('./Name').text self.assertEquals(name, bucket_name) @@ -90,7 +89,7 @@ class TestSwift3Bucket(Swift3TestCase): 'QUERY_STRING': 'max-keys=5'}, headers={'Authorization': 'AWS test:tester:hmac'}) status, headers, body = self.call_swift3(req) - elem = fromstring(body) + elem = fromstring(body, 'ListBucketResult') self.assertEquals(elem.find('./IsTruncated').text, 'false') req = Request.blank('/%s' % bucket_name, @@ -98,7 +97,7 @@ class TestSwift3Bucket(Swift3TestCase): 'QUERY_STRING': 'max-keys=4'}, headers={'Authorization': 'AWS test:tester:hmac'}) status, headers, body = self.call_swift3(req) - elem = fromstring(body) + elem = fromstring(body, 'ListBucketResult') self.assertEquals(elem.find('./IsTruncated').text, 'true') def test_bucket_GET_max_keys(self): @@ -109,7 +108,7 @@ class TestSwift3Bucket(Swift3TestCase): 'QUERY_STRING': 'max-keys=5'}, headers={'Authorization': 'AWS test:tester:hmac'}) status, headers, body = self.call_swift3(req) - elem = fromstring(body) + elem = fromstring(body, 'ListBucketResult') self.assertEquals(elem.find('./MaxKeys').text, '5') _, path = self.swift.calls[-1] _, query_string = path.split('?') @@ -121,7 +120,7 @@ class TestSwift3Bucket(Swift3TestCase): 'QUERY_STRING': 'max-keys=5000'}, headers={'Authorization': 'AWS test:tester:hmac'}) status, headers, body = self.call_swift3(req) - elem = fromstring(body) + elem = fromstring(body, 'ListBucketResult') self.assertEquals(elem.find('./MaxKeys').text, '1000') _, path = self.swift.calls[-1] _, query_string = path.split('?') @@ -135,7 +134,7 @@ class TestSwift3Bucket(Swift3TestCase): 'delimiter=a&marker=b&prefix=c'}, headers={'Authorization': 'AWS test:tester:hmac'}) status, headers, body = self.call_swift3(req) - elem = fromstring(body) + elem = fromstring(body, 'ListBucketResult') self.assertEquals(elem.find('./Prefix').text, 'c') self.assertEquals(elem.find('./Marker').text, 'b') self.assertEquals(elem.find('./Delimiter').text, 'a') diff --git a/swift3/test/unit/test_middleware.py b/swift3/test/unit/test_middleware.py index 526bd838..2b1c83a9 100644 --- a/swift3/test/unit/test_middleware.py +++ b/swift3/test/unit/test_middleware.py @@ -256,9 +256,8 @@ class TestSwift3Middleware(Swift3TestCase): environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac'}, body=xml) - # FIXME: swift3 should handle invalid xml file status, headers, body = self.call_swift3(req) - self.assertEquals(self._get_error_code(body), 'InternalError') + self.assertEquals(self._get_error_code(body), 'MalformedACLError') def _test_unsupported_resource(self, resource): req = Request.blank('/error?' + resource, diff --git a/swift3/test/unit/test_service.py b/swift3/test/unit/test_service.py index 2aab6107..4b192cb1 100644 --- a/swift3/test/unit/test_service.py +++ b/swift3/test/unit/test_service.py @@ -59,8 +59,7 @@ class TestSwift3Service(Swift3TestCase): status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200') - elem = fromstring(body) - self.assertEquals(elem.tag, 'ListAllMyBucketsResult') + elem = fromstring(body, 'ListAllMyBucketsResult') all_buckets = elem.find('./Buckets') buckets = all_buckets.iterchildren('Bucket') diff --git a/swift3/test/unit/test_utils.py b/swift3/test/unit/test_utils.py new file mode 100644 index 00000000..297e678f --- /dev/null +++ b/swift3/test/unit/test_utils.py @@ -0,0 +1,34 @@ +# Copyright (c) 2014 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from swift3 import utils + +strs = [ + ('Owner', 'owner'), + ('DisplayName', 'display_name'), + ('AccessControlPolicy', 'access_control_policy'), +] + + +class TestSwift3Utils(unittest.TestCase): + def test_camel_to_snake(self): + for s1, s2 in strs: + self.assertEquals(utils.camel_to_snake(s1), s2) + + def test_snake_to_camel(self): + for s1, s2 in strs: + self.assertEquals(s1, utils.snake_to_camel(s2)) diff --git a/swift3/test/unit/test_versioning.py b/swift3/test/unit/test_versioning.py index 36110b78..4999e757 100644 --- a/swift3/test/unit/test_versioning.py +++ b/swift3/test/unit/test_versioning.py @@ -31,8 +31,7 @@ class TestSwift3Versioning(Swift3TestCase): environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac'}) status, headers, body = self.call_swift3(req) - elem = fromstring(body) - self.assertEquals(elem.tag, 'VersioningConfiguration') + fromstring(body, 'VersioningConfiguration') if __name__ == '__main__': unittest.main() diff --git a/swift3/utils.py b/swift3/utils.py new file mode 100644 index 00000000..b09ef252 --- /dev/null +++ b/swift3/utils.py @@ -0,0 +1,23 @@ +# Copyright (c) 2014 OpenStack Foundation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +def camel_to_snake(camel): + return re.sub('(.)([A-Z])', r'\1_\2', camel).lower() + + +def snake_to_camel(snake): + return snake.title().replace('_', '')