Merge "Move libvirt RBD utilities to a new file"
This commit is contained in:
		@@ -18,7 +18,6 @@ import shutil
 | 
			
		||||
import tempfile
 | 
			
		||||
 | 
			
		||||
import fixtures
 | 
			
		||||
import mock
 | 
			
		||||
from oslo.config import cfg
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
@@ -30,6 +29,7 @@ from nova import test
 | 
			
		||||
from nova.tests import fake_processutils
 | 
			
		||||
from nova.tests.virt.libvirt import fake_libvirt_utils
 | 
			
		||||
from nova.virt.libvirt import imagebackend
 | 
			
		||||
from nova.virt.libvirt import rbd
 | 
			
		||||
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
 | 
			
		||||
@@ -671,14 +671,8 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
 | 
			
		||||
                   group='libvirt')
 | 
			
		||||
        self.libvirt_utils = imagebackend.libvirt_utils
 | 
			
		||||
        self.utils = imagebackend.utils
 | 
			
		||||
        self.rbd = self.mox.CreateMockAnything()
 | 
			
		||||
        self.rados = self.mox.CreateMockAnything()
 | 
			
		||||
 | 
			
		||||
    def prepare_mocks(self):
 | 
			
		||||
        fn = self.mox.CreateMockAnything()
 | 
			
		||||
        self.mox.StubOutWithMock(imagebackend, 'rbd')
 | 
			
		||||
        self.mox.StubOutWithMock(imagebackend, 'rados')
 | 
			
		||||
        return fn
 | 
			
		||||
        self.mox.StubOutWithMock(rbd, 'rbd')
 | 
			
		||||
        self.mox.StubOutWithMock(rbd, 'rados')
 | 
			
		||||
 | 
			
		||||
    def test_cache(self):
 | 
			
		||||
        image = self.image_class(self.INSTANCE, self.NAME)
 | 
			
		||||
@@ -746,10 +740,10 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
 | 
			
		||||
        self.mox.VerifyAll()
 | 
			
		||||
 | 
			
		||||
    def test_create_image(self):
 | 
			
		||||
        fn = self.prepare_mocks()
 | 
			
		||||
        fn(max_size=None, rbd=self.rbd, target=self.TEMPLATE_PATH)
 | 
			
		||||
        fn = self.mox.CreateMockAnything()
 | 
			
		||||
        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')
 | 
			
		||||
        imagebackend.disk.get_disk_size(self.TEMPLATE_PATH
 | 
			
		||||
@@ -762,7 +756,7 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
 | 
			
		||||
        self.mox.ReplayAll()
 | 
			
		||||
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
@@ -771,8 +765,6 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
 | 
			
		||||
 | 
			
		||||
        fake_processutils.fake_execute_clear_log()
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
        def fake_fetch(target, *args, **kwargs):
 | 
			
		||||
@@ -807,16 +799,6 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
    INSTANCE = {'name': 'fake-instance',
 | 
			
		||||
@@ -859,6 +841,8 @@ class BackendTestCase(test.NoDBTestCase):
 | 
			
		||||
        pool = "FakePool"
 | 
			
		||||
        self.flags(images_rbd_pool=pool, 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)
 | 
			
		||||
 | 
			
		||||
    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.libvirt import config as vconfig
 | 
			
		||||
from nova.virt.libvirt import lvm
 | 
			
		||||
from nova.virt.libvirt import rbd
 | 
			
		||||
from nova.virt.libvirt import utils as libvirt_utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import rados
 | 
			
		||||
    import rbd
 | 
			
		||||
except ImportError:
 | 
			
		||||
    rados = None
 | 
			
		||||
    rbd = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__imagebackend_opts = [
 | 
			
		||||
    cfg.StrOpt('images_type',
 | 
			
		||||
               default='default',
 | 
			
		||||
@@ -76,6 +68,8 @@ CONF = cfg.CONF
 | 
			
		||||
CONF.register_opts(__imagebackend_opts, 'libvirt')
 | 
			
		||||
CONF.import_opt('image_cache_subdirectory_name', 'nova.virt.imagecache')
 | 
			
		||||
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__)
 | 
			
		||||
 | 
			
		||||
@@ -488,51 +482,6 @@ class Lvm(Image):
 | 
			
		||||
                             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):
 | 
			
		||||
    def __init__(self, instance=None, disk_name=None, path=None, **kwargs):
 | 
			
		||||
        super(Rbd, self).__init__("block", "rbd", is_block_dev=True)
 | 
			
		||||
@@ -549,10 +498,13 @@ class Rbd(Image):
 | 
			
		||||
                                 ' images_rbd_pool'
 | 
			
		||||
                                 ' flag to use rbd images.'))
 | 
			
		||||
        self.pool = CONF.libvirt.images_rbd_pool
 | 
			
		||||
        self.ceph_conf = ascii_str(CONF.libvirt.images_rbd_ceph_conf)
 | 
			
		||||
        self.rbd_user = ascii_str(CONF.libvirt.rbd_user)
 | 
			
		||||
        self.rbd = kwargs.get('rbd', rbd)
 | 
			
		||||
        self.rados = kwargs.get('rados', rados)
 | 
			
		||||
        self.rbd_user = CONF.libvirt.rbd_user
 | 
			
		||||
        self.ceph_conf = CONF.libvirt.images_rbd_ceph_conf
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
        if self.rbd_user:
 | 
			
		||||
@@ -560,52 +512,6 @@ class Rbd(Image):
 | 
			
		||||
        if 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,
 | 
			
		||||
            extra_specs, hypervisor_version):
 | 
			
		||||
        """Get `LibvirtConfigGuestDisk` filled for this image.
 | 
			
		||||
@@ -618,7 +524,7 @@ class Rbd(Image):
 | 
			
		||||
        """
 | 
			
		||||
        info = vconfig.LibvirtConfigGuestDisk()
 | 
			
		||||
 | 
			
		||||
        hosts, ports = self._get_mon_addrs()
 | 
			
		||||
        hosts, ports = self.driver.get_mon_addrs()
 | 
			
		||||
        info.device_type = device_type
 | 
			
		||||
        info.driver_format = 'raw'
 | 
			
		||||
        info.driver_cache = cache_mode
 | 
			
		||||
@@ -644,21 +550,9 @@ class Rbd(Image):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def check_image_exists(self):
 | 
			
		||||
        rbd_volumes = libvirt_utils.list_rbd_volumes(self.pool)
 | 
			
		||||
        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))
 | 
			
		||||
        return self.driver.exists(self.rbd_name)
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
            prepare_template(target=base, max_size=size, *args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
@@ -667,15 +561,15 @@ class Rbd(Image):
 | 
			
		||||
        # keep using the command line import instead of librbd since it
 | 
			
		||||
        # detects zeroes to preserve sparseness in the image
 | 
			
		||||
        args = ['--pool', self.pool, base, self.rbd_name]
 | 
			
		||||
        if self._supports_layering():
 | 
			
		||||
        if self.driver.supports_layering():
 | 
			
		||||
            args += ['--new-format']
 | 
			
		||||
        args += self._ceph_args()
 | 
			
		||||
        args += self.driver.ceph_args()
 | 
			
		||||
        libvirt_utils.import_rbd_image(*args)
 | 
			
		||||
 | 
			
		||||
        base_size = disk.get_disk_size(base)
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
        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