diff --git a/core/storage/Makefile b/core/storage/Makefile index ec59cc64..7c7af60e 100644 --- a/core/storage/Makefile +++ b/core/storage/Makefile @@ -1,5 +1,6 @@ OS ?= Linux +SYSCONFDIR ?= /etc PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes all: @@ -13,6 +14,8 @@ endif mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)/storage cp __init__.py $(DESTDIR)$(PYTHON_QUBESPATH)/storage cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage + mkdir -p $(DESTDIR)$(SYSCONFDIR)/qubes + cp storage.conf $(DESTDIR)$(SYSCONFDIR)/qubes/ ifneq ($(BACKEND_VMM),) if [ -r $(BACKEND_VMM).py ]; then \ cp $(BACKEND_VMM).py $(DESTDIR)$(PYTHON_QUBESPATH)/storage && \ diff --git a/core/storage/__init__.py b/core/storage/__init__.py index 795aa37a..b9810881 100644 --- a/core/storage/__init__.py +++ b/core/storage/__init__.py @@ -22,6 +22,7 @@ from __future__ import absolute_import +import ConfigParser import os import os.path import shutil @@ -31,6 +32,8 @@ import sys import qubes.qubesutils from qubes.qubes import QubesException, defaults, system_path, vm_files +CONFIG_FILE = '/etc/qubes/storage.conf' + class QubesVmStorage(object): """ @@ -200,5 +203,97 @@ class QubesVmStorage(object): self.create_on_disk_private_img(verbose=False) +def dump(o): + """ Returns a string represention of the given object + + Args: + o (object): anything that response to `__module__` and `__class__` + + Given the class :class:`qubes.storage.QubesVmStorage` it returns + 'qubes.storage.QubesVmStorage' as string + """ + return o.__module__ + '.' + o.__class__.__name__ + + +def load(string): + """ Given a dotted full module string representation of a class it loads it + + Args: + string (str) i.e. 'qubes.storage.xen.QubesXenVmStorage' + + Returns: + type + + See also: + :func:`qubes.storage.dump` + """ + if not type(string) is str: + # This is a hack which allows giving a real class to a vm instead of a + # string as string_class parameter. + return string + + components = string.split(".") + module_path = ".".join(components[:-1]) + klass = components[-1:][0] + module = __import__(module_path, fromlist=[klass]) + return getattr(module, klass) + + +def get_pool(vm): + """ Instantiates the storage for the specified vm """ + config = _get_storage_config_parser() + + name = vm.storage_pool + klass = _get_pool_klass(name, config) + + keys = [k for k in config.options(name) if k != 'type' and k != 'class'] + values = [config.get(name, o) for o in keys] + kwargs = dict(zip(keys, values)) + return klass(vm, **kwargs) + + +def _get_storage_config_parser(): + """ Instantiates a `ConfigParaser` for specified storage config file. + + Returns: + RawConfigParser + """ + config = ConfigParser.RawConfigParser() + config.read(CONFIG_FILE) + return config + + +def _get_pool_klass(name, config=None): + """ Returns the storage klass for the specified pool. + + Args: + name: The pool name. + config: If ``config`` is not specified + `_get_storage_config_parser()` is called. + + Returns: + type: A class inheriting from `QubesVmStorage` + """ + if config is None: + config = _get_storage_config_parser() + + if not config.has_section(name): + raise StoragePoolException('Uknown storage pool ' + name) + + if config.has_option(name, 'class'): + klass = load(config.get(name, 'class')) + elif config.has_option(name, 'type'): + pool_type = config.get(name, 'type') + klass = defaults['pool_types'][pool_type] + + if klass is None: + raise StoragePoolException('Uknown storage pool type ' + name) + return klass + + +class StoragePoolException(QubesException): + pass + class Pool(object): pass + diff --git a/core/storage/storage.conf b/core/storage/storage.conf new file mode 100644 index 00000000..8fa44c7e --- /dev/null +++ b/core/storage/storage.conf @@ -0,0 +1,11 @@ +[default] ; poolname +type=xen ; the default xen storage +; class = qubes.storage.xen.XenStorage ; class always overwrites type +; +; To use our own storage adapter, you need just to specify the module path and +; class name +; [pool-b] +; class = foo.bar.MyStorage +; + + diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 86612c48..84e106ba 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -176,6 +176,7 @@ fi %files %defattr(-,root,root,-) %config(noreplace) %attr(0664,root,qubes) %{_sysconfdir}/qubes/qmemman.conf +%config(noreplace) %attr(0664,root,qubes) %{_sysconfdir}/qubes/storage.conf /usr/bin/qvm-* /usr/bin/qubes-* %dir %{python_sitearch}/qubes diff --git a/tests/Makefile b/tests/Makefile index 8523adde..f2dff357 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -27,3 +27,5 @@ endif cp regressions.py[co] $(DESTDIR)$(PYTHON_TESTSPATH) cp run.py $(DESTDIR)$(PYTHON_TESTSPATH) cp run.py[co] $(DESTDIR)$(PYTHON_TESTSPATH) + cp storage.py $(DESTDIR)$(PYTHON_TESTSPATH) + cp storage.py[co] $(DESTDIR)$(PYTHON_TESTSPATH) diff --git a/tests/__init__.py b/tests/__init__.py index 8abea4b8..8af1afbf 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -547,6 +547,7 @@ def load_tests(loader, tests, pattern): 'qubes.tests.backup', 'qubes.tests.backupcompatibility', 'qubes.tests.regressions', + 'qubes.tests.storage', ): tests.addTests(loader.loadTestsFromName(modname)) diff --git a/tests/storage.py b/tests/storage.py new file mode 100644 index 00000000..30b9cd2d --- /dev/null +++ b/tests/storage.py @@ -0,0 +1,63 @@ +# The Qubes OS Project, https://www.qubes-os.org/ +# +# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov +# +# 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 2 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +from qubes.tests import QubesTestCase, SystemTestsMixin +from qubes.qubes import defaults + +import qubes.storage +from qubes.storage.xen import QubesXenVmStorage, XenPool + + +class TC_00_Storage(SystemTestsMixin, QubesTestCase): + + def test_000_dump(self): + """ Dumps storage instance to a storage string """ + vmname = self.make_vm_name('appvm') + template = self.qc.get_default_template() + storage = self.qc.add_new_vm('QubesAppVm', name=vmname, pool='default', + template=template).storage + result = qubes.storage.dump(storage) + expected = 'qubes.storage.xen.QubesXenVmStorage' + self.assertEquals(result, expected) + + def test_001_load(self): + """ Loads storage type from a storage string """ + result = qubes.storage.load('qubes.storage.xen.QubesXenVmStorage') + self.assertTrue(result is QubesXenVmStorage) + + def test_002_default_pool_types(self): + """ The only predifined pool type is xen """ + result = defaults['pool_types'].keys() + expected = ["xen"] + self.assertEquals(result, expected) + + def test_003_get_pool_klass(self): + """ Expect the default pool to be `XenPool` """ + result = qubes.storage._get_pool_klass('default') + self.assertTrue(result is XenPool) + + +class TC_01_Storage(SystemTestsMixin, QubesTestCase): + + def test_000_vm_use_default_pool(self): + vmname = self.make_vm_name('appvm') + template = self.qc.get_default_template() + vm = self.qc.add_new_vm('QubesAppVm', name=vmname, template=template, + pool='default') + self.assertIsInstance(vm.storage, QubesXenVmStorage)