summaryrefslogtreecommitdiffstats
path: root/src/test/pybind
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/pybind')
-rw-r--r--src/test/pybind/CMakeLists.txt4
-rw-r--r--src/test/pybind/assertions.py26
-rw-r--r--src/test/pybind/pytest.ini9
-rwxr-xr-xsrc/test/pybind/test_ceph_argparse.py1334
-rwxr-xr-xsrc/test/pybind/test_ceph_daemon.py52
-rw-r--r--src/test/pybind/test_cephfs.py909
-rw-r--r--src/test/pybind/test_rados.py1543
-rw-r--r--src/test/pybind/test_rbd.py2810
-rw-r--r--src/test/pybind/test_rgwfs.py137
9 files changed, 6824 insertions, 0 deletions
diff --git a/src/test/pybind/CMakeLists.txt b/src/test/pybind/CMakeLists.txt
new file mode 100644
index 000000000..b2f0552a4
--- /dev/null
+++ b/src/test/pybind/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_ceph_test(test_ceph_daemon.py
+ ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_ceph_daemon.py)
+add_ceph_test(test_ceph_argparse.py
+ ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_ceph_argparse.py)
diff --git a/src/test/pybind/assertions.py b/src/test/pybind/assertions.py
new file mode 100644
index 000000000..719700f3a
--- /dev/null
+++ b/src/test/pybind/assertions.py
@@ -0,0 +1,26 @@
+def assert_equal(a, b):
+ assert a == b
+
+def assert_not_equal(a, b):
+ assert a != b
+
+def assert_greater(a, b):
+ assert a > b
+
+def assert_greater_equal(a, b):
+ assert a >= b
+
+def assert_raises(excClass, callableObj, *args, **kwargs):
+ """
+ Like unittest.TestCase.assertRaises, but returns the exception.
+ """
+ try:
+ callableObj(*args, **kwargs)
+ except excClass as e:
+ return e
+ else:
+ if hasattr(excClass, '__name__'):
+ excName = excClass.__name__
+ else:
+ excName = str(excClass)
+ raise AssertionError("%s not raised" % excName)
diff --git a/src/test/pybind/pytest.ini b/src/test/pybind/pytest.ini
new file mode 100644
index 000000000..dccf2a346
--- /dev/null
+++ b/src/test/pybind/pytest.ini
@@ -0,0 +1,9 @@
+[pytest]
+markers =
+ bench
+ ec
+ rollback
+ skip_if_crimson
+ stats
+ tier
+ watch
diff --git a/src/test/pybind/test_ceph_argparse.py b/src/test/pybind/test_ceph_argparse.py
new file mode 100755
index 000000000..6fe56bf98
--- /dev/null
+++ b/src/test/pybind/test_ceph_argparse.py
@@ -0,0 +1,1334 @@
+#!/usr/bin/env python3
+# -*- mode:python; tab-width:4; indent-tabs-mode:nil; coding:utf-8 -*-
+# vim: ts=4 sw=4 smarttab expandtab fileencoding=utf-8
+#
+# Ceph - scalable distributed file system
+#
+# Copyright (C) 2013,2014 Cloudwatt <libre.licensing@cloudwatt.com>
+# Copyright (C) 2014 Red Hat <contact@redhat.com>
+#
+# Author: Loic Dachary <loic@dachary.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+
+from ceph_argparse import validate_command, parse_json_funcsigs, validate, \
+ parse_funcsig, ArgumentError, ArgumentTooFew, ArgumentMissing, \
+ ArgumentNumber, ArgumentValid
+
+import os
+import random
+import re
+import string
+import sys
+import unittest
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+
+def get_command_descriptions(what):
+ CEPH_BIN = os.environ.get('CEPH_BIN', ".")
+ return os.popen(CEPH_BIN + "/get_command_descriptions " + "--" + what).read()
+
+
+class ParseJsonFuncsigs(unittest.TestCase):
+ def test_parse_json_funcsigs(self):
+ commands = get_command_descriptions("all")
+ cmd_json = parse_json_funcsigs(commands, 'cli')
+
+ # syntax error https://github.com/ceph/ceph/pull/585
+ commands = get_command_descriptions("pull585")
+ self.assertRaises(TypeError, parse_json_funcsigs, commands, 'cli')
+
+
+sigdict = parse_json_funcsigs(get_command_descriptions("all"), 'cli')
+
+
+class TestArgparse(unittest.TestCase):
+
+ def _assert_valid_command(self, args):
+ result = validate_command(sigdict, args)
+ self.assertNotIn(result, [{}, None])
+
+ def check_1_natural_arg(self, prefix, command):
+ self._assert_valid_command([prefix, command, '1'])
+ self.assertEqual({}, validate_command(sigdict, [prefix, command]))
+ self.assertEqual({}, validate_command(sigdict, [prefix, command, '-1']))
+ self.assertEqual({}, validate_command(sigdict, [prefix, command, '1',
+ '1']))
+
+ def check_0_or_1_natural_arg(self, prefix, command):
+ self._assert_valid_command([prefix, command, '1'])
+ self._assert_valid_command([prefix, command])
+ self.assertEqual({}, validate_command(sigdict, [prefix, command, '-1']))
+ self.assertEqual({}, validate_command(sigdict, [prefix, command, '1',
+ '1']))
+
+ def check_1_string_arg(self, prefix, command):
+ self.assertEqual({}, validate_command(sigdict, [prefix, command]))
+ self._assert_valid_command([prefix, command, 'string'])
+ self.assertEqual({}, validate_command(sigdict, [prefix,
+ command,
+ 'string',
+ 'toomany']))
+
+ def check_0_or_1_string_arg(self, prefix, command):
+ self._assert_valid_command([prefix, command, 'string'])
+ self._assert_valid_command([prefix, command])
+ self.assertEqual({}, validate_command(sigdict, [prefix,
+ command,
+ 'string',
+ 'toomany']))
+
+ def check_1_or_more_string_args(self, prefix, command):
+ self.assertEqual({}, validate_command(sigdict, [prefix,
+ command]))
+ self._assert_valid_command([prefix, command, 'string'])
+ self._assert_valid_command([prefix, command, 'string', 'more string'])
+
+ def check_no_arg(self, prefix, command):
+ self._assert_valid_command([prefix, command])
+ self.assertEqual({}, validate_command(sigdict, [prefix,
+ command,
+ 'toomany']))
+
+ def _capture_output(self, args, stdout=None, stderr=None):
+ if stdout:
+ stdout = StringIO()
+ sys.stdout = stdout
+ if stderr:
+ stderr = StringIO()
+ sys.stderr = stderr
+ ret = validate_command(sigdict, args)
+ if stdout:
+ stdout = stdout.getvalue().strip()
+ if stderr:
+ stderr = stderr.getvalue().strip()
+ return ret, stdout, stderr
+
+
+class TestBasic(unittest.TestCase):
+
+ def test_non_ascii_in_non_options(self):
+ # ArgumentPrefix("no match for {0}".format(s)) is not able to convert
+ # unicode str parameter into str. and validate_command() should not
+ # choke on it.
+ self.assertEqual({}, validate_command(sigdict, [u'章鱼和鱿鱼']))
+ self.assertEqual({}, validate_command(sigdict, [u'–w']))
+ # actually we always pass unicode strings to validate_command() in "ceph"
+ # CLI, but we also use bytestrings in our tests, so make sure it does not
+ # break.
+ self.assertEqual({}, validate_command(sigdict, ['章鱼和鱿鱼']))
+ self.assertEqual({}, validate_command(sigdict, ['–w']))
+
+
+class TestPG(TestArgparse):
+
+ def test_stat(self):
+ self._assert_valid_command(['pg', 'stat'])
+
+ def test_getmap(self):
+ self._assert_valid_command(['pg', 'getmap'])
+
+ def test_dump(self):
+ valid_commands = {
+ 'pg dump': {'prefix': 'pg dump'},
+ 'pg dump all summary sum delta pools osds pgs pgs_brief':
+ {'prefix': 'pg dump',
+ 'dumpcontents':
+ 'all summary sum delta pools osds pgs pgs_brief'.split()
+ },
+ 'pg dump --dumpcontents summary,sum':
+ {'prefix': 'pg dump',
+ 'dumpcontents': 'summary,sum'.split(',')
+ }
+ }
+ for command, expected_result in valid_commands.items():
+ actual_result = validate_command(sigdict, command.split())
+ expected_result['target'] = ('mon-mgr', '')
+ self.assertEqual(expected_result, actual_result)
+ invalid_commands = ['pg dump invalid']
+ for command in invalid_commands:
+ actual_result = validate_command(sigdict, command.split())
+ self.assertEqual({}, actual_result)
+
+ def test_dump_json(self):
+ self._assert_valid_command(['pg', 'dump_json'])
+ self._assert_valid_command(['pg', 'dump_json',
+ 'all',
+ 'summary',
+ 'sum',
+ 'pools',
+ 'osds',
+ 'pgs'])
+ self.assertEqual({}, validate_command(sigdict, ['pg', 'dump_json',
+ 'invalid']))
+
+ def test_dump_pools_json(self):
+ self._assert_valid_command(['pg', 'dump_pools_json'])
+
+ def test_dump_pools_stuck(self):
+ self._assert_valid_command(['pg', 'dump_stuck'])
+ self._assert_valid_command(['pg', 'dump_stuck',
+ 'inactive',
+ 'unclean',
+ 'stale'])
+ self.assertEqual({}, validate_command(sigdict, ['pg', 'dump_stuck',
+ 'invalid']))
+ self._assert_valid_command(['pg', 'dump_stuck',
+ 'inactive',
+ '1234'])
+
+ def one_pgid(self, command):
+ self._assert_valid_command(['pg', command, '1.1'])
+ self.assertEqual({}, validate_command(sigdict, ['pg', command]))
+ self.assertEqual({}, validate_command(sigdict, ['pg', command, '1']))
+
+ def test_map(self):
+ self.one_pgid('map')
+
+ def test_scrub(self):
+ self.one_pgid('scrub')
+
+ def test_deep_scrub(self):
+ self.one_pgid('deep-scrub')
+
+ def test_repair(self):
+ self.one_pgid('repair')
+
+ def test_debug(self):
+ self._assert_valid_command(['pg',
+ 'debug',
+ 'unfound_objects_exist'])
+ self._assert_valid_command(['pg',
+ 'debug',
+ 'degraded_pgs_exist'])
+ self.assertEqual({}, validate_command(sigdict, ['pg', 'debug']))
+ self.assertEqual({}, validate_command(sigdict, ['pg', 'debug',
+ 'invalid']))
+
+ def test_pg_missing_args_output(self):
+ ret, _, stderr = self._capture_output(['pg'], stderr=True)
+ self.assertEqual({}, ret)
+ self.assertRegexpMatches(stderr, re.compile('no valid command found.* closest matches'))
+
+ def test_pg_wrong_arg_output(self):
+ ret, _, stderr = self._capture_output(['pg', 'map', 'bad-pgid'],
+ stderr=True)
+ self.assertEqual({}, ret)
+ self.assertIn("Invalid command", stderr)
+
+
+class TestAuth(TestArgparse):
+
+ def test_export(self):
+ self._assert_valid_command(['auth', 'export'])
+ self._assert_valid_command(['auth', 'export', 'string'])
+ self.assertEqual({}, validate_command(sigdict, ['auth',
+ 'export',
+ 'string',
+ 'toomany']))
+
+ def test_get(self):
+ self.check_1_string_arg('auth', 'get')
+
+ def test_get_key(self):
+ self.check_1_string_arg('auth', 'get-key')
+
+ def test_print_key(self):
+ self.check_1_string_arg('auth', 'print-key')
+ self.check_1_string_arg('auth', 'print_key')
+
+ def test_list(self):
+ self.check_no_arg('auth', 'list')
+
+ def test_import(self):
+ self.check_no_arg('auth', 'import')
+
+ def test_add(self):
+ self.check_1_or_more_string_args('auth', 'add')
+
+ def test_get_or_create_key(self):
+ self.check_1_or_more_string_args('auth', 'get-or-create-key')
+ prefix = 'auth get-or-create-key'
+ entity = 'client.test'
+ caps = ['mon',
+ 'allow r',
+ 'osd',
+ 'allow rw pool=nfs-ganesha namespace=test, allow rw tag cephfs data=user_test_fs',
+ 'mds',
+ 'allow rw path=/']
+ cmd = prefix.split() + [entity] + caps
+ self.assertEqual(
+ {
+ 'prefix': prefix,
+ 'entity': entity,
+ 'caps': caps
+ }, validate_command(sigdict, cmd))
+
+ def test_get_or_create(self):
+ self.check_1_or_more_string_args('auth', 'get-or-create')
+
+ def test_caps(self):
+ self.assertEqual({}, validate_command(sigdict, ['auth',
+ 'caps']))
+ self.assertEqual({}, validate_command(sigdict, ['auth',
+ 'caps',
+ 'string']))
+ self._assert_valid_command(['auth',
+ 'caps',
+ 'string',
+ 'more string'])
+
+ def test_del(self):
+ self.check_1_string_arg('auth', 'del')
+
+
+class TestMonitor(TestArgparse):
+
+ def test_compact(self):
+ self._assert_valid_command(['compact'])
+
+ def test_fsid(self):
+ self._assert_valid_command(['fsid'])
+
+ def test_log(self):
+ self.assertEqual({}, validate_command(sigdict, ['log']))
+ self._assert_valid_command(['log', 'a logtext'])
+ self._assert_valid_command(['log', 'a logtext', 'and another'])
+
+ def test_injectargs(self):
+ self.assertEqual({}, validate_command(sigdict, ['injectargs']))
+ self._assert_valid_command(['injectargs', 'one'])
+ self._assert_valid_command(['injectargs', 'one', 'two'])
+
+ def test_status(self):
+ self._assert_valid_command(['status'])
+
+ def test_health(self):
+ self._assert_valid_command(['health'])
+ self._assert_valid_command(['health', 'detail'])
+ self.assertEqual({}, validate_command(sigdict, ['health', 'invalid']))
+ self.assertEqual({}, validate_command(sigdict, ['health', 'detail',
+ 'toomany']))
+
+ def test_df(self):
+ self._assert_valid_command(['df'])
+ self._assert_valid_command(['df', 'detail'])
+ self.assertEqual({}, validate_command(sigdict, ['df', 'invalid']))
+ self.assertEqual({}, validate_command(sigdict, ['df', 'detail',
+ 'toomany']))
+
+ def test_report(self):
+ self._assert_valid_command(['report'])
+ self._assert_valid_command(['report', 'tag1'])
+ self._assert_valid_command(['report', 'tag1', 'tag2'])
+
+ def test_quorum_status(self):
+ self._assert_valid_command(['quorum_status'])
+
+ def test_tell(self):
+ self.assertEqual({}, validate_command(sigdict, ['tell']))
+ self.assertEqual({}, validate_command(sigdict, ['tell', 'invalid']))
+ for name in ('osd', 'mon', 'client', 'mds'):
+ self.assertEqual({}, validate_command(sigdict, ['tell', name]))
+ self.assertEqual({}, validate_command(sigdict, ['tell',
+ name + ".42"]))
+ self._assert_valid_command(['tell', name + ".42", 'something'])
+ self._assert_valid_command(['tell', name + ".42",
+ 'something',
+ 'something else'])
+
+
+class TestMDS(TestArgparse):
+
+ def test_stat(self):
+ self.check_no_arg('mds', 'stat')
+
+ def test_compat_show(self):
+ self._assert_valid_command(['mds', 'compat', 'show'])
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'compat']))
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'compat',
+ 'show', 'toomany']))
+
+ def test_set_state(self):
+ self._assert_valid_command(['mds', 'set_state', '1', '2'])
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'set_state']))
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'set_state', '-1']))
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'set_state',
+ '1', '-1']))
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'set_state',
+ '1', '21']))
+
+ def test_fail(self):
+ self.check_1_string_arg('mds', 'fail')
+
+ def test_rm(self):
+ # Valid: single GID argument present
+ self._assert_valid_command(['mds', 'rm', '1'])
+
+ # Missing GID arg: invalid
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'rm']))
+ # Extra arg: invalid
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'rm', '1', 'mds.42']))
+
+ def test_rmfailed(self):
+ self._assert_valid_command(['mds', 'rmfailed', '0'])
+ self._assert_valid_command(['mds', 'rmfailed', '0', '--yes-i-really-mean-it'])
+ self.assertEqual({}, validate_command(sigdict, ['mds', 'rmfailed', '0',
+ '--yes-i-really-mean-it',
+ 'toomany']))
+
+ def test_compat_rm_compat(self):
+ self._assert_valid_command(['mds', 'compat', 'rm_compat', '1'])
+ self.assertEqual({}, validate_command(sigdict, ['mds',
+ 'compat',
+ 'rm_compat']))
+ self.assertEqual({}, validate_command(sigdict, ['mds',
+ 'compat',
+ 'rm_compat', '-1']))
+ self.assertEqual({}, validate_command(sigdict, ['mds',
+ 'compat',
+ 'rm_compat',
+ '1',
+ '1']))
+
+ def test_incompat_rm_incompat(self):
+ self._assert_valid_command(['mds', 'compat', 'rm_incompat', '1'])
+ self.assertEqual({}, validate_command(sigdict, ['mds',
+ 'compat',
+ 'rm_incompat']))
+ self.assertEqual({}, validate_command(sigdict, ['mds',
+ 'compat',
+ 'rm_incompat', '-1']))
+ self.assertEqual({}, validate_command(sigdict, ['mds',
+ 'compat',
+ 'rm_incompat',
+ '1',
+ '1']))
+
+
+class TestFS(TestArgparse):
+
+ def test_dump(self):
+ self.check_0_or_1_natural_arg('fs', 'dump')
+
+ def test_fs_new(self):
+ self._assert_valid_command(['fs', 'new', 'default', 'metadata', 'data'])
+
+ def test_fs_set_max_mds(self):
+ self._assert_valid_command(['fs', 'set', 'default', 'max_mds', '1'])
+ self._assert_valid_command(['fs', 'set', 'default', 'max_mds', '2'])
+
+ def test_fs_set_cluster_down(self):
+ self._assert_valid_command(['fs', 'set', 'default', 'down', 'true'])
+
+ def test_fs_set_cluster_up(self):
+ self._assert_valid_command(['fs', 'set', 'default', 'down', 'false'])
+
+ def test_fs_set_cluster_joinable(self):
+ self._assert_valid_command(['fs', 'set', 'default', 'joinable', 'true'])
+
+ def test_fs_set_cluster_not_joinable(self):
+ self._assert_valid_command(['fs', 'set', 'default', 'joinable', 'false'])
+
+ def test_fs_set(self):
+ self._assert_valid_command(['fs', 'set', 'default', 'max_file_size', '2'])
+ self._assert_valid_command(['fs', 'set', 'default', 'allow_new_snaps', 'no'])
+ self.assertEqual({}, validate_command(sigdict, ['fs',
+ 'set',
+ 'invalid']))
+
+ def test_fs_add_data_pool(self):
+ self._assert_valid_command(['fs', 'add_data_pool', 'default', '1'])
+ self._assert_valid_command(['fs', 'add_data_pool', 'default', 'foo'])
+
+ def test_fs_remove_data_pool(self):
+ self._assert_valid_command(['fs', 'rm_data_pool', 'default', '1'])
+ self._assert_valid_command(['fs', 'rm_data_pool', 'default', 'foo'])
+
+ def test_fs_rm(self):
+ self._assert_valid_command(['fs', 'rm', 'default'])
+ self._assert_valid_command(['fs', 'rm', 'default', '--yes-i-really-mean-it'])
+ self.assertEqual({}, validate_command(sigdict, ['fs', 'rm', 'default', '--yes-i-really-mean-it', 'toomany']))
+
+ def test_fs_ls(self):
+ self._assert_valid_command(['fs', 'ls'])
+ self.assertEqual({}, validate_command(sigdict, ['fs', 'ls', 'toomany']))
+
+ def test_fs_set_default(self):
+ self._assert_valid_command(['fs', 'set-default', 'cephfs'])
+ self.assertEqual({}, validate_command(sigdict, ['fs', 'set-default']))
+ self.assertEqual({}, validate_command(sigdict, ['fs', 'set-default', 'cephfs', 'toomany']))
+
+
+class TestMon(TestArgparse):
+
+ def test_dump(self):
+ self.check_0_or_1_natural_arg('mon', 'dump')
+
+ def test_stat(self):
+ self.check_no_arg('mon', 'stat')
+
+ def test_getmap(self):
+ self.check_0_or_1_natural_arg('mon', 'getmap')
+
+ def test_add(self):
+ self._assert_valid_command(['mon', 'add', 'name', '1.2.3.4:1234'])
+ self.assertEqual({}, validate_command(sigdict, ['mon', 'add']))
+ self.assertEqual({}, validate_command(sigdict, ['mon', 'add', 'name']))
+ self.assertEqual({}, validate_command(sigdict, ['mon', 'add',
+ 'name',
+ '400.500.600.700']))
+
+ def test_remove(self):
+ self._assert_valid_command(['mon', 'remove', 'name'])
+ self.assertEqual({}, validate_command(sigdict, ['mon', 'remove']))
+ self.assertEqual({}, validate_command(sigdict, ['mon', 'remove',
+ 'name', 'toomany']))
+
+
+class TestOSD(TestArgparse):
+
+ def test_stat(self):
+ self.check_no_arg('osd', 'stat')
+
+ def test_dump(self):
+ self.check_0_or_1_natural_arg('osd', 'dump')
+
+ def test_osd_tree(self):
+ self.check_0_or_1_natural_arg('osd', 'tree')
+ cmd = 'osd tree down,out'
+ self.assertEqual(
+ {
+ 'prefix': 'osd tree',
+ 'states': ['down', 'out']
+ }, validate_command(sigdict, cmd.split()))
+
+ def test_osd_ls(self):
+ self.check_0_or_1_natural_arg('osd', 'ls')
+
+ def test_osd_getmap(self):
+ self.check_0_or_1_natural_arg('osd', 'getmap')
+
+ def test_osd_getcrushmap(self):
+ self.check_0_or_1_natural_arg('osd', 'getcrushmap')
+
+ def test_perf(self):
+ self.check_no_arg('osd', 'perf')
+
+ def test_getmaxosd(self):
+ self.check_no_arg('osd', 'getmaxosd')
+
+ def test_find(self):
+ self.check_1_natural_arg('osd', 'find')
+
+ def test_map(self):
+ self._assert_valid_command(['osd', 'map', 'poolname', 'objectname'])
+ self._assert_valid_command(['osd', 'map', 'poolname', 'objectname', 'nspace'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'map']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'map', 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'map',
+ 'poolname', 'objectname', 'nspace',
+ 'toomany']))
+
+ def test_metadata(self):
+ self.check_0_or_1_natural_arg('osd', 'metadata')
+
+ def test_scrub(self):
+ self.check_1_string_arg('osd', 'scrub')
+
+ def test_deep_scrub(self):
+ self.check_1_string_arg('osd', 'deep-scrub')
+
+ def test_repair(self):
+ self.check_1_string_arg('osd', 'repair')
+
+ def test_lspools(self):
+ self._assert_valid_command(['osd', 'lspools'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'lspools',
+ 'toomany']))
+
+ def test_blocklist_ls(self):
+ self._assert_valid_command(['osd', 'blocklist', 'ls'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'blocklist']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'blocklist',
+ 'ls', 'toomany']))
+
+ def test_crush_rule(self):
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule']))
+ for subcommand in ('list', 'ls'):
+ self._assert_valid_command(['osd', 'crush', 'rule', subcommand])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rule', subcommand,
+ 'toomany']))
+
+ def test_crush_rule_dump(self):
+ self._assert_valid_command(['osd', 'crush', 'rule', 'dump'])
+ self._assert_valid_command(['osd', 'crush', 'rule', 'dump', 'RULE'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rule', 'dump',
+ 'RULE',
+ 'toomany']))
+
+ def test_crush_dump(self):
+ self._assert_valid_command(['osd', 'crush', 'dump'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'dump',
+ 'toomany']))
+
+ def test_setcrushmap(self):
+ self.check_no_arg('osd', 'setcrushmap')
+
+ def test_crush_add_bucket(self):
+ self._assert_valid_command(['osd', 'crush', 'add-bucket',
+ 'name', 'type'])
+ self._assert_valid_command(['osd', 'crush', 'add-bucket',
+ 'name', 'type', 'root=foo-root', 'host=foo-host'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'add-bucket']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'add-bucket', '^^^',
+ 'type']))
+
+ def test_crush_rename_bucket(self):
+ self._assert_valid_command(['osd', 'crush', 'rename-bucket',
+ 'srcname', 'dstname'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rename-bucket']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rename-bucket',
+ 'srcname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rename-bucket',
+ 'srcname',
+ 'dstname',
+ 'toomany']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rename-bucket', '^^^',
+ 'dstname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rename-bucket',
+ 'srcname',
+ '^^^^']))
+
+ def _check_crush_setter(self, setter):
+ self._assert_valid_command(['osd', 'crush', setter,
+ '*', '2.3', 'AZaz09-_.='])
+ self._assert_valid_command(['osd', 'crush', setter,
+ 'osd.0', '2.3', 'AZaz09-_.='])
+ self._assert_valid_command(['osd', 'crush', setter,
+ '0', '2.3', 'AZaz09-_.='])
+ self._assert_valid_command(['osd', 'crush', setter,
+ '0', '2.3', 'AZaz09-_.=', 'AZaz09-_.='])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ setter,
+ 'osd.0']))
+ ret = validate_command(sigdict, ['osd', 'crush',
+ setter,
+ 'osd.0',
+ '-1.0'])
+ assert ret in [None, {}]
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ setter,
+ 'osd.0',
+ '1.0',
+ '^^^']))
+
+ def test_crush_set(self):
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush']))
+ self._check_crush_setter('set')
+
+ def test_crush_add(self):
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush']))
+ self._check_crush_setter('add')
+
+ def test_crush_create_or_move(self):
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush']))
+ self._check_crush_setter('create-or-move')
+
+ def test_crush_move(self):
+ self._assert_valid_command(['osd', 'crush', 'move',
+ 'AZaz09-_.', 'AZaz09-_.='])
+ self._assert_valid_command(['osd', 'crush', 'move',
+ '0', 'AZaz09-_.=', 'AZaz09-_.='])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'move']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'move', 'AZaz09-_.']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'move', '^^^',
+ 'AZaz09-_.=']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'move', 'AZaz09-_.',
+ '^^^']))
+
+ def test_crush_link(self):
+ self._assert_valid_command(['osd', 'crush', 'link',
+ 'name', 'AZaz09-_.='])
+ self._assert_valid_command(['osd', 'crush', 'link',
+ 'name', 'AZaz09-_.=', 'AZaz09-_.='])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'link']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'link',
+ 'name']))
+
+ def test_crush_rm(self):
+ for alias in ('rm', 'remove', 'unlink'):
+ self._assert_valid_command(['osd', 'crush', alias, 'AZaz09-_.'])
+ self._assert_valid_command(['osd', 'crush', alias,
+ 'AZaz09-_.', 'AZaz09-_.'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ alias]))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ alias,
+ 'AZaz09-_.',
+ 'AZaz09-_.',
+ 'toomany']))
+
+ def test_crush_reweight(self):
+ self._assert_valid_command(['osd', 'crush', 'reweight',
+ 'AZaz09-_.', '2.3'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'reweight']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'reweight',
+ 'AZaz09-_.']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'reweight',
+ 'AZaz09-_.',
+ '-1.0']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'reweight',
+ '^^^',
+ '2.3']))
+
+ def test_crush_tunables(self):
+ for tunable in ('legacy', 'argonaut', 'bobtail', 'firefly',
+ 'optimal', 'default'):
+ self._assert_valid_command(['osd', 'crush', 'tunables',
+ tunable])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'tunables']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'tunables',
+ 'default', 'toomany']))
+
+ def test_crush_rule_create_simple(self):
+ self._assert_valid_command(['osd', 'crush', 'rule', 'create-simple',
+ 'AZaz09-_.', 'AZaz09-_.', 'AZaz09-_.'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-simple']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-simple',
+ 'AZaz09-_.']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-simple',
+ 'AZaz09-_.',
+ 'AZaz09-_.']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-simple',
+ '^^^',
+ 'AZaz09-_.',
+ 'AZaz09-_.']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-simple',
+ 'AZaz09-_.',
+ '|||',
+ 'AZaz09-_.']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-simple',
+ 'AZaz09-_.',
+ 'AZaz09-_.',
+ '+++']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-simple',
+ 'AZaz09-_.',
+ 'AZaz09-_.',
+ 'AZaz09-_.',
+ 'toomany']))
+
+ def test_crush_rule_create_erasure(self):
+ self._assert_valid_command(['osd', 'crush', 'rule', 'create-erasure',
+ 'AZaz09-_.'])
+ self._assert_valid_command(['osd', 'crush', 'rule', 'create-erasure',
+ 'AZaz09-_.', 'whatever'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-erasure']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-erasure',
+ '^^^']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush', 'rule',
+ 'create-erasure',
+ 'name', '^^^']))
+
+ def test_crush_rule_rm(self):
+ self._assert_valid_command(['osd', 'crush', 'rule', 'rm', 'AZaz09-_.'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rule', 'rm']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rule', 'rm',
+ '^^^^']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'crush',
+ 'rule', 'rm',
+ 'AZaz09-_.',
+ 'toomany']))
+
+ def test_setmaxosd(self):
+ self.check_1_natural_arg('osd', 'setmaxosd')
+
+ def test_pause(self):
+ self.check_no_arg('osd', 'pause')
+
+ def test_unpause(self):
+ self.check_no_arg('osd', 'unpause')
+
+ def test_erasure_code_profile_set(self):
+ self._assert_valid_command(['osd', 'erasure-code-profile', 'set',
+ 'name'])
+ self._assert_valid_command(['osd', 'erasure-code-profile', 'set',
+ 'name', 'A=B'])
+ self._assert_valid_command(['osd', 'erasure-code-profile', 'set',
+ 'name', 'A=B', 'C=D'])
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'erasure-code-profile',
+ 'set']))
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'erasure-code-profile',
+ 'set',
+ '^^^^']))
+
+ def test_erasure_code_profile_get(self):
+ self._assert_valid_command(['osd', 'erasure-code-profile', 'get',
+ 'name'])
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'erasure-code-profile',
+ 'get']))
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'erasure-code-profile',
+ 'get',
+ '^^^^']))
+
+ def test_erasure_code_profile_rm(self):
+ self._assert_valid_command(['osd', 'erasure-code-profile', 'rm',
+ 'name'])
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'erasure-code-profile',
+ 'rm']))
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'erasure-code-profile',
+ 'rm',
+ '^^^^']))
+
+ def test_erasure_code_profile_ls(self):
+ self._assert_valid_command(['osd', 'erasure-code-profile', 'ls'])
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'erasure-code-profile',
+ 'ls',
+ 'toomany']))
+
+ def test_set_unset(self):
+ for action in ('set', 'unset'):
+ for flag in ('pause', 'noup', 'nodown', 'noout', 'noin',
+ 'nobackfill', 'norecover', 'noscrub', 'nodeep-scrub'):
+ self._assert_valid_command(['osd', action, flag])
+ self.assertEqual({}, validate_command(sigdict, ['osd', action]))
+ self.assertEqual({}, validate_command(sigdict, ['osd', action,
+ 'invalid']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', action,
+ 'pause',
+ 'toomany']))
+
+ def test_down(self):
+ self.check_1_or_more_string_args('osd', 'down')
+
+ def test_out(self):
+ self.check_1_or_more_string_args('osd', 'out')
+
+ def test_in(self):
+ self.check_1_or_more_string_args('osd', 'in')
+
+ def test_rm(self):
+ self.check_1_or_more_string_args('osd', 'rm')
+
+ def test_reweight(self):
+ self._assert_valid_command(['osd', 'reweight', '1', '0.1'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'reweight']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'reweight',
+ '1']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'reweight',
+ '1', '2.0']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'reweight',
+ '-1', '0.1']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'reweight',
+ '1', '0.1',
+ 'toomany']))
+
+ def test_lost(self):
+ self._assert_valid_command(['osd', 'lost', '1',
+ '--yes-i-really-mean-it'])
+ self._assert_valid_command(['osd', 'lost', '1'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'lost']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'lost',
+ '1',
+ 'what?']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'lost',
+ '-1',
+ '--yes-i-really-mean-it']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'lost',
+ '1',
+ '--yes-i-really-mean-it',
+ 'toomany']))
+
+ def test_create(self):
+ uuid = '12345678123456781234567812345678'
+ self._assert_valid_command(['osd', 'create'])
+ self._assert_valid_command(['osd', 'create', uuid])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'create',
+ 'invalid']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'create',
+ uuid,
+ 'toomany']))
+
+ def test_blocklist(self):
+ for action in ('add', 'rm'):
+ self._assert_valid_command(['osd', 'blocklist', action,
+ '1.2.3.4/567'])
+ self._assert_valid_command(['osd', 'blocklist', action,
+ '1.2.3.4'])
+ self._assert_valid_command(['osd', 'blocklist', action,
+ '1.2.3.4/567', '600.40'])
+ self._assert_valid_command(['osd', 'blocklist', action,
+ '1.2.3.4', '600.40'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'blocklist',
+ action,
+ 'invalid',
+ '600.40']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'blocklist',
+ action,
+ '1.2.3.4/567',
+ '-1.0']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'blocklist',
+ action,
+ '1.2.3.4/567',
+ '600.40',
+ 'toomany']))
+
+ def test_pool_mksnap(self):
+ self._assert_valid_command(['osd', 'pool', 'mksnap',
+ 'poolname', 'snapname'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'mksnap']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'mksnap',
+ 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'mksnap',
+ 'poolname', 'snapname',
+ 'toomany']))
+
+ def test_pool_rmsnap(self):
+ self._assert_valid_command(['osd', 'pool', 'rmsnap',
+ 'poolname', 'snapname'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'rmsnap']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'rmsnap',
+ 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'rmsnap',
+ 'poolname', 'snapname',
+ 'toomany']))
+
+ def test_pool_kwargs(self):
+ """
+ Use the pool creation command to exercise keyword-style arguments
+ since it has lots of parameters
+ """
+ # Simply use a keyword arg instead of a positional arg, in its
+ # normal order (pgp_num after pg_num)
+ self.assertEqual(
+ {
+ "prefix": "osd pool create",
+ "pool": "foo",
+ "pg_num": 8,
+ "pgp_num": 16
+ }, validate_command(sigdict, [
+ 'osd', 'pool', 'create', "foo", "8", "--pgp_num", "16"]))
+
+ # Again, but using the "--foo=bar" style
+ self.assertEqual(
+ {
+ "prefix": "osd pool create",
+ "pool": "foo",
+ "pg_num": 8,
+ "pgp_num": 16
+ }, validate_command(sigdict, [
+ 'osd', 'pool', 'create', "foo", "8", "--pgp_num=16"]))
+
+ # Specify keyword args in a different order than their definitions
+ # (pgp_num after pool_type)
+ self.assertEqual(
+ {
+ "prefix": "osd pool create",
+ "pool": "foo",
+ "pg_num": 8,
+ "pgp_num": 16,
+ "pool_type": "replicated"
+ }, validate_command(sigdict, [
+ 'osd', 'pool', 'create', "foo", "8",
+ "--pool_type", "replicated",
+ "--pgp_num", "16"]))
+
+ # Use a keyword argument that doesn't exist, should fail validation
+ self.assertEqual({}, validate_command(sigdict,
+ ['osd', 'pool', 'create', "foo", "8", "--foo=bar"]))
+
+ def test_foo(self):
+ # Long form of a boolean argument (--foo=true)
+ self.assertEqual(
+ {
+ "prefix": "osd pool delete",
+ "pool": "foo",
+ "pool2": "foo",
+ "yes_i_really_really_mean_it": True
+ }, validate_command(sigdict, [
+ 'osd', 'pool', 'delete', "foo", "foo",
+ "--yes-i-really-really-mean-it=true"]))
+
+ def test_pool_bool_args(self):
+ """
+ Use pool deletion to exercise boolean arguments since it has
+ the --yes-i-really-really-mean-it flags
+ """
+
+ # Short form of a boolean argument (--foo)
+ self.assertEqual(
+ {
+ "prefix": "osd pool delete",
+ "pool": "foo",
+ "pool2": "foo",
+ "yes_i_really_really_mean_it": True
+ }, validate_command(sigdict, [
+ 'osd', 'pool', 'delete', "foo", "foo",
+ "--yes-i-really-really-mean-it"]))
+
+ # Long form of a boolean argument (--foo=true)
+ self.assertEqual(
+ {
+ "prefix": "osd pool delete",
+ "pool": "foo",
+ "pool2": "foo",
+ "yes_i_really_really_mean_it": True
+ }, validate_command(sigdict, [
+ 'osd', 'pool', 'delete', "foo", "foo",
+ "--yes-i-really-really-mean-it=true"]))
+
+ # Negative form of a boolean argument (--foo=false)
+ self.assertEqual(
+ {
+ "prefix": "osd pool delete",
+ "pool": "foo",
+ "pool2": "foo",
+ "yes_i_really_really_mean_it": False
+ }, validate_command(sigdict, [
+ 'osd', 'pool', 'delete', "foo", "foo",
+ "--yes-i-really-really-mean-it=false"]))
+
+ # Invalid value boolean argument (--foo=somethingelse)
+ self.assertEqual({}, validate_command(sigdict, [
+ 'osd', 'pool', 'delete', "foo", "foo",
+ "--yes-i-really-really-mean-it=rhubarb"]))
+
+ def test_pool_create(self):
+ self._assert_valid_command(['osd', 'pool', 'create',
+ 'poolname', '128'])
+ self._assert_valid_command(['osd', 'pool', 'create',
+ 'poolname', '128', '128'])
+ self._assert_valid_command(['osd', 'pool', 'create',
+ 'poolname', '128', '128',
+ 'replicated'])
+ self._assert_valid_command(['osd', 'pool', 'create',
+ 'poolname', '128', '128',
+ 'erasure', 'A-Za-z0-9-_.', 'rule^^'])
+ self._assert_valid_command(['osd', 'pool', 'create', 'poolname'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'create']))
+ # invalid pg_num and pgp_num, like "-1", could spill over to
+ # erasure_code_profile and rule as they are valid profile and rule
+ # names, so validate_commands() cannot identify such cases.
+ # but if they are matched by profile and rule, the "rule" argument
+ # won't get a chance to be matched anymore.
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'create',
+ 'poolname',
+ '-1', '-1',
+ 'rule']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'create',
+ 'poolname',
+ '128', '128',
+ 'erasure', '^^^',
+ 'rule']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'create',
+ 'poolname',
+ '128', '128',
+ 'erasure', 'profile',
+ 'rule',
+ 'toomany']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'create',
+ 'poolname',
+ '128', '128',
+ 'INVALID', 'profile',
+ 'rule']))
+
+ def test_pool_delete(self):
+ self._assert_valid_command(['osd', 'pool', 'delete',
+ 'poolname', 'poolname',
+ '--yes-i-really-really-mean-it'])
+ self._assert_valid_command(['osd', 'pool', 'delete',
+ 'poolname', 'poolname'])
+ self._assert_valid_command(['osd', 'pool', 'delete',
+ 'poolname'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'delete']))
+ self.assertEqual({}, validate_command(sigdict,
+ ['osd', 'pool', 'delete',
+ 'poolname', 'poolname',
+ '--yes-i-really-really-mean-it',
+ 'toomany']))
+
+ def test_pool_rename(self):
+ self._assert_valid_command(['osd', 'pool', 'rename',
+ 'poolname', 'othername'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'rename']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'rename',
+ 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool', 'rename',
+ 'poolname', 'othername',
+ 'toomany']))
+
+ def test_pool_get(self):
+ for var in ('size', 'min_size',
+ 'pg_num', 'pgp_num', 'crush_rule', 'fast_read',
+ 'scrub_min_interval', 'scrub_max_interval',
+ 'deep_scrub_interval', 'recovery_priority',
+ 'recovery_op_priority'):
+ self._assert_valid_command(['osd', 'pool', 'get', 'poolname', var])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'get']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'get', 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'get', 'poolname',
+ 'size', 'toomany']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'get', 'poolname',
+ 'invalid']))
+
+ def test_pool_set(self):
+ for var in ('size', 'min_size',
+ 'pg_num', 'pgp_num', 'crush_rule',
+ 'hashpspool', 'fast_read',
+ 'scrub_min_interval', 'scrub_max_interval',
+ 'deep_scrub_interval', 'recovery_priority',
+ 'recovery_op_priority'):
+ self._assert_valid_command(['osd', 'pool',
+ 'set', 'poolname', var, 'value'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set', 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set', 'poolname',
+ 'size', 'value',
+ 'toomany']))
+
+ def test_pool_set_quota(self):
+ for field in ('max_objects', 'max_bytes'):
+ self._assert_valid_command(['osd', 'pool', 'set-quota',
+ 'poolname', field, '10K'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set-quota']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set-quota',
+ 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set-quota',
+ 'poolname',
+ 'max_objects']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set-quota',
+ 'poolname',
+ 'invalid',
+ '10K']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'pool',
+ 'set-quota',
+ 'poolname',
+ 'max_objects',
+ '10K',
+ 'toomany']))
+
+ def test_reweight_by_utilization(self):
+ self._assert_valid_command(['osd', 'reweight-by-utilization'])
+ self._assert_valid_command(['osd', 'reweight-by-utilization', '100'])
+ self._assert_valid_command(['osd', 'reweight-by-utilization', '100', '.1'])
+ self.assertEqual({}, validate_command(sigdict, ['osd',
+ 'reweight-by-utilization',
+ '100',
+ 'toomany']))
+
+ def test_tier_op(self):
+ for op in ('add', 'remove', 'set-overlay'):
+ self._assert_valid_command(['osd', 'tier', op,
+ 'poolname', 'othername'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'tier', op]))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'tier', op,
+ 'poolname']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'tier', op,
+ 'poolname',
+ 'othername',
+ 'toomany']))
+
+ def test_tier_cache_mode(self):
+ for mode in ('none', 'writeback', 'readonly', 'readproxy'):
+ self._assert_valid_command(['osd', 'tier', 'cache-mode',
+ 'poolname', mode])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'tier',
+ 'cache-mode']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'tier',
+ 'cache-mode',
+ 'invalid']))
+
+ def test_tier_remove_overlay(self):
+ self._assert_valid_command(['osd', 'tier', 'remove-overlay',
+ 'poolname'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'tier',
+ 'remove-overlay']))
+ self.assertEqual({}, validate_command(sigdict, ['osd', 'tier',
+ 'remove-overlay',
+ 'poolname',
+ 'toomany']))
+
+ def _set_ratio(self, command):
+ self._assert_valid_command(['osd', command, '0.0'])
+ self.assertEqual({}, validate_command(sigdict, ['osd', command]))
+ self.assertEqual({}, validate_command(sigdict, ['osd', command, '2.0']))
+
+ def test_set_full_ratio(self):
+ self._set_ratio('set-full-ratio')
+
+ def test_set_backfillfull_ratio(self):
+ self._set_ratio('set-backfillfull-ratio')
+
+ def test_set_nearfull_ratio(self):
+ self._set_ratio('set-nearfull-ratio')
+
+
+class TestConfigKey(TestArgparse):
+
+ def test_get(self):
+ self.check_1_string_arg('config-key', 'get')
+
+ def test_put(self):
+ self._assert_valid_command(['config-key', 'put',
+ 'key'])
+ self._assert_valid_command(['config-key', 'put',
+ 'key', 'value'])
+ self.assertEqual({}, validate_command(sigdict, ['config-key', 'put']))
+ self.assertEqual({}, validate_command(sigdict, ['config-key', 'put',
+ 'key', 'value',
+ 'toomany']))
+
+ def test_del(self):
+ self.check_1_string_arg('config-key', 'del')
+
+ def test_exists(self):
+ self.check_1_string_arg('config-key', 'exists')
+
+ def test_dump(self):
+ self.check_0_or_1_string_arg('config-key', 'dump')
+
+ def test_list(self):
+ self.check_no_arg('config-key', 'list')
+
+
+class TestValidate(unittest.TestCase):
+
+ ARGS = 0
+ KWARGS = 1
+ KWARGS_EQ = 2
+ MIXED = 3
+
+ def setUp(self):
+ self.prefix = ['some', 'random', 'cmd']
+ self.args_dict = [
+ {'name': 'variable_one', 'type': 'CephString'},
+ {'name': 'variable_two', 'type': 'CephString'},
+ {'name': 'variable_three', 'type': 'CephString'},
+ {'name': 'variable_four', 'type': 'CephInt'},
+ {'name': 'variable_five', 'type': 'CephString'}]
+ self.args = []
+ for d in self.args_dict:
+ if d['type'] == 'CephInt':
+ val = "{}".format(random.randint(0, 100))
+ elif d['type'] == 'CephString':
+ letters = string.ascii_letters
+ str_len = random.randint(5, 10)
+ val = ''.join(random.choice(letters) for _ in range(str_len))
+ else:
+ raise skipTest()
+
+ self.args.append((d['name'], val))
+
+ self.sig = parse_funcsig(self.prefix + self.args_dict)
+
+ def _arg_kwarg_test(self, prefix, args, sig, arg_type=0):
+ """
+ Runs validate in different arg/kargs ways.
+
+ :param prefix: List of prefix commands (that can't be kwarged)
+ :param args: a list of kwarg, arg pairs: [(k1, v1), (k2, v2), ...]
+ :param sig: The sig to match
+ :param arg_type: how to build the args to send. As positional args (ARGS),
+ as long kwargs (KWARGS [--k v]), other style long kwargs
+ (KWARGS_EQ (--k=v]), and mixed (MIXED) where there will be
+ a random mix of the above.
+ :return: None, the method will assert.
+ """
+ final_args = list(prefix)
+ for k, v in args:
+ a_type = arg_type
+ if a_type == self.MIXED:
+ a_type = random.choice((self.ARGS,
+ self.KWARGS,
+ self.KWARGS_EQ))
+ if a_type == self.ARGS:
+ final_args.append(v)
+ elif a_type == self.KWARGS:
+ final_args.extend(["--{}".format(k), v])
+ else:
+ final_args.append("--{}={}".format(k, v))
+
+ try:
+ validate(final_args, sig)
+ except (ArgumentError, ArgumentMissing,
+ ArgumentNumber, ArgumentTooFew, ArgumentValid) as ex:
+ self.fail("Validation failed: {}".format(str(ex)))
+
+ def test_args_and_kwargs_validate(self):
+ for arg_type in (self.ARGS, self.KWARGS, self.KWARGS_EQ, self.MIXED):
+ self._arg_kwarg_test(self.prefix, self.args, self.sig, arg_type)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
+# Local Variables:
+# compile-command: "cd ../../..; cmake --build build --target get_command_descriptions -j4 &&
+# CEPH_BIN=build/bin \
+# PYTHONPATH=src/pybind python3 \
+# src/test/pybind/test_ceph_argparse.py"
+# End:
diff --git a/src/test/pybind/test_ceph_daemon.py b/src/test/pybind/test_ceph_daemon.py
new file mode 100755
index 000000000..df8d4c0b0
--- /dev/null
+++ b/src/test/pybind/test_ceph_daemon.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+# -*- mode:python; tab-width:4; indent-tabs-mode:t -*-
+# vim: ts=4 sw=4 smarttab expandtab
+#
+"""
+Copyright (C) 2015 Red Hat
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public
+License version 2, as published by the Free Software
+Foundation. See file COPYING.
+"""
+
+import unittest
+
+from ceph_daemon import DaemonWatcher
+
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+
+class TestDaemonWatcher(unittest.TestCase):
+ def test_format(self):
+ dw = DaemonWatcher(None)
+
+ self.assertEqual(dw.format_dimless(1, 4), " 1 ")
+ self.assertEqual(dw.format_dimless(1000, 4), "1.0k")
+ self.assertEqual(dw.format_dimless(3.14159, 4), " 3 ")
+ self.assertEqual(dw.format_dimless(1400000, 4), "1.4M")
+
+ def test_col_width(self):
+ dw = DaemonWatcher(None)
+
+ self.assertEqual(dw.col_width("foo"), 4)
+ self.assertEqual(dw.col_width("foobar"), 6)
+
+ def test_supports_color(self):
+ dw = DaemonWatcher(None)
+ # Can't count on having a tty available during tests, so only test the false case
+ self.assertFalse(dw.supports_color(StringIO()))
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+
+# Local Variables:
+# compile-command: "cd ../../..;
+# PYTHONPATH=src/pybind python3 src/test/pybind/test_ceph_daemon.py"
+# End:
diff --git a/src/test/pybind/test_cephfs.py b/src/test/pybind/test_cephfs.py
new file mode 100644
index 000000000..d16807de9
--- /dev/null
+++ b/src/test/pybind/test_cephfs.py
@@ -0,0 +1,909 @@
+# vim: expandtab smarttab shiftwidth=4 softtabstop=4
+from assertions import assert_raises, assert_equal, assert_not_equal, assert_greater
+import collections
+collections.Callable = collections.abc.Callable
+import cephfs as libcephfs
+import fcntl
+import os
+import pytest
+import random
+import time
+import stat
+import uuid
+from datetime import datetime
+
+cephfs = None
+
+def setup_module():
+ global cephfs
+ cephfs = libcephfs.LibCephFS(conffile='')
+ cephfs.mount()
+
+def teardown_module():
+ global cephfs
+ cephfs.shutdown()
+
+def purge_dir(path, is_snap = False):
+ print(b"Purge " + path)
+ d = cephfs.opendir(path)
+ if (not path.endswith(b"/")):
+ path = path + b"/"
+ dent = cephfs.readdir(d)
+ while dent:
+ if (dent.d_name not in [b".", b".."]):
+ print(path + dent.d_name)
+ if dent.is_dir():
+ if (not is_snap):
+ try:
+ snappath = path + dent.d_name + b"/.snap"
+ cephfs.stat(snappath)
+ purge_dir(snappath, True)
+ except:
+ pass
+ purge_dir(path + dent.d_name, False)
+ cephfs.rmdir(path + dent.d_name)
+ else:
+ print("rmsnap on {} snap {}".format(path, dent.d_name))
+ cephfs.rmsnap(path, dent.d_name);
+ else:
+ cephfs.unlink(path + dent.d_name)
+ dent = cephfs.readdir(d)
+ cephfs.closedir(d)
+
+@pytest.fixture
+def testdir():
+ purge_dir(b"/")
+
+ cephfs.chdir(b"/")
+ _, ret_buf = cephfs.listxattr("/")
+ print(f'ret_buf={ret_buf}')
+ xattrs = ret_buf.decode('utf-8').split('\x00')
+ for xattr in xattrs[:-1]:
+ cephfs.removexattr("/", xattr)
+
+def test_conf_get(testdir):
+ fsid = cephfs.conf_get("fsid")
+ assert(len(fsid) > 0)
+
+def test_version():
+ cephfs.version()
+
+def test_fstat(testdir):
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+ stat = cephfs.fstat(fd)
+ assert(len(stat) == 13)
+ cephfs.close(fd)
+
+def test_statfs(testdir):
+ stat = cephfs.statfs(b'/')
+ assert(len(stat) == 11)
+
+def test_statx(testdir):
+ stat = cephfs.statx(b'/', libcephfs.CEPH_STATX_MODE, 0)
+ assert('mode' in stat.keys())
+ stat = cephfs.statx(b'/', libcephfs.CEPH_STATX_BTIME, 0)
+ assert('btime' in stat.keys())
+
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ cephfs.close(fd)
+ cephfs.symlink(b'file-1', b'file-2')
+ stat = cephfs.statx(b'file-2', libcephfs.CEPH_STATX_MODE | libcephfs.CEPH_STATX_BTIME, libcephfs.AT_SYMLINK_NOFOLLOW)
+ assert('mode' in stat.keys())
+ assert('btime' in stat.keys())
+ cephfs.unlink(b'file-2')
+ cephfs.unlink(b'file-1')
+
+def test_syncfs(testdir):
+ stat = cephfs.sync_fs()
+
+def test_fsync(testdir):
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+ cephfs.write(fd, b"asdf", 0)
+ stat = cephfs.fsync(fd, 0)
+ cephfs.write(fd, b"qwer", 0)
+ stat = cephfs.fsync(fd, 1)
+ cephfs.close(fd)
+ #sync on non-existing fd (assume fd 12345 is not exists)
+ assert_raises(libcephfs.Error, cephfs.fsync, 12345, 0)
+
+def test_directory(testdir):
+ cephfs.mkdir(b"/temp-directory", 0o755)
+ cephfs.mkdirs(b"/temp-directory/foo/bar", 0o755)
+ cephfs.chdir(b"/temp-directory")
+ assert_equal(cephfs.getcwd(), b"/temp-directory")
+ cephfs.rmdir(b"/temp-directory/foo/bar")
+ cephfs.rmdir(b"/temp-directory/foo")
+ cephfs.rmdir(b"/temp-directory")
+ assert_raises(libcephfs.ObjectNotFound, cephfs.chdir, b"/temp-directory")
+
+def test_walk_dir(testdir):
+ cephfs.chdir(b"/")
+ dirs = [b"dir-1", b"dir-2", b"dir-3"]
+ for i in dirs:
+ cephfs.mkdir(i, 0o755)
+ handler = cephfs.opendir(b"/")
+ d = cephfs.readdir(handler)
+ dirs += [b".", b".."]
+ while d:
+ assert(d.d_name in dirs)
+ dirs.remove(d.d_name)
+ d = cephfs.readdir(handler)
+ assert(len(dirs) == 0)
+ dirs = [b"/dir-1", b"/dir-2", b"/dir-3"]
+ for i in dirs:
+ cephfs.rmdir(i)
+ cephfs.closedir(handler)
+
+def test_xattr(testdir):
+ assert_raises(libcephfs.OperationNotSupported, cephfs.setxattr, "/", "key", b"value", 0)
+ cephfs.setxattr("/", "user.key", b"value", 0)
+ assert_equal(b"value", cephfs.getxattr("/", "user.key"))
+
+ cephfs.setxattr("/", "user.big", b"x" * 300, 0)
+
+ # Default size is 255, get ERANGE
+ assert_raises(libcephfs.OutOfRange, cephfs.getxattr, "/", "user.big")
+
+ # Pass explicit size, and we'll get the value
+ assert_equal(300, len(cephfs.getxattr("/", "user.big", 300)))
+
+ cephfs.removexattr("/", "user.key")
+ # user.key is already removed
+ assert_raises(libcephfs.NoData, cephfs.getxattr, "/", "user.key")
+
+ # user.big is only listed
+ ret_val, ret_buff = cephfs.listxattr("/")
+ assert_equal(9, ret_val)
+ assert_equal("user.big\x00", ret_buff.decode('utf-8'))
+
+def test_ceph_mirror_xattr(testdir):
+ def gen_mirror_xattr():
+ cluster_id = str(uuid.uuid4())
+ fs_id = random.randint(1, 10)
+ mirror_xattr = f'cluster_id={cluster_id} fs_id={fs_id}'
+ return mirror_xattr.encode('utf-8')
+
+ mirror_xattr_enc_1 = gen_mirror_xattr()
+
+ # mirror xattr is only allowed on root
+ cephfs.mkdir('/d0', 0o755)
+ assert_raises(libcephfs.InvalidValue, cephfs.setxattr,
+ '/d0', 'ceph.mirror.info', mirror_xattr_enc_1, os.XATTR_CREATE)
+ cephfs.rmdir('/d0')
+
+ cephfs.setxattr('/', 'ceph.mirror.info', mirror_xattr_enc_1, os.XATTR_CREATE)
+ assert_equal(mirror_xattr_enc_1, cephfs.getxattr('/', 'ceph.mirror.info'))
+
+ # setting again with XATTR_CREATE should fail
+ assert_raises(libcephfs.ObjectExists, cephfs.setxattr,
+ '/', 'ceph.mirror.info', mirror_xattr_enc_1, os.XATTR_CREATE)
+
+ # ceph.mirror.info should not show up in listing
+ ret_val, _ = cephfs.listxattr("/")
+ assert_equal(0, ret_val)
+
+ mirror_xattr_enc_2 = gen_mirror_xattr()
+
+ cephfs.setxattr('/', 'ceph.mirror.info', mirror_xattr_enc_2, os.XATTR_REPLACE)
+ assert_equal(mirror_xattr_enc_2, cephfs.getxattr('/', 'ceph.mirror.info'))
+
+ cephfs.removexattr('/', 'ceph.mirror.info')
+ # ceph.mirror.info is already removed
+ assert_raises(libcephfs.NoData, cephfs.getxattr, '/', 'ceph.mirror.info')
+ # removing again should throw error
+ assert_raises(libcephfs.NoData, cephfs.removexattr, "/", "ceph.mirror.info")
+
+ # check mirror info xattr format
+ assert_raises(libcephfs.InvalidValue, cephfs.setxattr, '/', 'ceph.mirror.info', b"unknown", 0)
+
+def test_fxattr(testdir):
+ fd = cephfs.open(b'/file-fxattr', 'w', 0o755)
+ assert_raises(libcephfs.OperationNotSupported, cephfs.fsetxattr, fd, "key", b"value", 0)
+ assert_raises(TypeError, cephfs.fsetxattr, "fd", "user.key", b"value", 0)
+ assert_raises(TypeError, cephfs.fsetxattr, fd, "user.key", "value", 0)
+ assert_raises(TypeError, cephfs.fsetxattr, fd, "user.key", b"value", "0")
+ cephfs.fsetxattr(fd, "user.key", b"value", 0)
+ assert_equal(b"value", cephfs.fgetxattr(fd, "user.key"))
+
+ cephfs.fsetxattr(fd, "user.big", b"x" * 300, 0)
+
+ # Default size is 255, get ERANGE
+ assert_raises(libcephfs.OutOfRange, cephfs.fgetxattr, fd, "user.big")
+
+ # Pass explicit size, and we'll get the value
+ assert_equal(300, len(cephfs.fgetxattr(fd, "user.big", 300)))
+
+ cephfs.fremovexattr(fd, "user.key")
+ # user.key is already removed
+ assert_raises(libcephfs.NoData, cephfs.fgetxattr, fd, "user.key")
+
+ # user.big is only listed
+ ret_val, ret_buff = cephfs.flistxattr(fd)
+ assert_equal(9, ret_val)
+ assert_equal("user.big\x00", ret_buff.decode('utf-8'))
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-fxattr')
+
+def test_rename(testdir):
+ cephfs.mkdir(b"/a", 0o755)
+ cephfs.mkdir(b"/a/b", 0o755)
+ cephfs.rename(b"/a", b"/b")
+ cephfs.stat(b"/b/b")
+ cephfs.rmdir(b"/b/b")
+ cephfs.rmdir(b"/b")
+
+def test_open(testdir):
+ assert_raises(libcephfs.ObjectNotFound, cephfs.open, b'file-1', 'r')
+ assert_raises(libcephfs.ObjectNotFound, cephfs.open, b'file-1', 'r+')
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+ cephfs.write(fd, b"asdf", 0)
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-1', 'r', 0o755)
+ assert_equal(cephfs.read(fd, 0, 4), b"asdf")
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-1', 'r+', 0o755)
+ cephfs.write(fd, b"zxcv", 4)
+ assert_equal(cephfs.read(fd, 4, 8), b"zxcv")
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-1', 'w+', 0o755)
+ assert_equal(cephfs.read(fd, 0, 4), b"")
+ cephfs.write(fd, b"zxcv", 4)
+ assert_equal(cephfs.read(fd, 4, 8), b"zxcv")
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-1', os.O_RDWR, 0o755)
+ cephfs.write(fd, b"asdf", 0)
+ assert_equal(cephfs.read(fd, 0, 4), b"asdf")
+ cephfs.close(fd)
+ assert_raises(libcephfs.OperationNotSupported, cephfs.open, b'file-1', 'a')
+ cephfs.unlink(b'file-1')
+
+def test_link(testdir):
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ cephfs.close(fd)
+ cephfs.link(b'file-1', b'file-2')
+ fd = cephfs.open(b'file-2', 'r', 0o755)
+ assert_equal(cephfs.read(fd, 0, 4), b"1111")
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-2', 'r+', 0o755)
+ cephfs.write(fd, b"2222", 4)
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-1', 'r', 0o755)
+ assert_equal(cephfs.read(fd, 0, 8), b"11112222")
+ cephfs.close(fd)
+ cephfs.unlink(b'file-2')
+
+def test_symlink(testdir):
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ cephfs.close(fd)
+ cephfs.symlink(b'file-1', b'file-2')
+ fd = cephfs.open(b'file-2', 'r', 0o755)
+ assert_equal(cephfs.read(fd, 0, 4), b"1111")
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-2', 'r+', 0o755)
+ cephfs.write(fd, b"2222", 4)
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-1', 'r', 0o755)
+ assert_equal(cephfs.read(fd, 0, 8), b"11112222")
+ cephfs.close(fd)
+ cephfs.unlink(b'file-2')
+
+def test_readlink(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ cephfs.close(fd)
+ cephfs.symlink(b'/file-1', b'/file-2')
+ d = cephfs.readlink(b"/file-2",100)
+ assert_equal(d, b"/file-1")
+ cephfs.unlink(b'/file-2')
+ cephfs.unlink(b'/file-1')
+
+def test_delete_cwd(testdir):
+ assert_equal(b"/", cephfs.getcwd())
+
+ cephfs.mkdir(b"/temp-directory", 0o755)
+ cephfs.chdir(b"/temp-directory")
+ cephfs.rmdir(b"/temp-directory")
+
+ # getcwd gives you something stale here: it remembers the path string
+ # even when things are unlinked. It's up to the caller to find out
+ # whether it really still exists
+ assert_equal(b"/temp-directory", cephfs.getcwd())
+
+def test_flock(testdir):
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+
+ cephfs.flock(fd, fcntl.LOCK_EX, 123);
+ fd2 = cephfs.open(b'file-1', 'w', 0o755)
+
+ assert_raises(libcephfs.WouldBlock, cephfs.flock, fd2,
+ fcntl.LOCK_EX | fcntl.LOCK_NB, 456);
+ cephfs.close(fd2)
+
+ cephfs.close(fd)
+
+def test_mount_unmount(testdir):
+ test_directory(testdir)
+ cephfs.unmount()
+ cephfs.mount()
+ test_open(testdir)
+
+def test_lxattr(testdir):
+ fd = cephfs.open(b'/file-lxattr', 'w', 0o755)
+ cephfs.close(fd)
+ cephfs.setxattr(b"/file-lxattr", "user.key", b"value", 0)
+ cephfs.symlink(b"/file-lxattr", b"/file-sym-lxattr")
+ assert_equal(b"value", cephfs.getxattr(b"/file-sym-lxattr", "user.key"))
+ assert_raises(libcephfs.NoData, cephfs.lgetxattr, b"/file-sym-lxattr", "user.key")
+
+ cephfs.lsetxattr(b"/file-sym-lxattr", "trusted.key-sym", b"value-sym", 0)
+ assert_equal(b"value-sym", cephfs.lgetxattr(b"/file-sym-lxattr", "trusted.key-sym"))
+ cephfs.lsetxattr(b"/file-sym-lxattr", "trusted.big", b"x" * 300, 0)
+
+ # Default size is 255, get ERANGE
+ assert_raises(libcephfs.OutOfRange, cephfs.lgetxattr, b"/file-sym-lxattr", "trusted.big")
+
+ # Pass explicit size, and we'll get the value
+ assert_equal(300, len(cephfs.lgetxattr(b"/file-sym-lxattr", "trusted.big", 300)))
+
+ cephfs.lremovexattr(b"/file-sym-lxattr", "trusted.key-sym")
+ # trusted.key-sym is already removed
+ assert_raises(libcephfs.NoData, cephfs.lgetxattr, b"/file-sym-lxattr", "trusted.key-sym")
+
+ # trusted.big is only listed
+ ret_val, ret_buff = cephfs.llistxattr(b"/file-sym-lxattr")
+ assert_equal(12, ret_val)
+ assert_equal("trusted.big\x00", ret_buff.decode('utf-8'))
+ cephfs.unlink(b'/file-lxattr')
+ cephfs.unlink(b'/file-sym-lxattr')
+
+def test_mount_root(testdir):
+ cephfs.mkdir(b"/mount-directory", 0o755)
+ cephfs.unmount()
+ cephfs.mount(mount_root = b"/mount-directory")
+
+ assert_raises(libcephfs.Error, cephfs.mount, mount_root = b"/nowhere")
+ cephfs.unmount()
+ cephfs.mount()
+
+def test_utime(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b'0000', 0)
+ cephfs.close(fd)
+
+ stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ time.sleep(1)
+ cephfs.utime(b'/file-1')
+
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_greater(stx_post['atime'], stx_pre['atime'])
+ assert_greater(stx_post['mtime'], stx_pre['mtime'])
+
+ atime_pre = int(time.mktime(stx_pre['atime'].timetuple()))
+ mtime_pre = int(time.mktime(stx_pre['mtime'].timetuple()))
+
+ cephfs.utime(b'/file-1', (atime_pre, mtime_pre))
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_equal(stx_post['atime'], stx_pre['atime'])
+ assert_equal(stx_post['mtime'], stx_pre['mtime'])
+
+ cephfs.unlink(b'/file-1')
+
+def test_futime(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b'0000', 0)
+
+ stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ time.sleep(1)
+ cephfs.futime(fd)
+
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_greater(stx_post['atime'], stx_pre['atime'])
+ assert_greater(stx_post['mtime'], stx_pre['mtime'])
+
+ atime_pre = int(time.mktime(stx_pre['atime'].timetuple()))
+ mtime_pre = int(time.mktime(stx_pre['mtime'].timetuple()))
+
+ cephfs.futime(fd, (atime_pre, mtime_pre))
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_equal(stx_post['atime'], stx_pre['atime'])
+ assert_equal(stx_post['mtime'], stx_pre['mtime'])
+
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-1')
+
+def test_utimes(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b'0000', 0)
+ cephfs.close(fd)
+
+ stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ time.sleep(1)
+ cephfs.utimes(b'/file-1')
+
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_greater(stx_post['atime'], stx_pre['atime'])
+ assert_greater(stx_post['mtime'], stx_pre['mtime'])
+
+ atime_pre = time.mktime(stx_pre['atime'].timetuple())
+ mtime_pre = time.mktime(stx_pre['mtime'].timetuple())
+
+ cephfs.utimes(b'/file-1', (atime_pre, mtime_pre))
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_equal(stx_post['atime'], stx_pre['atime'])
+ assert_equal(stx_post['mtime'], stx_pre['mtime'])
+
+ cephfs.unlink(b'/file-1')
+
+def test_lutimes(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b'0000', 0)
+ cephfs.close(fd)
+
+ cephfs.symlink(b'/file-1', b'/file-2')
+
+ stx_pre_t = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+ stx_pre_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, libcephfs.AT_SYMLINK_NOFOLLOW)
+
+ time.sleep(1)
+ cephfs.lutimes(b'/file-2')
+
+ stx_post_t = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+ stx_post_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, libcephfs.AT_SYMLINK_NOFOLLOW)
+
+ assert_equal(stx_post_t['atime'], stx_pre_t['atime'])
+ assert_equal(stx_post_t['mtime'], stx_pre_t['mtime'])
+
+ assert_greater(stx_post_s['atime'], stx_pre_s['atime'])
+ assert_greater(stx_post_s['mtime'], stx_pre_s['mtime'])
+
+ atime_pre = time.mktime(stx_pre_s['atime'].timetuple())
+ mtime_pre = time.mktime(stx_pre_s['mtime'].timetuple())
+
+ cephfs.lutimes(b'/file-2', (atime_pre, mtime_pre))
+ stx_post_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, libcephfs.AT_SYMLINK_NOFOLLOW)
+
+ assert_equal(stx_post_s['atime'], stx_pre_s['atime'])
+ assert_equal(stx_post_s['mtime'], stx_pre_s['mtime'])
+
+ cephfs.unlink(b'/file-2')
+ cephfs.unlink(b'/file-1')
+
+def test_futimes(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b'0000', 0)
+
+ stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ time.sleep(1)
+ cephfs.futimes(fd)
+
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_greater(stx_post['atime'], stx_pre['atime'])
+ assert_greater(stx_post['mtime'], stx_pre['mtime'])
+
+ atime_pre = time.mktime(stx_pre['atime'].timetuple())
+ mtime_pre = time.mktime(stx_pre['mtime'].timetuple())
+
+ cephfs.futimes(fd, (atime_pre, mtime_pre))
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_equal(stx_post['atime'], stx_pre['atime'])
+ assert_equal(stx_post['mtime'], stx_pre['mtime'])
+
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-1')
+
+def test_futimens(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b'0000', 0)
+
+ stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ time.sleep(1)
+ cephfs.futimens(fd)
+
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_greater(stx_post['atime'], stx_pre['atime'])
+ assert_greater(stx_post['mtime'], stx_pre['mtime'])
+
+ atime_pre = time.mktime(stx_pre['atime'].timetuple())
+ mtime_pre = time.mktime(stx_pre['mtime'].timetuple())
+
+ cephfs.futimens(fd, (atime_pre, mtime_pre))
+ stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0)
+
+ assert_equal(stx_post['atime'], stx_pre['atime'])
+ assert_equal(stx_post['mtime'], stx_pre['mtime'])
+
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-1')
+
+def test_lchmod(testdir):
+ fd = cephfs.open(b'/file-1', 'w', 0o755)
+ cephfs.write(fd, b'0000', 0)
+ cephfs.close(fd)
+
+ cephfs.symlink(b'/file-1', b'/file-2')
+
+ stx_pre_t = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_MODE, 0)
+ stx_pre_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_MODE, libcephfs.AT_SYMLINK_NOFOLLOW)
+
+ time.sleep(1)
+ cephfs.lchmod(b'/file-2', 0o400)
+
+ stx_post_t = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_MODE, 0)
+ stx_post_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_MODE, libcephfs.AT_SYMLINK_NOFOLLOW)
+
+ assert_equal(stx_post_t['mode'], stx_pre_t['mode'])
+ assert_not_equal(stx_post_s['mode'], stx_pre_s['mode'])
+ stx_post_s_perm_bits = stx_post_s['mode'] & ~stat.S_IFMT(stx_post_s["mode"])
+ assert_equal(stx_post_s_perm_bits, 0o400)
+
+ cephfs.unlink(b'/file-2')
+ cephfs.unlink(b'/file-1')
+
+def test_fchmod(testdir):
+ fd = cephfs.open(b'/file-fchmod', 'w', 0o655)
+ st = cephfs.statx(b'/file-fchmod', libcephfs.CEPH_STATX_MODE, 0)
+ mode = st["mode"] | stat.S_IXUSR
+ cephfs.fchmod(fd, mode)
+ st = cephfs.statx(b'/file-fchmod', libcephfs.CEPH_STATX_MODE, 0)
+ assert_equal(st["mode"] & stat.S_IRWXU, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
+ assert_raises(TypeError, cephfs.fchmod, "/file-fchmod", stat.S_IXUSR)
+ assert_raises(TypeError, cephfs.fchmod, fd, "stat.S_IXUSR")
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-fchmod')
+
+def test_fchown(testdir):
+ fd = cephfs.open(b'/file-fchown', 'w', 0o655)
+ uid = os.getuid()
+ gid = os.getgid()
+ assert_raises(TypeError, cephfs.fchown, b'/file-fchown', uid, gid)
+ assert_raises(TypeError, cephfs.fchown, fd, "uid", "gid")
+ cephfs.fchown(fd, uid, gid)
+ st = cephfs.statx(b'/file-fchown', libcephfs.CEPH_STATX_UID | libcephfs.CEPH_STATX_GID, 0)
+ assert_equal(st["uid"], uid)
+ assert_equal(st["gid"], gid)
+ cephfs.fchown(fd, 9999, 9999)
+ st = cephfs.statx(b'/file-fchown', libcephfs.CEPH_STATX_UID | libcephfs.CEPH_STATX_GID, 0)
+ assert_equal(st["uid"], 9999)
+ assert_equal(st["gid"], 9999)
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-fchown')
+
+def test_truncate(testdir):
+ fd = cephfs.open(b'/file-truncate', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ cephfs.truncate(b'/file-truncate', 0)
+ stat = cephfs.fsync(fd, 0)
+ st = cephfs.statx(b'/file-truncate', libcephfs.CEPH_STATX_SIZE, 0)
+ assert_equal(st["size"], 0)
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-truncate')
+
+def test_ftruncate(testdir):
+ fd = cephfs.open(b'/file-ftruncate', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ assert_raises(TypeError, cephfs.ftruncate, b'/file-ftruncate', 0)
+ cephfs.ftruncate(fd, 0)
+ stat = cephfs.fsync(fd, 0)
+ st = cephfs.fstat(fd)
+ assert_equal(st.st_size, 0)
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-ftruncate')
+
+def test_fallocate(testdir):
+ fd = cephfs.open(b'/file-fallocate', 'w', 0o755)
+ assert_raises(TypeError, cephfs.fallocate, b'/file-fallocate', 0, 10)
+ cephfs.fallocate(fd, 0, 10)
+ stat = cephfs.fsync(fd, 0)
+ st = cephfs.fstat(fd)
+ assert_equal(st.st_size, 10)
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-fallocate')
+
+def test_mknod(testdir):
+ mode = stat.S_IFIFO | stat.S_IRUSR | stat.S_IWUSR
+ cephfs.mknod(b'/file-fifo', mode)
+ st = cephfs.statx(b'/file-fifo', libcephfs.CEPH_STATX_MODE, 0)
+ assert_equal(st["mode"] & mode, mode)
+ cephfs.unlink(b'/file-fifo')
+
+def test_lazyio(testdir):
+ fd = cephfs.open(b'/file-lazyio', 'w', 0o755)
+ assert_raises(TypeError, cephfs.lazyio, "fd", 1)
+ assert_raises(TypeError, cephfs.lazyio, fd, "1")
+ cephfs.lazyio(fd, 1)
+ cephfs.write(fd, b"1111", 0)
+ assert_raises(TypeError, cephfs.lazyio_propagate, "fd", 0, 4)
+ assert_raises(TypeError, cephfs.lazyio_propagate, fd, "0", 4)
+ assert_raises(TypeError, cephfs.lazyio_propagate, fd, 0, "4")
+ cephfs.lazyio_propagate(fd, 0, 4)
+ st = cephfs.fstat(fd)
+ assert_equal(st.st_size, 4)
+ cephfs.write(fd, b"2222", 4)
+ assert_raises(TypeError, cephfs.lazyio_synchronize, "fd", 0, 8)
+ assert_raises(TypeError, cephfs.lazyio_synchronize, fd, "0", 8)
+ assert_raises(TypeError, cephfs.lazyio_synchronize, fd, 0, "8")
+ cephfs.lazyio_synchronize(fd, 0, 8)
+ st = cephfs.fstat(fd)
+ assert_equal(st.st_size, 8)
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-lazyio')
+
+def test_replication(testdir):
+ fd = cephfs.open(b'/file-rep', 'w', 0o755)
+ assert_raises(TypeError, cephfs.get_file_replication, "fd")
+ l_dict = cephfs.get_layout(fd)
+ assert('pool_name' in l_dict.keys())
+ cnt = cephfs.get_file_replication(fd)
+ get_rep_cnt_cmd = "ceph osd pool get " + l_dict["pool_name"] + " size"
+ s=os.popen(get_rep_cnt_cmd).read().strip('\n')
+ size=int(s.split(" ")[-1])
+ assert_equal(cnt, size)
+ cnt = cephfs.get_path_replication(b'/file-rep')
+ assert_equal(cnt, size)
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-rep')
+
+def test_caps(testdir):
+ fd = cephfs.open(b'/file-caps', 'w', 0o755)
+ timeout = cephfs.get_cap_return_timeout()
+ assert_equal(timeout, 300)
+ fd_caps = cephfs.debug_get_fd_caps(fd)
+ file_caps = cephfs.debug_get_file_caps(b'/file-caps')
+ assert_equal(fd_caps, file_caps)
+ cephfs.close(fd)
+ cephfs.unlink(b'/file-caps')
+
+def test_setuuid(testdir):
+ ses_id_uid = uuid.uuid1()
+ ses_id_str = str(ses_id_uid)
+ cephfs.set_uuid(ses_id_str)
+
+def test_session_timeout(testdir):
+ assert_raises(TypeError, cephfs.set_session_timeout, "300")
+ cephfs.set_session_timeout(300)
+
+def test_readdirops(testdir):
+ cephfs.chdir(b"/")
+ dirs = [b"dir-1", b"dir-2", b"dir-3"]
+ for i in dirs:
+ cephfs.mkdir(i, 0o755)
+ handler = cephfs.opendir(b"/")
+ d1 = cephfs.readdir(handler)
+ d2 = cephfs.readdir(handler)
+ d3 = cephfs.readdir(handler)
+ offset_d4 = cephfs.telldir(handler)
+ d4 = cephfs.readdir(handler)
+ cephfs.rewinddir(handler)
+ d = cephfs.readdir(handler)
+ assert_equal(d.d_name, d1.d_name)
+ cephfs.seekdir(handler, offset_d4)
+ d = cephfs.readdir(handler)
+ assert_equal(d.d_name, d4.d_name)
+ dirs += [b".", b".."]
+ cephfs.rewinddir(handler)
+ d = cephfs.readdir(handler)
+ while d:
+ assert(d.d_name in dirs)
+ dirs.remove(d.d_name)
+ d = cephfs.readdir(handler)
+ assert(len(dirs) == 0)
+ dirs = [b"/dir-1", b"/dir-2", b"/dir-3"]
+ for i in dirs:
+ cephfs.rmdir(i)
+ cephfs.closedir(handler)
+
+def test_preadv_pwritev():
+ fd = cephfs.open(b'file-1', 'w', 0o755)
+ cephfs.pwritev(fd, [b"asdf", b"zxcvb"], 0)
+ cephfs.close(fd)
+ fd = cephfs.open(b'file-1', 'r', 0o755)
+ buf = [bytearray(i) for i in [4, 5]]
+ cephfs.preadv(fd, buf, 0)
+ assert_equal([b"asdf", b"zxcvb"], list(buf))
+ cephfs.close(fd)
+ cephfs.unlink(b'file-1')
+
+def test_setattrx(testdir):
+ fd = cephfs.open(b'file-setattrx', 'w', 0o655)
+ cephfs.write(fd, b"1111", 0)
+ cephfs.close(fd)
+ st = cephfs.statx(b'file-setattrx', libcephfs.CEPH_STATX_MODE, 0)
+ mode = st["mode"] | stat.S_IXUSR
+ assert_raises(TypeError, cephfs.setattrx, b'file-setattrx', "dict", 0, 0)
+
+ time.sleep(1)
+ statx_dict = dict()
+ statx_dict["mode"] = mode
+ statx_dict["uid"] = 9999
+ statx_dict["gid"] = 9999
+ dt = datetime.now()
+ statx_dict["mtime"] = dt
+ statx_dict["atime"] = dt
+ statx_dict["ctime"] = dt
+ statx_dict["size"] = 10
+ statx_dict["btime"] = dt
+ cephfs.setattrx(b'file-setattrx', statx_dict, libcephfs.CEPH_SETATTR_MODE | libcephfs.CEPH_SETATTR_UID |
+ libcephfs.CEPH_SETATTR_GID | libcephfs.CEPH_SETATTR_MTIME |
+ libcephfs.CEPH_SETATTR_ATIME | libcephfs.CEPH_SETATTR_CTIME |
+ libcephfs.CEPH_SETATTR_SIZE | libcephfs.CEPH_SETATTR_BTIME, 0)
+ st1 = cephfs.statx(b'file-setattrx', libcephfs.CEPH_STATX_MODE | libcephfs.CEPH_STATX_UID |
+ libcephfs.CEPH_STATX_GID | libcephfs.CEPH_STATX_MTIME |
+ libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_CTIME |
+ libcephfs.CEPH_STATX_SIZE | libcephfs.CEPH_STATX_BTIME, 0)
+ assert_equal(mode, st1["mode"])
+ assert_equal(9999, st1["uid"])
+ assert_equal(9999, st1["gid"])
+ assert_equal(int(dt.timestamp()), int(st1["mtime"].timestamp()))
+ assert_equal(int(dt.timestamp()), int(st1["atime"].timestamp()))
+ assert_equal(int(dt.timestamp()), int(st1["ctime"].timestamp()))
+ assert_equal(int(dt.timestamp()), int(st1["btime"].timestamp()))
+ assert_equal(10, st1["size"])
+ cephfs.unlink(b'file-setattrx')
+
+def test_fsetattrx(testdir):
+ fd = cephfs.open(b'file-fsetattrx', 'w', 0o655)
+ cephfs.write(fd, b"1111", 0)
+ st = cephfs.statx(b'file-fsetattrx', libcephfs.CEPH_STATX_MODE, 0)
+ mode = st["mode"] | stat.S_IXUSR
+ assert_raises(TypeError, cephfs.fsetattrx, fd, "dict", 0, 0)
+
+ time.sleep(1)
+ statx_dict = dict()
+ statx_dict["mode"] = mode
+ statx_dict["uid"] = 9999
+ statx_dict["gid"] = 9999
+ dt = datetime.now()
+ statx_dict["mtime"] = dt
+ statx_dict["atime"] = dt
+ statx_dict["ctime"] = dt
+ statx_dict["size"] = 10
+ statx_dict["btime"] = dt
+ cephfs.fsetattrx(fd, statx_dict, libcephfs.CEPH_SETATTR_MODE | libcephfs.CEPH_SETATTR_UID |
+ libcephfs.CEPH_SETATTR_GID | libcephfs.CEPH_SETATTR_MTIME |
+ libcephfs.CEPH_SETATTR_ATIME | libcephfs.CEPH_SETATTR_CTIME |
+ libcephfs.CEPH_SETATTR_SIZE | libcephfs.CEPH_SETATTR_BTIME)
+ st1 = cephfs.statx(b'file-fsetattrx', libcephfs.CEPH_STATX_MODE | libcephfs.CEPH_STATX_UID |
+ libcephfs.CEPH_STATX_GID | libcephfs.CEPH_STATX_MTIME |
+ libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_CTIME |
+ libcephfs.CEPH_STATX_SIZE | libcephfs.CEPH_STATX_BTIME, 0)
+ assert_equal(mode, st1["mode"])
+ assert_equal(9999, st1["uid"])
+ assert_equal(9999, st1["gid"])
+ assert_equal(int(dt.timestamp()), int(st1["mtime"].timestamp()))
+ assert_equal(int(dt.timestamp()), int(st1["atime"].timestamp()))
+ assert_equal(int(dt.timestamp()), int(st1["ctime"].timestamp()))
+ assert_equal(int(dt.timestamp()), int(st1["btime"].timestamp()))
+ assert_equal(10, st1["size"])
+ cephfs.close(fd)
+ cephfs.unlink(b'file-fsetattrx')
+
+def test_get_layout(testdir):
+ fd = cephfs.open(b'file-get-layout', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ assert_raises(TypeError, cephfs.get_layout, "fd")
+ l_dict = cephfs.get_layout(fd)
+ assert('stripe_unit' in l_dict.keys())
+ assert('stripe_count' in l_dict.keys())
+ assert('object_size' in l_dict.keys())
+ assert('pool_id' in l_dict.keys())
+ assert('pool_name' in l_dict.keys())
+
+ cephfs.close(fd)
+ cephfs.unlink(b'file-get-layout')
+
+def test_get_default_pool(testdir):
+ dp_dict = cephfs.get_default_pool()
+ assert('pool_id' in dp_dict.keys())
+ assert('pool_name' in dp_dict.keys())
+
+def test_get_pool(testdir):
+ dp_dict = cephfs.get_default_pool()
+ assert('pool_id' in dp_dict.keys())
+ assert('pool_name' in dp_dict.keys())
+ assert_equal(cephfs.get_pool_id(dp_dict["pool_name"]), dp_dict["pool_id"])
+ get_rep_cnt_cmd = "ceph osd pool get " + dp_dict["pool_name"] + " size"
+ s=os.popen(get_rep_cnt_cmd).read().strip('\n')
+ size=int(s.split(" ")[-1])
+ assert_equal(cephfs.get_pool_replication(dp_dict["pool_id"]), size)
+
+def test_disk_quota_exceeeded_error(testdir):
+ cephfs.mkdir("/dir-1", 0o755)
+ cephfs.setxattr("/dir-1", "ceph.quota.max_bytes", b"5", 0)
+ fd = cephfs.open(b'/dir-1/file-1', 'w', 0o755)
+ assert_raises(libcephfs.DiskQuotaExceeded, cephfs.write, fd, b"abcdeghiklmnopqrstuvwxyz", 0)
+ cephfs.close(fd)
+ cephfs.unlink(b"/dir-1/file-1")
+
+def test_empty_snapshot_info(testdir):
+ cephfs.mkdir("/dir-1", 0o755)
+
+ # snap without metadata
+ cephfs.mkdir("/dir-1/.snap/snap0", 0o755)
+ snap_info = cephfs.snap_info("/dir-1/.snap/snap0")
+ assert_equal(snap_info["metadata"], {})
+ assert_greater(snap_info["id"], 0)
+ cephfs.rmdir("/dir-1/.snap/snap0")
+
+ # remove directory
+ cephfs.rmdir("/dir-1")
+
+def test_snapshot_info(testdir):
+ cephfs.mkdir("/dir-1", 0o755)
+
+ # snap with custom metadata
+ md = {"foo": "bar", "zig": "zag", "abcdefg": "12345"}
+ cephfs.mksnap("/dir-1", "snap0", 0o755, metadata=md)
+ snap_info = cephfs.snap_info("/dir-1/.snap/snap0")
+ assert_equal(snap_info["metadata"]["foo"], md["foo"])
+ assert_equal(snap_info["metadata"]["zig"], md["zig"])
+ assert_equal(snap_info["metadata"]["abcdefg"], md["abcdefg"])
+ assert_greater(snap_info["id"], 0)
+ cephfs.rmsnap("/dir-1", "snap0")
+
+ # remove directory
+ cephfs.rmdir("/dir-1")
+
+def test_set_mount_timeout_post_mount(testdir):
+ assert_raises(libcephfs.LibCephFSStateError, cephfs.set_mount_timeout, 5)
+
+def test_set_mount_timeout(testdir):
+ cephfs.unmount()
+ cephfs.set_mount_timeout(5)
+ cephfs.mount()
+
+def test_set_mount_timeout_lt0(testdir):
+ cephfs.unmount()
+ assert_raises(libcephfs.InvalidValue, cephfs.set_mount_timeout, -5)
+ cephfs.mount()
+
+def test_snapdiff(testdir):
+ cephfs.mkdir("/snapdiff_test", 0o755)
+ fd = cephfs.open('/snapdiff_test/file-1', 'w', 0o755)
+ cephfs.write(fd, b"1111", 0)
+ cephfs.close(fd)
+ fd = cephfs.open('/snapdiff_test/file-2', 'w', 0o755)
+ cephfs.write(fd, b"2222", 0)
+ cephfs.close(fd)
+ cephfs.mksnap("/snapdiff_test", "snap1", 0o755)
+ fd = cephfs.open('/snapdiff_test/file-1', 'w', 0o755)
+ cephfs.write(fd, b"1222", 0)
+ cephfs.close(fd)
+ cephfs.unlink('/snapdiff_test/file-2')
+ cephfs.mksnap("/snapdiff_test", "snap2", 0o755)
+ snap1id = cephfs.snap_info(b"/snapdiff_test/.snap/snap1")['id']
+ snap2id = cephfs.snap_info(b"/snapdiff_test/.snap/snap2")['id']
+ diff = cephfs.opensnapdiff(b"/snapdiff_test", b"/", b"snap2", b"snap1")
+ cnt = 0
+ e = diff.readdir()
+ while e is not None:
+ if (e.d_name == b"file-1"):
+ cnt = cnt + 1
+ assert_equal(snap2id, e.d_snapid)
+ elif (e.d_name == b"file-2"):
+ cnt = cnt + 1
+ assert_equal(snap1id, e.d_snapid)
+ elif (e.d_name != b"." and e.d_name != b".."):
+ cnt = cnt + 1
+ e = diff.readdir()
+ assert_equal(cnt, 2)
+ diff.close()
+
+ # remove directory
+ purge_dir(b"/snapdiff_test");
diff --git a/src/test/pybind/test_rados.py b/src/test/pybind/test_rados.py
new file mode 100644
index 000000000..236b2f1d5
--- /dev/null
+++ b/src/test/pybind/test_rados.py
@@ -0,0 +1,1543 @@
+from __future__ import print_function
+from assertions import assert_equal as eq, assert_raises
+from rados import (Rados, Error, RadosStateError, Object, ObjectExists,
+ ObjectNotFound, ObjectBusy, NotConnected,
+ LIBRADOS_ALL_NSPACES, WriteOpCtx, ReadOpCtx, LIBRADOS_CREATE_EXCLUSIVE,
+ LIBRADOS_CMPXATTR_OP_EQ, LIBRADOS_CMPXATTR_OP_GT, LIBRADOS_CMPXATTR_OP_LT, OSError,
+ LIBRADOS_SNAP_HEAD, LIBRADOS_OPERATION_BALANCE_READS, LIBRADOS_OPERATION_SKIPRWLOCKS, MonitorLog, MAX_ERRNO, NoData, ExtendMismatch)
+from datetime import timedelta
+import time
+import threading
+import json
+import errno
+import os
+import pytest
+import re
+import sys
+
+def test_rados_init_error():
+ assert_raises(Error, Rados, conffile='', rados_id='admin',
+ name='client.admin')
+ assert_raises(Error, Rados, conffile='', name='invalid')
+ assert_raises(Error, Rados, conffile='', name='bad.invalid')
+
+def test_rados_init():
+ with Rados(conffile='', rados_id='admin'):
+ pass
+ with Rados(conffile='', name='client.admin'):
+ pass
+ with Rados(conffile='', name='client.admin'):
+ pass
+ with Rados(conffile='', name='client.admin'):
+ pass
+
+def test_ioctx_context_manager():
+ with Rados(conffile='', rados_id='admin') as conn:
+ with conn.open_ioctx('rbd') as ioctx:
+ pass
+
+def test_parse_argv():
+ args = ['osd', 'pool', 'delete', 'foobar', 'foobar', '--yes-i-really-really-mean-it']
+ r = Rados()
+ eq(args, r.conf_parse_argv(args))
+
+def test_parse_argv_empty_str():
+ args = ['']
+ r = Rados()
+ eq(args, r.conf_parse_argv(args))
+
+class TestRadosStateError(object):
+ def _requires_configuring(self, rados):
+ assert_raises(RadosStateError, rados.connect)
+
+ def _requires_configuring_or_connected(self, rados):
+ assert_raises(RadosStateError, rados.conf_read_file)
+ assert_raises(RadosStateError, rados.conf_parse_argv, None)
+ assert_raises(RadosStateError, rados.conf_parse_env)
+ assert_raises(RadosStateError, rados.conf_get, 'opt')
+ assert_raises(RadosStateError, rados.conf_set, 'opt', 'val')
+ assert_raises(RadosStateError, rados.ping_monitor, '0')
+
+ def _requires_connected(self, rados):
+ assert_raises(RadosStateError, rados.pool_exists, 'foo')
+ assert_raises(RadosStateError, rados.pool_lookup, 'foo')
+ assert_raises(RadosStateError, rados.pool_reverse_lookup, 0)
+ assert_raises(RadosStateError, rados.create_pool, 'foo')
+ assert_raises(RadosStateError, rados.get_pool_base_tier, 0)
+ assert_raises(RadosStateError, rados.delete_pool, 'foo')
+ assert_raises(RadosStateError, rados.list_pools)
+ assert_raises(RadosStateError, rados.get_fsid)
+ assert_raises(RadosStateError, rados.open_ioctx, 'foo')
+ assert_raises(RadosStateError, rados.mon_command, '', b'')
+ assert_raises(RadosStateError, rados.osd_command, 0, '', b'')
+ assert_raises(RadosStateError, rados.pg_command, '', '', b'')
+ assert_raises(RadosStateError, rados.wait_for_latest_osdmap)
+ assert_raises(RadosStateError, rados.blocklist_add, '127.0.0.1/123', 0)
+
+ def test_configuring(self):
+ rados = Rados(conffile='')
+ eq('configuring', rados.state)
+ self._requires_connected(rados)
+
+ def test_connected(self):
+ rados = Rados(conffile='')
+ with rados:
+ eq('connected', rados.state)
+ self._requires_configuring(rados)
+
+ def test_shutdown(self):
+ rados = Rados(conffile='')
+ with rados:
+ pass
+ eq('shutdown', rados.state)
+ self._requires_configuring(rados)
+ self._requires_configuring_or_connected(rados)
+ self._requires_connected(rados)
+
+
+class TestRados(object):
+
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.conf_parse_env('FOO_DOES_NOT_EXIST_BLAHBLAH')
+ self.rados.conf_parse_env()
+ self.rados.connect()
+
+ # Assume any pre-existing pools are the cluster's defaults
+ self.default_pools = self.rados.list_pools()
+
+ def teardown_method(self, method):
+ self.rados.shutdown()
+
+ def test_ping_monitor(self):
+ assert_raises(ObjectNotFound, self.rados.ping_monitor, 'not_exists_monitor')
+ cmd = {'prefix': 'mon dump', 'format':'json'}
+ ret, buf, out = self.rados.mon_command(json.dumps(cmd), b'')
+ for mon in json.loads(buf.decode('utf8'))['mons']:
+ while True:
+ output = self.rados.ping_monitor(mon['name'])
+ if output is None:
+ continue
+ buf = json.loads(output)
+ if buf.get('health'):
+ break
+
+ def test_annotations(self):
+ with pytest.raises(TypeError):
+ self.rados.create_pool(0xf00)
+
+ def test_create(self):
+ self.rados.create_pool('foo')
+ self.rados.delete_pool('foo')
+
+ def test_create_utf8(self):
+ poolname = "\u9ec4"
+ self.rados.create_pool(poolname)
+ assert self.rados.pool_exists(u"\u9ec4")
+ self.rados.delete_pool(poolname)
+
+ def test_pool_lookup_utf8(self):
+ poolname = '\u9ec4'
+ self.rados.create_pool(poolname)
+ try:
+ poolid = self.rados.pool_lookup(poolname)
+ eq(poolname, self.rados.pool_reverse_lookup(poolid))
+ finally:
+ self.rados.delete_pool(poolname)
+
+ def test_eexist(self):
+ self.rados.create_pool('foo')
+ assert_raises(ObjectExists, self.rados.create_pool, 'foo')
+ self.rados.delete_pool('foo')
+
+ def list_non_default_pools(self):
+ pools = self.rados.list_pools()
+ for p in self.default_pools:
+ pools.remove(p)
+ return set(pools)
+
+ def test_list_pools(self):
+ eq(set(), self.list_non_default_pools())
+ self.rados.create_pool('foo')
+ eq(set(['foo']), self.list_non_default_pools())
+ self.rados.create_pool('bar')
+ eq(set(['foo', 'bar']), self.list_non_default_pools())
+ self.rados.create_pool('baz')
+ eq(set(['foo', 'bar', 'baz']), self.list_non_default_pools())
+ self.rados.delete_pool('foo')
+ eq(set(['bar', 'baz']), self.list_non_default_pools())
+ self.rados.delete_pool('baz')
+ eq(set(['bar']), self.list_non_default_pools())
+ self.rados.delete_pool('bar')
+ eq(set(), self.list_non_default_pools())
+ self.rados.create_pool('a' * 500)
+ eq(set(['a' * 500]), self.list_non_default_pools())
+ self.rados.delete_pool('a' * 500)
+
+ @pytest.mark.tier
+ def test_get_pool_base_tier(self):
+ self.rados.create_pool('foo')
+ try:
+ self.rados.create_pool('foo-cache')
+ try:
+ pool_id = self.rados.pool_lookup('foo')
+ tier_pool_id = self.rados.pool_lookup('foo-cache')
+
+ cmd = {"prefix":"osd tier add", "pool":"foo", "tierpool":"foo-cache", "force_nonempty":""}
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ eq(ret, 0)
+
+ try:
+ cmd = {"prefix":"osd tier cache-mode", "pool":"foo-cache", "tierpool":"foo-cache", "mode":"readonly", "yes_i_really_mean_it": True}
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ eq(ret, 0)
+
+ eq(self.rados.wait_for_latest_osdmap(), 0)
+
+ eq(pool_id, self.rados.get_pool_base_tier(pool_id))
+ eq(pool_id, self.rados.get_pool_base_tier(tier_pool_id))
+ finally:
+ cmd = {"prefix":"osd tier remove", "pool":"foo", "tierpool":"foo-cache"}
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ eq(ret, 0)
+ finally:
+ self.rados.delete_pool('foo-cache')
+ finally:
+ self.rados.delete_pool('foo')
+
+ def test_get_fsid(self):
+ fsid = self.rados.get_fsid()
+ assert re.match('[0-9a-f\-]{36}', fsid, re.I)
+
+ def test_blocklist_add(self):
+ self.rados.blocklist_add("1.2.3.4/123", 1)
+
+ @pytest.mark.stats
+ def test_get_cluster_stats(self):
+ stats = self.rados.get_cluster_stats()
+ assert stats['kb'] > 0
+ assert stats['kb_avail'] > 0
+ assert stats['kb_used'] > 0
+ assert stats['num_objects'] >= 0
+
+ def test_monitor_log(self):
+ lock = threading.Condition()
+ def cb(arg, line, who, sec, nsec, seq, level, msg):
+ # NOTE(sileht): the old pyrados API was received the pointer as int
+ # instead of the value of arg
+ eq(arg, "arg")
+ with lock:
+ lock.notify()
+ return 0
+
+ # NOTE(sileht): force don't save the monitor into local var
+ # to ensure all references are correctly tracked into the lib
+ MonitorLog(self.rados, "debug", cb, "arg")
+ with lock:
+ lock.wait()
+ MonitorLog(self.rados, "debug", None, None)
+ eq(None, self.rados.monitor_callback)
+
+class TestIoctx(object):
+
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.connect()
+ self.rados.create_pool('test_pool')
+ assert self.rados.pool_exists('test_pool')
+ self.ioctx = self.rados.open_ioctx('test_pool')
+
+ def teardown_method(self, method):
+ cmd = {"prefix":"osd unset", "key":"noup"}
+ self.rados.mon_command(json.dumps(cmd), b'')
+ self.ioctx.close()
+ self.rados.delete_pool('test_pool')
+ self.rados.shutdown()
+
+ def test_get_last_version(self):
+ version = self.ioctx.get_last_version()
+ assert version >= 0
+
+ def test_get_stats(self):
+ stats = self.ioctx.get_stats()
+ eq(stats, {'num_objects_unfound': 0,
+ 'num_objects_missing_on_primary': 0,
+ 'num_object_clones': 0,
+ 'num_objects': 0,
+ 'num_object_copies': 0,
+ 'num_bytes': 0,
+ 'num_rd_kb': 0,
+ 'num_wr_kb': 0,
+ 'num_kb': 0,
+ 'num_wr': 0,
+ 'num_objects_degraded': 0,
+ 'num_rd': 0})
+
+ def test_write(self):
+ self.ioctx.write('abc', b'abc')
+ eq(self.ioctx.read('abc'), b'abc')
+
+ def test_write_full(self):
+ self.ioctx.write('abc', b'abc')
+ eq(self.ioctx.read('abc'), b'abc')
+ self.ioctx.write_full('abc', b'd')
+ eq(self.ioctx.read('abc'), b'd')
+
+ def test_writesame(self):
+ self.ioctx.writesame('ob', b'rzx', 9)
+ eq(self.ioctx.read('ob'), b'rzxrzxrzx')
+
+ def test_append(self):
+ self.ioctx.write('abc', b'a')
+ self.ioctx.append('abc', b'b')
+ self.ioctx.append('abc', b'c')
+ eq(self.ioctx.read('abc'), b'abc')
+
+ def test_write_zeros(self):
+ self.ioctx.write('abc', b'a\0b\0c')
+ eq(self.ioctx.read('abc'), b'a\0b\0c')
+
+ def test_trunc(self):
+ self.ioctx.write('abc', b'abc')
+ self.ioctx.trunc('abc', 2)
+ eq(self.ioctx.read('abc'), b'ab')
+ size = self.ioctx.stat('abc')[0]
+ eq(size, 2)
+
+ def test_cmpext(self):
+ self.ioctx.write('test_object', b'abcdefghi')
+ eq(0, self.ioctx.cmpext('test_object', b'abcdefghi', 0))
+ eq(-MAX_ERRNO - 4, self.ioctx.cmpext('test_object', b'abcdxxxxx', 0))
+
+ def test_list_objects_empty(self):
+ eq(list(self.ioctx.list_objects()), [])
+
+ def test_list_objects(self):
+ self.ioctx.write('a', b'')
+ self.ioctx.write('b', b'foo')
+ self.ioctx.write_full('c', b'bar')
+ self.ioctx.append('d', b'jazz')
+ object_names = [obj.key for obj in self.ioctx.list_objects()]
+ eq(sorted(object_names), ['a', 'b', 'c', 'd'])
+
+ def test_list_ns_objects(self):
+ self.ioctx.write('a', b'')
+ self.ioctx.write('b', b'foo')
+ self.ioctx.write_full('c', b'bar')
+ self.ioctx.append('d', b'jazz')
+ self.ioctx.set_namespace("ns1")
+ self.ioctx.write('ns1-a', b'')
+ self.ioctx.write('ns1-b', b'foo')
+ self.ioctx.write_full('ns1-c', b'bar')
+ self.ioctx.append('ns1-d', b'jazz')
+ self.ioctx.append('d', b'jazz')
+ self.ioctx.set_namespace(LIBRADOS_ALL_NSPACES)
+ object_names = [(obj.nspace, obj.key) for obj in self.ioctx.list_objects()]
+ eq(sorted(object_names), [('', 'a'), ('','b'), ('','c'), ('','d'),\
+ ('ns1', 'd'), ('ns1', 'ns1-a'), ('ns1', 'ns1-b'),\
+ ('ns1', 'ns1-c'), ('ns1', 'ns1-d')])
+
+ def test_xattrs(self):
+ xattrs = dict(a=b'1', b=b'2', c=b'3', d=b'a\0b', e=b'\0', f=b'')
+ self.ioctx.write('abc', b'')
+ for key, value in xattrs.items():
+ self.ioctx.set_xattr('abc', key, value)
+ eq(self.ioctx.get_xattr('abc', key), value)
+ stored_xattrs = {}
+ for key, value in self.ioctx.get_xattrs('abc'):
+ stored_xattrs[key] = value
+ eq(stored_xattrs, xattrs)
+
+ def test_obj_xattrs(self):
+ xattrs = dict(a=b'1', b=b'2', c=b'3', d=b'a\0b', e=b'\0', f=b'')
+ self.ioctx.write('abc', b'')
+ obj = list(self.ioctx.list_objects())[0]
+ for key, value in xattrs.items():
+ obj.set_xattr(key, value)
+ eq(obj.get_xattr(key), value)
+ stored_xattrs = {}
+ for key, value in obj.get_xattrs():
+ stored_xattrs[key] = value
+ eq(stored_xattrs, xattrs)
+
+ def test_get_pool_id(self):
+ eq(self.ioctx.get_pool_id(), self.rados.pool_lookup('test_pool'))
+
+ def test_get_pool_name(self):
+ eq(self.ioctx.get_pool_name(), 'test_pool')
+
+ def test_create_snap(self):
+ assert_raises(ObjectNotFound, self.ioctx.remove_snap, 'foo')
+ self.ioctx.create_snap('foo')
+ self.ioctx.remove_snap('foo')
+
+ def test_list_snaps_empty(self):
+ eq(list(self.ioctx.list_snaps()), [])
+
+ def test_list_snaps(self):
+ snaps = ['snap1', 'snap2', 'snap3']
+ for snap in snaps:
+ self.ioctx.create_snap(snap)
+ listed_snaps = [snap.name for snap in self.ioctx.list_snaps()]
+ eq(snaps, listed_snaps)
+
+ def test_lookup_snap(self):
+ self.ioctx.create_snap('foo')
+ snap = self.ioctx.lookup_snap('foo')
+ eq(snap.name, 'foo')
+
+ def test_snap_timestamp(self):
+ self.ioctx.create_snap('foo')
+ snap = self.ioctx.lookup_snap('foo')
+ snap.get_timestamp()
+
+ def test_remove_snap(self):
+ self.ioctx.create_snap('foo')
+ (snap,) = self.ioctx.list_snaps()
+ eq(snap.name, 'foo')
+ self.ioctx.remove_snap('foo')
+ eq(list(self.ioctx.list_snaps()), [])
+
+ @pytest.mark.rollback
+ def test_snap_rollback(self):
+ self.ioctx.write("insnap", b"contents1")
+ self.ioctx.create_snap("snap1")
+ self.ioctx.remove_object("insnap")
+ self.ioctx.snap_rollback("insnap", "snap1")
+ eq(self.ioctx.read("insnap"), b"contents1")
+ self.ioctx.remove_snap("snap1")
+ self.ioctx.remove_object("insnap")
+
+ @pytest.mark.rollback
+ def test_snap_rollback_removed(self):
+ self.ioctx.write("insnap", b"contents1")
+ self.ioctx.create_snap("snap1")
+ self.ioctx.write("insnap", b"contents2")
+ self.ioctx.snap_rollback("insnap", "snap1")
+ eq(self.ioctx.read("insnap"), b"contents1")
+ self.ioctx.remove_snap("snap1")
+ self.ioctx.remove_object("insnap")
+
+ def test_snap_read(self):
+ self.ioctx.write("insnap", b"contents1")
+ self.ioctx.create_snap("snap1")
+ self.ioctx.remove_object("insnap")
+ snap = self.ioctx.lookup_snap("snap1")
+ self.ioctx.set_read(snap.snap_id)
+ eq(self.ioctx.read("insnap"), b"contents1")
+ self.ioctx.set_read(LIBRADOS_SNAP_HEAD)
+ self.ioctx.write("inhead", b"contents2")
+ eq(self.ioctx.read("inhead"), b"contents2")
+ self.ioctx.remove_snap("snap1")
+ self.ioctx.remove_object("inhead")
+
+ def test_set_omap(self):
+ keys = ("1", "2", "3", "4", b"\xff")
+ values = (b"aaa", b"bbb", b"ccc", b"\x04\x04\x04\x04", b"5")
+ with WriteOpCtx() as write_op:
+ self.ioctx.set_omap(write_op, keys, values)
+ write_op.set_flags(LIBRADOS_OPERATION_SKIPRWLOCKS)
+ self.ioctx.operate_write_op(write_op, "hw")
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals(read_op, "", "", 5, omap_key_type=bytes)
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, "hw")
+ next(iter)
+ eq(list(iter), [(b"2", b"bbb"), (b"3", b"ccc"), (b"4", b"\x04\x04\x04\x04"), (b"\xff", b"5")])
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals(read_op, b"2", "", 4, omap_key_type=bytes)
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, "hw")
+ eq((b"3", b"ccc"), next(iter))
+ eq(list(iter), [(b"4", b"\x04\x04\x04\x04"), (b"\xff", b"5")])
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals(read_op, "", "2", 4, omap_key_type=bytes)
+ eq(ret, 0)
+ read_op.set_flags(LIBRADOS_OPERATION_BALANCE_READS)
+ self.ioctx.operate_read_op(read_op, "hw")
+ eq(list(iter), [(b"2", b"bbb")])
+
+ def test_set_omap_aio(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+
+ keys = ("1", "2", "3", "4")
+ values = (b"aaa", b"bbb", b"ccc", b"\x04\x04\x04\x04")
+ with WriteOpCtx() as write_op:
+ self.ioctx.set_omap(write_op, keys, values)
+ comp = self.ioctx.operate_aio_write_op(write_op, "hw", cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals(read_op, "", "", 4)
+ eq(ret, 0)
+ comp = self.ioctx.operate_aio_read_op(read_op, "hw", cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 4:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ next(iter)
+ eq(list(iter), [("2", b"bbb"), ("3", b"ccc"), ("4", b"\x04\x04\x04\x04")])
+
+ def test_write_ops(self):
+ with WriteOpCtx() as write_op:
+ write_op.new(0)
+ self.ioctx.operate_write_op(write_op, "write_ops")
+ eq(self.ioctx.read('write_ops'), b'')
+
+ write_op.write_full(b'1')
+ write_op.append(b'2')
+ self.ioctx.operate_write_op(write_op, "write_ops")
+ eq(self.ioctx.read('write_ops'), b'12')
+
+ write_op.write_full(b'12345')
+ write_op.write(b'x', 2)
+ self.ioctx.operate_write_op(write_op, "write_ops")
+ eq(self.ioctx.read('write_ops'), b'12x45')
+
+ write_op.write_full(b'12345')
+ write_op.zero(2, 2)
+ self.ioctx.operate_write_op(write_op, "write_ops")
+ eq(self.ioctx.read('write_ops'), b'12\x00\x005')
+
+ write_op.write_full(b'12345')
+ write_op.truncate(2)
+ self.ioctx.operate_write_op(write_op, "write_ops")
+ eq(self.ioctx.read('write_ops'), b'12')
+
+ write_op.remove()
+ self.ioctx.operate_write_op(write_op, "write_ops")
+ with pytest.raises(ObjectNotFound):
+ self.ioctx.read('write_ops')
+
+ def test_execute_op(self):
+ with WriteOpCtx() as write_op:
+ write_op.execute("hello", "record_hello", b"ebs")
+ self.ioctx.operate_write_op(write_op, "object")
+ eq(self.ioctx.read('object'), b"Hello, ebs!")
+
+ def test_writesame_op(self):
+ with WriteOpCtx() as write_op:
+ write_op.writesame(b'rzx', 9)
+ self.ioctx.operate_write_op(write_op, 'abc')
+ eq(self.ioctx.read('abc'), b'rzxrzxrzx')
+
+ def test_get_omap_vals_by_keys(self):
+ keys = ("1", "2", "3", "4", b"\xff")
+ values = (b"aaa", b"bbb", b"ccc", b"\x04\x04\x04\x04", b"5")
+ with WriteOpCtx() as write_op:
+ self.ioctx.set_omap(write_op, keys, values)
+ self.ioctx.operate_write_op(write_op, "hw")
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op,("3","4",b"\xff"), omap_key_type=bytes)
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, "hw")
+ eq(list(iter), [(b"3", b"ccc"), (b"4", b"\x04\x04\x04\x04"), (b"\xff", b"5")])
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op,("3","4",), omap_key_type=bytes)
+ eq(ret, 0)
+ with pytest.raises(ObjectNotFound):
+ self.ioctx.operate_read_op(read_op, "no_such")
+
+ def test_get_omap_keys(self):
+ keys = ("1", "2", "3")
+ values = (b"aaa", b"bbb", b"ccc")
+ with WriteOpCtx() as write_op:
+ self.ioctx.set_omap(write_op, keys, values)
+ self.ioctx.operate_write_op(write_op, "hw")
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_keys(read_op,"",2)
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, "hw")
+ eq(list(iter), [("1", None), ("2", None)])
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_keys(read_op,"",2)
+ eq(ret, 0)
+ with pytest.raises(ObjectNotFound):
+ self.ioctx.operate_read_op(read_op, "no_such")
+
+ def test_clear_omap(self):
+ keys = ("1", "2", "3")
+ values = (b"aaa", b"bbb", b"ccc")
+ with WriteOpCtx() as write_op:
+ self.ioctx.set_omap(write_op, keys, values)
+ self.ioctx.operate_write_op(write_op, "hw")
+ with WriteOpCtx() as write_op_1:
+ self.ioctx.clear_omap(write_op_1)
+ self.ioctx.operate_write_op(write_op_1, "hw")
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op,("1",))
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, "hw")
+ eq(list(iter), [])
+
+ def test_remove_omap_range2(self):
+ keys = ("1", "2", "3", "4")
+ values = (b"a", b"bb", b"ccc", b"dddd")
+ with WriteOpCtx() as write_op:
+ self.ioctx.set_omap(write_op, keys, values)
+ self.ioctx.operate_write_op(write_op, "test_obj")
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op, keys)
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, "test_obj")
+ eq(list(iter), list(zip(keys, values)))
+ with WriteOpCtx() as write_op:
+ self.ioctx.remove_omap_range2(write_op, "1", "4")
+ self.ioctx.operate_write_op(write_op, "test_obj")
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op, keys)
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, "test_obj")
+ eq(list(iter), [("4", b"dddd")])
+
+ def test_omap_cmp(self):
+ object_id = 'test'
+ self.ioctx.write(object_id, b'omap_cmp')
+ with WriteOpCtx() as write_op:
+ self.ioctx.set_omap(write_op, ('key1',), ('1',))
+ self.ioctx.operate_write_op(write_op, object_id)
+ with WriteOpCtx() as write_op:
+ write_op.omap_cmp('key1', '1', LIBRADOS_CMPXATTR_OP_EQ)
+ self.ioctx.set_omap(write_op, ('key1',), ('2',))
+ self.ioctx.operate_write_op(write_op, object_id)
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op, ('key1',))
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, object_id)
+ eq(list(iter), [('key1', b'2')])
+ with WriteOpCtx() as write_op:
+ write_op.omap_cmp('key1', '1', LIBRADOS_CMPXATTR_OP_GT)
+ self.ioctx.set_omap(write_op, ('key1',), ('3',))
+ self.ioctx.operate_write_op(write_op, object_id)
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op, ('key1',))
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, object_id)
+ eq(list(iter), [('key1', b'3')])
+ with WriteOpCtx() as write_op:
+ write_op.omap_cmp('key1', '4', LIBRADOS_CMPXATTR_OP_LT)
+ self.ioctx.set_omap(write_op, ('key1',), ('4',))
+ self.ioctx.operate_write_op(write_op, object_id)
+ with ReadOpCtx() as read_op:
+ iter, ret = self.ioctx.get_omap_vals_by_keys(read_op, ('key1',))
+ eq(ret, 0)
+ self.ioctx.operate_read_op(read_op, object_id)
+ eq(list(iter), [('key1', b'4')])
+ with WriteOpCtx() as write_op:
+ write_op.omap_cmp('key1', '1', LIBRADOS_CMPXATTR_OP_EQ)
+ self.ioctx.set_omap(write_op, ('key1',), ('5',))
+ try:
+ self.ioctx.operate_write_op(write_op, object_id)
+ except (OSError, ExtendMismatch) as e:
+ eq(e.errno, 125)
+ else:
+ message = "omap_cmp did not raise Exception when omap content does not match"
+ raise AssertionError(message)
+
+ def test_cmpext_op(self):
+ object_id = 'test'
+ with WriteOpCtx() as write_op:
+ write_op.write(b'12345', 0)
+ self.ioctx.operate_write_op(write_op, object_id)
+ with WriteOpCtx() as write_op:
+ write_op.cmpext(b'12345', 0)
+ write_op.write(b'54321', 0)
+ self.ioctx.operate_write_op(write_op, object_id)
+ eq(self.ioctx.read(object_id), b'54321')
+ with WriteOpCtx() as write_op:
+ write_op.cmpext(b'56789', 0)
+ write_op.write(b'12345', 0)
+ try:
+ self.ioctx.operate_write_op(write_op, object_id)
+ except ExtendMismatch as e:
+ # the cmpext_result compare with expected error number, it should be (-MAX_ERRNO - 1)
+ # where "1" is the offset of the first unmatched byte
+ eq(-e.errno, -MAX_ERRNO - 1)
+ eq(e.offset, 1)
+ else:
+ message = "cmpext did not raise Exception when object content does not match"
+ raise AssertionError(message)
+ with ReadOpCtx() as read_op:
+ read_op.cmpext(b'54321', 0)
+ self.ioctx.operate_read_op(read_op, object_id)
+ with ReadOpCtx() as read_op:
+ read_op.cmpext(b'54789', 0)
+ try:
+ self.ioctx.operate_read_op(read_op, object_id)
+ except ExtendMismatch as e:
+ # the cmpext_result compare with expected error number, it should be (-MAX_ERRNO - 2)
+ # where "2" is the offset of the first unmatched byte
+ eq(-e.errno, -MAX_ERRNO - 2)
+ eq(e.offset, 2)
+ else:
+ message = "cmpext did not raise Exception when object content does not match"
+ raise AssertionError(message)
+
+ def test_xattrs_op(self):
+ xattrs = dict(a=b'1', b=b'2', c=b'3', d=b'a\0b', e=b'\0')
+ with WriteOpCtx() as write_op:
+ write_op.new(LIBRADOS_CREATE_EXCLUSIVE)
+ for key, value in xattrs.items():
+ write_op.set_xattr(key, value)
+ self.ioctx.operate_write_op(write_op, 'abc')
+ eq(self.ioctx.get_xattr('abc', key), value)
+
+ stored_xattrs_1 = {}
+ for key, value in self.ioctx.get_xattrs('abc'):
+ stored_xattrs_1[key] = value
+ eq(stored_xattrs_1, xattrs)
+
+ for key in xattrs.keys():
+ write_op.rm_xattr(key)
+ self.ioctx.operate_write_op(write_op, 'abc')
+ stored_xattrs_2 = {}
+ for key, value in self.ioctx.get_xattrs('abc'):
+ stored_xattrs_2[key] = value
+ eq(stored_xattrs_2, {})
+
+ write_op.remove()
+ self.ioctx.operate_write_op(write_op, 'abc')
+
+ def test_locator(self):
+ self.ioctx.set_locator_key("bar")
+ self.ioctx.write('foo', b'contents1')
+ objects = [i for i in self.ioctx.list_objects()]
+ eq(len(objects), 1)
+ eq(self.ioctx.get_locator_key(), "bar")
+ self.ioctx.set_locator_key("")
+ objects[0].seek(0)
+ objects[0].write(b"contents2")
+ eq(self.ioctx.get_locator_key(), "")
+ self.ioctx.set_locator_key("bar")
+ contents = self.ioctx.read("foo")
+ eq(contents, b"contents2")
+ eq(self.ioctx.get_locator_key(), "bar")
+ objects[0].remove()
+ objects = [i for i in self.ioctx.list_objects()]
+ eq(objects, [])
+ self.ioctx.set_locator_key("")
+
+ def test_operate_aio_write_op(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ with WriteOpCtx() as write_op:
+ write_op.write(b'rzx')
+ comp = self.ioctx.operate_aio_write_op(write_op, "object", cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ eq(self.ioctx.read('object'), b'rzx')
+
+ def test_aio_write(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ comp = self.ioctx.aio_write("foo", b"bar", 0, cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ contents = self.ioctx.read("foo")
+ eq(contents, b"bar")
+ [i.remove() for i in self.ioctx.list_objects()]
+
+ def test_aio_cmpext(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+
+ self.ioctx.write('test_object', b'abcdefghi')
+ comp = self.ioctx.aio_cmpext('test_object', b'abcdefghi', 0, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 1:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+
+ def test_aio_rmxattr(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ self.ioctx.set_xattr("xyz", "key", b'value')
+ eq(self.ioctx.get_xattr("xyz", "key"), b'value')
+ comp = self.ioctx.aio_rmxattr("xyz", "key", cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 1:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ with pytest.raises(NoData):
+ self.ioctx.get_xattr("xyz", "key")
+
+ def test_aio_write_no_comp_ref(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ # NOTE(sileht): force don't save the comp into local var
+ # to ensure all references are correctly tracked into the lib
+ self.ioctx.aio_write("foo", b"bar", 0, cb, cb)
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ contents = self.ioctx.read("foo")
+ eq(contents, b"bar")
+ [i.remove() for i in self.ioctx.list_objects()]
+
+ def test_aio_append(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ comp = self.ioctx.aio_write("foo", b"bar", 0, cb, cb)
+ comp2 = self.ioctx.aio_append("foo", b"baz", cb, cb)
+ comp.wait_for_complete()
+ contents = self.ioctx.read("foo")
+ eq(contents, b"barbaz")
+ with lock:
+ while count[0] < 4:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ eq(comp2.get_return_value(), 0)
+ [i.remove() for i in self.ioctx.list_objects()]
+
+ def test_aio_write_full(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ self.ioctx.aio_write("foo", b"barbaz", 0, cb, cb)
+ comp = self.ioctx.aio_write_full("foo", b"bar", cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ contents = self.ioctx.read("foo")
+ eq(contents, b"bar")
+ [i.remove() for i in self.ioctx.list_objects()]
+
+ def test_aio_writesame(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ comp = self.ioctx.aio_writesame("abc", b"rzx", 9, 0, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 1:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ eq(self.ioctx.read("abc"), b"rzxrzxrzx")
+ [i.remove() for i in self.ioctx.list_objects()]
+
+ def test_aio_stat(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(_, size, mtime):
+ with lock:
+ count[0] += 1
+ lock.notify()
+
+ comp = self.ioctx.aio_stat("foo", cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 1:
+ lock.wait()
+ eq(comp.get_return_value(), -2)
+
+ self.ioctx.write("foo", b"bar")
+
+ comp = self.ioctx.aio_stat("foo", cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+
+ [i.remove() for i in self.ioctx.list_objects()]
+
+ def test_aio_remove(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ self.ioctx.write('foo', b'wrx')
+ eq(self.ioctx.read('foo'), b'wrx')
+ comp = self.ioctx.aio_remove('foo', cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ eq(list(self.ioctx.list_objects()), [])
+
+ def _take_down_acting_set(self, pool, objectname):
+ # find acting_set for pool:objectname and take it down; used to
+ # verify that async reads don't complete while acting set is missing
+ cmd = {
+ "prefix":"osd map",
+ "pool":pool,
+ "object":objectname,
+ "format":"json",
+ }
+ r, jsonout, _ = self.rados.mon_command(json.dumps(cmd), b'')
+ objmap = json.loads(jsonout.decode("utf-8"))
+ acting_set = objmap['acting']
+ cmd = {"prefix":"osd set", "key":"noup"}
+ r, _, _ = self.rados.mon_command(json.dumps(cmd), b'')
+ eq(r, 0)
+ cmd = {"prefix":"osd down", "ids":[str(i) for i in acting_set]}
+ r, _, _ = self.rados.mon_command(json.dumps(cmd), b'')
+ eq(r, 0)
+
+ # wait for OSDs to acknowledge the down
+ eq(self.rados.wait_for_latest_osdmap(), 0)
+
+ def _let_osds_back_up(self):
+ cmd = {"prefix":"osd unset", "key":"noup"}
+ r, _, _ = self.rados.mon_command(json.dumps(cmd), b'')
+ eq(r, 0)
+
+ @pytest.mark.wait
+ def test_aio_read_wait_for_complete(self):
+ # use wait_for_complete() and wait for cb by
+ # watching retval[0]
+
+ # this is a list so that the local cb() can modify it
+ payload = b"bar\000frob"
+ self.ioctx.write("foo", payload)
+ self._take_down_acting_set('test_pool', 'foo')
+
+ retval = [None]
+ lock = threading.Condition()
+ def cb(_, buf):
+ with lock:
+ retval[0] = buf
+ lock.notify()
+
+ comp = self.ioctx.aio_read("foo", len(payload), 0, cb)
+ eq(False, comp.is_complete())
+ time.sleep(3)
+ eq(False, comp.is_complete())
+ with lock:
+ eq(None, retval[0])
+
+ self._let_osds_back_up()
+ comp.wait_for_complete()
+ loops = 0
+ with lock:
+ while retval[0] is None and loops <= 10:
+ lock.wait(timeout=5)
+ loops += 1
+ assert(loops <= 10)
+
+ eq(retval[0], payload)
+ eq(sys.getrefcount(comp), 2)
+
+ @pytest.mark.wait
+ def test_aio_read_wait_for_complete_and_cb(self):
+ # use wait_for_complete_and_cb(), verify retval[0] is
+ # set by the time we regain control
+ payload = b"bar\000frob"
+ self.ioctx.write("foo", payload)
+
+ self._take_down_acting_set('test_pool', 'foo')
+ # this is a list so that the local cb() can modify it
+ retval = [None]
+ lock = threading.Condition()
+ def cb(_, buf):
+ with lock:
+ retval[0] = buf
+ lock.notify()
+ comp = self.ioctx.aio_read("foo", len(payload), 0, cb)
+ eq(False, comp.is_complete())
+ time.sleep(3)
+ eq(False, comp.is_complete())
+ with lock:
+ eq(None, retval[0])
+
+ self._let_osds_back_up()
+ comp.wait_for_complete_and_cb()
+ assert(retval[0] is not None)
+ eq(retval[0], payload)
+ eq(sys.getrefcount(comp), 2)
+
+ @pytest.mark.wait
+ def test_aio_read_wait_for_complete_and_cb_error(self):
+ # error case, use wait_for_complete_and_cb(), verify retval[0] is
+ # set by the time we regain control
+ self._take_down_acting_set('test_pool', 'bar')
+
+ # this is a list so that the local cb() can modify it
+ retval = [1]
+ lock = threading.Condition()
+ def cb(_, buf):
+ with lock:
+ retval[0] = buf
+ lock.notify()
+
+ # read from a DNE object
+ comp = self.ioctx.aio_read("bar", 3, 0, cb)
+ eq(False, comp.is_complete())
+ time.sleep(3)
+ eq(False, comp.is_complete())
+ with lock:
+ eq(1, retval[0])
+ self._let_osds_back_up()
+
+ comp.wait_for_complete_and_cb()
+ eq(None, retval[0])
+ assert(comp.get_return_value() < 0)
+ eq(sys.getrefcount(comp), 2)
+
+ def test_lock(self):
+ self.ioctx.lock_exclusive("foo", "lock", "locker", "desc_lock",
+ 10000, 0)
+ assert_raises(ObjectExists,
+ self.ioctx.lock_exclusive,
+ "foo", "lock", "locker", "desc_lock", 10000, 0)
+ self.ioctx.unlock("foo", "lock", "locker")
+ assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker")
+
+ self.ioctx.lock_shared("foo", "lock", "locker1", "tag", "desc_lock",
+ 10000, 0)
+ self.ioctx.lock_shared("foo", "lock", "locker2", "tag", "desc_lock",
+ 10000, 0)
+ assert_raises(ObjectBusy,
+ self.ioctx.lock_exclusive,
+ "foo", "lock", "locker3", "desc_lock", 10000, 0)
+ self.ioctx.unlock("foo", "lock", "locker1")
+ self.ioctx.unlock("foo", "lock", "locker2")
+ assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker1")
+ assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker2")
+
+ def test_execute(self):
+ self.ioctx.write("foo", b"") # ensure object exists
+
+ ret, buf = self.ioctx.execute("foo", "hello", "say_hello", b"")
+ eq(buf, b"Hello, world!")
+
+ ret, buf = self.ioctx.execute("foo", "hello", "say_hello", b"nose")
+ eq(buf, b"Hello, nose!")
+
+ def test_aio_execute(self):
+ count = [0]
+ retval = [None]
+ lock = threading.Condition()
+ def cb(_, buf):
+ with lock:
+ if retval[0] is None:
+ retval[0] = buf
+ count[0] += 1
+ lock.notify()
+ self.ioctx.write("foo", b"") # ensure object exists
+
+ comp = self.ioctx.aio_execute("foo", "hello", "say_hello", b"", 32, cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 2:
+ lock.wait()
+ eq(comp.get_return_value(), 13)
+ eq(retval[0], b"Hello, world!")
+
+ retval[0] = None
+ comp = self.ioctx.aio_execute("foo", "hello", "say_hello", b"nose", 32, cb, cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 4:
+ lock.wait()
+ eq(comp.get_return_value(), 12)
+ eq(retval[0], b"Hello, nose!")
+
+ [i.remove() for i in self.ioctx.list_objects()]
+
+ def test_aio_setxattr(self):
+ lock = threading.Condition()
+ count = [0]
+ def cb(blah):
+ with lock:
+ count[0] += 1
+ lock.notify()
+ return 0
+ comp = self.ioctx.aio_setxattr("obj", "key", b'value', cb)
+ comp.wait_for_complete()
+ with lock:
+ while count[0] < 1:
+ lock.wait()
+ eq(comp.get_return_value(), 0)
+ eq(self.ioctx.get_xattr("obj", "key"), b'value')
+
+ def test_applications(self):
+ cmd = {"prefix":"osd dump", "format":"json"}
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'')
+ eq(ret, 0)
+ assert len(buf) > 0
+ release = json.loads(buf.decode("utf-8")).get("require_osd_release",
+ None)
+ if not release or release[0] < 'l':
+ pytest.skip('required_osd_release >= l')
+
+ eq([], self.ioctx.application_list())
+
+ self.ioctx.application_enable("app1")
+ assert_raises(Error, self.ioctx.application_enable, "app2")
+ self.ioctx.application_enable("app2", True)
+
+ assert_raises(Error, self.ioctx.application_metadata_list, "dne")
+ eq([], self.ioctx.application_metadata_list("app1"))
+
+ assert_raises(Error, self.ioctx.application_metadata_set, "dne", "key",
+ "key")
+ self.ioctx.application_metadata_set("app1", "key1", "val1")
+ eq("val1", self.ioctx.application_metadata_get("app1", "key1"))
+ self.ioctx.application_metadata_set("app1", "key2", "val2")
+ eq("val2", self.ioctx.application_metadata_get("app1", "key2"))
+ self.ioctx.application_metadata_set("app2", "key1", "val1")
+ eq("val1", self.ioctx.application_metadata_get("app2", "key1"))
+
+ eq([("key1", "val1"), ("key2", "val2")],
+ self.ioctx.application_metadata_list("app1"))
+
+ self.ioctx.application_metadata_remove("app1", "key1")
+ eq([("key2", "val2")], self.ioctx.application_metadata_list("app1"))
+
+ def test_service_daemon(self):
+ name = "pid-" + str(os.getpid())
+ metadata = {'version': '3.14', 'memory': '42'}
+ self.rados.service_daemon_register("laundry", name, metadata)
+ status = {'result': 'unknown', 'test': 'running'}
+ self.rados.service_daemon_update(status)
+
+ def test_alignment(self):
+ eq(self.ioctx.alignment(), None)
+
+
+@pytest.mark.ec
+class TestIoctxEc(object):
+
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.connect()
+ self.pool = 'test-ec'
+ self.profile = 'testprofile-%s' % self.pool
+ cmd = {"prefix": "osd erasure-code-profile set",
+ "name": self.profile, "profile": ["k=2", "m=1", "crush-failure-domain=osd"]}
+ ret, buf, out = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ assert ret == 0, out
+ # create ec pool with profile created above
+ cmd = {'prefix': 'osd pool create', 'pg_num': 8, 'pgp_num': 8,
+ 'pool': self.pool, 'pool_type': 'erasure',
+ 'erasure_code_profile': self.profile}
+ ret, buf, out = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ assert ret == 0, out
+ assert self.rados.pool_exists(self.pool)
+ self.ioctx = self.rados.open_ioctx(self.pool)
+
+ def teardown_method(self, method):
+ cmd = {"prefix": "osd unset", "key": "noup"}
+ self.rados.mon_command(json.dumps(cmd), b'')
+ self.ioctx.close()
+ self.rados.delete_pool(self.pool)
+ self.rados.shutdown()
+
+ def test_alignment(self):
+ eq(self.ioctx.alignment(), 8192)
+
+
+class TestIoctx2(object):
+
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.connect()
+ self.rados.create_pool('test_pool')
+ assert self.rados.pool_exists('test_pool')
+ pool_id = self.rados.pool_lookup('test_pool')
+ assert pool_id > 0
+ self.ioctx2 = self.rados.open_ioctx2(pool_id)
+
+ def teardown_method(self, method):
+ cmd = {"prefix": "osd unset", "key": "noup"}
+ self.rados.mon_command(json.dumps(cmd), b'')
+ self.ioctx2.close()
+ self.rados.delete_pool('test_pool')
+ self.rados.shutdown()
+
+ def test_get_last_version(self):
+ version = self.ioctx2.get_last_version()
+ assert version >= 0
+
+ def test_get_stats(self):
+ stats = self.ioctx2.get_stats()
+ eq(stats, {'num_objects_unfound': 0,
+ 'num_objects_missing_on_primary': 0,
+ 'num_object_clones': 0,
+ 'num_objects': 0,
+ 'num_object_copies': 0,
+ 'num_bytes': 0,
+ 'num_rd_kb': 0,
+ 'num_wr_kb': 0,
+ 'num_kb': 0,
+ 'num_wr': 0,
+ 'num_objects_degraded': 0,
+ 'num_rd': 0})
+
+
+class TestObject(object):
+
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.connect()
+ self.rados.create_pool('test_pool')
+ assert self.rados.pool_exists('test_pool')
+ self.ioctx = self.rados.open_ioctx('test_pool')
+ self.ioctx.write('foo', b'bar')
+ self.object = Object(self.ioctx, 'foo')
+
+ def teardown_method(self, method):
+ self.ioctx.close()
+ self.ioctx = None
+ self.rados.delete_pool('test_pool')
+ self.rados.shutdown()
+ self.rados = None
+
+ def test_read(self):
+ eq(self.object.read(3), b'bar')
+ eq(self.object.read(100), b'')
+
+ def test_seek(self):
+ self.object.write(b'blah')
+ self.object.seek(0)
+ eq(self.object.read(4), b'blah')
+ self.object.seek(1)
+ eq(self.object.read(3), b'lah')
+
+ def test_write(self):
+ self.object.write(b'barbaz')
+ self.object.seek(0)
+ eq(self.object.read(3), b'bar')
+ eq(self.object.read(3), b'baz')
+
+class TestIoCtxSelfManagedSnaps(object):
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.connect()
+ self.rados.create_pool('test_pool')
+ assert self.rados.pool_exists('test_pool')
+ self.ioctx = self.rados.open_ioctx('test_pool')
+
+ def teardown_method(self, method):
+ cmd = {"prefix":"osd unset", "key":"noup"}
+ self.rados.mon_command(json.dumps(cmd), b'')
+ self.ioctx.close()
+ self.rados.delete_pool('test_pool')
+ self.rados.shutdown()
+
+ @pytest.mark.rollback
+ def test(self):
+ # cannot mix-and-match pool and self-managed snapshot mode
+ self.ioctx.set_self_managed_snap_write([])
+ self.ioctx.write('abc', b'abc')
+ snap_id_1 = self.ioctx.create_self_managed_snap()
+ self.ioctx.set_self_managed_snap_write([snap_id_1])
+
+ self.ioctx.write('abc', b'def')
+ snap_id_2 = self.ioctx.create_self_managed_snap()
+ self.ioctx.set_self_managed_snap_write([snap_id_1, snap_id_2])
+
+ self.ioctx.write('abc', b'ghi')
+
+ self.ioctx.rollback_self_managed_snap('abc', snap_id_1)
+ eq(self.ioctx.read('abc'), b'abc')
+
+ self.ioctx.rollback_self_managed_snap('abc', snap_id_2)
+ eq(self.ioctx.read('abc'), b'def')
+
+ self.ioctx.remove_self_managed_snap(snap_id_1)
+ self.ioctx.remove_self_managed_snap(snap_id_2)
+
+class TestCommand(object):
+
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.connect()
+
+ def teardown_method(self, method):
+ self.rados.shutdown()
+
+ def test_monmap_dump(self):
+
+ # check for success and some plain output with epoch in it
+ cmd = {"prefix":"mon dump"}
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ eq(ret, 0)
+ assert len(buf) > 0
+ assert(b'epoch' in buf)
+
+ # JSON, and grab current epoch
+ cmd['format'] = 'json'
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ eq(ret, 0)
+ assert len(buf) > 0
+ d = json.loads(buf.decode("utf-8"))
+ assert('epoch' in d)
+ epoch = d['epoch']
+
+ # assume epoch + 1000 does not exist; test for ENOENT
+ cmd['epoch'] = epoch + 1000
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30)
+ eq(ret, -errno.ENOENT)
+ eq(len(buf), 0)
+ del cmd['epoch']
+
+ # send to specific target by name, rank
+ cmd = {"prefix": "version"}
+
+ target = d['mons'][0]['name']
+ print(target)
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30,
+ target=target)
+ eq(ret, 0)
+ assert len(buf) > 0
+ e = json.loads(buf.decode("utf-8"))
+ assert('release' in e)
+
+ target = d['mons'][0]['rank']
+ print(target)
+ ret, buf, errs = self.rados.mon_command(json.dumps(cmd), b'', timeout=30,
+ target=target)
+ eq(ret, 0)
+ assert len(buf) > 0
+ e = json.loads(buf.decode("utf-8"))
+ assert('release' in e)
+
+ @pytest.mark.bench
+ def test_osd_bench(self):
+ cmd = dict(prefix='bench', size=4096, count=8192)
+ ret, buf, err = self.rados.osd_command(0, json.dumps(cmd), b'',
+ timeout=30)
+ eq(ret, 0)
+ assert len(buf) > 0
+ out = json.loads(buf.decode('utf-8'))
+ eq(out['blocksize'], cmd['size'])
+ eq(out['bytes_written'], cmd['count'])
+
+ def test_ceph_osd_pool_create_utf8(self):
+ poolname = "\u9ec5"
+
+ cmd = {"prefix": "osd pool create", "pg_num": 16, "pool": poolname}
+ ret, buf, out = self.rados.mon_command(json.dumps(cmd), b'')
+ eq(ret, 0)
+ assert len(out) > 0
+ eq(u"pool '\u9ec5' created", out)
+
+
+@pytest.mark.watch
+class TestWatchNotify(object):
+ OID = "test_watch_notify"
+
+ def setup_method(self, method):
+ self.rados = Rados(conffile='')
+ self.rados.connect()
+ self.rados.create_pool('test_pool')
+ assert self.rados.pool_exists('test_pool')
+ self.ioctx = self.rados.open_ioctx('test_pool')
+ self.ioctx.write(self.OID, b'test watch notify')
+ self.lock = threading.Condition()
+ self.notify_cnt = {}
+ self.notify_data = {}
+ self.notify_error = {}
+ # aio related
+ self.ack_cnt = {}
+ self.ack_data = {}
+ self.instance_id = self.rados.get_instance_id()
+
+ def teardown_method(self, method):
+ self.ioctx.close()
+ self.rados.delete_pool('test_pool')
+ self.rados.shutdown()
+
+ def make_callback(self):
+ def callback(notify_id, notifier_id, watch_id, data):
+ with self.lock:
+ if watch_id not in self.notify_cnt:
+ self.notify_cnt[watch_id] = 1
+ elif self.notify_data[watch_id] != data:
+ self.notify_cnt[watch_id] += 1
+ self.notify_data[watch_id] = data
+ return callback
+
+ def make_error_callback(self):
+ def callback(watch_id, error):
+ with self.lock:
+ self.notify_error[watch_id] = error
+ return callback
+
+
+ def test(self):
+ with self.ioctx.watch(self.OID, self.make_callback(),
+ self.make_error_callback()) as watch1:
+ watch_id1 = watch1.get_id()
+ assert(watch_id1 > 0)
+
+ with self.rados.open_ioctx('test_pool') as ioctx:
+ watch2 = ioctx.watch(self.OID, self.make_callback(),
+ self.make_error_callback())
+ watch_id2 = watch2.get_id()
+ assert(watch_id2 > 0)
+
+ assert(self.ioctx.notify(self.OID, 'test'))
+ with self.lock:
+ assert(watch_id1 in self.notify_cnt)
+ assert(watch_id2 in self.notify_cnt)
+ eq(self.notify_cnt[watch_id1], 1)
+ eq(self.notify_cnt[watch_id2], 1)
+ eq(self.notify_data[watch_id1], b'test')
+ eq(self.notify_data[watch_id2], b'test')
+
+ assert(watch1.check() >= timedelta())
+ assert(watch2.check() >= timedelta())
+
+ assert(self.ioctx.notify(self.OID, 'best'))
+ with self.lock:
+ eq(self.notify_cnt[watch_id1], 2)
+ eq(self.notify_cnt[watch_id2], 2)
+ eq(self.notify_data[watch_id1], b'best')
+ eq(self.notify_data[watch_id2], b'best')
+
+ watch2.close()
+
+ assert(self.ioctx.notify(self.OID, 'rest'))
+ with self.lock:
+ eq(self.notify_cnt[watch_id1], 3)
+ eq(self.notify_cnt[watch_id2], 2)
+ eq(self.notify_data[watch_id1], b'rest')
+ eq(self.notify_data[watch_id2], b'best')
+
+ assert(watch1.check() >= timedelta())
+
+ self.ioctx.remove_object(self.OID)
+
+ for i in range(10):
+ with self.lock:
+ if watch_id1 in self.notify_error:
+ break
+ time.sleep(1)
+ eq(self.notify_error[watch_id1], -errno.ENOTCONN)
+ assert_raises(NotConnected, watch1.check)
+
+ assert_raises(ObjectNotFound, self.ioctx.notify, self.OID, 'test')
+
+ def make_callback_reply(self):
+ def callback(notify_id, notifier_id, watch_id, data):
+ with self.lock:
+ return data
+ return callback
+
+ def notify_callback(self, _, r, ack_list, timeout_list):
+ eq(r, 0)
+ with self.lock:
+ for notifier_id, _, notifier_data in ack_list:
+ if notifier_id not in self.ack_cnt:
+ self.ack_cnt[notifier_id] = 0
+ self.ack_cnt[notifier_id] += 1
+ self.ack_data[notifier_id] = notifier_data
+
+ def notify_callback_err(self, _, r, ack_list, timeout_list):
+ eq(r, -errno.ENOENT)
+
+ def test_aio_notify(self):
+ with self.ioctx.watch(self.OID, self.make_callback_reply(),
+ self.make_error_callback()) as watch1:
+ watch_id1 = watch1.get_id()
+ assert watch_id1 > 0
+
+ with self.rados.open_ioctx('test_pool') as ioctx:
+ watch2 = ioctx.watch(self.OID, self.make_callback_reply(),
+ self.make_error_callback())
+ watch_id2 = watch2.get_id()
+ assert watch_id2 > 0
+
+ comp = self.ioctx.aio_notify(self.OID, self.notify_callback, msg='test')
+ comp.wait_for_complete_and_cb()
+ with self.lock:
+ assert self.instance_id in self.ack_cnt
+ eq(self.ack_cnt[self.instance_id], 2)
+ eq(self.ack_data[self.instance_id], b'test')
+
+ assert watch1.check() >= timedelta()
+ assert watch2.check() >= timedelta()
+
+ comp = self.ioctx.aio_notify(self.OID, self.notify_callback, msg='best')
+ comp.wait_for_complete_and_cb()
+ with self.lock:
+ eq(self.ack_cnt[self.instance_id], 4)
+ eq(self.ack_data[self.instance_id], b'best')
+
+ watch2.close()
+
+ comp = self.ioctx.aio_notify(self.OID, self.notify_callback, msg='rest')
+ comp.wait_for_complete_and_cb()
+ with self.lock:
+ eq(self.ack_cnt[self.instance_id], 5)
+ eq(self.ack_data[self.instance_id], b'rest')
+
+ assert(watch1.check() >= timedelta())
+ self.ioctx.remove_object(self.OID)
+
+ for i in range(10):
+ with self.lock:
+ if watch_id1 in self.notify_error:
+ break
+ time.sleep(1)
+ eq(self.notify_error[watch_id1], -errno.ENOTCONN)
+ assert_raises(NotConnected, watch1.check)
+
+ comp = self.ioctx.aio_notify(self.OID, self.notify_callback_err, msg='test')
+ comp.wait_for_complete_and_cb()
diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py
new file mode 100644
index 000000000..7b5f31b57
--- /dev/null
+++ b/src/test/pybind/test_rbd.py
@@ -0,0 +1,2810 @@
+# vim: expandtab smarttab shiftwidth=4 softtabstop=4
+import base64
+import copy
+import errno
+import functools
+import json
+import socket
+import os
+import platform
+import pytest
+import time
+import sys
+
+from assertions import (assert_equal as eq, assert_raises, assert_not_equal,
+ assert_greater_equal)
+from datetime import datetime, timedelta
+from rados import (Rados,
+ LIBRADOS_OP_FLAG_FADVISE_DONTNEED,
+ LIBRADOS_OP_FLAG_FADVISE_NOCACHE,
+ LIBRADOS_OP_FLAG_FADVISE_RANDOM)
+from rbd import (RBD, Group, Image, ImageNotFound, InvalidArgument, ImageExists,
+ ImageBusy, ImageHasSnapshots, ReadOnlyImage,
+ FunctionNotSupported, ArgumentOutOfRange,
+ ECANCELED, OperationCanceled,
+ DiskQuotaExceeded, ConnectionShutdown, PermissionError,
+ RBD_FEATURE_LAYERING, RBD_FEATURE_STRIPINGV2,
+ RBD_FEATURE_EXCLUSIVE_LOCK, RBD_FEATURE_JOURNALING,
+ RBD_FEATURE_DEEP_FLATTEN, RBD_FEATURE_FAST_DIFF,
+ RBD_FEATURE_OBJECT_MAP,
+ RBD_MIRROR_MODE_DISABLED, RBD_MIRROR_MODE_IMAGE,
+ RBD_MIRROR_MODE_POOL, RBD_MIRROR_IMAGE_ENABLED,
+ RBD_MIRROR_IMAGE_DISABLED, MIRROR_IMAGE_STATUS_STATE_UNKNOWN,
+ RBD_MIRROR_IMAGE_MODE_JOURNAL, RBD_MIRROR_IMAGE_MODE_SNAPSHOT,
+ RBD_LOCK_MODE_EXCLUSIVE, RBD_OPERATION_FEATURE_GROUP,
+ RBD_SNAP_NAMESPACE_TYPE_TRASH,
+ RBD_SNAP_NAMESPACE_TYPE_MIRROR,
+ RBD_IMAGE_MIGRATION_STATE_PREPARED, RBD_CONFIG_SOURCE_CONFIG,
+ RBD_CONFIG_SOURCE_POOL, RBD_CONFIG_SOURCE_IMAGE,
+ RBD_MIRROR_PEER_ATTRIBUTE_NAME_MON_HOST,
+ RBD_MIRROR_PEER_ATTRIBUTE_NAME_KEY,
+ RBD_MIRROR_PEER_DIRECTION_RX, RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ RBD_SNAP_REMOVE_UNPROTECT, RBD_SNAP_MIRROR_STATE_PRIMARY,
+ RBD_SNAP_MIRROR_STATE_PRIMARY_DEMOTED,
+ RBD_SNAP_CREATE_SKIP_QUIESCE,
+ RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR,
+ RBD_WRITE_ZEROES_FLAG_THICK_PROVISION,
+ RBD_ENCRYPTION_FORMAT_LUKS1, RBD_ENCRYPTION_FORMAT_LUKS2,
+ RBD_ENCRYPTION_FORMAT_LUKS)
+
+rados = None
+ioctx = None
+features = None
+image_idx = 0
+group_idx = 0
+snap_idx = 0
+image_name = None
+group_name = None
+snap_name = None
+pool_idx = 0
+pool_name = None
+IMG_SIZE = 8 << 20 # 8 MiB
+IMG_ORDER = 22 # 4 MiB objects
+
+os.environ["RBD_FORCE_ALLOW_V1"] = "1"
+
+def setup_module():
+ global rados
+ rados = Rados(conffile='')
+ rados.connect()
+ global pool_name
+ pool_name = get_temp_pool_name()
+ rados.create_pool(pool_name)
+ global ioctx
+ ioctx = rados.open_ioctx(pool_name)
+ RBD().pool_init(ioctx, True)
+ global features
+ features = os.getenv("RBD_FEATURES")
+ if features is not None:
+ features = int(features)
+
+def teardown_module():
+ global ioctx
+ ioctx.close()
+ global rados
+ rados.delete_pool(pool_name)
+ rados.shutdown()
+
+def get_temp_pool_name():
+ global pool_idx
+ pool_idx += 1
+ return "test-rbd-api-" + socket.gethostname() + '-' + str(os.getpid()) + \
+ '-' + str(pool_idx)
+
+def get_temp_image_name():
+ global image_idx
+ image_idx += 1
+ return "image" + str(image_idx)
+
+def get_temp_group_name():
+ global group_idx
+ group_idx += 1
+ return "group" + str(group_idx)
+
+def get_temp_snap_name():
+ global snap_idx
+ snap_idx += 1
+ return "snap" + str(snap_idx)
+
+def create_image():
+ global image_name
+ image_name = get_temp_image_name()
+ if features is not None:
+ RBD().create(ioctx, image_name, IMG_SIZE, IMG_ORDER, old_format=False,
+ features=int(features))
+ else:
+ RBD().create(ioctx, image_name, IMG_SIZE, IMG_ORDER, old_format=True)
+ return image_name
+
+def remove_image():
+ if image_name is not None:
+ RBD().remove(ioctx, image_name)
+
+@pytest.fixture
+def tmp_image():
+ create_image()
+ yield
+ remove_image()
+
+def create_group():
+ global group_name
+ group_name = get_temp_group_name()
+ RBD().group_create(ioctx, group_name)
+
+def remove_group():
+ if group_name is not None:
+ RBD().group_remove(ioctx, group_name)
+
+@pytest.fixture
+def tmp_group():
+ create_group()
+ yield
+ remove_group()
+
+def rename_group():
+ new_group_name = "new" + group_name
+ RBD().group_rename(ioctx, group_name, new_group_name)
+
+def require_new_format():
+ def wrapper(fn):
+ def _require_new_format(*args, **kwargs):
+ global features
+ if features is None:
+ pytest.skip('requires new format')
+ return fn(*args, **kwargs)
+ return functools.wraps(fn)(_require_new_format)
+ return wrapper
+
+def require_features(required_features):
+ def wrapper(fn):
+ def _require_features(*args, **kwargs):
+ global features
+ if features is None:
+ pytest.skip('requires new format')
+ for feature in required_features:
+ if feature & features != feature:
+ pytest.skip('missing required feature')
+ return fn(*args, **kwargs)
+ return functools.wraps(fn)(_require_features)
+ return wrapper
+
+def require_linux():
+ def wrapper(fn):
+ def _require_linux(*args, **kwargs):
+ if platform.system() != "Linux":
+ pytest.skip('requires linux')
+ return fn(*args, **kwargs)
+ return functools.wraps(fn)(_require_linux)
+ return wrapper
+
+def blocklist_features(blocklisted_features):
+ def wrapper(fn):
+ def _blocklist_features(*args, **kwargs):
+ global features
+ for feature in blocklisted_features:
+ if features is not None and feature & features == feature:
+ pytest.skip('blocklisted feature enabled')
+ return fn(*args, **kwargs)
+ return functools.wraps(fn)(_blocklist_features)
+ return wrapper
+
+def test_version():
+ RBD().version()
+
+def test_create():
+ create_image()
+ remove_image()
+
+def check_default_params(format, order=None, features=None, stripe_count=None,
+ stripe_unit=None, exception=None):
+ global rados
+ global ioctx
+ orig_vals = {}
+ for k in ['rbd_default_format', 'rbd_default_order', 'rbd_default_features',
+ 'rbd_default_stripe_count', 'rbd_default_stripe_unit']:
+ orig_vals[k] = rados.conf_get(k)
+ try:
+ rados.conf_set('rbd_default_format', str(format))
+ if order is not None:
+ rados.conf_set('rbd_default_order', str(order or 0))
+ if features is not None:
+ rados.conf_set('rbd_default_features', str(features or 0))
+ if stripe_count is not None:
+ rados.conf_set('rbd_default_stripe_count', str(stripe_count or 0))
+ if stripe_unit is not None:
+ rados.conf_set('rbd_default_stripe_unit', str(stripe_unit or 0))
+ feature_data_pool = 0
+ datapool = rados.conf_get('rbd_default_data_pool')
+ if not len(datapool) == 0:
+ feature_data_pool = 128
+ image_name = get_temp_image_name()
+ if exception is None:
+ RBD().create(ioctx, image_name, IMG_SIZE, old_format=(format == 1))
+ try:
+ with Image(ioctx, image_name) as image:
+ eq(format == 1, image.old_format())
+
+ expected_order = int(rados.conf_get('rbd_default_order'))
+ actual_order = image.stat()['order']
+ eq(expected_order, actual_order)
+
+ expected_features = features
+ if format == 1:
+ expected_features = 0
+ elif expected_features is None:
+ expected_features = 61 | feature_data_pool
+ else:
+ expected_features |= feature_data_pool
+ eq(expected_features, image.features())
+
+ expected_stripe_count = stripe_count
+ if not expected_stripe_count or format == 1 or \
+ features & RBD_FEATURE_STRIPINGV2 == 0:
+ expected_stripe_count = 1
+ eq(expected_stripe_count, image.stripe_count())
+
+ expected_stripe_unit = stripe_unit
+ if not expected_stripe_unit or format == 1 or \
+ features & RBD_FEATURE_STRIPINGV2 == 0:
+ expected_stripe_unit = 1 << actual_order
+ eq(expected_stripe_unit, image.stripe_unit())
+ finally:
+ RBD().remove(ioctx, image_name)
+ else:
+ assert_raises(exception, RBD().create, ioctx, image_name, IMG_SIZE)
+ finally:
+ for k, v in orig_vals.items():
+ rados.conf_set(k, v)
+
+def test_create_defaults():
+ # basic format 1 and 2
+ check_default_params(1)
+ check_default_params(2)
+ # invalid order
+ check_default_params(1, 0, exception=ArgumentOutOfRange)
+ check_default_params(2, 0, exception=ArgumentOutOfRange)
+ check_default_params(1, 11, exception=ArgumentOutOfRange)
+ check_default_params(2, 11, exception=ArgumentOutOfRange)
+ check_default_params(1, 65, exception=ArgumentOutOfRange)
+ check_default_params(2, 65, exception=ArgumentOutOfRange)
+ # striping and features are ignored for format 1
+ check_default_params(1, 20, 0, 1, 1)
+ check_default_params(1, 20, 3, 1, 1)
+ check_default_params(1, 20, 0, 0, 0)
+ # striping is ignored if stripingv2 is not set
+ check_default_params(2, 20, 0, 1, 1 << 20)
+ check_default_params(2, 20, RBD_FEATURE_LAYERING, 1, 1 << 20)
+ check_default_params(2, 20, 0, 0, 0)
+ # striping with stripingv2 is fine
+ check_default_params(2, 20, RBD_FEATURE_STRIPINGV2, 1, 1 << 16)
+ check_default_params(2, 20, RBD_FEATURE_STRIPINGV2, 10, 1 << 20)
+ check_default_params(2, 20, RBD_FEATURE_STRIPINGV2, 10, 1 << 16)
+ check_default_params(2, 20, 0, 0, 0)
+ # make sure invalid combinations of stripe unit and order are still invalid
+ check_default_params(2, 22, RBD_FEATURE_STRIPINGV2, 10, 1 << 50, exception=InvalidArgument)
+ check_default_params(2, 22, RBD_FEATURE_STRIPINGV2, 10, 100, exception=InvalidArgument)
+ check_default_params(2, 22, RBD_FEATURE_STRIPINGV2, 0, 1, exception=InvalidArgument)
+ check_default_params(2, 22, RBD_FEATURE_STRIPINGV2, 1, 0, exception=InvalidArgument)
+ # 0 stripe unit and count are still ignored
+ check_default_params(2, 22, 0, 0, 0)
+
+def test_context_manager():
+ with Rados(conffile='') as cluster:
+ with cluster.open_ioctx(pool_name) as ioctx:
+ image_name = get_temp_image_name()
+ RBD().create(ioctx, image_name, IMG_SIZE)
+ with Image(ioctx, image_name) as image:
+ data = rand_data(256)
+ image.write(data, 0)
+ read = image.read(0, 256)
+ RBD().remove(ioctx, image_name)
+ eq(data, read)
+
+def test_open_read_only():
+ with Rados(conffile='') as cluster:
+ with cluster.open_ioctx(pool_name) as ioctx:
+ image_name = get_temp_image_name()
+ RBD().create(ioctx, image_name, IMG_SIZE)
+ data = rand_data(256)
+ with Image(ioctx, image_name) as image:
+ image.write(data, 0)
+ image.create_snap('snap')
+ with Image(ioctx, image_name, read_only=True) as image:
+ read = image.read(0, 256)
+ eq(data, read)
+ assert_raises(ReadOnlyImage, image.write, data, 0)
+ assert_raises(ReadOnlyImage, image.create_snap, 'test')
+ assert_raises(ReadOnlyImage, image.remove_snap, 'snap')
+ assert_raises(ReadOnlyImage, image.rollback_to_snap, 'snap')
+ assert_raises(ReadOnlyImage, image.protect_snap, 'snap')
+ assert_raises(ReadOnlyImage, image.unprotect_snap, 'snap')
+ assert_raises(ReadOnlyImage, image.unprotect_snap, 'snap')
+ assert_raises(ReadOnlyImage, image.flatten)
+ with Image(ioctx, image_name) as image:
+ image.remove_snap('snap')
+ RBD().remove(ioctx, image_name)
+ eq(data, read)
+
+def test_open_dne():
+ for i in range(100):
+ image_name = get_temp_image_name()
+ assert_raises(ImageNotFound, Image, ioctx, image_name + 'dne')
+ assert_raises(ImageNotFound, Image, ioctx, image_name, 'snap')
+
+def test_open_readonly_dne():
+ for i in range(100):
+ image_name = get_temp_image_name()
+ assert_raises(ImageNotFound, Image, ioctx, image_name + 'dne',
+ read_only=True)
+ assert_raises(ImageNotFound, Image, ioctx, image_name, 'snap',
+ read_only=True)
+
+@require_new_format()
+def test_open_by_id():
+ with Rados(conffile='') as cluster:
+ with cluster.open_ioctx(pool_name) as ioctx:
+ image_name = get_temp_image_name()
+ RBD().create(ioctx, image_name, IMG_SIZE)
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+ with Image(ioctx, image_id=image_id) as image:
+ eq(image.get_name(), image_name)
+ RBD().remove(ioctx, image_name)
+
+def test_aio_open():
+ with Rados(conffile='') as cluster:
+ with cluster.open_ioctx(pool_name) as ioctx:
+ image_name = get_temp_image_name()
+ order = 20
+ RBD().create(ioctx, image_name, IMG_SIZE, order)
+
+ # this is a list so that the open_cb() can modify it
+ image = [None]
+ def open_cb(_, image_):
+ image[0] = image_
+
+ comp = RBD().aio_open_image(open_cb, ioctx, image_name)
+ comp.wait_for_complete_and_cb()
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+ assert_not_equal(image[0], None)
+
+ image = image[0]
+ eq(image.get_name(), image_name)
+ check_stat(image.stat(), IMG_SIZE, order)
+
+ closed = [False]
+ def close_cb(_):
+ closed[0] = True
+
+ comp = image.aio_close(close_cb)
+ comp.wait_for_complete_and_cb()
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+ eq(closed[0], True)
+
+ RBD().remove(ioctx, image_name)
+
+def test_remove_dne():
+ assert_raises(ImageNotFound, remove_image)
+
+def test_list_empty():
+ eq([], RBD().list(ioctx))
+
+def test_list(tmp_image):
+ eq([image_name], RBD().list(ioctx))
+
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+ eq([{'id': image_id, 'name': image_name}], list(RBD().list2(ioctx)))
+
+def test_remove_with_progress():
+ create_image()
+ d = {'received_callback': False}
+ def progress_cb(current, total):
+ d['received_callback'] = True
+ return 0
+
+ RBD().remove(ioctx, image_name, on_progress=progress_cb)
+ eq(True, d['received_callback'])
+
+def test_remove_canceled(tmp_image):
+ def progress_cb(current, total):
+ return -ECANCELED
+
+ assert_raises(OperationCanceled, RBD().remove, ioctx, image_name,
+ on_progress=progress_cb)
+
+def test_rename(tmp_image):
+ rbd = RBD()
+ image_name2 = get_temp_image_name()
+ rbd.rename(ioctx, image_name, image_name2)
+ eq([image_name2], rbd.list(ioctx))
+ rbd.rename(ioctx, image_name2, image_name)
+ eq([image_name], rbd.list(ioctx))
+
+def test_pool_metadata():
+ rbd = RBD()
+ metadata = list(rbd.pool_metadata_list(ioctx))
+ eq(len(metadata), 0)
+ assert_raises(KeyError, rbd.pool_metadata_get, ioctx, "key1")
+ rbd.pool_metadata_set(ioctx, "key1", "value1")
+ rbd.pool_metadata_set(ioctx, "key2", "value2")
+ value = rbd.pool_metadata_get(ioctx, "key1")
+ eq(value, "value1")
+ value = rbd.pool_metadata_get(ioctx, "key2")
+ eq(value, "value2")
+ metadata = list(rbd.pool_metadata_list(ioctx))
+ eq(len(metadata), 2)
+ rbd.pool_metadata_remove(ioctx, "key1")
+ metadata = list(rbd.pool_metadata_list(ioctx))
+ eq(len(metadata), 1)
+ eq(metadata[0], ("key2", "value2"))
+ rbd.pool_metadata_remove(ioctx, "key2")
+ assert_raises(KeyError, rbd.pool_metadata_remove, ioctx, "key2")
+ metadata = list(rbd.pool_metadata_list(ioctx))
+ eq(len(metadata), 0)
+
+ N = 65
+ for i in range(N):
+ rbd.pool_metadata_set(ioctx, "key" + str(i), "X" * 1025)
+ metadata = list(rbd.pool_metadata_list(ioctx))
+ eq(len(metadata), N)
+ for i in range(N):
+ rbd.pool_metadata_remove(ioctx, "key" + str(i))
+ metadata = list(rbd.pool_metadata_list(ioctx))
+ eq(len(metadata), N - i - 1)
+
+def test_config_list():
+ rbd = RBD()
+
+ for option in rbd.config_list(ioctx):
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "true")
+
+ for option in rbd.config_list(ioctx):
+ if option['name'] == "rbd_cache":
+ eq(option['source'], RBD_CONFIG_SOURCE_POOL)
+ else:
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ rbd.pool_metadata_remove(ioctx, "conf_rbd_cache")
+
+ for option in rbd.config_list(ioctx):
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+def test_pool_config_set_and_get_and_remove():
+ rbd = RBD()
+
+ for option in rbd.config_list(ioctx):
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ rbd.config_set(ioctx, "rbd_request_timed_out_seconds", "100")
+ new_value = rbd.config_get(ioctx, "rbd_request_timed_out_seconds")
+ eq(new_value, "100")
+ rbd.config_remove(ioctx, "rbd_request_timed_out_seconds")
+
+ for option in rbd.config_list(ioctx):
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+def test_namespaces():
+ rbd = RBD()
+
+ eq(False, rbd.namespace_exists(ioctx, 'ns1'))
+ eq([], rbd.namespace_list(ioctx))
+ assert_raises(ImageNotFound, rbd.namespace_remove, ioctx, 'ns1')
+
+ rbd.namespace_create(ioctx, 'ns1')
+ eq(True, rbd.namespace_exists(ioctx, 'ns1'))
+
+ assert_raises(ImageExists, rbd.namespace_create, ioctx, 'ns1')
+ eq(['ns1'], rbd.namespace_list(ioctx))
+ rbd.namespace_remove(ioctx, 'ns1')
+ eq([], rbd.namespace_list(ioctx))
+
+@require_new_format()
+def test_pool_stats():
+ rbd = RBD()
+
+ try:
+ image1 = create_image()
+ image2 = create_image()
+ image3 = create_image()
+ image4 = create_image()
+ with Image(ioctx, image4) as image:
+ image.create_snap('snap')
+ image.resize(0)
+
+ stats = rbd.pool_stats_get(ioctx)
+ eq(stats['image_count'], 4)
+ eq(stats['image_provisioned_bytes'], 3 * IMG_SIZE)
+ eq(stats['image_max_provisioned_bytes'], 4 * IMG_SIZE)
+ eq(stats['image_snap_count'], 1)
+ eq(stats['trash_count'], 0)
+ eq(stats['trash_provisioned_bytes'], 0)
+ eq(stats['trash_max_provisioned_bytes'], 0)
+ eq(stats['trash_snap_count'], 0)
+ finally:
+ rbd.remove(ioctx, image1)
+ rbd.remove(ioctx, image2)
+ rbd.remove(ioctx, image3)
+ with Image(ioctx, image4) as image:
+ image.remove_snap('snap')
+ rbd.remove(ioctx, image4)
+
+def rand_data(size):
+ return os.urandom(size)
+
+def check_stat(info, size, order):
+ assert 'block_name_prefix' in info
+ eq(info['size'], size)
+ eq(info['order'], order)
+ eq(info['num_objs'], size // (1 << order))
+ eq(info['obj_size'], 1 << order)
+
+@require_new_format()
+def test_features_to_string():
+ rbd = RBD()
+ features = RBD_FEATURE_DEEP_FLATTEN | RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_FAST_DIFF \
+ | RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP
+ expected_features_string = "deep-flatten,exclusive-lock,fast-diff,layering,object-map"
+ features_string = rbd.features_to_string(features)
+ eq(expected_features_string, features_string)
+
+ features = RBD_FEATURE_LAYERING
+ features_string = rbd.features_to_string(features)
+ eq(features_string, "layering")
+
+ features = 16777216
+ assert_raises(InvalidArgument, rbd.features_to_string, features)
+
+@require_new_format()
+def test_features_from_string():
+ rbd = RBD()
+ features_string = "deep-flatten,exclusive-lock,fast-diff,layering,object-map"
+ expected_features_bitmask = RBD_FEATURE_DEEP_FLATTEN | RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_FAST_DIFF \
+ | RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP
+ features = rbd.features_from_string(features_string)
+ eq(expected_features_bitmask, features)
+
+ features_string = "layering"
+ features = rbd.features_from_string(features_string)
+ eq(features, RBD_FEATURE_LAYERING)
+
+class TestImage(object):
+
+ def setup_method(self, method):
+ self.rbd = RBD()
+ create_image()
+ self.image = Image(ioctx, image_name)
+
+ def teardown_method(self, method):
+ self.image.close()
+ remove_image()
+ self.image = None
+
+ @require_new_format()
+ @blocklist_features([RBD_FEATURE_EXCLUSIVE_LOCK])
+ def test_update_features(self):
+ features = self.image.features()
+ self.image.update_features(RBD_FEATURE_EXCLUSIVE_LOCK, True)
+ eq(features | RBD_FEATURE_EXCLUSIVE_LOCK, self.image.features())
+
+ @require_features([RBD_FEATURE_STRIPINGV2])
+ def test_create_with_params(self):
+ global features
+ image_name = get_temp_image_name()
+ order = 20
+ stripe_unit = 1 << 20
+ stripe_count = 10
+ self.rbd.create(ioctx, image_name, IMG_SIZE, order,
+ False, features, stripe_unit, stripe_count)
+ image = Image(ioctx, image_name)
+ info = image.stat()
+ check_stat(info, IMG_SIZE, order)
+ eq(image.features(), features)
+ eq(image.stripe_unit(), stripe_unit)
+ eq(image.stripe_count(), stripe_count)
+ image.close()
+ RBD().remove(ioctx, image_name)
+
+ @require_new_format()
+ def test_id(self):
+ assert_not_equal(b'', self.image.id())
+
+ def test_block_name_prefix(self):
+ assert_not_equal(b'', self.image.block_name_prefix())
+
+ def test_data_pool_id(self):
+ assert_greater_equal(self.image.data_pool_id(), 0)
+
+ def test_create_timestamp(self):
+ timestamp = self.image.create_timestamp()
+ assert_not_equal(0, timestamp.year)
+ assert_not_equal(1970, timestamp.year)
+
+ def test_access_timestamp(self):
+ timestamp = self.image.access_timestamp()
+ assert_not_equal(0, timestamp.year)
+ assert_not_equal(1970, timestamp.year)
+
+ def test_modify_timestamp(self):
+ timestamp = self.image.modify_timestamp()
+ assert_not_equal(0, timestamp.year)
+ assert_not_equal(1970, timestamp.year)
+
+ def test_invalidate_cache(self):
+ self.image.write(b'abc', 0)
+ eq(b'abc', self.image.read(0, 3))
+ self.image.invalidate_cache()
+ eq(b'abc', self.image.read(0, 3))
+
+ def test_stat(self):
+ info = self.image.stat()
+ check_stat(info, IMG_SIZE, IMG_ORDER)
+
+ def test_flags(self):
+ flags = self.image.flags()
+ eq(0, flags)
+
+ def test_image_auto_close(self):
+ image = Image(ioctx, image_name)
+
+ def test_use_after_close(self):
+ self.image.close()
+ assert_raises(InvalidArgument, self.image.stat)
+
+ def test_write(self):
+ data = rand_data(256)
+ self.image.write(data, 0)
+
+ def test_write_with_fadvise_flags(self):
+ data = rand_data(256)
+ self.image.write(data, 0, LIBRADOS_OP_FLAG_FADVISE_DONTNEED)
+ self.image.write(data, 0, LIBRADOS_OP_FLAG_FADVISE_NOCACHE)
+
+ def test_write_zeroes(self):
+ data = rand_data(256)
+ self.image.write(data, 0)
+ self.image.write_zeroes(0, 256)
+ eq(self.image.read(256, 256), b'\0' * 256)
+ check_diff(self.image, 0, IMG_SIZE, None, [])
+
+ def test_write_zeroes_thick_provision(self):
+ data = rand_data(256)
+ self.image.write(data, 0)
+ self.image.write_zeroes(0, 256, RBD_WRITE_ZEROES_FLAG_THICK_PROVISION)
+ eq(self.image.read(256, 256), b'\0' * 256)
+ check_diff(self.image, 0, IMG_SIZE, None, [(0, 256, True)])
+
+ def test_read(self):
+ data = self.image.read(0, 20)
+ eq(data, b'\0' * 20)
+
+ def test_read_with_fadvise_flags(self):
+ data = self.image.read(0, 20, LIBRADOS_OP_FLAG_FADVISE_DONTNEED)
+ eq(data, b'\0' * 20)
+ data = self.image.read(0, 20, LIBRADOS_OP_FLAG_FADVISE_RANDOM)
+ eq(data, b'\0' * 20)
+
+ def test_large_write(self):
+ data = rand_data(IMG_SIZE)
+ self.image.write(data, 0)
+
+ def test_large_read(self):
+ data = self.image.read(0, IMG_SIZE)
+ eq(data, b'\0' * IMG_SIZE)
+
+ def test_write_read(self):
+ data = rand_data(256)
+ offset = 50
+ self.image.write(data, offset)
+ read = self.image.read(offset, 256)
+ eq(data, read)
+
+ def test_read_bad_offset(self):
+ assert_raises(InvalidArgument, self.image.read, IMG_SIZE + 1, IMG_SIZE)
+
+ def test_resize(self):
+ new_size = IMG_SIZE * 2
+ self.image.resize(new_size)
+ info = self.image.stat()
+ check_stat(info, new_size, IMG_ORDER)
+
+ def test_resize_allow_shrink_False(self):
+ new_size = IMG_SIZE * 2
+ self.image.resize(new_size)
+ info = self.image.stat()
+ check_stat(info, new_size, IMG_ORDER)
+ assert_raises(InvalidArgument, self.image.resize, IMG_SIZE, False)
+
+ def test_size(self):
+ eq(IMG_SIZE, self.image.size())
+ self.image.create_snap('snap1')
+ new_size = IMG_SIZE * 2
+ self.image.resize(new_size)
+ eq(new_size, self.image.size())
+ self.image.create_snap('snap2')
+ self.image.set_snap('snap2')
+ eq(new_size, self.image.size())
+ self.image.set_snap('snap1')
+ eq(IMG_SIZE, self.image.size())
+ self.image.set_snap(None)
+ eq(new_size, self.image.size())
+ self.image.remove_snap('snap1')
+ self.image.remove_snap('snap2')
+
+ def test_resize_down(self):
+ new_size = IMG_SIZE // 2
+ data = rand_data(256)
+ self.image.write(data, IMG_SIZE // 2);
+ self.image.resize(new_size)
+ self.image.resize(IMG_SIZE)
+ read = self.image.read(IMG_SIZE // 2, 256)
+ eq(b'\0' * 256, read)
+
+ def test_resize_bytes(self):
+ new_size = IMG_SIZE // 2 - 5
+ data = rand_data(256)
+ self.image.write(data, IMG_SIZE // 2 - 10);
+ self.image.resize(new_size)
+ self.image.resize(IMG_SIZE)
+ read = self.image.read(IMG_SIZE // 2 - 10, 5)
+ eq(data[:5], read)
+ read = self.image.read(IMG_SIZE // 2 - 5, 251)
+ eq(b'\0' * 251, read)
+
+ def _test_copy(self, features=None, order=None, stripe_unit=None,
+ stripe_count=None):
+ global ioctx
+ data = rand_data(256)
+ self.image.write(data, 256)
+ image_name = get_temp_image_name()
+ if features is None:
+ self.image.copy(ioctx, image_name)
+ elif order is None:
+ self.image.copy(ioctx, image_name, features)
+ elif stripe_unit is None:
+ self.image.copy(ioctx, image_name, features, order)
+ elif stripe_count is None:
+ self.image.copy(ioctx, image_name, features, order, stripe_unit)
+ else:
+ self.image.copy(ioctx, image_name, features, order, stripe_unit,
+ stripe_count)
+ assert_raises(ImageExists, self.image.copy, ioctx, image_name)
+ copy = Image(ioctx, image_name)
+ copy_data = copy.read(256, 256)
+ copy.close()
+ self.rbd.remove(ioctx, image_name)
+ eq(data, copy_data)
+
+ def test_copy(self):
+ self._test_copy()
+
+ def test_copy2(self):
+ self._test_copy(self.image.features(), self.image.stat()['order'])
+
+ @require_features([RBD_FEATURE_STRIPINGV2])
+ def test_copy3(self):
+ global features
+ self._test_copy(features, self.image.stat()['order'],
+ self.image.stripe_unit(), self.image.stripe_count())
+
+ @pytest.mark.skip_if_crimson
+ def test_deep_copy(self):
+ global ioctx
+ global features
+ self.image.write(b'a' * 256, 0)
+ self.image.create_snap('snap1')
+ self.image.write(b'b' * 256, 0)
+ dst_name = get_temp_image_name()
+ self.image.deep_copy(ioctx, dst_name, features=features,
+ order=self.image.stat()['order'],
+ stripe_unit=self.image.stripe_unit(),
+ stripe_count=self.image.stripe_count(),
+ data_pool=None)
+ self.image.remove_snap('snap1')
+ with Image(ioctx, dst_name, 'snap1') as copy:
+ copy_data = copy.read(0, 256)
+ eq(b'a' * 256, copy_data)
+ with Image(ioctx, dst_name) as copy:
+ copy_data = copy.read(0, 256)
+ eq(b'b' * 256, copy_data)
+ copy.remove_snap('snap1')
+ self.rbd.remove(ioctx, dst_name)
+
+ @require_features([RBD_FEATURE_LAYERING])
+ def test_deep_copy_clone(self):
+ global ioctx
+ global features
+ self.image.write(b'a' * 256, 0)
+ self.image.create_snap('snap1')
+ self.image.write(b'b' * 256, 0)
+ self.image.protect_snap('snap1')
+ clone_name = get_temp_image_name()
+ dst_name = get_temp_image_name()
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name)
+ with Image(ioctx, clone_name) as child:
+ child.create_snap('snap1')
+ child.deep_copy(ioctx, dst_name, features=features,
+ order=self.image.stat()['order'],
+ stripe_unit=self.image.stripe_unit(),
+ stripe_count=self.image.stripe_count(),
+ data_pool=None)
+ child.remove_snap('snap1')
+
+ with Image(ioctx, dst_name) as copy:
+ copy_data = copy.read(0, 256)
+ eq(b'a' * 256, copy_data)
+ copy.remove_snap('snap1')
+ self.rbd.remove(ioctx, dst_name)
+ self.rbd.remove(ioctx, clone_name)
+ self.image.unprotect_snap('snap1')
+ self.image.remove_snap('snap1')
+
+ def test_create_snap(self):
+ global ioctx
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ at_snapshot = Image(ioctx, image_name, 'snap1')
+ snap_data = at_snapshot.read(0, 256)
+ at_snapshot.close()
+ eq(snap_data, b'\0' * 256)
+ self.image.remove_snap('snap1')
+
+ def test_create_snap_exists(self):
+ self.image.create_snap('snap1')
+ assert_raises(ImageExists, self.image.create_snap, 'snap1')
+ self.image.remove_snap('snap1')
+
+ def test_create_snap_flags(self):
+ self.image.create_snap('snap1', 0)
+ self.image.remove_snap('snap1')
+ self.image.create_snap('snap1', RBD_SNAP_CREATE_SKIP_QUIESCE)
+ self.image.remove_snap('snap1')
+ self.image.create_snap('snap1', RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR)
+ self.image.remove_snap('snap1')
+
+ def test_list_snaps(self):
+ eq([], list(self.image.list_snaps()))
+ self.image.create_snap('snap1')
+ eq(['snap1'], [snap['name'] for snap in self.image.list_snaps()])
+ self.image.create_snap('snap2')
+ eq(['snap1', 'snap2'], [snap['name'] for snap in self.image.list_snaps()])
+ self.image.remove_snap('snap1')
+ self.image.remove_snap('snap2')
+
+ def test_list_snaps_iterator_auto_close(self):
+ self.image.create_snap('snap1')
+ self.image.list_snaps()
+ self.image.remove_snap('snap1')
+
+ def test_remove_snap(self):
+ eq([], list(self.image.list_snaps()))
+ self.image.create_snap('snap1')
+ eq(['snap1'], [snap['name'] for snap in self.image.list_snaps()])
+ self.image.remove_snap('snap1')
+ eq([], list(self.image.list_snaps()))
+
+ def test_remove_snap_not_found(self):
+ assert_raises(ImageNotFound, self.image.remove_snap, 'snap1')
+
+ @require_features([RBD_FEATURE_LAYERING])
+ def test_remove_snap2(self):
+ self.image.create_snap('snap1')
+ self.image.protect_snap('snap1')
+ assert(self.image.is_protected_snap('snap1'))
+ self.image.remove_snap2('snap1', RBD_SNAP_REMOVE_UNPROTECT)
+ eq([], list(self.image.list_snaps()))
+
+ def test_remove_snap_by_id(self):
+ eq([], list(self.image.list_snaps()))
+ self.image.create_snap('snap1')
+ eq(['snap1'], [snap['name'] for snap in self.image.list_snaps()])
+ for snap in self.image.list_snaps():
+ snap_id = snap["id"]
+ self.image.remove_snap_by_id(snap_id)
+ eq([], list(self.image.list_snaps()))
+
+ def test_rename_snap(self):
+ eq([], list(self.image.list_snaps()))
+ self.image.create_snap('snap1')
+ eq(['snap1'], [snap['name'] for snap in self.image.list_snaps()])
+ self.image.rename_snap("snap1", "snap1-rename")
+ eq(['snap1-rename'], [snap['name'] for snap in self.image.list_snaps()])
+ self.image.remove_snap('snap1-rename')
+ eq([], list(self.image.list_snaps()))
+
+ @require_features([RBD_FEATURE_LAYERING])
+ def test_protect_snap(self):
+ self.image.create_snap('snap1')
+ assert(not self.image.is_protected_snap('snap1'))
+ self.image.protect_snap('snap1')
+ assert(self.image.is_protected_snap('snap1'))
+ assert_raises(ImageBusy, self.image.remove_snap, 'snap1')
+ self.image.unprotect_snap('snap1')
+ assert(not self.image.is_protected_snap('snap1'))
+ self.image.remove_snap('snap1')
+ assert_raises(ImageNotFound, self.image.unprotect_snap, 'snap1')
+ assert_raises(ImageNotFound, self.image.is_protected_snap, 'snap1')
+
+ def test_snap_exists(self):
+ self.image.create_snap('snap1')
+ eq(self.image.snap_exists('snap1'), True)
+ self.image.remove_snap('snap1')
+ eq(self.image.snap_exists('snap1'), False)
+
+ def test_snap_timestamp(self):
+ self.image.create_snap('snap1')
+ eq(['snap1'], [snap['name'] for snap in self.image.list_snaps()])
+ for snap in self.image.list_snaps():
+ snap_id = snap["id"]
+ time = self.image.get_snap_timestamp(snap_id)
+ assert_not_equal(b'', time.year)
+ assert_not_equal(0, time.year)
+ assert_not_equal(time.year, '1970')
+ self.image.remove_snap('snap1')
+
+ def test_limit_snaps(self):
+ self.image.set_snap_limit(2)
+ eq(2, self.image.get_snap_limit())
+ self.image.create_snap('snap1')
+ self.image.create_snap('snap2')
+ assert_raises(DiskQuotaExceeded, self.image.create_snap, 'snap3')
+ self.image.remove_snap_limit()
+ self.image.create_snap('snap3')
+
+ self.image.remove_snap('snap1')
+ self.image.remove_snap('snap2')
+ self.image.remove_snap('snap3')
+
+ @require_features([RBD_FEATURE_EXCLUSIVE_LOCK])
+ def test_remove_with_exclusive_lock(self):
+ assert_raises(ImageBusy, remove_image)
+
+ @blocklist_features([RBD_FEATURE_EXCLUSIVE_LOCK])
+ def test_remove_with_snap(self):
+ self.image.create_snap('snap1')
+ assert_raises(ImageHasSnapshots, remove_image)
+ self.image.remove_snap('snap1')
+
+ @blocklist_features([RBD_FEATURE_EXCLUSIVE_LOCK])
+ def test_remove_with_watcher(self):
+ data = rand_data(256)
+ self.image.write(data, 0)
+ assert_raises(ImageBusy, remove_image)
+ read = self.image.read(0, 256)
+ eq(read, data)
+
+ def test_rollback_to_snap(self):
+ self.image.write(b'\0' * 256, 0)
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.rollback_to_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ self.image.remove_snap('snap1')
+
+ def test_rollback_to_snap_sparse(self):
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.rollback_to_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ self.image.remove_snap('snap1')
+
+ def test_rollback_with_resize(self):
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, data)
+ new_size = IMG_SIZE * 2
+ self.image.resize(new_size)
+ check_stat(self.image.stat(), new_size, IMG_ORDER)
+ self.image.write(data, new_size - 256)
+ self.image.create_snap('snap2')
+ read = self.image.read(new_size - 256, 256)
+ eq(read, data)
+ self.image.rollback_to_snap('snap1')
+ check_stat(self.image.stat(), IMG_SIZE, IMG_ORDER)
+ assert_raises(InvalidArgument, self.image.read, new_size - 256, 256)
+ self.image.rollback_to_snap('snap2')
+ check_stat(self.image.stat(), new_size, IMG_ORDER)
+ read = self.image.read(new_size - 256, 256)
+ eq(read, data)
+ self.image.remove_snap('snap1')
+ self.image.remove_snap('snap2')
+
+ def test_set_snap(self):
+ self.image.write(b'\0' * 256, 0)
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.set_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ assert_raises(ReadOnlyImage, self.image.write, data, 0)
+ self.image.remove_snap('snap1')
+
+ def test_set_no_snap(self):
+ self.image.write(b'\0' * 256, 0)
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.set_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ assert_raises(ReadOnlyImage, self.image.write, data, 0)
+ self.image.set_snap(None)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.remove_snap('snap1')
+
+ def test_set_snap_by_id(self):
+ self.image.write(b'\0' * 256, 0)
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ snaps = list(self.image.list_snaps())
+ self.image.set_snap_by_id(snaps[0]['id'])
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ assert_raises(ReadOnlyImage, self.image.write, data, 0)
+ self.image.set_snap_by_id(None)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.remove_snap('snap1')
+
+ def test_snap_get_name(self):
+ eq([], list(self.image.list_snaps()))
+ self.image.create_snap('snap1')
+ self.image.create_snap('snap2')
+ self.image.create_snap('snap3')
+
+ for snap in self.image.list_snaps():
+ expected_snap_name = self.image.snap_get_name(snap['id'])
+ eq(expected_snap_name, snap['name'])
+ self.image.remove_snap('snap1')
+ self.image.remove_snap('snap2')
+ self.image.remove_snap('snap3')
+ eq([], list(self.image.list_snaps()))
+
+ assert_raises(ImageNotFound, self.image.snap_get_name, 1)
+
+ def test_snap_get_id(self):
+ eq([], list(self.image.list_snaps()))
+ self.image.create_snap('snap1')
+ self.image.create_snap('snap2')
+ self.image.create_snap('snap3')
+
+ for snap in self.image.list_snaps():
+ expected_snap_id = self.image.snap_get_id(snap['name'])
+ eq(expected_snap_id, snap['id'])
+ self.image.remove_snap('snap1')
+ self.image.remove_snap('snap2')
+ self.image.remove_snap('snap3')
+ eq([], list(self.image.list_snaps()))
+
+ assert_raises(ImageNotFound, self.image.snap_get_id, 'snap1')
+
+ def test_set_snap_sparse(self):
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.set_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ assert_raises(ReadOnlyImage, self.image.write, data, 0)
+ self.image.remove_snap('snap1')
+
+ def test_many_snaps(self):
+ num_snaps = 200
+ for i in range(num_snaps):
+ self.image.create_snap(str(i))
+ snaps = sorted(self.image.list_snaps(),
+ key=lambda snap: int(snap['name']))
+ eq(len(snaps), num_snaps)
+ for i, snap in enumerate(snaps):
+ eq(snap['size'], IMG_SIZE)
+ eq(snap['name'], str(i))
+ for i in range(num_snaps):
+ self.image.remove_snap(str(i))
+
+ def test_set_snap_deleted(self):
+ self.image.write(b'\0' * 256, 0)
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.set_snap('snap1')
+ self.image.remove_snap('snap1')
+ assert_raises(ImageNotFound, self.image.read, 0, 256)
+ self.image.set_snap(None)
+ read = self.image.read(0, 256)
+ eq(read, data)
+
+ def test_set_snap_recreated(self):
+ self.image.write(b'\0' * 256, 0)
+ self.image.create_snap('snap1')
+ read = self.image.read(0, 256)
+ eq(read, b'\0' * 256)
+ data = rand_data(256)
+ self.image.write(data, 0)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.set_snap('snap1')
+ self.image.remove_snap('snap1')
+ self.image.create_snap('snap1')
+ assert_raises(ImageNotFound, self.image.read, 0, 256)
+ self.image.set_snap(None)
+ read = self.image.read(0, 256)
+ eq(read, data)
+ self.image.remove_snap('snap1')
+
+ def test_lock_unlock(self):
+ assert_raises(ImageNotFound, self.image.unlock, '')
+ self.image.lock_exclusive('')
+ assert_raises(ImageExists, self.image.lock_exclusive, '')
+ assert_raises(ImageBusy, self.image.lock_exclusive, 'test')
+ assert_raises(ImageExists, self.image.lock_shared, '', '')
+ assert_raises(ImageBusy, self.image.lock_shared, 'foo', '')
+ self.image.unlock('')
+
+ def test_list_lockers(self):
+ eq([], self.image.list_lockers())
+ self.image.lock_exclusive('test')
+ lockers = self.image.list_lockers()
+ eq(1, len(lockers['lockers']))
+ _, cookie, _ = lockers['lockers'][0]
+ eq(cookie, 'test')
+ eq('', lockers['tag'])
+ assert lockers['exclusive']
+ self.image.unlock('test')
+ eq([], self.image.list_lockers())
+
+ num_shared = 10
+ for i in range(num_shared):
+ self.image.lock_shared(str(i), 'tag')
+ lockers = self.image.list_lockers()
+ eq('tag', lockers['tag'])
+ assert not lockers['exclusive']
+ eq(num_shared, len(lockers['lockers']))
+ cookies = sorted(map(lambda x: x[1], lockers['lockers']))
+ for i in range(num_shared):
+ eq(str(i), cookies[i])
+ self.image.unlock(str(i))
+ eq([], self.image.list_lockers())
+
+ def test_diff_iterate(self):
+ check_diff(self.image, 0, IMG_SIZE, None, [])
+ self.image.write(b'a' * 256, 0)
+ check_diff(self.image, 0, IMG_SIZE, None, [(0, 256, True)])
+ self.image.write(b'b' * 256, 256)
+ check_diff(self.image, 0, IMG_SIZE, None, [(0, 512, True)])
+ self.image.discard(128, 256)
+ check_diff(self.image, 0, IMG_SIZE, None, [(0, 512, True)])
+
+ self.image.create_snap('snap1')
+ self.image.discard(0, 1 << IMG_ORDER)
+ self.image.create_snap('snap2')
+ self.image.set_snap('snap2')
+ check_diff(self.image, 0, IMG_SIZE, 'snap1', [(0, 512, False)])
+ self.image.remove_snap('snap1')
+ self.image.remove_snap('snap2')
+
+ def test_aio_read(self):
+ # this is a list so that the local cb() can modify it
+ retval = [None]
+ def cb(_, buf):
+ retval[0] = buf
+
+ # test1: success case
+ comp = self.image.aio_read(0, 20, cb)
+ comp.wait_for_complete_and_cb()
+ eq(retval[0], b'\0' * 20)
+ eq(comp.get_return_value(), 20)
+ eq(sys.getrefcount(comp), 2)
+
+ # test2: error case
+ retval[0] = 1
+ comp = self.image.aio_read(IMG_SIZE, 20, cb)
+ comp.wait_for_complete_and_cb()
+ eq(None, retval[0])
+ assert(comp.get_return_value() < 0)
+ eq(sys.getrefcount(comp), 2)
+
+ def test_aio_write(self):
+ retval = [None]
+ def cb(comp):
+ retval[0] = comp.get_return_value()
+
+ data = rand_data(256)
+ comp = self.image.aio_write(data, 256, cb)
+ comp.wait_for_complete_and_cb()
+ eq(retval[0], 0)
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+ eq(self.image.read(256, 256), data)
+
+ def test_aio_discard(self):
+ retval = [None]
+ def cb(comp):
+ retval[0] = comp.get_return_value()
+
+ data = rand_data(256)
+ self.image.write(data, 0)
+ comp = self.image.aio_discard(0, 256, cb)
+ comp.wait_for_complete_and_cb()
+ eq(retval[0], 0)
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+ eq(self.image.read(256, 256), b'\0' * 256)
+
+ def test_aio_write_zeroes(self):
+ retval = [None]
+ def cb(comp):
+ retval[0] = comp.get_return_value()
+
+ data = rand_data(256)
+ self.image.write(data, 0)
+ comp = self.image.aio_write_zeroes(0, 256, cb)
+ comp.wait_for_complete_and_cb()
+ eq(retval[0], 0)
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+ eq(self.image.read(256, 256), b'\0' * 256)
+
+ def test_aio_flush(self):
+ retval = [None]
+ def cb(comp):
+ retval[0] = comp.get_return_value()
+
+ comp = self.image.aio_flush(cb)
+ comp.wait_for_complete_and_cb()
+ eq(retval[0], 0)
+ eq(sys.getrefcount(comp), 2)
+
+ def test_metadata(self):
+ metadata = list(self.image.metadata_list())
+ eq(len(metadata), 0)
+ assert_raises(KeyError, self.image.metadata_get, "key1")
+ self.image.metadata_set("key1", "value1")
+ self.image.metadata_set("key2", "value2")
+ value = self.image.metadata_get("key1")
+ eq(value, "value1")
+ value = self.image.metadata_get("key2")
+ eq(value, "value2")
+ metadata = list(self.image.metadata_list())
+ eq(len(metadata), 2)
+ self.image.metadata_remove("key1")
+ metadata = list(self.image.metadata_list())
+ eq(len(metadata), 1)
+ eq(metadata[0], ("key2", "value2"))
+ self.image.metadata_remove("key2")
+ assert_raises(KeyError, self.image.metadata_remove, "key2")
+ metadata = list(self.image.metadata_list())
+ eq(len(metadata), 0)
+
+ N = 65
+ for i in range(N):
+ self.image.metadata_set("key" + str(i), "X" * 1025)
+ metadata = list(self.image.metadata_list())
+ eq(len(metadata), N)
+ for i in range(N):
+ self.image.metadata_remove("key" + str(i))
+ metadata = list(self.image.metadata_list())
+ eq(len(metadata), N - i - 1)
+
+ def test_watchers_list(self):
+ watchers = list(self.image.watchers_list())
+ # The image is open (in r/w mode) from setup, so expect there to be one
+ # watcher.
+ eq(len(watchers), 1)
+
+ def test_config_list(self):
+ with Image(ioctx, image_name) as image:
+ for option in image.config_list():
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ image.metadata_set("conf_rbd_cache", "true")
+
+ for option in image.config_list():
+ if option['name'] == "rbd_cache":
+ eq(option['source'], RBD_CONFIG_SOURCE_IMAGE)
+ else:
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ image.metadata_remove("conf_rbd_cache")
+
+ for option in image.config_list():
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ def test_image_config_set_and_get_and_remove(self):
+ with Image(ioctx, image_name) as image:
+ for option in image.config_list():
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ image.config_set("rbd_request_timed_out_seconds", "100")
+ modify_value = image.config_get("rbd_request_timed_out_seconds")
+ eq(modify_value, '100')
+
+ image.config_remove("rbd_request_timed_out_seconds")
+
+ for option in image.config_list():
+ eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+ def test_sparsify(self):
+ assert_raises(InvalidArgument, self.image.sparsify, 16)
+ self.image.sparsify(4096)
+
+ @require_linux()
+ @blocklist_features([RBD_FEATURE_JOURNALING])
+ def test_encryption_luks1(self):
+ data = b'hello world'
+ offset = 16<<20
+ image_size = 32<<20
+
+ with Image(ioctx, image_name) as image:
+ image.resize(image_size)
+ image.write(data, offset)
+ image.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, "password")
+ assert_not_equal(data, image.read(offset, len(data)))
+ with Image(ioctx, image_name) as image:
+ image.encryption_load(RBD_ENCRYPTION_FORMAT_LUKS1, "password")
+ assert_not_equal(data, image.read(offset, len(data)))
+ image.write(data, offset)
+ with Image(ioctx, image_name) as image:
+ image.encryption_load(RBD_ENCRYPTION_FORMAT_LUKS, "password")
+ eq(data, image.read(offset, len(data)))
+
+ @require_linux()
+ @blocklist_features([RBD_FEATURE_JOURNALING])
+ def test_encryption_luks2(self):
+ data = b'hello world'
+ offset = 16<<20
+ image_size = 256<<20
+
+ with Image(ioctx, image_name) as image:
+ image.resize(image_size)
+ image.write(data, offset)
+ image.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, "password")
+ assert_not_equal(data, image.read(offset, len(data)))
+ with Image(ioctx, image_name) as image:
+ image.encryption_load(RBD_ENCRYPTION_FORMAT_LUKS2, "password")
+ assert_not_equal(data, image.read(offset, len(data)))
+ image.write(data, offset)
+ with Image(ioctx, image_name) as image:
+ image.encryption_load(RBD_ENCRYPTION_FORMAT_LUKS, "password")
+ eq(data, image.read(offset, len(data)))
+
+
+class TestImageId(object):
+
+ def setup_method(self, method):
+ self.rbd = RBD()
+ create_image()
+ self.image = Image(ioctx, image_name)
+ self.image2 = Image(ioctx, None, None, False, self.image.id())
+
+ def teardown_method(self, method):
+ self.image.close()
+ self.image2.close()
+ remove_image()
+ self.image = None
+ self.image2 = None
+
+ def test_read(self):
+ data = self.image2.read(0, 20)
+ eq(data, b'\0' * 20)
+
+ def test_write(self):
+ data = rand_data(256)
+ self.image2.write(data, 0)
+
+ def test_resize(self):
+ new_size = IMG_SIZE * 2
+ self.image2.resize(new_size)
+ info = self.image2.stat()
+ check_stat(info, new_size, IMG_ORDER)
+
+def check_diff(image, offset, length, from_snapshot, expected):
+ extents = []
+ def cb(offset, length, exists):
+ extents.append((offset, length, exists))
+ image.diff_iterate(0, IMG_SIZE, None, cb)
+ eq(extents, expected)
+
+class TestClone(object):
+
+ @require_features([RBD_FEATURE_LAYERING])
+ def setup_method(self, method):
+ global ioctx
+ global features
+ self.rbd = RBD()
+ create_image()
+ self.image = Image(ioctx, image_name)
+ data = rand_data(256)
+ self.image.write(data, IMG_SIZE // 2)
+ self.image.create_snap('snap1')
+ global features
+ self.image.protect_snap('snap1')
+ self.clone_name = get_temp_image_name()
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, self.clone_name,
+ features)
+ self.clone = Image(ioctx, self.clone_name)
+
+ def teardown_method(self, method):
+ global ioctx
+ self.clone.close()
+ self.rbd.remove(ioctx, self.clone_name)
+ self.image.unprotect_snap('snap1')
+ self.image.remove_snap('snap1')
+ self.image.close()
+ remove_image()
+
+ def _test_with_params(self, features=None, order=None, stripe_unit=None,
+ stripe_count=None):
+ self.image.create_snap('snap2')
+ self.image.protect_snap('snap2')
+ clone_name2 = get_temp_image_name()
+ if features is None:
+ self.rbd.clone(ioctx, image_name, 'snap2', ioctx, clone_name2)
+ elif order is None:
+ self.rbd.clone(ioctx, image_name, 'snap2', ioctx, clone_name2,
+ features)
+ elif stripe_unit is None:
+ self.rbd.clone(ioctx, image_name, 'snap2', ioctx, clone_name2,
+ features, order)
+ elif stripe_count is None:
+ self.rbd.clone(ioctx, image_name, 'snap2', ioctx, clone_name2,
+ features, order, stripe_unit)
+ else:
+ self.rbd.clone(ioctx, image_name, 'snap2', ioctx, clone_name2,
+ features, order, stripe_unit, stripe_count)
+ self.rbd.remove(ioctx, clone_name2)
+ self.image.unprotect_snap('snap2')
+ self.image.remove_snap('snap2')
+
+ def test_with_params(self):
+ self._test_with_params()
+
+ def test_with_params2(self):
+ global features
+ self._test_with_params(features, self.image.stat()['order'])
+
+ @require_features([RBD_FEATURE_STRIPINGV2])
+ def test_with_params3(self):
+ global features
+ self._test_with_params(features, self.image.stat()['order'],
+ self.image.stripe_unit(),
+ self.image.stripe_count())
+
+ def test_stripe_unit_and_count(self):
+ global features
+ global ioctx
+ image_name = get_temp_image_name()
+ RBD().create(ioctx, image_name, IMG_SIZE, IMG_ORDER, old_format=False,
+ features=int(features), stripe_unit=1048576, stripe_count=8)
+ image = Image(ioctx, image_name)
+ image.create_snap('snap1')
+ image.protect_snap('snap1')
+ clone_name = get_temp_image_name()
+ RBD().clone(ioctx, image_name, 'snap1', ioctx, clone_name)
+ clone = Image(ioctx, clone_name)
+
+ eq(1048576, clone.stripe_unit())
+ eq(8, clone.stripe_count())
+
+ clone.close()
+ RBD().remove(ioctx, clone_name)
+ image.unprotect_snap('snap1')
+ image.remove_snap('snap1')
+ image.close()
+ RBD().remove(ioctx, image_name)
+
+
+ def test_unprotected(self):
+ self.image.create_snap('snap2')
+ global features
+ clone_name2 = get_temp_image_name()
+ rados.conf_set("rbd_default_clone_format", "1")
+ assert_raises(InvalidArgument, self.rbd.clone, ioctx, image_name,
+ 'snap2', ioctx, clone_name2, features)
+ rados.conf_set("rbd_default_clone_format", "auto")
+ self.image.remove_snap('snap2')
+
+ def test_unprotect_with_children(self):
+ global features
+ # can't remove a snapshot that has dependent clones
+ assert_raises(ImageBusy, self.image.remove_snap, 'snap1')
+
+ # validate parent info of clone created by TestClone.setup_method
+ (pool, image, snap) = self.clone.parent_info()
+ eq(pool, pool_name)
+ eq(image, image_name)
+ eq(snap, 'snap1')
+ eq(self.image.id(), self.clone.parent_id())
+
+ # create a new pool...
+ pool_name2 = get_temp_pool_name()
+ rados.create_pool(pool_name2)
+ other_ioctx = rados.open_ioctx(pool_name2)
+ other_ioctx.application_enable('rbd')
+
+ # ...with a clone of the same parent
+ other_clone_name = get_temp_image_name()
+ rados.conf_set("rbd_default_clone_format", "1")
+ self.rbd.clone(ioctx, image_name, 'snap1', other_ioctx,
+ other_clone_name, features)
+ rados.conf_set("rbd_default_clone_format", "auto")
+ self.other_clone = Image(other_ioctx, other_clone_name)
+ # validate its parent info
+ (pool, image, snap) = self.other_clone.parent_info()
+ eq(pool, pool_name)
+ eq(image, image_name)
+ eq(snap, 'snap1')
+ eq(self.image.id(), self.other_clone.parent_id())
+
+ # can't unprotect snap with children
+ assert_raises(ImageBusy, self.image.unprotect_snap, 'snap1')
+
+ # 2 children, check that cannot remove the parent snap
+ assert_raises(ImageBusy, self.image.remove_snap, 'snap1')
+
+ # close and remove other pool's clone
+ self.other_clone.close()
+ self.rbd.remove(other_ioctx, other_clone_name)
+
+ # check that we cannot yet remove the parent snap
+ assert_raises(ImageBusy, self.image.remove_snap, 'snap1')
+
+ other_ioctx.close()
+ rados.delete_pool(pool_name2)
+
+ # unprotect, remove parent snap happen in cleanup, and should succeed
+
+ def test_stat(self):
+ image_info = self.image.stat()
+ clone_info = self.clone.stat()
+ eq(clone_info['size'], image_info['size'])
+ eq(clone_info['size'], self.clone.overlap())
+
+ def test_resize_stat(self):
+ self.clone.resize(IMG_SIZE // 2)
+ image_info = self.image.stat()
+ clone_info = self.clone.stat()
+ eq(clone_info['size'], IMG_SIZE // 2)
+ eq(image_info['size'], IMG_SIZE)
+ eq(self.clone.overlap(), IMG_SIZE // 2)
+
+ self.clone.resize(IMG_SIZE * 2)
+ image_info = self.image.stat()
+ clone_info = self.clone.stat()
+ eq(clone_info['size'], IMG_SIZE * 2)
+ eq(image_info['size'], IMG_SIZE)
+ eq(self.clone.overlap(), IMG_SIZE // 2)
+
+ def test_resize_io(self):
+ parent_data = self.image.read(IMG_SIZE // 2, 256)
+ self.image.resize(0)
+ self.clone.resize(IMG_SIZE // 2 + 128)
+ child_data = self.clone.read(IMG_SIZE // 2, 128)
+ eq(child_data, parent_data[:128])
+ self.clone.resize(IMG_SIZE)
+ child_data = self.clone.read(IMG_SIZE // 2, 256)
+ eq(child_data, parent_data[:128] + (b'\0' * 128))
+ self.clone.resize(IMG_SIZE // 2 + 1)
+ child_data = self.clone.read(IMG_SIZE // 2, 1)
+ eq(child_data, parent_data[0:1])
+ self.clone.resize(0)
+ self.clone.resize(IMG_SIZE)
+ child_data = self.clone.read(IMG_SIZE // 2, 256)
+ eq(child_data, b'\0' * 256)
+
+ def test_read(self):
+ parent_data = self.image.read(IMG_SIZE // 2, 256)
+ child_data = self.clone.read(IMG_SIZE // 2, 256)
+ eq(child_data, parent_data)
+
+ def test_write(self):
+ parent_data = self.image.read(IMG_SIZE // 2, 256)
+ new_data = rand_data(256)
+ self.clone.write(new_data, IMG_SIZE // 2 + 256)
+ child_data = self.clone.read(IMG_SIZE // 2 + 256, 256)
+ eq(child_data, new_data)
+ child_data = self.clone.read(IMG_SIZE // 2, 256)
+ eq(child_data, parent_data)
+ parent_data = self.image.read(IMG_SIZE // 2 + 256, 256)
+ eq(parent_data, b'\0' * 256)
+
+ def check_children(self, expected):
+ actual = self.image.list_children()
+ # dedup for cache pools until
+ # http://tracker.ceph.com/issues/8187 is fixed
+ deduped = set([(pool_name, image[1]) for image in actual])
+ eq(deduped, set(expected))
+
+ def check_children2(self, expected):
+ actual = [{k:v for k,v in x.items() if k in expected[0]} \
+ for x in self.image.list_children2()]
+ eq(actual, expected)
+
+ def check_descendants(self, expected):
+ eq(list(self.image.list_descendants()), expected)
+
+ def get_image_id(self, ioctx, name):
+ with Image(ioctx, name) as image:
+ return image.id()
+
+ def test_list_children(self):
+ global ioctx
+ global features
+ self.image.set_snap('snap1')
+ self.check_children([(pool_name, self.clone_name)])
+ self.check_children2(
+ [{'pool': pool_name, 'pool_namespace': '',
+ 'image': self.clone_name, 'trash': False,
+ 'id': self.get_image_id(ioctx, self.clone_name)}])
+ self.check_descendants(
+ [{'pool': pool_name, 'pool_namespace': '',
+ 'image': self.clone_name, 'trash': False,
+ 'id': self.get_image_id(ioctx, self.clone_name)}])
+ self.clone.close()
+ self.rbd.remove(ioctx, self.clone_name)
+ eq(self.image.list_children(), [])
+ eq(list(self.image.list_children2()), [])
+ eq(list(self.image.list_descendants()), [])
+
+ clone_name = get_temp_image_name() + '_'
+ expected_children = []
+ expected_children2 = []
+ for i in range(10):
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx,
+ clone_name + str(i), features)
+ expected_children.append((pool_name, clone_name + str(i)))
+ expected_children2.append(
+ {'pool': pool_name, 'pool_namespace': '',
+ 'image': clone_name + str(i), 'trash': False,
+ 'id': self.get_image_id(ioctx, clone_name + str(i))})
+ self.check_children(expected_children)
+ self.check_children2(expected_children2)
+ self.check_descendants(expected_children2)
+
+ image6_id = self.get_image_id(ioctx, clone_name + str(5))
+ RBD().trash_move(ioctx, clone_name + str(5), 0)
+ expected_children.remove((pool_name, clone_name + str(5)))
+ for item in expected_children2:
+ for k, v in item.items():
+ if v == image6_id:
+ item["trash"] = True
+ self.check_children(expected_children)
+ self.check_children2(expected_children2)
+ self.check_descendants(expected_children2)
+
+ RBD().trash_restore(ioctx, image6_id, clone_name + str(5))
+ expected_children.append((pool_name, clone_name + str(5)))
+ for item in expected_children2:
+ for k, v in item.items():
+ if v == image6_id:
+ item["trash"] = False
+ self.check_children(expected_children)
+ self.check_children2(expected_children2)
+ self.check_descendants(expected_children2)
+
+ for i in range(10):
+ self.rbd.remove(ioctx, clone_name + str(i))
+ expected_children.remove((pool_name, clone_name + str(i)))
+ expected_children2.pop(0)
+ self.check_children(expected_children)
+ self.check_children2(expected_children2)
+ self.check_descendants(expected_children2)
+
+ eq(self.image.list_children(), [])
+ eq(list(self.image.list_children2()), [])
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, self.clone_name,
+ features)
+ self.check_children([(pool_name, self.clone_name)])
+ self.check_children2(
+ [{'pool': pool_name, 'pool_namespace': '',
+ 'image': self.clone_name, 'trash': False,
+ 'id': self.get_image_id(ioctx, self.clone_name)}])
+ self.check_descendants(
+ [{'pool': pool_name, 'pool_namespace': '',
+ 'image': self.clone_name, 'trash': False,
+ 'id': self.get_image_id(ioctx, self.clone_name)}])
+ self.clone = Image(ioctx, self.clone_name)
+
+ def test_flatten_errors(self):
+ # test that we can't flatten a non-clone
+ assert_raises(InvalidArgument, self.image.flatten)
+
+ # test that we can't flatten a snapshot
+ self.clone.create_snap('snap2')
+ self.clone.set_snap('snap2')
+ assert_raises(ReadOnlyImage, self.clone.flatten)
+ self.clone.remove_snap('snap2')
+
+ def check_flatten_with_order(self, new_order, stripe_unit=None,
+ stripe_count=None):
+ global ioctx
+ global features
+ clone_name2 = get_temp_image_name()
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name2,
+ features, new_order, stripe_unit, stripe_count)
+ #with Image(ioctx, 'clone2') as clone:
+ clone2 = Image(ioctx, clone_name2)
+ clone2.flatten()
+ eq(clone2.overlap(), 0)
+ clone2.close()
+ self.rbd.remove(ioctx, clone_name2)
+
+ # flatten after resizing to non-block size
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name2,
+ features, new_order, stripe_unit, stripe_count)
+ with Image(ioctx, clone_name2) as clone:
+ clone.resize(IMG_SIZE // 2 - 1)
+ clone.flatten()
+ eq(0, clone.overlap())
+ self.rbd.remove(ioctx, clone_name2)
+
+ # flatten after resizing to non-block size
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name2,
+ features, new_order, stripe_unit, stripe_count)
+ with Image(ioctx, clone_name2) as clone:
+ clone.resize(IMG_SIZE // 2 + 1)
+ clone.flatten()
+ eq(clone.overlap(), 0)
+ self.rbd.remove(ioctx, clone_name2)
+
+ def test_flatten_basic(self):
+ self.check_flatten_with_order(IMG_ORDER)
+
+ def test_flatten_smaller_order(self):
+ self.check_flatten_with_order(IMG_ORDER - 2, 1048576, 1)
+
+ def test_flatten_larger_order(self):
+ self.check_flatten_with_order(IMG_ORDER + 2)
+
+ def test_flatten_drops_cache(self):
+ global ioctx
+ global features
+ clone_name2 = get_temp_image_name()
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name2,
+ features, IMG_ORDER)
+ with Image(ioctx, clone_name2) as clone:
+ with Image(ioctx, clone_name2) as clone2:
+ # cache object non-existence
+ data = clone.read(IMG_SIZE // 2, 256)
+ clone2_data = clone2.read(IMG_SIZE // 2, 256)
+ eq(data, clone2_data)
+ clone.flatten()
+ assert_raises(ImageNotFound, clone.parent_info)
+ assert_raises(ImageNotFound, clone2.parent_info)
+ assert_raises(ImageNotFound, clone.parent_id)
+ assert_raises(ImageNotFound, clone2.parent_id)
+ after_flatten = clone.read(IMG_SIZE // 2, 256)
+ eq(data, after_flatten)
+ after_flatten = clone2.read(IMG_SIZE // 2, 256)
+ eq(data, after_flatten)
+ self.rbd.remove(ioctx, clone_name2)
+
+ def test_flatten_multi_level(self):
+ self.clone.create_snap('snap2')
+ self.clone.protect_snap('snap2')
+ clone_name3 = get_temp_image_name()
+ self.rbd.clone(ioctx, self.clone_name, 'snap2', ioctx, clone_name3,
+ features)
+ self.clone.flatten()
+ with Image(ioctx, clone_name3) as clone3:
+ clone3.flatten()
+ self.clone.unprotect_snap('snap2')
+ self.clone.remove_snap('snap2')
+ self.rbd.remove(ioctx, clone_name3)
+
+ def test_flatten_with_progress(self):
+ d = {'received_callback': False}
+ def progress_cb(current, total):
+ d['received_callback'] = True
+ return 0
+
+ global ioctx
+ global features
+ clone_name = get_temp_image_name()
+ self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name,
+ features, 0)
+ with Image(ioctx, clone_name) as clone:
+ clone.flatten(on_progress=progress_cb)
+ self.rbd.remove(ioctx, clone_name)
+ eq(True, d['received_callback'])
+
+ def test_resize_flatten_multi_level(self):
+ self.clone.create_snap('snap2')
+ self.clone.protect_snap('snap2')
+ clone_name3 = get_temp_image_name()
+ self.rbd.clone(ioctx, self.clone_name, 'snap2', ioctx, clone_name3,
+ features)
+ self.clone.resize(1)
+ orig_data = self.image.read(0, 256)
+ with Image(ioctx, clone_name3) as clone3:
+ clone3_data = clone3.read(0, 256)
+ eq(orig_data, clone3_data)
+ self.clone.flatten()
+ with Image(ioctx, clone_name3) as clone3:
+ clone3_data = clone3.read(0, 256)
+ eq(orig_data, clone3_data)
+ self.rbd.remove(ioctx, clone_name3)
+ self.clone.unprotect_snap('snap2')
+ self.clone.remove_snap('snap2')
+
+ def test_trash_snapshot(self):
+ self.image.create_snap('snap2')
+ global features
+ clone_name = get_temp_image_name()
+ rados.conf_set("rbd_default_clone_format", "2")
+ self.rbd.clone(ioctx, image_name, 'snap2', ioctx, clone_name, features)
+ rados.conf_set("rbd_default_clone_format", "auto")
+
+ self.image.remove_snap('snap2')
+
+ snaps = [s for s in self.image.list_snaps() if s['name'] != 'snap1']
+ eq([RBD_SNAP_NAMESPACE_TYPE_TRASH], [s['namespace'] for s in snaps])
+ eq([{'original_name' : 'snap2'}], [s['trash'] for s in snaps])
+
+ self.rbd.remove(ioctx, clone_name)
+ eq([], [s for s in self.image.list_snaps() if s['name'] != 'snap1'])
+
+ @require_linux()
+ @blocklist_features([RBD_FEATURE_JOURNALING])
+ def test_encryption_luks1(self):
+ data = b'hello world'
+ offset = 16<<20
+ image_size = 32<<20
+
+ self.clone.resize(image_size)
+ self.clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, "password")
+ self.clone.encryption_load2(
+ ((RBD_ENCRYPTION_FORMAT_LUKS1, "password"),))
+ self.clone.write(data, offset)
+ eq(self.clone.read(0, 16), self.image.read(0, 16))
+
+ @require_linux()
+ @blocklist_features([RBD_FEATURE_JOURNALING])
+ def test_encryption_luks2(self):
+ data = b'hello world'
+ offset = 16<<20
+ image_size = 64<<20
+
+ self.clone.resize(image_size)
+ self.clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, "password")
+ self.clone.encryption_load2(
+ ((RBD_ENCRYPTION_FORMAT_LUKS2, "password"),))
+ self.clone.write(data, offset)
+ eq(self.clone.read(0, 16), self.image.read(0, 16))
+
+class TestExclusiveLock(object):
+
+ @require_features([RBD_FEATURE_EXCLUSIVE_LOCK])
+ def setup_method(self, method):
+ global rados2
+ rados2 = Rados(conffile='')
+ rados2.connect()
+ global ioctx2
+ ioctx2 = rados2.open_ioctx(pool_name)
+ create_image()
+
+ def teardown_method(self, method):
+ remove_image()
+ global ioctx2
+ ioctx2.close()
+ global rados2
+ rados2.shutdown()
+
+ def test_ownership(self):
+ with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
+ image1.write(b'0'*256, 0)
+ eq(image1.is_exclusive_lock_owner(), True)
+ eq(image2.is_exclusive_lock_owner(), False)
+
+ def test_snapshot_leadership(self):
+ with Image(ioctx, image_name) as image:
+ image.create_snap('snap')
+ eq(image.is_exclusive_lock_owner(), True)
+ try:
+ with Image(ioctx, image_name) as image:
+ image.write(b'0'*256, 0)
+ eq(image.is_exclusive_lock_owner(), True)
+ image.set_snap('snap')
+ eq(image.is_exclusive_lock_owner(), False)
+ with Image(ioctx, image_name, snapshot='snap') as image:
+ eq(image.is_exclusive_lock_owner(), False)
+ finally:
+ with Image(ioctx, image_name) as image:
+ image.remove_snap('snap')
+
+ def test_read_only_leadership(self):
+ with Image(ioctx, image_name, read_only=True) as image:
+ eq(image.is_exclusive_lock_owner(), False)
+
+ def test_follower_flatten(self):
+ with Image(ioctx, image_name) as image:
+ image.create_snap('snap')
+ image.protect_snap('snap')
+ try:
+ RBD().clone(ioctx, image_name, 'snap', ioctx, 'clone', features)
+ with Image(ioctx, 'clone') as image1, Image(ioctx2, 'clone') as image2:
+ data = rand_data(256)
+ image1.write(data, 0)
+ image2.flatten()
+ assert_raises(ImageNotFound, image1.parent_info)
+ assert_raises(ImageNotFound, image1.parent_id)
+ parent = True
+ for x in range(30):
+ try:
+ image2.parent_info()
+ except ImageNotFound:
+ parent = False
+ break
+ eq(False, parent)
+ finally:
+ RBD().remove(ioctx, 'clone')
+ with Image(ioctx, image_name) as image:
+ image.unprotect_snap('snap')
+ image.remove_snap('snap')
+
+ def test_follower_resize(self):
+ with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
+ image1.write(b'0'*256, 0)
+ for new_size in [IMG_SIZE * 2, IMG_SIZE // 2]:
+ image2.resize(new_size);
+ eq(new_size, image1.size())
+ for x in range(30):
+ if new_size == image2.size():
+ break
+ time.sleep(1)
+ eq(new_size, image2.size())
+
+ def test_follower_snap_create(self):
+ with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
+ image2.create_snap('snap1')
+ image1.remove_snap('snap1')
+
+ def test_follower_snap_rollback(self):
+ with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
+ image1.create_snap('snap')
+ try:
+ assert_raises(ReadOnlyImage, image2.rollback_to_snap, 'snap')
+ image1.rollback_to_snap('snap')
+ finally:
+ image1.remove_snap('snap')
+
+ def test_follower_discard(self):
+ global rados
+ with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
+ data = rand_data(256)
+ image1.write(data, 0)
+ image2.discard(0, 256)
+ eq(image1.is_exclusive_lock_owner(), False)
+ eq(image2.is_exclusive_lock_owner(), True)
+ read = image2.read(0, 256)
+ if rados.conf_get('rbd_skip_partial_discard') == 'false':
+ eq(256 * b'\0', read)
+ else:
+ eq(data, read)
+
+ def test_follower_write(self):
+ with Image(ioctx, image_name) as image1, Image(ioctx2, image_name) as image2:
+ data = rand_data(256)
+ image1.write(data, 0)
+ image2.write(data, IMG_SIZE // 2)
+ eq(image1.is_exclusive_lock_owner(), False)
+ eq(image2.is_exclusive_lock_owner(), True)
+ for offset in [0, IMG_SIZE // 2]:
+ read = image2.read(offset, 256)
+ eq(data, read)
+ def test_acquire_release_lock(self):
+ with Image(ioctx, image_name) as image:
+ image.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE)
+ image.lock_release()
+
+ @pytest.mark.skip_if_crimson
+ def test_break_lock(self):
+ blocklist_rados = Rados(conffile='')
+ blocklist_rados.connect()
+ try:
+ blocklist_ioctx = blocklist_rados.open_ioctx(pool_name)
+ try:
+ rados2.conf_set('rbd_blocklist_on_break_lock', 'true')
+ with Image(ioctx2, image_name) as image, \
+ Image(blocklist_ioctx, image_name) as blocklist_image:
+
+ lock_owners = list(image.lock_get_owners())
+ eq(0, len(lock_owners))
+
+ blocklist_image.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE)
+ assert_raises(ReadOnlyImage, image.lock_acquire,
+ RBD_LOCK_MODE_EXCLUSIVE)
+ lock_owners = list(image.lock_get_owners())
+ eq(1, len(lock_owners))
+ eq(RBD_LOCK_MODE_EXCLUSIVE, lock_owners[0]['mode'])
+ image.lock_break(RBD_LOCK_MODE_EXCLUSIVE,
+ lock_owners[0]['owner'])
+
+ assert_raises(ConnectionShutdown,
+ blocklist_image.is_exclusive_lock_owner)
+
+ blocklist_rados.wait_for_latest_osdmap()
+ data = rand_data(256)
+ assert_raises(ConnectionShutdown,
+ blocklist_image.write, data, 0)
+
+ image.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE)
+
+ try:
+ blocklist_image.close()
+ except ConnectionShutdown:
+ pass
+ finally:
+ blocklist_ioctx.close()
+ finally:
+ blocklist_rados.shutdown()
+
+class TestMirroring(object):
+
+ @staticmethod
+ def check_info(info, global_id, state, primary=None):
+ eq(global_id, info['global_id'])
+ eq(state, info['state'])
+ if primary is not None:
+ eq(primary, info['primary'])
+
+ def setup_method(self, method):
+ self.rbd = RBD()
+ self.initial_mirror_mode = self.rbd.mirror_mode_get(ioctx)
+ self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_POOL)
+ create_image()
+ self.image = Image(ioctx, image_name)
+
+ def teardown_method(self, method):
+ self.image.close()
+ remove_image()
+ self.rbd.mirror_mode_set(ioctx, self.initial_mirror_mode)
+
+ def test_uuid(self):
+ mirror_uuid = self.rbd.mirror_uuid_get(ioctx)
+ assert(mirror_uuid)
+
+ def test_site_name(self):
+ site_name = "us-west-1"
+ self.rbd.mirror_site_name_set(rados, site_name)
+ eq(site_name, self.rbd.mirror_site_name_get(rados))
+ self.rbd.mirror_site_name_set(rados, "")
+ eq(rados.get_fsid(), self.rbd.mirror_site_name_get(rados))
+
+ def test_mirror_peer_bootstrap(self):
+ eq([], list(self.rbd.mirror_peer_list(ioctx)))
+
+ self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_DISABLED)
+ assert_raises(InvalidArgument, self.rbd.mirror_peer_bootstrap_create,
+ ioctx);
+
+ self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_POOL)
+ token_b64 = self.rbd.mirror_peer_bootstrap_create(ioctx)
+ token = base64.b64decode(token_b64)
+ token_dict = json.loads(token)
+ eq(sorted(['fsid', 'client_id', 'key', 'mon_host']),
+ sorted(list(token_dict.keys())))
+
+ # requires different cluster
+ assert_raises(InvalidArgument, self.rbd.mirror_peer_bootstrap_import,
+ ioctx, RBD_MIRROR_PEER_DIRECTION_RX, token_b64)
+
+ def test_mirror_peer(self):
+ eq([], list(self.rbd.mirror_peer_list(ioctx)))
+ site_name = "test_site"
+ client_name = "test_client"
+ uuid = self.rbd.mirror_peer_add(ioctx, site_name, client_name,
+ direction=RBD_MIRROR_PEER_DIRECTION_RX_TX)
+ assert(uuid)
+ peer = {
+ 'uuid' : uuid,
+ 'direction': RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ 'site_name' : site_name,
+ 'cluster_name' : site_name,
+ 'mirror_uuid': '',
+ 'client_name' : client_name,
+ }
+ eq([peer], list(self.rbd.mirror_peer_list(ioctx)))
+ cluster_name = "test_cluster1"
+ self.rbd.mirror_peer_set_cluster(ioctx, uuid, cluster_name)
+ client_name = "test_client1"
+ self.rbd.mirror_peer_set_client(ioctx, uuid, client_name)
+ peer = {
+ 'uuid' : uuid,
+ 'direction': RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ 'site_name' : cluster_name,
+ 'cluster_name' : cluster_name,
+ 'mirror_uuid': '',
+ 'client_name' : client_name,
+ }
+ eq([peer], list(self.rbd.mirror_peer_list(ioctx)))
+
+ attribs = {
+ RBD_MIRROR_PEER_ATTRIBUTE_NAME_MON_HOST: 'host1',
+ RBD_MIRROR_PEER_ATTRIBUTE_NAME_KEY: 'abc'
+ }
+ self.rbd.mirror_peer_set_attributes(ioctx, uuid, attribs)
+ eq(attribs, self.rbd.mirror_peer_get_attributes(ioctx, uuid))
+
+ self.rbd.mirror_peer_remove(ioctx, uuid)
+ eq([], list(self.rbd.mirror_peer_list(ioctx)))
+
+ @require_features([RBD_FEATURE_EXCLUSIVE_LOCK,
+ RBD_FEATURE_JOURNALING])
+ def test_mirror_image(self):
+
+ self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_IMAGE)
+ self.image.mirror_image_disable(True)
+ info = self.image.mirror_image_get_info()
+ self.check_info(info, '', RBD_MIRROR_IMAGE_DISABLED, False)
+
+ self.image.mirror_image_enable()
+ info = self.image.mirror_image_get_info()
+ global_id = info['global_id']
+ self.check_info(info, global_id, RBD_MIRROR_IMAGE_ENABLED, True)
+
+ self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_POOL)
+ fail = False
+ try:
+ self.image.mirror_image_disable(True)
+ except InvalidArgument:
+ fail = True
+ eq(True, fail) # Fails because of mirror mode pool
+
+ self.image.mirror_image_demote()
+ info = self.image.mirror_image_get_info()
+ self.check_info(info, global_id, RBD_MIRROR_IMAGE_ENABLED, False)
+
+ entries = dict(self.rbd.mirror_image_info_list(ioctx))
+ info['mode'] = RBD_MIRROR_IMAGE_MODE_JOURNAL;
+ eq(info, entries[self.image.id()])
+
+ self.image.mirror_image_resync()
+
+ self.image.mirror_image_promote(True)
+ info = self.image.mirror_image_get_info()
+ self.check_info(info, global_id, RBD_MIRROR_IMAGE_ENABLED, True)
+
+ entries = dict(self.rbd.mirror_image_info_list(ioctx))
+ info['mode'] = RBD_MIRROR_IMAGE_MODE_JOURNAL;
+ eq(info, entries[self.image.id()])
+
+ fail = False
+ try:
+ self.image.mirror_image_resync()
+ except InvalidArgument:
+ fail = True
+ eq(True, fail) # Fails because it is primary
+
+ status = self.image.mirror_image_get_status()
+ eq(image_name, status['name'])
+ eq(False, status['up'])
+ eq(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status['state'])
+ info = status['info']
+ self.check_info(info, global_id, RBD_MIRROR_IMAGE_ENABLED, True)
+
+ @require_features([RBD_FEATURE_EXCLUSIVE_LOCK,
+ RBD_FEATURE_JOURNALING])
+ def test_mirror_image_status(self):
+ info = self.image.mirror_image_get_info()
+ global_id = info['global_id']
+ state = info['state']
+ primary = info['primary']
+
+ status = self.image.mirror_image_get_status()
+ eq(image_name, status['name'])
+ eq(False, status['up'])
+ eq(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status['state'])
+ eq([], status['remote_statuses'])
+ info = status['info']
+ self.check_info(info, global_id, state, primary)
+
+ images = list(self.rbd.mirror_image_status_list(ioctx))
+ eq(1, len(images))
+ status = images[0]
+ eq(image_name, status['name'])
+ eq(False, status['up'])
+ eq(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status['state'])
+ info = status['info']
+ self.check_info(info, global_id, state)
+
+ states = self.rbd.mirror_image_status_summary(ioctx)
+ eq([(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, 1)], states)
+
+ assert_raises(ImageNotFound, self.image.mirror_image_get_instance_id)
+ instance_ids = list(self.rbd.mirror_image_instance_id_list(ioctx))
+ eq(0, len(instance_ids))
+
+ N = 65
+ for i in range(N):
+ self.rbd.create(ioctx, image_name + str(i), IMG_SIZE, IMG_ORDER,
+ old_format=False, features=int(features))
+ images = list(self.rbd.mirror_image_status_list(ioctx))
+ eq(N + 1, len(images))
+ for i in range(N):
+ self.rbd.remove(ioctx, image_name + str(i))
+
+ def test_mirror_image_create_snapshot(self):
+ assert_raises(InvalidArgument, self.image.mirror_image_create_snapshot)
+
+ peer1_uuid = self.rbd.mirror_peer_add(ioctx, "cluster1", "client")
+ peer2_uuid = self.rbd.mirror_peer_add(ioctx, "cluster2", "client")
+ self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_IMAGE)
+ self.image.mirror_image_disable(False)
+ self.image.mirror_image_enable(RBD_MIRROR_IMAGE_MODE_SNAPSHOT)
+ mode = self.image.mirror_image_get_mode()
+ eq(RBD_MIRROR_IMAGE_MODE_SNAPSHOT, mode)
+
+ snaps = list(self.image.list_snaps())
+ eq(1, len(snaps))
+ snap = snaps[0]
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ eq(RBD_SNAP_MIRROR_STATE_PRIMARY, snap['mirror']['state'])
+
+ info = self.image.mirror_image_get_info()
+ eq(True, info['primary'])
+ entries = dict(
+ self.rbd.mirror_image_info_list(ioctx,
+ RBD_MIRROR_IMAGE_MODE_SNAPSHOT))
+ info['mode'] = RBD_MIRROR_IMAGE_MODE_SNAPSHOT;
+ eq(info, entries[self.image.id()])
+
+ snap_id = self.image.mirror_image_create_snapshot(
+ RBD_SNAP_CREATE_SKIP_QUIESCE)
+
+ snaps = list(self.image.list_snaps())
+ eq(2, len(snaps))
+ snap = snaps[0]
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ eq(RBD_SNAP_MIRROR_STATE_PRIMARY, snap['mirror']['state'])
+ snap = snaps[1]
+ eq(snap['id'], snap_id)
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ eq(RBD_SNAP_MIRROR_STATE_PRIMARY, snap['mirror']['state'])
+ eq(sorted([peer1_uuid, peer2_uuid]),
+ sorted(snap['mirror']['mirror_peer_uuids']))
+
+ eq(RBD_SNAP_NAMESPACE_TYPE_MIRROR,
+ self.image.snap_get_namespace_type(snap_id))
+ mirror_snap = self.image.snap_get_mirror_namespace(snap_id)
+ eq(mirror_snap, snap['mirror'])
+
+ self.image.mirror_image_demote()
+
+ assert_raises(InvalidArgument, self.image.mirror_image_create_snapshot)
+
+ snaps = list(self.image.list_snaps())
+ eq(3, len(snaps))
+ snap = snaps[0]
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ snap = snaps[1]
+ eq(snap['id'], snap_id)
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ snap = snaps[2]
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ eq(RBD_SNAP_MIRROR_STATE_PRIMARY_DEMOTED, snap['mirror']['state'])
+ eq(sorted([peer1_uuid, peer2_uuid]),
+ sorted(snap['mirror']['mirror_peer_uuids']))
+
+ self.rbd.mirror_peer_remove(ioctx, peer1_uuid)
+ self.rbd.mirror_peer_remove(ioctx, peer2_uuid)
+ self.image.mirror_image_promote(False)
+
+ def test_aio_mirror_image_create_snapshot(self):
+ peer_uuid = self.rbd.mirror_peer_add(ioctx, "cluster", "client")
+ self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_IMAGE)
+ self.image.mirror_image_disable(False)
+ self.image.mirror_image_enable(RBD_MIRROR_IMAGE_MODE_SNAPSHOT)
+
+ snaps = list(self.image.list_snaps())
+ eq(1, len(snaps))
+ snap = snaps[0]
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ eq(RBD_SNAP_MIRROR_STATE_PRIMARY, snap['mirror']['state'])
+
+ # this is a list so that the local cb() can modify it
+ info = [None]
+ def cb(_, _info):
+ info[0] = _info
+
+ comp = self.image.aio_mirror_image_get_info(cb)
+ comp.wait_for_complete_and_cb()
+ assert_not_equal(info[0], None)
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+ info = info[0]
+ global_id = info['global_id']
+ self.check_info(info, global_id, RBD_MIRROR_IMAGE_ENABLED, True)
+
+ mode = [None]
+ def cb(_, _mode):
+ mode[0] = _mode
+
+ comp = self.image.aio_mirror_image_get_mode(cb)
+ comp.wait_for_complete_and_cb()
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+ eq(mode[0], RBD_MIRROR_IMAGE_MODE_SNAPSHOT)
+
+ snap_id = [None]
+ def cb(_, _snap_id):
+ snap_id[0] = _snap_id
+
+ comp = self.image.aio_mirror_image_create_snapshot(0, cb)
+ comp.wait_for_complete_and_cb()
+ assert_not_equal(snap_id[0], None)
+ eq(comp.get_return_value(), 0)
+ eq(sys.getrefcount(comp), 2)
+
+ snaps = list(self.image.list_snaps())
+ eq(2, len(snaps))
+ snap = snaps[1]
+ eq(snap['id'], snap_id[0])
+ eq(snap['namespace'], RBD_SNAP_NAMESPACE_TYPE_MIRROR)
+ eq(RBD_SNAP_MIRROR_STATE_PRIMARY, snap['mirror']['state'])
+ eq([peer_uuid], snap['mirror']['mirror_peer_uuids'])
+
+ self.rbd.mirror_peer_remove(ioctx, peer_uuid)
+
+class TestTrash(object):
+
+ def setup_method(self, method):
+ global rados2
+ rados2 = Rados(conffile='')
+ rados2.connect()
+ global ioctx2
+ ioctx2 = rados2.open_ioctx(pool_name)
+
+ def teardown_method(self, method):
+ global ioctx2
+ ioctx2.close()
+ global rados2
+ rados2.shutdown()
+
+ def test_move(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 1000)
+ RBD().trash_remove(ioctx, image_id, True)
+
+ def test_purge(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_name1 = image_name
+ image_id1 = image.id()
+
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_name2 = image_name
+ image_id2 = image.id()
+
+ RBD().trash_move(ioctx, image_name1, 0)
+ RBD().trash_move(ioctx, image_name2, 1000)
+ RBD().trash_purge(ioctx, datetime.now())
+
+ entries = list(RBD().trash_list(ioctx))
+ eq([image_id2], [x['id'] for x in entries])
+ RBD().trash_remove(ioctx, image_id2, True)
+
+ def test_remove_denied(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 1000)
+ assert_raises(PermissionError, RBD().trash_remove, ioctx, image_id)
+ RBD().trash_remove(ioctx, image_id, True)
+
+ def test_remove(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 0)
+ RBD().trash_remove(ioctx, image_id)
+
+ def test_remove_with_progress(self):
+ d = {'received_callback': False}
+ def progress_cb(current, total):
+ d['received_callback'] = True
+ return 0
+
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 0)
+ RBD().trash_remove(ioctx, image_id, on_progress=progress_cb)
+ eq(True, d['received_callback'])
+
+ def test_get(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 1000)
+
+ info = RBD().trash_get(ioctx, image_id)
+ eq(image_id, info['id'])
+ eq(image_name, info['name'])
+ eq('USER', info['source'])
+ assert(info['deferment_end_time'] > info['deletion_time'])
+
+ RBD().trash_remove(ioctx, image_id, True)
+
+ def test_list(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id1 = image.id()
+ image_name1 = image_name
+ RBD().trash_move(ioctx, image_name, 1000)
+
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id2 = image.id()
+ image_name2 = image_name
+ RBD().trash_move(ioctx, image_name, 1000)
+
+ entries = list(RBD().trash_list(ioctx))
+ for e in entries:
+ if e['id'] == image_id1:
+ eq(e['name'], image_name1)
+ elif e['id'] == image_id2:
+ eq(e['name'], image_name2)
+ else:
+ assert False
+ eq(e['source'], 'USER')
+ assert e['deferment_end_time'] > e['deletion_time']
+
+ RBD().trash_remove(ioctx, image_id1, True)
+ RBD().trash_remove(ioctx, image_id2, True)
+
+ def test_restore(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+ RBD().trash_move(ioctx, image_name, 1000)
+ RBD().trash_restore(ioctx, image_id, image_name)
+ remove_image()
+
+def test_create_group():
+ create_group()
+ remove_group()
+
+def test_rename_group():
+ create_group()
+ if group_name is not None:
+ rename_group()
+ eq(["new" + group_name], RBD().group_list(ioctx))
+ RBD().group_remove(ioctx, "new" + group_name)
+ else:
+ remove_group()
+
+def test_list_groups_empty():
+ eq([], RBD().group_list(ioctx))
+
+def test_list_groups(tmp_group):
+ eq([group_name], RBD().group_list(ioctx))
+
+def test_list_groups_after_removed():
+ create_group()
+ remove_group()
+ eq([], RBD().group_list(ioctx))
+
+class TestGroups(object):
+
+ def setup_method(self, method):
+ global snap_name
+ self.rbd = RBD()
+ create_image()
+ self.image_names = [image_name]
+ self.image = Image(ioctx, image_name)
+
+ create_group()
+ snap_name = get_temp_snap_name()
+ self.group = Group(ioctx, group_name)
+
+ def teardown_method(self, method):
+ remove_group()
+ self.image = None
+ for name in self.image_names:
+ RBD().remove(ioctx, name)
+
+ def test_group_image_add(self):
+ self.group.add_image(ioctx, image_name)
+
+ def test_group_image_list_empty(self):
+ eq([], list(self.group.list_images()))
+
+ def test_group_image_list(self):
+ eq([], list(self.group.list_images()))
+ self.group.add_image(ioctx, image_name)
+ eq([image_name], [img['name'] for img in self.group.list_images()])
+
+ def test_group_image_list_move_to_trash(self):
+ eq([], list(self.group.list_images()))
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+ self.group.add_image(ioctx, image_name)
+ eq([image_name], [img['name'] for img in self.group.list_images()])
+ RBD().trash_move(ioctx, image_name, 0)
+ eq([], list(self.group.list_images()))
+ RBD().trash_restore(ioctx, image_id, image_name)
+
+ def test_group_image_many_images(self):
+ eq([], list(self.group.list_images()))
+ self.group.add_image(ioctx, image_name)
+
+ for x in range(0, 20):
+ create_image()
+ self.image_names.append(image_name)
+ self.group.add_image(ioctx, image_name)
+
+ self.image_names.sort()
+ answer = [img['name'] for img in self.group.list_images()]
+ answer.sort()
+ eq(self.image_names, answer)
+
+ def test_group_image_remove(self):
+ eq([], list(self.group.list_images()))
+ self.group.add_image(ioctx, image_name)
+ with Image(ioctx, image_name) as image:
+ eq(RBD_OPERATION_FEATURE_GROUP,
+ image.op_features() & RBD_OPERATION_FEATURE_GROUP)
+ group = image.group()
+ eq(group_name, group['name'])
+
+ eq([image_name], [img['name'] for img in self.group.list_images()])
+ self.group.remove_image(ioctx, image_name)
+ eq([], list(self.group.list_images()))
+ with Image(ioctx, image_name) as image:
+ eq(0, image.op_features() & RBD_OPERATION_FEATURE_GROUP)
+
+ def test_group_snap(self):
+ global snap_name
+ eq([], list(self.group.list_snaps()))
+ self.group.create_snap(snap_name)
+ eq([snap_name], [snap['name'] for snap in self.group.list_snaps()])
+
+ for snap in self.image.list_snaps():
+ eq(rbd.RBD_SNAP_NAMESPACE_TYPE_GROUP, snap['namespace'])
+ info = snap['group']
+ eq(group_name, info['group_name'])
+ eq(snap_name, info['group_snap_name'])
+
+ self.group.remove_snap(snap_name)
+ eq([], list(self.group.list_snaps()))
+
+ def test_group_snap_flags(self):
+ global snap_name
+ eq([], list(self.group.list_snaps()))
+
+ self.group.create_snap(snap_name, 0)
+ eq([snap_name], [snap['name'] for snap in self.group.list_snaps()])
+ self.group.remove_snap(snap_name)
+
+ self.group.create_snap(snap_name, RBD_SNAP_CREATE_SKIP_QUIESCE)
+ eq([snap_name], [snap['name'] for snap in self.group.list_snaps()])
+ self.group.remove_snap(snap_name)
+
+ self.group.create_snap(snap_name, RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR)
+ eq([snap_name], [snap['name'] for snap in self.group.list_snaps()])
+ self.group.remove_snap(snap_name)
+
+ assert_raises(InvalidArgument, self.group.create_snap, snap_name,
+ RBD_SNAP_CREATE_SKIP_QUIESCE |
+ RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR)
+ eq([], list(self.group.list_snaps()))
+
+ def test_group_snap_list_many(self):
+ global snap_name
+ eq([], list(self.group.list_snaps()))
+ snap_names = []
+ for x in range(0, 20):
+ snap_names.append(snap_name)
+ self.group.create_snap(snap_name)
+ snap_name = get_temp_snap_name()
+
+ snap_names.sort()
+ answer = [snap['name'] for snap in self.group.list_snaps()]
+ answer.sort()
+ eq(snap_names, answer)
+
+ def test_group_snap_namespace(self):
+ global snap_name
+ eq([], list(self.group.list_snaps()))
+ self.group.add_image(ioctx, image_name)
+ self.group.create_snap(snap_name)
+ eq(1, len([snap['name'] for snap in self.image.list_snaps()]))
+ self.group.remove_image(ioctx, image_name)
+ self.group.remove_snap(snap_name)
+ eq([], list(self.group.list_snaps()))
+
+ def test_group_snap_rename(self):
+ global snap_name
+ new_snap_name = "new" + snap_name
+
+ eq([], list(self.group.list_snaps()))
+ self.group.create_snap(snap_name)
+ eq([snap_name], [snap['name'] for snap in self.group.list_snaps()])
+ self.group.rename_snap(snap_name, new_snap_name)
+ eq([new_snap_name], [snap['name'] for snap in self.group.list_snaps()])
+ self.group.remove_snap(new_snap_name)
+ eq([], list(self.group.list_snaps()))
+
+ def test_group_snap_rollback(self):
+ eq([], list(self.group.list_images()))
+ self.group.add_image(ioctx, image_name)
+ with Image(ioctx, image_name) as image:
+ image.write(b'\0' * 256, 0)
+ read = image.read(0, 256)
+ eq(read, b'\0' * 256)
+
+ global snap_name
+ eq([], list(self.group.list_snaps()))
+ self.group.create_snap(snap_name)
+ eq([snap_name], [snap['name'] for snap in self.group.list_snaps()])
+
+ with Image(ioctx, image_name) as image:
+ data = rand_data(256)
+ image.write(data, 0)
+ read = image.read(0, 256)
+ eq(read, data)
+
+ self.group.rollback_to_snap(snap_name)
+ with Image(ioctx, image_name) as image:
+ read = image.read(0, 256)
+ eq(read, b'\0' * 256)
+
+ self.group.remove_image(ioctx, image_name)
+ eq([], list(self.group.list_images()))
+ self.group.remove_snap(snap_name)
+ eq([], list(self.group.list_snaps()))
+
+class TestMigration(object):
+
+ def test_migration(self):
+ create_image()
+ RBD().migration_prepare(ioctx, image_name, ioctx, image_name, features=63,
+ order=23, stripe_unit=1<<23, stripe_count=1,
+ data_pool=None)
+
+ status = RBD().migration_status(ioctx, image_name)
+ eq(image_name, status['source_image_name'])
+ eq(image_name, status['dest_image_name'])
+ eq(RBD_IMAGE_MIGRATION_STATE_PREPARED, status['state'])
+
+ with Image(ioctx, image_name) as image:
+ source_spec = image.migration_source_spec()
+ eq("native", source_spec["type"])
+
+ RBD().migration_execute(ioctx, image_name)
+ RBD().migration_commit(ioctx, image_name)
+ remove_image()
+
+ def test_migration_import(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+ image.create_snap('snap')
+
+ source_spec = json.dumps(
+ {'type': 'native',
+ 'pool_id': ioctx.get_pool_id(),
+ 'pool_namespace': '',
+ 'image_name': image_name,
+ 'image_id': image_id,
+ 'snap_name': 'snap'})
+ dst_image_name = get_temp_image_name()
+ RBD().migration_prepare_import(source_spec, ioctx, dst_image_name,
+ features=63, order=23, stripe_unit=1<<23,
+ stripe_count=1, data_pool=None)
+
+ status = RBD().migration_status(ioctx, dst_image_name)
+ eq('', status['source_image_name'])
+ eq(dst_image_name, status['dest_image_name'])
+ eq(RBD_IMAGE_MIGRATION_STATE_PREPARED, status['state'])
+
+ with Image(ioctx, dst_image_name) as image:
+ source_spec = image.migration_source_spec()
+ eq("native", source_spec["type"])
+
+ RBD().migration_execute(ioctx, dst_image_name)
+ RBD().migration_commit(ioctx, dst_image_name)
+
+ with Image(ioctx, image_name) as image:
+ image.remove_snap('snap')
+ with Image(ioctx, dst_image_name) as image:
+ image.remove_snap('snap')
+
+ RBD().remove(ioctx, dst_image_name)
+ RBD().remove(ioctx, image_name)
+
+ def test_migration_with_progress(self):
+ d = {'received_callback': False}
+ def progress_cb(current, total):
+ d['received_callback'] = True
+ return 0
+
+ create_image()
+ RBD().migration_prepare(ioctx, image_name, ioctx, image_name, features=63,
+ order=23, stripe_unit=1<<23, stripe_count=1,
+ data_pool=None)
+ RBD().migration_execute(ioctx, image_name, on_progress=progress_cb)
+ eq(True, d['received_callback'])
+ d['received_callback'] = False
+
+ RBD().migration_commit(ioctx, image_name, on_progress=progress_cb)
+ eq(True, d['received_callback'])
+ remove_image()
+
+ def test_migrate_abort(self):
+ create_image()
+ RBD().migration_prepare(ioctx, image_name, ioctx, image_name, features=63,
+ order=23, stripe_unit=1<<23, stripe_count=1,
+ data_pool=None)
+ RBD().migration_abort(ioctx, image_name)
+ remove_image()
+
+ def test_migrate_abort_with_progress(self):
+ d = {'received_callback': False}
+ def progress_cb(current, total):
+ d['received_callback'] = True
+ return 0
+
+ create_image()
+ RBD().migration_prepare(ioctx, image_name, ioctx, image_name, features=63,
+ order=23, stripe_unit=1<<23, stripe_count=1,
+ data_pool=None)
+ RBD().migration_abort(ioctx, image_name, on_progress=progress_cb)
+ eq(True, d['received_callback'])
+ remove_image()
diff --git a/src/test/pybind/test_rgwfs.py b/src/test/pybind/test_rgwfs.py
new file mode 100644
index 000000000..0e0fcce44
--- /dev/null
+++ b/src/test/pybind/test_rgwfs.py
@@ -0,0 +1,137 @@
+# vim: expandtab smarttab shiftwidth=4 softtabstop=4
+import pytest
+from assertions import assert_raises, assert_equal
+import rgw as librgwfs
+
+rgwfs = None
+root_handler = None
+root_dir_handler = None
+
+
+def setup_module():
+ global rgwfs
+ global root_handler
+ rgwfs = librgwfs.LibRGWFS("testid", "", "")
+ root_handler = rgwfs.mount()
+
+
+def teardown_module():
+ global rgwfs
+ rgwfs.shutdown()
+
+
+@pytest.fixture
+def testdir():
+ global root_dir_handler
+
+ names = []
+
+ try:
+ root_dir_handler = rgwfs.opendir(root_handler, b"bucket", 0)
+ except Exception:
+ root_dir_handler = rgwfs.mkdir(root_handler, b"bucket", 0)
+
+ def cb(name, offset, flags):
+ names.append(name)
+ rgwfs.readdir(root_dir_handler, cb, 0, 0)
+ for name in names:
+ rgwfs.unlink(root_dir_handler, name, 0)
+
+
+def test_version():
+ rgwfs.version()
+
+
+def test_fstat(testdir):
+ stat = rgwfs.fstat(root_dir_handler)
+ assert(len(stat) == 13)
+ file_handler = rgwfs.create(root_dir_handler, b'file-1', 0)
+ stat = rgwfs.fstat(file_handler)
+ assert(len(stat) == 13)
+ rgwfs.close(file_handler)
+
+
+def test_statfs(testdir):
+ stat = rgwfs.statfs()
+ assert(len(stat) == 11)
+
+
+def test_fsync(testdir):
+ fd = rgwfs.create(root_dir_handler, b'file-1', 0)
+ rgwfs.write(fd, 0, b"asdf")
+ rgwfs.fsync(fd, 0)
+ rgwfs.write(fd, 4, b"qwer")
+ rgwfs.fsync(fd, 1)
+ rgwfs.close(fd)
+
+
+def test_directory(testdir):
+ dir_handler = rgwfs.mkdir(root_dir_handler, b"temp-directory", 0)
+ rgwfs.close(dir_handler)
+ rgwfs.unlink(root_dir_handler, b"temp-directory")
+
+
+def test_walk_dir(testdir):
+ dirs = [b"dir-1", b"dir-2", b"dir-3"]
+ handles = []
+ for i in dirs:
+ d = rgwfs.mkdir(root_dir_handler, i, 0)
+ handles.append(d)
+ entries = []
+
+ def cb(name, offset):
+ entries.append((name, offset))
+
+ offset, eof = rgwfs.readdir(root_dir_handler, cb, 0)
+
+ for i in handles:
+ rgwfs.close(i)
+
+ for name, _ in entries:
+ assert(name in dirs)
+ rgwfs.unlink(root_dir_handler, name)
+
+
+def test_rename(testdir):
+ file_handler = rgwfs.create(root_dir_handler, b"a", 0)
+ rgwfs.close(file_handler)
+ rgwfs.rename(root_dir_handler, b"a", root_dir_handler, b"b")
+ file_handler = rgwfs.open(root_dir_handler, b"b", 0)
+ rgwfs.fstat(file_handler)
+ rgwfs.close(file_handler)
+ rgwfs.unlink(root_dir_handler, b"b")
+
+
+def test_open(testdir):
+ assert_raises(librgwfs.ObjectNotFound, rgwfs.open,
+ root_dir_handler, b'file-1', 0)
+ assert_raises(librgwfs.ObjectNotFound, rgwfs.open,
+ root_dir_handler, b'file-1', 0)
+ fd = rgwfs.create(root_dir_handler, b'file-1', 0)
+ rgwfs.write(fd, 0, b"asdf")
+ rgwfs.close(fd)
+ fd = rgwfs.open(root_dir_handler, b'file-1', 0)
+ assert_equal(rgwfs.read(fd, 0, 4), b"asdf")
+ rgwfs.close(fd)
+ fd = rgwfs.open(root_dir_handler, b'file-1', 0)
+ rgwfs.write(fd, 0, b"aaaazxcv")
+ rgwfs.close(fd)
+ fd = rgwfs.open(root_dir_handler, b'file-1', 0)
+ assert_equal(rgwfs.read(fd, 4, 4), b"zxcv")
+ rgwfs.close(fd)
+ fd = rgwfs.open(root_dir_handler, b'file-1', 0)
+ assert_equal(rgwfs.read(fd, 0, 4), b"aaaa")
+ rgwfs.close(fd)
+ rgwfs.unlink(root_dir_handler, b"file-1")
+
+
+def test_mount_unmount(testdir):
+ global root_handler
+ global root_dir_handler
+ test_directory()
+ rgwfs.close(root_dir_handler)
+ rgwfs.close(root_handler)
+ rgwfs.unmount()
+ root_handler = rgwfs.mount()
+ root_dir_handler = rgwfs.opendir(root_handler, b"bucket", 0)
+ test_open()