diff --git a/os_brick/remotefs/remotefs.py b/os_brick/remotefs/remotefs.py index b648f432..5b875798 100644 --- a/os_brick/remotefs/remotefs.py +++ b/os_brick/remotefs/remotefs.py @@ -18,6 +18,7 @@ import hashlib import os import re +import tempfile from oslo_concurrency import processutils as putils from oslo_log import log as logging @@ -54,6 +55,12 @@ def __init__(self, mount_type, root_helper, raise exception.InvalidParameterValue( err=_('glusterfs_mount_point_base required')) self._mount_options = None + elif mount_type == "vzstorage": + self._mount_base = kwargs.get('vzstorage_mount_point_base', None) + if not self._mount_base: + raise exception.InvalidParameterValue( + err=_('vzstorage_mount_point_base required')) + self._mount_options = None else: raise exception.ProtocolNotSupported(protocol=mount_type) self.root_helper = root_helper @@ -101,6 +108,8 @@ def mount(self, share, flags=None): self._execute('mkdir', '-p', mount_path, check_exit_code=0) if self._mount_type == 'nfs': self._mount_nfs(share, mount_path, flags) + elif self._mount_type == 'vzstorage': + self._mount_vzstorage(share, mount_path, flags) else: self._do_mount(self._mount_type, share, mount_path, self._mount_options, flags) @@ -140,6 +149,53 @@ def _mount_nfs(self, nfs_share, mount_path, flags=None): % {'sh': nfs_share, 'error': mnt_errors}) + def _vzstorage_write_mds_list(self, cluster_name, mdss): + tmp_dir = tempfile.mkdtemp(prefix='vzstorage-') + tmp_bs_path = os.path.join(tmp_dir, 'bs_list') + with open(tmp_bs_path, 'w') as f: + for mds in mdss: + f.write(mds + "\n") + + conf_dir = os.path.join('/etc/pstorage/clusters', cluster_name) + if os.path.exists(conf_dir): + bs_path = os.path.join(conf_dir, 'bs_list') + self._execute('cp', '-f', tmp_bs_path, bs_path, + root_helper=self.root_helper, run_as_root=True) + else: + self._execute('cp', '-rf', tmp_dir, conf_dir, + root_helper=self.root_helper, run_as_root=True) + self._execute('chown', '-R', 'root:root', conf_dir, + root_helper=self.root_helper, run_as_root=True) + + def _mount_vzstorage(self, vz_share, mount_path, flags=None): + m = re.search("(?:(\S+):\/)?([a-zA-Z0-9_-]+)(?::(\S+))?", vz_share) + if not m: + msg = (_("Invalid Virtuozzo Storage share specification: %r." + "Must be: [MDS1[,MDS2],...:/][:PASSWORD].") + % vz_share) + raise exception.BrickException(msg) + + mdss = m.group(1) + cluster_name = m.group(2) + passwd = m.group(3) + + if mdss: + mdss = mdss.split(',') + self._vzstorage_write_mds_list(cluster_name, mdss) + + if passwd: + self._execute('pstorage', '-c', cluster_name, 'auth-node', '-P', + process_input=passwd, + root_helper=self.root_helper, run_as_root=True) + + mnt_cmd = ['pstorage-mount', '-c', cluster_name] + if flags: + mnt_cmd.extend(flags) + mnt_cmd.extend([mount_path]) + + self._execute(*mnt_cmd, root_helper=self.root_helper, + run_as_root=True, check_exit_code=0) + def _check_nfs_options(self): """Checks and prepares nfs mount type options.""" self._nfs_mount_type_opts = {'nfs': self._mount_options} diff --git a/os_brick/tests/remotefs/test_remotefs.py b/os_brick/tests/remotefs/test_remotefs.py new file mode 100644 index 00000000..f3e856a5 --- /dev/null +++ b/os_brick/tests/remotefs/test_remotefs.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +# 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 +import tempfile + +from oslo_concurrency import processutils as putils + +from os_brick import exception +from os_brick.remotefs import remotefs +from os_brick.tests import base + + +class RemoteFsClientTestCase(base.TestCase): + + def setUp(self): + super(RemoteFsClientTestCase, self).setUp() + self.mock_execute = mock.patch.object(putils, 'execute').start() + + @mock.patch.object(remotefs.RemoteFsClient, '_read_mounts', + return_value=[]) + def test_cifs(self, mock_read_mounts): + client = remotefs.RemoteFsClient("cifs", root_helper='true', + execute=putils.execute, + smbfs_mount_point_base='/mnt') + share = '10.0.0.1:/qwe' + mount_point = client.get_mount_point(share) + client.mount(share) + calls = [mock.call('mkdir', '-p', mount_point, check_exit_code=0), + mock.call('mount', '-t', 'cifs', share, mount_point, + run_as_root=True, root_helper='true', + check_exit_code=0)] + self.mock_execute.assert_has_calls(calls) + + @mock.patch.object(remotefs.RemoteFsClient, '_read_mounts', + return_value=[]) + def test_vzstorage_by_cluster_name(self, mock_read_mounts): + client = remotefs.RemoteFsClient("vzstorage", root_helper='true', + execute=putils.execute, + vzstorage_mount_point_base='/mnt') + share = 'qwe' + cluster_name = share + mount_point = client.get_mount_point(share) + client.mount(share) + calls = [mock.call('mkdir', '-p', mount_point, check_exit_code=0), + mock.call('pstorage-mount', '-c', cluster_name, mount_point, + root_helper='true', check_exit_code=0, + run_as_root=True)] + self.mock_execute.assert_has_calls(calls) + + @mock.patch.object(remotefs.RemoteFsClient, '_read_mounts', + return_value=[]) + def test_vzstorage_with_auth(self, mock_read_mounts): + client = remotefs.RemoteFsClient("vzstorage", root_helper='true', + execute=putils.execute, + vzstorage_mount_point_base='/mnt') + cluster_name = 'qwe' + password = '123456' + share = '%s:%s' % (cluster_name, password) + mount_point = client.get_mount_point(share) + client.mount(share) + calls = [mock.call('mkdir', '-p', mount_point, check_exit_code=0), + mock.call('pstorage', '-c', cluster_name, 'auth-node', '-P', + process_input=password, root_helper='true', + run_as_root=True), + mock.call('pstorage-mount', '-c', cluster_name, mount_point, + root_helper='true', check_exit_code=0, + run_as_root=True)] + self.mock_execute.assert_has_calls(calls) + + @mock.patch.object(remotefs.RemoteFsClient, '_read_mounts', + return_value=[]) + def test_vzstorage_with_mds_list(self, mock_read_mounts): + client = remotefs.RemoteFsClient("vzstorage", root_helper='true', + execute=putils.execute, + vzstorage_mount_point_base='/mnt') + cluster_name = 'qwe' + mds_list = ['10.0.0.1', '10.0.0.2'] + share = '%s:/%s' % (','.join(mds_list), cluster_name) + mount_point = client.get_mount_point(share) + vz_conf_dir = os.path.join('/etc/pstorage/clusters/', cluster_name) + + tmp_dir = tempfile.mkdtemp() + with mock.patch.object(tempfile, 'mkdtemp', + return_value=tmp_dir): + client.mount(share) + + saved_mds_list = open(os.path.join(tmp_dir, 'bs_list')).read().split() + self.assertEqual(set(mds_list), set(saved_mds_list)) + calls = [mock.call('mkdir', '-p', mount_point, check_exit_code=0), + mock.call('cp', '-rf', tmp_dir, vz_conf_dir, + run_as_root=True, root_helper='true'), + mock.call('chown', '-R', 'root:root', vz_conf_dir, + run_as_root=True, root_helper='true'), + mock.call('pstorage-mount', '-c', cluster_name, mount_point, + root_helper='true', check_exit_code=0, + run_as_root=True)] + self.mock_execute.assert_has_calls(calls) + + @mock.patch.object(remotefs.RemoteFsClient, '_read_mounts', + return_value=[]) + def test_vzstorage_invalid_share(self, mock_read_mounts): + client = remotefs.RemoteFsClient("vzstorage", root_helper='true', + execute=putils.execute, + vzstorage_mount_point_base='/mnt') + self.assertRaises(exception.BrickException, client.mount, ':') + + @mock.patch.object(remotefs.RemoteFsClient, '_read_mounts', + return_value=[]) + def test_nfs(self, mock_read_mounts): + client = remotefs.RemoteFsClient("nfs", root_helper='true', + execute=putils.execute, + nfs_mount_point_base='/mnt') + share = '10.0.0.1:/qwe' + mount_point = client.get_mount_point(share) + client.mount(share) + calls = [mock.call('mkdir', '-p', mount_point, check_exit_code=0), + mock.call('mount', '-t', 'nfs', '-o', 'vers=4,minorversion=1', + share, mount_point, check_exit_code=0, + run_as_root=True, root_helper='true')] + self.mock_execute.assert_has_calls(calls) + + def test_read_mounts(self): + mounts = """device1 on mnt_point1 + device2 on mnt_point2 type ext4 opts""" + with mock.patch.object(putils, 'execute', return_value=[mounts, '']): + client = remotefs.RemoteFsClient("cifs", root_helper='true', + execute=putils.execute, + smbfs_mount_point_base='/mnt') + ret = client._read_mounts() + self.assertEqual(ret, {'mnt_point1': 'device1', + 'mnt_point2': 'device2'}) + + @mock.patch.object(putils, 'execute') + @mock.patch.object(remotefs.RemoteFsClient, '_do_mount') + def test_mount_already_mounted(self, mock_do_mount, mock_execute): + share = "10.0.0.1:/share" + client = remotefs.RemoteFsClient("cifs", root_helper='true', + execute=putils.execute, + smbfs_mount_point_base='/mnt') + mounts = {client.get_mount_point(share): 'some_dev'} + with mock.patch.object(client, '_read_mounts', + return_value=mounts): + client.mount(share) + self.assertEqual(mock_do_mount.call_count, 0) + self.assertEqual(mock_execute.call_count, 0) + + def _test_no_mount_point(self, fs_type): + self.assertRaises(exception.InvalidParameterValue, + remotefs.RemoteFsClient, + fs_type, root_helper='true', execute=putils.execute) + + def test_no_mount_point_nfs(self): + self._test_no_mount_point('nfs') + + def test_no_mount_point_cifs(self): + self._test_no_mount_point('cifs') + + def test_no_mount_point_glusterfs(self): + self._test_no_mount_point('glusterfs') + + def test_no_mount_point_vzstorage(self): + self._test_no_mount_point('vzstorage') + + def test_invalid_fs(self): + self.assertRaises(exception.ProtocolNotSupported, + remotefs.RemoteFsClient, + 'my_fs', root_helper='true', execute=putils.execute)