"Fossies" - the Fresh Open Source Software Archive

Member "manila-8.1.4/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py" (19 Nov 2020, 18771 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. For more information about "lib_multi_svm.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 8.1.3_vs_8.1.4.

    1 # Copyright (c) 2015 Clinton Knight.  All rights reserved.
    2 #
    3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 #    not use this file except in compliance with the License. You may obtain
    5 #    a copy of the License at
    6 #
    7 #         http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 #    Unless required by applicable law or agreed to in writing, software
   10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 #    License for the specific language governing permissions and limitations
   13 #    under the License.
   14 """
   15 NetApp Data ONTAP cDOT multi-SVM storage driver library.
   16 
   17 This library extends the abstract base library and completes the multi-SVM
   18 functionality needed by the cDOT multi-SVM Manila driver.  This library
   19 variant creates Data ONTAP storage virtual machines (i.e. 'vservers')
   20 as needed to provision shares.
   21 """
   22 
   23 import re
   24 
   25 from oslo_log import log
   26 from oslo_serialization import jsonutils
   27 from oslo_utils import excutils
   28 
   29 from manila import exception
   30 from manila.i18n import _
   31 from manila.share.drivers.netapp.dataontap.client import client_cmode
   32 from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
   33 from manila.share.drivers.netapp import utils as na_utils
   34 from manila import utils
   35 
   36 
   37 LOG = log.getLogger(__name__)
   38 SUPPORTED_NETWORK_TYPES = (None, 'flat', 'vlan')
   39 SEGMENTED_NETWORK_TYPES = ('vlan',)
   40 DEFAULT_MTU = 1500
   41 CLUSTER_IPSPACES = ('Cluster', 'Default')
   42 
   43 
   44 class NetAppCmodeMultiSVMFileStorageLibrary(
   45         lib_base.NetAppCmodeFileStorageLibrary):
   46 
   47     @na_utils.trace
   48     def check_for_setup_error(self):
   49 
   50         if self._have_cluster_creds:
   51             if self.configuration.netapp_vserver:
   52                 msg = ('Vserver is specified in the configuration. This is '
   53                        'ignored when the driver is managing share servers.')
   54                 LOG.warning(msg)
   55 
   56         else:  # only have vserver creds, which is an error in multi_svm mode
   57             msg = _('Cluster credentials must be specified in the '
   58                     'configuration when the driver is managing share servers.')
   59             raise exception.InvalidInput(reason=msg)
   60 
   61         # Ensure one or more aggregates are available.
   62         if not self._find_matching_aggregates():
   63             msg = _('No aggregates are available for provisioning shares. '
   64                     'Ensure that the configuration option '
   65                     'netapp_aggregate_name_search_pattern is set correctly.')
   66             raise exception.NetAppException(msg)
   67 
   68         (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
   69             check_for_setup_error())
   70 
   71     @na_utils.trace
   72     def _get_vserver(self, share_server=None, vserver_name=None):
   73 
   74         if share_server:
   75             backend_details = share_server.get('backend_details')
   76             vserver = backend_details.get(
   77                 'vserver_name') if backend_details else None
   78 
   79             if not vserver:
   80                 msg = _('Vserver name is absent in backend details. Please '
   81                         'check whether Vserver was created properly.')
   82                 raise exception.VserverNotSpecified(msg)
   83         elif vserver_name:
   84             vserver = vserver_name
   85         else:
   86             msg = _('Share server not provided')
   87             raise exception.InvalidInput(reason=msg)
   88 
   89         if not self._client.vserver_exists(vserver):
   90             raise exception.VserverNotFound(vserver=vserver)
   91 
   92         vserver_client = self._get_api_client(vserver)
   93         return vserver, vserver_client
   94 
   95     def _get_ems_pool_info(self):
   96         return {
   97             'pools': {
   98                 'vserver': None,
   99                 'aggregates': self._find_matching_aggregates(),
  100             },
  101         }
  102 
  103     @na_utils.trace
  104     def _handle_housekeeping_tasks(self):
  105         """Handle various cleanup activities."""
  106         self._client.prune_deleted_nfs_export_policies()
  107         self._client.prune_deleted_snapshots()
  108         self._client.remove_unused_qos_policy_groups()
  109 
  110         (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
  111             _handle_housekeeping_tasks())
  112 
  113     @na_utils.trace
  114     def _find_matching_aggregates(self):
  115         """Find all aggregates match pattern."""
  116         aggregate_names = self._client.list_non_root_aggregates()
  117         pattern = self.configuration.netapp_aggregate_name_search_pattern
  118         return [aggr_name for aggr_name in aggregate_names
  119                 if re.match(pattern, aggr_name)]
  120 
  121     @na_utils.trace
  122     def setup_server(self, network_info, metadata=None):
  123         """Creates and configures new Vserver."""
  124 
  125         vlan = network_info['segmentation_id']
  126         ports = {}
  127         for network_allocation in network_info['network_allocations']:
  128             ports[network_allocation['id']] = network_allocation['ip_address']
  129 
  130         @utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
  131         def setup_server_with_lock():
  132             LOG.debug('Creating server %s', network_info['server_id'])
  133             self._validate_network_type(network_info)
  134 
  135             vserver_name = self._get_vserver_name(network_info['server_id'])
  136             server_details = {
  137                 'vserver_name': vserver_name,
  138                 'ports': jsonutils.dumps(ports)
  139             }
  140 
  141             try:
  142                 self._create_vserver(vserver_name, network_info)
  143             except Exception as e:
  144                 e.detail_data = {'server_details': server_details}
  145                 raise
  146 
  147             return server_details
  148 
  149         return setup_server_with_lock()
  150 
  151     @na_utils.trace
  152     def _validate_network_type(self, network_info):
  153         """Raises exception if the segmentation type is incorrect."""
  154         if network_info['network_type'] not in SUPPORTED_NETWORK_TYPES:
  155             msg = _('The specified network type %s is unsupported by the '
  156                     'NetApp clustered Data ONTAP driver')
  157             raise exception.NetworkBadConfigurationException(
  158                 reason=msg % network_info['network_type'])
  159 
  160     @na_utils.trace
  161     def _get_vserver_name(self, server_id):
  162         return self.configuration.netapp_vserver_name_template % server_id
  163 
  164     @na_utils.trace
  165     def _create_vserver(self, vserver_name, network_info):
  166         """Creates Vserver with given parameters if it doesn't exist."""
  167 
  168         if self._client.vserver_exists(vserver_name):
  169             msg = _('Vserver %s already exists.')
  170             raise exception.NetAppException(msg % vserver_name)
  171 
  172         # NOTE(lseki): If there's already an ipspace created for the same VLAN
  173         # port, reuse it. It will be named after the previously created share
  174         # server's neutron subnet id.
  175         node_name = self._client.list_cluster_nodes()[0]
  176         port = self._get_node_data_port(node_name)
  177         vlan = network_info['segmentation_id']
  178         ipspace_name = self._client.get_ipspace_name_for_vlan_port(
  179             node_name, port, vlan) or self._create_ipspace(network_info)
  180 
  181         LOG.debug('Vserver %s does not exist, creating.', vserver_name)
  182         self._client.create_vserver(
  183             vserver_name,
  184             self.configuration.netapp_root_volume_aggregate,
  185             self.configuration.netapp_root_volume,
  186             self._find_matching_aggregates(),
  187             ipspace_name)
  188 
  189         vserver_client = self._get_api_client(vserver=vserver_name)
  190         security_services = None
  191         try:
  192             self._create_vserver_lifs(vserver_name,
  193                                       vserver_client,
  194                                       network_info,
  195                                       ipspace_name)
  196 
  197             self._create_vserver_admin_lif(vserver_name,
  198                                            vserver_client,
  199                                            network_info,
  200                                            ipspace_name)
  201 
  202             self._create_vserver_routes(vserver_client,
  203                                         network_info)
  204 
  205             vserver_client.enable_nfs(
  206                 self.configuration.netapp_enabled_share_protocols)
  207 
  208             security_services = network_info.get('security_services')
  209             if security_services:
  210                 self._client.setup_security_services(security_services,
  211                                                      vserver_client,
  212                                                      vserver_name)
  213         except Exception:
  214             with excutils.save_and_reraise_exception():
  215                 LOG.error("Failed to configure Vserver.")
  216                 # NOTE(dviroel): At this point, the lock was already acquired
  217                 # by the caller of _create_vserver.
  218                 self._delete_vserver(vserver_name,
  219                                      security_services=security_services,
  220                                      needs_lock=False)
  221 
  222     def _get_valid_ipspace_name(self, network_id):
  223         """Get IPspace name according to network id."""
  224         return 'ipspace_' + network_id.replace('-', '_')
  225 
  226     @na_utils.trace
  227     def _create_ipspace(self, network_info):
  228         """If supported, create an IPspace for a new Vserver."""
  229 
  230         if not self._client.features.IPSPACES:
  231             return None
  232 
  233         if (network_info['network_allocations'][0]['network_type']
  234                 not in SEGMENTED_NETWORK_TYPES):
  235             return client_cmode.DEFAULT_IPSPACE
  236 
  237         # NOTE(cknight): Neutron needs cDOT IP spaces because it can provide
  238         # overlapping IP address ranges for different subnets.  That is not
  239         # believed to be an issue for any of Manila's other network plugins.
  240         ipspace_id = network_info.get('neutron_subnet_id')
  241         if not ipspace_id:
  242             return client_cmode.DEFAULT_IPSPACE
  243 
  244         ipspace_name = self._get_valid_ipspace_name(ipspace_id)
  245         self._client.create_ipspace(ipspace_name)
  246 
  247         return ipspace_name
  248 
  249     @na_utils.trace
  250     def _create_vserver_lifs(self, vserver_name, vserver_client, network_info,
  251                              ipspace_name):
  252         """Create Vserver data logical interfaces (LIFs)."""
  253 
  254         nodes = self._client.list_cluster_nodes()
  255         node_network_info = zip(nodes, network_info['network_allocations'])
  256 
  257         for node_name, network_allocation in node_network_info:
  258             lif_name = self._get_lif_name(node_name, network_allocation)
  259             self._create_lif(vserver_client, vserver_name, ipspace_name,
  260                              node_name, lif_name, network_allocation)
  261 
  262     @na_utils.trace
  263     def _create_vserver_admin_lif(self, vserver_name, vserver_client,
  264                                   network_info, ipspace_name):
  265         """Create Vserver admin LIF, if defined."""
  266 
  267         network_allocations = network_info.get('admin_network_allocations')
  268         if not network_allocations:
  269             LOG.info('No admin network defined for Vserver %s.',
  270                      vserver_name)
  271             return
  272 
  273         node_name = self._client.list_cluster_nodes()[0]
  274         network_allocation = network_allocations[0]
  275         lif_name = self._get_lif_name(node_name, network_allocation)
  276 
  277         self._create_lif(vserver_client, vserver_name, ipspace_name,
  278                          node_name, lif_name, network_allocation)
  279 
  280     @na_utils.trace
  281     def _create_vserver_routes(self, vserver_client, network_info):
  282         """Create Vserver route and set gateways."""
  283         route_gateways = []
  284         # NOTE(gouthamr): Use the gateway from the tenant subnet/s
  285         # for the static routes. Do not configure a route for the admin
  286         # subnet because fast path routing will work for incoming
  287         # connections and there are no requirements for outgoing
  288         # connections on the admin network yet.
  289         for net_allocation in (network_info['network_allocations']):
  290             if net_allocation['gateway'] not in route_gateways:
  291                 vserver_client.create_route(net_allocation['gateway'])
  292                 route_gateways.append(net_allocation['gateway'])
  293 
  294     @na_utils.trace
  295     def _get_node_data_port(self, node):
  296         port_names = self._client.list_node_data_ports(node)
  297         pattern = self.configuration.netapp_port_name_search_pattern
  298         matched_port_names = [port_name for port_name in port_names
  299                               if re.match(pattern, port_name)]
  300         if not matched_port_names:
  301             raise exception.NetAppException(
  302                 _('Could not find eligible network ports on node %s on which '
  303                   'to create Vserver LIFs.') % node)
  304         return matched_port_names[0]
  305 
  306     def _get_lif_name(self, node_name, network_allocation):
  307         """Get LIF name based on template from manila.conf file."""
  308         lif_name_args = {
  309             'node': node_name,
  310             'net_allocation_id': network_allocation['id'],
  311         }
  312         return self.configuration.netapp_lif_name_template % lif_name_args
  313 
  314     @na_utils.trace
  315     def _create_lif(self, vserver_client, vserver_name, ipspace_name,
  316                     node_name, lif_name, network_allocation):
  317         """Creates LIF for Vserver."""
  318 
  319         port = self._get_node_data_port(node_name)
  320         ip_address = network_allocation['ip_address']
  321         netmask = utils.cidr_to_netmask(network_allocation['cidr'])
  322         vlan = network_allocation['segmentation_id']
  323         network_mtu = network_allocation.get('mtu')
  324         mtu = network_mtu or DEFAULT_MTU
  325 
  326         if not vserver_client.network_interface_exists(
  327                 vserver_name, node_name, port, ip_address, netmask, vlan):
  328 
  329             self._client.create_network_interface(
  330                 ip_address, netmask, vlan, node_name, port, vserver_name,
  331                 lif_name, ipspace_name, mtu)
  332 
  333     @na_utils.trace
  334     def get_network_allocations_number(self):
  335         """Get number of network interfaces to be created."""
  336         return len(self._client.list_cluster_nodes())
  337 
  338     @na_utils.trace
  339     def get_admin_network_allocations_number(self, admin_network_api):
  340         """Get number of network allocations for creating admin LIFs."""
  341         return 1 if admin_network_api else 0
  342 
  343     @na_utils.trace
  344     def teardown_server(self, server_details, security_services=None):
  345         """Teardown share server."""
  346         vserver = server_details.get(
  347             'vserver_name') if server_details else None
  348 
  349         if not vserver:
  350             LOG.warning("Vserver not specified for share server being "
  351                         "deleted. Deletion of share server record will "
  352                         "proceed anyway.")
  353             return
  354 
  355         elif not self._client.vserver_exists(vserver):
  356             LOG.warning("Could not find Vserver for share server being "
  357                         "deleted: %s. Deletion of share server "
  358                         "record will proceed anyway.", vserver)
  359             return
  360 
  361         self._delete_vserver(vserver, security_services=security_services)
  362 
  363     @na_utils.trace
  364     def _delete_vserver(self, vserver, security_services=None,
  365                         needs_lock=True):
  366         """Delete a Vserver plus IPspace and security services as needed."""
  367 
  368         ipspace_name = self._client.get_vserver_ipspace(vserver)
  369 
  370         vserver_client = self._get_api_client(vserver=vserver)
  371         network_interfaces = vserver_client.get_network_interfaces()
  372 
  373         interfaces_on_vlans = []
  374         vlans = []
  375         for interface in network_interfaces:
  376             if '-' in interface['home-port']:
  377                 interfaces_on_vlans.append(interface)
  378                 vlans.append(interface['home-port'])
  379 
  380         if vlans:
  381             vlans = '-'.join(sorted(set(vlans))) if vlans else None
  382             vlan_id = vlans.split('-')[-1]
  383         else:
  384             vlan_id = None
  385 
  386         def _delete_vserver_without_lock():
  387             self._client.delete_vserver(vserver,
  388                                         vserver_client,
  389                                         security_services=security_services)
  390 
  391             if (ipspace_name and ipspace_name not in CLUSTER_IPSPACES
  392                     and not self._client.ipspace_has_data_vservers(
  393                         ipspace_name)):
  394                 self._client.delete_ipspace(ipspace_name)
  395 
  396             self._delete_vserver_vlans(interfaces_on_vlans)
  397 
  398         @utils.synchronized('netapp-VLAN-%s' % vlan_id, external=True)
  399         def _delete_vserver_with_lock():
  400             _delete_vserver_without_lock()
  401 
  402         if needs_lock:
  403             return _delete_vserver_with_lock()
  404         else:
  405             return _delete_vserver_without_lock()
  406 
  407     @na_utils.trace
  408     def _delete_vserver_vlans(self, network_interfaces_on_vlans):
  409         """Delete Vserver's VLAN configuration from ports"""
  410         for interface in network_interfaces_on_vlans:
  411             try:
  412                 home_port = interface['home-port']
  413                 port, vlan = home_port.split('-')
  414                 node = interface['home-node']
  415                 self._client.delete_vlan(node, port, vlan)
  416             except exception.NetAppException:
  417                 LOG.exception("Deleting Vserver VLAN failed.")
  418 
  419     def get_configured_ip_versions(self):
  420         versions = [4]
  421         options = self._client.get_net_options()
  422         if options['ipv6-enabled']:
  423             versions.append(6)
  424         return versions
  425 
  426     def manage_server(self, context, share_server, identifier, driver_options):
  427         """Manages a vserver by renaming it and returning backend_details."""
  428         new_vserver_name = self._get_vserver_name(share_server['id'])
  429         old_vserver_name = self._get_correct_vserver_old_name(identifier)
  430 
  431         if new_vserver_name != old_vserver_name:
  432             self._client.rename_vserver(old_vserver_name, new_vserver_name)
  433 
  434         backend_details = {'vserver_name': new_vserver_name}
  435         return new_vserver_name, backend_details
  436 
  437     def unmanage_server(self, server_details, security_services=None):
  438         pass
  439 
  440     def get_share_server_network_info(
  441             self, context, share_server, identifier, driver_options):
  442         """Returns a list of IPs for each vserver network interface."""
  443         vserver_name = self._get_correct_vserver_old_name(identifier)
  444 
  445         vserver, vserver_client = self._get_vserver(vserver_name=vserver_name)
  446 
  447         interfaces = vserver_client.get_network_interfaces()
  448         allocations = []
  449         for lif in interfaces:
  450             allocations.append(lif['address'])
  451         return allocations
  452 
  453     def _get_correct_vserver_old_name(self, identifier):
  454 
  455         # In case vserver_name includes the template, we check and add it here
  456         if not self._client.vserver_exists(identifier):
  457             return self._get_vserver_name(identifier)
  458         return identifier