/handle?url_params. Send JSON encoded data in body of POST request.
Exceptions:
urllib2.URLError
1. Name or service not found (e.reason is socket.gaierror)
2. Socket blocking operation timeout (e.reason is
socket.timeout)
3. Network IO error (e.reason is socket.error)
urllib2.HTTPError
1. HTTP 404, HTTP 500 etc.
CoraidJsonEncodeFailure - bad REST response
"""
# Handle must be simple path, for example:
# /configure
if '?' in handle or '&' in handle:
raise ValueError(_('Invalid REST handle name. Expected path.'))
# Request url includes base ESM url, handle path and optional
# URL params.
rest_url = urlparse.urljoin(self._esm_url, handle)
encoded_url_params = urllib.urlencode(url_params)
if encoded_url_params:
rest_url += '?' + encoded_url_params
if data is None:
json_request = None
else:
json_request = jsonutils.dumps(data)
request = urllib2.Request(rest_url, json_request)
response = self._url_opener.open(request).read()
try:
if not response and allow_empty_response:
reply = {}
else:
reply = jsonutils.loads(response)
except (TypeError, ValueError) as exc:
msg = (_('Call to json.loads() failed: %(ex)s.'
' Response: %(resp)s') %
{'ex': exc, 'resp': response})
raise exception.CoraidJsonEncodeFailure(msg)
return reply
def to_coraid_kb(gb):
return math.ceil(float(gb) * units.GiB / 1000)
def coraid_volume_size(gb):
return '{0}K'.format(to_coraid_kb(gb))
**** CubicPower OpenStack Study ****
def to_coraid_kb(gb):
return math.ceil(float(gb) * units.GiB / 1000)
**** CubicPower OpenStack Study ****
def coraid_volume_size(gb):
return '{0}K'.format(to_coraid_kb(gb))
**** CubicPower OpenStack Study ****
class CoraidAppliance(object):
**** CubicPower OpenStack Study ****
def __init__(self, rest_client, username, password, group):
self._rest_client = rest_client
self._username = username
self._password = password
self._group = group
self._logined = False
**** CubicPower OpenStack Study ****
def _login(self):
"""Login into ESM.
Perform login request and return available groups.
:returns: dict -- map with group_name to group_id
"""
ADMIN_GROUP_PREFIX = 'admin group:'
url_params = {'op': 'login',
'username': self._username,
'password': self._password}
reply = self._rest_client.rpc('admin', url_params, 'Login')
if reply['state'] != 'adminSucceed':
raise exception.CoraidESMBadCredentials()
# Read groups map from login reply.
groups_map = {}
for group_info in reply.get('values', []):
full_group_name = group_info['fullPath']
if full_group_name.startswith(ADMIN_GROUP_PREFIX):
group_name = full_group_name[len(ADMIN_GROUP_PREFIX):]
groups_map[group_name] = group_info['groupId']
return groups_map
**** CubicPower OpenStack Study ****
def _set_effective_group(self, groups_map, group):
"""Set effective group.
Use groups_map returned from _login method.
"""
try:
group_id = groups_map[group]
except KeyError:
raise exception.CoraidESMBadGroup(group_name=group)
url_params = {'op': 'setRbacGroup',
'groupId': group_id}
reply = self._rest_client.rpc('admin', url_params, 'Group')
if reply['state'] != 'adminSucceed':
raise exception.CoraidESMBadCredentials()
self._logined = True
**** CubicPower OpenStack Study ****
def _ensure_session(self):
if not self._logined:
groups_map = self._login()
self._set_effective_group(groups_map, self._group)
**** CubicPower OpenStack Study ****
def _relogin(self):
self._logined = False
self._ensure_session()
**** CubicPower OpenStack Study ****
def rpc(self, handle, url_params, data, allow_empty_response=False):
self._ensure_session()
relogin_attempts = 3
# Do action, relogin if needed and repeat action.
while True:
reply = self._rest_client.rpc(handle, url_params, data,
allow_empty_response)
if self._is_session_expired(reply):
relogin_attempts -= 1
if relogin_attempts <= 0:
raise exception.CoraidESMReloginFailed()
LOG.debug(_('Session is expired. Relogin on ESM.'))
self._relogin()
else:
return reply
**** CubicPower OpenStack Study ****
def _is_session_expired(self, reply):
return ('state' in reply and
reply['state'] in ESM_SESSION_EXPIRED_STATES and
reply['metaCROp'] == 'reboot')
**** CubicPower OpenStack Study ****
def _is_bad_config_state(self, reply):
return (not reply or
'configState' not in reply or
reply['configState'] != 'completedSuccessfully')
**** CubicPower OpenStack Study ****
def configure(self, json_request):
reply = self.rpc('configure', {}, json_request)
if self._is_bad_config_state(reply):
# Calculate error message
if not reply:
reason = _('Reply is empty.')
else:
reason = reply.get('message', _('Error message is empty.'))
raise exception.CoraidESMConfigureError(reason=reason)
return reply
**** CubicPower OpenStack Study ****
def esm_command(self, request):
request['data'] = jsonutils.dumps(request['data'])
return self.configure([request])
**** CubicPower OpenStack Study ****
def get_volume_info(self, volume_name):
"""Retrieve volume information for a given volume name."""
url_params = {'shelf': 'cms',
'orchStrRepo': '',
'lv': volume_name}
reply = self.rpc('fetch', url_params, None)
try:
volume_info = reply[0][1]['reply'][0]
except (IndexError, KeyError):
raise exception.VolumeNotFound(volume_id=volume_name)
return {'pool': volume_info['lv']['containingPool'],
'repo': volume_info['repoName'],
'lun': volume_info['lv']['lvStatus']['exportedLun']['lun'],
'shelf': volume_info['lv']['lvStatus']['exportedLun']['shelf']}
**** CubicPower OpenStack Study ****
def get_volume_repository(self, volume_name):
volume_info = self.get_volume_info(volume_name)
return volume_info['repo']
**** CubicPower OpenStack Study ****
def get_all_repos(self):
reply = self.rpc('fetch', {'orchStrRepo': ''}, None)
try:
return reply[0][1]['reply']
except (IndexError, KeyError):
return []
**** CubicPower OpenStack Study ****
def ping(self):
try:
self.rpc('fetch', {}, None, allow_empty_response=True)
except Exception as e:
LOG.debug(_('Coraid Appliance ping failed: %s'), e)
raise exception.CoraidESMNotAvailable(reason=e)
**** CubicPower OpenStack Study ****
def create_lun(self, repository_name, volume_name, volume_size_in_gb):
request = {'addr': 'cms',
'data': {
'servers': [],
'repoName': repository_name,
'lvName': volume_name,
'size': coraid_volume_size(volume_size_in_gb)},
'op': 'orchStrLun',
'args': 'add'}
esm_result = self.esm_command(request)
LOG.debug(_('Volume "%(name)s" created with VSX LUN "%(lun)s"') %
{'name': volume_name,
'lun': esm_result['firstParam']})
return esm_result
**** CubicPower OpenStack Study ****
def delete_lun(self, volume_name):
repository_name = self.get_volume_repository(volume_name)
request = {'addr': 'cms',
'data': {
'repoName': repository_name,
'lvName': volume_name},
'op': 'orchStrLun/verified',
'args': 'delete'}
esm_result = self.esm_command(request)
LOG.debug(_('Volume "%s" deleted.'), volume_name)
return esm_result
**** CubicPower OpenStack Study ****
def resize_volume(self, volume_name, new_volume_size_in_gb):
LOG.debug(_('Resize volume "%(name)s" to %(size)s GB.') %
{'name': volume_name,
'size': new_volume_size_in_gb})
repository = self.get_volume_repository(volume_name)
LOG.debug(_('Repository for volume "%(name)s" found: "%(repo)s"') %
{'name': volume_name,
'repo': repository})
request = {'addr': 'cms',
'data': {
'lvName': volume_name,
'newLvName': volume_name + '-resize',
'size': coraid_volume_size(new_volume_size_in_gb),
'repoName': repository},
'op': 'orchStrLunMods',
'args': 'resize'}
esm_result = self.esm_command(request)
LOG.debug(_('Volume "%(name)s" resized. New size is %(size)s GB.') %
{'name': volume_name,
'size': new_volume_size_in_gb})
return esm_result
**** CubicPower OpenStack Study ****
def create_snapshot(self, volume_name, snapshot_name):
volume_repository = self.get_volume_repository(volume_name)
request = {'addr': 'cms',
'data': {
'repoName': volume_repository,
'lvName': volume_name,
'newLvName': snapshot_name},
'op': 'orchStrLunMods',
'args': 'addClSnap'}
esm_result = self.esm_command(request)
return esm_result
**** CubicPower OpenStack Study ****
def delete_snapshot(self, snapshot_name):
repository_name = self.get_volume_repository(snapshot_name)
request = {'addr': 'cms',
'data': {
'repoName': repository_name,
'lvName': snapshot_name},
'op': 'orchStrLunMods',
'args': 'delClSnap'}
esm_result = self.esm_command(request)
return esm_result
**** CubicPower OpenStack Study ****
def create_volume_from_snapshot(self,
snapshot_name,
volume_name,
dest_repository_name):
snapshot_repo = self.get_volume_repository(snapshot_name)
request = {'addr': 'cms',
'data': {
'lvName': snapshot_name,
'repoName': snapshot_repo,
'newLvName': volume_name,
'newRepoName': dest_repository_name},
'op': 'orchStrLunMods',
'args': 'addClone'}
esm_result = self.esm_command(request)
return esm_result
**** CubicPower OpenStack Study ****
def clone_volume(self,
src_volume_name,
dst_volume_name,
dst_repository_name):
src_volume_info = self.get_volume_info(src_volume_name)
if src_volume_info['repo'] != dst_repository_name:
raise exception.CoraidException(
_('Cannot create clone volume in different repository.'))
request = {'addr': 'cms',
'data': {
'shelfLun': '{0}.{1}'.format(src_volume_info['shelf'],
src_volume_info['lun']),
'lvName': src_volume_name,
'repoName': src_volume_info['repo'],
'newLvName': dst_volume_name,
'newRepoName': dst_repository_name},
'op': 'orchStrLunMods',
'args': 'addClone'}
return self.esm_command(request)
**** CubicPower OpenStack Study ****
class CoraidDriver(driver.VolumeDriver):
"""This is the Class to set in cinder.conf (volume_driver)."""
VERSION = '1.0.0'
**** CubicPower OpenStack Study ****
def __init__(self, *args, **kwargs):
super(CoraidDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(coraid_opts)
self._stats = {'driver_version': self.VERSION,
'free_capacity_gb': 'unknown',
'reserved_percentage': 0,
'storage_protocol': 'aoe',
'total_capacity_gb': 'unknown',
'vendor_name': 'Coraid'}
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats['volume_backend_name'] = backend_name or 'EtherCloud ESM'
@property
**** CubicPower OpenStack Study ****
def appliance(self):
# NOTE(nsobolevsky): This is workaround for bug in the ESM appliance.
# If there is a lot of request with the same session/cookie/connection,
# the appliance could corrupt all following request in session.
# For that purpose we just create a new appliance.
esm_url = "https://{0}:8443".format(
self.configuration.coraid_esm_address)
return CoraidAppliance(CoraidRESTClient(esm_url),
self.configuration.coraid_user,
self.configuration.coraid_password,
self.configuration.coraid_group)
**** CubicPower OpenStack Study ****
def check_for_setup_error(self):
"""Return an error if prerequisites aren't met."""
self.appliance.ping()
**** CubicPower OpenStack Study ****
def _get_repository(self, volume_type):
"""Get the ESM Repository from the Volume Type.
The ESM Repository is stored into a volume_type_extra_specs key.
"""
volume_type_id = volume_type['id']
repository_key_name = self.configuration.coraid_repository_key
repository = volume_types.get_volume_type_extra_specs(
volume_type_id, repository_key_name)
# Remove keyword from repository name if needed if repository.startswith(' '): return repository[len(' '):] else:
return repository
**** CubicPower OpenStack Study ****
def create_volume(self, volume):
"""Create a Volume."""
repository = self._get_repository(volume['volume_type'])
self.appliance.create_lun(repository, volume['name'], volume['size'])
**** CubicPower OpenStack Study ****
def create_cloned_volume(self, volume, src_vref):
dst_volume_repository = self._get_repository(volume['volume_type'])
self.appliance.clone_volume(src_vref['name'],
volume['name'],
dst_volume_repository)
if volume['size'] != src_vref['size']:
self.appliance.resize_volume(volume['name'], volume['size'])
**** CubicPower OpenStack Study ****
def delete_volume(self, volume):
"""Delete a Volume."""
try:
self.appliance.delete_lun(volume['name'])
except exception.VolumeNotFound:
self.appliance.ping()
**** CubicPower OpenStack Study ****
def create_snapshot(self, snapshot):
"""Create a Snapshot."""
volume_name = snapshot['volume_name']
snapshot_name = snapshot['name']
self.appliance.create_snapshot(volume_name, snapshot_name)
**** CubicPower OpenStack Study ****
def delete_snapshot(self, snapshot):
"""Delete a Snapshot."""
snapshot_name = snapshot['name']
self.appliance.delete_snapshot(snapshot_name)
**** CubicPower OpenStack Study ****
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a Volume from a Snapshot."""
snapshot_name = snapshot['name']
repository = self._get_repository(volume['volume_type'])
self.appliance.create_volume_from_snapshot(snapshot_name,
volume['name'],
repository)
if volume['size'] > snapshot['volume_size']:
self.appliance.resize_volume(volume['name'], volume['size'])
**** CubicPower OpenStack Study ****
def extend_volume(self, volume, new_size):
"""Extend an existing volume."""
self.appliance.resize_volume(volume['name'], new_size)
**** CubicPower OpenStack Study ****
def initialize_connection(self, volume, connector):
"""Return connection information."""
volume_info = self.appliance.get_volume_info(volume['name'])
shelf = volume_info['shelf']
lun = volume_info['lun']
LOG.debug(_('Initialize connection %(shelf)s/%(lun)s for %(name)s') %
{'shelf': shelf,
'lun': lun,
'name': volume['name']})
aoe_properties = {'target_shelf': shelf,
'target_lun': lun}
return {'driver_volume_type': 'aoe',
'data': aoe_properties}
**** CubicPower OpenStack Study ****
def _get_repository_capabilities(self):
repos_list = map(lambda i: i['profile']['fullName'] + ':' + i['name'],
self.appliance.get_all_repos())
return ' '.join(repos_list)
**** CubicPower OpenStack Study ****
def update_volume_stats(self):
capabilities = self._get_repository_capabilities()
self._stats[self.configuration.coraid_repository_key] = capabilities
**** CubicPower OpenStack Study ****
def get_volume_stats(self, refresh=False):
"""Return Volume Stats."""
if refresh:
self.update_volume_stats()
return self._stats
**** CubicPower OpenStack Study ****
def local_path(self, volume):
pass
**** CubicPower OpenStack Study ****
def create_export(self, context, volume):
pass
**** CubicPower OpenStack Study ****
def remove_export(self, context, volume):
pass
**** CubicPower OpenStack Study ****
def terminate_connection(self, volume, connector, **kwargs):
pass
**** CubicPower OpenStack Study ****
def ensure_export(self, context, volume):
pass