"Fossies" - the Fresh Open Source Software Archive

Member "manila-8.1.4/manila/share/drivers/dell_emc/plugins/vmax/connection.py" (19 Nov 2020, 36031 Bytes) of package /linux/misc/openstack/manila-8.1.4.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 "connection.py": 8.1.3_vs_8.1.4.

    1 # Copyright (c) 2016 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 """VMAX backend for the EMC Manila driver."""
   16 
   17 import copy
   18 import random
   19 import six
   20 
   21 from oslo_config import cfg
   22 from oslo_log import log
   23 from oslo_utils import excutils
   24 from oslo_utils import units
   25 
   26 from manila.common import constants as const
   27 from manila import exception
   28 from manila.i18n import _
   29 from manila.share.drivers.dell_emc.common.enas import constants
   30 from manila.share.drivers.dell_emc.common.enas import utils as enas_utils
   31 from manila.share.drivers.dell_emc.plugins import base as driver
   32 from manila.share.drivers.dell_emc.plugins.vmax import (
   33     object_manager as manager)
   34 from manila.share import utils as share_utils
   35 from manila import utils
   36 
   37 """Version history:
   38     1.0.0 - Initial version
   39     2.0.0 - Implement IPv6 support
   40 """
   41 VERSION = "2.0.0"
   42 
   43 LOG = log.getLogger(__name__)
   44 
   45 VMAX_OPTS = [
   46     cfg.StrOpt('vmax_server_container',
   47                deprecated_name='emc_nas_server_container',
   48                help='Data mover to host the NAS server.'),
   49     cfg.ListOpt('vmax_share_data_pools',
   50                 deprecated_name='emc_nas_pool_names',
   51                 help='Comma separated list of pools that can be used to '
   52                      'persist share data.'),
   53     cfg.ListOpt('vmax_ethernet_ports',
   54                 deprecated_name='emc_interface_ports',
   55                 help='Comma separated list of ports that can be used for '
   56                      'share server interfaces. Members of the list '
   57                      'can be Unix-style glob expressions.')
   58 ]
   59 
   60 CONF = cfg.CONF
   61 CONF.register_opts(VMAX_OPTS)
   62 
   63 
   64 @enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
   65                                  debug_only=True)
   66 class VMAXStorageConnection(driver.StorageConnection):
   67     """Implements vmax specific functionality for EMC Manila driver."""
   68 
   69     @enas_utils.log_enter_exit
   70     def __init__(self, *args, **kwargs):
   71         super(VMAXStorageConnection, self).__init__(*args, **kwargs)
   72         if 'configuration' in kwargs:
   73             kwargs['configuration'].append_config_values(VMAX_OPTS)
   74 
   75         self.mover_name = None
   76         self.pools = None
   77         self.manager = None
   78         self.pool_conf = None
   79         self.reserved_percentage = None
   80         self.driver_handles_share_servers = True
   81         self.port_conf = None
   82         self.ipv6_implemented = True
   83 
   84     def create_share(self, context, share, share_server=None):
   85         """Create a share and export it based on protocol used."""
   86         share_name = share['id']
   87         size = share['size'] * units.Ki
   88 
   89         share_proto = share['share_proto'].upper()
   90 
   91         # Validate the share protocol
   92         if share_proto not in ('NFS', 'CIFS'):
   93             raise exception.InvalidShare(
   94                 reason=(_('Invalid NAS protocol supplied: %s.')
   95                         % share_proto))
   96 
   97         # Get the pool name from share host field
   98         pool_name = share_utils.extract_host(share['host'], level='pool')
   99         if not pool_name:
  100             message = (_("Pool is not available in the share host %s.") %
  101                        share['host'])
  102             raise exception.InvalidHost(reason=message)
  103 
  104         # Validate share server
  105         self._share_server_validation(share_server)
  106 
  107         if share_proto == 'CIFS':
  108             vdm_name = self._get_share_server_name(share_server)
  109             server_name = vdm_name
  110 
  111             # Check if CIFS server exists.
  112             status, server = self._get_context('CIFSServer').get(server_name,
  113                                                                  vdm_name)
  114             if status != constants.STATUS_OK:
  115                 message = (_("CIFS server %s not found.") % server_name)
  116                 LOG.error(message)
  117                 raise exception.EMCVmaxXMLAPIError(err=message)
  118 
  119         self._allocate_container(share_name, size, share_server, pool_name)
  120 
  121         if share_proto == 'NFS':
  122             location = self._create_nfs_share(share_name, share_server)
  123         elif share_proto == 'CIFS':
  124             location = self._create_cifs_share(share_name, share_server)
  125 
  126         return location
  127 
  128     def _share_server_validation(self, share_server):
  129         """Validate the share server."""
  130         if not share_server:
  131             msg = _('Share server not provided')
  132             raise exception.InvalidInput(reason=msg)
  133 
  134         backend_details = share_server.get('backend_details')
  135         vdm = backend_details.get(
  136             'share_server_name') if backend_details else None
  137 
  138         if vdm is None:
  139             message = _("No share server found.")
  140             LOG.error(message)
  141             raise exception.EMCVmaxXMLAPIError(err=message)
  142 
  143     def _allocate_container(self, share_name, size, share_server, pool_name):
  144         """Allocate file system for share."""
  145         vdm_name = self._get_share_server_name(share_server)
  146 
  147         self._get_context('FileSystem').create(
  148             share_name, size, pool_name, vdm_name)
  149 
  150     def _allocate_container_from_snapshot(self, share, snapshot, share_server,
  151                                           pool_name):
  152         """Allocate file system from snapshot."""
  153         vdm_name = self._get_share_server_name(share_server)
  154 
  155         interconn_id = self._get_context('Mover').get_interconnect_id(
  156             self.mover_name, self.mover_name)
  157 
  158         self._get_context('FileSystem').create_from_snapshot(
  159             share['id'], snapshot['id'], snapshot['share_id'],
  160             pool_name, vdm_name, interconn_id)
  161 
  162         nwe_size = share['size'] * units.Ki
  163         self._get_context('FileSystem').extend(share['id'], pool_name,
  164                                                nwe_size)
  165 
  166     @enas_utils.log_enter_exit
  167     def _create_cifs_share(self, share_name, share_server):
  168         """Create CIFS share."""
  169         vdm_name = self._get_share_server_name(share_server)
  170         server_name = vdm_name
  171 
  172         # Get available CIFS Server and interface (one CIFS server per VDM)
  173         status, server = self._get_context('CIFSServer').get(server_name,
  174                                                              vdm_name)
  175 
  176         if 'interfaces' not in server or len(server['interfaces']) == 0:
  177             message = (_("CIFS server %s doesn't have interface, "
  178                          "so the share is inaccessible.")
  179                        % server['compName'])
  180             LOG.error(message)
  181             raise exception.EMCVmaxXMLAPIError(err=message)
  182 
  183         interface = enas_utils.export_unc_path(server['interfaces'][0])
  184 
  185         self._get_context('CIFSShare').create(share_name, server['name'],
  186                                               vdm_name)
  187 
  188         self._get_context('CIFSShare').disable_share_access(share_name,
  189                                                             vdm_name)
  190         locations = []
  191         location = (r'\\%(interface)s\%(name)s' %
  192                     {'interface': interface, 'name': share_name})
  193 
  194         locations.append(location)
  195 
  196         return locations
  197 
  198     @enas_utils.log_enter_exit
  199     def _create_nfs_share(self, share_name, share_server):
  200         """Create NFS share."""
  201         vdm_name = self._get_share_server_name(share_server)
  202 
  203         self._get_context('NFSShare').create(share_name, vdm_name)
  204 
  205         nfs_if = enas_utils.convert_ipv6_format_if_needed(
  206             share_server['backend_details']['nfs_if'])
  207 
  208         return ('%(nfs_if)s:/%(share_name)s'
  209                 % {'nfs_if': nfs_if,
  210                    'share_name': share_name})
  211 
  212     def create_share_from_snapshot(self, context, share, snapshot,
  213                                    share_server=None):
  214         """Create a share from a snapshot - clone a snapshot."""
  215         share_name = share['id']
  216 
  217         share_proto = share['share_proto'].upper()
  218 
  219         # Validate the share protocol
  220         if share_proto not in ('NFS', 'CIFS'):
  221             raise exception.InvalidShare(
  222                 reason=(_('Invalid NAS protocol supplied: %s.')
  223                         % share_proto))
  224 
  225         # Get the pool name from share host field
  226         pool_name = share_utils.extract_host(share['host'], level='pool')
  227         if not pool_name:
  228             message = (_("Pool is not available in the share host %s.") %
  229                        share['host'])
  230             raise exception.InvalidHost(reason=message)
  231 
  232         self._share_server_validation(share_server)
  233 
  234         self._allocate_container_from_snapshot(
  235             share, snapshot, share_server, pool_name)
  236 
  237         nfs_if = enas_utils.convert_ipv6_format_if_needed(
  238             share_server['backend_details']['nfs_if'])
  239 
  240         if share_proto == 'NFS':
  241             self._create_nfs_share(share_name, share_server)
  242             location = ('%(nfs_if)s:/%(share_name)s'
  243                         % {'nfs_if': nfs_if,
  244                            'share_name': share_name})
  245         elif share_proto == 'CIFS':
  246             location = self._create_cifs_share(share_name, share_server)
  247 
  248         return location
  249 
  250     def create_snapshot(self, context, snapshot, share_server=None):
  251         """Create snapshot from share."""
  252         share_name = snapshot['share_id']
  253         status, filesystem = self._get_context('FileSystem').get(share_name)
  254         if status != constants.STATUS_OK:
  255             message = (_("File System %s not found.") % share_name)
  256             LOG.error(message)
  257             raise exception.EMCVmaxXMLAPIError(err=message)
  258 
  259         pool_id = filesystem['pools_id'][0]
  260 
  261         self._get_context('Snapshot').create(snapshot['id'],
  262                                              snapshot['share_id'],
  263                                              pool_id)
  264 
  265     def delete_share(self, context, share, share_server=None):
  266         """Delete a share."""
  267         if share_server is None:
  268             LOG.warning("Share network should be specified for "
  269                         "share deletion.")
  270             return
  271 
  272         share_proto = share['share_proto'].upper()
  273 
  274         if share_proto == 'NFS':
  275             self._delete_nfs_share(share, share_server)
  276         elif share_proto == 'CIFS':
  277             self._delete_cifs_share(share, share_server)
  278         else:
  279             raise exception.InvalidShare(
  280                 reason=_('Unsupported share protocol'))
  281 
  282     @enas_utils.log_enter_exit
  283     def _delete_cifs_share(self, share, share_server):
  284         """Delete CIFS share."""
  285         vdm_name = self._get_share_server_name(share_server)
  286 
  287         name = share['id']
  288 
  289         self._get_context('CIFSShare').delete(name, vdm_name)
  290 
  291         self._deallocate_container(name, vdm_name)
  292 
  293     @enas_utils.log_enter_exit
  294     def _delete_nfs_share(self, share, share_server):
  295         """Delete NFS share."""
  296         vdm_name = self._get_share_server_name(share_server)
  297 
  298         name = share['id']
  299 
  300         self._get_context('NFSShare').delete(name, vdm_name)
  301 
  302         self._deallocate_container(name, vdm_name)
  303 
  304     @enas_utils.log_enter_exit
  305     def _deallocate_container(self, share_name, vdm_name):
  306         """Delete underneath objects of the share."""
  307         path = '/' + share_name
  308 
  309         try:
  310             # Delete mount point
  311             self._get_context('MountPoint').delete(path, vdm_name)
  312         except exception.EMCVmaxXMLAPIError as e:
  313             LOG.exception("CIFS server %(name)s on mover %(mover_name)s "
  314                           "not found due to error %(err)s. Skip the "
  315                           "deletion.",
  316                           {'name': path, 'mover_name': vdm_name,
  317                            'err': e.message})
  318 
  319         try:
  320             # Delete file system
  321             self._get_context('FileSystem').delete(share_name)
  322         except exception.EMCVmaxXMLAPIError as e:
  323             LOG.exception("File system  %(share_name)s not found due to "
  324                           "error %(err)s. Skip the deletion.",
  325                           {'share_name': share_name,
  326                            'err': e.message})
  327 
  328     def delete_snapshot(self, context, snapshot, share_server=None):
  329         """Delete a snapshot."""
  330         self._get_context('Snapshot').delete(snapshot['id'])
  331 
  332     def ensure_share(self, context, share, share_server=None):
  333         """Ensure that the share is exported."""
  334 
  335     def extend_share(self, share, new_size, share_server=None):
  336         # Get the pool name from share host field
  337         pool_name = share_utils.extract_host(share['host'], level='pool')
  338         if not pool_name:
  339             message = (_("Pool is not available in the share host %s.") %
  340                        share['host'])
  341             raise exception.InvalidHost(reason=message)
  342 
  343         share_name = share['id']
  344 
  345         self._get_context('FileSystem').extend(
  346             share_name, pool_name, new_size * units.Ki)
  347 
  348     def allow_access(self, context, share, access, share_server=None):
  349         """Allow access to a share."""
  350         access_level = access['access_level']
  351         if access_level not in const.ACCESS_LEVELS:
  352             raise exception.InvalidShareAccessLevel(level=access_level)
  353 
  354         share_proto = share['share_proto']
  355 
  356         if share_proto == 'NFS':
  357             self._nfs_allow_access(context, share, access, share_server)
  358         elif share_proto == 'CIFS':
  359             self._cifs_allow_access(context, share, access, share_server)
  360         else:
  361             raise exception.InvalidShare(
  362                 reason=(_('Invalid NAS protocol supplied: %s.')
  363                         % share_proto))
  364 
  365     @enas_utils.log_enter_exit
  366     def _cifs_allow_access(self, context, share, access, share_server):
  367         """Allow access to CIFS share."""
  368         vdm_name = self._get_share_server_name(share_server)
  369         share_name = share['id']
  370 
  371         if access['access_type'] != 'user':
  372             reason = _('Only user access type allowed for CIFS share')
  373             raise exception.InvalidShareAccess(reason=reason)
  374 
  375         user_name = access['access_to']
  376 
  377         access_level = access['access_level']
  378         if access_level == const.ACCESS_LEVEL_RW:
  379             cifs_access = constants.CIFS_ACL_FULLCONTROL
  380         else:
  381             cifs_access = constants.CIFS_ACL_READ
  382 
  383         # Check if CIFS server exists.
  384         server_name = vdm_name
  385         status, server = self._get_context('CIFSServer').get(server_name,
  386                                                              vdm_name)
  387         if status != constants.STATUS_OK:
  388             message = (_("CIFS server %s not found.") % server_name)
  389             LOG.error(message)
  390             raise exception.EMCVmaxXMLAPIError(err=message)
  391 
  392         self._get_context('CIFSShare').allow_share_access(
  393             vdm_name,
  394             share_name,
  395             user_name,
  396             server['domain'],
  397             access=cifs_access)
  398 
  399     @enas_utils.log_enter_exit
  400     def _nfs_allow_access(self, context, share, access, share_server):
  401         """Allow access to NFS share."""
  402         vdm_name = self._get_share_server_name(share_server)
  403 
  404         access_type = access['access_type']
  405         if access_type != 'ip':
  406             reason = _('Only ip access type allowed.')
  407             raise exception.InvalidShareAccess(reason=reason)
  408 
  409         host_ip = access['access_to']
  410         access_level = access['access_level']
  411 
  412         self._get_context('NFSShare').allow_share_access(
  413             share['id'], host_ip, vdm_name, access_level)
  414 
  415     def update_access(self, context, share, access_rules, add_rules,
  416                       delete_rules, share_server=None):
  417         # deleting rules
  418         for rule in delete_rules:
  419             self.deny_access(context, share, rule, share_server)
  420 
  421         # adding rules
  422         for rule in add_rules:
  423             self.allow_access(context, share, rule, share_server)
  424 
  425         # recovery mode
  426         if not (add_rules or delete_rules):
  427             white_list = []
  428             for rule in access_rules:
  429                 self.allow_access(context, share, rule, share_server)
  430                 white_list.append(
  431                     enas_utils.convert_ipv6_format_if_needed(
  432                         rule['access_to']))
  433             self.clear_access(share, share_server, white_list)
  434 
  435     def clear_access(self, share, share_server, white_list):
  436         share_proto = share['share_proto'].upper()
  437         share_name = share['id']
  438         if share_proto == 'CIFS':
  439             self._cifs_clear_access(share_name, share_server, white_list)
  440         elif share_proto == 'NFS':
  441             self._nfs_clear_access(share_name, share_server, white_list)
  442 
  443     @enas_utils.log_enter_exit
  444     def _cifs_clear_access(self, share_name, share_server, white_list):
  445         """Clear access for CIFS share except hosts in the white list."""
  446         vdm_name = self._get_share_server_name(share_server)
  447 
  448         # Check if CIFS server exists.
  449         server_name = vdm_name
  450         status, server = self._get_context('CIFSServer').get(server_name,
  451                                                              vdm_name)
  452         if status != constants.STATUS_OK:
  453             message = (_("CIFS server %(server_name)s has issue. "
  454                          "Detail: %(status)s") %
  455                        {'server_name': server_name, 'status': status})
  456             raise exception.EMCVmaxXMLAPIError(err=message)
  457 
  458         self._get_context('CIFSShare').clear_share_access(
  459             share_name=share_name,
  460             mover_name=vdm_name,
  461             domain=server['domain'],
  462             white_list_users=white_list)
  463 
  464     @enas_utils.log_enter_exit
  465     def _nfs_clear_access(self, share_name, share_server, white_list):
  466         """Clear access for NFS share except hosts in the white list."""
  467         self._get_context('NFSShare').clear_share_access(
  468             share_name=share_name,
  469             mover_name=self._get_share_server_name(share_server),
  470             white_list_hosts=white_list)
  471 
  472     def deny_access(self, context, share, access, share_server=None):
  473         """Deny access to a share."""
  474         share_proto = share['share_proto']
  475 
  476         if share_proto == 'NFS':
  477             self._nfs_deny_access(share, access, share_server)
  478         elif share_proto == 'CIFS':
  479             self._cifs_deny_access(share, access, share_server)
  480         else:
  481             raise exception.InvalidShare(
  482                 reason=_('Unsupported share protocol'))
  483 
  484     @enas_utils.log_enter_exit
  485     def _cifs_deny_access(self, share, access, share_server):
  486         """Deny access to CIFS share."""
  487         vdm_name = self._get_share_server_name(share_server)
  488         share_name = share['id']
  489 
  490         if access['access_type'] != 'user':
  491             LOG.warning("Only user access type allowed for CIFS share.")
  492             return
  493 
  494         user_name = access['access_to']
  495 
  496         access_level = access['access_level']
  497         if access_level == const.ACCESS_LEVEL_RW:
  498             cifs_access = constants.CIFS_ACL_FULLCONTROL
  499         else:
  500             cifs_access = constants.CIFS_ACL_READ
  501 
  502         # Check if CIFS server exists.
  503         server_name = vdm_name
  504         status, server = self._get_context('CIFSServer').get(server_name,
  505                                                              vdm_name)
  506         if status != constants.STATUS_OK:
  507             message = (_("CIFS server %s not found.") % server_name)
  508             LOG.error(message)
  509             raise exception.EMCVmaxXMLAPIError(err=message)
  510 
  511         self._get_context('CIFSShare').deny_share_access(
  512             vdm_name,
  513             share_name,
  514             user_name,
  515             server['domain'],
  516             access=cifs_access)
  517 
  518     @enas_utils.log_enter_exit
  519     def _nfs_deny_access(self, share, access, share_server):
  520         """Deny access to NFS share."""
  521         vdm_name = self._get_share_server_name(share_server)
  522 
  523         access_type = access['access_type']
  524         if access_type != 'ip':
  525             LOG.warning("Only ip access type allowed.")
  526             return
  527 
  528         host_ip = enas_utils.convert_ipv6_format_if_needed(access['access_to'])
  529 
  530         self._get_context('NFSShare').deny_share_access(share['id'], host_ip,
  531                                                         vdm_name)
  532 
  533     def check_for_setup_error(self):
  534         """Check for setup error."""
  535         # To verify the input from Manila configuration
  536         status, out = self._get_context('Mover').get_ref(self.mover_name,
  537                                                          True)
  538         if constants.STATUS_ERROR == status:
  539             message = (_("Could not find Data Mover by name: %s.") %
  540                        self.mover_name)
  541             LOG.error(message)
  542             raise exception.InvalidParameterValue(err=message)
  543 
  544         self.pools = self._get_managed_storage_pools(self.pool_conf)
  545 
  546     def _get_managed_storage_pools(self, pools):
  547         matched_pools = set()
  548         if pools:
  549             # Get the real pools from the backend storage
  550             status, backend_pools = self._get_context('StoragePool').get_all()
  551             if status != constants.STATUS_OK:
  552                 message = (_("Failed to get storage pool information. "
  553                              "Reason: %s") % backend_pools)
  554                 LOG.error(message)
  555                 raise exception.EMCVmaxXMLAPIError(err=message)
  556 
  557             real_pools = set([item for item in backend_pools])
  558             conf_pools = set([item.strip() for item in pools])
  559             matched_pools, unmatched_pools = enas_utils.do_match_any(
  560                 real_pools, conf_pools)
  561 
  562             if not matched_pools:
  563                 msg = (_("None of the specified storage pools to be managed "
  564                          "exist. Please check your configuration "
  565                          "emc_nas_pool_names in manila.conf. "
  566                          "The available pools in the backend are %s.") %
  567                        ",".join(real_pools))
  568                 raise exception.InvalidParameterValue(err=msg)
  569 
  570             LOG.info("Storage pools: %s will be managed.",
  571                      ",".join(matched_pools))
  572         else:
  573             LOG.debug("No storage pool is specified, so all pools "
  574                       "in storage system will be managed.")
  575         return matched_pools
  576 
  577     def connect(self, emc_share_driver, context):
  578         """Connect to VMAX NAS server."""
  579         config = emc_share_driver.configuration
  580         config.append_config_values(VMAX_OPTS)
  581         self.mover_name = config.vmax_server_container
  582 
  583         self.pool_conf = config.safe_get('vmax_share_data_pools')
  584 
  585         self.reserved_percentage = config.safe_get('reserved_share_percentage')
  586         if self.reserved_percentage is None:
  587             self.reserved_percentage = 0
  588 
  589         self.manager = manager.StorageObjectManager(config)
  590         self.port_conf = config.safe_get('vmax_ethernet_ports')
  591 
  592     def get_managed_ports(self):
  593         # Get the real ports(devices) list from the backend storage
  594         real_ports = self._get_physical_devices(self.mover_name)
  595 
  596         if not self.port_conf:
  597             LOG.debug("No ports are specified, so any of the ports on the "
  598                       "Data Mover can be used.")
  599             return real_ports
  600 
  601         matched_ports, unmanaged_ports = enas_utils.do_match_any(
  602             real_ports, self.port_conf)
  603 
  604         if not matched_ports:
  605             msg = (_("None of the specified network ports exist. "
  606                      "Please check your configuration vmax_ethernet_ports "
  607                      "in manila.conf. The available ports on the Data Mover "
  608                      "are %s.") %
  609                    ",".join(real_ports))
  610             raise exception.BadConfigurationException(reason=msg)
  611 
  612         LOG.debug("Ports: %s can be used.", ",".join(matched_ports))
  613 
  614         return list(matched_ports)
  615 
  616     def update_share_stats(self, stats_dict):
  617         """Communicate with EMCNASClient to get the stats."""
  618         stats_dict['driver_version'] = VERSION
  619 
  620         self._get_context('Mover').get_ref(self.mover_name, True)
  621 
  622         stats_dict['pools'] = []
  623 
  624         status, pools = self._get_context('StoragePool').get_all()
  625         for name, pool in pools.items():
  626             if not self.pools or pool['name'] in self.pools:
  627                 total_size = float(pool['total_size'])
  628                 used_size = float(pool['used_size'])
  629 
  630                 pool_stat = {
  631                     'pool_name': pool['name'],
  632                     'total_capacity_gb': enas_utils.mb_to_gb(total_size),
  633                     'free_capacity_gb':
  634                         enas_utils.mb_to_gb(total_size - used_size),
  635                     'qos': False,
  636                     'reserved_percentage': self.reserved_percentage,
  637                     'snapshot_support': True,
  638                     'create_share_from_snapshot_support': True,
  639                     'revert_to_snapshot_support': False,
  640                     'ipv6_support': True
  641                 }
  642                 stats_dict['pools'].append(pool_stat)
  643 
  644         if not stats_dict['pools']:
  645             message = _("Failed to update storage pool.")
  646             LOG.error(message)
  647             raise exception.EMCVmaxXMLAPIError(err=message)
  648 
  649     def get_pool(self, share):
  650         """Get the pool name of the share."""
  651         share_name = share['id']
  652         status, filesystem = self._get_context('FileSystem').get(share_name)
  653         if status != constants.STATUS_OK:
  654             message = (_("File System %(name)s not found. "
  655                          "Reason: %(err)s") %
  656                        {'name': share_name, 'err': filesystem})
  657             LOG.error(message)
  658             raise exception.EMCVmaxXMLAPIError(err=message)
  659 
  660         pool_id = filesystem['pools_id'][0]
  661 
  662         # Get the real pools from the backend storage
  663         status, backend_pools = self._get_context('StoragePool').get_all()
  664         if status != constants.STATUS_OK:
  665             message = (_("Failed to get storage pool information. "
  666                          "Reason: %s") % backend_pools)
  667             LOG.error(message)
  668             raise exception.EMCVmaxXMLAPIError(err=message)
  669 
  670         for name, pool_info in backend_pools.items():
  671             if pool_info['id'] == pool_id:
  672                 return name
  673 
  674         available_pools = [item for item in backend_pools]
  675         message = (_("No matched pool name for share: %(share)s. "
  676                      "Available pools: %(pools)s") %
  677                    {'share': share_name, 'pools': available_pools})
  678         raise exception.EMCVmaxXMLAPIError(err=message)
  679 
  680     def get_network_allocations_number(self):
  681         """Returns number of network allocations for creating VIFs."""
  682         return constants.IP_ALLOCATIONS
  683 
  684     def setup_server(self, network_info, metadata=None):
  685         """Set up and configure share server.
  686 
  687         Sets up and configures share server with given network parameters.
  688         """
  689         # Only support single security service with type 'active_directory'
  690         vdm_name = network_info['server_id']
  691         vlan_id = network_info['segmentation_id']
  692         active_directory = None
  693         allocated_interfaces = []
  694 
  695         if network_info.get('security_services'):
  696             is_valid, active_directory = self._get_valid_security_service(
  697                 network_info['security_services'])
  698 
  699             if not is_valid:
  700                 raise exception.EMCVmaxXMLAPIError(err=active_directory)
  701 
  702         try:
  703             if not self._vdm_exist(vdm_name):
  704                 LOG.debug('Share server %s not found, creating '
  705                           'share server...', vdm_name)
  706                 self._get_context('VDM').create(vdm_name, self.mover_name)
  707 
  708             devices = self.get_managed_ports()
  709 
  710             for net_info in network_info['network_allocations']:
  711                 random.shuffle(devices)
  712                 ip_version = net_info['ip_version']
  713                 interface = {
  714                     'name': net_info['id'][-12:],
  715                     'device_name': devices[0],
  716                     'ip': net_info['ip_address'],
  717                     'mover_name': self.mover_name,
  718                     'vlan_id': vlan_id if vlan_id else -1,
  719                 }
  720                 if ip_version == 6:
  721                     interface['ip_version'] = ip_version
  722                     interface['net_mask'] = six.text_type(
  723                         utils.cidr_to_prefixlen(
  724                             network_info['cidr']))
  725                 else:
  726                     interface['net_mask'] = utils.cidr_to_netmask(
  727                         network_info['cidr'])
  728 
  729                 self._get_context('MoverInterface').create(interface)
  730 
  731                 allocated_interfaces.append(interface)
  732 
  733             cifs_interface = allocated_interfaces[0]
  734             nfs_interface = allocated_interfaces[1]
  735             if active_directory:
  736                 self._configure_active_directory(
  737                     active_directory, vdm_name, cifs_interface)
  738 
  739             self._get_context('VDM').attach_nfs_interface(
  740                 vdm_name, nfs_interface['name'])
  741 
  742             return {
  743                 'share_server_name': vdm_name,
  744                 'cifs_if': cifs_interface['ip'],
  745                 'nfs_if': nfs_interface['ip'],
  746             }
  747 
  748         except Exception:
  749             with excutils.save_and_reraise_exception():
  750                 LOG.exception('Could not setup server')
  751                 server_details = self._construct_backend_details(
  752                     vdm_name, allocated_interfaces)
  753                 self.teardown_server(
  754                     server_details, network_info['security_services'])
  755 
  756     def _construct_backend_details(self, vdm_name, interfaces):
  757         if_number = len(interfaces)
  758         cifs_if = interfaces[0]['ip'] if if_number > 0 else None
  759         nfs_if = interfaces[1]['ip'] if if_number > 1 else None
  760 
  761         return {
  762             'share_server_name': vdm_name,
  763             'cifs_if': cifs_if,
  764             'nfs_if': nfs_if,
  765         }
  766 
  767     @enas_utils.log_enter_exit
  768     def _vdm_exist(self, name):
  769         status, out = self._get_context('VDM').get(name)
  770         if constants.STATUS_OK != status:
  771             return False
  772 
  773         return True
  774 
  775     def _get_physical_devices(self, mover_name):
  776         """Get a proper network device to create interface."""
  777         devices = self._get_context('Mover').get_physical_devices(mover_name)
  778         if not devices:
  779             message = (_("Could not get physical device port on mover %s.") %
  780                        self.mover_name)
  781             LOG.error(message)
  782             raise exception.EMCVmaxXMLAPIError(err=message)
  783 
  784         return devices
  785 
  786     def _configure_active_directory(
  787             self, security_service, vdm_name, interface):
  788 
  789         domain = security_service['domain']
  790         server = security_service['dns_ip']
  791 
  792         self._get_context('DNSDomain').create(self.mover_name, domain, server)
  793 
  794         cifs_server_args = {
  795             'name': vdm_name,
  796             'interface_ip': interface['ip'],
  797             'domain_name': security_service['domain'],
  798             'user_name': security_service['user'],
  799             'password': security_service['password'],
  800             'mover_name': vdm_name,
  801             'is_vdm': True,
  802         }
  803 
  804         self._get_context('CIFSServer').create(cifs_server_args)
  805 
  806     def teardown_server(self, server_details, security_services=None):
  807         """Teardown share server."""
  808         if not server_details:
  809             LOG.debug('Server details are empty.')
  810             return
  811 
  812         vdm_name = server_details.get('share_server_name')
  813         if not vdm_name:
  814             LOG.debug('No share server found in server details.')
  815             return
  816 
  817         cifs_if = server_details.get('cifs_if')
  818         nfs_if = server_details.get('nfs_if')
  819 
  820         status, vdm = self._get_context('VDM').get(vdm_name)
  821         if constants.STATUS_OK != status:
  822             LOG.debug('Share server %s not found.', vdm_name)
  823             return
  824 
  825         interfaces = self._get_context('VDM').get_interfaces(vdm_name)
  826 
  827         for if_name in interfaces['nfs']:
  828             self._get_context('VDM').detach_nfs_interface(vdm_name, if_name)
  829 
  830         if security_services:
  831             # Only support single security service with type 'active_directory'
  832             is_valid, active_directory = self._get_valid_security_service(
  833                 security_services)
  834             if is_valid:
  835                 status, servers = self._get_context('CIFSServer').get_all(
  836                     vdm_name)
  837                 if constants.STATUS_OK != status:
  838                     LOG.error('Could not find CIFS server by name: %s.',
  839                               vdm_name)
  840                 else:
  841                     cifs_servers = copy.deepcopy(servers)
  842                     for name, server in cifs_servers.items():
  843                         # Unjoin CIFS Server from domain
  844                         cifs_server_args = {
  845                             'name': server['name'],
  846                             'join_domain': False,
  847                             'user_name': active_directory['user'],
  848                             'password': active_directory['password'],
  849                             'mover_name': vdm_name,
  850                             'is_vdm': True,
  851                         }
  852 
  853                         try:
  854                             self._get_context('CIFSServer').modify(
  855                                 cifs_server_args)
  856                         except exception.EMCVmaxXMLAPIError as expt:
  857                             LOG.debug("Failed to modify CIFS server "
  858                                       "%(server)s. Reason: %(err)s.",
  859                                       {'server': server, 'err': expt})
  860 
  861                         self._get_context('CIFSServer').delete(name, vdm_name)
  862 
  863         # Delete interface from Data Mover
  864         if cifs_if:
  865             self._get_context('MoverInterface').delete(cifs_if,
  866                                                        self.mover_name)
  867 
  868         if nfs_if:
  869             self._get_context('MoverInterface').delete(nfs_if,
  870                                                        self.mover_name)
  871 
  872         # Delete Virtual Data Mover
  873         self._get_context('VDM').delete(vdm_name)
  874 
  875     def _get_valid_security_service(self, security_services):
  876         """Validate security services and return a supported security service.
  877 
  878         :param security_services:
  879         :returns: (<is_valid>, <data>) -- <is_valid> is true to indicate
  880             security_services includes zero or single security service for
  881             active directory. Otherwise, it would return false. <data> return
  882             error message when <is_valid> is false. Otherwise, it will
  883             return zero or single security service for active directory.
  884         """
  885 
  886         # Only support single security service with type 'active_directory'
  887         if (len(security_services) > 1 or
  888                 (security_services and
  889                     security_services[0]['type'] != 'active_directory')):
  890             return False, _("Unsupported security services. "
  891                             "Only support single security service and "
  892                             "only support type 'active_directory'")
  893 
  894         return True, security_services[0]
  895 
  896     def _get_share_server_name(self, share_server):
  897         try:
  898             return share_server['backend_details']['share_server_name']
  899         except Exception:
  900             LOG.debug("Didn't get share server name from share_server %s.",
  901                       share_server)
  902         return share_server['id']
  903 
  904     def _get_context(self, context_type):
  905         return self.manager.getStorageContext(context_type)