"Fossies" - the Fresh Open Source Software Archive

Member "cinder-17.1.0/cinder/volume/drivers/dell_emc/powerstore/adapter.py" (8 Mar 2021, 36914 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. For more information about "adapter.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 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 """Adapter for Dell EMC PowerStore Cinder driver."""
   17 
   18 from oslo_log import log as logging
   19 from oslo_utils import strutils
   20 
   21 from cinder import coordination
   22 from cinder import exception
   23 from cinder.i18n import _
   24 from cinder.objects.snapshot import Snapshot
   25 from cinder.volume.drivers.dell_emc.powerstore import client
   26 from cinder.volume.drivers.dell_emc.powerstore import options
   27 from cinder.volume.drivers.dell_emc.powerstore import utils
   28 from cinder.volume import volume_utils
   29 
   30 
   31 LOG = logging.getLogger(__name__)
   32 PROTOCOL_FC = "FC"
   33 PROTOCOL_ISCSI = "iSCSI"
   34 CHAP_MODE_SINGLE = "Single"
   35 
   36 
   37 class CommonAdapter(object):
   38     def __init__(self, active_backend_id, configuration):
   39         self.active_backend_id = active_backend_id
   40         self.appliances = None
   41         self.appliances_to_ids_map = {}
   42         self.client = None
   43         self.configuration = configuration
   44         self.storage_protocol = None
   45         self.allowed_ports = None
   46         self.use_chap_auth = None
   47 
   48     @staticmethod
   49     def initiators(connector):
   50         raise NotImplementedError
   51 
   52     def _port_is_allowed(self, port):
   53         """Check if port is in allowed ports list.
   54 
   55         If allowed ports are empty then all ports are allowed.
   56 
   57         :param port: iSCSI IP/FC WWN to check
   58         :return: is port allowed
   59         """
   60 
   61         if not self.allowed_ports:
   62             return True
   63         return port.lower() in self.allowed_ports
   64 
   65     def _get_connection_properties(self, appliance_id, volume_lun):
   66         raise NotImplementedError
   67 
   68     def do_setup(self):
   69         self.appliances = (
   70             self.configuration.safe_get(options.POWERSTORE_APPLIANCES)
   71         )
   72         self.allowed_ports = [
   73             port.strip().lower() for port in
   74             self.configuration.safe_get(options.POWERSTORE_PORTS)
   75         ]
   76         self.client = client.PowerStoreClient(configuration=self.configuration)
   77         self.client.do_setup()
   78 
   79     def check_for_setup_error(self):
   80         self.client.check_for_setup_error()
   81         if not self.appliances:
   82             msg = _("PowerStore appliances must be set.")
   83             raise exception.VolumeBackendAPIException(data=msg)
   84         self.appliances_to_ids_map = {}
   85         for appliance_name in self.appliances:
   86             self.appliances_to_ids_map[appliance_name] = (
   87                 self.client.get_appliance_id_by_name(appliance_name)
   88             )
   89         self.use_chap_auth = False
   90         if self.storage_protocol == PROTOCOL_ISCSI:
   91             chap_config = self.client.get_chap_config()
   92             if chap_config.get("mode") == CHAP_MODE_SINGLE:
   93                 self.use_chap_auth = True
   94         LOG.debug("Successfully initialized PowerStore %(protocol)s adapter. "
   95                   "PowerStore appliances: %(appliances)s. "
   96                   "Allowed ports: %(allowed_ports)s. "
   97                   "Use CHAP authentication: %(use_chap_auth)s.",
   98                   {
   99                       "protocol": self.storage_protocol,
  100                       "appliances": self.appliances,
  101                       "allowed_ports": self.allowed_ports,
  102                       "use_chap_auth": self.use_chap_auth,
  103                   })
  104 
  105     def create_volume(self, volume):
  106         appliance_name = volume_utils.extract_host(volume.host, "pool")
  107         appliance_id = self.appliances_to_ids_map[appliance_name]
  108         LOG.debug("Create PowerStore volume %(volume_name)s of size "
  109                   "%(volume_size)s GiB with id %(volume_id)s on appliance "
  110                   "%(appliance_name)s.",
  111                   {
  112                       "volume_name": volume.name,
  113                       "volume_size": volume.size,
  114                       "volume_id": volume.id,
  115                       "appliance_name": appliance_name,
  116                   })
  117         size_in_bytes = utils.gib_to_bytes(volume.size)
  118         provider_id = self.client.create_volume(appliance_id,
  119                                                 volume.name,
  120                                                 size_in_bytes)
  121         LOG.debug("Successfully created PowerStore volume %(volume_name)s of "
  122                   "size %(volume_size)s GiB with id %(volume_id)s on "
  123                   "appliance %(appliance_name)s. "
  124                   "PowerStore volume id: %(volume_provider_id)s.",
  125                   {
  126                       "volume_name": volume.name,
  127                       "volume_size": volume.size,
  128                       "volume_id": volume.id,
  129                       "appliance_name": appliance_name,
  130                       "volume_provider_id": provider_id,
  131                   })
  132         return {
  133             "provider_id": provider_id,
  134         }
  135 
  136     def delete_volume(self, volume):
  137         if not volume.provider_id:
  138             LOG.warning("Volume %(volume_name)s with id %(volume_id)s "
  139                         "does not have provider_id thus does not "
  140                         "map to PowerStore volume.",
  141                         {
  142                             "volume_name": volume.name,
  143                             "volume_id": volume.id,
  144                         })
  145             return
  146         LOG.debug("Delete PowerStore volume %(volume_name)s with id "
  147                   "%(volume_id)s. PowerStore volume id: "
  148                   "%(volume_provider_id)s.",
  149                   {
  150                       "volume_name": volume.name,
  151                       "volume_id": volume.id,
  152                       "volume_provider_id": volume.provider_id,
  153                   })
  154         self._detach_volume_from_hosts(volume)
  155         self.client.delete_volume_or_snapshot(volume.provider_id)
  156         LOG.debug("Successfully deleted PowerStore volume %(volume_name)s "
  157                   "with id %(volume_id)s. PowerStore volume id: "
  158                   "%(volume_provider_id)s.",
  159                   {
  160                       "volume_name": volume.name,
  161                       "volume_id": volume.id,
  162                       "volume_provider_id": volume.provider_id,
  163                   })
  164 
  165     def extend_volume(self, volume, new_size):
  166         LOG.debug("Extend PowerStore volume %(volume_name)s of size "
  167                   "%(volume_size)s GiB with id %(volume_id)s to "
  168                   "%(volume_new_size)s GiB. "
  169                   "PowerStore volume id: %(volume_provider_id)s.",
  170                   {
  171                       "volume_name": volume.name,
  172                       "volume_size": volume.size,
  173                       "volume_id": volume.id,
  174                       "volume_new_size": new_size,
  175                       "volume_provider_id": volume.provider_id,
  176                   })
  177         size_in_bytes = utils.gib_to_bytes(new_size)
  178         self.client.extend_volume(volume.provider_id, size_in_bytes)
  179         LOG.debug("Successfully extended PowerStore volume %(volume_name)s "
  180                   "of size %(volume_size)s GiB with id "
  181                   "%(volume_id)s to %(volume_new_size)s GiB. "
  182                   "PowerStore volume id: %(volume_provider_id)s.",
  183                   {
  184                       "volume_name": volume.name,
  185                       "volume_size": volume.size,
  186                       "volume_id": volume.id,
  187                       "volume_new_size": new_size,
  188                       "volume_provider_id": volume.provider_id,
  189                   })
  190 
  191     def create_snapshot(self, snapshot):
  192         LOG.debug("Create PowerStore snapshot %(snapshot_name)s with id "
  193                   "%(snapshot_id)s of volume %(volume_name)s with id "
  194                   "%(volume_id)s. PowerStore volume id: "
  195                   "%(volume_provider_id)s.",
  196                   {
  197                       "snapshot_name": snapshot.name,
  198                       "snapshot_id": snapshot.id,
  199                       "volume_name": snapshot.volume.name,
  200                       "volume_id": snapshot.volume.id,
  201                       "volume_provider_id": snapshot.volume.provider_id,
  202                   })
  203         snapshot_provider_id = self.client.create_snapshot(
  204             snapshot.volume.provider_id,
  205             snapshot.name)
  206         LOG.debug("Successfully created PowerStore snapshot %(snapshot_name)s "
  207                   "with id %(snapshot_id)s of volume %(volume_name)s with "
  208                   "id %(volume_id)s. PowerStore snapshot id: "
  209                   "%(snapshot_provider_id)s, volume id: "
  210                   "%(volume_provider_id)s.",
  211                   {
  212                       "snapshot_name": snapshot.name,
  213                       "snapshot_id": snapshot.id,
  214                       "volume_name": snapshot.volume.name,
  215                       "volume_id": snapshot.volume.id,
  216                       "snapshot_provider_id": snapshot_provider_id,
  217                       "volume_provider_id": snapshot.volume.provider_id,
  218                   })
  219         return {
  220             "provider_id": snapshot_provider_id,
  221         }
  222 
  223     def delete_snapshot(self, snapshot):
  224         LOG.debug("Delete PowerStore snapshot %(snapshot_name)s with id "
  225                   "%(snapshot_id)s of volume %(volume_name)s with "
  226                   "id %(volume_id)s. PowerStore snapshot id: "
  227                   "%(snapshot_provider_id)s, volume id: "
  228                   "%(volume_provider_id)s.",
  229                   {
  230                       "snapshot_name": snapshot.name,
  231                       "snapshot_id": snapshot.id,
  232                       "volume_name": snapshot.volume.name,
  233                       "volume_id": snapshot.volume.id,
  234                       "snapshot_provider_id": snapshot.provider_id,
  235                       "volume_provider_id": snapshot.volume.provider_id,
  236                   })
  237         self.client.delete_volume_or_snapshot(snapshot.provider_id,
  238                                               entity="snapshot")
  239         LOG.debug("Successfully deleted PowerStore snapshot %(snapshot_name)s "
  240                   "with id %(snapshot_id)s of volume %(volume_name)s with "
  241                   "id %(volume_id)s. PowerStore snapshot id: "
  242                   "%(snapshot_provider_id)s, volume id: "
  243                   "%(volume_provider_id)s.",
  244                   {
  245                       "snapshot_name": snapshot.name,
  246                       "snapshot_id": snapshot.id,
  247                       "volume_name": snapshot.volume.name,
  248                       "volume_id": snapshot.volume.id,
  249                       "snapshot_provider_id": snapshot.provider_id,
  250                       "volume_provider_id": snapshot.volume.provider_id,
  251                   })
  252 
  253     def create_cloned_volume(self, volume, src_vref):
  254         LOG.debug("Clone PowerStore volume %(source_volume_name)s with id "
  255                   "%(source_volume_id)s to volume %(cloned_volume_name)s of "
  256                   "size %(cloned_volume_size)s GiB with id "
  257                   "%(cloned_volume_id)s. PowerStore source volume id: "
  258                   "%(source_volume_provider_id)s.",
  259                   {
  260                       "source_volume_name": src_vref.name,
  261                       "source_volume_id": src_vref.id,
  262                       "cloned_volume_name": volume.name,
  263                       "cloned_volume_size": volume.size,
  264                       "cloned_volume_id": volume.id,
  265                       "source_volume_provider_id": src_vref.provider_id,
  266                   })
  267         cloned_provider_id = self._create_volume_from_source(volume, src_vref)
  268         LOG.debug("Successfully cloned PowerStore volume "
  269                   "%(source_volume_name)s with id %(source_volume_id)s to "
  270                   "volume %(cloned_volume_name)s of size "
  271                   "%(cloned_volume_size)s GiB with id %(cloned_volume_id)s. "
  272                   "PowerStore source volume id: "
  273                   "%(source_volume_provider_id)s, "
  274                   "cloned volume id: %(cloned_volume_provider_id)s.",
  275                   {
  276                       "source_volume_name": src_vref.name,
  277                       "source_volume_id": src_vref.id,
  278                       "cloned_volume_name": volume.name,
  279                       "cloned_volume_size": volume.size,
  280                       "cloned_volume_id": volume.id,
  281                       "source_volume_provider_id": src_vref.provider_id,
  282                       "cloned_volume_provider_id": cloned_provider_id,
  283                   })
  284         return {
  285             "provider_id": cloned_provider_id,
  286         }
  287 
  288     def create_volume_from_snapshot(self, volume, snapshot):
  289         LOG.debug("Create PowerStore volume %(volume_name)s of size "
  290                   "%(volume_size)s GiB with id %(volume_id)s from snapshot "
  291                   "%(snapshot_name)s with id %(snapshot_id)s. PowerStore "
  292                   "snapshot id: %(snapshot_provider_id)s.",
  293                   {
  294                       "volume_name": volume.name,
  295                       "volume_id": volume.id,
  296                       "volume_size": volume.size,
  297                       "snapshot_name": snapshot.name,
  298                       "snapshot_id": snapshot.id,
  299                       "snapshot_provider_id": snapshot.provider_id,
  300                   })
  301         volume_provider_id = self._create_volume_from_source(volume, snapshot)
  302         LOG.debug("Successfully created PowerStore volume %(volume_name)s "
  303                   "of size %(volume_size)s GiB with id %(volume_id)s from "
  304                   "snapshot %(snapshot_name)s with id %(snapshot_id)s. "
  305                   "PowerStore volume id: %(volume_provider_id)s, "
  306                   "snapshot id: %(snapshot_provider_id)s.",
  307                   {
  308                       "volume_name": volume.name,
  309                       "volume_id": volume.id,
  310                       "volume_size": volume.size,
  311                       "snapshot_name": snapshot.name,
  312                       "snapshot_id": snapshot.id,
  313                       "volume_provider_id": volume_provider_id,
  314                       "snapshot_provider_id": snapshot.provider_id,
  315                   })
  316         return {
  317             "provider_id": volume_provider_id,
  318         }
  319 
  320     def initialize_connection(self, volume, connector, **kwargs):
  321         connection_properties = self._connect_volume(volume, connector)
  322         LOG.debug("Connection properties for volume %(volume_name)s with id "
  323                   "%(volume_id)s: %(connection_properties)s.",
  324                   {
  325                       "volume_name": volume.name,
  326                       "volume_id": volume.id,
  327                       "connection_properties": strutils.mask_password(
  328                           connection_properties
  329                       ),
  330                   })
  331         return connection_properties
  332 
  333     def terminate_connection(self, volume, connector, **kwargs):
  334         self._disconnect_volume(volume, connector)
  335         return {}
  336 
  337     def update_volume_stats(self):
  338         stats = {
  339             "volume_backend_name": (
  340                 self.configuration.safe_get("volume_backend_name") or
  341                 "powerstore"
  342             ),
  343             "storage_protocol": self.storage_protocol,
  344             "thick_provisioning_support": False,
  345             "thin_provisioning_support": True,
  346             "compression_support": True,
  347             "multiattach": True,
  348             "pools": [],
  349         }
  350         backend_total_capacity = 0
  351         backend_free_capacity = 0
  352         for appliance_name in self.appliances:
  353             appliance_stats = self.client.get_appliance_metrics(
  354                 self.appliances_to_ids_map[appliance_name]
  355             )
  356             appliance_total_capacity = utils.bytes_to_gib(
  357                 appliance_stats["physical_total"]
  358             )
  359             appliance_free_capacity = (
  360                 appliance_total_capacity -
  361                 utils.bytes_to_gib(appliance_stats["physical_used"])
  362             )
  363             pool = {
  364                 "pool_name": appliance_name,
  365                 "total_capacity_gb": appliance_total_capacity,
  366                 "free_capacity_gb": appliance_free_capacity,
  367                 "thick_provisioning_support": False,
  368                 "thin_provisioning_support": True,
  369                 "compression_support": True,
  370                 "multiattach": True,
  371             }
  372             backend_total_capacity += appliance_total_capacity
  373             backend_free_capacity += appliance_free_capacity
  374             stats["pools"].append(pool)
  375         stats["total_capacity_gb"] = backend_total_capacity
  376         stats["free_capacity_gb"] = backend_free_capacity
  377         LOG.debug("Free capacity for backend '%(backend)s': "
  378                   "%(free)s GiB, total capacity: %(total)s GiB.",
  379                   {
  380                       "backend": stats["volume_backend_name"],
  381                       "free": backend_free_capacity,
  382                       "total": backend_total_capacity,
  383                   })
  384         return stats
  385 
  386     def _create_volume_from_source(self, volume, source):
  387         """Create PowerStore volume from source (snapshot or another volume).
  388 
  389         :param volume: OpenStack volume object
  390         :param source: OpenStack source snapshot or volume
  391         :return: newly created PowerStore volume id
  392         """
  393 
  394         if isinstance(source, Snapshot):
  395             entity = "snapshot"
  396             source_size = source.volume_size
  397         else:
  398             entity = "volume"
  399             source_size = source.size
  400         volume_provider_id = self.client.clone_volume_or_snapshot(
  401             volume.name,
  402             source.provider_id,
  403             entity
  404         )
  405         if volume.size > source_size:
  406             size_in_bytes = utils.gib_to_bytes(volume.size)
  407             self.client.extend_volume(volume_provider_id, size_in_bytes)
  408         return volume_provider_id
  409 
  410     def _filter_hosts_by_initiators(self, initiators):
  411         """Filter hosts by given list of initiators.
  412 
  413         If initiators are added to different hosts the exception will be
  414         raised. In this case one of the hosts should be deleted.
  415 
  416         :param initiators: list of initiators
  417         :return: PowerStore host object
  418         """
  419 
  420         LOG.debug("Query PowerStore %(protocol)s hosts.",
  421                   {
  422                       "protocol": self.storage_protocol,
  423                   })
  424         hosts = self.client.get_all_hosts(self.storage_protocol)
  425         hosts_found = utils.filter_hosts_by_initiators(hosts, initiators)
  426         if hosts_found:
  427             if len(hosts_found) > 1:
  428                 hosts_names_found = [host["name"] for host in hosts_found]
  429                 msg = (_("Initiators are added to different PowerStore hosts: "
  430                          "%(hosts_names_found)s. Remove all of the hosts "
  431                          "except one to proceed. Initiators will be modified "
  432                          "during the next volume attach procedure.")
  433                        % {"hosts_names_found": hosts_names_found, })
  434                 LOG.error(msg)
  435                 raise exception.VolumeBackendAPIException(data=msg)
  436             else:
  437                 return hosts_found[0]
  438 
  439     @coordination.synchronized("powerstore-create-host")
  440     def _create_host_if_not_exist(self, connector):
  441         """Create PowerStore host if it does not exist.
  442 
  443         :param connector: connection properties
  444         :return: PowerStore host object, iSCSI CHAP credentials
  445         """
  446 
  447         initiators = self.initiators(connector)
  448         host = self._filter_hosts_by_initiators(initiators)
  449         if self.use_chap_auth:
  450             chap_credentials = utils.get_chap_credentials()
  451         else:
  452             chap_credentials = {}
  453         if host:
  454             self._modify_host_initiators(host, chap_credentials, initiators)
  455         else:
  456             host_name = utils.powerstore_host_name(
  457                 connector,
  458                 self.storage_protocol
  459             )
  460             LOG.debug("Create PowerStore host %(host_name)s. "
  461                       "Initiators: %(initiators)s.",
  462                       {
  463                           "host_name": host_name,
  464                           "initiators": initiators,
  465                       })
  466             ports = [
  467                 {
  468                     "port_name": initiator,
  469                     "port_type": self.storage_protocol,
  470                     **chap_credentials,
  471                 } for initiator in initiators
  472             ]
  473             host = self.client.create_host(host_name, ports)
  474             host["name"] = host_name
  475             LOG.debug("Successfully created PowerStore host %(host_name)s. "
  476                       "Initiators: %(initiators)s. PowerStore host id: "
  477                       "%(host_provider_id)s.",
  478                       {
  479                           "host_name": host["name"],
  480                           "initiators": initiators,
  481                           "host_provider_id": host["id"],
  482                       })
  483         return host, chap_credentials
  484 
  485     def _modify_host_initiators(self, host, chap_credentials, initiators):
  486         """Update PowerStore host initiators if needed.
  487 
  488         :param host: PowerStore host object
  489         :param chap_credentials: iSCSI CHAP credentials
  490         :param initiators: list of initiators
  491         :return: None
  492         """
  493 
  494         initiators_added = [
  495             initiator["port_name"] for initiator in host["host_initiators"]
  496         ]
  497         initiators_to_add = []
  498         initiators_to_modify = []
  499         initiators_to_remove = [
  500             initiator for initiator in initiators_added
  501             if initiator not in initiators
  502         ]
  503         for initiator in initiators:
  504             initiator_add_modify = {
  505                 "port_name": initiator,
  506                 **chap_credentials,
  507             }
  508             if initiator not in initiators_added:
  509                 initiator_add_modify["port_type"] = self.storage_protocol
  510                 initiators_to_add.append(initiator_add_modify)
  511             elif self.use_chap_auth:
  512                 initiators_to_modify.append(initiator_add_modify)
  513         if initiators_to_remove:
  514             LOG.debug("Remove initiators from PowerStore host %(host_name)s. "
  515                       "Initiators: %(initiators_to_remove)s. "
  516                       "PowerStore host id: %(host_provider_id)s.",
  517                       {
  518                           "host_name": host["name"],
  519                           "initiators_to_remove": initiators_to_remove,
  520                           "host_provider_id": host["id"],
  521                       })
  522             self.client.modify_host_initiators(
  523                 host["id"],
  524                 remove_initiators=initiators_to_remove
  525             )
  526             LOG.debug("Successfully removed initiators from PowerStore host "
  527                       "%(host_name)s. Initiators: %(initiators_to_remove)s. "
  528                       "PowerStore host id: %(host_provider_id)s.",
  529                       {
  530                           "host_name": host["name"],
  531                           "initiators_to_remove": initiators_to_remove,
  532                           "host_provider_id": host["id"],
  533                       })
  534         if initiators_to_add:
  535             LOG.debug("Add initiators to PowerStore host %(host_name)s. "
  536                       "Initiators: %(initiators_to_add)s. PowerStore host id: "
  537                       "%(host_provider_id)s.",
  538                       {
  539                           "host_name": host["name"],
  540                           "initiators_to_add": strutils.mask_password(
  541                               initiators_to_add
  542                           ),
  543                           "host_provider_id": host["id"],
  544                       })
  545             self.client.modify_host_initiators(
  546                 host["id"],
  547                 add_initiators=initiators_to_add
  548             )
  549             LOG.debug("Successfully added initiators to PowerStore host "
  550                       "%(host_name)s. Initiators: %(initiators_to_add)s. "
  551                       "PowerStore host id: %(host_provider_id)s.",
  552                       {
  553                           "host_name": host["name"],
  554                           "initiators_to_add": strutils.mask_password(
  555                               initiators_to_add
  556                           ),
  557                           "host_provider_id": host["id"],
  558                       })
  559         if initiators_to_modify:
  560             LOG.debug("Modify initiators of PowerStore host %(host_name)s. "
  561                       "Initiators: %(initiators_to_modify)s. "
  562                       "PowerStore host id: %(host_provider_id)s.",
  563                       {
  564                           "host_name": host["name"],
  565                           "initiators_to_modify": strutils.mask_password(
  566                               initiators_to_modify
  567                           ),
  568                           "host_provider_id": host["id"],
  569                       })
  570             self.client.modify_host_initiators(
  571                 host["id"],
  572                 modify_initiators=initiators_to_modify
  573             )
  574             LOG.debug("Successfully modified initiators of PowerStore host "
  575                       "%(host_name)s. Initiators: %(initiators_to_modify)s. "
  576                       "PowerStore host id: %(host_provider_id)s.",
  577                       {
  578                           "host_name": host["name"],
  579                           "initiators_to_modify": strutils.mask_password(
  580                               initiators_to_modify
  581                           ),
  582                           "host_provider_id": host["id"],
  583                       })
  584 
  585     def _attach_volume_to_host(self, host, volume):
  586         """Attach PowerStore volume to host.
  587 
  588         :param host: PowerStore host object
  589         :param volume: OpenStack volume object
  590         :return: attached volume logical number
  591         """
  592 
  593         LOG.debug("Attach PowerStore volume %(volume_name)s with id "
  594                   "%(volume_id)s to host %(host_name)s. PowerStore volume id: "
  595                   "%(volume_provider_id)s, host id: %(host_provider_id)s.",
  596                   {
  597                       "volume_name": volume.name,
  598                       "volume_id": volume.id,
  599                       "host_name": host["name"],
  600                       "volume_provider_id": volume.provider_id,
  601                       "host_provider_id": host["id"],
  602                   })
  603         self.client.attach_volume_to_host(host["id"], volume.provider_id)
  604         volume_lun = self.client.get_volume_lun(
  605             host["id"], volume.provider_id
  606         )
  607         LOG.debug("Successfully attached PowerStore volume %(volume_name)s "
  608                   "with id %(volume_id)s to host %(host_name)s. "
  609                   "PowerStore volume id: %(volume_provider_id)s, "
  610                   "host id: %(host_provider_id)s. Volume LUN: "
  611                   "%(volume_lun)s.",
  612                   {
  613                       "volume_name": volume.name,
  614                       "volume_id": volume.id,
  615                       "host_name": host["name"],
  616                       "volume_provider_id": volume.provider_id,
  617                       "host_provider_id": host["id"],
  618                       "volume_lun": volume_lun,
  619                   })
  620         return volume_lun
  621 
  622     def _create_host_and_attach(self, connector, volume):
  623         """Create PowerStore host and attach volume.
  624 
  625         :param connector: connection properties
  626         :param volume: OpenStack volume object
  627         :return: iSCSI CHAP credentials, volume logical number
  628         """
  629 
  630         host, chap_credentials = self._create_host_if_not_exist(connector)
  631         return chap_credentials, self._attach_volume_to_host(host, volume)
  632 
  633     def _connect_volume(self, volume, connector):
  634         """Attach PowerStore volume and return it's connection properties.
  635 
  636         :param volume: OpenStack volume object
  637         :param connector: connection properties
  638         :return: volume connection properties
  639         """
  640 
  641         appliance_name = volume_utils.extract_host(volume.host, "pool")
  642         appliance_id = self.appliances_to_ids_map[appliance_name]
  643         chap_credentials, volume_lun = self._create_host_and_attach(
  644             connector,
  645             volume
  646         )
  647         connection_properties = self._get_connection_properties(appliance_id,
  648                                                                 volume_lun)
  649         if self.use_chap_auth:
  650             connection_properties["data"]["auth_method"] = "CHAP"
  651             connection_properties["data"]["auth_username"] = (
  652                 chap_credentials.get("chap_single_username")
  653             )
  654             connection_properties["data"]["auth_password"] = (
  655                 chap_credentials.get("chap_single_password")
  656             )
  657         return connection_properties
  658 
  659     def _detach_volume_from_hosts(self, volume, hosts_to_detach=None):
  660         """Detach volume from PowerStore hosts.
  661 
  662         If hosts_to_detach is None, detach volume from all hosts.
  663 
  664         :param volume: OpenStack volume object
  665         :param hosts_to_detach: list of hosts to detach from
  666         :return: None
  667         """
  668 
  669         if hosts_to_detach is None:
  670             # Force detach. Get all mapped hosts and detach.
  671             hosts_to_detach = self.client.get_volume_mapped_hosts(
  672                 volume.provider_id
  673             )
  674         if not hosts_to_detach:
  675             # Volume is not attached to any host.
  676             return
  677         LOG.debug("Detach PowerStore volume %(volume_name)s with id "
  678                   "%(volume_id)s from hosts. PowerStore volume id: "
  679                   "%(volume_provider_id)s, hosts ids: %(hosts_provider_ids)s.",
  680                   {
  681                       "volume_name": volume.name,
  682                       "volume_id": volume.id,
  683                       "volume_provider_id": volume.provider_id,
  684                       "hosts_provider_ids": hosts_to_detach,
  685                   })
  686         for host_id in hosts_to_detach:
  687             self.client.detach_volume_from_host(host_id, volume.provider_id)
  688         LOG.debug("Successfully detached PowerStore volume "
  689                   "%(volume_name)s with id %(volume_id)s from hosts. "
  690                   "PowerStore volume id: %(volume_provider_id)s, "
  691                   "hosts ids: %(hosts_provider_ids)s.",
  692                   {
  693                       "volume_name": volume.name,
  694                       "volume_id": volume.id,
  695                       "volume_provider_id": volume.provider_id,
  696                       "hosts_provider_ids": hosts_to_detach,
  697                   })
  698 
  699     def _disconnect_volume(self, volume, connector):
  700         """Detach PowerStore volume.
  701 
  702         :param volume: OpenStack volume object
  703         :param connector: connection properties
  704         :return: None
  705         """
  706 
  707         if connector is None:
  708             self._detach_volume_from_hosts(volume)
  709         else:
  710             is_multiattached = utils.is_multiattached_to_host(
  711                 volume.volume_attachment,
  712                 connector["host"]
  713             )
  714             if is_multiattached:
  715                 # Do not detach volume until it is attached to more than one
  716                 # instance on the same host.
  717                 return
  718             initiators = self.initiators(connector)
  719             host = self._filter_hosts_by_initiators(initiators)
  720             if host:
  721                 self._detach_volume_from_hosts(volume, [host["id"]])
  722 
  723     def revert_to_snapshot(self, volume, snapshot):
  724         LOG.debug("Restore PowerStore volume %(volume_name)s with id "
  725                   "%(volume_id)s from snapshot %(snapshot_name)s with id "
  726                   "%(snapshot_id)s. PowerStore volume id: "
  727                   "%(volume_provider_id)s, snapshot id: "
  728                   "%(snapshot_provider_id)s.",
  729                   {
  730                       "volume_name": volume.name,
  731                       "volume_id": volume.id,
  732                       "snapshot_name": snapshot.name,
  733                       "snapshot_id": snapshot.id,
  734                       "volume_provider_id": volume.provider_id,
  735                       "snapshot_provider_id": snapshot.provider_id,
  736                   })
  737         self.client.restore_from_snapshot(volume.provider_id,
  738                                           snapshot.provider_id)
  739         LOG.debug("Successfully restored PowerStore volume %(volume_name)s "
  740                   "with id %(volume_id)s from snapshot %(snapshot_name)s "
  741                   "with id %(snapshot_id)s. PowerStore volume id: "
  742                   "%(volume_provider_id)s, snapshot id: "
  743                   "%(snapshot_provider_id)s.",
  744                   {
  745                       "volume_name": volume.name,
  746                       "volume_id": volume.id,
  747                       "snapshot_name": snapshot.name,
  748                       "snapshot_id": snapshot.id,
  749                       "volume_provider_id": volume.provider_id,
  750                       "snapshot_provider_id": snapshot.provider_id,
  751                   })
  752 
  753 
  754 class FibreChannelAdapter(CommonAdapter):
  755     def __init__(self, active_backend_id, configuration):
  756         super(FibreChannelAdapter, self).__init__(active_backend_id,
  757                                                   configuration)
  758         self.storage_protocol = PROTOCOL_FC
  759         self.driver_volume_type = "fibre_channel"
  760 
  761     @staticmethod
  762     def initiators(connector):
  763         return utils.extract_fc_wwpns(connector)
  764 
  765     def _get_fc_targets(self, appliance_id):
  766         """Get available FC WWNs for PowerStore appliance.
  767 
  768         :param appliance_id: PowerStore appliance id
  769         :return: list of FC WWNs
  770         """
  771 
  772         wwns = []
  773         fc_ports = self.client.get_fc_port(appliance_id)
  774         for port in fc_ports:
  775             if self._port_is_allowed(port["wwn"]):
  776                 wwns.append(utils.fc_wwn_to_string(port["wwn"]))
  777         if not wwns:
  778             msg = _("There are no accessible Fibre Channel targets on the "
  779                     "system.")
  780             raise exception.VolumeBackendAPIException(data=msg)
  781         return wwns
  782 
  783     def _get_connection_properties(self, appliance_id, volume_lun):
  784         """Fill connection properties dict with data to attach volume.
  785 
  786         :param appliance_id: PowerStore appliance id
  787         :param volume_lun: attached volume logical unit number
  788         :return: connection properties
  789         """
  790 
  791         target_wwns = self._get_fc_targets(appliance_id)
  792         return {
  793             "driver_volume_type": self.driver_volume_type,
  794             "data": {
  795                 "target_discovered": False,
  796                 "target_lun": volume_lun,
  797                 "target_wwn": target_wwns,
  798             }
  799         }
  800 
  801 
  802 class iSCSIAdapter(CommonAdapter):
  803     def __init__(self, active_backend_id, configuration):
  804         super(iSCSIAdapter, self).__init__(active_backend_id, configuration)
  805         self.storage_protocol = PROTOCOL_ISCSI
  806         self.driver_volume_type = "iscsi"
  807 
  808     @staticmethod
  809     def initiators(connector):
  810         return [connector["initiator"]]
  811 
  812     def _get_iscsi_targets(self, appliance_id):
  813         """Get available iSCSI portals and IQNs for PowerStore appliance.
  814 
  815         :param appliance_id: PowerStore appliance id
  816         :return: iSCSI portals and IQNs
  817         """
  818 
  819         iqns = []
  820         portals = []
  821         ip_pool_addresses = self.client.get_ip_pool_address(appliance_id)
  822         for address in ip_pool_addresses:
  823             if self._port_is_allowed(address["address"]):
  824                 portals.append(
  825                     utils.iscsi_portal_with_port(address["address"])
  826                 )
  827                 iqns.append(address["ip_port"]["target_iqn"])
  828         if not portals:
  829             msg = _("There are no accessible iSCSI targets on the "
  830                     "system.")
  831             raise exception.VolumeBackendAPIException(data=msg)
  832         return iqns, portals
  833 
  834     def _get_connection_properties(self, appliance_id, volume_lun):
  835         """Fill connection properties dict with data to attach volume.
  836 
  837         :param appliance_id: PowerStore appliance id
  838         :param volume_lun: attached volume logical unit number
  839         :return: connection properties
  840         """
  841 
  842         iqns, portals = self._get_iscsi_targets(appliance_id)
  843         return {
  844             "driver_volume_type": self.driver_volume_type,
  845             "data": {
  846                 "target_discovered": False,
  847                 "target_portal": portals[0],
  848                 "target_iqn": iqns[0],
  849                 "target_lun": volume_lun,
  850                 "target_portals": portals,
  851                 "target_iqns": iqns,
  852                 "target_luns": [volume_lun] * len(portals),
  853             },
  854         }