From: Erlon Cruz Date: Thu, 12 Jun 2014 10:55:38 +0000 (-0300) Subject: Adds SSH communication to HNAS drivers X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=0614375b7ea2e58cb96b67b3bfd4fa539ae0f2ee;p=openstack-build%2Fcinder-build.git Adds SSH communication to HNAS drivers This commit adds SSH communication to HNAS iSCSI and NFS drivers. The user now has the option to export the server public keys to HNAS and configure the authentication via public keys. This avoid exposing HNAS passwords in the plain text config file and exposing the private HNAS internal network. Implements: blueprint hds-hnas-ssh-backend Change-Id: Iafb5d7a4b8607128437b4e606e9ed478c24a43a0 --- diff --git a/cinder/tests/test_hds_hnas_backend.py b/cinder/tests/test_hds_hnas_backend.py index 160fe1d3f..f4c1142fb 100644 --- a/cinder/tests/test_hds_hnas_backend.py +++ b/cinder/tests/test_hds_hnas_backend.py @@ -15,13 +15,14 @@ # import mock +from oslo_concurrency import processutils from oslo_config import cfg from cinder.openstack.common import log as logging from cinder import test from cinder import utils from cinder.volume.drivers.hds import hnas_backend - +from cinder.volume.drivers.hds import nfs CONF = cfg.CONF @@ -39,7 +40,9 @@ FS ID FS Label FS Permanent ID EVS ID EVS Label\n\ 1031 cinder2 0xaadfcf7e0769a6bc 1 EVSTest1\n\ 1024 fs02-husvm 0xaac8715e2e9406cd 2 EVSTest2\n\ \n" + HNAS_RESULT2 = "cluster MAC: 83-68-96-AA-DA-5D" + HNAS_RESULT3 = "\n\ Model: HNAS 4040 \n\ Software: 11.2.3319.14 (built 2013-09-19 12:34:24+01:00) \n\ @@ -54,6 +57,7 @@ Serial no B1339745 (Thu Jan 1 00:00:50 2009) \n\ board MCP \n\ Serial no B1339109 (Thu Jan 1 00:00:49 2009) \n\ \n" + HNAS_RESULT4 = "\n\ EVS Type Label IP Address Mask Port \n\ ---------- --------------- ------------------ --------------- ------\n\ @@ -63,38 +67,52 @@ evs 1 EVSTest1 172.24.44.20 255.255.255.0 ag1 \n\ evs 1 EVSTest1 10.0.0.20 255.255.255.0 ag1 \n\ evs 2 EVSTest2 172.24.44.21 255.255.255.0 ag1 \n\ \n" + HNAS_RESULT5 = "\n\ - ID Label EVS Size Used Snapshots Deduped Avail\ - Thin ThinSize ThinAvail FS Type \n\ ----- ----------- --- ------- ------------- --------- -------- -------\ ------- ---- -------- --------- ---------------------------------- \n\ -1025 fs01-husvm 1 250 GB 21.4 GB (9%) 0 B (0%) NA 228 GB\ - (91%) No 32 KB,WFS-2,128 DSBs\n\ -1026 gold 1 19.9 GB 2.30 GB (12% NA 0 B (0%) 17.6 GB\ - (88%) No 4 KB,WFS-2,128 DSBs,dedupe enabled\n\ -1027 large-files 1 19.8 GB 2.43 GB (12%) 0 B (0%) NA 17.3 GB\ - (88%) No 32 KB,WFS-2,128 DSBs\n\ -1028 platinun 1 19.9 GB 2.30 GB (12%) NA 0 B (0%) 17.6 GB\ - (88%) No 4 KB,WFS-2,128 DSBs,dedupe enabled\n\ -1029 silver 1 19.9 GB 3.19 GB (16%) 0 B (0%) NA 6.7 GB\ - (84%) No 4 KB,WFS-2,128 DSBs\n\ -1030 cinder1 1 40.8 GB 2.24 GB (5%) 0 B (0%) NA 38.5 GB\ - (95%) No 4 KB,WFS-2,128 DSBs\n\ -1031 cinder2 1 39.8 GB 2.23 GB (6%) 0 B (0%) NA 37.6 GB\ - (94%) No 4 KB,WFS-2,128 DSBs\n\ -1024 fs02-husvm 2 49.8 GB 3.54 GB (7%) 0 B (0%) NA 46.2 GB\ - (93%) No 32 KB,WFS-2,128 DSBs\n\ -1032 test 2 3.97 GB 2.12 GB (53%) 0 B (0%) NA 1.85 GB\ - (47%) No 4 KB,WFS-2,128 DSBs\n\ + ID Label EVS Size Used Snapshots Deduped\ + Avail Thin ThinSize ThinAvail \ + FS Type \n\ +---- ----------- --- ------- ------------- --------- -------\ +- ------------- ---- -------- --------- ---------------------\ +------------- \n\ +1025 fs01-husvm 1 250 GB 21.4 GB (9%) 0 B (0%) NA \ + 228 GB (91%) No 32 KB,\ + WFS-2,128 DSBs\n\ +1026 gold 1 19.9 GB 2.30 GB (12% NA 0 B (0%)\ + 17.6 GB (88%) No 4 KB,WFS-2,128 DSBs,\ + dedupe enabled\n\ +1027 large-files 1 19.8 GB 2.43 GB (12%) 0 B (0%) NA \ + 17.3 GB (88%) No 32 KB,\ + WFS-2,128 DSBs\n\ +1028 platinun 1 19.9 GB 2.30 GB (12%) NA 0 B (0%)\ + 17.6 GB (88%) No 4 KB,WFS-2,128 DSBs,\ + dedupe enabled\n\ +1029 silver 1 19.9 GB 3.19 GB (16%) 0 B (0%) NA \ + 6.7 GB (84%) No 4 KB,\ + WFS-2,128 DSBs\n\ +1030 cinder1 1 40.8 GB 2.24 GB (5%) 0 B (0%) NA \ + 38.5 GB (95%) No 4 KB,\ + WFS-2,128 DSBs\n\ +1031 cinder2 1 39.8 GB 2.23 GB (6%) 0 B (0%) NA \ + 37.6 GB (94%) No 4 KB,\ + WFS-2,128 DSBs\n\ +1024 fs02-husvm 2 49.8 GB 3.54 GB (7%) 0 B (0%) NA \ + 46.2 GB (93%) No 32 KB,\ + WFS-2,128 DSBs\n\ +1032 test 2 3.97 GB 2.12 GB (53%) 0 B (0%) NA \ + 1.85 GB (47%) No 4 KB,\ + WFS-2,128 DSBs\n\ \n" + HNAS_RESULT6 = "\n\ -ID Label EVS Size Used Snapshots Deduped Avail\ - Thin ThinSize ThinAvail FS Type\n\ ----- ---------- --- ------ ------------ --------- ------- ------------\ - ---- -------- --------- --------------------\n\ -1025 fs01-husvm 1 250 GB 21.4 GB (9%) 0 B (0%) NA 228 GB (91%)\ - No 32 KB,WFS-2,128 DSBs\n\ +ID Label EVS Size Used Snapshots Deduped Avail \ +Thin ThinSize ThinAvail FS Type\n\ +---- ---------- --- ------ ------------ --------- ------- ------------ \ +---- -------- --------- --------------------\n\ +1025 fs01-husvm 1 250 GB 21.4 GB (9%) 0 B (0%) NA 228 GB (91%) \ + No 32 KB,WFS-2,128 DSBs\n\ \n" + HNAS_RESULT7 = "\n\ Export configuration: \n\ Export name: /export01-husvm \n\ @@ -114,10 +132,12 @@ Disaster recovery setting: \n\ Recovered = No \n\ Transfer setting = Use file system default \n\ \n" + HNAS_RESULT8 = "Logical unit creation started at 2014-12-24 00:38:30+00:00." HNAS_RESULT9 = "Logical unit deleted successfully." HNAS_RESULT10 = "" HNAS_RESULT11 = "Logical unit expansion started at 2014-12-24 01:25:03+00:00." + HNAS_RESULT12 = "\n\ Alias : test_iqn \n\ Globally unique name: iqn.2014-12.10.10.10.10:evstest1.cinder-silver \n\ @@ -126,10 +146,12 @@ Secret : test_secret \n\ Authentication : Enabled \n\ Logical units : No logical units. \n\ \n" + HNAS_RESULT13 = "Logical unit added successfully." HNAS_RESULT14 = "Logical unit removed successfully." HNAS_RESULT15 = "Target created successfully." HNAS_RESULT16 = "" + HNAS_RESULT17 = "\n\ EVS Type Label IP Address Mask Port \n\ ---------- --------------- ------------------ --------------- ------\n\ @@ -163,72 +185,129 @@ Logical units : No logical units. \n\ \n" HNAS_CMDS = { - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', 'evsfs', - u'list'): ["%s" % HNAS_RESULT1, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', - 'cluster-getmac'): ["%s" % HNAS_RESULT2, ""], - ('ssc', '-version',): ["%s" % HNAS_RESULT18, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', 'ver',): + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'evsfs', 'list'): + ["%s" % HNAS_RESULT1, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'cluster-getmac',): + ["%s" % HNAS_RESULT2, ""], + ('ssh', '-version',): ["%s" % HNAS_RESULT18, ""], + ('ssh', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', 'ver',): ["%s" % HNAS_RESULT3, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', 'evsipaddr', - '-l'): ["%s" % HNAS_RESULT4, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', 'df', '-a'): - ["%s" % HNAS_RESULT5, ""], - ('df', '-f', 'test_hdp'): ["%s" % HNAS_RESULT6, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', 'for-each-evs', - '-q', 'nfs-export', 'list'): ["%s" % HNAS_RESULT7, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'ver',): + ["%s" % HNAS_RESULT3, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'evsipaddr', '-l'): + ["%s" % HNAS_RESULT4, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'df', '-a'): + ["%s" % HNAS_RESULT5, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'df', '-f', 'test_hdp'): + ["%s" % HNAS_RESULT6, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'for-each-evs', '-q', + 'nfs-export', 'list'): + ["%s" % HNAS_RESULT7, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs', '1', 'iscsi-lu', 'add', '-e', 'test_name', - 'test_hdp', '/.cinder/test_name.iscsi', '1M'): ["%s" % HNAS_RESULT8, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', + 'test_hdp', '/.cinder/test_name.iscsi', + '1M'): + ["%s" % HNAS_RESULT8, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs', '1', 'iscsi-lu', 'del', '-d', '-f', - 'test_lun'): ["%s" % HNAS_RESULT9, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', + 'test_lun'): + ["%s" % HNAS_RESULT9, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs', '1', 'file-clone-create', '-f', 'fs01-husvm', - '/.cinder/test_lu.iscsi', 'cloned_lu'): ["%s" % HNAS_RESULT10, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', + '/.cinder/test_lu.iscsi', 'cloned_lu'): + ["%s" % HNAS_RESULT10, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs', '1', 'iscsi-lu', 'expand', 'expanded_lu', - '1M'): ["%s" % HNAS_RESULT11, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', - 'console-context', '--evs', '1', 'iscsi-target', 'list', - 'test_iqn'): ["%s" % HNAS_RESULT12, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', + '1M'): + ["%s" % HNAS_RESULT11, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', + 'console-context', '--evs', '1', 'iscsi-target', 'list', 'test_iqn'): + ["%s" % HNAS_RESULT12, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs', '1', 'iscsi-target', 'addlu', 'test_iqn', - 'test_lun', 0): ["%s" % HNAS_RESULT13, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', - 'console-context', '--evs', '1', 'iscsi-target', 'dellu', 'test_iqn', 0): - ["%s" % HNAS_RESULT14, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', - 'console-context', '--evs', '1', 'iscsi-target', 'add', 'test_iqn', - 'test_secret'): ["%s" % HNAS_RESULT15, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', + 'test_lun', 0): + ["%s" % HNAS_RESULT13, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', + 'console-context', '--evs', '1', 'iscsi-target', 'dellu', 'test_iqn', + 0): + ["%s" % HNAS_RESULT14, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', + 'console-context', '--evs', '1', 'iscsi-target', 'add', 'myTarget', + 'secret'): + ["%s" % HNAS_RESULT15, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs', '1', 'iscsi-target', 'mod', '-s', 'test_secret', '-a', 'enable', 'test_iqn'): ["%s" % HNAS_RESULT15, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs', '1', 'iscsi-lu', 'clone', '-e', 'test_lu', - 'test_clone', '/.cinder/test_clone.iscsi'): ["%s" % HNAS_RESULT16, ""], - ('ssc', '-u', 'supervisor', '-p', 'supervisor', '0.0.0.0', 'evsipaddr', - '-e', '1'): ["%s" % HNAS_RESULT17, ""] + 'test_clone', + '/.cinder/test_clone.iscsi'): + ["%s" % HNAS_RESULT16, ""], + ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'evsipaddr', '-e', '1'): + ["%s" % HNAS_RESULT17, ""] } +DRV_CONF = {'ssh_enabled': 'True', + 'mgmt_ip0': '0.0.0.0', + 'cluster_admin_ip0': None, + 'ssh_port': '22', + 'ssh_private_key': 'test_key', + 'username': 'supervisor', + 'password': 'supervisor'} + +UTILS_EXEC_OUT = ["output: test_cmd", ""] + -def m_execute(*args, **kargs): +def m_run_cmd(*args, **kargs): + print(args) + print(HNAS_CMDS.get(args)) return HNAS_CMDS.get(args) class HDSHNASBendTest(test.TestCase): - """Test HNAS NFS volume driver.""" def __init__(self, *args, **kwargs): super(HDSHNASBendTest, self).__init__(*args, **kwargs) - def setUp(self): + @mock.patch.object(nfs, 'factory_bend') + def setUp(self, m_factory_bend): super(HDSHNASBendTest, self).setUp() - self.hnas_bend = hnas_backend.HnasBackend() + self.hnas_bend = hnas_backend.HnasBackend(DRV_CONF) + + @mock.patch('__builtin__.open') + @mock.patch('os.path.isfile', return_value=True) + @mock.patch('paramiko.RSAKey.from_private_key_file') + @mock.patch('paramiko.SSHClient') + @mock.patch.object(processutils, 'ssh_execute', + return_value=(HNAS_RESULT5, '')) + def test_run_cmd(self, m_ssh_exec, m_ssh_cli, m_pvt_key, m_file, m_open): + save_hkey_file = CONF.ssh_hosts_key_file + save_spath = CONF.state_path + CONF.ssh_hosts_key_file = '/var/lib/cinder/ssh_known_hosts' + CONF.state_path = '/var/lib/cinder' + + out, err = self.hnas_bend.run_cmd('ssh', '0.0.0.0', + 'supervisor', 'supervisor', + 'df', '-a') + self.assertIn('fs01-husvm', out) + self.assertIn('WFS-2,128 DSBs', out) + + CONF.state_path = save_spath + CONF.ssh_hosts_key_file = save_hkey_file - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_get_iscsi_info(self, m_mock): - out = self.hnas_bend.get_iscsi_info("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + @mock.patch.object(utils, 'execute', return_value=UTILS_EXEC_OUT) + def test_get_version(self, m_cmd, m_exec): + out = self.hnas_bend.get_version("ssh", "1.0", "0.0.0.0", "supervisor", + "supervisor") + self.assertIn('11.2.3319.14', out) + self.assertIn('83-68-96-AA-DA-5D', out) + + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_get_iscsi_info(self, m_execute): + out = self.hnas_bend.get_iscsi_info("ssh", "0.0.0.0", "supervisor", "supervisor") self.assertIn('172.24.44.20', out) @@ -236,19 +315,10 @@ class HDSHNASBendTest(test.TestCase): self.assertIn('10.0.0.20', out) self.assertEqual(len(out.split('\n')), 4) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_get_version(self, m_execute): - out = self.hnas_bend.get_version("ssc", "1.0", "0.0.0.0", "supervisor", - "supervisor") - - self.assertIn('11.2.3319.14', out) - self.assertIn('11.1.3225.01', out) - self.assertIn('83-68-96-AA-DA-5D', out) - - @mock.patch.object(utils, 'execute') - def test_get_hdp_info(self, m_exec): + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd') + def test_get_hdp_info(self, m_run_cmd): # tests when there is two or more evs - m_exec.return_value = (HNAS_RESULT5, "") + m_run_cmd.return_value = (HNAS_RESULT5, "") out = self.hnas_bend.get_hdp_info("ssh", "0.0.0.0", "supervisor", "supervisor") @@ -259,7 +329,7 @@ class HDSHNASBendTest(test.TestCase): self.assertEqual(len(line1.split()), 12) # test when there is only one evs - m_exec.return_value = (HNAS_RESULT19, "") + m_run_cmd.return_value = (HNAS_RESULT19, "") out = self.hnas_bend.get_hdp_info("ssh", "0.0.0.0", "supervisor", "supervisor") self.assertEqual(len(out.split('\n')), 3) @@ -268,9 +338,10 @@ class HDSHNASBendTest(test.TestCase): line1 = out.split('\n')[0] self.assertEqual(len(line1.split()), 12) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_get_nfs_info(self, m_execute): - out = self.hnas_bend.get_nfs_info("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_get_nfs_info(self, m_run_cmd): + out = self.hnas_bend.get_nfs_info("ssh", "0.0.0.0", "supervisor", "supervisor") self.assertEqual(len(out.split('\n')), 2) @@ -278,78 +349,89 @@ class HDSHNASBendTest(test.TestCase): self.assertIn('172.24.44.20', out) self.assertIn('10.0.0.20', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_create_lu(self, m_execute): - out = self.hnas_bend.create_lu("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_create_lu(self, m_cmd): + out = self.hnas_bend.create_lu("ssh", "0.0.0.0", "supervisor", "supervisor", "test_hdp", "1", "test_name") self.assertIn('successfully created', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_delete_lu(self, m_execute): - out = self.hnas_bend.delete_lu("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_delete_lu(self, m_cmd): + out = self.hnas_bend.delete_lu("ssh", "0.0.0.0", "supervisor", "supervisor", "test_hdp", "test_lun") self.assertIn('deleted successfully', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_create_dup(self, m_execute): - out = self.hnas_bend.create_dup("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_create_dup(self, m_cmd): + + out = self.hnas_bend.create_dup("ssh", "0.0.0.0", "supervisor", "supervisor", "test_lu", "test_hdp", "1", "test_clone") self.assertIn('successfully created', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_file_clone(self, m_execute): - out = self.hnas_bend.file_clone("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_file_clone(self, m_cmd): + out = self.hnas_bend.file_clone("ssh", "0.0.0.0", "supervisor", "supervisor", "fs01-husvm", "/.cinder/test_lu.iscsi", "cloned_lu") self.assertIn('LUN cloned_lu HDP', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_extend_vol(self, m_execute): - out = self.hnas_bend.extend_vol("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_extend_vol(self, m_cmd): + out = self.hnas_bend.extend_vol("ssh", "0.0.0.0", "supervisor", "supervisor", "test_hdp", "test_lun", "1", "expanded_lu") self.assertIn('successfully extended', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_add_iscsi_conn(self, m_execute): - out = self.hnas_bend.add_iscsi_conn("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_add_iscsi_conn(self, m_cmd): + out = self.hnas_bend.add_iscsi_conn("ssh", "0.0.0.0", "supervisor", "supervisor", "test_lun", "test_hdp", "test_port", "test_iqn", "test_init") self.assertIn('successfully paired', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_del_iscsi_conn(self, m_execute): - out = self.hnas_bend.del_iscsi_conn("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_del_iscsi_conn(self, m_cmd): + out = self.hnas_bend.del_iscsi_conn("ssh", "0.0.0.0", "supervisor", "supervisor", "1", "test_iqn", 0) self.assertIn('already deleted', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) - def test_get_targetiqn(self, m_execute): - out = self.hnas_bend.get_targetiqn("ssc", "0.0.0.0", "supervisor", + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) + def test_get_targetiqn(self, m_cmd): + out = self.hnas_bend.get_targetiqn("ssh", "0.0.0.0", "supervisor", "supervisor", "test_iqn", "test_hdp", "test_secret") + self.assertEqual('test_iqn', out) - @mock.patch.object(utils, 'execute', side_effect=m_execute) + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd', + side_effect=m_run_cmd) def test_set_targetsecret(self, m_execute): - self.hnas_bend.set_targetsecret("ssc", "0.0.0.0", "supervisor", + self.hnas_bend.set_targetsecret("ssh", "0.0.0.0", "supervisor", "supervisor", "test_iqn", "test_hdp", "test_secret") - @mock.patch.object(utils, 'execute') - def test_get_targetsecret(self, m_exec): + @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd') + def test_get_targetsecret(self, m_run_cmd): # test when target has secret - m_exec.return_value = (HNAS_RESULT12, "") + m_run_cmd.return_value = (HNAS_RESULT12, "") out = self.hnas_bend.get_targetsecret("ssh", "0.0.0.0", "supervisor", "supervisor", "test_iqn", "test_hdp") @@ -357,7 +439,7 @@ class HDSHNASBendTest(test.TestCase): self.assertEqual('test_secret', out) # test when target don't have secret - m_exec.return_value = (HNAS_RESULT20, "") + m_run_cmd.return_value = (HNAS_RESULT20, "") out = self.hnas_bend.get_targetsecret("ssh", "0.0.0.0", "supervisor", "supervisor", "test_iqn", "test_hdp") diff --git a/cinder/volume/drivers/hds/hnas_backend.py b/cinder/volume/drivers/hds/hnas_backend.py index 7ac2fb8ec..c0751ada4 100644 --- a/cinder/volume/drivers/hds/hnas_backend.py +++ b/cinder/volume/drivers/hds/hnas_backend.py @@ -20,16 +20,80 @@ Hitachi Unified Storage (HUS-HNAS) platform. Backend operations. import re +from oslo_concurrency import processutils from oslo_utils import units +import six +from cinder.i18n import _LE, _LW, _LI from cinder.openstack.common import log as logging +from cinder import ssh_utils from cinder import utils LOG = logging.getLogger("cinder.volume.driver") -class HnasBackend(): +class HnasBackend(object): """Back end. Talks to HUS-HNAS.""" + def __init__(self, drv_configs): + self.drv_configs = drv_configs + self.sshpool = None + + def run_cmd(self, cmd, ip0, user, pw, *args, **kwargs): + """Run a command on SMU or using SSH + + :param cmd: the command that will be run on SMU + :param ip0: string IP address of controller + :param user: string user authentication for array + :param pw: string password authentication for array + :returns: formated string with version information + """ + LOG.debug('Enable ssh: %s', + six.text_type(self.drv_configs['ssh_enabled'])) + + if self.drv_configs['ssh_enabled'] != 'True': + # Direct connection via ssc + args = (cmd, '-u', user, '-p', pw, ip0) + args + out, err = utils.execute(*args, **kwargs) + LOG.debug("command %(cmd)s result: out = %(out)s - err = " + "%(err)s", {'cmd': cmd, 'out': out, 'err': err}) + return out, err + else: + if self.drv_configs['cluster_admin_ip0'] is None: + # Connect to SMU through SSH and run ssc locally + args = (cmd, 'localhost') + args + else: + args = (cmd, '--smuauth', + self.drv_configs['cluster_admin_ip0']) + args + + utils.check_ssh_injection(args) + command = ' '.join(args) + command = command.replace('"', '\\"') + + if not self.sshpool: + server = self.drv_configs['mgmt_ip0'] + port = int(self.drv_configs['ssh_port']) + username = self.drv_configs['username'] + # We only accept private/public key auth + password = "" + privatekey = self.drv_configs['ssh_private_key'] + self.sshpool = ssh_utils.SSHPool(server, + port, + None, + username, + password=password, + privatekey=privatekey) + + with self.sshpool.item() as ssh: + try: + out, err = processutils.ssh_execute(ssh, command, + check_exit_code=True) + LOG.debug("command %(cmd)s result: out = %(out)s - err = " + "%(err)s", {'cmd': cmd, 'out': out, 'err': err}) + return out, err + except processutils.ProcessExecutionError: + LOG.error(_LE("Error running SSH command.")) + raise + def get_version(self, cmd, ver, ip0, user, pw): """Gets version information from the storage unit @@ -39,19 +103,20 @@ class HnasBackend(): :param pw: string password authentication for array :returns: formated string with version information """ - out, err = utils.execute(cmd, - "-version", - check_exit_code=True) - util = out.split()[1] - out, err = utils.execute(cmd, - '-u', user, '-p', pw, ip0, - "cluster-getmac", - check_exit_code=True) + if (self.drv_configs['ssh_enabled'] == 'True' and + self.drv_configs['cluster_admin_ip0'] is not None): + util = 'SMU ' + cmd + else: + out, err = utils.execute(cmd, + "-version", + check_exit_code=True) + util = out.split()[1] + + out, err = self.run_cmd(cmd, ip0, user, pw, "cluster-getmac", + check_exit_code=True) hardware = out.split()[2] - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, 'ver', - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "ver", + check_exit_code=True) lines = out.split('\n') model = "" @@ -64,7 +129,7 @@ class HnasBackend(): out = "Array_ID: %s (%s) version: %s LU: 256 RG: 0 RG_LU: 0 \ Utility_version: %s" % (hardware, model, ver, util) - LOG.debug('get_version: ' + out + ' -- ' + err) + LOG.debug('get_version: %(out)s -- %(err)s', {'out': out, 'err': err}) return out def get_iscsi_info(self, cmd, ip0, user, pw): @@ -76,10 +141,9 @@ class HnasBackend(): :returns: formated string with iSCSI information """ - out, err = utils.execute(cmd, - '-u', user, '-p', pw, ip0, - 'evsipaddr', '-l', - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, + 'evsipaddr', '-l', + check_exit_code=True) lines = out.split('\n') newout = "" @@ -90,7 +154,8 @@ class HnasBackend(): newout += "CTL: %s Port: 0 IP: %s Port: 3260 Link: Up\n" \ % (evsnum, ip) - LOG.debug('get_iscsi_info: ' + out + ' -- ' + err) + LOG.debug('get_iscsi_info: %(out)s -- %(err)s', + {'out': out, 'err': err}) return newout def get_hdp_info(self, cmd, ip0, user, pw): @@ -102,10 +167,8 @@ class HnasBackend(): :returns: formated string with filesystems and fsids """ - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, 'df', '-a', - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, 'df', '-a', + check_exit_code=True) lines = out.split('\n') single_evs = True @@ -145,17 +208,16 @@ class HnasBackend(): int(float(used) * usedmultiplier), int(percent), fslabel) - LOG.debug('get_hdp_info: ' + newout + ' -- ' + err) + LOG.debug('get_hdp_info: %(out)s -- %(err)s', + {'out': newout, 'err': err}) return newout def _get_evs(self, cmd, ip0, user, pw, fsid): """Gets the EVSID for the named filesystem.""" - out, err = utils.execute(cmd, - '-u', user, '-p', pw, ip0, - "evsfs", "list", - check_exit_code=True) - LOG.debug('get_evs: out ' + out) + out, err = self.run_cmd(cmd, ip0, user, pw, "evsfs", "list", + check_exit_code=True) + LOG.debug('get_evs: out %s', out) lines = out.split('\n') for line in lines: @@ -163,16 +225,16 @@ class HnasBackend(): if fsid in line and (fsid == inf[0] or fsid == inf[1]): return inf[3] - LOG.warn('get_evs: ' + out + ' -- ' + 'No info for ' + fsid) + LOG.warning(_LW('get_evs: %(out)s -- No find for %(fsid)s'), + {'out': out, 'fsid': fsid}) return 0 def _get_evsips(self, cmd, ip0, user, pw, evsid): """Gets the EVS IPs for the named filesystem.""" - out, err = utils.execute(cmd, - '-u', user, '-p', pw, ip0, - 'evsipaddr', '-e', evsid, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, + 'evsipaddr', '-e', evsid, + check_exit_code=True) iplist = "" lines = out.split('\n') @@ -181,26 +243,25 @@ class HnasBackend(): if 'evs' in line: iplist += inf[3] + ' ' - LOG.debug('get_evsips: ' + iplist) + LOG.debug('get_evsips: %s', iplist) return iplist def _get_fsid(self, cmd, ip0, user, pw, fslabel): """Gets the FSID for the named filesystem.""" - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, 'evsfs', 'list', - check_exit_code=True) - LOG.debug('get_fsid: out ' + out) + out, err = self.run_cmd(cmd, ip0, user, pw, 'evsfs', 'list', + check_exit_code=True) + LOG.debug('get_fsid: out %s', out) lines = out.split('\n') for line in lines: inf = line.split() if fslabel in line and fslabel == inf[1]: - LOG.debug('get_fsid: ' + line) + LOG.debug('get_fsid: %s', line) return inf[0] - LOG.warn('get_fsid: ' + out + ' -- ' + 'No infor for ' + fslabel) + LOG.warning(_LW('get_fsid: %(out)s -- No info for %(fslabel)s'), + {'out': out, 'fslabel': fslabel}) return 0 def get_nfs_info(self, cmd, ip0, user, pw): @@ -212,11 +273,10 @@ class HnasBackend(): :returns: formated string """ - out, err = utils.execute(cmd, - '-u', user, '-p', pw, ip0, - 'for-each-evs', '-q', - 'nfs-export', 'list', - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, + 'for-each-evs', '-q', + 'nfs-export', 'list', + check_exit_code=True) lines = out.split('\n') newout = "" @@ -241,7 +301,8 @@ class HnasBackend(): % (export, path, fs, fsid, evsid, ips) fs = "" - LOG.debug('get_nfs_info: ' + newout + ' -- ' + err) + LOG.debug('get_nfs_info: %(out)s -- %(err)s', + {'out': newout, 'err': err}) return newout def create_lu(self, cmd, ip0, user, pw, hdp, size, name): @@ -261,20 +322,18 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-lu', 'add', "-e", - name, hdp, - '/.cinder/' + name + '.iscsi', - size + 'M', - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-lu', 'add', "-e", + name, hdp, + '/.cinder/' + name + '.iscsi', + size + 'M', + check_exit_code=True) out = "LUN %s HDP: %s size: %s MB, is successfully created" \ % (name, hdp, size) - LOG.debug('create_lu: ' + out) + LOG.debug('create_lu: %s', out) return out def delete_lu(self, cmd, ip0, user, pw, hdp, lun): @@ -289,15 +348,13 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-lu', 'del', '-d', - '-f', lun, - check_exit_code=True) - - LOG.debug('delete_lu: ' + out + ' -- ' + err) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-lu', 'del', '-d', + '-f', lun, + check_exit_code=True) + + LOG.debug('delete_lu: %(out)s -- %(err)s', {'out': out, 'err': err}) return out def create_dup(self, cmd, ip0, user, pw, src_lun, hdp, size, name): @@ -316,19 +373,17 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-lu', 'clone', '-e', - src_lun, name, - '/.cinder/' + name + '.iscsi', - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-lu', 'clone', '-e', + src_lun, name, + '/.cinder/' + name + '.iscsi', + check_exit_code=True) out = "LUN %s HDP: %s size: %s MB, is successfully created" \ % (name, hdp, size) - LOG.debug('create_dup: ' + out + ' -- ' + err) + LOG.debug('create_dup: %(out)s -- %(err)s', {'out': out, 'err': err}) return out def file_clone(self, cmd, ip0, user, pw, fslabel, src, name): @@ -347,17 +402,15 @@ class HnasBackend(): _fsid = self._get_fsid(cmd, ip0, user, pw, fslabel) _evsid = self._get_evs(cmd, ip0, user, pw, _fsid) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'file-clone-create', '-f', fslabel, - src, name, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'file-clone-create', '-f', fslabel, + src, name, + check_exit_code=True) out = "LUN %s HDP: %s Clone: %s -> %s" % (name, _fsid, src, name) - LOG.debug('file_clone: ' + out + ' -- ' + err) + LOG.debug('file_clone: %(out)s -- %(err)s', {'out': out, 'err': err}) return out def extend_vol(self, cmd, ip0, user, pw, hdp, lun, new_size, name): @@ -373,17 +426,15 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-lu', 'expand', - name, new_size + 'M', - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-lu', 'expand', + name, new_size + 'M', + check_exit_code=True) out = ("LUN: %s successfully extended to %s MB" % (name, new_size)) - LOG.debug('extend_vol: ' + out) + LOG.debug('extend_vol: %s', out) return out def add_iscsi_conn(self, cmd, ip0, user, pw, lun, hdp, @@ -401,12 +452,10 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'list', iqn, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'list', iqn, + check_exit_code=True) # even though ssc uses the target alias, need to return the full iqn fulliqn = "" @@ -424,14 +473,15 @@ class HnasBackend(): if lunline[0].isdigit(): # see if already mounted if vol[:29] == lun[:29]: - LOG.info('lun: %s already mounted %s' % (lun, lunline)) + LOG.info(_LI('lun: %(lun)s already mounted (lline)%s'), + {'lun': lun, 'lline': lunline}) conn = (int(lunline), lun, initiator, hlun, fulliqn, hlun, hdp, port) out = "H-LUN: %d alreadymapped LUN: %s, iSCSI \ Initiator: %s @ index: %d, and Target: %s \ @ index %d is successfully paired @ CTL: \ %s, Port: %s" % conn - LOG.debug('add_iscsi_conn: returns ' + out) + LOG.debug('add_iscsi_conn: returns %s', out) return out if int(lunline) == hlun: @@ -440,13 +490,11 @@ class HnasBackend(): # found a hole break - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'addlu', - iqn, lun, hlun, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'addlu', + iqn, lun, hlun, + check_exit_code=True) conn = (int(hlun), lun, initiator, int(hlun), fulliqn, int(hlun), hdp, port) @@ -454,7 +502,7 @@ class HnasBackend(): @ index: %d, and Target: %s @ index %d is \ successfully paired @ CTL: %s, Port: %s" % conn - LOG.debug('add_iscsi_conn: returns ' + out) + LOG.debug('add_iscsi_conn: returns %s', out) return out def del_iscsi_conn(self, cmd, ip0, user, pw, evsid, iqn, hlun): @@ -469,12 +517,10 @@ class HnasBackend(): :return: formated string """ - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", evsid, - 'iscsi-target', 'list', iqn, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", evsid, + 'iscsi-target', 'list', iqn, + check_exit_code=True) lines = out.split('\n') out = ("H-LUN: %d already deleted from target %s" % (int(hlun), iqn)) @@ -488,22 +534,20 @@ class HnasBackend(): if out != "": # hlun wasn't found - LOG.info('del_iscsi_conn: hlun not found' + out) + LOG.info(_LI('del_iscsi_conn: hlun not found %s'), out) return out # remove the LU from the target - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", evsid, - 'iscsi-target', 'dellu', - '-f', iqn, hlun, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", evsid, + 'iscsi-target', 'dellu', + '-f', iqn, hlun, + check_exit_code=True) out = "H-LUN: %d successfully deleted from target %s" \ % (int(hlun), iqn) - LOG.debug('del_iscsi_conn: ' + out + ' -- ') + LOG.debug('del_iscsi_conn: %s', out) return out def get_targetiqn(self, cmd, ip0, user, pw, targetalias, hdp, secret): @@ -521,31 +565,25 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'list', targetalias, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'list', targetalias, + check_exit_code=True) if "does not exist" in out: if secret == "": secret = '""' - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'add', - targetalias, secret, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'add', + targetalias, secret, + check_exit_code=True) else: - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'add', - targetalias, secret, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'add', + targetalias, secret, + check_exit_code=True) lines = out.split('\n') # returns the first iqn @@ -565,32 +603,26 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'list', - targetalias, - check_exit_code=False) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'list', + targetalias, + check_exit_code=False) if "does not exist" in out: - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'add', - targetalias, secret, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'add', + targetalias, secret, + check_exit_code=True) else: - LOG.info('targetlist: ' + targetalias + ' -- ' + out) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'mod', - '-s', secret, '-a', 'enable', - targetalias, - check_exit_code=True) + LOG.info(_LI('targetlist: %s'), targetalias) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'mod', + '-s', secret, '-a', 'enable', + targetalias, + check_exit_code=True) def get_targetsecret(self, cmd, ip0, user, pw, targetalias, hdp): """Returns the chap secret for the specified target. @@ -603,12 +635,10 @@ class HnasBackend(): """ _evsid = self._get_evs(cmd, ip0, user, pw, hdp) - out, err = utils.execute(cmd, - '-u', user, '-p', pw, - ip0, "console-context", - "--evs", _evsid, - 'iscsi-target', 'list', targetalias, - check_exit_code=True) + out, err = self.run_cmd(cmd, ip0, user, pw, "console-context", + "--evs", _evsid, + 'iscsi-target', 'list', targetalias, + check_exit_code=True) enabled = "" secret = "" diff --git a/cinder/volume/drivers/hds/iscsi.py b/cinder/volume/drivers/hds/iscsi.py index 9ddebc5a2..80d528289 100644 --- a/cinder/volume/drivers/hds/iscsi.py +++ b/cinder/volume/drivers/hds/iscsi.py @@ -32,7 +32,7 @@ from cinder.volume.drivers.hds import hnas_backend from cinder.volume import utils -HDS_HNAS_ISCSI_VERSION = '1.0.0' +HDS_HNAS_ISCSI_VERSION = '2.2.0' LOG = logging.getLogger(__name__) @@ -44,11 +44,13 @@ iSCSI_OPTS = [ CONF = cfg.CONF CONF.register_opts(iSCSI_OPTS) -HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc', 'chap_enabled': 'True'} +HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc', + 'chap_enabled': 'True', + 'ssh_port': '22'} -def factory_bend(type): - return hnas_backend.HnasBackend() +def factory_bend(drv_configs): + return hnas_backend.HnasBackend(drv_configs) def _loc_info(loc): @@ -100,14 +102,31 @@ def _read_config(xml_config_file): # mandatory parameters config = {} - arg_prereqs = ['mgmt_ip0', 'username', 'password'] + arg_prereqs = ['mgmt_ip0', 'username'] for req in arg_prereqs: config[req] = _xml_read(root, req, 'check') # optional parameters - for opt in ['hnas_cmd', 'chap_enabled']: - config[opt] = _xml_read(root, opt) or\ - HNAS_DEFAULT_CONFIG[opt] + opt_parameters = ['hnas_cmd', 'ssh_enabled', 'chap_enabled', + 'cluster_admin_ip0'] + for req in opt_parameters: + config[req] = _xml_read(root, req) + + if config['chap_enabled'] is None: + config['chap_enabled'] = HNAS_DEFAULT_CONFIG['chap_enabled'] + + if config['ssh_enabled'] == 'True': + config['ssh_private_key'] = _xml_read(root, 'ssh_private_key', 'check') + config['ssh_port'] = _xml_read(root, 'ssh_port') + config['password'] = _xml_read(root, 'password') + if config['ssh_port'] is None: + config['ssh_port'] = HNAS_DEFAULT_CONFIG['ssh_port'] + else: + # password is mandatory when not using SSH + config['password'] = _xml_read(root, 'password', 'check') + + if config['hnas_cmd'] is None: + config['hnas_cmd'] = HNAS_DEFAULT_CONFIG['hnas_cmd'] config['hdp'] = {} config['services'] = {} @@ -132,7 +151,11 @@ def _read_config(xml_config_file): class HDSISCSIDriver(driver.ISCSIDriver): - """HDS HNAS volume driver.""" + """HDS HNAS volume driver. + + Version 1.0.0: Initial driver version + Version 2.2.0: Added support to SSH authentication + """ def __init__(self, *args, **kwargs): """Initialize, read different config parameters.""" @@ -147,7 +170,7 @@ class HDSISCSIDriver(driver.ISCSIDriver): self.platform = self.type.lower() LOG.info(_LI("Backend type: %s"), self.type) - self.bend = factory_bend(self.type) + self.bend = factory_bend(self.config) def _array_info_get(self): """Get array parameters.""" diff --git a/cinder/volume/drivers/hds/nfs.py b/cinder/volume/drivers/hds/nfs.py index 36a19cc85..55e7aeb88 100644 --- a/cinder/volume/drivers/hds/nfs.py +++ b/cinder/volume/drivers/hds/nfs.py @@ -34,7 +34,7 @@ from cinder.volume.drivers.hds import hnas_backend from cinder.volume.drivers import nfs -HDS_HNAS_NFS_VERSION = '1.0.0' +HDS_HNAS_NFS_VERSION = '2.2.0' LOG = logging.getLogger(__name__) @@ -46,7 +46,7 @@ NFS_OPTS = [ CONF = cfg.CONF CONF.register_opts(NFS_OPTS) -HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc'} +HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc', 'ssh_port': '22'} def _xml_read(root, element, check=None): @@ -92,13 +92,27 @@ def _read_config(xml_config_file): # mandatory parameters config = {} - arg_prereqs = ['mgmt_ip0', 'username', 'password'] + arg_prereqs = ['mgmt_ip0', 'username'] for req in arg_prereqs: config[req] = _xml_read(root, req, 'check') # optional parameters - config['hnas_cmd'] = _xml_read(root, 'hnas_cmd') or\ - HNAS_DEFAULT_CONFIG['hnas_cmd'] + opt_parameters = ['hnas_cmd', 'ssh_enabled', 'cluster_admin_ip0'] + for req in opt_parameters: + config[req] = _xml_read(root, req) + + if config['ssh_enabled'] == 'True': + config['ssh_private_key'] = _xml_read(root, 'ssh_private_key', 'check') + config['password'] = _xml_read(root, 'password') + config['ssh_port'] = _xml_read(root, 'ssh_port') + if config['ssh_port'] is None: + config['ssh_port'] = HNAS_DEFAULT_CONFIG['ssh_port'] + else: + # password is mandatory when not using SSH + config['password'] = _xml_read(root, 'password', 'check') + + if config['hnas_cmd'] is None: + config['hnas_cmd'] = HNAS_DEFAULT_CONFIG['hnas_cmd'] config['hdp'] = {} config['services'] = {} @@ -122,15 +136,19 @@ def _read_config(xml_config_file): return config -def factory_bend(): +def factory_bend(drv_config): """Factory over-ride in self-tests.""" - return hnas_backend.HnasBackend() + return hnas_backend.HnasBackend(drv_config) class HDSNFSDriver(nfs.NfsDriver): """Base class for Hitachi NFS driver. - Executes commands relating to Volumes. + Executes commands relating to Volumes. + + Version 1.0.0: Initial driver version + Version 2.2.0: Added support to SSH authentication + """ def __init__(self, *args, **kwargs): @@ -145,8 +163,7 @@ class HDSNFSDriver(nfs.NfsDriver): self.configuration.hds_hnas_nfs_config_file) super(HDSNFSDriver, self).__init__(*args, **kwargs) - self.bend = factory_bend() - (self.arid, self.nfs_name, self.lumax) = self._array_info_get() + self.bend = factory_bend(self.config) def _array_info_get(self): """Get array parameters."""