"Fossies" - the Fresh Open Source Software Archive

Member "cinder-17.1.0/cinder/volume/drivers/dell_emc/powerstore/client.py" (8 Mar 2021, 16955 Bytes) of package /linux/misc/openstack/cinder-17.1.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "client.py": 17.0.1_vs_17.1.0.

    1 # Copyright (c) 2020 Dell Inc. or its subsidiaries.
    2 # All Rights Reserved.
    3 #
    4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 # not use this file except in compliance with the License. You may obtain
    6 # a copy of the License at
    7 #
    8 #      http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 # Unless required by applicable law or agreed to in writing, software
   11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 # License for the specific language governing permissions and limitations
   14 # under the License.
   15 
   16 """REST client for Dell EMC PowerStore Cinder Driver."""
   17 
   18 import functools
   19 import json
   20 
   21 from oslo_log import log as logging
   22 from oslo_utils import strutils
   23 import requests
   24 
   25 from cinder import exception
   26 from cinder.i18n import _
   27 
   28 
   29 LOG = logging.getLogger(__name__)
   30 VOLUME_NOT_MAPPED_ERROR = "0xE0A08001000F"
   31 
   32 
   33 class PowerStoreClient(object):
   34     def __init__(self, configuration):
   35         self.configuration = configuration
   36         self.rest_ip = None
   37         self.rest_username = None
   38         self.rest_password = None
   39         self.verify_certificate = None
   40         self.certificate_path = None
   41         self.base_url = None
   42         self.ok_codes = [
   43             requests.codes.ok,
   44             requests.codes.created,
   45             requests.codes.no_content,
   46             requests.codes.partial_content
   47         ]
   48 
   49     @property
   50     def _verify_cert(self):
   51         verify_cert = self.verify_certificate
   52         if self.verify_certificate and self.certificate_path:
   53             verify_cert = self.certificate_path
   54         return verify_cert
   55 
   56     def do_setup(self):
   57         self.rest_ip = self.configuration.safe_get("san_ip")
   58         self.rest_username = self.configuration.safe_get("san_login")
   59         self.rest_password = self.configuration.safe_get("san_password")
   60         self.base_url = "https://%s:/api/rest" % self.rest_ip
   61         self.verify_certificate = self.configuration.safe_get(
   62             "driver_ssl_cert_verify"
   63         )
   64         if self.verify_certificate:
   65             self.certificate_path = (
   66                 self.configuration.safe_get("driver_ssl_cert_path")
   67             )
   68 
   69     def check_for_setup_error(self):
   70         if not all([self.rest_ip, self.rest_username, self.rest_password]):
   71             msg = _("REST server IP, username and password must be set.")
   72             raise exception.VolumeBackendAPIException(data=msg)
   73 
   74         # log warning if not using certificates
   75         if not self.verify_certificate:
   76             LOG.warning("Verify certificate is not set, using default of "
   77                         "False.")
   78         LOG.debug("Successfully initialized PowerStore REST client. "
   79                   "Server IP: %(ip)s, username: %(username)s. "
   80                   "Verify server's certificate: %(verify_cert)s.",
   81                   {
   82                       "ip": self.rest_ip,
   83                       "username": self.rest_username,
   84                       "verify_cert": self._verify_cert,
   85                   })
   86 
   87     def _send_request(self,
   88                       method,
   89                       url,
   90                       payload=None,
   91                       params=None,
   92                       log_response_data=True):
   93         if not payload:
   94             payload = {}
   95         if not params:
   96             params = {}
   97         request_params = {
   98             "auth": (self.rest_username, self.rest_password),
   99             "verify": self._verify_cert,
  100         }
  101         if method == "GET":
  102             request_params["params"] = params
  103         else:
  104             request_params["data"] = json.dumps(payload)
  105         request_url = self.base_url + url
  106         r = requests.request(method, request_url, **request_params)
  107 
  108         log_level = logging.DEBUG
  109         if r.status_code not in self.ok_codes:
  110             log_level = logging.ERROR
  111         LOG.log(log_level,
  112                 "REST Request: %s %s with body %s",
  113                 r.request.method,
  114                 r.request.url,
  115                 strutils.mask_password(r.request.body))
  116         if log_response_data or log_level == logging.ERROR:
  117             msg = "REST Response: %s with data %s" % (r.status_code, r.text)
  118         else:
  119             msg = "REST Response: %s" % r.status_code
  120         LOG.log(log_level, msg)
  121 
  122         try:
  123             response = r.json()
  124         except ValueError:
  125             response = None
  126         return r, response
  127 
  128     _send_get_request = functools.partialmethod(_send_request, "GET")
  129     _send_post_request = functools.partialmethod(_send_request, "POST")
  130     _send_patch_request = functools.partialmethod(_send_request, "PATCH")
  131     _send_delete_request = functools.partialmethod(_send_request, "DELETE")
  132 
  133     def get_chap_config(self):
  134         r, response = self._send_get_request(
  135             "/chap_config/0",
  136             params={
  137                 "select": "mode"
  138             }
  139         )
  140         if r.status_code not in self.ok_codes:
  141             msg = _("Failed to query PowerStore CHAP configuration.")
  142             LOG.error(msg)
  143             raise exception.VolumeBackendAPIException(data=msg)
  144         return response
  145 
  146     def get_appliance_id_by_name(self, appliance_name):
  147         r, response = self._send_get_request(
  148             "/appliance",
  149             params={
  150                 "name": "eq.%s" % appliance_name,
  151             }
  152         )
  153         if r.status_code not in self.ok_codes:
  154             msg = _("Failed to query PowerStore appliances.")
  155             LOG.error(msg)
  156             raise exception.VolumeBackendAPIException(data=msg)
  157         try:
  158             appliance_id = response[0].get("id")
  159             return appliance_id
  160         except IndexError:
  161             msg = _("PowerStore appliance %s is not found.") % appliance_name
  162             LOG.error(msg)
  163             raise exception.VolumeBackendAPIException(data=msg)
  164 
  165     def get_appliance_metrics(self, appliance_id):
  166         r, response = self._send_post_request(
  167             "/metrics/generate",
  168             payload={
  169                 "entity": "space_metrics_by_appliance",
  170                 "entity_id": appliance_id,
  171             },
  172             log_response_data=False
  173         )
  174         if r.status_code not in self.ok_codes:
  175             msg = (_("Failed to query metrics for "
  176                      "PowerStore appliance with id %s.") % appliance_id)
  177             LOG.error(msg)
  178             raise exception.VolumeBackendAPIException(data=msg)
  179         try:
  180             latest_metrics = response[-1]
  181             return latest_metrics
  182         except IndexError:
  183             msg = (_("Failed to query metrics for "
  184                      "PowerStore appliance with id %s.") % appliance_id)
  185             LOG.error(msg)
  186             raise exception.VolumeBackendAPIException(data=msg)
  187 
  188     def create_volume(self, appliance_id, name, size):
  189         r, response = self._send_post_request(
  190             "/volume",
  191             payload={
  192                 "appliance_id": appliance_id,
  193                 "name": name,
  194                 "size": size,
  195             }
  196         )
  197         if r.status_code not in self.ok_codes:
  198             msg = _("Failed to create PowerStore volume %s.") % name
  199             LOG.error(msg)
  200             raise exception.VolumeBackendAPIException(data=msg)
  201         return response["id"]
  202 
  203     def delete_volume_or_snapshot(self, entity_id, entity="volume"):
  204         r, response = self._send_delete_request("/volume/%s" % entity_id)
  205         if r.status_code not in self.ok_codes:
  206             if r.status_code == requests.codes.not_found:
  207                 LOG.warning("PowerStore %(entity)s with id %(entity_id)s is "
  208                             "not found. Ignoring error.",
  209                             {
  210                                 "entity": entity,
  211                                 "entity_id": entity_id,
  212                             })
  213             else:
  214                 msg = (_("Failed to delete PowerStore %(entity)s with id "
  215                          "%(entity_id)s.")
  216                        % {"entity": entity,
  217                           "entity_id": entity_id, })
  218                 LOG.error(msg)
  219                 raise exception.VolumeBackendAPIException(data=msg)
  220 
  221     def extend_volume(self, volume_id, size):
  222         r, response = self._send_patch_request(
  223             "/volume/%s" % volume_id,
  224             payload={
  225                 "size": size,
  226             }
  227         )
  228         if r.status_code not in self.ok_codes:
  229             msg = (_("Failed to extend PowerStore volume with id %s.")
  230                    % volume_id)
  231             LOG.error(msg)
  232             raise exception.VolumeBackendAPIException(data=msg)
  233 
  234     def create_snapshot(self, volume_id, name):
  235         r, response = self._send_post_request(
  236             "/volume/%s/snapshot" % volume_id,
  237             payload={
  238                 "name": name,
  239             }
  240         )
  241         if r.status_code not in self.ok_codes:
  242             msg = (_("Failed to create snapshot %(snapshot_name)s for "
  243                      "PowerStore volume with id %(volume_id)s.")
  244                    % {"snapshot_name": name,
  245                       "volume_id": volume_id, })
  246             LOG.error(msg)
  247             raise exception.VolumeBackendAPIException(data=msg)
  248         return response["id"]
  249 
  250     def clone_volume_or_snapshot(self,
  251                                  name,
  252                                  entity_id,
  253                                  entity="volume"):
  254         r, response = self._send_post_request(
  255             "/volume/%s/clone" % entity_id,
  256             payload={
  257                 "name": name,
  258             }
  259         )
  260         if r.status_code not in self.ok_codes:
  261             msg = (_("Failed to create clone %(clone_name)s for "
  262                      "PowerStore %(entity)s with id %(entity_id)s.")
  263                    % {"clone_name": name,
  264                       "entity": entity,
  265                       "entity_id": entity_id, })
  266             LOG.error(msg)
  267             raise exception.VolumeBackendAPIException(data=msg)
  268         return response["id"]
  269 
  270     def get_all_hosts(self, protocol):
  271         r, response = self._send_get_request(
  272             "/host",
  273             params={
  274                 "select": "id,name,host_initiators",
  275                 "host_initiators->0->>port_type": "eq.%s" % protocol,
  276             }
  277         )
  278         if r.status_code not in self.ok_codes:
  279             msg = _("Failed to query PowerStore hosts.")
  280             LOG.error(msg)
  281             raise exception.VolumeBackendAPIException(data=msg)
  282         return response
  283 
  284     def create_host(self, name, ports):
  285         r, response = self._send_post_request(
  286             "/host",
  287             payload={
  288                 "name": name,
  289                 "os_type": "Linux",
  290                 "initiators": ports
  291             }
  292         )
  293         if r.status_code not in self.ok_codes:
  294             msg = _("Failed to create PowerStore host %s.") % name
  295             LOG.error(msg)
  296             raise exception.VolumeBackendAPIException(data=msg)
  297         return response
  298 
  299     def modify_host_initiators(self, host_id, **kwargs):
  300         r, response = self._send_patch_request(
  301             "/host/%s" % host_id,
  302             payload={
  303                 **kwargs,
  304             }
  305         )
  306         if r.status_code not in self.ok_codes:
  307             msg = (_("Failed to modify initiators of PowerStore host "
  308                      "with id %s.") % host_id)
  309             LOG.error(msg)
  310             raise exception.VolumeBackendAPIException(data=msg)
  311 
  312     def attach_volume_to_host(self, host_id, volume_id):
  313         r, response = self._send_post_request(
  314             "/volume/%s/attach" % volume_id,
  315             payload={
  316                 "host_id": host_id,
  317             }
  318         )
  319         if r.status_code not in self.ok_codes:
  320             msg = (_("Failed to attach PowerStore volume %(volume_id)s "
  321                      "to host %(host_id)s.")
  322                    % {"volume_id": volume_id,
  323                       "host_id": host_id, })
  324             LOG.error(msg)
  325             raise exception.VolumeBackendAPIException(data=msg)
  326 
  327     def get_volume_mapped_hosts(self, volume_id):
  328         r, response = self._send_get_request(
  329             "/host_volume_mapping",
  330             params={
  331                 "volume_id": "eq.%s" % volume_id,
  332                 "select": "host_id"
  333             }
  334         )
  335         if r.status_code not in self.ok_codes:
  336             msg = _("Failed to query PowerStore host volume mappings.")
  337             LOG.error(msg)
  338             raise exception.VolumeBackendAPIException(data=msg)
  339         mapped_hosts = [mapped_host["host_id"] for mapped_host in response]
  340         return mapped_hosts
  341 
  342     def get_volume_lun(self, host_id, volume_id):
  343         r, response = self._send_get_request(
  344             "/host_volume_mapping",
  345             params={
  346                 "host_id": "eq.%s" % host_id,
  347                 "volume_id": "eq.%s" % volume_id,
  348                 "select": "logical_unit_number"
  349             }
  350         )
  351         if r.status_code not in self.ok_codes:
  352             msg = _("Failed to query PowerStore host volume mappings.")
  353             LOG.error(msg)
  354             raise exception.VolumeBackendAPIException(data=msg)
  355         try:
  356             logical_unit_number = response[0].get("logical_unit_number")
  357             return logical_unit_number
  358         except IndexError:
  359             msg = (_("PowerStore mapping of volume with id %(volume_id)s "
  360                      "to host %(host_id)s is not found.")
  361                    % {"volume_id": volume_id,
  362                       "host_id": host_id, })
  363             LOG.error(msg)
  364             raise exception.VolumeBackendAPIException(data=msg)
  365 
  366     def get_fc_port(self, appliance_id):
  367         r, response = self._send_get_request(
  368             "/fc_port",
  369             params={
  370                 "appliance_id": "eq.%s" % appliance_id,
  371                 "is_link_up": "eq.True",
  372                 "select": "wwn"
  373 
  374             }
  375         )
  376         if r.status_code not in self.ok_codes:
  377             msg = _("Failed to query PowerStore IP pool addresses.")
  378             LOG.error(msg)
  379             raise exception.VolumeBackendAPIException(data=msg)
  380         return response
  381 
  382     def get_ip_pool_address(self, appliance_id):
  383         r, response = self._send_get_request(
  384             "/ip_pool_address",
  385             params={
  386                 "appliance_id": "eq.%s" % appliance_id,
  387                 "purposes": "eq.{Storage_Iscsi_Target}",
  388                 "select": "address,ip_port(target_iqn)"
  389 
  390             }
  391         )
  392         if r.status_code not in self.ok_codes:
  393             msg = _("Failed to query PowerStore IP pool addresses.")
  394             LOG.error(msg)
  395             raise exception.VolumeBackendAPIException(data=msg)
  396         return response
  397 
  398     def detach_volume_from_host(self, host_id, volume_id):
  399         r, response = self._send_post_request(
  400             "/volume/%s/detach" % volume_id,
  401             payload={
  402                 "host_id": host_id,
  403             }
  404         )
  405         if r.status_code not in self.ok_codes:
  406             if r.status_code == requests.codes.not_found:
  407                 LOG.warning("PowerStore volume with id %(volume_id)s is "
  408                             "not found. Ignoring error.",
  409                             {
  410                                 "volume_id": volume_id,
  411                             })
  412             elif (
  413                     r.status_code == requests.codes.unprocessable and
  414                     any([
  415                         message["code"] == VOLUME_NOT_MAPPED_ERROR
  416                         for message in response["messages"]
  417                     ])
  418             ):
  419                 LOG.warning("PowerStore volume with id %(volume_id)s is "
  420                             "not mapped to host with id %(host_id)s. "
  421                             "Ignoring error.",
  422                             {
  423                                 "volume_id": volume_id,
  424                                 "host_id": host_id,
  425                             })
  426             else:
  427                 msg = (_("Failed to detach PowerStore volume %(volume_id)s "
  428                          "to host %(host_id)s.")
  429                        % {"volume_id": volume_id,
  430                           "host_id": host_id, })
  431                 LOG.error(msg)
  432                 raise exception.VolumeBackendAPIException(data=msg)
  433 
  434     def restore_from_snapshot(self, volume_id, snapshot_id):
  435         r, response = self._send_post_request(
  436             "/volume/%s/restore" % volume_id,
  437             payload={
  438                 "from_snap_id": snapshot_id,
  439                 "create_backup_snap": False,
  440             }
  441         )
  442         if r.status_code not in self.ok_codes:
  443             msg = (_("Failed to restore PowerStore volume with id "
  444                      "%(volume_id)s from snapshot with id %(snapshot_id)s.")
  445                    % {"volume_id": volume_id,
  446                       "snapshot_id": snapshot_id, })
  447             LOG.error(msg)
  448             raise exception.VolumeBackendAPIException(data=msg)