'usedMiB': 256},
'SDGrowth': {'LDLayout': {'RAIDType': 4,
'diskPatterns': [{'diskType': 2}]},
- 'incrementMiB': 32768},
+ 'incrementMiB': 32768,
+ 'limitMiB': 1024000},
'SDUsage': {'rawTotalMiB': 49152,
'rawUsedMiB': 1023,
'totalMiB': 36864,
class TestHP3PARDriver(test.TestCase):
TARGET_IQN = "iqn.2000-05.com.3pardata:21810002ac00383d"
+ VOLUME_ID = "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"
VOLUME_NAME = "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7"
+ SNAPSHOT_ID = "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31"
SNAPSHOT_NAME = "snapshot-2f823bdc-e36e-4dc8-bd15-de1c7a28ff31"
VOLUME_3PAR_NAME = "osv-0DM4qZEVSKON-DXN-NwVpw"
- SNAPSHOT_VOL_NAME = "oss-L4I73ONuTci9Fd4ceij-MQ"
+ SNAPSHOT_3PAR_NAME = "oss-L4I73ONuTci9Fd4ceij-MQ"
FAKE_HOST = "fakehost"
_hosts = {}
self.fake_delete_3par_host)
self.stubs.Set(hpdriver.HP3PARCommon, "_create_3par_vlun",
self.fake_create_3par_vlun)
+ self.stubs.Set(hpdriver.HP3PARCommon, "get_ports",
+ self.fake_get_ports)
self.driver = hpdriver.HP3PARISCSIDriver()
self.driver.do_setup(None)
self.volume = {'name': self.VOLUME_NAME,
+ 'id': self.VOLUME_ID,
'display_name': 'Foo Volume',
- 'size': 1,
+ 'size': 2,
'host': self.FAKE_HOST}
user_id = '2689d9a913974c008b1d859013f23607'
project_id = 'fac88235b9d64685a3530f73e490348f'
volume_id = '761fc5e5-5191-4ec7-aeba-33e36de44156'
fake_desc = 'test description name'
- self.snapshot = type('snapshot',
- (object,),
- {'name': self.SNAPSHOT_NAME,
- 'user_id': user_id,
- 'project_id': project_id,
- 'volume_id': volume_id,
- 'volume_name': self.VOLUME_NAME,
- 'status': 'creating',
- 'progress': '0%',
- 'volume_size': 2,
- 'display_name': 'fakesnap',
- 'display_description': fake_desc})()
+ fake_fc_ports = ['0987654321234', '123456789000987']
+ fake_iscsi_ports = ['10.10.10.10', '10.10.10.11']
+ self.snapshot = {'name': self.SNAPSHOT_NAME,
+ 'id': self.SNAPSHOT_ID,
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'volume_id': volume_id,
+ 'volume_name': self.VOLUME_NAME,
+ 'status': 'creating',
+ 'progress': '0%',
+ 'volume_size': 2,
+ 'display_name': 'fakesnap',
+ 'display_description': fake_desc}
self.connector = {'ip': '10.0.0.2',
'initiator': 'iqn.1993-08.org.debian:01:222',
'host': 'fakehost'}
'target_lun': 186,
'target_portal': '1.1.1.2:1234'},
'driver_volume_type': 'iscsi'}
+ self.stats = {'driver_version': '1.0',
+ 'free_capacity_gb': 968,
+ 'reserved_percentage': 0,
+ 'storage_protocol': 'iSCSI',
+ 'total_capacity_gb': 1000,
+ 'vendor_name': 'Hewlett-Packard',
+ 'volume_backend_name': 'HP3PARISCSIDriver'}
def tearDown(self):
shutil.rmtree(self.tempdir)
def fake_create_3par_vlun(self, volume, hostname):
self.driver.client.createVLUN(volume, 19, hostname)
+ def fake_get_ports(self):
+ return {'FC': self.fake_fc_ports, 'iSCSI': self.fake_iscsi_ports}
+
def test_create_volume(self):
self.flags(lock_path=self.tempdir)
model_update = self.driver.create_volume(self.volume)
self.driver.client.getVolume,
self.VOLUME_NAME)
+ def test_get_volume_stats(self):
+ self.flags(lock_path=self.tempdir)
+ vol_stats = self.driver.get_volume_stats(True)
+ self.assertEqual(vol_stats['driver_version'],
+ self.stats['driver_version'])
+ self.assertEqual(vol_stats['free_capacity_gb'],
+ self.stats['free_capacity_gb'])
+ self.assertEqual(vol_stats['reserved_percentage'],
+ self.stats['reserved_percentage'])
+ self.assertEqual(vol_stats['storage_protocol'],
+ self.stats['storage_protocol'])
+ self.assertEqual(vol_stats['vendor_name'],
+ self.stats['vendor_name'])
+ self.assertEqual(vol_stats['volume_backend_name'],
+ self.stats['volume_backend_name'])
+
def test_create_snapshot(self):
self.flags(lock_path=self.tempdir)
self.driver.create_snapshot(self.snapshot)
# check to see if the snapshot was created
- snap_vol = self.driver.client.getVolume(self.SNAPSHOT_VOL_NAME)
- self.assertEqual(snap_vol['name'], self.SNAPSHOT_VOL_NAME)
+ snap_vol = self.driver.client.getVolume(self.SNAPSHOT_3PAR_NAME)
+ self.assertEqual(snap_vol['name'], self.SNAPSHOT_3PAR_NAME)
def test_delete_snapshot(self):
self.flags(lock_path=self.tempdir)
# the snapshot should be deleted now
self.assertRaises(hpexceptions.HTTPNotFound,
self.driver.client.getVolume,
- self.SNAPSHOT_VOL_NAME)
+ self.SNAPSHOT_NAME)
def test_create_volume_from_snapshot(self):
self.flags(lock_path=self.tempdir)
self.driver.create_volume_from_snapshot(self.volume, self.snapshot)
- snap_vol = self.driver.client.getVolume(self.SNAPSHOT_VOL_NAME)
- self.assertEqual(snap_vol['name'], self.SNAPSHOT_VOL_NAME)
+ snap_vol = self.driver.client.getVolume(self.SNAPSHOT_3PAR_NAME)
+ self.assertEqual(snap_vol['name'], self.SNAPSHOT_3PAR_NAME)
def test_initialize_connection(self):
self.flags(lock_path=self.tempdir)
class HP3PARCommon():
+ stats = {'driver_version': '1.0',
+ 'free_capacity_gb': 'unknown',
+ 'reserved_percentage': 0,
+ 'storage_protocol': None,
+ 'total_capacity_gb': 'unknown',
+ 'vendor_name': 'Hewlett-Packard',
+ 'volume_backend_name': None}
+
def __init__(self):
self.sshpool = None
if not getattr(FLAGS, flag, None):
raise exception.InvalidInput(reason=_('%s is not set') % flag)
- def _get_3par_vol_name(self, name):
+ def _get_3par_vol_name(self, volume_id):
"""
- Converts the openstack volume name from
- volume-ecffc30f-98cb-4cf5-85ee-d7309cc17cd2
+ Converts the openstack volume id from
+ ecffc30f-98cb-4cf5-85ee-d7309cc17cd2
to
osv-7P.DD5jLTPWF7tcwnMF80g
We strip the padding '=' and replace + with .
and / with -
"""
- name = name.replace("volume-", "")
- volume_name = self._encode_name(name)
+ volume_name = self._encode_name(volume_id)
return "osv-%s" % volume_name
- def _get_3par_snap_name(self, name):
- name = name.replace("snapshot-", "")
- snapshot_name = self._encode_name(name)
+ def _get_3par_snap_name(self, snapshot_id):
+ snapshot_name = self._encode_name(snapshot_id)
return "oss-%s" % snapshot_name
def _encode_name(self, name):
# 3par doesn't allow +, nor /
vol_encoded = vol_encoded.replace('+', '.')
vol_encoded = vol_encoded.replace('/', '-')
- #strip off the == as 3par doesn't like those.
+ # strip off the == as 3par doesn't like those.
vol_encoded = vol_encoded.replace('=', '')
return vol_encoded
exit
''' % cmd)
- #stdin.write('process_input would go here')
- #stdin.flush()
+ # stdin.write('process_input would go here')
+ # stdin.flush()
# NOTE(justinsb): This seems suspicious...
# ...other SSH clients have buffering issues with this approach
# couldn't find it
index = len(hostname)
- #we'll just chop this off for now.
+ # we'll just chop this off for now.
if index > 23:
index = 23
return host
+ def get_ports(self):
+ # First get the active FC ports
+ out = self._cli_run('showport', None)
+
+ # strip out header
+ # N:S:P,Mode,State,----Node_WWN----,-Port_WWN/HW_Addr-,Type,
+ # Protocol,Label,Partner,FailoverState
+ out = out[1:len(out) - 2]
+
+ ports = {'FC': [], 'iSCSI': []}
+ for line in out:
+ tmp = line.split(',')
+
+ if tmp:
+ if tmp[1] == 'target' and tmp[2] == 'ready':
+ if tmp[6] == 'FC':
+ port = {'wwn': tmp[4], 'nsp': tmp[0]}
+ ports['FC'].append(port)
+
+ # now get the active iSCSI ports
+ out = self._cli_run('showport -iscsi', None)
+
+ # strip out header
+ # N:S:P,State,IPAddr,Netmask,Gateway,
+ # TPGT,MTU,Rate,DHCP,iSNS_Addr,iSNS_Port
+ out = out[1:len(out) - 2]
+ for line in out:
+ tmp = line.split(',')
+
+ if tmp:
+ if tmp[1] == 'ready':
+ port = {'ip': tmp[2], 'nsp': tmp[0]}
+ ports['iSCSI'].append(port)
+
+ LOG.debug("PORTS = %s" % pprint.pformat(ports))
+ return ports
+
+ def get_volume_stats(self, refresh, client):
+ # const to convert MiB to GB
+ const = 0.0009765625
+
+ if refresh:
+ try:
+ cpg = client.getCPG(FLAGS.hp3par_cpg)
+ if 'limitMiB' not in cpg['SDGrowth']:
+ total_capacity = 'infinite'
+ free_capacity = 'infinite'
+ else:
+ total_capacity = int(cpg['SDGrowth']['limitMiB'] * const)
+ free_capacity = int((cpg['SDGrowth']['limitMiB'] -
+ cpg['UsrUsage']['usedMiB']) * const)
+
+ self.stats['total_capacity_gb'] = total_capacity
+ self.stats['free_capacity_gb'] = free_capacity
+ except hpexceptions.HTTPNotFound:
+ err = _("CPG (%s) doesn't exist on array") % FLAGS.hp3par_cpg
+ LOG.error(err)
+ raise exception.InvalidInput(reason=err)
+
+ return self.stats
+
def create_vlun(self, volume, host, client):
"""
In order to export a volume on a 3PAR box, we have to
create a VLUN.
"""
- volume_name = self._get_3par_vol_name(volume['name'])
+ volume_name = self._get_3par_vol_name(volume['id'])
self._create_3par_vlun(volume_name, host['name'])
return client.getVLUN(volume_name)
def delete_vlun(self, volume, connector, client):
hostname = self._safe_hostname(connector['host'])
- volume_name = self._get_3par_vol_name(volume['name'])
+ volume_name = self._get_3par_vol_name(volume['id'])
vlun = client.getVLUN(volume_name)
client.deleteVLUN(volume_name, vlun['lun'], hostname)
self._delete_3par_host(hostname)
""" Create a new volume """
LOG.debug("CREATE VOLUME (%s : %s %s)" %
(volume['display_name'], volume['name'],
- self._get_3par_vol_name(volume['name'])))
+ self._get_3par_vol_name(volume['id'])))
try:
- comments = {'name': volume['name'],
- 'display_name': volume['display_name'],
+ comments = {'volume_id': volume['id'],
+ 'name': volume['name'],
'type': 'OpenStack'}
+
+ name = volume.get('display_name', None)
+ if name:
+ comments['display_name'] = name
+
extras = {'comment': json.dumps(comments),
'snapCPG': FLAGS.hp3par_cpg_snap}
extras['snapCPG'] = FLAGS.hp3par_cpg
capacity = self._capacity_from_size(volume['size'])
- volume_name = self._get_3par_vol_name(volume['name'])
+ volume_name = self._get_3par_vol_name(volume['id'])
client.createVolume(volume_name, FLAGS.hp3par_cpg,
capacity, extras)
LOG.error(str(ex))
raise exception.CinderException(ex.get_description())
+ metadata = {'3ParName': volume_name, 'CPG': FLAGS.hp3par_cpg,
+ 'snapCPG': extras['snapCPG']}
+ return metadata
+
@lockutils.synchronized('3par', 'cinder-', True)
def delete_volume(self, volume, client):
""" Delete a volume """
try:
- volume_name = self._get_3par_vol_name(volume['name'])
+ volume_name = self._get_3par_vol_name(volume['id'])
client.deleteVolume(volume_name)
except hpexceptions.HTTPNotFound as ex:
+ # We'll let this act as if it worked
+ # it helps clean up the cinder entries.
LOG.error(str(ex))
- raise exception.NotFound(ex.get_description())
except hpexceptions.HTTPForbidden as ex:
LOG.error(str(ex))
raise exception.NotAuthorized(ex.get_description())
"""
LOG.debug("Create Volume from Snapshot\n%s\n%s" %
(pprint.pformat(volume['display_name']),
- pprint.pformat(snapshot.display_name)))
+ pprint.pformat(snapshot['display_name'])))
+
+ if snapshot['volume_size'] != volume['size']:
+ err = "You cannot change size of the volume. It must \
+be the same as it's Snapshot."
+ LOG.error(err)
+ raise exception.InvalidInput(reason=err)
+
try:
- snap_name = self._get_3par_snap_name(snapshot.name)
- vol_name = self._get_3par_vol_name(volume['name'])
+ snap_name = self._get_3par_snap_name(snapshot['id'])
+ vol_name = self._get_3par_vol_name(volume['id'])
+
+ extra = {'volume_id': volume['id'],
+ 'snapshot_id': snapshot['id']}
+ name = snapshot.get('display_name', None)
+ if name:
+ extra['name'] = name
- extra = {'name': snapshot.display_name,
- 'description': snapshot.display_description}
+ description = snapshot.get('display_description', None)
+ if description:
+ extra['description'] = description
optional = {'comment': json.dumps(extra),
'readOnly': False}
client.createSnapshot(vol_name, snap_name, optional)
- except hpexceptions.HTTPForbidden as ex:
+ except hpexceptions.HTTPForbidden:
raise exception.NotAuthorized()
- except hpexceptions.HTTPNotFound as ex:
+ except hpexceptions.HTTPNotFound:
raise exception.NotFound()
@lockutils.synchronized('3par', 'cinder-', True)
LOG.debug("Create Snapshot\n%s" % pprint.pformat(snapshot))
try:
- snap_name = self._get_3par_snap_name(snapshot.name)
- vol_name = self._get_3par_vol_name(snapshot.volume_name)
+ snap_name = self._get_3par_snap_name(snapshot['id'])
+ vol_name = self._get_3par_vol_name(snapshot['volume_id'])
+
+ extra = {'volume_name': snapshot['volume_name']}
+ vol_id = snapshot.get('volume_id', None)
+ if vol_id:
+ extra['volume_id'] = vol_id
- extra = {'name': snapshot.display_name,
- 'vol_name': snapshot.volume_name,
- 'description': snapshot.display_description}
+ try:
+ extra['name'] = snapshot['display_name']
+ except AttribteError:
+ pass
+
+ try:
+ extra['description'] = snapshot['display_description']
+ except AttribteError:
+ pass
optional = {'comment': json.dumps(extra),
'readOnly': True}
LOG.debug("Delete Snapshot\n%s" % pprint.pformat(snapshot))
try:
- snap_name = self._get_3par_snap_name(snapshot.name)
+ snap_name = self._get_3par_snap_name(snapshot['id'])
client.deleteVolume(snap_name)
except hpexceptions.HTTPForbidden:
raise exception.NotAuthorized()
- except hpexceptions.HTTPNotFound:
- raise exception.NotFound()
+ except hpexceptions.HTTPNotFound as ex:
+ LOG.error(str(ex))