cinder/cinder/volume/drivers/coprhd/helpers/volume.py

518 lines
20 KiB
Python

# Copyright (c) 2016 EMC Corporation
# 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.
import oslo_serialization
from oslo_utils import units
import six
from cinder.i18n import _
from cinder.volume.drivers.coprhd.helpers import commoncoprhdapi as common
from cinder.volume.drivers.coprhd.helpers import consistencygroup
from cinder.volume.drivers.coprhd.helpers import project
from cinder.volume.drivers.coprhd.helpers import virtualarray
from cinder.volume.drivers.coprhd.helpers import virtualpool
class Volume(common.CoprHDResource):
# Commonly used URIs for the 'Volume' module
URI_SEARCH_VOLUMES = '/block/volumes/search?project={0}'
URI_SEARCH_VOLUMES_BY_TAG = '/block/volumes/search?tag={0}'
URI_VOLUMES = '/block/volumes'
URI_VOLUME = URI_VOLUMES + '/{0}'
URI_VOLUME_EXPORTS = URI_VOLUME + '/exports'
URI_BULK_DELETE = URI_VOLUMES + '/deactivate'
URI_DEACTIVATE = URI_VOLUME + '/deactivate'
URI_EXPAND = URI_VOLUME + '/expand'
URI_TAG_VOLUME = URI_VOLUME + "/tags"
URI_VOLUME_CHANGE_VPOOL = URI_VOLUMES + "/vpool-change"
# Protection REST APIs - clone
URI_VOLUME_PROTECTION_FULLCOPIES = (
'/block/volumes/{0}/protection/full-copies')
URI_SNAPSHOT_PROTECTION_FULLCOPIES = (
'/block/snapshots/{0}/protection/full-copies')
URI_VOLUME_CLONE_DETACH = "/block/full-copies/{0}/detach"
# New CG URIs
URI_CG_CLONE = "/block/consistency-groups/{0}/protection/full-copies"
URI_CG_CLONE_DETACH = (
"/block/consistency-groups/{0}/protection/full-copies/{1}/detach")
VOLUMES = 'volumes'
CG = 'consistency-groups'
BLOCK = 'block'
SNAPSHOTS = 'snapshots'
# Lists volumes in a project
def list_volumes(self, project):
"""Makes REST API call to list volumes under a project.
:param project: name of project
:returns: List of volumes uuids in JSON response payload
"""
volume_uris = self.search_volumes(project)
volumes = []
for uri in volume_uris:
volume = self.show_by_uri(uri)
if volume:
volumes.append(volume)
return volumes
def search_volumes(self, project_name):
proj = project.Project(self.ipaddr, self.port)
project_uri = proj.project_query(project_name)
(s, h) = common.service_json_request(self.ipaddr, self.port,
"GET",
Volume.URI_SEARCH_VOLUMES.format(
project_uri),
None)
o = common.json_decode(s)
if not o:
return []
volume_uris = []
resources = common.get_node_value(o, "resource")
for resource in resources:
volume_uris.append(resource["id"])
return volume_uris
# Shows volume information given its uri
def show_by_uri(self, uri):
"""Makes REST API call and retrieves volume details based on UUID.
:param uri: UUID of volume
:returns: Volume details in JSON response payload
"""
(s, h) = common.service_json_request(self.ipaddr, self.port,
"GET",
Volume.URI_VOLUME.format(uri),
None)
o = common.json_decode(s)
inactive = common.get_node_value(o, 'inactive')
if inactive:
return None
return o
# Creates a volume given label, project, vpool and size
def create(self, project_name, label, size, varray, vpool,
sync, consistencygroup, synctimeout=0):
"""Makes REST API call to create volume under a project.
:param project_name: name of the project under which the volume
will be created
:param label: name of volume
:param size: size of volume
:param varray: name of varray
:param vpool: name of vpool
:param sync: synchronous request
:param consistencygroup: To create volume under a consistencygroup
:param synctimeout: Query for task status for 'synctimeout' secs.
If the task doesn't complete in synctimeout secs,
an exception is thrown
:returns: Created task details in JSON response payload
"""
proj_obj = project.Project(self.ipaddr, self.port)
project_uri = proj_obj.project_query(project_name)
vpool_obj = virtualpool.VirtualPool(self.ipaddr, self.port)
vpool_uri = vpool_obj.vpool_query(vpool, "block")
varray_obj = virtualarray.VirtualArray(self.ipaddr, self.port)
varray_uri = varray_obj.varray_query(varray)
request = {
'name': label,
'size': size,
'varray': varray_uri,
'project': project_uri,
'vpool': vpool_uri,
'count': 1
}
if consistencygroup:
request['consistency_group'] = consistencygroup
body = oslo_serialization.jsonutils.dumps(request)
(s, h) = common.service_json_request(self.ipaddr, self.port,
"POST",
Volume.URI_VOLUMES,
body)
o = common.json_decode(s)
if sync:
# check task empty
if len(o["task"]) > 0:
task = o["task"][0]
return self.check_for_sync(task, sync, synctimeout)
else:
raise common.CoprHdError(
common.CoprHdError.SOS_FAILURE_ERR,
_("error: task list is empty, no task response found"))
else:
return o
# Blocks the operation until the task is complete/error out/timeout
def check_for_sync(self, result, sync, synctimeout=0):
if sync:
if len(result["resource"]) > 0:
resource = result["resource"]
return (
common.block_until_complete("volume", resource["id"],
result["id"], self.ipaddr,
self.port, synctimeout)
)
else:
raise common.CoprHdError(
common.CoprHdError.SOS_FAILURE_ERR,
_("error: task list is empty, no task response found"))
else:
return result
# Queries a volume given its name
def volume_query(self, full_project_name, volume_name):
"""Makes REST API call to query the volume by name.
:param volume_name: name of volume
:param full_project_name: Full project path
:returns: Volume details in JSON response payload
"""
if common.is_uri(volume_name):
return volume_name
if not full_project_name:
raise common.CoprHdError(common.CoprHdError.NOT_FOUND_ERR,
_("Project name not specified"))
uris = self.search_volumes(full_project_name)
for uri in uris:
volume = self.show_by_uri(uri)
if volume and 'name' in volume and volume['name'] == volume_name:
return volume['id']
raise common.CoprHdError(common.CoprHdError.NOT_FOUND_ERR,
(_("Volume"
"%s: not found") % volume_name))
def get_storageAttributes(self, volume_name, cg_name, snapshot_name=None):
storageres_type = None
storageres_typename = None
if snapshot_name is not None:
storageres_type = Volume.BLOCK
storageres_typename = Volume.SNAPSHOTS
elif volume_name is not None:
storageres_type = Volume.BLOCK
storageres_typename = Volume.VOLUMES
elif cg_name is not None:
storageres_type = Volume.BLOCK
storageres_typename = Volume.CG
else:
storageres_type = None
storageres_typename = None
return (storageres_type, storageres_typename)
def storage_resource_query(self,
storageres_type,
volume_name,
cg_name,
snapshot_name,
project,
tenant):
resourcepath = "/" + project
if tenant is not None:
resourcepath = tenant + resourcepath
resUri = None
resourceObj = None
if Volume.BLOCK == storageres_type and volume_name is not None:
resUri = self.volume_query(resourcepath, volume_name)
if snapshot_name is not None:
from cinder.volume.drivers.coprhd.helpers import snapshot
snapobj = snapshot.Snapshot(self.ipaddr, self.port)
resUri = snapobj.snapshot_query(storageres_type,
Volume.VOLUMES, resUri,
snapshot_name)
elif Volume.BLOCK == storageres_type and cg_name is not None:
resourceObj = consistencygroup.ConsistencyGroup(
self.ipaddr, self.port)
resUri = resourceObj.consistencygroup_query(
cg_name,
project,
tenant)
else:
resourceObj = None
return resUri
# Creates volume(s) from given source volume
def clone(self, new_vol_name, resource_uri,
sync, synctimeout=0):
"""Makes REST API call to clone volume.
:param new_vol_name: name of volume
:param resource_uri: uri of source volume
:param sync: synchronous request
:param synctimeout: Query for task status for 'synctimeout' secs.
If the task doesn't complete in synctimeout secs,
an exception is thrown
:returns: Created task details in JSON response payload
"""
is_snapshot_clone = False
clone_full_uri = None
# consistency group
if resource_uri.find("BlockConsistencyGroup") > 0:
clone_full_uri = Volume.URI_CG_CLONE.format(resource_uri)
elif resource_uri.find("BlockSnapshot") > 0:
is_snapshot_clone = True
clone_full_uri = (
Volume.URI_SNAPSHOT_PROTECTION_FULLCOPIES.format(resource_uri))
else:
clone_full_uri = (
Volume.URI_VOLUME_PROTECTION_FULLCOPIES.format(resource_uri))
request = {
'name': new_vol_name,
'type': None,
'count': 1
}
request["count"] = 1
body = oslo_serialization.jsonutils.dumps(request)
(s, h) = common.service_json_request(self.ipaddr, self.port,
"POST",
clone_full_uri,
body)
o = common.json_decode(s)
if sync:
task = o["task"][0]
if is_snapshot_clone:
return (
common.block_until_complete(
"block",
task["resource"]["id"],
task["id"], self.ipaddr, self.port)
)
else:
return self.check_for_sync(task, sync, synctimeout)
else:
return o
# To check whether a cloned volume is in detachable state or not
def is_volume_detachable(self, full_project_name, name):
volume_uri = self.volume_query(full_project_name, name)
vol = self.show_by_uri(volume_uri)
# Filtering based on "replicaState" attribute value of Cloned volume.
# If "replicaState" value is "SYNCHRONIZED" then only Cloned volume
# would be in detachable state.
try:
return vol['protection']['full_copies'][
'replicaState'] == 'SYNCHRONIZED'
except TypeError:
return False
def volume_clone_detach(self, resource_uri, full_project_name,
name, sync, synctimeout=0):
volume_uri = self.volume_query(full_project_name, name)
# consistency group
if resource_uri.find("BlockConsistencyGroup") > 0:
(s, h) = common.service_json_request(
self.ipaddr, self.port,
"POST",
Volume.URI_CG_CLONE_DETACH.format(
resource_uri,
volume_uri), None)
else:
(s, h) = common.service_json_request(
self.ipaddr, self.port,
"POST",
Volume.URI_VOLUME_CLONE_DETACH.format(volume_uri), None)
o = common.json_decode(s)
if sync:
task = o["task"][0]
return self.check_for_sync(task, sync, synctimeout)
else:
return o
# Shows volume information given its name
def show(self, full_project_name, name):
"""Retrieves volume details based on volume name.
:param full_project_name: project path of the volume
:param name: name of the volume. If the volume is under a project,
then full XPath needs to be specified.
Example: If VOL1 is a volume under project PROJ1,
then the name of volume is PROJ1/VOL1
:returns: Volume details in JSON response payload
"""
if common.is_uri(name):
return name
if full_project_name is None:
raise common.CoprHdError(common.CoprHdError.NOT_FOUND_ERR,
(_("Volume %s : not found") %
six.text_type(name)))
uris = self.search_volumes(full_project_name)
for uri in uris:
volume = self.show_by_uri(uri)
if volume and 'name' in volume and volume['name'] == name:
return volume
raise common.CoprHdError(common.CoprHdError.NOT_FOUND_ERR,
(_("Volume"
" %s : not found") % six.text_type(name)))
def expand(self, full_project_name, volume_name, new_size,
sync=False, synctimeout=0):
volume_detail = self.show(full_project_name, volume_name)
from decimal import Decimal
new_size_in_gb = Decimal(Decimal(new_size) / (units.Gi))
current_size = Decimal(volume_detail["provisioned_capacity_gb"])
if new_size_in_gb <= current_size:
raise common.CoprHdError(
common.CoprHdError.VALUE_ERR,
(_("error: Incorrect value of new size: %(new_size_in_gb)s"
" GB\nNew size must be greater than current size: "
"%(current_size)s GB") % {'new_size_in_gb': new_size_in_gb,
'current_size': current_size}))
body = oslo_serialization.jsonutils.dumps({
"new_size": new_size
})
(s, h) = common.service_json_request(self.ipaddr, self.port,
"POST",
Volume.URI_EXPAND.format(
volume_detail["id"]),
body)
if not s:
return None
o = common.json_decode(s)
if sync:
return self.check_for_sync(o, sync, synctimeout)
return o
# Deletes a volume given a volume name
def delete(self, full_project_name, name, sync=False,
force_delete=False, coprhdonly=False, synctimeout=0):
"""Deletes a volume based on volume name.
:param full_project_name: project name
:param name: name of volume to be deleted
:param sync: synchronous request
:param force_delete: if true, it will force the delete of internal
volumes that have the SUPPORTS_FORCE flag
:param coprhdonly: to delete volumes from coprHD only
:param synctimeout: Query for task status for 'synctimeout' secs. If
the task doesn't complete in synctimeout secs,
an exception is thrown
"""
volume_uri = self.volume_query(full_project_name, name)
return self.delete_by_uri(volume_uri, sync, force_delete,
coprhdonly, synctimeout)
# Deletes a volume given a volume uri
def delete_by_uri(self, uri, sync=False,
force_delete=False, coprhdonly=False, synctimeout=0):
"""Deletes a volume based on volume uri."""
params = ''
if force_delete:
params += '&' if ('?' in params) else '?'
params += "force=" + "true"
if coprhdonly is True:
params += '&' if ('?' in params) else '?'
params += "type=" + 'CoprHD_ONLY'
(s, h) = common.service_json_request(self.ipaddr, self.port,
"POST",
Volume.URI_DEACTIVATE.format(
uri) + params,
None)
if not s:
return None
o = common.json_decode(s)
if sync:
return self.check_for_sync(o, sync, synctimeout)
return o
# Gets the exports info given a volume uri
def get_exports_by_uri(self, uri):
"""Makes REST API call to get exports info of a volume.
:param uri: URI of the volume
:returns: Exports details in JSON response payload
"""
(s, h) = common.service_json_request(self.ipaddr, self.port,
"GET",
Volume.URI_VOLUME_EXPORTS.format(
uri),
None)
return common.json_decode(s)
# Update a volume information
# Changed the volume vpool
def update(self, prefix_path, name, vpool):
"""Makes REST API call to update a volume information.
:param name: name of the volume to be updated
:param vpool: name of vpool
:returns: Created task details in JSON response payload
"""
namelist = []
if isinstance(name, list):
namelist = name
else:
namelist.append(name)
volumeurilist = []
for item in namelist:
volume_uri = self.volume_query(prefix_path, item)
volumeurilist.append(volume_uri)
vpool_obj = virtualpool.VirtualPool(self.ipaddr, self.port)
vpool_uri = vpool_obj.vpool_query(vpool, "block")
params = {
'vpool': vpool_uri,
'volumes': volumeurilist
}
body = oslo_serialization.jsonutils.dumps(params)
(s, h) = common.service_json_request(
self.ipaddr, self.port, "POST",
Volume.URI_VOLUME_CHANGE_VPOOL,
body)
o = common.json_decode(s)
return o