# Copyright (c) 2020 Dell Inc. or its subsidiaries.
# 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 http.client as http_client
import json
import re
import urllib.parse

from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import units
import requests
from requests.exceptions import Timeout

from cinder import exception
from cinder.i18n import _
from cinder.utils import retry
from cinder.volume.drivers.dell_emc.powerflex import simplecache
from cinder.volume.drivers.dell_emc.powerflex import utils as flex_utils

LOG = logging.getLogger(__name__)


VOLUME_MIGRATION_IN_PROGRESS_ERROR = 717
VOLUME_MIGRATION_ALREADY_ON_DESTINATION_POOL_ERROR = 718
VOLUME_NOT_FOUND_ERROR = 79
OLD_VOLUME_NOT_FOUND_ERROR = 78
TOO_MANY_SNAPS_ERROR = 182
ILLEGAL_SYNTAX = 0

MAX_SNAPS_IN_VTREE = 126
TOKEN_REFRESH_SUCC_INTERVAL = 300
TOKEN_REFRESH_FAIL_INTERVAL = 60


class RestClient(object):
    def __init__(self, configuration, is_primary=True):
        self.configuration = configuration
        self.is_primary = is_primary
        self.spCache = simplecache.SimpleCache("Storage Pool", age_minutes=5)
        self.pdCache = simplecache.SimpleCache("Protection Domain",
                                               age_minutes=5)
        self.rcgCache = simplecache.SimpleCache("Replication CG",
                                                age_minutes=5)
        self.rest_ip = None
        self.rest_port = None
        self.rest_username = None
        self.rest_password = None
        self.rest_api_version = None
        self.verify_certificate = None
        self.certificate_path = None
        self.base_url = None
        self.is_configured = False
        self.rest_api_connect_timeout = 30
        self.rest_api_read_timeout = 30

    @staticmethod
    def _get_headers():
        return {"content-type": "application/json"}

    def do_setup(self):
        if self.is_primary:
            get_config_value = self.configuration.safe_get
        else:
            replication_targets = self.configuration.safe_get(
                "replication_device"
            )
            if not replication_targets:
                return
            elif len(replication_targets) > 1:
                msg = _("PowerFlex does not support more than one "
                        "replication backend.")
                raise exception.InvalidInput(reason=msg)
            get_config_value = replication_targets[0].get
        self.verify_certificate = bool(
            get_config_value("sio_verify_server_certificate") or
            get_config_value("driver_ssl_cert_verify")
        )
        self.rest_ip = get_config_value("san_ip")
        self.rest_port = int(
            get_config_value("powerflex_rest_server_port") or
            get_config_value("sio_rest_server_port") or
            443
        )
        self.rest_username = get_config_value("san_login")
        self.rest_password = get_config_value("san_password")
        self.rest_api_connect_timeout = (
            get_config_value(flex_utils.POWERFLEX_REST_CONNECT_TIMEOUT)
            or self.rest_api_connect_timeout)
        self.rest_api_read_timeout = (
            get_config_value(flex_utils.POWERFLEX_REST_READ_TIMEOUT)
            or self.rest_api_read_timeout)
        if self.verify_certificate:
            self.certificate_path = (
                get_config_value("sio_server_certificate_path") or
                get_config_value("driver_ssl_cert_path")
            )
        if not all([self.rest_ip, self.rest_username, self.rest_password]):
            msg = _("REST server IP, username and password must be specified.")
            raise exception.InvalidInput(reason=msg)
        # validate certificate settings
        if self.verify_certificate and not self.certificate_path:
            msg = _("Path to REST server's certificate must be specified.")
            raise exception.InvalidInput(reason=msg)
        # log warning if not using certificates
        if not self.verify_certificate:
            LOG.warning("Verify certificate is not set, using default of "
                        "False.")
        self.base_url = ("https://%(server_ip)s:%(server_port)s/api" %
                         {
                             "server_ip": self.rest_ip,
                             "server_port": self.rest_port
                         })
        LOG.info("REST server IP: %(ip)s, port: %(port)s, "
                 "username: %(user)s, rest connect timeout: "
                 "%(rest_api_connect_timeout)s, rest read timeout: "
                 "%(rest_api_read_timeout)s. Verify server's certificate: "
                 "%(verify_cert)s.",
                 {
                     "ip": self.rest_ip,
                     "port": self.rest_port,
                     "user": self.rest_username,
                     "verify_cert": self.verify_certificate,
                     "rest_api_connect_timeout": self.rest_api_connect_timeout,
                     "rest_api_read_timeout": self.rest_api_read_timeout
                 })
        self.is_configured = True
        # Set PowerFlex rest token
        self._set_rest_token()

    def _set_rest_token(self):
        self.rest_token = None

        # Initilaize PowerFlex rest token
        LOG.info("Initialize PowerFlex rest token.")
        self._refresh_token()

        # Start token refresh thread
        self._start_token_refresh_thread()

    def _start_token_refresh_thread(self):
        LOG.info("Start token refresh thread.")
        timer = loopingcall.DynamicLoopingCall(
            self._refresh_token_periodically)
        timer.start(initial_delay=0)

    def _refresh_token_periodically(self):
        if self._refresh_token():
            LOG.info("Token refresh succeeded. Sleeping for %d seconds.",
                     TOKEN_REFRESH_SUCC_INTERVAL)
            return TOKEN_REFRESH_SUCC_INTERVAL

        # Token refresh failed. Continue using current token.
        LOG.warning("Token refresh failed. Sleeping for %d seconds.",
                    TOKEN_REFRESH_FAIL_INTERVAL)
        return TOKEN_REFRESH_FAIL_INTERVAL

    def _refresh_token(self):
        login_url = "/login"
        login_request = self.base_url + login_url
        verify_cert = self._get_verify_cert()
        timeout = (self.rest_api_connect_timeout,
                   self.rest_api_read_timeout)

        try:
            r = requests.get(login_request,
                             auth=(self.rest_username, self.rest_password),
                             verify=verify_cert, timeout=timeout)
            token = r.json()
        except (requests.exceptions.RequestException, ValueError) as e:
            LOG.warning("Failed to refresh token: %s", str(e))
            return False

        self.rest_token = token
        return True

    def query_rest_api_version(self, fromcache=True):
        url = "/version"

        if self.rest_api_version is None or fromcache is False:
            r, unused = self.execute_powerflex_get_request(url)
            if r.status_code == http_client.OK:
                self.rest_api_version = r.text.replace('\"', "")
                LOG.info("REST API Version: %(api_version)s.",
                         {"api_version": self.rest_api_version})
            else:
                msg = (_("Failed to query REST API version. "
                         "Status code: %d.") % r.status_code)
                raise exception.VolumeBackendAPIException(data=msg)
            # make sure the response was valid
            pattern = re.compile(r"^\d+(\.\d+)*$")
            if not pattern.match(self.rest_api_version):
                msg = (_("Failed to query REST API version. Response: %s.") %
                       r.text)
                raise exception.VolumeBackendAPIException(data=msg)
        return self.rest_api_version

    def query_volume(self, vol_id):
        url = "/instances/Volume::%(vol_id)s"

        r, response = self.execute_powerflex_get_request(url, vol_id=vol_id)
        if r.status_code != http_client.OK and "errorCode" in response:
            msg = (_("Failed to query volume: %s.") % response["message"])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return response

    def create_volume(self,
                      protection_domain_name,
                      storage_pool_name,
                      volume_id,
                      volume_size,
                      provisioning,
                      compression):
        url = "/types/Volume/instances"

        domain_id = self._get_protection_domain_id(protection_domain_name)
        LOG.info("Protection Domain id: %s.", domain_id)
        pool_id = self.get_storage_pool_id(protection_domain_name,
                                           storage_pool_name)
        LOG.info("Storage Pool id: %s.", pool_id)
        volume_name = flex_utils.id_to_base64(volume_id)
        # units.Mi = 1024 ** 2
        volume_size_kb = volume_size * units.Mi
        params = {
            "protectionDomainId": domain_id,
            "storagePoolId": pool_id,
            "name": volume_name,
            "volumeType": provisioning,
            "volumeSizeInKb": str(volume_size_kb),
            "compressionMethod": compression,
        }
        r, response = self.execute_powerflex_post_request(url, params)
        if r.status_code != http_client.OK and "errorCode" in response:
            msg = (_("Failed to create volume: %s.") % response["message"])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return response["id"]

    def snapshot_volume(self, volume_provider_id, snapshot_id):
        url = "/instances/System/action/snapshotVolumes"

        snap_name = flex_utils.id_to_base64(snapshot_id)
        params = {
            "snapshotDefs": [
                {
                    "volumeId": volume_provider_id,
                    "snapshotName": snap_name,
                },
            ],
        }
        r, response = self.execute_powerflex_post_request(url, params)
        if r.status_code != http_client.OK and "errorCode" in response:
            msg = (_("Failed to create snapshot for volume %(vol_name)s: "
                     "%(response)s.") %
                   {"vol_name": volume_provider_id,
                    "response": response["message"]})
            LOG.error(msg)
            # check if the volume reached snapshot limit
            if ("details" in response and
                    response["details"][0]["rc"] == TOO_MANY_SNAPS_ERROR):
                raise exception.SnapshotLimitReached(
                    set_limit=MAX_SNAPS_IN_VTREE)
            raise exception.VolumeBackendAPIException(data=msg)
        return response["volumeIdList"][0]

    def _get_replication_cg_id_by_name(self, rcg_name):
        url = ("/types/ReplicationConsistencyGroup/instances"
               "/action/queryIdByKey")

        if not rcg_name:
            msg = _("Unable to query Replication CG id with None name.")
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        cached_val = self.rcgCache.get_value(rcg_name)
        if cached_val is not None:
            return cached_val
        encoded_rcg_name = urllib.parse.quote(rcg_name, "")
        params = {"name": encoded_rcg_name}
        r, rcg_id = self.execute_powerflex_post_request(url, params)
        if not rcg_id:
            msg = (_("Replication CG with name %s wasn't found.") % rcg_id)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        if r.status_code != http_client.OK and "errorCode" in rcg_id:
            msg = (_("Failed to get Replication CG id with name "
                     "%(name)s: %(message)s.") %
                   {"name": rcg_name, "message": rcg_id["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        LOG.info("Replication CG id: %s.", rcg_id)
        self.rcgCache.update(rcg_name, rcg_id)
        return rcg_id

    def _query_volumes_pair(self,
                            pair_id):
        url = "/instances/ReplicationPair::%(pair_id)s"

        r, response = self.execute_powerflex_get_request(url, pair_id=pair_id)
        if r.status_code != http_client.OK and "errorCode" in response:
            msg = (_("Failed to query volumes pair %(pair_id)s: %(err)s.") %
                   {"pair_id": pair_id, "err": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return response

    def _query_replication_pairs(self):
        url = "/types/ReplicationPair/instances"

        r, response = self.execute_powerflex_get_request(url)
        if r.status_code != http_client.OK and "errorCode" in response:
            msg = (_("Failed to query replication pairs: %s.") %
                   response["message"])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return response

    @staticmethod
    def _filter_replication_pairs(replication_pairs,
                                  filter_key,
                                  filter_value):
        try:
            return next(filter(lambda pair: pair[filter_key] == filter_value,
                               replication_pairs))
        except StopIteration:
            msg = (_("Volume pair for volume with id %s is not found.")
                   % filter_value)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def get_volumes_pair_attrs(self, filter_key, filter_value):
        replication_pairs = self._query_replication_pairs()
        founded = self._filter_replication_pairs(replication_pairs,
                                                 filter_key,
                                                 filter_value)
        pair_id = founded["id"]
        remote_pair_id = founded["remoteId"]
        vol_provider_id = founded["localVolumeId"]
        remote_vol_provider_id = founded["remoteVolumeId"]
        return pair_id, remote_pair_id, vol_provider_id, remote_vol_provider_id

    def create_volumes_pair(self,
                            rcg_name,
                            source_provider_id,
                            dest_provider_id):
        url = "/types/ReplicationPair/instances"

        rcg_id = self._get_replication_cg_id_by_name(rcg_name)
        params = {
            "name": source_provider_id,
            "replicationConsistencyGroupId": rcg_id,
            "copyType": "OnlineCopy",
            "sourceVolumeId": source_provider_id,
            "destinationVolumeId": dest_provider_id,
        }
        r, response = self.execute_powerflex_post_request(url, params, )
        if r.status_code != http_client.OK and "errorCode" in response:
            msg = (_("Failed to create volumes pair: %s.") %
                   response["message"])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        replication_pair = self._query_volumes_pair(response["id"])
        LOG.info("Created volumes pair %(vol_pair_id)s. "
                 "Remote pair %(remote_pair_id)s.",
                 {
                     "vol_pair_id": replication_pair["id"],
                     "remote_pair_id": replication_pair["remoteId"],
                 })
        return replication_pair["id"], replication_pair["remoteId"]

    def remove_volumes_pair(self, vol_pair_id):
        url = ("/instances/ReplicationPair::%(vol_pair_id)s/action"
               "/removeReplicationPair")

        r, response = self.execute_powerflex_post_request(
            url, vol_pair_id=vol_pair_id
        )
        if r.status_code != http_client.OK:
            msg = (_("Failed to delete volumes pair "
                     "%(vol_pair_id)s: %(err)s.") %
                   {"vol_pair_id": vol_pair_id, "err": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def _get_protection_domain_id_by_name(self, domain_name):
        url = "/types/Domain/instances/getByName::%(encoded_domain_name)s"

        if not domain_name:
            msg = _("Unable to query Protection Domain id with None name.")
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        encoded_domain_name = urllib.parse.quote(domain_name, "")
        r, domain_id = self.execute_powerflex_get_request(
            url, encoded_domain_name=encoded_domain_name
        )
        if not domain_id:
            msg = (_("Prorection Domain with name %s wasn't found.")
                   % domain_name)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        if r.status_code != http_client.OK and "errorCode" in domain_id:
            msg = (_("Failed to get Protection Domain id with name "
                     "%(name)s: %(err_msg)s.") %
                   {"name": domain_name, "err_msg": domain_id["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        LOG.info("Protection Domain id: %s.", domain_id)
        return domain_id

    def _get_protection_domain_id(self, domain_name):
        response = self._get_protection_domain_properties(domain_name)
        if response is None:
            return None
        return response["id"]

    def _get_protection_domain_properties(self, domain_name):
        url = "/instances/ProtectionDomain::%(domain_id)s"

        cached_val = self.pdCache.get_value(domain_name)
        if cached_val is not None:
            return cached_val
        domain_id = self._get_protection_domain_id_by_name(domain_name)
        r, response = self.execute_powerflex_get_request(
            url, domain_id=domain_id
        )
        if r.status_code != http_client.OK:
            msg = (_("Failed to get domain properties from id %(domain_id)s: "
                     "%(err_msg)s.") %
                   {"domain_id": domain_id, "err_msg": response})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        self.pdCache.update(domain_name, response)
        return response

    def _get_storage_pool_id_by_name(self, domain_name, pool_name):
        url = ("/types/Pool/instances/getByName::"
               "%(domain_id)s,%(encoded_pool_name)s")

        if not domain_name or not pool_name:
            msg = (_("Unable to query storage pool id for "
                     "Pool %(pool_name)s and Domain %(domain_name)s.") %
                   {"pool_name": pool_name, "domain_name": domain_name})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        domain_id = self._get_protection_domain_id(domain_name)
        encoded_pool_name = urllib.parse.quote(pool_name, "")
        r, pool_id = self.execute_powerflex_get_request(
            url, domain_id=domain_id, encoded_pool_name=encoded_pool_name
        )
        if not pool_id:
            msg = (_("Pool with name %(pool_name)s wasn't found in "
                     "domain %(domain_id)s.") %
                   {"pool_name": pool_name, "domain_id": domain_id})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        if r.status_code != http_client.OK and "errorCode" in pool_id:
            msg = (_("Failed to get pool id from name %(pool_name)s: "
                     "%(err_msg)s.") %
                   {"pool_name": pool_name, "err_msg": pool_id["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        LOG.info("Pool id: %s.", pool_id)
        return pool_id

    def get_storage_pool_properties(self, domain_name, pool_name):
        url = "/instances/StoragePool::%(pool_id)s"

        fullname = "{}:{}".format(domain_name, pool_name)
        cached_val = self.spCache.get_value(fullname)
        if cached_val is not None:
            return cached_val
        pool_id = self._get_storage_pool_id_by_name(domain_name, pool_name)
        r, response = self.execute_powerflex_get_request(url, pool_id=pool_id)
        if r.status_code != http_client.OK:
            msg = (_("Failed to get pool properties from id %(pool_id)s: "
                     "%(err_msg)s.") %
                   {"pool_id": pool_id, "err_msg": response})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        self.spCache.update(fullname, response)
        return response

    def get_storage_pool_id(self, domain_name, pool_name):
        response = self.get_storage_pool_properties(domain_name, pool_name)
        if response is None:
            return None
        return response["id"]

    def _get_verify_cert(self):
        verify_cert = False
        if self.verify_certificate:
            verify_cert = self.certificate_path
        return verify_cert

    def execute_powerflex_get_request(self, url, **url_params):
        r = requests.Response
        try:
            request = self.base_url + url % url_params
            timeout = (self.rest_api_connect_timeout,
                       self.rest_api_read_timeout)
            r = requests.get(request,
                             auth=(self.rest_username, self.rest_token),
                             verify=self._get_verify_cert(), timeout=timeout)
            r = self._check_response(r, request)
            response = r.json()
        except Timeout as e:
            r.status_code = http_client.INTERNAL_SERVER_ERROR
            msg = _("The request to URL %s failed with timeout "
                    "exception %s" % (url, str(e)))
            LOG.error(msg)
            response = {'errorCode': http_client.INTERNAL_SERVER_ERROR,
                        'message': msg}
        return r, response

    def execute_powerflex_post_request(self, url, params=None, **url_params):
        r = requests.Response
        try:
            if not params:
                params = {}
            request = self.base_url + url % url_params
            timeout = (self.rest_api_connect_timeout,
                       self.rest_api_read_timeout)
            r = requests.post(request,
                              data=json.dumps(params),
                              headers=self._get_headers(),
                              auth=(self.rest_username, self.rest_token),
                              verify=self._get_verify_cert(), timeout=timeout)
            r = self._check_response(r, request, params)
            try:
                response = r.json()
            except ValueError:
                response = None
        except Timeout as e:
            r.status_code = http_client.INTERNAL_SERVER_ERROR
            msg = _("The request to URL %s failed with timeout "
                    "exception %s" % (url, str(e)))
            LOG.error(msg)
            response = {'errorCode': http_client.INTERNAL_SERVER_ERROR,
                        'message': msg}
        return r, response

    def _check_response(self,
                        response,
                        request,
                        params=None):

        level = logging.DEBUG
        # for anything other than an OK from the REST API, log an error
        if response.status_code != http_client.OK:
            level = logging.ERROR
        LOG.log(level,
                "REST Request: %s with params %s",
                request,
                json.dumps(params))
        LOG.log(level,
                "REST Response: %s with data %s",
                response.status_code,
                response.text)
        return response

    @retry(exception.VolumeBackendAPIException)
    def extend_volume(self, vol_id, new_size):
        url = "/instances/Volume::%(vol_id)s/action/setVolumeSize"

        round_volume_capacity = (
            self.configuration.powerflex_round_volume_capacity
        )
        if not round_volume_capacity and not new_size % 8 == 0:
            LOG.warning("PowerFlex only supports volumes with a granularity "
                        "of 8 GBs. The new volume size is: %d.",
                        new_size)
        params = {"sizeInGB": str(new_size)}
        r, response = self.execute_powerflex_post_request(url,
                                                          params,
                                                          vol_id=vol_id)
        if r.status_code != http_client.OK:
            response = r.json()
            msg = (_("Failed to extend volume %(vol_id)s: %(err)s.") %
                   {"vol_id": vol_id, "err": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def _unmap_volume_from_all_sdcs(self, vol_id):
        url = "/instances/Volume::%(vol_id)s/action/removeMappedSdc"

        volume_is_mapped = False
        try:
            volume = self.query_volume(vol_id)
            if volume.get("mappedSdcInfo") is not None:
                volume_is_mapped = True
        except exception.VolumeBackendAPIException:
            LOG.info("Volume %s is not found thus is not mapped to any SDC.",
                     vol_id)
        if volume_is_mapped:
            params = {"allSdcs": ""}
            LOG.info("Unmap volume from all sdcs.")
            r, response = self.execute_powerflex_post_request(url,
                                                              params,
                                                              vol_id=vol_id)
            if r.status_code != http_client.OK:
                msg = (_("Failed to unmap volume %(vol_id)s from all SDCs: "
                         "%(err)s.") % {"vol_id": vol_id,
                                        "err": response["message"]})
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

    @retry(exception.VolumeBackendAPIException)
    def remove_volume(self, vol_id):
        url = "/instances/Volume::%(vol_id)s/action/removeVolume"

        self._unmap_volume_from_all_sdcs(vol_id)
        params = {"removeMode": "ONLY_ME"}
        r, response = self.execute_powerflex_post_request(url,
                                                          params,
                                                          vol_id=vol_id)
        if r.status_code != http_client.OK:
            error_code = response["errorCode"]
            if error_code == VOLUME_NOT_FOUND_ERROR:
                LOG.warning("Ignoring error in delete volume %s: "
                            "Volume not found.", vol_id)
            elif vol_id is None:
                LOG.warning("Volume does not have provider_id thus does not "
                            "map to PowerFlex volume. "
                            "Allowing deletion to proceed.")
            else:
                msg = (_("Failed to delete volume %(vol_id)s: %(err)s.") %
                       {"vol_id": vol_id, "err": response["message"]})
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)

    def is_volume_creation_safe(self, protection_domain, storage_pool):
        """Checks if volume creation is safe or not.

        Using volumes with zero padding disabled can lead to existing data
        being read off of a newly created volume.
        """

        # if we have been told to allow unsafe volumes
        if self.configuration.powerflex_allow_non_padded_volumes:
            # Enabled regardless of type, so safe to proceed
            return True
        try:
            properties = self.get_storage_pool_properties(
                protection_domain, storage_pool
            )
            padded = properties["zeroPaddingEnabled"]
        except Exception:
            msg = (_("Unable to retrieve properties for pool %s.") %
                   storage_pool)
            raise exception.InvalidInput(reason=msg)
        # zero padded storage pools are safe
        if padded:
            return True
        # if we got here, it's unsafe
        return False

    def rename_volume(self, volume, name):
        url = "/instances/Volume::%(id)s/action/setVolumeName"

        new_name = flex_utils.id_to_base64(name)
        vol_id = volume["provider_id"]
        params = {"newName": new_name}
        r, response = self.execute_powerflex_post_request(url,
                                                          params,
                                                          id=vol_id)
        if r.status_code != http_client.OK:
            error_code = response["errorCode"]
            if ((error_code == VOLUME_NOT_FOUND_ERROR or
                 error_code == OLD_VOLUME_NOT_FOUND_ERROR or
                 error_code == ILLEGAL_SYNTAX)):
                LOG.info("Ignore renaming action because the volume "
                         "%(vol_id)s is not a PowerFlex volume.",
                         {"vol_id": vol_id})
            else:
                msg = (_("Failed to rename volume %(vol_id)s: %(err)s.") %
                       {"vol_id": vol_id, "err": response["message"]})
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)
        else:
            LOG.info("PowerFlex volume %(vol_id)s was renamed to "
                     "%(new_name)s.", {"vol_id": vol_id, "new_name": new_name})

    def failover_failback_replication_cg(self, rcg_name, is_failback):
        url = ("/instances/ReplicationConsistencyGroup::%(rcg_id)s"
               "/action/%(action)sReplicationConsistencyGroup")

        action = "restore" if is_failback else "failover"
        rcg_id = self._get_replication_cg_id_by_name(rcg_name)
        r, response = self.execute_powerflex_post_request(url,
                                                          rcg_id=rcg_id,
                                                          action=action)
        if r.status_code != http_client.OK:
            msg = (_("Failed to %(action)s rcg with id %(rcg_id)s: "
                     "%(err_msg)s.") % {"action": action,
                                        "rcg_id": rcg_id,
                                        "err_msg": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def query_vtree(self, vtree_id, vol_id):
        url = "/instances/VTree::%(vtree_id)s"

        r, response = self.execute_powerflex_get_request(url,
                                                         vtree_id=vtree_id)
        if r.status_code != http_client.OK:
            msg = (_("Failed to check migration status of volume %s.")
                   % vol_id)
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(msg)
        return response

    def query_vtree_statistics(self, vtree_id):
        url = "/instances/VTree::%(vtree_id)s/relationships/Statistics"

        r, response = self.execute_powerflex_get_request(url,
                                                         vtree_id=vtree_id)
        if r.status_code != http_client.OK and "errorCode" in response:
            msg = (_("Failed to query vtree statistics: %s.") %
                   response["message"])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return response

    def migrate_vtree(self, volume, params):
        url = "/instances/Volume::%(vol_id)s/action/migrateVTree"

        r, response = self.execute_powerflex_post_request(
            url,
            params=params,
            vol_id=volume.provider_id
        )
        if r.status_code != http_client.OK:
            error_code = response["errorCode"]
            if error_code not in [
                VOLUME_MIGRATION_IN_PROGRESS_ERROR,
                VOLUME_MIGRATION_ALREADY_ON_DESTINATION_POOL_ERROR,
            ]:
                msg = (_("Failed to migrate volume %s.") % volume.id)
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(msg)
        return response

    def overwrite_volume_content(self, volume, snapshot):
        url = "/instances/Volume::%(vol_id)s/action/overwriteVolumeContent"

        params = {"srcVolumeId": snapshot.provider_id}
        r, response = self.execute_powerflex_post_request(
            url,
            params=params,
            vol_id=volume.provider_id
        )
        if r.status_code != http_client.OK:
            msg = (_("Failed to revert volume %(vol_id)s to snapshot "
                     "%(snap_id)s: %(err)s.") % {"vol_id": volume.id,
                                                 "snap_id": snapshot.id,
                                                 "err": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(msg)

    def query_sdc_id_by_guid(self, sdc_guid):
        url = "/types/Sdc/instances"
        r, response = self.execute_powerflex_get_request(url)
        if r.status_code != http_client.OK:
            msg = (_("Failed to query SDC: %(err)s.") %
                   {"err": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        for sdc in response:
            if (sdc["sdcGuid"] and
                    sdc["sdcGuid"].lower() == sdc_guid.lower()):
                return sdc["id"]
        msg = (_("Failed to query SDC by guid %(sdc)s: Not Found.") %
               {"sdc": sdc_guid})
        LOG.error(msg)
        raise exception.VolumeBackendAPIException(data=msg)

    def query_sdc_by_id(self, sdc_id):
        url = "/instances/Sdc::%(sdc_id)s"

        r, response = self.execute_powerflex_get_request(
            url,
            sdc_id = sdc_id)
        if r.status_code != http_client.OK:
            msg = (_("Failed to query SDC id %(sdc_id)s: %(err)s.") % {
                "sdc_id": sdc_id, "err": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return response

    def map_volume(self, volume_id, sdc_id):
        params = {'sdcId': sdc_id, 'allowMultipleMappings': 'True'}
        url = "/instances/Volume::%(vol_id)s/action/addMappedSdc" % {
            'vol_id': volume_id
        }
        r, response = self.execute_powerflex_post_request(url, params)
        if r.status_code != http_client.OK:
            msg = (_("Failed to map volume %(vol_id)s to SDC "
                     "%(host_id)s: %(err)s.") % {"vol_id": volume_id,
                                                 "host_id": sdc_id,
                                                 "err": response["message"]})
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)

    def unmap_volume(self, volume_id, sdc_id=None):
        if sdc_id:
            params = {'sdcId': sdc_id}
            url = "/instances/Volume::%(vol_id)s/action/removeMappedSdc" % {
                'vol_id': volume_id
            }
            r, response = self.execute_powerflex_post_request(url, params)
            if r.status_code != http_client.OK:
                msg = (_("Failed to unmap volume %(vol_id)s from SDC "
                         "%(sdc_id)s: %(err)s.") % {
                             "vol_id": volume_id,
                             "sdc_id": sdc_id,
                             "err": response["message"]})
                LOG.error(msg)
                raise exception.VolumeBackendAPIException(data=msg)
        else:
            self._unmap_volume_from_all_sdcs(volume_id)

    def query_sdc_volumes(self, sdc_id):
        url = ("/instances/Sdc::%(sdc_id)s/relationships/Volume" %
               {'sdc_id': sdc_id})

        r, response = self.execute_powerflex_get_request(url)
        if r.status_code != http_client.OK:
            msg = (_("Failed to query SDC volumes: %s.") % response["message"])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
        return [volume["id"] for volume in response]

    def set_sdc_limits(self, volume_id, sdc_id,
                       bandwidth_limit=None, iops_limit=None):
        params = {'sdcId': sdc_id}
        if bandwidth_limit is not None:
            params['bandwidthLimitInKbps'] = bandwidth_limit
        if iops_limit is not None:
            params['iopsLimit'] = iops_limit

        url = (
            "/instances/Volume::%(volume_id)s/action/setMappedSdcLimits" %
            {'volume_id': volume_id}
        )

        r, response = self.execute_powerflex_post_request(url, params)
        if r.status_code != http_client.OK:
            msg = (_("Failed to set SDC limits: %s.") % response["message"])
            LOG.error(msg)
            raise exception.VolumeBackendAPIException(data=msg)
