summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Marc Saffroy <jean.marc.saffroy@scality.com>2013-01-15 00:27:42 +0100
committerVishvananda Ishaya <vishvananda@gmail.com>2013-02-20 11:29:44 -0800
commit2ccbfd8f528f6fff2ad95f2581812cbcd04ad493 (patch)
tree02c0eb38a22d4443c402a042858b780c08b355a7
parentf478fa696746ba61f3749a9b0559bbbaaf25cfa5 (diff)
downloadnova-2ccbfd8f528f6fff2ad95f2581812cbcd04ad493.tar.gz
nova-2ccbfd8f528f6fff2ad95f2581812cbcd04ad493.tar.xz
nova-2ccbfd8f528f6fff2ad95f2581812cbcd04ad493.zip
Add a volume driver in Nova for Scality SOFS
Scality SOFS is a network filesystem mounted with FUSE, with most options given in a configuration file. Given a mount point and a SOFS configuration file as driver options, the Scality volume driver mounts SOFS, and then creates, accesses and deletes volumes as regular (sparse) files on SOFS. Change-Id: I84bf268e5a2c5c33b8706830e8067914fae44aed Implements: blueprint scality-volume-driver
-rw-r--r--etc/nova/rootwrap.d/compute.filters1
-rw-r--r--nova/tests/test_libvirt_volume.py41
-rwxr-xr-xnova/virt/libvirt/driver.py4
-rw-r--r--nova/virt/libvirt/volume.py79
4 files changed, 124 insertions, 1 deletions
diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters
index 00b73d831..f0d9c6fee 100644
--- a/etc/nova/rootwrap.d/compute.filters
+++ b/etc/nova/rootwrap.d/compute.filters
@@ -14,6 +14,7 @@ tune2fs: CommandFilter, /sbin/tune2fs, root
# nova/virt/disk/api.py: 'mount', '-o', 'bind', src, target
# nova/virt/xenapi/vm_utils.py: 'mount', '-t', 'ext2,ext3,ext4,reiserfs'..
# nova/virt/configdrive.py: 'mount', device, mountdir
+# nova/virt/libvirt/volume.py: 'mount', '-t', 'sofs' ...
mount: CommandFilter, /bin/mount, root
# nova/virt/disk/mount/api.py: 'umount', mapped_device
diff --git a/nova/tests/test_libvirt_volume.py b/nova/tests/test_libvirt_volume.py
index 7945329f8..28d0c8088 100644
--- a/nova/tests/test_libvirt_volume.py
+++ b/nova/tests/test_libvirt_volume.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
import os
from oslo.config import cfg
@@ -547,3 +548,43 @@ class LibvirtVolumeTestCase(test.TestCase):
"/0000:05:00.3/0000:06:00.6/host2/fc_host/host2"}
pci_num = libvirt_driver._get_pci_num(hba)
self.assertEqual("0000:06:00.6", pci_num)
+
+ def test_libvirt_scality_driver(self):
+ tempdir = self.useFixture(fixtures.TempDir()).path
+ TEST_MOUNT = os.path.join(tempdir, 'fake_mount')
+ TEST_CONFIG = os.path.join(tempdir, 'fake_config')
+ TEST_VOLDIR = 'volumes'
+ TEST_VOLNAME = 'volume_name'
+ TEST_CONN_INFO = {
+ 'data': {
+ 'sofs_path': os.path.join(TEST_VOLDIR, TEST_VOLNAME)
+ }
+ }
+ TEST_VOLPATH = os.path.join(TEST_MOUNT,
+ TEST_VOLDIR,
+ TEST_VOLNAME)
+ TEST_DISK_INFO = {
+ "bus": "virtio",
+ "dev": "vde",
+ "type": "disk",
+ }
+
+ open(TEST_CONFIG, "w+").close()
+ os.makedirs(os.path.join(TEST_MOUNT, 'sys'))
+
+ def _access_wrapper(path, flags):
+ if path == '/sbin/mount.sofs':
+ return True
+ else:
+ return os.access(path, flags)
+
+ self.stubs.Set(os, 'access', _access_wrapper)
+
+ self.flags(scality_sofs_config=TEST_CONFIG,
+ scality_sofs_mount_point=TEST_MOUNT)
+ driver = volume.LibvirtScalityVolumeDriver(self.fake_conn)
+ conf = driver.connect_volume(TEST_CONN_INFO, TEST_DISK_INFO)
+
+ tree = conf.format_dom()
+ self.assertEqual(tree.get('type'), 'file')
+ self.assertEqual(tree.find('./source').get('file'), TEST_VOLPATH)
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 6f1cfef6f..57e364115 100755
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -166,7 +166,9 @@ libvirt_opts = [
'glusterfs='
'nova.virt.libvirt.volume.LibvirtGlusterfsVolumeDriver',
'fibre_channel=nova.virt.libvirt.volume.'
- 'LibvirtFibreChannelVolumeDriver'
+ 'LibvirtFibreChannelVolumeDriver',
+ 'scality='
+ 'nova.virt.libvirt.volume.LibvirtScalityVolumeDriver',
],
help='Libvirt handlers for remote volumes.'),
cfg.StrOpt('libvirt_disk_prefix',
diff --git a/nova/virt/libvirt/volume.py b/nova/virt/libvirt/volume.py
index a5bd2dd43..577cfee2c 100644
--- a/nova/virt/libvirt/volume.py
+++ b/nova/virt/libvirt/volume.py
@@ -21,6 +21,8 @@
import hashlib
import os
import time
+import urllib2
+import urlparse
from oslo.config import cfg
@@ -63,6 +65,12 @@ volume_opts = [
cfg.BoolOpt('libvirt_iscsi_use_multipath',
default=False,
help='use multipath connection of the iSCSI volume'),
+ cfg.StrOpt('scality_sofs_config',
+ default=None,
+ help='Path or URL to Scality SOFS configuration file'),
+ cfg.StrOpt('scality_sofs_mount_point',
+ default='$state_path/scality',
+ help='Base dir where Scality SOFS shall be mounted'),
]
CONF = cfg.CONF
@@ -749,3 +757,74 @@ class LibvirtFibreChannelVolumeDriver(LibvirtBaseVolumeDriver):
# all of them
for device in devices:
linuxscsi.remove_device(device)
+
+
+class LibvirtScalityVolumeDriver(LibvirtBaseVolumeDriver):
+ """Scality SOFS Nova driver. Provide hypervisors with access
+ to sparse files on SOFS. """
+
+ def __init__(self, connection):
+ """Create back-end to SOFS and check connection."""
+ super(LibvirtScalityVolumeDriver,
+ self).__init__(connection, is_block_dev=False)
+
+ def connect_volume(self, connection_info, disk_info):
+ """Connect the volume. Returns xml for libvirt."""
+ self._check_prerequisites()
+ self._mount_sofs()
+ conf = super(LibvirtScalityVolumeDriver,
+ self).connect_volume(connection_info, disk_info)
+ path = os.path.join(CONF.scality_sofs_mount_point,
+ connection_info['data']['sofs_path'])
+ conf.source_type = 'file'
+ conf.source_path = path
+
+ # The default driver cache policy is 'none', and this causes
+ # qemu/kvm to open the volume file with O_DIRECT, which is
+ # rejected by FUSE (on kernels older than 3.3). Scality SOFS
+ # is FUSE based, so we must provide a more sensible default.
+ conf.driver_cache = 'writethrough'
+
+ return conf
+
+ def _check_prerequisites(self):
+ """Sanity checks before attempting to mount SOFS."""
+
+ # config is mandatory
+ config = CONF.scality_sofs_config
+ if not config:
+ msg = _("Value required for 'scality_sofs_config'")
+ LOG.warn(msg)
+ raise exception.NovaException(msg)
+
+ # config can be a file path or a URL, check it
+ if urlparse.urlparse(config).scheme == '':
+ # turn local path into URL
+ config = 'file://%s' % config
+ try:
+ urllib2.urlopen(config, timeout=5).close()
+ except urllib2.URLError as e:
+ msg = _("Cannot access 'scality_sofs_config': %s") % e
+ LOG.warn(msg)
+ raise exception.NovaException(msg)
+
+ # mount.sofs must be installed
+ if not os.access('/sbin/mount.sofs', os.X_OK):
+ msg = _("Cannot execute /sbin/mount.sofs")
+ LOG.warn(msg)
+ raise exception.NovaException(msg)
+
+ def _mount_sofs(self):
+ config = CONF.scality_sofs_config
+ mount_path = CONF.scality_sofs_mount_point
+ sysdir = os.path.join(mount_path, 'sys')
+
+ if not os.path.isdir(mount_path):
+ utils.execute('mkdir', '-p', mount_path)
+ if not os.path.isdir(sysdir):
+ utils.execute('mount', '-t', 'sofs', config, mount_path,
+ run_as_root=True)
+ if not os.path.isdir(sysdir):
+ msg = _("Cannot mount Scality SOFS, check syslog for errors")
+ LOG.warn(msg)
+ raise exception.NovaException(msg)