Merge "libvirt: add nova volume driver for vzstorage"
This commit is contained in:
commit
5f75130c94
@ -253,3 +253,6 @@ xend: CommandFilter, xend, root
|
||||
|
||||
# nova/virt/libvirt/utils.py:
|
||||
touch: CommandFilter, touch, root
|
||||
|
||||
# nova/virt/libvirt/volume/vzstorage.py
|
||||
pstorage-mount: CommandFilter, pstorage-mount, root
|
||||
|
@ -438,6 +438,143 @@ libvirt_remotefs_opts = [
|
||||
'removing files on the remote host.'),
|
||||
]
|
||||
|
||||
libvirt_volume_vzstorage_opts = [
|
||||
cfg.StrOpt('vzstorage_mount_point_base',
|
||||
default=paths.state_path_def('mnt'),
|
||||
help="""
|
||||
Directory where the Virtuozzo Storage clusters are mounted on the compute node.
|
||||
|
||||
This option defines non-standard mountpoint for Vzstorage cluster.
|
||||
|
||||
* Services that use this:
|
||||
|
||||
``nova-compute``
|
||||
|
||||
* Related options:
|
||||
|
||||
vzstorage_mount_* group of parameters
|
||||
"""
|
||||
),
|
||||
cfg.StrOpt('vzstorage_mount_user',
|
||||
default='stack',
|
||||
help="""
|
||||
Mount owner user name.
|
||||
|
||||
This option defines the owner user of Vzstorage cluster mountpoint.
|
||||
|
||||
* Services that use this:
|
||||
|
||||
``nova-compute``
|
||||
|
||||
* Related options:
|
||||
|
||||
vzstorage_mount_* group of parameters
|
||||
"""
|
||||
),
|
||||
cfg.StrOpt('vzstorage_mount_group',
|
||||
default='qemu',
|
||||
help="""
|
||||
Mount owner group name.
|
||||
|
||||
This option defines the owner group of Vzstorage cluster mountpoint.
|
||||
|
||||
* Services that use this:
|
||||
|
||||
``nova-compute``
|
||||
|
||||
* Related options:
|
||||
|
||||
vzstorage_mount_* group of parameters
|
||||
"""
|
||||
),
|
||||
cfg.StrOpt('vzstorage_mount_perms',
|
||||
default='0770',
|
||||
help="""
|
||||
Mount access mode.
|
||||
|
||||
This option defines the access bits of Vzstorage cluster mountpoint,
|
||||
in the format similar to one of chmod(1) utility, like this: 0770.
|
||||
It consists of one to four digits ranging from 0 to 7, with missing
|
||||
lead digits assumed to be 0's.
|
||||
|
||||
* Services that use this:
|
||||
|
||||
``nova-compute``
|
||||
|
||||
* Related options:
|
||||
|
||||
vzstorage_mount_* group of parameters
|
||||
"""
|
||||
),
|
||||
cfg.StrOpt('vzstorage_log_path',
|
||||
default='/var/log/pstorage/%(cluster_name)s/nova.log.gz',
|
||||
help="""
|
||||
Path to vzstorage client log.
|
||||
|
||||
This option defines the log of cluster operations,
|
||||
it should include "%(cluster_name)s" template to separate
|
||||
logs from multiple shares.
|
||||
|
||||
* Services that use this:
|
||||
|
||||
``nova-compute``
|
||||
|
||||
* Related options:
|
||||
|
||||
vzstorage_mount_opts may include more detailed logging options.
|
||||
"""
|
||||
),
|
||||
cfg.StrOpt('vzstorage_cache_path',
|
||||
default=None,
|
||||
help="""
|
||||
Path to the SSD cache file.
|
||||
|
||||
You can attach an SSD drive to a client and configure the drive to store
|
||||
a local cache of frequently accessed data. By having a local cache on a
|
||||
client's SSD drive, you can increase the overall cluster performance by
|
||||
up to 10 and more times.
|
||||
WARNING! There is a lot of SSD models which are not server grade and
|
||||
may loose arbitrary set of data changes on power loss.
|
||||
Such SSDs should not be used in Vstorage and are dangerous as may lead
|
||||
to data corruptions and inconsistencies. Please consult with the manual
|
||||
on which SSD models are known to be safe or verify it using
|
||||
vstorage-hwflush-check(1) utility.
|
||||
|
||||
This option defines the path which should include "%(cluster_name)s"
|
||||
template to separate caches from multiple shares.
|
||||
|
||||
* Services that use this:
|
||||
|
||||
``nova-compute``
|
||||
|
||||
* Related options:
|
||||
|
||||
vzstorage_mount_opts may include more detailed cache options.
|
||||
"""
|
||||
),
|
||||
cfg.ListOpt('vzstorage_mount_opts',
|
||||
default=[],
|
||||
help="""
|
||||
Extra mount options for pstorage-mount
|
||||
|
||||
For full description of them, see
|
||||
https://static.openvz.org/vz-man/man1/pstorage-mount.1.gz.html
|
||||
Format is a python string representation of arguments list, like:
|
||||
"[\'-v\', \'-R\', \'500\']"
|
||||
Shouldn\'t include -c, -l, -C, -u, -g and -m as those have
|
||||
explicit vzstorage_* options.
|
||||
|
||||
* Services that use this:
|
||||
|
||||
``nova-compute``
|
||||
|
||||
* Related options:
|
||||
|
||||
All other vzstorage_* options
|
||||
"""
|
||||
),
|
||||
]
|
||||
|
||||
ALL_OPTS = list(itertools.chain(
|
||||
libvirt_general_opts,
|
||||
libvirt_imagebackend_opts,
|
||||
@ -455,7 +592,8 @@ ALL_OPTS = list(itertools.chain(
|
||||
libvirt_volume_quobyte_opts,
|
||||
libvirt_volume_scality_opts,
|
||||
libvirt_volume_smbfs_opts,
|
||||
libvirt_remotefs_opts
|
||||
libvirt_remotefs_opts,
|
||||
libvirt_volume_vzstorage_opts,
|
||||
))
|
||||
|
||||
|
||||
|
109
nova/tests/unit/virt/libvirt/volume/test_vzstorage.py
Normal file
109
nova/tests/unit/virt/libvirt/volume/test_vzstorage.py
Normal file
@ -0,0 +1,109 @@
|
||||
# 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 mock
|
||||
import os
|
||||
|
||||
from nova import exception
|
||||
from nova.tests.unit.virt.libvirt.volume import test_volume
|
||||
from nova import utils
|
||||
from nova.virt.libvirt.volume import vzstorage
|
||||
from os_brick.initiator import connector
|
||||
|
||||
|
||||
class LibvirtVZStorageTestCase(test_volume.LibvirtVolumeBaseTestCase):
|
||||
"""Tests the libvirt vzstorage volume driver."""
|
||||
|
||||
def setUp(self):
|
||||
super(LibvirtVZStorageTestCase, self).setUp()
|
||||
self.mnt_base = '/mnt'
|
||||
self.flags(vzstorage_mount_point_base=self.mnt_base, group='libvirt')
|
||||
self.flags(vzstorage_cache_path="/tmp/ssd-cache/%(cluster_name)s",
|
||||
group='libvirt')
|
||||
|
||||
def test_libvirt_vzstorage_driver(self):
|
||||
libvirt_driver = vzstorage.LibvirtVZStorageVolumeDriver(self.fake_conn)
|
||||
self.assertIsInstance(libvirt_driver.connector,
|
||||
connector.RemoteFsConnector)
|
||||
|
||||
def test_libvirt_vzstorage_driver_opts_negative(self):
|
||||
"""Test that custom options cannot duplicate the configured"""
|
||||
bad_opts = [
|
||||
["-c", "clus111", "-v"],
|
||||
["-l", "/var/log/pstorage.log", "-L", "5x5"],
|
||||
["-u", "user1", "-p", "pass1"],
|
||||
["-v", "-R", "100", "-C", "/ssd"],
|
||||
]
|
||||
for opts in bad_opts:
|
||||
self.flags(vzstorage_mount_opts=opts, group='libvirt')
|
||||
self.assertRaises(exception.NovaException,
|
||||
vzstorage.LibvirtVZStorageVolumeDriver,
|
||||
self.fake_conn)
|
||||
|
||||
def test_libvirt_vzstorage_driver_share_fmt_neg(self):
|
||||
|
||||
drv = vzstorage.LibvirtVZStorageVolumeDriver(self.fake_conn)
|
||||
|
||||
wrong_export_string = 'mds1, mds2:/testcluster:passwd12111'
|
||||
connection_info = {'data': {'export': wrong_export_string,
|
||||
'name': self.name}}
|
||||
|
||||
err_pattern = ("^Valid share format is "
|
||||
"\[mds\[,mds1\[\.\.\.\]\]:/\]clustername\[:password\]$")
|
||||
self.assertRaisesRegex(exception.InvalidVolume,
|
||||
err_pattern,
|
||||
drv.connect_volume,
|
||||
connection_info,
|
||||
self.disk_info)
|
||||
|
||||
def test_libvirt_vzstorage_driver_connect(self):
|
||||
def brick_conn_vol(data):
|
||||
return {'path': 'vstorage://testcluster'}
|
||||
|
||||
drv = vzstorage.LibvirtVZStorageVolumeDriver(self.fake_conn)
|
||||
drv.connector.connect_volume = brick_conn_vol
|
||||
|
||||
export_string = 'testcluster'
|
||||
connection_info = {'data': {'export': export_string,
|
||||
'name': self.name}}
|
||||
|
||||
drv.connect_volume(connection_info, self.disk_info)
|
||||
self.assertEqual('vstorage://testcluster',
|
||||
connection_info['data']['device_path'])
|
||||
self.assertEqual('-u stack -g qemu -m 0770 '
|
||||
'-l /var/log/pstorage/testcluster/nova.log.gz '
|
||||
'-C /tmp/ssd-cache/testcluster',
|
||||
connection_info['data']['options'])
|
||||
|
||||
def test_libvirt_vzstorage_driver_disconnect(self):
|
||||
drv = vzstorage.LibvirtVZStorageVolumeDriver(self.fake_conn)
|
||||
drv.connector.disconnect_volume = mock.MagicMock()
|
||||
conn = {'data': self.disk_info}
|
||||
drv.disconnect_volume(conn, self.disk_info)
|
||||
drv.connector.disconnect_volume.assert_called_once_with(
|
||||
self.disk_info, None)
|
||||
|
||||
def test_libvirt_vzstorage_driver_get_config(self):
|
||||
libvirt_driver = vzstorage.LibvirtVZStorageVolumeDriver(self.fake_conn)
|
||||
export_string = 'vzstorage'
|
||||
export_mnt_base = os.path.join(self.mnt_base,
|
||||
utils.get_hash_str(export_string))
|
||||
file_path = os.path.join(export_mnt_base, self.name)
|
||||
|
||||
connection_info = {'data': {'export': export_string,
|
||||
'name': self.name,
|
||||
'device_path': file_path}}
|
||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
||||
self.assertEqual('file', conf.source_type)
|
||||
self.assertEqual(file_path, conf.source_path)
|
||||
self.assertEqual('raw', conf.driver_format)
|
||||
self.assertEqual('writethrough', conf.driver_cache)
|
@ -165,6 +165,8 @@ libvirt_volume_drivers = [
|
||||
'hgst=nova.virt.libvirt.volume.hgst.LibvirtHGSTVolumeDriver',
|
||||
'scaleio=nova.virt.libvirt.volume.scaleio.LibvirtScaleIOVolumeDriver',
|
||||
'disco=nova.virt.libvirt.volume.disco.LibvirtDISCOVolumeDriver',
|
||||
'vzstorage='
|
||||
'nova.virt.libvirt.volume.vzstorage.LibvirtVZStorageVolumeDriver',
|
||||
]
|
||||
|
||||
|
||||
|
127
nova/virt/libvirt/volume/vzstorage.py
Normal file
127
nova/virt/libvirt/volume/vzstorage.py
Normal file
@ -0,0 +1,127 @@
|
||||
# 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 collections
|
||||
import re
|
||||
|
||||
from os_brick.initiator import connector
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova import utils
|
||||
from nova.virt.libvirt.volume import fs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
VzShare = collections.namedtuple('VzShare',
|
||||
['cluster_name', 'mds_list', 'password'])
|
||||
|
||||
|
||||
class LibvirtVZStorageVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver):
|
||||
"""Class implements libvirt part of volume driver for VzStorage."""
|
||||
|
||||
SHARE_FORMAT_REGEX = r'(?:(\S+):/)?([a-zA-Z0-9_-]+)(?::(\S+))?$'
|
||||
|
||||
def __init__(self, connection):
|
||||
super(LibvirtVZStorageVolumeDriver, self).__init__(connection)
|
||||
|
||||
# Check for duplicate options:
|
||||
# -c - cluster name
|
||||
# -l - log file, includes %(cluster_name)s, so it's handled as a
|
||||
# separate config parameter
|
||||
# -C - SSD cache file, the same thing with %(cluster_name)s
|
||||
# -u, -g, -m - there are default values for these options, so
|
||||
# they're separate config parameters
|
||||
cfg_opts_set = set(CONF.libvirt.vzstorage_mount_opts)
|
||||
invalid_opts_set = set(('-c', '-l', '-C', '-u', '-g', '-m',))
|
||||
invalid_cfg_opts = cfg_opts_set.intersection(invalid_opts_set)
|
||||
|
||||
if invalid_cfg_opts:
|
||||
msg = (_("You can't use %s options in vzstorage_mount_opts "
|
||||
"configuration parameter.") %
|
||||
', '.join(invalid_cfg_opts))
|
||||
raise exception.NovaException(msg)
|
||||
|
||||
# Call the factory here so we can support
|
||||
# more than x86 architectures.
|
||||
self.connector = connector.InitiatorConnector.factory(
|
||||
'vzstorage', utils.get_root_helper(),
|
||||
vzstorage_mount_point_base=CONF.libvirt.vzstorage_mount_point_base)
|
||||
|
||||
def _get_mount_point_base(self):
|
||||
return CONF.libvirt.vzstorage_mount_point_base
|
||||
|
||||
def get_config(self, connection_info, disk_info):
|
||||
"""Returns xml for libvirt."""
|
||||
conf = super(LibvirtVZStorageVolumeDriver,
|
||||
self).get_config(connection_info, disk_info)
|
||||
|
||||
conf.source_type = 'file'
|
||||
conf.driver_cache = 'writethrough'
|
||||
conf.source_path = connection_info['data']['device_path']
|
||||
conf.driver_format = connection_info['data'].get('format', 'raw')
|
||||
return conf
|
||||
|
||||
def _parse_vz_share(self, vz_share):
|
||||
m = re.match(self.SHARE_FORMAT_REGEX, vz_share)
|
||||
if not m:
|
||||
msg = _("Valid share format is "
|
||||
"[mds[,mds1[...]]:/]clustername[:password]")
|
||||
raise exception.InvalidVolume(msg)
|
||||
|
||||
if m.group(1):
|
||||
mds_list = m.group(1).split(',')
|
||||
else:
|
||||
mds_list = None
|
||||
|
||||
return VzShare(cluster_name=m.group(2),
|
||||
mds_list=mds_list,
|
||||
password=m.group(3))
|
||||
|
||||
def _get_mount_opts(self, vz_share):
|
||||
cluster_name = self._parse_vz_share(vz_share).cluster_name
|
||||
|
||||
# pstorage-mount man page:
|
||||
# https://static.openvz.org/vz-man/man1/pstorage-mount.1.gz.html
|
||||
mount_opts = ['-u', CONF.libvirt.vzstorage_mount_user,
|
||||
'-g', CONF.libvirt.vzstorage_mount_group,
|
||||
'-m', CONF.libvirt.vzstorage_mount_perms,
|
||||
'-l', (CONF.libvirt.vzstorage_log_path %
|
||||
{'cluster_name': cluster_name})]
|
||||
|
||||
if CONF.libvirt.vzstorage_cache_path:
|
||||
mount_opts.extend(['-C', (CONF.libvirt.vzstorage_cache_path %
|
||||
{'cluster_name': cluster_name})])
|
||||
mount_opts.extend(CONF.libvirt.vzstorage_mount_opts)
|
||||
|
||||
return ' '.join(mount_opts)
|
||||
|
||||
def connect_volume(self, connection_info, disk_info):
|
||||
"""Attach the volume to instance_name."""
|
||||
|
||||
LOG.debug("Calling os-brick to mount vzstorage")
|
||||
vz_share = connection_info['data']['export']
|
||||
connection_info['data']['options'] = self._get_mount_opts(vz_share)
|
||||
device_info = self.connector.connect_volume(connection_info['data'])
|
||||
LOG.debug("Attached vzstorage volume %s", device_info)
|
||||
|
||||
connection_info['data']['device_path'] = device_info['path']
|
||||
|
||||
def disconnect_volume(self, connection_info, disk_dev):
|
||||
"""Detach the volume from instance_name."""
|
||||
|
||||
LOG.debug("calling os-brick to detach Vzstorage Volume")
|
||||
self.connector.disconnect_volume(connection_info['data'], None)
|
||||
LOG.debug("Disconnected Vzstorage Volume %s", disk_dev)
|
Loading…
Reference in New Issue
Block a user