def list_volume_infos(self, filters=None):
        """
        Returns list of volumes in the system.
        Each item in the list will include the name and id of the volume
        :return: list of volume dictionaries
        """

        if isinstance(filters, dict) and 'name_prefix' in filters:
            name_prefix = filters['name_prefix']
            filter_func = lambda i: i['name'].startswith(name_prefix)
        else:
            filter_func = lambda i: True

        infos = []
        r_uri = '/api/types/Volume/instances'
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error listing volumes: %s'
                                   % req.json().get('message'))

        volume_objects = req.json()
        for volume in volume_objects:
            if filter_func(volume):
                infos.append({'id': volume['id'],
                              'name': volume['name']})

        return infos
    def _get_pdid(self, protection_domain):
        """
        Private method retrieves the ScaleIO protection domain ID. ScaleIO
        objects are assigned a unique ID that can be used to identify the
        object.
        :param protection_domain: Unique 32 character string name of Protection
                                  Domain
        :return: Protection domain ID
        """

        if not protection_domain:
            raise ValueError(
                'Invalid protection_domain parameter, protection_domain=%s'
                % protection_domain)

        # request uri to retrieve pd id
        r_uri = '/api/types/Domain/instances/getByName::' + \
            utilities.encode_string(protection_domain, double=True)
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error(
                'Error retrieving ScaleIO protection domain ID for %s: %s'
                % (protection_domain, req.content))

        return req.json()
示例#3
0
    def get_storage_pool_statistics(self, protection_domain, storage_pool, props):
        """
        Returns the requested statistics on a storage pool.
        :param: protection_domain. Name of the protection domain
        :param: storage_pool. Name of the storage pool
        :param: props. Array of property names to query
        :return: dict of storage pool properties
        """

        if not protection_domain:
            raise ValueError(
                'Invalid protection_domain parameter, protection_domain=%s'
                % protection_domain)
        if not storage_pool:
            raise ValueError(
                'Invalid storage_pool parameter, storage_pool=%s'
                % storage_pool)
        if not props:
            raise ValueError('No properties specified to query')

        pd_id = self._get_pdid(protection_domain)
        sp_id = self._get_spid(storage_pool, pd_id)

        params = {'ids': [sp_id], 'properties': props}

        r_uri = '/api/types/StoragePool/instances/action/querySelectedStatistics'
        req = self._post(r_uri, params=params)
        if req.status_code != 200:
            raise exceptions.Error('Error retrieving storage pool stats: %s'
                                   % req.json().get('message'))

        stats = req.json()[sp_id]

        return stats
    def extend_volume(self, volume_id_or_name, volume_size_gb):
        """
        Extend the volume size.  Extend a volume in multiples of 8GB.
        Increases the capacity of a volume. You can increase
        (but not decrease) a volume capacity at any time,
        as long as there is enough capacity for the volume size to grow.
        :param volume_id_or_name: ScaleIO volume ID or volume name
        :param volume_size_gb: New volume size in GB
        :return: Nothing
        """

        volume_id = self._validate_volume_id(volume_id_or_name)

        # extend requires size in GB, so we will convert and check size is
        # multiple of 8GB
        volume_size_gb = self._validate_size(
            volume_size_gb, utilities.UnitSize.GBYTE, utilities.UnitSize.GBYTE)
        params = {'sizeInGB': str(volume_size_gb)}

        r_uri = '/api/instances/Volume::' + volume_id + '/action/setVolumeSize'
        req = self._post(r_uri, params=params)
        if req.status_code == 200:
            LOG.debug('Extended volume size %s successfully new size '
                     'is %s GB', volume_id, volume_size_gb)
        elif req.json().get('errorCode') == VOLUME_CANNOT_EXTEND:
            LOG.error('Volume %s extend error: %s', volume_id,
                      (req.json().get('message')))
            raise exceptions.SizeTooSmall(
                "Required size %s GB for volume '%s' is too small: %s"
                % (volume_size_gb, volume_id, req.json().get('message')))
        else:
            raise exceptions.Error("Error extending volume '%s': %s"
                                   % (volume_id, req.json().get('message')))
    def systempool_size(self):
        """
        Return ScaleIO cluster storage statistics in kilobytes.
        This is the raw total system capacity.
        :return: Tuple (used, total, free) in kilobytes
        """

        used_kb = 0
        total_kb = 0
        free_kb = 0

        r_uri = '/api/types/System/instances/action/querySelectedStatistics'
        params = {'ids': [],
                  'properties': ['capacityInUseInKb', 'capacityLimitInKb']}
        req = self._post(r_uri, params=params)
        if req.status_code == 200:
            used_kb = req.json().get('capacityInUseInKb')
            total_kb = req.json().get('capacityLimitInKb')
            free_kb = (int(total_kb) - int(used_kb))
            LOG.debug('Total=%sKB, Used=%sKB, Free=%sKB' %
                      (total_kb, used_kb, free_kb))
        else:
            raise exceptions.Error('Error retrieving cluster statistics: %s'
                                   % req.json().get('message'))

        return (used_kb, total_kb, free_kb)
    def list_storage_pool_infos(self, protection_domain):
        """
        Returns list of storage pools within a protection domain.
        Each item in the list will include the name and id of the storage pool
        :param: protection_domain. Name of the protection domain to query for pools
        :return: list of storage pool dictionaries, containing 'name' and 'id'
        """

        if not protection_domain:
            raise ValueError(
                'Invalid protection_domain parameter, protection_domain=%s'
                % protection_domain)

        pd_id = self._get_pdid(protection_domain)
        infos = []
        r_uri = '/api/instances/ProtectionDomain::' + pd_id + '/relationships/StoragePool'
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error listing storage pools: %s'
                                   % req.json().get('message'))

        sp_objects = req.json()
        for sp in sp_objects:
            infos.append({'id': sp['id'],
                          'name': sp['name']})

        return infos
    def rename_volume(self, volume_id_or_name, new_volume_name):
        """
        Rename an existing volume
        :param volume_id_or_name: ScaleIO volume ID or volume name
        :param new_volume_name: New volume name
        :return: Nothing
        """
        volume_id = self._validate_volume_id(volume_id_or_name)
        if not new_volume_name:
            raise ValueError(
                'Invalid new_volume_name parameter, volume_name=%s'
                % new_volume_name)

        params = {'newName': new_volume_name}

        r_uri = '/api/instances/Volume::' + volume_id + '/action/setVolumeName'
        req = self._post(r_uri, params=params)
        if req.status_code == 200:  # success
            LOG.debug('Renamed volume %s successfully' % volume_id)
        elif req.json().get('errorCode') == VOLUME_ALREADY_EXISTS:
            raise exceptions.VolumeExists(
                "Volume name '%s' already exists, cannot rename '%s'"
                % (new_volume_name, volume_id))
        else:
            raise exceptions.Error(
                "Error renaming volume '%s' to '%s': %s"
                % (volume_id, new_volume_name, req.json().get('message')))
    def get_volumeid(self, volume_name):
        """
        Return ScaleIO volume ID given a unique string volume name
        :param volume_name: Unique 32 character string name of the volume
        :return: ScaleIO ID of volume
        """

        volume_id = None

        if not volume_name:
            raise ValueError(
                'Invalid volume_name parameter, volume_name=%s' % volume_name)

        r_uri = '/api/types/Volume/instances/getByName::' + \
            utilities.encode_string(volume_name, double=True)
        req = self._get(r_uri)
        if req.status_code == 200:
            volume_id = req.json()
            LOG.debug('Retrieved volume id %s successfully', volume_id)
            return volume_id
        elif req.json().get('errorCode') == RESOURCE_NOT_FOUND_ERROR:
            raise exceptions.VolumeNotFound("Volume name '%s' is not found"
                                            % volume_name)
        else:
            raise exceptions.Error(
                "Error resolving volume name '%s' to id: %s"
                % (volume_name, req.json().get('message')))
    def get_pool_id(self, protection_domain, storage_pool):
        """
        Returns the id of a specified storage pool.
        :param: protection_domain. Name of the protection domain to query
        :param: storage_pool. Name of the storage pool within the protection domain
        :return: id of the specified storage pool
        """

        if not protection_domain:
            raise ValueError(
                'Invalid protection_domain parameter, protection_domain=%s'
                % protection_domain)
        if not storage_pool:
            raise ValueError(
                'Invalid storage_pool parameter, storage_pool=%s'
                % storage_pool)

        r_uri = '/api/types/StoragePool/instances/action/queryIdByKey'
        params = {
            'name': storage_pool, 'protectionDomainName': protection_domain}
        req = self._post(r_uri, params=params)
        if req.status_code == 200:
            pool_id = req.json()
        else:
            raise exceptions.Error('Error retrieving Pool ID: %s'
                                   % req.json().get('message'))

        return pool_id
    def _map_volume(self, volume_id, sdc_guid, allow_multi_map=True):
        """
        Private method maps a volume to a SDC
        :param volume_id: ScaleIO volume ID
        :param sdc_guid: Unique SDC identifier supplied by drv_cfg utility
        :param allow_multi_map: Allow this volume to be mapped to another SDC.
                                Actual for the first mapping operation only.
        :return: Nothing
        """

        multi_map = str(allow_multi_map).lower()
        params = {'guid': sdc_guid, 'allowMultipleMappings': multi_map}

        r_uri = '/api/instances/Volume::' + volume_id + '/action/addMappedSdc'
        req = self._post(r_uri, params=params)
        if req.status_code == 200:  # success
            LOG.debug('Mapped volume %s successfully', volume_id)
        elif req.json().get('errorCode') == VOLUME_ALREADY_MAPPED_ERROR:
            LOG.warn('Volume already mapped: %s', (req.json().get('message')))
            raise exceptions.VolumeAlreadyMapped(
                "Volume '%s' is already mapped: %s"
                % (volume_id, req.json().get('message')))
        else:
            raise exceptions.Error("Error mapping volume '%s': %s"
                                   % (volume_id, req.json().get('message')))
示例#11
0
    def storagepool_size(self, protection_domain, storage_pool):
        """
        For a given single storage pool, return the used, total and free space
        in bytes that can be allocated for Volumes. Note this is not the total
        capacity of the system.
        :param protection_domain: Protection domain name
        :param storage_pool: Storage pool name
        :return: Tuple used_bytes, total_bytes, free_bytes
        """

        if not protection_domain:
            raise ValueError(
                'Invalid protection_domain parameter, protection_domain=%s' %
                protection_domain)
        if not storage_pool:
            raise ValueError(
                'Invalid storage_pool parameter, storage_pool=%s' %
                storage_pool)

        used_bytes = 0
        total_bytes = 0
        free_bytes = 0

        pd_id = self._get_pdid(protection_domain)
        sp_id = self._get_spid(storage_pool, pd_id)

        r_uri = ('/api/types/StoragePool/instances/action/'
                 'querySelectedStatistics')
        params = {
            'ids': [sp_id],
            'properties': [
                'capacityAvailableForVolumeAllocationInKb',
                'capacityLimitInKb', 'spareCapacityInKb'
            ]
        }
        req = self._post(r_uri, params=params)

        if req.status_code == 200:
            stat = req.json()[sp_id]
            # Divide by two because ScaleIO creates a copy for each volume
            total_kb = (stat['capacityLimitInKb'] -
                        stat['spareCapacityInKb']) / 2
            # Free capacity for the storage pool
            free_kb = stat['capacityAvailableForVolumeAllocationInKb']
            # Calculate used capacity
            used_kb = total_kb - free_kb
            # convert to bytes
            used_bytes = used_kb * 1024
            total_bytes = total_kb * 1024
            free_bytes = free_kb * 1024
        else:
            raise exceptions.Error(
                'Error retrieving storage pool statistics: %s' %
                req.json().get('message'))

        return (used_bytes, total_bytes, free_bytes)
    def create_volume(self, volume_name, protection_domain, storage_pool,
                      provisioning_type='thick', volume_size_gb=8):
        """
        Add a volume. You can create a volume when the requested capacity is
        available. To start allocating volumes, the system requires that
        there be at least three SDS nodes.

        User-defined names are optional. You can define object names,
        according to the following rules:
          * Contain less than 32 characters
          * Contain only alphanumeric and punctuation characters
          * Be unique within the object type
        :param volume_name: Name of the volume you want to create
        :param protection_domain: Protection domain name
        :param storage_pool: Storage pool name
        :param provisioning_type: thick/ThickProvisioned or
                                  thin/ThinProvisioned
        :param volume_size_gb: The size of the volume in GB
                                (must be multiple of 8GB)
        :return: Tuple containing the volume id and volume name created
        """

        if not volume_name:
            raise ValueError(
                'Invalid volume_name parameter, volume_name=%s' % volume_name)

        # get protection domain id for request store this for the duration of
        # the object
        pd_id = self._get_pdid(protection_domain)
        sp_id = self._get_spid(storage_pool, pd_id)
        # create requires size in KB, so we will convert and check size is
        # multiple of 8GB
        volume_size_kb = self._validate_size(
            volume_size_gb, utilities.UnitSize.GBYTE, utilities.UnitSize.KBYTE)

        # request payload containing volume create params
        params = {'protectionDomainId': pd_id,
                  'volumeSizeInKb': str(volume_size_kb),
                  'name': volume_name,
                  'volumeType': self._get_provisiontype(provisioning_type),
                  'storagePoolId': sp_id}

        r_uri = '/api/types/Volume/instances'
        req = self._post(r_uri, params=params)
        if req.status_code == 200:
            volume_id = req.json().get('id')
            LOG.debug('Created volume %s successfully', volume_id)
        elif req.json().get('errorCode') == VOLUME_ALREADY_EXISTS:
            raise exceptions.VolumeExists("Volume name '%s' already exists"
                                          % volume_name)
        else:
            raise exceptions.Error("Error creating volume '%s': %s"
                                   % (volume_name, req.json().get('message')))

        return volume_id, volume_name
示例#13
0
    def list_volume_infos(self):

        infos = []
        r_uri = '/api/types/Volume/instances'
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error listing volumes: %s' %
                                   req.json().get('message'))

        volume_objects = req.json()
        for volume in volume_objects:
            infos.append({'id': volume['id'], 'name': volume['name']})

        return infos
示例#14
0
    def get_storage_pool_properties_from_id(self, storage_pool_id):
        """
        Returns the properties of the specified storage pool.
        :param: storage_pool_id. ID of the storage pool
        :return: dict of storage pool properties
        """

        r_uri = '/api/instances/StoragePool::' + storage_pool_id
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error retrieving storage pool props: %s'
                                   % req.json().get('message'))

        return req.json()
示例#15
0
    def get_protection_domain_properties_from_id(self, protection_domain_id):
        """
        Returns the properties of the specified protection domain.
        :param: protection_domain_id. ID of the protection domain
        :return: dict of protection domain properties
        """

        r_uri = '/api/instances/ProtectionDomain::' + protection_domain_id
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error retrieving protection domain props: %s'
                                   % req.json().get('message'))

        return req.json()
示例#16
0
    def get_configuration(self):
        """
        Returns the system, configuration
        :return: dict of system properties
        """

        r_uri = '/api/Configuration'
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error retrieving system properties: %s'
                                   % req.json().get('message'))

        props = req.json()

        return props
示例#17
0
    def is_volume_attached(self, volume_id_or_name, sdc_guid):
        """
        Check if a volume is attached to a SDC
        :param volume_id_or_name: ScaleIO volume ID or volume name
        :sdc_guid GUID of SDC
        :return: True if the volume is attached to the SDC
        """
        volume_object = self._volume(volume_id_or_name)

        params = {'ids': [si['sdcId'] for si in volume_object.mappedSdcInfo]}
        r_uri = '/api/types/Sdc/instances/action/queryBySelectedIds'
        req = self._post(r_uri, params=params)
        if req.status_code != 200:
            raise exceptions.Error('Error requesting SDC: %s' %
                                   req.json.get('message'))
        return any(sdc_guid == sdc['sdcGuid'] for sdc in req.json())
    def get_scaleio_api_version(self):
        """
        Returns ScaleIO REST API version.
        This may or may not match the version of ScaleIO, but
        will be in the form of n.n.n, matching the regex: '\d+\.\d+\.\d+'
        :return: String containing version number
        """

        r_uri = '/api/version'
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error retrieving api version information: %s'
                                   % req.json().get('message'))

        version = req.text.replace('\"', '')

        return version
    def list_protection_domain_infos(self):
        """
        Returns list of protection domains in the system.
        Each item in the list will include the name and id of the protection domain
        :return: list of protection domain dictionaries, containing 'name' and 'id'
        """

        infos = []
        r_uri = '/api/types/ProtectionDomain/instances'
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error('Error listing protection domains: %s'
                                   % req.json().get('message'))

        pd_objects = req.json()
        for pd in pd_objects:
            infos.append({'id': pd['id'],
                          'name': pd['name']})

        return infos
示例#20
0
    def snapshot_volume(self, volume_id_or_name, snapshot_name):
        """
        Snapshot an existing volume. The ScaleIO storage system
        enables you to take snapshots of existing volumes,
        up to 31 per volume. The snapshots are thinly provisioned
        and are extremely quick. Once a snapshot is generated,
        it becomes a new, unmapped volume in the system.
        :param volume_id_or_name: ScaleIO volume ID or volume name
        :param snapshot_name: Name of the snapshot
        :return: Tuple containing the volume id of snapshot and volume list
        """

        volume_id = self._validate_volume_id(volume_id_or_name)
        if not snapshot_name:
            raise ValueError(
                'Invalid snapshot snapshot_name parameter, snapshot_name=%s' %
                snapshot_name)

        snapshot_gid = volume_list = None
        params = {
            'snapshotDefs': [{
                'volumeId': volume_id,
                'snapshotName': snapshot_name
            }]
        }

        r_uri = '/api/instances/System/action/snapshotVolumes'
        req = self._post(r_uri, params=params)
        if req.status_code == 200:
            snapshot_gid = req.json().get('snapshotGroupId')
            volume_list = req.json().get('volumeIdList')
        elif req.json().get('errorCode') == VOLUME_ALREADY_EXISTS:
            raise exceptions.VolumeExists(
                "Volume name '%s' already exists, cannot make snapshot '%s'" %
                (snapshot_name, volume_id))
        else:
            raise exceptions.Error(
                "Error making snapshot '%s' of volume '%s': %s" %
                (snapshot_name, volume_id, req.json().get('message')))

        return snapshot_gid, volume_list
示例#21
0
    def get_volume_properties(self, volume_id_or_name):
        """
        Returns properties of a ScaleIO volume
        :param volume_id_or_name: ScaleIO volume ID or volume name
        :return: Dict containing volume properties
        """

        volume_id = self._validate_volume_id(volume_id_or_name)

        r_uri = '/api/instances/Volume::' + volume_id
        req = self._get(r_uri)
        if req.status_code == 200:  # success
            LOG.debug('Retrieved volume object %s successfully', volume_id)
        elif req.json().get('errorCode') == VOLUME_NOT_FOUND_ERROR:
            raise exceptions.VolumeNotFound('Volume %s is not found'
                                            % volume_id)
        else:
            raise exceptions.Error("Error retrieving volume '%s': %s"
                                   % (volume_id, req.json().get('message')))

        return req.json()
示例#22
0
    def snapshot_volume_from_defs(self, snapshot_defs):
        """
        Snapshot existing volume(s) from a list of volume/snapshots.
        Enables the taking of snapshots of multiple existing volumes,
        up to 31 per volume. The snapshots are thinly provisioned
        and are extremely quick. Once a snapshot is generated,
        it becomes a new, unmapped volume in the system.
        :param snapshot_defs: List of maps of volumeID->snapshot_names
        :return: List of newly created snapshots
        """

        if not snapshot_defs:
            raise ValueError(
                'Invalid snapshot snapshot_defs parameter, None')

        params = {'snapshotDefs': snapshot_defs}

        r_uri = '/api/instances/System/action/snapshotVolumes'
        req = self._post(r_uri, params=params)
        if req.status_code != 200:
            raise exceptions.Error(
                "Error making snapshots from: %s" % snapshot_defs)

        return req.json()
    def _get_spid(self, storage_pool, pd_id):
        """
        Private method retrieves the ScaleIO storage pool ID. ScaleIO objects
        are assigned a unique ID that can be used to identify the object.
        :param storage_pool: Unique 32 character string name of Storage Pool
        :param pd_id: Protection domain id associated with storage pool
        :return: Storage pool id
        """

        if not storage_pool:
            raise ValueError(
                'Invalid storage_pool parameter, storage_pool=%s'
                % storage_pool)

        # request uri to retrieve sp id
        r_uri = '/api/types/Pool/instances/getByName::' + \
            pd_id + ',' + utilities.encode_string(storage_pool, double=True)
        req = self._get(r_uri)
        if req.status_code != 200:
            raise exceptions.Error(
                'Error retrieving ScaleIO storage pool ID for %s: %s'
                % (storage_pool, req.content))

        return req.json()
    def _unmap_volume(self, volume_id, sdc_guid=None, unmap_all=False):
        """
        Private method unmaps a volume from one or all SDCs.
        :param volume_id: ScaleIO volume ID
        :param sdc_guid: Unique SDC identifier
        :param unmap_all: True, unmap from all SDCs, False only unmap from
                          local SDC
        :return: Nothing
        """

        if not unmap_all:
            if not sdc_guid:
                raise ValueError(
                    'sdc_guid must be specified or unmap_all must be True')
            else:
                LOG.debug('Using ScaleIO SDC client GUID %s for '
                          'unmap operation.' % sdc_guid)

        if unmap_all:  # unmap from all sdcs
            params = {'allSdcs': ''}
        else:  # only unmap from local sdc
            params = {'guid': sdc_guid}

        r_uri = '/api/instances/Volume::' + \
            volume_id + '/action/removeMappedSdc'
        req = self._post(r_uri, params=params)
        if req.status_code == 200:  # success
            LOG.debug('Unmapped volume %s successfully', volume_id)
        elif req.json().get('errorCode') == VOLUME_NOT_MAPPED_ERROR:
            LOG.warn('Volume %s cannot be unmapped: %s',
                     volume_id, req.json().get('message'))
            raise exceptions.VolumeNotMapped("Volume '%s' is not mapped"
                                             % volume_id)
        else:
            raise exceptions.Error("Error unmapping volume '%s': %s"
                                   % (volume_id, req.json().get('message')))
    def _volume(self, volume_id_or_name):
        """
        Return a ScaleIOVolume object
        :param volume_id_or_name: ScaleIO volume ID or volume name
        :return: ScaleIOVolume object or None if no valid volume found
        """

        volume_id = self._validate_volume_id(volume_id_or_name)

        volume_obj = None

        r_uri = '/api/instances/Volume::' + volume_id
        req = self._get(r_uri)
        if req.status_code == 200:  # success
            LOG.debug('Retrieved volume object %s successfully', volume_id)
            volume_obj = _ScaleIOVolume(req.json())
        elif req.json().get('errorCode') == VOLUME_NOT_FOUND_ERROR:
            raise exceptions.VolumeNotFound('Volume %s is not found'
                                            % volume_id)
        else:
            raise exceptions.Error("Error retrieving volume '%s': %s"
                                   % (volume_id, req.json().get('message')))

        return volume_obj
    def delete_volume(self, volume_id_or_name, include_descendents=False,
                      only_descendents=False, vtree=False,
                      unmap_on_delete=False, force_delete=True):
        """
        Delete a volume. This command removes a ScaleIO volume. Before
        removing a volume, you must ensure that it is not mapped to any SDCs.

        When removing a volume, you can remove the VTree as well
        (all related snapshots), the volume and its snapshots, or
        just the snapshots. Before removing a VTree, you must unmap
        all volumes in the VTree before removing them.

        Note: Removal of a volume erases all the data on the corresponding
        volume.

        :param volume_id_or_name: ScaleIO volume ID or volume name
        :param include_descendents: Remove volume along with any descendents
        :param only_descendents: Remove only the descendents of the volume
        :param vtree: Remove the entire VTREE
        :param unmap_on_delete: Unmap volume from all SDCs before deleting
        :param force_delete: Ignore if volume is already deleted
        :return: Nothing
        """

        volume_id = self._validate_volume_id(volume_id_or_name)

        # the removeMode options are mutually exclusive
        if [include_descendents, only_descendents, vtree].count(True) > 1:
            raise ValueError(
                'Only one removeMode flag can be specified '
                '(include_descendants, only_descendants, vtree)')

        # default removeMode is ONLY_ME
        remove_mode = 'ONLY_ME'
        if (include_descendents):
            remove_mode = 'INCLUDING_DESCENDANTS'
        if (only_descendents):
            remove_mode = 'DESCENDANTS_ONLY'
        if (vtree):
            remove_mode = 'WHOLE_VTREE'

        params = {'removeMode': remove_mode}

        if unmap_on_delete:
            LOG.debug('Unmap before delete flag True, attempting '
                      'to unmap volume %s from all sdcs before deletion',
                      volume_id)
            try:
                self._unmap_volume(volume_id, unmap_all=True)
            except exceptions.VolumeNotMapped:
                pass

        r_uri = '/api/instances/Volume::' + volume_id + '/action/removeVolume'
        req = self._post(r_uri, params=params)
        if req.status_code == 200:
            LOG.debug('Removed volume %s successfully', volume_id)
        elif req.json().get('errorCode') == VOLUME_NOT_FOUND_ERROR:
            if not force_delete:
                LOG.warn('Error removing volume %s: %s', volume_id,
                         (req.json().get('message')))
                raise exceptions.VolumeNotFound("Volume '%s' is not found"
                                                % volume_id)
        else:
            raise exceptions.Error("Error removing volume '%s': %s"
                                   % (volume_id, req.json().get('message')))