Merge "Move libvirt RBD utilities to a new file"
This commit is contained in:
@@ -18,7 +18,6 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
import mock
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
@@ -30,6 +29,7 @@ from nova import test
|
|||||||
from nova.tests import fake_processutils
|
from nova.tests import fake_processutils
|
||||||
from nova.tests.virt.libvirt import fake_libvirt_utils
|
from nova.tests.virt.libvirt import fake_libvirt_utils
|
||||||
from nova.virt.libvirt import imagebackend
|
from nova.virt.libvirt import imagebackend
|
||||||
|
from nova.virt.libvirt import rbd
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@@ -671,14 +671,8 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
|
|||||||
group='libvirt')
|
group='libvirt')
|
||||||
self.libvirt_utils = imagebackend.libvirt_utils
|
self.libvirt_utils = imagebackend.libvirt_utils
|
||||||
self.utils = imagebackend.utils
|
self.utils = imagebackend.utils
|
||||||
self.rbd = self.mox.CreateMockAnything()
|
self.mox.StubOutWithMock(rbd, 'rbd')
|
||||||
self.rados = self.mox.CreateMockAnything()
|
self.mox.StubOutWithMock(rbd, 'rados')
|
||||||
|
|
||||||
def prepare_mocks(self):
|
|
||||||
fn = self.mox.CreateMockAnything()
|
|
||||||
self.mox.StubOutWithMock(imagebackend, 'rbd')
|
|
||||||
self.mox.StubOutWithMock(imagebackend, 'rados')
|
|
||||||
return fn
|
|
||||||
|
|
||||||
def test_cache(self):
|
def test_cache(self):
|
||||||
image = self.image_class(self.INSTANCE, self.NAME)
|
image = self.image_class(self.INSTANCE, self.NAME)
|
||||||
@@ -746,10 +740,10 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
|
|||||||
self.mox.VerifyAll()
|
self.mox.VerifyAll()
|
||||||
|
|
||||||
def test_create_image(self):
|
def test_create_image(self):
|
||||||
fn = self.prepare_mocks()
|
fn = self.mox.CreateMockAnything()
|
||||||
fn(max_size=None, rbd=self.rbd, target=self.TEMPLATE_PATH)
|
fn(max_size=None, target=self.TEMPLATE_PATH)
|
||||||
|
|
||||||
self.rbd.RBD_FEATURE_LAYERING = 1
|
rbd.rbd.RBD_FEATURE_LAYERING = 1
|
||||||
|
|
||||||
self.mox.StubOutWithMock(imagebackend.disk, 'get_disk_size')
|
self.mox.StubOutWithMock(imagebackend.disk, 'get_disk_size')
|
||||||
imagebackend.disk.get_disk_size(self.TEMPLATE_PATH
|
imagebackend.disk.get_disk_size(self.TEMPLATE_PATH
|
||||||
@@ -762,7 +756,7 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
image = self.image_class(self.INSTANCE, self.NAME)
|
image = self.image_class(self.INSTANCE, self.NAME)
|
||||||
image.create_image(fn, self.TEMPLATE_PATH, None, rbd=self.rbd)
|
image.create_image(fn, self.TEMPLATE_PATH, None)
|
||||||
|
|
||||||
self.mox.VerifyAll()
|
self.mox.VerifyAll()
|
||||||
|
|
||||||
@@ -771,8 +765,6 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
|
|||||||
|
|
||||||
fake_processutils.fake_execute_clear_log()
|
fake_processutils.fake_execute_clear_log()
|
||||||
fake_processutils.stub_out_processutils_execute(self.stubs)
|
fake_processutils.stub_out_processutils_execute(self.stubs)
|
||||||
self.mox.StubOutWithMock(imagebackend, 'rbd')
|
|
||||||
self.mox.StubOutWithMock(imagebackend, 'rados')
|
|
||||||
image = self.image_class(self.INSTANCE, self.NAME)
|
image = self.image_class(self.INSTANCE, self.NAME)
|
||||||
|
|
||||||
def fake_fetch(target, *args, **kwargs):
|
def fake_fetch(target, *args, **kwargs):
|
||||||
@@ -807,16 +799,6 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
|
|||||||
|
|
||||||
self.assertEqual(image.path, rbd_path)
|
self.assertEqual(image.path, rbd_path)
|
||||||
|
|
||||||
def test_resize(self):
|
|
||||||
image = self.image_class(self.INSTANCE, self.NAME)
|
|
||||||
with mock.patch.object(imagebackend, "RBDVolumeProxy") as mock_proxy:
|
|
||||||
volume_mock = mock.Mock()
|
|
||||||
mock_proxy.side_effect = [mock_proxy]
|
|
||||||
mock_proxy.__enter__.side_effect = [volume_mock]
|
|
||||||
|
|
||||||
image._resize(image.rbd_name, self.SIZE)
|
|
||||||
volume_mock.resize.assert_called_once_with(self.SIZE)
|
|
||||||
|
|
||||||
|
|
||||||
class BackendTestCase(test.NoDBTestCase):
|
class BackendTestCase(test.NoDBTestCase):
|
||||||
INSTANCE = {'name': 'fake-instance',
|
INSTANCE = {'name': 'fake-instance',
|
||||||
@@ -859,6 +841,8 @@ class BackendTestCase(test.NoDBTestCase):
|
|||||||
pool = "FakePool"
|
pool = "FakePool"
|
||||||
self.flags(images_rbd_pool=pool, group='libvirt')
|
self.flags(images_rbd_pool=pool, group='libvirt')
|
||||||
self.flags(images_rbd_ceph_conf=conf, group='libvirt')
|
self.flags(images_rbd_ceph_conf=conf, group='libvirt')
|
||||||
|
self.mox.StubOutWithMock(rbd, 'rbd')
|
||||||
|
self.mox.StubOutWithMock(rbd, 'rados')
|
||||||
self._test_image('rbd', imagebackend.Rbd, imagebackend.Rbd)
|
self._test_image('rbd', imagebackend.Rbd, imagebackend.Rbd)
|
||||||
|
|
||||||
def test_image_default(self):
|
def test_image_default(self):
|
||||||
|
|||||||
169
nova/tests/virt/libvirt/test_rbd.py
Normal file
169
nova/tests/virt/libvirt/test_rbd.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
from nova.openstack.common import log as logging
|
||||||
|
from nova import test
|
||||||
|
from nova import utils
|
||||||
|
from nova.virt.libvirt import rbd
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
CEPH_MON_DUMP = """dumped monmap epoch 1
|
||||||
|
{ "epoch": 1,
|
||||||
|
"fsid": "33630410-6d93-4d66-8e42-3b953cf194aa",
|
||||||
|
"modified": "2013-05-22 17:44:56.343618",
|
||||||
|
"created": "2013-05-22 17:44:56.343618",
|
||||||
|
"mons": [
|
||||||
|
{ "rank": 0,
|
||||||
|
"name": "a",
|
||||||
|
"addr": "[::1]:6789\/0"},
|
||||||
|
{ "rank": 1,
|
||||||
|
"name": "b",
|
||||||
|
"addr": "[::1]:6790\/0"},
|
||||||
|
{ "rank": 2,
|
||||||
|
"name": "c",
|
||||||
|
"addr": "[::1]:6791\/0"},
|
||||||
|
{ "rank": 3,
|
||||||
|
"name": "d",
|
||||||
|
"addr": "127.0.0.1:6792\/0"},
|
||||||
|
{ "rank": 4,
|
||||||
|
"name": "e",
|
||||||
|
"addr": "example.com:6791\/0"}],
|
||||||
|
"quorum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class RbdTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
|
@mock.patch.object(rbd, 'rbd')
|
||||||
|
@mock.patch.object(rbd, 'rados')
|
||||||
|
def setUp(self, mock_rados, mock_rbd):
|
||||||
|
super(RbdTestCase, self).setUp()
|
||||||
|
|
||||||
|
self.mock_rados = mock_rados
|
||||||
|
self.mock_rados.Rados = mock.Mock
|
||||||
|
self.mock_rados.Rados.ioctx = mock.Mock()
|
||||||
|
self.mock_rados.Rados.connect = mock.Mock()
|
||||||
|
self.mock_rados.Rados.shutdown = mock.Mock()
|
||||||
|
self.mock_rados.Rados.open_ioctx = mock.Mock()
|
||||||
|
self.mock_rados.Rados.open_ioctx.return_value = \
|
||||||
|
self.mock_rados.Rados.ioctx
|
||||||
|
self.mock_rados.Error = Exception
|
||||||
|
|
||||||
|
self.mock_rbd = mock_rbd
|
||||||
|
self.mock_rbd.RBD = mock.Mock
|
||||||
|
self.mock_rbd.Image = mock.Mock
|
||||||
|
self.mock_rbd.Image.close = mock.Mock()
|
||||||
|
self.mock_rbd.RBD.Error = Exception
|
||||||
|
|
||||||
|
self.rbd_pool = 'rbd'
|
||||||
|
self.driver = rbd.RBDDriver(self.rbd_pool, None, None)
|
||||||
|
|
||||||
|
self.volume_name = u'volume-00000001'
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(RbdTestCase, self).tearDown()
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'execute')
|
||||||
|
def test_get_mon_addrs(self, mock_execute):
|
||||||
|
mock_execute.return_value = (CEPH_MON_DUMP, '')
|
||||||
|
hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com']
|
||||||
|
ports = ['6789', '6790', '6791', '6792', '6791']
|
||||||
|
self.assertEqual((hosts, ports), self.driver.get_mon_addrs())
|
||||||
|
|
||||||
|
@mock.patch.object(rbd, 'RBDVolumeProxy')
|
||||||
|
def test_resize(self, mock_proxy):
|
||||||
|
size = 1024
|
||||||
|
proxy = mock_proxy.return_value
|
||||||
|
proxy.__enter__.return_value = proxy
|
||||||
|
self.driver.resize(self.volume_name, size)
|
||||||
|
proxy.resize.assert_called_once_with(size)
|
||||||
|
|
||||||
|
@mock.patch.object(rbd.RBDDriver, '_disconnect_from_rados')
|
||||||
|
@mock.patch.object(rbd.RBDDriver, '_connect_to_rados')
|
||||||
|
@mock.patch.object(rbd, 'rbd')
|
||||||
|
@mock.patch.object(rbd, 'rados')
|
||||||
|
def test_rbd_volume_proxy_init(self, mock_rados, mock_rbd,
|
||||||
|
mock_connect_from_rados,
|
||||||
|
mock_disconnect_from_rados):
|
||||||
|
mock_connect_from_rados.return_value = (None, None)
|
||||||
|
mock_disconnect_from_rados.return_value = (None, None)
|
||||||
|
|
||||||
|
with rbd.RBDVolumeProxy(self.driver, self.volume_name):
|
||||||
|
mock_connect_from_rados.assert_called_once_with(None)
|
||||||
|
self.assertFalse(mock_disconnect_from_rados.called)
|
||||||
|
|
||||||
|
mock_disconnect_from_rados.assert_called_once_with(None, None)
|
||||||
|
|
||||||
|
@mock.patch.object(rbd, 'rbd')
|
||||||
|
@mock.patch.object(rbd, 'rados')
|
||||||
|
def test_connect_to_rados_default(self, mock_rados, mock_rbd):
|
||||||
|
ret = self.driver._connect_to_rados()
|
||||||
|
self.assertTrue(self.mock_rados.Rados.connect.called)
|
||||||
|
self.assertTrue(self.mock_rados.Rados.open_ioctx.called)
|
||||||
|
self.assertIsInstance(ret[0], self.mock_rados.Rados)
|
||||||
|
self.assertEqual(ret[1], self.mock_rados.Rados.ioctx)
|
||||||
|
self.mock_rados.Rados.open_ioctx.assert_called_with(self.rbd_pool)
|
||||||
|
|
||||||
|
@mock.patch.object(rbd, 'rbd')
|
||||||
|
@mock.patch.object(rbd, 'rados')
|
||||||
|
def test_connect_to_rados_different_pool(self, mock_rados, mock_rbd):
|
||||||
|
ret = self.driver._connect_to_rados('alt_pool')
|
||||||
|
self.assertTrue(self.mock_rados.Rados.connect.called)
|
||||||
|
self.assertTrue(self.mock_rados.Rados.open_ioctx.called)
|
||||||
|
self.assertIsInstance(ret[0], self.mock_rados.Rados)
|
||||||
|
self.assertEqual(ret[1], self.mock_rados.Rados.ioctx)
|
||||||
|
self.mock_rados.Rados.open_ioctx.assert_called_with('alt_pool')
|
||||||
|
|
||||||
|
@mock.patch.object(rbd, 'rados')
|
||||||
|
def test_connect_to_rados_error(self, mock_rados):
|
||||||
|
mock_rados.Rados.open_ioctx.side_effect = mock_rados.Error
|
||||||
|
self.assertRaises(mock_rados.Error, self.driver._connect_to_rados)
|
||||||
|
mock_rados.Rados.open_ioctx.assert_called_once_with(self.rbd_pool)
|
||||||
|
mock_rados.Rados.shutdown.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_ceph_args_none(self):
|
||||||
|
self.driver.rbd_user = None
|
||||||
|
self.driver.ceph_conf = None
|
||||||
|
self.assertEqual([], self.driver.ceph_args())
|
||||||
|
|
||||||
|
def test_ceph_args_rbd_user(self):
|
||||||
|
self.driver.rbd_user = 'foo'
|
||||||
|
self.driver.ceph_conf = None
|
||||||
|
self.assertEqual(['--id', 'foo'], self.driver.ceph_args())
|
||||||
|
|
||||||
|
def test_ceph_args_ceph_conf(self):
|
||||||
|
self.driver.rbd_user = None
|
||||||
|
self.driver.ceph_conf = '/path/bar.conf'
|
||||||
|
self.assertEqual(['--conf', '/path/bar.conf'],
|
||||||
|
self.driver.ceph_args())
|
||||||
|
|
||||||
|
def test_ceph_args_rbd_user_and_ceph_conf(self):
|
||||||
|
self.driver.rbd_user = 'foo'
|
||||||
|
self.driver.ceph_conf = '/path/bar.conf'
|
||||||
|
self.assertEqual(['--id', 'foo', '--conf', '/path/bar.conf'],
|
||||||
|
self.driver.ceph_args())
|
||||||
|
|
||||||
|
@mock.patch.object(rbd, 'RBDVolumeProxy')
|
||||||
|
def test_exists(self, mock_proxy):
|
||||||
|
proxy = mock_proxy.return_value
|
||||||
|
self.assertTrue(self.driver.exists(self.volume_name))
|
||||||
|
proxy.__enter__.assert_called_once_with()
|
||||||
|
proxy.__exit__.assert_called_once_with(None, None, None)
|
||||||
@@ -33,17 +33,9 @@ from nova.virt.disk import api as disk
|
|||||||
from nova.virt import images
|
from nova.virt import images
|
||||||
from nova.virt.libvirt import config as vconfig
|
from nova.virt.libvirt import config as vconfig
|
||||||
from nova.virt.libvirt import lvm
|
from nova.virt.libvirt import lvm
|
||||||
|
from nova.virt.libvirt import rbd
|
||||||
from nova.virt.libvirt import utils as libvirt_utils
|
from nova.virt.libvirt import utils as libvirt_utils
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
import rados
|
|
||||||
import rbd
|
|
||||||
except ImportError:
|
|
||||||
rados = None
|
|
||||||
rbd = None
|
|
||||||
|
|
||||||
|
|
||||||
__imagebackend_opts = [
|
__imagebackend_opts = [
|
||||||
cfg.StrOpt('images_type',
|
cfg.StrOpt('images_type',
|
||||||
default='default',
|
default='default',
|
||||||
@@ -76,6 +68,8 @@ CONF = cfg.CONF
|
|||||||
CONF.register_opts(__imagebackend_opts, 'libvirt')
|
CONF.register_opts(__imagebackend_opts, 'libvirt')
|
||||||
CONF.import_opt('image_cache_subdirectory_name', 'nova.virt.imagecache')
|
CONF.import_opt('image_cache_subdirectory_name', 'nova.virt.imagecache')
|
||||||
CONF.import_opt('preallocate_images', 'nova.virt.driver')
|
CONF.import_opt('preallocate_images', 'nova.virt.driver')
|
||||||
|
CONF.import_opt('rbd_user', 'nova.virt.libvirt.volume', group='libvirt')
|
||||||
|
CONF.import_opt('rbd_secret_uuid', 'nova.virt.libvirt.volume', group='libvirt')
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -488,51 +482,6 @@ class Lvm(Image):
|
|||||||
run_as_root=True)
|
run_as_root=True)
|
||||||
|
|
||||||
|
|
||||||
class RBDVolumeProxy(object):
|
|
||||||
"""Context manager for dealing with an existing rbd volume.
|
|
||||||
|
|
||||||
This handles connecting to rados and opening an ioctx automatically, and
|
|
||||||
otherwise acts like a librbd Image object.
|
|
||||||
|
|
||||||
The underlying librados client and ioctx can be accessed as the attributes
|
|
||||||
'client' and 'ioctx'.
|
|
||||||
"""
|
|
||||||
def __init__(self, driver, name, pool=None):
|
|
||||||
client, ioctx = driver._connect_to_rados(pool)
|
|
||||||
try:
|
|
||||||
self.volume = driver.rbd.Image(ioctx, str(name), snapshot=None)
|
|
||||||
except driver.rbd.Error:
|
|
||||||
LOG.exception(_LE("error opening rbd image %s"), name)
|
|
||||||
driver._disconnect_from_rados(client, ioctx)
|
|
||||||
raise
|
|
||||||
self.driver = driver
|
|
||||||
self.client = client
|
|
||||||
self.ioctx = ioctx
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, type_, value, traceback):
|
|
||||||
try:
|
|
||||||
self.volume.close()
|
|
||||||
finally:
|
|
||||||
self.driver._disconnect_from_rados(self.client, self.ioctx)
|
|
||||||
|
|
||||||
def __getattr__(self, attrib):
|
|
||||||
return getattr(self.volume, attrib)
|
|
||||||
|
|
||||||
|
|
||||||
def ascii_str(s):
|
|
||||||
"""Convert a string to ascii, or return None if the input is None.
|
|
||||||
|
|
||||||
This is useful when a parameter is None by default, or a string. LibRBD
|
|
||||||
only accepts ascii, hence the need for conversion.
|
|
||||||
"""
|
|
||||||
if s is None:
|
|
||||||
return s
|
|
||||||
return str(s)
|
|
||||||
|
|
||||||
|
|
||||||
class Rbd(Image):
|
class Rbd(Image):
|
||||||
def __init__(self, instance=None, disk_name=None, path=None, **kwargs):
|
def __init__(self, instance=None, disk_name=None, path=None, **kwargs):
|
||||||
super(Rbd, self).__init__("block", "rbd", is_block_dev=True)
|
super(Rbd, self).__init__("block", "rbd", is_block_dev=True)
|
||||||
@@ -549,10 +498,13 @@ class Rbd(Image):
|
|||||||
' images_rbd_pool'
|
' images_rbd_pool'
|
||||||
' flag to use rbd images.'))
|
' flag to use rbd images.'))
|
||||||
self.pool = CONF.libvirt.images_rbd_pool
|
self.pool = CONF.libvirt.images_rbd_pool
|
||||||
self.ceph_conf = ascii_str(CONF.libvirt.images_rbd_ceph_conf)
|
self.rbd_user = CONF.libvirt.rbd_user
|
||||||
self.rbd_user = ascii_str(CONF.libvirt.rbd_user)
|
self.ceph_conf = CONF.libvirt.images_rbd_ceph_conf
|
||||||
self.rbd = kwargs.get('rbd', rbd)
|
|
||||||
self.rados = kwargs.get('rados', rados)
|
self.driver = rbd.RBDDriver(
|
||||||
|
pool=self.pool,
|
||||||
|
ceph_conf=self.ceph_conf,
|
||||||
|
rbd_user=self.rbd_user)
|
||||||
|
|
||||||
self.path = 'rbd:%s/%s' % (self.pool, self.rbd_name)
|
self.path = 'rbd:%s/%s' % (self.pool, self.rbd_name)
|
||||||
if self.rbd_user:
|
if self.rbd_user:
|
||||||
@@ -560,52 +512,6 @@ class Rbd(Image):
|
|||||||
if self.ceph_conf:
|
if self.ceph_conf:
|
||||||
self.path += ':conf=' + self.ceph_conf
|
self.path += ':conf=' + self.ceph_conf
|
||||||
|
|
||||||
def _connect_to_rados(self, pool=None):
|
|
||||||
client = self.rados.Rados(rados_id=self.rbd_user,
|
|
||||||
conffile=self.ceph_conf)
|
|
||||||
try:
|
|
||||||
client.connect()
|
|
||||||
pool_to_open = str(pool or self.pool)
|
|
||||||
ioctx = client.open_ioctx(pool_to_open)
|
|
||||||
return client, ioctx
|
|
||||||
except self.rados.Error:
|
|
||||||
# shutdown cannot raise an exception
|
|
||||||
client.shutdown()
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _disconnect_from_rados(self, client, ioctx):
|
|
||||||
# closing an ioctx cannot raise an exception
|
|
||||||
ioctx.close()
|
|
||||||
client.shutdown()
|
|
||||||
|
|
||||||
def _supports_layering(self):
|
|
||||||
return hasattr(self.rbd, 'RBD_FEATURE_LAYERING')
|
|
||||||
|
|
||||||
def _ceph_args(self):
|
|
||||||
args = []
|
|
||||||
if self.rbd_user:
|
|
||||||
args.extend(['--id', self.rbd_user])
|
|
||||||
if self.ceph_conf:
|
|
||||||
args.extend(['--conf', self.ceph_conf])
|
|
||||||
return args
|
|
||||||
|
|
||||||
def _get_mon_addrs(self):
|
|
||||||
args = ['ceph', 'mon', 'dump', '--format=json'] + self._ceph_args()
|
|
||||||
out, _ = utils.execute(*args)
|
|
||||||
lines = out.split('\n')
|
|
||||||
if lines[0].startswith('dumped monmap epoch'):
|
|
||||||
lines = lines[1:]
|
|
||||||
monmap = jsonutils.loads('\n'.join(lines))
|
|
||||||
addrs = [mon['addr'] for mon in monmap['mons']]
|
|
||||||
hosts = []
|
|
||||||
ports = []
|
|
||||||
for addr in addrs:
|
|
||||||
host_port = addr[:addr.rindex('/')]
|
|
||||||
host, port = host_port.rsplit(':', 1)
|
|
||||||
hosts.append(host.strip('[]'))
|
|
||||||
ports.append(port)
|
|
||||||
return hosts, ports
|
|
||||||
|
|
||||||
def libvirt_info(self, disk_bus, disk_dev, device_type, cache_mode,
|
def libvirt_info(self, disk_bus, disk_dev, device_type, cache_mode,
|
||||||
extra_specs, hypervisor_version):
|
extra_specs, hypervisor_version):
|
||||||
"""Get `LibvirtConfigGuestDisk` filled for this image.
|
"""Get `LibvirtConfigGuestDisk` filled for this image.
|
||||||
@@ -618,7 +524,7 @@ class Rbd(Image):
|
|||||||
"""
|
"""
|
||||||
info = vconfig.LibvirtConfigGuestDisk()
|
info = vconfig.LibvirtConfigGuestDisk()
|
||||||
|
|
||||||
hosts, ports = self._get_mon_addrs()
|
hosts, ports = self.driver.get_mon_addrs()
|
||||||
info.device_type = device_type
|
info.device_type = device_type
|
||||||
info.driver_format = 'raw'
|
info.driver_format = 'raw'
|
||||||
info.driver_cache = cache_mode
|
info.driver_cache = cache_mode
|
||||||
@@ -644,21 +550,9 @@ class Rbd(Image):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def check_image_exists(self):
|
def check_image_exists(self):
|
||||||
rbd_volumes = libvirt_utils.list_rbd_volumes(self.pool)
|
return self.driver.exists(self.rbd_name)
|
||||||
for vol in rbd_volumes:
|
|
||||||
if vol.startswith(self.rbd_name):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _resize(self, volume_name, size):
|
|
||||||
with RBDVolumeProxy(self, volume_name) as vol:
|
|
||||||
vol.resize(int(size))
|
|
||||||
|
|
||||||
def create_image(self, prepare_template, base, size, *args, **kwargs):
|
def create_image(self, prepare_template, base, size, *args, **kwargs):
|
||||||
if self.rbd is None:
|
|
||||||
raise RuntimeError(_('rbd python libraries not found'))
|
|
||||||
|
|
||||||
if not os.path.exists(base):
|
if not os.path.exists(base):
|
||||||
prepare_template(target=base, max_size=size, *args, **kwargs)
|
prepare_template(target=base, max_size=size, *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
@@ -667,15 +561,15 @@ class Rbd(Image):
|
|||||||
# keep using the command line import instead of librbd since it
|
# keep using the command line import instead of librbd since it
|
||||||
# detects zeroes to preserve sparseness in the image
|
# detects zeroes to preserve sparseness in the image
|
||||||
args = ['--pool', self.pool, base, self.rbd_name]
|
args = ['--pool', self.pool, base, self.rbd_name]
|
||||||
if self._supports_layering():
|
if self.driver.supports_layering():
|
||||||
args += ['--new-format']
|
args += ['--new-format']
|
||||||
args += self._ceph_args()
|
args += self.driver.ceph_args()
|
||||||
libvirt_utils.import_rbd_image(*args)
|
libvirt_utils.import_rbd_image(*args)
|
||||||
|
|
||||||
base_size = disk.get_disk_size(base)
|
base_size = disk.get_disk_size(base)
|
||||||
|
|
||||||
if size and size > base_size:
|
if size and size > base_size:
|
||||||
self._resize(self.rbd_name, size)
|
self.driver.resize(self.rbd_name, size)
|
||||||
|
|
||||||
def snapshot_extract(self, target, out_format):
|
def snapshot_extract(self, target, out_format):
|
||||||
images.convert_image(self.path, target, out_format)
|
images.convert_image(self.path, target, out_format)
|
||||||
|
|||||||
147
nova/virt/libvirt/rbd.py
Normal file
147
nova/virt/libvirt/rbd.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Copyright 2012 Grid Dynamics
|
||||||
|
# Copyright 2013 Inktank Storage, Inc.
|
||||||
|
# Copyright 2014 Mirantis, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
try:
|
||||||
|
import rados
|
||||||
|
import rbd
|
||||||
|
except ImportError:
|
||||||
|
rados = None
|
||||||
|
rbd = None
|
||||||
|
|
||||||
|
from nova.i18n import _
|
||||||
|
from nova.i18n import _LE
|
||||||
|
from nova.openstack.common import jsonutils
|
||||||
|
from nova.openstack.common import log as logging
|
||||||
|
from nova import utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RBDVolumeProxy(object):
|
||||||
|
"""Context manager for dealing with an existing rbd volume.
|
||||||
|
|
||||||
|
This handles connecting to rados and opening an ioctx automatically, and
|
||||||
|
otherwise acts like a librbd Image object.
|
||||||
|
|
||||||
|
The underlying librados client and ioctx can be accessed as the attributes
|
||||||
|
'client' and 'ioctx'.
|
||||||
|
"""
|
||||||
|
def __init__(self, driver, name, pool=None):
|
||||||
|
client, ioctx = driver._connect_to_rados(pool)
|
||||||
|
try:
|
||||||
|
self.volume = rbd.Image(ioctx, str(name), snapshot=None)
|
||||||
|
except rbd.Error:
|
||||||
|
LOG.exception(_LE("error opening rbd image %s"), name)
|
||||||
|
driver._disconnect_from_rados(client, ioctx)
|
||||||
|
raise
|
||||||
|
self.driver = driver
|
||||||
|
self.client = client
|
||||||
|
self.ioctx = ioctx
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type_, value, traceback):
|
||||||
|
try:
|
||||||
|
self.volume.close()
|
||||||
|
finally:
|
||||||
|
self.driver._disconnect_from_rados(self.client, self.ioctx)
|
||||||
|
|
||||||
|
def __getattr__(self, attrib):
|
||||||
|
return getattr(self.volume, attrib)
|
||||||
|
|
||||||
|
|
||||||
|
class RBDDriver(object):
|
||||||
|
|
||||||
|
def __init__(self, pool, ceph_conf, rbd_user):
|
||||||
|
self.pool = pool.encode('utf8')
|
||||||
|
# NOTE(angdraug): rados.Rados fails to connect if ceph_conf is None:
|
||||||
|
# https://github.com/ceph/ceph/pull/1787
|
||||||
|
self.ceph_conf = ceph_conf.encode('utf8') if ceph_conf else ''
|
||||||
|
self.rbd_user = rbd_user.encode('utf8') if rbd_user else None
|
||||||
|
if rbd is None:
|
||||||
|
raise RuntimeError(_('rbd python libraries not found'))
|
||||||
|
|
||||||
|
def _connect_to_rados(self, pool=None):
|
||||||
|
client = rados.Rados(rados_id=self.rbd_user,
|
||||||
|
conffile=self.ceph_conf)
|
||||||
|
try:
|
||||||
|
client.connect()
|
||||||
|
pool_to_open = str(pool or self.pool)
|
||||||
|
ioctx = client.open_ioctx(pool_to_open)
|
||||||
|
return client, ioctx
|
||||||
|
except rados.Error:
|
||||||
|
# shutdown cannot raise an exception
|
||||||
|
client.shutdown()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _disconnect_from_rados(self, client, ioctx):
|
||||||
|
# closing an ioctx cannot raise an exception
|
||||||
|
ioctx.close()
|
||||||
|
client.shutdown()
|
||||||
|
|
||||||
|
def supports_layering(self):
|
||||||
|
return hasattr(rbd, 'RBD_FEATURE_LAYERING')
|
||||||
|
|
||||||
|
def ceph_args(self):
|
||||||
|
"""List of command line parameters to be passed to ceph commands to
|
||||||
|
reflect RBDDriver configuration such as RBD user name and location
|
||||||
|
of ceph.conf.
|
||||||
|
"""
|
||||||
|
args = []
|
||||||
|
if self.rbd_user:
|
||||||
|
args.extend(['--id', self.rbd_user])
|
||||||
|
if self.ceph_conf:
|
||||||
|
args.extend(['--conf', self.ceph_conf])
|
||||||
|
return args
|
||||||
|
|
||||||
|
def get_mon_addrs(self):
|
||||||
|
args = ['ceph', 'mon', 'dump', '--format=json'] + self.ceph_args()
|
||||||
|
out, _ = utils.execute(*args)
|
||||||
|
lines = out.split('\n')
|
||||||
|
if lines[0].startswith('dumped monmap epoch'):
|
||||||
|
lines = lines[1:]
|
||||||
|
monmap = jsonutils.loads('\n'.join(lines))
|
||||||
|
addrs = [mon['addr'] for mon in monmap['mons']]
|
||||||
|
hosts = []
|
||||||
|
ports = []
|
||||||
|
for addr in addrs:
|
||||||
|
host_port = addr[:addr.rindex('/')]
|
||||||
|
host, port = host_port.rsplit(':', 1)
|
||||||
|
hosts.append(host.strip('[]'))
|
||||||
|
ports.append(port)
|
||||||
|
return hosts, ports
|
||||||
|
|
||||||
|
def size(self, name):
|
||||||
|
with RBDVolumeProxy(self, name) as vol:
|
||||||
|
return vol.size()
|
||||||
|
|
||||||
|
def resize(self, name, size):
|
||||||
|
"""Resize RBD volume.
|
||||||
|
|
||||||
|
:name: Name of RBD object
|
||||||
|
:size: New size in bytes
|
||||||
|
"""
|
||||||
|
LOG.debug('resizing rbd image %s to %d', name, size)
|
||||||
|
with RBDVolumeProxy(self, name) as vol:
|
||||||
|
vol.resize(size)
|
||||||
|
|
||||||
|
def exists(self, name):
|
||||||
|
try:
|
||||||
|
with RBDVolumeProxy(self, name):
|
||||||
|
return True
|
||||||
|
except rbd.ImageNotFound:
|
||||||
|
return False
|
||||||
Reference in New Issue
Block a user