From 96ef263f61385f486402c50f3f3abc7e614a8b49 Mon Sep 17 00:00:00 2001 From: Jim Branen Date: Fri, 6 Dec 2013 15:32:53 -0800 Subject: [PATCH] 3PAR FC: add ability to add WWNs to host When a host is created on the HP 3PAR array, at initialize connection time, the host is created with all WWNs provided in the connector structure. However, the driver did not have the ability to add new WWNs after a host is created. This patch adds the ability to add WWNs to a host, at initialize connection time, if a new WWN is provided in the connector structure. Change-Id: I6fd8a5511f83d5460da30ff5558a3e95964a95f5 Closes-bug: 1258229 --- cinder/tests/test_hp3par.py | 93 +++++++++++++++++++++- cinder/volume/drivers/san/hp/hp_3par_fc.py | 35 +++++++- 2 files changed, 120 insertions(+), 8 deletions(-) diff --git a/cinder/tests/test_hp3par.py b/cinder/tests/test_hp3par.py index 80f28e65cee..e4e2b293e44 100644 --- a/cinder/tests/test_hp3par.py +++ b/cinder/tests/test_hp3par.py @@ -19,6 +19,7 @@ Unit tests for OpenStack Cinder volume drivers """ import ast +import mock import mox import shutil import tempfile @@ -787,7 +788,10 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): '123456789054321']) _run_ssh(create_host_cmd, False).AndReturn([CLI_CR, '']) - getHost('fakehost').AndReturn({'name': self.FAKE_HOST}) + getHost('fakehost').AndReturn({'name': self.FAKE_HOST, + 'FCPaths': [{'wwn': '123456789012345'}, + {'wwn': '123456789054321'}]} + ) self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) @@ -818,7 +822,12 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): 'already used by host fakehost.foo (19)') _run_ssh(create_host_cmd, False).AndReturn([create_host_ret, '']) - getHost('fakehost.foo').AndReturn({'name': 'fakehost.foo'}) + host_ret = { + 'name': 'fakehost.foo', + 'FCPaths': [{'wwn': '123456789012345'}, + {'wwn': '123456789054321'}]} + getHost('fakehost.foo').AndReturn(host_ret) + self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) @@ -849,8 +858,8 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): 'pathOperation': 1}) getHost('fakehost').AndReturn({'name': self.FAKE_HOST, - 'FCPaths': [{'WWN': '123456789012345'}, - {'WWN': '123456789054321'}]} + 'FCPaths': [{'wwn': '123456789012345'}, + {'wwn': '123456789054321'}]} ) self.mox.ReplayAll() @@ -859,6 +868,82 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): self.assertEqual(host['name'], self.FAKE_HOST) self.assertEqual(len(host['FCPaths']), 2) + def test_modify_host_with_new_wwn(self): + self.flags(lock_path=self.tempdir) + self.clear_mox() + + hpdriver.hpcommon.HP3PARCommon.get_cpg = mock.Mock( + return_value=self.fake_get_cpg) + hpdriver.hpcommon.HP3PARCommon.get_domain = mock.Mock( + return_value=self.fake_get_domain) + + # set up the getHost mock + self.driver.common.client.getHost = mock.Mock() + # define the return values for the 2 calls + getHost_ret1 = { + 'name': self.FAKE_HOST, + 'FCPaths': [{'wwn': '123456789054321'}]} + getHost_ret2 = { + 'name': self.FAKE_HOST, + 'FCPaths': [{'wwn': '123456789012345'}, + {'wwn': '123456789054321'}]} + self.driver.common.client.getHost.side_effect = [ + getHost_ret1, getHost_ret2] + + # setup the modifyHost mock + self.driver.common.client.modifyHost = mock.Mock() + + host = self.driver._create_host(self.volume, self.connector) + + # mock assertions + self.driver.common.client.getHost.assert_has_calls([ + mock.call('fakehost'), + mock.call('fakehost')]) + self.driver.common.client.modifyHost.assert_called_once_with( + 'fakehost', {'FCWWNs': ['123456789012345'], 'pathOperation': 1}) + + self.assertEqual(host['name'], self.FAKE_HOST) + self.assertEqual(len(host['FCPaths']), 2) + + def test_modify_host_with_unknown_wwn_and_new_wwn(self): + self.flags(lock_path=self.tempdir) + self.clear_mox() + + hpdriver.hpcommon.HP3PARCommon.get_cpg = mock.Mock( + return_value=self.fake_get_cpg) + hpdriver.hpcommon.HP3PARCommon.get_domain = mock.Mock( + return_value=self.fake_get_domain) + + # set up the getHost mock + self.driver.common.client.getHost = mock.Mock() + # define the return values for the 2 calls + getHost_ret1 = { + 'name': self.FAKE_HOST, + 'FCPaths': [{'wwn': '123456789054321'}, + {'wwn': 'xxxxxxxxxxxxxxx'}]} + getHost_ret2 = { + 'name': self.FAKE_HOST, + 'FCPaths': [{'wwn': '123456789012345'}, + {'wwn': '123456789054321'}, + {'wwn': 'xxxxxxxxxxxxxxx'}]} + self.driver.common.client.getHost.side_effect = [ + getHost_ret1, getHost_ret2] + + # setup the modifyHost mock + self.driver.common.client.modifyHost = mock.Mock() + + host = self.driver._create_host(self.volume, self.connector) + + # mock assertions + self.driver.common.client.getHost.assert_has_calls([ + mock.call('fakehost'), + mock.call('fakehost')]) + self.driver.common.client.modifyHost.assert_called_once_with( + 'fakehost', {'FCWWNs': ['123456789012345'], 'pathOperation': 1}) + + self.assertEqual(host['name'], self.FAKE_HOST) + self.assertEqual(len(host['FCPaths']), 3) + class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): diff --git a/cinder/volume/drivers/san/hp/hp_3par_fc.py b/cinder/volume/drivers/san/hp/hp_3par_fc.py index 39f605da7e8..4e11be05d57 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_fc.py +++ b/cinder/volume/drivers/san/hp/hp_3par_fc.py @@ -56,9 +56,10 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): the drivers to use the new APIs. 1.2.1 - Synchronized extend_volume method. 1.2.2 - Added try/finally around client login/logout. + 1.2.3 - Added ability to add WWNs to host. """ - VERSION = "1.2.2" + VERSION = "1.2.3" def __init__(self, *args, **kwargs): super(HP3PARFCDriver, self).__init__(*args, **kwargs) @@ -261,9 +262,6 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): domain = self.common.get_domain(cpg) try: host = self.common._get_3par_host(hostname) - if 'FCPaths' not in host or len(host['FCPaths']) < 1: - self._modify_3par_fibrechan_host(hostname, connector['wwpns']) - host = self.common._get_3par_host(hostname) except hpexceptions.HTTPNotFound as ex: # get persona from the volume type extra specs persona_id = self.common.get_persona_type(volume) @@ -274,6 +272,35 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): persona_id) host = self.common._get_3par_host(hostname) + return self._add_new_wwn_to_host(host, connector['wwpns']) + + def _add_new_wwn_to_host(self, host, wwns): + """Add wwns to a host if one or more don't exist. + + Identify if argument wwns contains any world wide names + not configured in the 3PAR host path. If any are found, + add them to the 3PAR host. + """ + # get the currently configured wwns + # from the host's FC paths + host_wwns = [] + if 'FCPaths' in host: + for path in host['FCPaths']: + wwn = path.get('wwn', None) + if wwn is not None: + host_wwns.append(wwn.lower()) + + # lower case all wwns in the compare list + compare_wwns = [x.lower() for x in wwns] + + # calculate wwns in compare list, but not in host_wwns list + new_wwns = list(set(compare_wwns).difference(host_wwns)) + + # if any wwns found that were not in host list, + # add them to the host + if (len(new_wwns) > 0): + self._modify_3par_fibrechan_host(host['name'], new_wwns) + host = self.common._get_3par_host(host['name']) return host @utils.synchronized('3par', external=True)