summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2013-05-05 19:39:29 +0100
committerLars Wirzenius <liw@liw.fi>2013-05-05 19:39:29 +0100
commit4d52a1ab3acc09da27245afb7707587117d42f4d (patch)
treea0818d482faba49dc1b11506a8a6184eec95bb65
parent8b64b3d5b21e1d1bf62affd0689bfd06a8d037e7 (diff)
downloadobnam-4d52a1ab3acc09da27245afb7707587117d42f4d.tar.gz
Start RepositoryInterface class
-rw-r--r--obnamlib/__init__.py2
-rw-r--r--obnamlib/repo_dummy.py28
-rw-r--r--obnamlib/repo_dummy_tests.py26
-rw-r--r--obnamlib/repo_interface.py194
-rw-r--r--without-tests1
5 files changed, 251 insertions, 0 deletions
diff --git a/obnamlib/__init__.py b/obnamlib/__init__.py
index cb091a34..1b652cb3 100644
--- a/obnamlib/__init__.py
+++ b/obnamlib/__init__.py
@@ -85,6 +85,8 @@ from vfs import VirtualFileSystem, VfsFactory, VfsTests
from vfs_local import LocalFS
from metadata import (read_metadata, set_metadata, Metadata, metadata_fields,
metadata_verify_fields, encode_metadata, decode_metadata)
+from repo_interface import RepositoryInterface, RepositoryInterfaceTests
+from repo_dummy import RepositoryFormatDummy
from repo_tree import RepositoryTree
from chunklist import ChunkList
from clientlist import ClientList
diff --git a/obnamlib/repo_dummy.py b/obnamlib/repo_dummy.py
new file mode 100644
index 00000000..bd68198c
--- /dev/null
+++ b/obnamlib/repo_dummy.py
@@ -0,0 +1,28 @@
+# Copyright 2013 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# =*= License: GPL-3+ =*=
+
+
+import obnamlib
+
+
+class RepositoryFormatDummy(obnamlib.RepositoryInterface):
+
+ format = 'dummy'
+
+ def set_fs(self, fs):
+ pass
+
diff --git a/obnamlib/repo_dummy_tests.py b/obnamlib/repo_dummy_tests.py
new file mode 100644
index 00000000..eca24ed9
--- /dev/null
+++ b/obnamlib/repo_dummy_tests.py
@@ -0,0 +1,26 @@
+# Copyright 2013 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# =*= License: GPL-3+ =*=
+
+
+import obnamlib
+
+
+class RepositoryFormatDummyTests(obnamlib.RepositoryInterfaceTests):
+
+ def setUp(self):
+ self.repo = obnamlib.RepositoryFormatDummy()
+
diff --git a/obnamlib/repo_interface.py b/obnamlib/repo_interface.py
new file mode 100644
index 00000000..a08b7803
--- /dev/null
+++ b/obnamlib/repo_interface.py
@@ -0,0 +1,194 @@
+# repo_interface.py -- interface class for repository access
+#
+# Copyright 2013 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# =*= License: GPL-3+ =*=
+
+
+import unittest
+
+
+class RepositoryInterface(object):
+
+ '''Abstract interface to Obnam backup repositories.
+
+ An Obnam backup repository stores backups for backup clients.
+ As development of Obnam progresses, the details of how things
+ are stored can change. This is usually necessary for performance
+ improvements.
+
+ To allow Obnam to access, both for reading and writing, any
+ version of the repository format, this class defines an interface
+ for repository access. Every different version of the format
+ implements a class with this interface, so that the rest of
+ Obnam can just use the interface.
+
+ The interface is suitably high level that using the repository
+ is convenient, and that it allows a variety of implementations.
+ At the same time it concentrates on the needs of repository
+ access only.
+
+ The interface also specifies the interface with which the
+ implementation accesses the actual filesystem: it is the
+ Obnam VFS layer.
+
+ [rest of Obnam code]
+ |
+ | calls RepositoryInterface API
+ |
+ V
+ [RepositoryFormatX implementing RepositoryInterface API]
+ |
+ | calls VFS API
+ |
+ V
+ [FooFS implementing VirtualFileSystem API]
+
+ The VFS API implementation is given to the RepositoryInterface
+ implementation with the ``set_fs`` method.
+
+ It must be stressed that ALL access to the repository go via
+ an implemention of RepositoryInterface. Further, all the
+ implementation classes must be instantiated via RepositoryFactory.
+
+ The abstraction RepositoryInterface provides for repositories
+ consists of a few key concepts:
+
+ * A repository contains data about one or more clients.
+ * For each client, there is some metadata, plus a list of generations.
+ * For each generation, there is some metadata, plus a list of
+ files (where directories are treated as files).
+ * For each file, there is some metadata, plus a list of chunk
+ identifiers.
+ * File contents data is split into chunks, each given a unique
+ identifier.
+ * There is optionally some indexing for content based lookups of
+ chunks (e.g., look up chunks based on an MD5 checksum).
+ * There are three levels of locking: the list of clients,
+ the per-client data (information about generations), and
+ the chunk lookup indexes are all locked up individually.
+ * All metadata is stored as key/value pairs, where the key is
+ one of a strictly limited list of strings, and the value is
+ a binary string.
+
+ Further, the repository format version implementation is given
+ a directory in which it stores the repository, using any number
+ of files it wishes. No other files will be in that directory.
+ (RepositoryFactory creates the actual directory.) The only
+ restriction is that within that directory, the
+ ``metadata/format``file MUST be a plain text file (no encryption,
+ compression), containing a single line, giving the format
+ of the repository, as an arbitrary string. Each RepositoryInterface
+ implementation will work with exactly one such format, and have
+ a class attribute ``format`` which contains the string.
+
+ Each RepositoryInterface implementation can have a custom
+ initialiser. RepositoryFactory will know how to call it,
+ giving it all the information it needs.
+
+ '''
+
+ # Operations on the repository itself.
+
+ def set_fs(self, fs):
+ '''Set the Obnam VFS instance for accessing the filesystem.'''
+ raise NotImplementedError()
+
+ #def init_repo(self):
+ #def destroy_repo(self):
+ #def get_repo_keys(self):
+ #def get_repo_key_value(self, key):
+ #def set_repo_key_value(self, key, value):
+ #def remove_repo_key(self, key):
+ #def lock_repo(self):
+ #def unlock_repo(self):
+
+ ## Client list.
+ #def get_client_names(self):
+ #def lock_client_list(self):
+ #def commit_client_list(self):
+ #def unlock_client_list(self):
+ #def force_client_list_lock(self):
+ #def add_client(self, client_name):
+ #def remove_client(self, client_name):
+ #def rename_client(self, old_client_name, new_client_name):
+
+ ## A particular client.
+ #def get_client_keys(self, client_name):
+ #def get_client_key_value(self, client_name, key):
+ #def set_client_key_value(self, client_name, key, value):
+ #def remove_client_key(self, clientname, key):
+ #def lock_client(self, client_name):
+ #def commit_client(self, client_name): # commits started generation too
+ #def unlock_client(self, client_name):
+ #def force_client_lock(self, client_name):
+ #def get_client_generation_ids(self, client_name):
+ #def create_generation(self, client_name): # return generation_id
+
+ ## Generations. The generation id identifies client as well.
+ #def get_generation_keys(self, generation_id):
+ #def get_generation_key_value(self, generation_id, key):
+ #def set_generation_key_value(self, generation_id, key, value):
+ #def remove_generation_key(self, generation_id, key):
+ #def remove_generation(self, generation_id):
+ #def get_chunk_ids_in_generation(self, generation_id):
+ #def interpret_generation_spec(self, genspec): # returns generation id
+ #def walk_generation(self, generation_id, filename): # generator
+
+ ## Individual files and directories in a generation.
+ #def file_exists(self, generation_id, filename):
+ #def add_file(self, generation_id, filename):
+ #def remove_file(self, generation_id, filename):
+ #def get_file_keys(self, generation_id, filename):
+ #def get_file_key_value(self, generation_id, filename, key):
+ #def set_file_key_value(self, generation_id, filename, key, value):
+ #def remove_file_key(self, generation_id, filename, key):
+ #def get_file_chunk_ids(self, generation_id, filename):
+ #def clear_file_chunk_ids(self, generation_id, filename):
+ #def append_file_chunk_id(self, generation_id, filename, chunk_id):
+ #def get_file_children(self, generation_id, filename):
+
+ ## Chunks.
+ #def put_chunk_content(self, data): # return new chunk_id
+ #def get_chunk_content(self, chunk_id):
+ #def lock_chunk_indexes(self):
+ #def unlock_chunk_indexes(self):
+ #def force_chunk_index_lock(self):
+ #def put_chunk_into_indexes(self, chunk_id, data):
+ #def find_chunk_id_by_content(self, data):
+ #def remove_chunk(self, chunk_id):
+
+ ## Fsck.
+ #def get_fsck_work_items(self):
+
+
+class RepositoryInterfaceTests(unittest.TestCase): # pragma: no cover
+
+ '''Tests for implementations of RepositoryInterface.
+
+ Each implementation of RepositoryInterface should have a corresponding
+ test class, which inherits this class. The test subclass must set
+ ``self.repo`` to an instance of the class to be tested.
+
+ '''
+
+ def test_has_format_attribute(self):
+ self.assertEqual(type(self.repo.format), str)
+
+ def test_has_set_fs_method(self):
+ # We merely test that set_fs can be called.
+ self.assertEqual(self.repo.set_fs(None), None)
+
diff --git a/without-tests b/without-tests
index 48fbd09d..56e50bbf 100644
--- a/without-tests
+++ b/without-tests
@@ -28,3 +28,4 @@
./.pc/debian-changes-0.22-2/obnamlib/plugins/fsck_plugin.py
./.pc/debian-changes-0.22-2/obnamlib/plugins/restore_plugin.py
obnamlib/plugins/convert5to6_plugin.py
+obnamlib/repo_interface.py