diff --git a/manila/share/drivers/glusterfs.py b/manila/share/drivers/glusterfs.py index 41b50cb053..202a9eb725 100644 --- a/manila/share/drivers/glusterfs.py +++ b/manila/share/drivers/glusterfs.py @@ -55,7 +55,7 @@ GlusterfsManilaShare_opts = [ cfg.StrOpt('glusterfs_nfs_server_type', default='Gluster', help='Type of NFS server that mediate access to the Gluster ' - 'volumes (for now: only Gluster).'), + 'volumes (Gluster or Ganesha).'), cfg.StrOpt('glusterfs_server_password', default=None, secret=True, @@ -65,6 +65,18 @@ GlusterfsManilaShare_opts = [ cfg.StrOpt('glusterfs_path_to_private_key', default=None, help='Path of Manila host\'s private SSH key file.'), + cfg.StrOpt('glusterfs_ganesha_server_ip', + default=None, + help="Remote Ganesha server node's IP address."), + cfg.StrOpt('glusterfs_ganesha_server_username', + default='root', + help="Remote Ganesha server node's username."), + cfg.StrOpt('glusterfs_ganesha_server_password', + default=None, + secret=True, + help="Remote Ganesha server node's login password. " + "This is not required if 'glusterfs_path_to_private_key'" + ' is configured.'), ] CONF = cfg.CONF @@ -463,3 +475,32 @@ class GlusterNFSHelper(ganesha.NASHelperBase): ddict.pop(edir) self._manage_access(share['name'], access['access_type'], access['access_to'], cbk) + + +class GaneshaNFSHelper(ganesha.GaneshaNASHelper): + + def __init__(self, execute, config_object, **kwargs): + self.gluster_manager = kwargs.pop('gluster_manager') + if config_object.glusterfs_ganesha_server_ip: + execute = ganesha_utils.SSHExecutor( + config_object.glusterfs_ganesha_server_ip, 22, None, + config_object.glusterfs_ganesha_server_username, + password=config_object.glusterfs_ganesha_server_password, + privatekey=config_object.glusterfs_path_to_private_key) + else: + execute = ganesha_utils.RootExecutor(execute) + super(GaneshaNFSHelper, self).__init__(execute, config_object, + **kwargs) + + def _default_config_hook(self): + """Callback to provide default export block.""" + dconf = super(GaneshaNFSHelper, self)._default_config_hook() + conf_dir = ganesha_utils.path_from(__file__, "glusterfs", "conf") + ganesha_utils.patch(dconf, self._load_conf_dir(conf_dir)) + return dconf + + def _fsal_hook(self, base, share, access): + """Callback to create FSAL subblock.""" + return {"Hostname": self.gluster_manager.host, + "Volume": self.gluster_manager.volume, + "Volpath": "/" + share['name']} diff --git a/manila/share/drivers/glusterfs/conf/10-glusterfs-export-template.conf b/manila/share/drivers/glusterfs/conf/10-glusterfs-export-template.conf new file mode 100644 index 0000000000..d6bcb5ecea --- /dev/null +++ b/manila/share/drivers/glusterfs/conf/10-glusterfs-export-template.conf @@ -0,0 +1,8 @@ +EXPORT { + FSAL { + Name = GLUSTER; + Hostname = @config; + Volume = @config; + Volpath = @runtime; + } +} diff --git a/manila/tests/share/drivers/test_glusterfs.py b/manila/tests/share/drivers/test_glusterfs.py index 0cd2db02b5..c6199541c4 100644 --- a/manila/tests/share/drivers/test_glusterfs.py +++ b/manila/tests/share/drivers/test_glusterfs.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import errno import os @@ -43,7 +44,6 @@ fake_gluster_manager_attrs = { fake_local_share_path = '/mnt/nfs/testvol/fakename' - fake_args = ('foo', 'bar') fake_kwargs = {'key1': 'value1', 'key2': 'value2'} fake_path_to_private_key = '/fakepath/to/privatekey' @@ -844,3 +844,81 @@ class GlusterNFSHelperTestCase(test.TestCase): self._helper._get_export_dir_dict.assert_called_once_with() self._helper.gluster_manager.gluster_call.assert_called_once_with( *args) + + +class GaneshaNFSHelperTestCase(test.TestCase): + """Tests GaneshaNFSHelper.""" + + def setUp(self): + super(GaneshaNFSHelperTestCase, self).setUp() + self.gluster_manager = mock.Mock(**fake_gluster_manager_attrs) + self._execute = mock.Mock(return_value=('', '')) + self._root_execute = mock.Mock(return_value=('', '')) + self.access = fake_share.fake_access() + self.fake_conf = config.Configuration(None) + self.fake_template = {'key': 'value'} + self.share = fake_share.fake_share() + self.mock_object(glusterfs.ganesha_utils, 'RootExecutor', + mock.Mock(return_value=self._root_execute)) + self.mock_object(glusterfs.ganesha.GaneshaNASHelper, '__init__', + mock.Mock()) + self._helper = glusterfs.GaneshaNFSHelper( + self._execute, self.fake_conf, + gluster_manager=self.gluster_manager) + + def test_init_local_ganesha_server(self): + glusterfs.ganesha_utils.RootExecutor.assert_called_once_with( + self._execute) + glusterfs.ganesha.GaneshaNASHelper.__init__.assert_has_calls( + [mock.call(self._root_execute, self.fake_conf)]) + + def test_init_remote_ganesha_server(self): + ssh_execute = mock.Mock(return_value=('', '')) + CONF.set_default('glusterfs_ganesha_server_ip', 'fakeip') + self.mock_object(glusterfs.ganesha_utils, 'SSHExecutor', + mock.Mock(return_value=ssh_execute)) + glusterfs.GaneshaNFSHelper( + self._execute, self.fake_conf, + gluster_manager=self.gluster_manager) + glusterfs.ganesha_utils.SSHExecutor.assert_called_once_with( + 'fakeip', 22, None, 'root', password=None, privatekey=None) + glusterfs.ganesha.GaneshaNASHelper.__init__.assert_has_calls( + [mock.call(ssh_execute, self.fake_conf)]) + + def test_default_config_hook(self): + fake_conf_dict = {'key': 'value1'} + mock_ganesha_utils_patch = mock.Mock() + + def fake_patch_run(tmpl1, tmpl2): + mock_ganesha_utils_patch( + copy.deepcopy(tmpl1), tmpl2) + tmpl1.update(tmpl2) + + self.mock_object(glusterfs.ganesha.GaneshaNASHelper, + '_default_config_hook', + mock.Mock(return_value=self.fake_template)) + self.mock_object(glusterfs.ganesha_utils, 'path_from', + mock.Mock(return_value='/fakedir/glusterfs/conf')) + self.mock_object(self._helper, '_load_conf_dir', + mock.Mock(return_value=fake_conf_dict)) + self.mock_object(glusterfs.ganesha_utils, 'patch', + mock.Mock(side_effect=fake_patch_run)) + ret = self._helper._default_config_hook() + glusterfs.ganesha.GaneshaNASHelper._default_config_hook.\ + assert_called_once_with() + glusterfs.ganesha_utils.path_from.assert_called_once_with( + glusterfs.__file__, 'glusterfs', 'conf') + self._helper._load_conf_dir.assert_called_once_with( + '/fakedir/glusterfs/conf') + glusterfs.ganesha_utils.patch.assert_called_once_with( + self.fake_template, fake_conf_dict) + self.assertEqual(fake_conf_dict, ret) + + def test_fsal_hook(self): + output = { + 'Hostname': '127.0.0.1', + 'Volume': 'testvol', + 'Volpath': '/fakename' + } + ret = self._helper._fsal_hook('/fakepath', self.share, self.access) + self.assertEqual(output, ret)