Skip to content

Commit

Permalink
Merge pull request #134 from owncloud/locking-with-provisioning
Browse files Browse the repository at this point in the history
Test file locking
  • Loading branch information
nickvergessen committed Mar 22, 2016
2 parents d36af48 + 5c9064a commit 33f7488
Show file tree
Hide file tree
Showing 3 changed files with 388 additions and 0 deletions.
245 changes: 245 additions & 0 deletions lib/owncloud/test_locking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import re

from smashbox.owncloudorg.locking import *
from smashbox.utilities import *
import os
import signal

__doc__ = """
Test locking enforcement
+------+------------------------------------+
| Step | User |
+------+------------------------------------+
| 2 | Enable QA testing app |
| 3 | Create dir/subdir/ |
| 4 | Populate locks |
| 5 | Try to upload dir/subdir/file2.dat |
| 6 | Remove locks |
| 7 | Upload dir/subdir/file2.dat |
+------+------------------------------------+
"""


DIR_NAME = 'dir'
SUBDIR_NAME = os.path.join(DIR_NAME, 'subdir')

testsets = [
{
'locks': [
{
'lock': LockProvider.LOCK_EXCLUSIVE,
'path': DIR_NAME
}
],
'can_upload': False
},
{
'locks': [
{
'lock': LockProvider.LOCK_SHARED,
'path': DIR_NAME
}
],
'can_upload': True
},
{
'locks': [
{
'lock': LockProvider.LOCK_EXCLUSIVE,
'path': SUBDIR_NAME
}
],
'can_upload': False
},
{
'locks': [
{
'lock': LockProvider.LOCK_SHARED,
'path': SUBDIR_NAME
}
],
'can_upload': True
},
{
'locks': [
{
'lock': LockProvider.LOCK_EXCLUSIVE,
'path': DIR_NAME
},
{
'lock': LockProvider.LOCK_SHARED,
'path': SUBDIR_NAME
}
],
'can_upload': False
},
{
'locks': [
{
'lock': LockProvider.LOCK_SHARED,
'path': DIR_NAME
},
{
'lock': LockProvider.LOCK_EXCLUSIVE,
'path': SUBDIR_NAME
}
],
'can_upload': False
},
{
'locks': [
{
'lock': LockProvider.LOCK_SHARED,
'path': DIR_NAME
},
{
'lock': LockProvider.LOCK_SHARED,
'path': SUBDIR_NAME
}
],
'can_upload': True
}
]

use_locks = config.get('locks', testsets[0]['locks'])
can_upload = config.get('can_upload', testsets[0]['can_upload'])
original_cmd = config.oc_sync_cmd


@add_worker
def owner_worker(step):

if compare_client_version('2.1.1', '<='):
# The client has a bug with permissions of folders on the first sync before 2.1.2
logger.warning('Skipping test, because the client version is known to behave incorrectly')
return

if compare_oc_version('9.0', '<='):
# The server has no fake locking support
logger.warning('Skipping test, because the server has no fake locking support')
return

oc_api = get_oc_api()
oc_api.login(config.oc_admin_user, config.oc_admin_password)
lock_provider = LockProvider(oc_api)
lock_provider.enable_testing_app()

if not lock_provider.isUsingDBLocking():
logger.warning('Skipping test, because DB Locking is not enabled or lock provisioning is not supported')
return

step(2, 'Create workdir')
d = make_workdir()

from owncloud import OCSResponseError
try:
lock_provider.unlock()
except OCSResponseError:
fatal_check(False, 'Testing App seems to not be enabled')

step(3, 'Create test folder')

mkdir(os.path.join(d, DIR_NAME))
mkdir(os.path.join(d, SUBDIR_NAME))
createfile(os.path.join(d, DIR_NAME, 'file.dat'), '0', count=1000, bs=1)
createfile(os.path.join(d, SUBDIR_NAME, 'sub_file.dat'), '0', count=1000, bs=1)

run_ocsync(d)

step(4, 'Lock items')

for lock in use_locks:
fatal_check(
lock_provider.is_locked(lock['lock'], config.oc_account_name, lock['path']) == False,
'Resource is already locked'
)

lock_provider.lock(lock['lock'], config.oc_account_name, lock['path'])

fatal_check(
lock_provider.is_locked(lock['lock'], config.oc_account_name, lock['path']),
'Resource should be locked'
)

step(5, 'Try to upload a file in locked item')

createfile(os.path.join(d, SUBDIR_NAME, 'file2.dat'), '0', count=1000, bs=1)

try:
save_run_ocsync(d, seconds=10, max_sync_retries=1)
except TimeoutError as err:
if compare_client_version('2.1.0', '>='):
# Max retries should terminate in time
error_check(False, err.message)
else:
# Client does not terminate before 2.1: https://github.com/owncloud/client/issues/4037
logger.warning(err.message)

if can_upload:
expect_webdav_exist(os.path.join(SUBDIR_NAME, 'file2.dat'))
else:
expect_webdav_does_not_exist(os.path.join(SUBDIR_NAME, 'file2.dat'))

step(6, 'Unlock item and sync again')

for lock in use_locks:
fatal_check(
lock_provider.is_locked(lock['lock'], config.oc_account_name, lock['path']),
'Resource is already locked'
)

lock_provider.unlock(lock['lock'], config.oc_account_name, lock['path'])

fatal_check(
lock_provider.is_locked(lock['lock'], config.oc_account_name, lock['path']) == False,
'Resource should be locked'
)

step(7, 'Upload a file in unlocked item')

run_ocsync(d)

expect_webdav_exist(os.path.join(SUBDIR_NAME, 'file2.dat'))

step(8, 'Final - Unlock everything')

lock_provider.unlock()
lock_provider.disable_testing_app()


class TimeoutError(Exception):
pass


def handler(signum, frame):
config.oc_sync_cmd = original_cmd
raise TimeoutError('Sync client did not terminate in time')


def save_run_ocsync(local_folder, seconds=10, max_sync_retries=1, remote_folder="", n=None, user_num=None):
"""
A save variation of run_ocsync, that terminates after n seconds or x retries depending on the client version
:param local_folder: The local folder to sync
:param seconds: Number of seconds until the request should be terminated
:param max_sync_retries: Number of retries for each sync
:param remote_folder: The remote target folder to sync to
:param n: Number of syncs
:param user_num: User number
"""

if compare_client_version('2.1.0', '>='):
pattern = re.compile(r' \-\-max\-sync\-retries \d+')
config.oc_sync_cmd = pattern.sub('', config.oc_sync_cmd)
config.oc_sync_cmd += ' --max-sync-retries %i' % max_sync_retries

signal.signal(signal.SIGALRM, handler)
signal.alarm(seconds)

# This run_ocsync() may hang indefinitely
run_ocsync(local_folder, remote_folder, n, user_num)

signal.alarm(0)
config.oc_sync_cmd = original_cmd
Empty file.
143 changes: 143 additions & 0 deletions python/smashbox/owncloudorg/locking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import owncloud

__author__ = 'nickv'


class LockProvider:
LOCK_SHARED = 1
LOCK_EXCLUSIVE = 2

def __init__(self, oc_api):
"""
:param oc_api owncloud.Client
"""
self.oc_api = oc_api

def enable_testing_app(self):
try:
self.oc_api.make_ocs_request(
'POST',
'cloud',
'apps/testing'
)
except owncloud.ResponseError as err:
raise err

def disable_testing_app(self):
try:
self.oc_api.make_ocs_request(
'DELETE',
'cloud',
'apps/testing'
)
except owncloud.ResponseError as err:
raise err

def isUsingDBLocking(self):
try:
kwargs = {'accepted_codes': [100, 501, 999]}
res = self.oc_api.make_ocs_request(
'GET',
'apps/testing/api/v1',
'lockprovisioning',
**kwargs
)

import xml.etree.ElementTree as ET
tree = ET.fromstring(res.content)
code_el = tree.find('meta/statuscode')

return int(code_el.text) == 100

except owncloud.ResponseError as err:
raise err


def lock(self, lock_level, user, path):
"""
Lock the path for the given user
:param lock_level: 1 (shared) or 2 (exclusive)
:param user: User to lock the path
:param path: Path to lock
:raises: ResponseError if the path could not be locked
"""
try:
self.oc_api.make_ocs_request(
'POST',
'apps/testing/api/v1',
'lockprovisioning/%i/%s?path=%s' % (lock_level, user, path)
)
except owncloud.ResponseError as err:
raise err

def change_lock(self, lock_level, user, path):
"""
Change an existing lock
:param lock_level: 1 (shared) or 2 (exclusive)
:param user: User to lock the path
:param path: Path to lock
:raises: ResponseError if the lock could not be changed
"""
try:
self.oc_api.make_ocs_request(
'PUT',
'apps/testing/api/v1',
'lockprovisioning/%i/%s?path=%s' % (lock_level, user, path)
)
except owncloud.ResponseError as err:
raise err

def is_locked(self, lock_level, user, path):
"""
Check whether the path is locked
:param lock_level: 1 (shared) or 2 (exclusive)
:param user: User to lock the path
:param path: Path to lock
:returns bool
"""
try:
kwargs = {'accepted_codes': [100, 423]}
res = self.oc_api.make_ocs_request(
'GET',
'apps/testing/api/v1',
'lockprovisioning/%i/%s?path=%s' % (lock_level, user, path),
**kwargs
)

import xml.etree.ElementTree as ET
tree = ET.fromstring(res.content)
code_el = tree.find('meta/statuscode')

return int(code_el.text) == 100

except owncloud.ResponseError as err:
raise err

def unlock(self, lock_level=None, user=None, path=None):
"""
Remove all set locks
:param lock_level: 1 (shared) or 2 (exclusive)
:param user: User to unlock the path
:param path: Path to unlock
:raises: ResponseError if the lock could not be removed
"""
ocs_path = 'lockprovisioning'

if lock_level is not None:
ocs_path = '%s/%i' % (ocs_path, lock_level)

if user is not None:
ocs_path = '%s/%s?path=%s' % (ocs_path, user, path)

try:
self.oc_api.make_ocs_request(
'DELETE',
'apps/testing/api/v1',
ocs_path
)
except owncloud.ResponseError as err:
raise err

0 comments on commit 33f7488

Please sign in to comment.