Add manage/unmanage implementation to Container Driver

This patch implements managing and unmanaging of share
servers and shares functionality to the Container driver.

Depends-On: I452c2a99b186f53d737cb7fbd7eabfcfd9b249d6
Implements: bp container-driver-manage-unmanage-with-share-servers
Change-Id: I623c88dd1155740f0a444d2063236d91efd3bd1e
This commit is contained in:
Rodrigo Barbieri 2019-01-02 15:25:59 -02:00 committed by Rodrigo Barbieri
parent 28df9d2268
commit edf227ee27
9 changed files with 363 additions and 13 deletions

View File

@ -260,7 +260,6 @@ elif [[ "$DRIVER" == "dummy" ]]; then
iniset $TEMPEST_CONFIG share create_networks_when_multitenancy_enabled False iniset $TEMPEST_CONFIG share create_networks_when_multitenancy_enabled False
iniset $TEMPEST_CONFIG share multi_backend True iniset $TEMPEST_CONFIG share multi_backend True
elif [[ "$DRIVER" == "container"* ]]; then elif [[ "$DRIVER" == "container"* ]]; then
MANILA_TESTS="(^manila_tempest_tests.tests.api)(?=.*\[.*\bbackend\b.*\])"
if [[ "$DRIVER" == "container_with_custom_image" ]]; then if [[ "$DRIVER" == "container_with_custom_image" ]]; then
# TODO(vponomaryov): set scenario tests for run when # TODO(vponomaryov): set scenario tests for run when
# manila tempest plugin supports share protocol and rules that # manila tempest plugin supports share protocol and rules that
@ -270,7 +269,7 @@ elif [[ "$DRIVER" == "container"* ]]; then
fi fi
MANILA_TEMPEST_CONCURRENCY=8 MANILA_TEMPEST_CONCURRENCY=8
RUN_MANILA_SG_TESTS=False RUN_MANILA_SG_TESTS=False
RUN_MANILA_MANAGE_TESTS=False RUN_MANILA_MANAGE_TESTS=True
RUN_MANILA_QUOTA_TESTS=False RUN_MANILA_QUOTA_TESTS=False
RUN_MANILA_SHRINK_TESTS=False RUN_MANILA_SHRINK_TESTS=False
RUN_MANILA_SNAPSHOT_TESTS=False RUN_MANILA_SNAPSHOT_TESTS=False

View File

@ -187,3 +187,9 @@ sha256sum: CommandFilter, sha256sum, root
# manila/utils.py: 'tee', '%s' # manila/utils.py: 'tee', '%s'
tee: CommandFilter, tee, root tee: CommandFilter, tee, root
# manila/share/drivers/container/storage_helper.py: lvs -o lv_size --noheadings --nosuffix --units g <device>
lvs: CommandFilter, lvs, root
# manila/share/drivers/container/storage_helper.py: lvrename --autobackup n <old_name> <new_name>
lvrename: CommandFilter, lvrename, root

View File

@ -115,6 +115,34 @@ class DockerExecHelper(driver.ExecuteMixin):
address = address_w_prefix.split('/')[0] address = address_w_prefix.split('/')[0]
return address return address
def rename_container(self, name, new_name):
veth_name = self.find_container_veth(name)
if not veth_name:
raise exception.ManilaException(
_("Could not find OVS information related to "
"container %s.") % name)
try:
self._inner_execute(["docker", "rename", name, new_name])
except (exception.ProcessExecutionError, OSError):
raise exception.ShareBackendException(
msg="Could not rename container %s." % name)
cmd = ["ovs-vsctl", "set", "interface", veth_name,
"external-ids:manila-container=%s" % new_name]
try:
self._inner_execute(cmd)
except (exception.ProcessExecutionError, OSError):
try:
self._inner_execute(["docker", "rename", new_name, name])
except (exception.ProcessExecutionError, OSError):
msg = _("Could not rename back container %s.") % name
LOG.exception(msg)
raise exception.ShareBackendException(
msg="Could not update OVS information %s." % name)
LOG.info("Container %s has been successfully renamed.", name)
def find_container_veth(self, name): def find_container_veth(self, name):
interfaces = self._execute("ovs-vsctl", "list", "interface", interfaces = self._execute("ovs-vsctl", "list", "interface",
run_as_root=True)[0] run_as_root=True)[0]

View File

@ -20,6 +20,7 @@ Current implementation suggests that a container when started by Docker will
be plugged into a Linux bridge. Also it is suggested that all interfaces be plugged into a Linux bridge. Also it is suggested that all interfaces
willing to talk to each other reside in an OVS bridge.""" willing to talk to each other reside in an OVS bridge."""
import math
import re import re
from oslo_config import cfg from oslo_config import cfg
@ -122,7 +123,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin):
share_name = share.share_id share_name = share.share_id
self.storage.provide_storage(share_name, share['size']) self.storage.provide_storage(share_name, share['size'])
location = self._create_and_mount_share_links( location = self._create_export_and_mount_storage(
share, server_id, share_name) share, server_id, share_name)
return location return location
@ -135,8 +136,8 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin):
server_id = self._get_container_name(share_server["id"]) server_id = self._get_container_name(share_server["id"])
share_name = self._get_share_name(share) share_name = self._get_share_name(share)
self._delete_and_umount_share_links(share, server_id, share_name, self._delete_export_and_umount_storage(share, server_id, share_name,
ignore_errors=True) ignore_errors=True)
self.storage.remove_storage(share_name) self.storage.remove_storage(share_name)
LOG.debug("Deleted share %s successfully.", share_name) LOG.debug("Deleted share %s successfully.", share_name)
@ -295,7 +296,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin):
LOG.info("Container %s was created.", server_id) LOG.info("Container %s was created.", server_id)
return {"id": network_info["server_id"]} return {"id": network_info["server_id"]}
def _delete_and_umount_share_links( def _delete_export_and_umount_storage(
self, share, server_id, share_name, ignore_errors=False): self, share, server_id, share_name, ignore_errors=False):
self._get_helper(share).delete_share(server_id, share_name, self._get_helper(share).delete_share(server_id, share_name,
@ -317,7 +318,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin):
ignore_errors=True ignore_errors=True
) )
def _create_and_mount_share_links(self, share, server_id, share_name): def _create_export_and_mount_storage(self, share, server_id, share_name):
self.container.execute( self.container.execute(
server_id, server_id,
["mkdir", "-m", "750", "/shares/%s" % share_name] ["mkdir", "-m", "750", "/shares/%s" % share_name]
@ -329,3 +330,50 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin):
) )
location = self._get_helper(share).create_share(server_id) location = self._get_helper(share).create_share(server_id)
return location return location
def manage_existing_with_server(
self, share, driver_options, share_server=None):
if not share_server and self.driver_handles_share_servers:
raise exception.ShareBackendException(
"A share server object is needed to manage a share in this "
"driver mode of operation.")
server_id = self._get_container_name(share_server["id"])
share_name = self._get_share_name(share)
size = int(math.ceil(float(self.storage.get_size(share_name))))
self._delete_export_and_umount_storage(share, server_id, share_name)
new_share_name = share.share_id
self.storage.rename_storage(share_name, new_share_name)
location = self._create_export_and_mount_storage(
share, server_id, new_share_name)
result = {'size': size, 'export_locations': [location]}
LOG.info("Successfully managed share %(share)s, returning %(data)s",
{'share': share.id, 'data': result})
return result
def unmanage_with_server(self, share, share_server=None):
pass
def get_share_server_network_info(
self, context, share_server, identifier, driver_options):
name = self._get_correct_container_old_name(identifier)
return [self.container.fetch_container_address(name, "inet")]
def manage_server(self, context, share_server, identifier, driver_options):
new_name = self._get_container_name(share_server['id'])
old_name = self._get_correct_container_old_name(identifier)
self.container.rename_container(old_name, new_name)
return new_name, {'id': share_server['id']}
def unmanage_server(self, server_details, security_services=None):
pass
def _get_correct_container_old_name(self, name):
# Check if the container with the given name exists, else return
# the name based on the driver template
if not self.container.container_exists(name):
return self._get_container_name(name)
return name

View File

@ -111,9 +111,33 @@ class LVMHelper(driver.ExecuteMixin):
LOG.warning("Failed to remove logical volume %(device)s due to " LOG.warning("Failed to remove logical volume %(device)s due to "
"%(reason)s.", {'device': device, 'reason': e}) "%(reason)s.", {'device': device, 'reason': e})
def rename_storage(self, share_name, new_share_name):
old_device = self._get_lv_device(share_name)
new_device = self._get_lv_device(new_share_name)
self._try_to_unmount_device(old_device)
try:
self._execute("lvrename", "--autobackup", "n",
old_device, new_device, run_as_root=True)
except exception.ProcessExecutionError as e:
msg = ("Failed to rename logical volume %(device)s due to "
"%(reason)s." % {'device': old_device, 'reason': e})
LOG.exception(msg)
raise
def extend_share(self, share_name, new_size, share_server=None): def extend_share(self, share_name, new_size, share_server=None):
lv_device = self._get_lv_device(share_name) lv_device = self._get_lv_device(share_name)
cmd = ('lvextend', '-L', '%sG' % new_size, '-n', lv_device) cmd = ('lvextend', '-L', '%sG' % new_size, '-n', lv_device)
self._execute(*cmd, run_as_root=True) self._execute(*cmd, run_as_root=True)
self._execute("e2fsck", "-f", "-y", lv_device, run_as_root=True) self._execute("e2fsck", "-f", "-y", lv_device, run_as_root=True)
self._execute('resize2fs', lv_device, run_as_root=True) self._execute('resize2fs', lv_device, run_as_root=True)
def get_size(self, share_name):
device = self._get_lv_device(share_name)
size = self._execute(
"lvs", "-o", "lv_size", "--noheadings", "--nosuffix",
"--units", "g", device, run_as_root=True)
LOG.debug("Found size %(size)s for LVM device "
"%(lvm)s.", {'size': size[0], 'lvm': share_name})
return size[0]

View File

@ -144,6 +144,63 @@ class DockerExecHelperTestCase(test.TestCase):
"show", "scope", "global", "dev", "eth0"] "show", "scope", "global", "dev", "eth0"]
) )
def test_rename_container(self):
fake_old_name = "old_name"
fake_new_name = "new_name"
fake_veth_name = "veth_fake"
self.DockerExecHelper.find_container_veth = mock.Mock(
return_value=fake_veth_name)
mock__inner_execute = self.DockerExecHelper._inner_execute = mock.Mock(
return_value=['fake', ''])
self.DockerExecHelper.rename_container(fake_old_name, fake_new_name)
self.DockerExecHelper.find_container_veth.assert_called_once_with(
fake_old_name
)
mock__inner_execute.assert_has_calls([
mock.call(["docker", "rename", fake_old_name, fake_new_name]),
mock.call(["ovs-vsctl", "set", "interface", fake_veth_name,
"external-ids:manila-container=%s" % fake_new_name])
])
def test_rename_container_exception_veth(self):
self.DockerExecHelper.find_container_veth = mock.Mock(
return_value=None)
self.assertRaises(exception.ManilaException,
self.DockerExecHelper.rename_container,
"old_name", "new_name")
@ddt.data([['fake', ''], OSError, ['fake', '']],
[['fake', ''], OSError, OSError],
[OSError])
def test_rename_container_exception_cmds(self, side_effect):
fake_old_name = "old_name"
fake_new_name = "new_name"
fake_veth_name = "veth_fake"
self.DockerExecHelper.find_container_veth = mock.Mock(
return_value=fake_veth_name)
mock__inner_execute = self.DockerExecHelper._inner_execute = mock.Mock(
side_effect=side_effect)
self.assertRaises(exception.ShareBackendException,
self.DockerExecHelper.rename_container,
fake_old_name, fake_new_name)
if len(side_effect) > 1:
mock__inner_execute.assert_has_calls([
mock.call(["docker", "rename", fake_old_name, fake_new_name]),
mock.call(["ovs-vsctl", "set", "interface", fake_veth_name,
"external-ids:manila-container=%s" % fake_new_name])
])
else:
mock__inner_execute.assert_has_calls([
mock.call(["docker", "rename", fake_old_name, fake_new_name]),
])
@ddt.data('my_container', 'manila_my_container') @ddt.data('my_container', 'manila_my_container')
def test_find_container_veth(self, name): def test_find_container_veth(self, name):

View File

@ -119,7 +119,7 @@ class ContainerShareDriverTestCase(test.TestCase):
self._driver, '_get_container_name', self._driver, '_get_container_name',
mock.Mock(return_value=fake_container_name)) mock.Mock(return_value=fake_container_name))
mock_create_and_mount = self.mock_object( mock_create_and_mount = self.mock_object(
self._driver, '_create_and_mount_share_links', self._driver, '_create_export_and_mount_storage',
mock.Mock(return_value='export_location')) mock.Mock(return_value='export_location'))
self.assertEqual('export_location', self.assertEqual('export_location',
@ -135,7 +135,7 @@ class ContainerShareDriverTestCase(test.TestCase):
share_server['id'] share_server['id']
) )
def test__create_and_mount_share_links(self): def test__create_export_and_mount_storage(self):
helper = mock.Mock() helper = mock.Mock()
server_id = 'fake_id' server_id = 'fake_id'
share_name = 'fake_name' share_name = 'fake_name'
@ -149,7 +149,7 @@ class ContainerShareDriverTestCase(test.TestCase):
mock_execute = self.mock_object(self._driver.container, 'execute') mock_execute = self.mock_object(self._driver.container, 'execute')
self.assertEqual('export_location', self.assertEqual('export_location',
self._driver._create_and_mount_share_links( self._driver._create_export_and_mount_storage(
self.share, server_id, share_name)) self.share, server_id, share_name))
mock_create_share.assert_called_once_with(server_id) mock_create_share.assert_called_once_with(server_id)
mock__get_helper.assert_called_once_with(self.share) mock__get_helper.assert_called_once_with(self.share)
@ -160,7 +160,7 @@ class ContainerShareDriverTestCase(test.TestCase):
"/shares/%s" % share_name]) "/shares/%s" % share_name])
]) ])
def test__delete_and_umount_share_links(self): def test__delete_export_and_umount_storage(self):
helper = mock.Mock() helper = mock.Mock()
server_id = 'fake_id' server_id = 'fake_id'
share_name = 'fake_name' share_name = 'fake_name'
@ -168,7 +168,7 @@ class ContainerShareDriverTestCase(test.TestCase):
self._driver, "_get_helper", mock.Mock(return_value=helper)) self._driver, "_get_helper", mock.Mock(return_value=helper))
mock_delete_share = self.mock_object(helper, 'delete_share') mock_delete_share = self.mock_object(helper, 'delete_share')
mock_execute = self.mock_object(self._driver.container, 'execute') mock_execute = self.mock_object(self._driver.container, 'execute')
self._driver._delete_and_umount_share_links( self._driver._delete_export_and_umount_storage(
self.share, server_id, share_name) self.share, server_id, share_name)
mock__get_helper.assert_called_once_with(self.share) mock__get_helper.assert_called_once_with(self.share)
@ -194,7 +194,7 @@ class ContainerShareDriverTestCase(test.TestCase):
mock.Mock(return_value=fake_share_name)) mock.Mock(return_value=fake_share_name))
self.mock_object(self._driver.storage, 'remove_storage') self.mock_object(self._driver.storage, 'remove_storage')
mock_delete_and_umount = self.mock_object( mock_delete_and_umount = self.mock_object(
self._driver, '_delete_and_umount_share_links') self._driver, '_delete_export_and_umount_storage')
self._driver.delete_share(self._context, self.share, fake_share_server) self._driver.delete_share(self._context, self.share, fake_share_server)
@ -391,3 +391,133 @@ class ContainerShareDriverTestCase(test.TestCase):
self._driver._connect_to_network.assert_called_once_with(server_id, self._driver._connect_to_network.assert_called_once_with(server_id,
network_info, network_info,
'veth0') 'veth0')
def test_manage_existing(self):
fake_container_name = "manila_fake_container"
fake_export_location = 'export_location'
expected_result = {
'size': 1,
'export_locations': [fake_export_location]
}
fake_share_server = cont_fakes.fake_share()
fake_share_name = self._driver._get_share_name(self.share)
mock_get_container_name = self.mock_object(
self._driver, '_get_container_name',
mock.Mock(return_value=fake_container_name))
mock_get_share_name = self.mock_object(
self._driver, '_get_share_name',
mock.Mock(return_value=fake_share_name))
mock_rename_storage = self.mock_object(
self._driver.storage, 'rename_storage')
mock_get_size = self.mock_object(
self._driver.storage, 'get_size', mock.Mock(return_value=1))
mock_delete_and_umount = self.mock_object(
self._driver, '_delete_export_and_umount_storage')
mock_create_and_mount = self.mock_object(
self._driver, '_create_export_and_mount_storage',
mock.Mock(return_value=fake_export_location)
)
result = self._driver.manage_existing_with_server(
self.share, {}, fake_share_server)
mock_rename_storage.assert_called_once_with(
fake_share_name, self.share.share_id
)
mock_get_size.assert_called_once_with(
fake_share_name
)
mock_delete_and_umount.assert_called_once_with(
self.share, fake_container_name, fake_share_name
)
mock_create_and_mount.assert_called_once_with(
self.share, fake_container_name, self.share.share_id
)
mock_get_container_name.assert_called_once_with(
fake_share_server['id']
)
mock_get_share_name.assert_called_with(
self.share
)
self.assertEqual(expected_result, result)
def test_manage_existing_no_share_server(self):
self.assertRaises(exception.ShareBackendException,
self._driver.manage_existing_with_server,
self.share, {})
def test_unmanage(self):
self.assertIsNone(self._driver.unmanage_with_server(self.share))
def test_get_share_server_network_info(self):
fake_share_server = cont_fakes.fake_share_server()
fake_id = cont_fakes.fake_identifier()
expected_result = ['veth11b2c34']
interfaces = [cont_fakes.FAKE_VSCTL_LIST_INTERFACE_1,
cont_fakes.FAKE_VSCTL_LIST_INTERFACE_2,
cont_fakes.FAKE_VSCTL_LIST_INTERFACE_4,
cont_fakes.FAKE_VSCTL_LIST_INTERFACE_3]
self.mock_object(self._driver.container, 'execute',
mock.Mock(return_value=interfaces))
result = self._driver.get_share_server_network_info(self._context,
fake_share_server,
fake_id, {})
self.assertEqual(expected_result, result)
def test_manage_server(self):
fake_id = cont_fakes.fake_identifier()
fake_share_server = cont_fakes.fake_share_server()
fake_container_name = "manila_fake_container"
fake_container_old_name = "fake_old_name"
mock_get_container_name = self.mock_object(
self._driver, '_get_container_name',
mock.Mock(return_value=fake_container_name))
mock_get_correct_container_old_name = self.mock_object(
self._driver, '_get_correct_container_old_name',
mock.Mock(return_value=fake_container_old_name)
)
mock_rename_container = self.mock_object(self._driver.container,
'rename_container')
expected_result = {'id': fake_share_server['id']}
new_identifier, new_backend_details = self._driver.manage_server(
self._context, fake_share_server, fake_id, {})
self.assertEqual(expected_result, new_backend_details)
self.assertEqual(fake_container_name, new_identifier)
mock_rename_container.assert_called_once_with(
fake_container_old_name, fake_container_name)
mock_get_container_name.assert_called_with(
fake_share_server['id']
)
mock_get_correct_container_old_name.assert_called_once_with(
fake_id
)
@ddt.data(True, False)
def test__get_correct_container_old_name(self, container_exists):
expected_name = 'fake-name'
fake_name = 'fake-name'
mock_container_exists = self.mock_object(
self._driver.container, 'container_exists',
mock.Mock(return_value=container_exists))
if not container_exists:
expected_name = 'manila_fake_name'
result = self._driver._get_correct_container_old_name(fake_name)
self.assertEqual(expected_name, result)
mock_container_exists.assert_called_once_with(
fake_name
)

View File

@ -136,6 +136,39 @@ class LVMHelperTestCase(test.TestCase):
self.assertTrue(storage_helper.LOG.warning.called) self.assertTrue(storage_helper.LOG.warning.called)
@ddt.data(None, exception.ProcessExecutionError)
def test_rename_storage(self, side_effect):
fake_old_share_name = 'fake_old_name'
fake_new_share_name = 'fake_new_name'
fake_new_device = "/dev/new_device"
fake_old_device = "/dev/old_device"
mock_get_lv_device = self.mock_object(
self.LVMHelper, '_get_lv_device',
mock.Mock(side_effect=[fake_old_device, fake_new_device]))
mock_try_to_umount = self.mock_object(self.LVMHelper,
'_try_to_unmount_device')
mock_execute = self.mock_object(self.LVMHelper, '_execute',
mock.Mock(side_effect=side_effect))
if side_effect is None:
self.LVMHelper.rename_storage(fake_old_share_name,
fake_new_share_name)
else:
self.assertRaises(exception.ProcessExecutionError,
self.LVMHelper.rename_storage,
fake_old_share_name, fake_new_share_name)
mock_try_to_umount.assert_called_once_with(fake_old_device)
mock_execute.mock_assert_called_once_with(
"lvrename", "--autobackup", "n", fake_old_device, fake_new_device,
run_as_root=True
)
mock_get_lv_device.assert_has_calls([
mock.call(fake_old_share_name),
mock.call(fake_new_share_name)
])
def test_extend_share(self): def test_extend_share(self):
actual_arguments = [] actual_arguments = []
expected_arguments = [ expected_arguments = [
@ -152,3 +185,22 @@ class LVMHelperTestCase(test.TestCase):
self.LVMHelper.extend_share(fake_share_name, 'share', 3) self.LVMHelper.extend_share(fake_share_name, 'share', 3)
self.assertEqual(expected_arguments, actual_arguments) self.assertEqual(expected_arguments, actual_arguments)
def test_get_size(self):
share_name = 'fakeshareid'
fake_old_device = {}
mock_get_lv_device = self.mock_object(
self.LVMHelper, '_get_lv_device',
mock.Mock(return_value=fake_old_device))
mock_execute = self.mock_object(self.LVMHelper, '_execute',
mock.Mock(return_value=[1, "args"]))
result = self.LVMHelper.get_size(share_name)
mock_execute.assert_called_once_with(
"lvs", "-o", "lv_size", "--noheadings", "--nosuffix", "--units",
"g", fake_old_device, run_as_root=True
)
mock_get_lv_device.assert_called_once_with(share_name)
self.assertEqual(result, 1)

View File

@ -0,0 +1,6 @@
---
features:
- Added managing and unmanaging of share servers functionality
to the Container Driver, allowing for shares to be managed
and unmanaged.