Update SolidFire volume driver

Implements blueprint update-solidfire-driver

* Updates driver to reflect changes in the release version of SF API
* Modify SF naming scheme
* Implement snapshot functionality
* Implement setting qos on create via metadata
* Update/Add tests

Change-Id: I08f7aac31e9d95f971d297a19c285dfa7151b931
This commit is contained in:
John Griffith 2012-08-13 18:35:35 -06:00
parent 87ba5de73e
commit df5c4ba864
3 changed files with 508 additions and 304 deletions

View File

@ -17,8 +17,8 @@
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.volume import san
from cinder import test
from cinder.volume.solidfire import SolidFire
LOG = logging.getLogger(__name__)
@ -31,12 +31,12 @@ class SolidFireVolumeTestCase(test.TestCase):
if method is 'GetClusterInfo':
LOG.info('Called Fake GetClusterInfo...')
results = {'result': {'clusterInfo':
{'name': 'fake-cluster',
'mvip': '1.1.1.1',
'svip': '1.1.1.1',
'uniqueID': 'unqid',
'repCount': 2,
'attributes': {}}}}
{'name': 'fake-cluster',
'mvip': '1.1.1.1',
'svip': '1.1.1.1',
'uniqueID': 'unqid',
'repCount': 2,
'attributes': {}}}}
return results
elif method is 'AddAccount':
@ -45,15 +45,15 @@ class SolidFireVolumeTestCase(test.TestCase):
elif method is 'GetAccountByName':
LOG.info('Called Fake GetAccountByName...')
results = {'result': {'account': {
'accountID': 25,
'username': params['username'],
'status': 'active',
'initiatorSecret': '123456789012',
'targetSecret': '123456789012',
'attributes': {},
'volumes': [6, 7, 20]}},
"id": 1}
results = {'result': {'account':
{'accountID': 25,
'username': params['username'],
'status': 'active',
'initiatorSecret': '123456789012',
'targetSecret': '123456789012',
'attributes': {},
'volumes': [6, 7, 20]}},
"id": 1}
return results
elif method is 'CreateVolume':
@ -65,46 +65,67 @@ class SolidFireVolumeTestCase(test.TestCase):
return {'result': {}, 'id': 1}
elif method is 'ListVolumesForAccount':
test_name = 'OS-VOLID-a720b3c0-d1f0-11e1-9b23-0800200c9a66'
LOG.info('Called Fake ListVolumesForAccount...')
result = {'result': {'volumes': [{
'volumeID': '5',
'name': 'test_volume',
'accountID': 25,
'sliceCount': 1,
'totalSize': 1048576 * 1024,
'enable512e': False,
'access': "readWrite",
'status': "active",
'attributes':None,
'qos':None}]}}
result = {'result': {
'volumes': [{'volumeID': 5,
'name': test_name,
'accountID': 25,
'sliceCount': 1,
'totalSize': 1048576 * 1024,
'enable512e': True,
'access': "readWrite",
'status': "active",
'attributes':None,
'qos': None,
'iqn': test_name}]}}
return result
else:
LOG.error('Crap, unimplemented API call in Fake:%s' % method)
def fake_issue_api_request_fails(obj, method, params):
return {'error': {
'code': 000,
'name': 'DummyError',
'message': 'This is a fake error response'},
'id': 1}
return {'error': {'code': 000,
'name': 'DummyError',
'message': 'This is a fake error response'},
'id': 1}
def fake_volume_get(obj, key, default=None):
return {'qos': 'fast'}
def test_create_volume(self):
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request)
testvol = {'project_id': 'testprjid',
'name': 'testvol',
'size': 1}
sfv = san.SolidFireSanISCSIDriver()
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
sfv = SolidFire()
model_update = sfv.create_volume(testvol)
def test_create_volume_with_qos(self):
preset_qos = {}
preset_qos['qos'] = 'fast'
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request)
testvol = {'project_id': 'testprjid',
'name': 'testvol',
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'metadata': [preset_qos]}
sfv = SolidFire()
model_update = sfv.create_volume(testvol)
def test_create_volume_fails(self):
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request_fails)
testvol = {'project_id': 'testprjid',
'name': 'testvol',
'size': 1}
sfv = san.SolidFireSanISCSIDriver()
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
sfv = SolidFire()
try:
sfv.create_volume(testvol)
self.fail("Should have thrown Error")
@ -112,49 +133,51 @@ class SolidFireVolumeTestCase(test.TestCase):
pass
def test_create_sfaccount(self):
sfv = san.SolidFireSanISCSIDriver()
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
sfv = SolidFire()
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request)
account = sfv._create_sfaccount('project-id')
self.assertNotEqual(account, None)
def test_create_sfaccount_fails(self):
sfv = san.SolidFireSanISCSIDriver()
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
sfv = SolidFire()
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request_fails)
account = sfv._create_sfaccount('project-id')
self.assertEqual(account, None)
def test_get_sfaccount_by_name(self):
sfv = san.SolidFireSanISCSIDriver()
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
sfv = SolidFire()
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request)
account = sfv._get_sfaccount_by_name('some-name')
self.assertNotEqual(account, None)
def test_get_sfaccount_by_name_fails(self):
sfv = san.SolidFireSanISCSIDriver()
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
sfv = SolidFire()
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request_fails)
account = sfv._get_sfaccount_by_name('some-name')
self.assertEqual(account, None)
def test_delete_volume(self):
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request)
testvol = {'project_id': 'testprjid',
'name': 'test_volume',
'size': 1}
sfv = san.SolidFireSanISCSIDriver()
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
sfv = SolidFire()
model_update = sfv.delete_volume(testvol)
def test_delete_volume_fails_no_volume(self):
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request)
testvol = {'project_id': 'testprjid',
'name': 'no-name',
'size': 1}
sfv = san.SolidFireSanISCSIDriver()
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
sfv = SolidFire()
try:
model_update = sfv.delete_volume(testvol)
self.fail("Should have thrown Error")
@ -162,25 +185,26 @@ class SolidFireVolumeTestCase(test.TestCase):
pass
def test_delete_volume_fails_account_lookup(self):
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
self.fake_issue_api_request)
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request_fails)
testvol = {'project_id': 'testprjid',
'name': 'no-name',
'size': 1}
sfv = san.SolidFireSanISCSIDriver()
self.assertRaises(exception.DuplicateSfVolumeNames,
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
sfv = SolidFire()
self.assertRaises(exception.SfAccountNotFound,
sfv.delete_volume,
testvol)
def test_get_cluster_info(self):
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request)
sfv = san.SolidFireSanISCSIDriver()
sfv = SolidFire()
sfv._get_cluster_info()
def test_get_cluster_info_fail(self):
self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
self.stubs.Set(SolidFire, '_issue_api_request',
self.fake_issue_api_request_fails)
sfv = san.SolidFireSanISCSIDriver()
sfv = SolidFire()
self.assertRaises(exception.SolidFireAPIException,
sfv._get_cluster_info)

View File

@ -644,246 +644,3 @@ class HpSanISCSIDriver(SanISCSIDriver):
cliq_args['volumeName'] = volume['name']
cliq_args['serverName'] = connector['host']
self._cliq_run_xml("unassignVolumeToServer", cliq_args)
class SolidFireSanISCSIDriver(SanISCSIDriver):
def _issue_api_request(self, method_name, params):
"""All API requests to SolidFire device go through this method
Simple json-rpc web based API calls.
each call takes a set of paramaters (dict)
and returns results in a dict as well.
"""
host = FLAGS.san_ip
# For now 443 is the only port our server accepts requests on
port = 443
# NOTE(john-griffith): Probably don't need this, but the idea is
# we provide a request_id so we can correlate
# responses with requests
request_id = int(uuid.uuid4()) # just generate a random number
cluster_admin = FLAGS.san_login
cluster_password = FLAGS.san_password
command = {'method': method_name,
'id': request_id}
if params is not None:
command['params'] = params
payload = jsonutils.dumps(command, ensure_ascii=False)
payload.encode('utf-8')
# we use json-rpc, webserver needs to see json-rpc in header
header = {'Content-Type': 'application/json-rpc; charset=utf-8'}
if cluster_password is not None:
# base64.encodestring includes a newline character
# in the result, make sure we strip it off
auth_key = base64.encodestring('%s:%s' % (cluster_admin,
cluster_password))[:-1]
header['Authorization'] = 'Basic %s' % auth_key
LOG.debug(_("Payload for SolidFire API call: %s"), payload)
connection = httplib.HTTPSConnection(host, port)
connection.request('POST', '/json-rpc/1.0', payload, header)
response = connection.getresponse()
data = {}
if response.status != 200:
connection.close()
raise exception.SolidFireAPIException(status=response.status)
else:
data = response.read()
try:
data = jsonutils.loads(data)
except (TypeError, ValueError), exc:
connection.close()
msg = _("Call to json.loads() raised an exception: %s") % exc
raise exception.SfJsonEncodeFailure(msg)
connection.close()
LOG.debug(_("Results of SolidFire API call: %s"), data)
return data
def _get_volumes_by_sfaccount(self, account_id):
params = {'accountID': account_id}
data = self._issue_api_request('ListVolumesForAccount', params)
if 'result' in data:
return data['result']['volumes']
def _get_sfaccount_by_name(self, sf_account_name):
sfaccount = None
params = {'username': sf_account_name}
data = self._issue_api_request('GetAccountByName', params)
if 'result' in data and 'account' in data['result']:
LOG.debug(_('Found solidfire account: %s'), sf_account_name)
sfaccount = data['result']['account']
return sfaccount
def _create_sfaccount(self, cinder_project_id):
"""Create account on SolidFire device if it doesn't already exist.
We're first going to check if the account already exits, if it does
just return it. If not, then create it.
"""
sf_account_name = socket.gethostname() + '-' + cinder_project_id
sfaccount = self._get_sfaccount_by_name(sf_account_name)
if sfaccount is None:
LOG.debug(_('solidfire account: %s does not exist, create it...'),
sf_account_name)
chap_secret = self._generate_random_string(12)
params = {'username': sf_account_name,
'initiatorSecret': chap_secret,
'targetSecret': chap_secret,
'attributes': {}}
data = self._issue_api_request('AddAccount', params)
if 'result' in data:
sfaccount = self._get_sfaccount_by_name(sf_account_name)
return sfaccount
def _get_cluster_info(self):
params = {}
data = self._issue_api_request('GetClusterInfo', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
return data['result']
def _do_export(self, volume):
"""Gets the associated account, retrieves CHAP info and updates."""
sfaccount_name = '%s-%s' % (socket.gethostname(), volume['project_id'])
sfaccount = self._get_sfaccount_by_name(sfaccount_name)
model_update = {}
model_update['provider_auth'] = ('CHAP %s %s'
% (sfaccount['username'], sfaccount['targetSecret']))
return model_update
def _generate_random_string(self, length):
"""Generates random_string to use for CHAP password."""
char_set = string.ascii_uppercase + string.digits
return ''.join(random.sample(char_set, length))
def create_volume(self, volume):
"""Create volume on SolidFire device.
The account is where CHAP settings are derived from, volume is
created and exported. Note that the new volume is immediately ready
for use.
One caveat here is that an existing user account must be specified
in the API call to create a new volume. We use a set algorithm to
determine account info based on passed in cinder volume object. First
we check to see if the account already exists (and use it), or if it
does not already exist, we'll go ahead and create it.
For now, we're just using very basic settings, QOS is
turned off, 512 byte emulation is off etc. Will be
looking at extensions for these things later, or
this module can be hacked to suit needs.
"""
LOG.debug(_("Enter SolidFire create_volume..."))
GB = 1048576 * 1024
slice_count = 1
enable_emulation = False
attributes = {}
cluster_info = self._get_cluster_info()
iscsi_portal = cluster_info['clusterInfo']['svip'] + ':3260'
sfaccount = self._create_sfaccount(volume['project_id'])
account_id = sfaccount['accountID']
account_name = sfaccount['username']
chap_secret = sfaccount['targetSecret']
params = {'name': volume['name'],
'accountID': account_id,
'sliceCount': slice_count,
'totalSize': volume['size'] * GB,
'enable512e': enable_emulation,
'attributes': attributes}
data = self._issue_api_request('CreateVolume', params)
if 'result' not in data or 'volumeID' not in data['result']:
raise exception.SolidFireAPIDataException(data=data)
volume_id = data['result']['volumeID']
volume_list = self._get_volumes_by_sfaccount(account_id)
iqn = None
for v in volume_list:
if v['volumeID'] == volume_id:
iqn = 'iqn.2010-01.com.solidfire:' + v['iqn']
break
model_update = {}
# NOTE(john-griffith): SF volumes are always at lun 0
model_update['provider_location'] = ('%s %s %s'
% (iscsi_portal, iqn, 0))
model_update['provider_auth'] = ('CHAP %s %s'
% (account_name, chap_secret))
LOG.debug(_("Leaving SolidFire create_volume"))
return model_update
def delete_volume(self, volume):
"""Delete SolidFire Volume from device.
SolidFire allows multipe volumes with same name,
volumeID is what's guaranteed unique.
What we'll do here is check volumes based on account. this
should work because cinder will increment its volume_id
so we should always get the correct volume. This assumes
that cinder does not assign duplicate ID's.
"""
LOG.debug(_("Enter SolidFire delete_volume..."))
sf_account_name = socket.gethostname() + '-' + volume['project_id']
sfaccount = self._get_sfaccount_by_name(sf_account_name)
if sfaccount is None:
raise exception.SfAccountNotFound(account_name=sf_account_name)
params = {'accountID': sfaccount['accountID']}
data = self._issue_api_request('ListVolumesForAccount', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
found_count = 0
volid = -1
for v in data['result']['volumes']:
if v['name'] == volume['name']:
found_count += 1
volid = v['volumeID']
if found_count != 1:
LOG.debug(_("Deleting volumeID: %s"), volid)
raise exception.DuplicateSfVolumeNames(vol_name=volume['name'])
params = {'volumeID': volid}
data = self._issue_api_request('DeleteVolume', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
LOG.debug(_("Leaving SolidFire delete_volume"))
def ensure_export(self, context, volume):
LOG.debug(_("Executing SolidFire ensure_export..."))
return self._do_export(volume)
def create_export(self, context, volume):
LOG.debug(_("Executing SolidFire create_export..."))
return self._do_export(volume)

423
cinder/volume/solidfire.py Normal file
View File

@ -0,0 +1,423 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# 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.
"""
Drivers for san-stored volumes.
The unique thing about a SAN is that we don't expect that we can run the volume
controller on the SAN hardware. We expect to access it over SSH or some API.
"""
import base64
import httplib
import json
import random
import socket
import string
import uuid
from cinder import exception
from cinder import flags
from cinder.openstack.common import cfg
from cinder.openstack.common import log as logging
from cinder.volume.san import SanISCSIDriver
LOG = logging.getLogger(__name__)
sf_opts = [
cfg.BoolOpt('sf_emulate_512',
default=True,
help='Set 512 byte emulation on volume creation; '),
cfg.StrOpt('sf_mvip',
default='',
help='IP address of SolidFire MVIP'),
cfg.StrOpt('sf_login',
default='admin',
help='Username for SF Cluster Admin'),
cfg.StrOpt('sf_password',
default='',
help='Password for SF Cluster Admin'),
cfg.StrOpt('sf_allow_tenant_qos',
default=True,
help='Allow tenants to specify QOS on create'), ]
FLAGS = flags.FLAGS
FLAGS.register_opts(sf_opts)
class SolidFire(SanISCSIDriver):
sf_qos_dict = {'slow': {'minIOPS': 100,
'maxIOPS': 200,
'burstIOPS': 200},
'medium': {'minIOPS': 200,
'maxIOPS': 400,
'burstIOPS': 400},
'fast': {'minIOPS': 500,
'maxIOPS': 1000,
'burstIOPS': 1000},
'performant': {'minIOPS': 2000,
'maxIOPS': 4000,
'burstIOPS': 4000},
'off': None}
def __init__(self, *args, **kwargs):
super(SolidFire, self).__init__(*args, **kwargs)
def _issue_api_request(self, method_name, params):
"""All API requests to SolidFire device go through this method
Simple json-rpc web based API calls.
each call takes a set of paramaters (dict)
and returns results in a dict as well.
"""
host = FLAGS.san_ip
# For now 443 is the only port our server accepts requests on
port = 443
# NOTE(john-griffith): Probably don't need this, but the idea is
# we provide a request_id so we can correlate
# responses with requests
request_id = int(uuid.uuid4()) # just generate a random number
cluster_admin = FLAGS.san_login
cluster_password = FLAGS.san_password
command = {'method': method_name,
'id': request_id}
if params is not None:
command['params'] = params
payload = json.dumps(command, ensure_ascii=False)
payload.encode('utf-8')
# we use json-rpc, webserver needs to see json-rpc in header
header = {'Content-Type': 'application/json-rpc; charset=utf-8'}
if cluster_password is not None:
# base64.encodestring includes a newline character
# in the result, make sure we strip it off
auth_key = base64.encodestring('%s:%s' % (cluster_admin,
cluster_password))[:-1]
header['Authorization'] = 'Basic %s' % auth_key
LOG.debug(_("Payload for SolidFire API call: %s"), payload)
connection = httplib.HTTPSConnection(host, port)
connection.request('POST', '/json-rpc/1.0', payload, header)
response = connection.getresponse()
data = {}
if response.status != 200:
connection.close()
raise exception.SolidFireAPIException(status=response.status)
else:
data = response.read()
try:
data = json.loads(data)
except (TypeError, ValueError), exc:
connection.close()
msg = _("Call to json.loads() raised an exception: %s") % exc
raise exception.SfJsonEncodeFailure(msg)
connection.close()
LOG.debug(_("Results of SolidFire API call: %s"), data)
return data
def _get_volumes_by_sfaccount(self, account_id):
params = {'accountID': account_id}
data = self._issue_api_request('ListVolumesForAccount', params)
if 'result' in data:
return data['result']['volumes']
def _get_sfaccount_by_name(self, sf_account_name):
sfaccount = None
params = {'username': sf_account_name}
data = self._issue_api_request('GetAccountByName', params)
if 'result' in data and 'account' in data['result']:
LOG.debug(_('Found solidfire account: %s'), sf_account_name)
sfaccount = data['result']['account']
return sfaccount
def _create_sfaccount(self, cinder_project_id):
"""Create account on SolidFire device if it doesn't already exist.
We're first going to check if the account already exits, if it does
just return it. If not, then create it.
"""
sf_account_name = socket.gethostname() + '-' + cinder_project_id
sfaccount = self._get_sfaccount_by_name(sf_account_name)
if sfaccount is None:
LOG.debug(_('solidfire account: %s does not exist, create it...'),
sf_account_name)
chap_secret = self._generate_random_string(12)
params = {'username': sf_account_name,
'initiatorSecret': chap_secret,
'targetSecret': chap_secret,
'attributes': {}}
data = self._issue_api_request('AddAccount', params)
if 'result' in data:
sfaccount = self._get_sfaccount_by_name(sf_account_name)
return sfaccount
def _get_cluster_info(self):
params = {}
data = self._issue_api_request('GetClusterInfo', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
return data['result']
def _do_export(self, volume):
"""Gets the associated account, retrieves CHAP info and updates."""
sfaccount_name = '%s-%s' % (socket.gethostname(), volume['project_id'])
sfaccount = self._get_sfaccount_by_name(sfaccount_name)
model_update = {}
model_update['provider_auth'] = ('CHAP %s %s'
% (sfaccount['username'],
sfaccount['targetSecret']))
return model_update
def _generate_random_string(self, length):
"""Generates random_string to use for CHAP password."""
char_set = string.ascii_uppercase + string.digits
return ''.join(random.sample(char_set, length))
def _do_volume_create(self, project_id, params):
cluster_info = self._get_cluster_info()
iscsi_portal = cluster_info['clusterInfo']['svip'] + ':3260'
sfaccount = self._create_sfaccount(project_id)
chap_secret = sfaccount['targetSecret']
params['accountID'] = sfaccount['accountID']
data = self._issue_api_request('CreateVolume', params)
if 'result' not in data or 'volumeID' not in data['result']:
raise exception.SolidFireAPIDataException(data=data)
volume_id = data['result']['volumeID']
volume_list = self._get_volumes_by_sfaccount(sfaccount['accountID'])
iqn = None
for v in volume_list:
if v['volumeID'] == volume_id:
iqn = 'iqn.2010-01.com.solidfire:' + v['iqn']
break
model_update = {}
# NOTE(john-griffith): SF volumes are always at lun 0
model_update['provider_location'] = ('%s %s %s'
% (iscsi_portal, iqn, 0))
model_update['provider_auth'] = ('CHAP %s %s'
% (sfaccount['username'],
chap_secret))
return model_update
def create_volume(self, volume):
"""Create volume on SolidFire device.
The account is where CHAP settings are derived from, volume is
created and exported. Note that the new volume is immediately ready
for use.
One caveat here is that an existing user account must be specified
in the API call to create a new volume. We use a set algorithm to
determine account info based on passed in cinder volume object. First
we check to see if the account already exists (and use it), or if it
does not already exist, we'll go ahead and create it.
For now, we're just using very basic settings, QOS is
turned off, 512 byte emulation is off etc. Will be
looking at extensions for these things later, or
this module can be hacked to suit needs.
"""
GB = 1048576 * 1024
slice_count = 1
attributes = {}
qos = {}
qos_keys = ['minIOPS', 'maxIOPS', 'burstIOPS']
valid_presets = self.sf_qos_dict.keys()
if FLAGS.sf_allow_tenant_qos and \
volume.get('volume_metadata')is not None:
#First look to see if they included a preset
presets = [i.value for i in volume.get('volume_metadata')
if i.key == 'sf-qos' and i.value in valid_presets]
if len(presets) > 0:
if len(presets) > 1:
LOG.warning(_('More than one valid preset was '
'detected, using %s' % presets[0]))
qos = self.sf_qos_dict[presets[0]]
else:
#if there was no preset, look for explicit settings
for i in volume.get('volume_metadata'):
if i.key in qos_keys:
qos[i.key] = int(i.value)
params = {'name': 'OS-VOLID-%s' % volume['id'],
'accountID': None,
'sliceCount': slice_count,
'totalSize': volume['size'] * GB,
'enable512e': FLAGS.sf_emulate_512,
'attributes': attributes,
'qos': qos}
return self._do_volume_create(volume['project_id'], params)
def delete_volume(self, volume, is_snapshot=False):
"""Delete SolidFire Volume from device.
SolidFire allows multipe volumes with same name,
volumeID is what's guaranteed unique.
"""
LOG.debug(_("Enter SolidFire delete_volume..."))
sf_account_name = socket.gethostname() + '-' + volume['project_id']
sfaccount = self._get_sfaccount_by_name(sf_account_name)
if sfaccount is None:
raise exception.SfAccountNotFound(account_name=sf_account_name)
params = {'accountID': sfaccount['accountID']}
data = self._issue_api_request('ListVolumesForAccount', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
if is_snapshot:
seek = 'OS-SNAPID-%s' % (volume['id'])
else:
seek = 'OS-VOLID-%s' % volume['id']
#params = {'name': 'OS-VOLID-:%s' % volume['id'],
found_count = 0
volid = -1
for v in data['result']['volumes']:
if v['name'] == seek:
found_count += 1
volid = v['volumeID']
if found_count == 0:
raise exception.VolumeNotFound(volume_id=volume['id'])
if found_count > 1:
LOG.debug(_("Deleting volumeID: %s"), volid)
raise exception.DuplicateSfVolumeNames(vol_name=volume['id'])
params = {'volumeID': volid}
data = self._issue_api_request('DeleteVolume', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
LOG.debug(_("Leaving SolidFire delete_volume"))
def ensure_export(self, context, volume):
LOG.debug(_("Executing SolidFire ensure_export..."))
return self._do_export(volume)
def create_export(self, context, volume):
LOG.debug(_("Executing SolidFire create_export..."))
return self._do_export(volume)
def _do_create_snapshot(self, snapshot, snapshot_name):
"""Creates a snapshot."""
LOG.debug(_("Enter SolidFire create_snapshot..."))
sf_account_name = socket.gethostname() + '-' + snapshot['project_id']
sfaccount = self._get_sfaccount_by_name(sf_account_name)
if sfaccount is None:
raise exception.SfAccountNotFound(account_name=sf_account_name)
params = {'accountID': sfaccount['accountID']}
data = self._issue_api_request('ListVolumesForAccount', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
found_count = 0
volid = -1
for v in data['result']['volumes']:
if v['name'] == 'OS-VOLID-%s' % snapshot['volume_id']:
found_count += 1
volid = v['volumeID']
if found_count == 0:
raise exception.VolumeNotFound(volume_id=snapshot['volume_id'])
if found_count != 1:
raise exception.DuplicateSfVolumeNames(
vol_name='OS-VOLID-%s' % snapshot['volume_id'])
params = {'volumeID': int(volid),
'name': snapshot_name,
'attributes': {'OriginatingVolume': volid}}
data = self._issue_api_request('CloneVolume', params)
if 'result' not in data:
raise exception.SolidFireAPIDataException(data=data)
return (data, sfaccount)
def delete_snapshot(self, snapshot):
self.delete_volume(snapshot, True)
def create_snapshot(self, snapshot):
snapshot_name = 'OS-SNAPID-%s' % (
snapshot['id'])
(data, sf_account) = self._do_create_snapshot(snapshot, snapshot_name)
def create_volume_from_snapshot(self, volume, snapshot):
cluster_info = self._get_cluster_info()
iscsi_portal = cluster_info['clusterInfo']['svip'] + ':3260'
sfaccount = self._create_sfaccount(snapshot['project_id'])
chap_secret = sfaccount['targetSecret']
snapshot_name = 'OS-VOLID-%s' % volume['id']
(data, sf_account) = self._do_create_snapshot(snapshot, snapshot_name)
if 'result' not in data or 'volumeID' not in data['result']:
raise exception.SolidFireAPIDataException(data=data)
volume_id = data['result']['volumeID']
volume_list = self._get_volumes_by_sfaccount(sf_account['accountID'])
iqn = None
for v in volume_list:
if v['volumeID'] == volume_id:
iqn = 'iqn.2010-01.com.solidfire:' + v['iqn']
break
model_update = {}
# NOTE(john-griffith): SF volumes are always at lun 0
model_update['provider_location'] = ('%s %s %s'
% (iscsi_portal, iqn, 0))
model_update['provider_auth'] = ('CHAP %s %s'
% (sfaccount['username'],
chap_secret))
return model_update