PUT of a Directory failed

PUT of a directory fails with gluster-swift when there exists
object of a same name. This is because the objects and directories are placed on
the glusterfs. And hence the filesystem semantics are applicable there.

Exceptions raised in such situation are needed to be handled and reported
correctly back to proxy-server with HTTPConflict as a error code. Although
swift still continues to choose 503 as the best respose in such cases. No
tracebacks reported.

Fix covers the case when there exists a directory and object of the same
name is PUT.

Code changes fixes both the failure cases mentioned in the bug.

Examples of failing PUT requests:
1) curl -v  -X PUT http://127.0.0.1:8080/v1/AUTH_test/c1/dir1/obj2/anotherobject
-d'asdasdsadA'
-- obj2 was already an object.

2)curl -v -H 'Content-Length: 0' -H 'Content-Type: application/directory' -X PUT
http://127.0.0.1:8080/v1/AUTH_test/c1/obj1
-- obj1 was already and object

Bug: https://bugzilla.redhat.com/show_bug.cgi?id=1071021

Change-Id: Id3042d920e3f99e740d4042ef5907ac8c59e04db
Signed-off-by: Chetan Risbud <crisbud@redhat.com>
Reviewed-on: http://review.gluster.org/7181
Reviewed-by: Prashanth Pai <ppai@redhat.com>
Tested-by: Prashanth Pai <ppai@redhat.com>
This commit is contained in:
Chetan Risbud 2014-03-04 19:35:24 +05:30
parent 47cbf09a22
commit 1a6b55714f
3 changed files with 45 additions and 15 deletions

View File

@ -29,6 +29,8 @@ from hashlib import md5
from eventlet import sleep from eventlet import sleep
from greenlet import getcurrent from greenlet import getcurrent
from contextlib import contextmanager from contextlib import contextmanager
from gluster.swift.common.exceptions import AlreadyExistsAsFile, \
AlreadyExistsAsDir
from swift.common.utils import TRUE_VALUES, ThreadPool, config_true_value from swift.common.utils import TRUE_VALUES, ThreadPool, config_true_value
from swift.common.exceptions import DiskFileNotExist, DiskFileError, \ from swift.common.exceptions import DiskFileNotExist, DiskFileError, \
DiskFileNoSpace, DiskFileDeviceUnavailable, DiskFileNotOpen DiskFileNoSpace, DiskFileDeviceUnavailable, DiskFileNotOpen
@ -166,17 +168,19 @@ def _make_directory_unlocked(full_path, uid, gid, metadata=None):
if not is_dir: if not is_dir:
# FIXME: Ideally we'd want to return an appropriate error # FIXME: Ideally we'd want to return an appropriate error
# message and code in the PUT Object REST API response. # message and code in the PUT Object REST API response.
raise DiskFileError("_make_directory_unlocked: os.mkdir" raise AlreadyExistsAsFile("_make_directory_unlocked:"
" failed on path %s because it already" " os.mkdir failed on path %s"
" exists but not as a directory" % ( " because it already exists"
full_path)) " but not as a directory"
% (full_path))
return True, metadata return True, metadata
elif err.errno == errno.ENOTDIR: elif err.errno == errno.ENOTDIR:
# FIXME: Ideally we'd want to return an appropriate error # FIXME: Ideally we'd want to return an appropriate error
# message and code in the PUT Object REST API response. # message and code in the PUT Object REST API response.
raise DiskFileError("_make_directory_unlocked: os.mkdir failed" raise AlreadyExistsAsFile("_make_directory_unlocked:"
" because some part of path %s is not in fact" " os.mkdir failed because some "
" a directory" % (full_path)) "part of path %s is not in fact"
" a directory" % (full_path))
elif err.errno == errno.EIO: elif err.errno == errno.EIO:
# Sometimes Fuse will return an EIO error when it does not know # Sometimes Fuse will return an EIO error when it does not know
# how to handle an unexpected, but transient situation. It is # how to handle an unexpected, but transient situation. It is
@ -482,6 +486,8 @@ class DiskFileWriter(object):
written to the temp file. written to the temp file.
:param metadata: dictionary of metadata to be written :param metadata: dictionary of metadata to be written
:raises AlreadyExistsAsDir : If there exists a directory of the same
name
""" """
assert self._tmppath is not None assert self._tmppath is not None
metadata = _adjust_metadata(metadata) metadata = _adjust_metadata(metadata)
@ -497,9 +503,9 @@ class DiskFileWriter(object):
# system, perhaps gratuitously created when another # system, perhaps gratuitously created when another
# object was created, or created externally to Swift # object was created, or created externally to Swift
# REST API servicing (UFO use case). # REST API servicing (UFO use case).
raise DiskFileError('DiskFile.put(): file creation failed since' raise AlreadyExistsAsDir('DiskFile.put(): file creation failed'
' the target, %s, already exists as a' ' since the target, %s, already exists'
' directory' % df._data_file) ' as a directory' % df._data_file)
df._threadpool.force_run_in_thread(self._finalize_put, metadata) df._threadpool.force_run_in_thread(self._finalize_put, metadata)
@ -897,6 +903,8 @@ class DiskFile(object):
:param size: optional initial size of file to explicitly allocate on :param size: optional initial size of file to explicitly allocate on
disk disk
:raises DiskFileNoSpace: if a size is specified and allocation fails :raises DiskFileNoSpace: if a size is specified and allocation fails
:raises AlreadyExistsAsFile: if path or part of a path is not a \
directory
""" """
data_file = os.path.join(self._put_datadir, self._obj) data_file = os.path.join(self._put_datadir, self._obj)
@ -917,6 +925,12 @@ class DiskFile(object):
# Raise DiskFileNoSpace to be handled by upper layers when # Raise DiskFileNoSpace to be handled by upper layers when
# there is no space on disk OR when quota is exceeded # there is no space on disk OR when quota is exceeded
raise DiskFileNoSpace() raise DiskFileNoSpace()
if gerr.errno == errno.ENOTDIR:
raise AlreadyExistsAsFile('do_open(): failed on %s,'
' path or part of a'
' path is not a directory'
% (tmppath))
if gerr.errno not in (errno.ENOENT, errno.EEXIST, errno.EIO): if gerr.errno not in (errno.ENOENT, errno.EEXIST, errno.EIO):
# FIXME: Other cases we should handle? # FIXME: Other cases we should handle?
raise raise

View File

@ -1,4 +1,4 @@
# Copyright (c) 2012-2013 Red Hat, Inc. # Copyright (c) 2012-2014 Red Hat, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -18,6 +18,11 @@
# Simply importing this monkey patches the constraint handling to fit our # Simply importing this monkey patches the constraint handling to fit our
# needs # needs
import gluster.swift.common.constraints # noqa import gluster.swift.common.constraints # noqa
from swift.common.swob import HTTPConflict
from swift.common.utils import public, timing_stats
from gluster.swift.common.exceptions import AlreadyExistsAsFile, \
AlreadyExistsAsDir
from swift.common.request_helpers import split_and_validate_path
from swift.obj import server from swift.obj import server
@ -80,6 +85,16 @@ class ObjectController(server.ObjectController):
""" """
return return
@public
@timing_stats()
def PUT(self, request):
try:
return server.ObjectController.PUT(self, request)
except (AlreadyExistsAsFile, AlreadyExistsAsDir):
device = \
split_and_validate_path(request, 1, 5, True)
return HTTPConflict(drive=device, request=request)
def app_factory(global_conf, **local_conf): def app_factory(global_conf, **local_conf):
"""paste.deploy app factory for creating WSGI object server apps""" """paste.deploy app factory for creating WSGI object server apps"""

View File

@ -26,7 +26,8 @@ from eventlet import tpool
from mock import Mock, patch from mock import Mock, patch
from hashlib import md5 from hashlib import md5
from copy import deepcopy from copy import deepcopy
from gluster.swift.common.exceptions import AlreadyExistsAsDir, \
AlreadyExistsAsFile
from swift.common.exceptions import DiskFileNotExist, DiskFileError, \ from swift.common.exceptions import DiskFileNotExist, DiskFileError, \
DiskFileNoSpace, DiskFileNotOpen DiskFileNoSpace, DiskFileNotOpen
from swift.common.utils import ThreadPool from swift.common.utils import ThreadPool
@ -409,7 +410,7 @@ class TestDiskFile(unittest.TestCase):
dc = gluster.swift.obj.diskfile.do_chown dc = gluster.swift.obj.diskfile.do_chown
gluster.swift.obj.diskfile.do_chown = _mock_do_chown gluster.swift.obj.diskfile.do_chown = _mock_do_chown
self.assertRaises( self.assertRaises(
DiskFileError, gdf._create_dir_object, the_dir) AlreadyExistsAsFile, gdf._create_dir_object, the_dir)
gluster.swift.obj.diskfile.do_chown = dc gluster.swift.obj.diskfile.do_chown = dc
self.assertFalse(os.path.isdir(the_dir)) self.assertFalse(os.path.isdir(the_dir))
self.assertFalse(_mapit(the_dir) in _metadata) self.assertFalse(_mapit(the_dir) in _metadata)
@ -431,7 +432,7 @@ class TestDiskFile(unittest.TestCase):
dc = gluster.swift.obj.diskfile.do_chown dc = gluster.swift.obj.diskfile.do_chown
gluster.swift.obj.diskfile.do_chown = _mock_do_chown gluster.swift.obj.diskfile.do_chown = _mock_do_chown
self.assertRaises( self.assertRaises(
DiskFileError, gdf._create_dir_object, the_dir) AlreadyExistsAsFile, gdf._create_dir_object, the_dir)
gluster.swift.obj.diskfile.do_chown = dc gluster.swift.obj.diskfile.do_chown = dc
self.assertFalse(os.path.isdir(the_dir)) self.assertFalse(os.path.isdir(the_dir))
self.assertFalse(_mapit(the_dir) in _metadata) self.assertFalse(_mapit(the_dir) in _metadata)
@ -622,7 +623,7 @@ class TestDiskFile(unittest.TestCase):
# directory. # directory.
dw.write('12345\n') dw.write('12345\n')
dw.put(newmd) dw.put(newmd)
except DiskFileError: except AlreadyExistsAsDir:
pass pass
else: else:
self.fail("Expected to encounter" self.fail("Expected to encounter"