nova/nova/virt/libvirt/volume/scality.py

146 lines
5.1 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import io
import os
from oslo_config import cfg
from oslo_log import log as logging
from six.moves import urllib
import six.moves.urllib.parse as urlparse
from nova import exception
from nova.i18n import _
from nova import utils
from nova.virt.libvirt.volume import fs
LOG = logging.getLogger(__name__)
volume_opts = [
cfg.StrOpt('scality_sofs_config',
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
CONF.register_opts(volume_opts, 'libvirt')
class LibvirtScalityVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver):
"""Scality SOFS Nova driver. Provide hypervisors with access
to sparse files on SOFS.
"""
def _get_mount_point_base(self):
return CONF.libvirt.scality_sofs_mount_point
def _get_device_path(self, connection_info):
"""Returns the hashed path to the device.
:param connection_info: dict of the form
::
connection_info = {
'data': {
'sofs_path': the file system share
...
}
...
}
:returns: The full path to the device.
"""
# TODO(mriedem): change the scality volume driver in cinder to set
# the export and name keys rather than the sofs_path so this is
# standardized.
path = os.path.join(CONF.libvirt.scality_sofs_mount_point,
connection_info['data']['sofs_path'])
return path
def get_config(self, connection_info, disk_info):
"""Returns xml for libvirt."""
conf = super(LibvirtScalityVolumeDriver,
self).get_config(connection_info, disk_info)
conf.source_type = 'file'
conf.source_path = connection_info['data']['device_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 connect_volume(self, connection_info, disk_info):
"""Connect the volume."""
self._check_prerequisites()
self._mount_sofs()
connection_info['data']['device_path'] = \
self._get_device_path(connection_info)
def _check_prerequisites(self):
"""Sanity checks before attempting to mount SOFS."""
# config is mandatory
config = CONF.libvirt.scality_sofs_config
if not config:
msg = _("Value required for 'scality_sofs_config'")
LOG.warning(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:
urllib.request.urlopen(config, timeout=5).close()
except urllib.error.URLError as e:
msg = _("Cannot access 'scality_sofs_config': %s") % e
LOG.warning(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.warning(msg)
raise exception.NovaException(msg)
def _sofs_is_mounted(self):
"""Detects whether Scality SOFS is already mounted."""
mount_path = CONF.libvirt.scality_sofs_mount_point.rstrip('/')
with io.open('/proc/mounts') as mounts:
for mount in mounts.readlines():
parts = mount.split()
if (parts[0].endswith('fuse') and
parts[1].rstrip('/') == mount_path):
return True
return False
def _mount_sofs(self):
config = CONF.libvirt.scality_sofs_config
mount_path = CONF.libvirt.scality_sofs_mount_point
if not os.path.isdir(mount_path):
utils.execute('mkdir', '-p', mount_path)
if not self._sofs_is_mounted():
utils.execute('mount', '-t', 'sofs', config, mount_path,
run_as_root=True)
if not self._sofs_is_mounted():
msg = _("Cannot mount Scality SOFS, check syslog for errors")
LOG.warning(msg)
raise exception.NovaException(msg)