components/openstack/cinder/files/solaris/nfs.py
changeset 5405 66fd59fecd68
child 6849 f9a2279efa0d
equal deleted inserted replaced
5404:55e409ba4e72 5405:66fd59fecd68
       
     1 # vim: tabstop=4 shiftwidth=4 softtabstop=4
       
     2 # Copyright (c) 2012 OpenStack LLC.
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
       
     6 #
       
     7 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
       
     8 #    not use this file except in compliance with the License. You may obtain
       
     9 #    a copy of the License at
       
    10 #
       
    11 #         http://www.apache.org/licenses/LICENSE-2.0
       
    12 #
       
    13 #    Unless required by applicable law or agreed to in writing, software
       
    14 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
       
    15 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
       
    16 #    License for the specific language governing permissions and limitations
       
    17 #    under the License.
       
    18 """
       
    19 Volume driver for Solaris ZFS NFS storage
       
    20 """
       
    21 
       
    22 import os
       
    23 
       
    24 from oslo_config import cfg
       
    25 from oslo_log import log as logging
       
    26 from oslo_utils import units
       
    27 
       
    28 from cinder import exception
       
    29 from cinder.i18n import _
       
    30 from cinder.volume.drivers import nfs
       
    31 
       
    32 ZFS_NFS_VERSION = '1.0.0'
       
    33 
       
    34 LOG = logging.getLogger(__name__)
       
    35 
       
    36 solaris_zfs_nfs_opts = [
       
    37     cfg.BoolOpt('nfs_round_robin',
       
    38                 default=True,
       
    39                 help=('Schedule volumes round robin across NFS shares.')),
       
    40 ]
       
    41 
       
    42 CONF = cfg.CONF
       
    43 CONF.register_opts(solaris_zfs_nfs_opts)
       
    44 
       
    45 
       
    46 class ZfsNfsVolumeDriver(nfs.NfsDriver):
       
    47     """Local ZFS NFS volume operations."""
       
    48 
       
    49     driver_volume_type = 'nfs'
       
    50     driver_prefix = 'nfs'
       
    51     volume_backend_name = 'Solaris_NFS'
       
    52 
       
    53     def __init__(self, *args, **kwargs):
       
    54         super(ZfsNfsVolumeDriver, self).__init__(*args, **kwargs)
       
    55         self.configuration.append_config_values(solaris_zfs_nfs_opts)
       
    56 
       
    57         self.last_rr_pos = None
       
    58 
       
    59         if self.configuration.nfs_mount_options:
       
    60             LOG.warning(_("Solaris NFS driver ignores mount options"))
       
    61 
       
    62     def _update_volume_stats(self):
       
    63         """Retrieve volume status info."""
       
    64 
       
    65         stats = {}
       
    66         backend_name = self.configuration.safe_get('volume_backend_name')
       
    67         stats["volume_backend_name"] = backend_name or self.__class__.__name__
       
    68         stats["driver_version"] = ZFS_NFS_VERSION
       
    69         stats["vendor_name"] = 'Oracle'
       
    70         stats['storage_protocol'] = self.driver_volume_type
       
    71 
       
    72         self._ensure_shares_mounted()
       
    73 
       
    74         global_capacity = 0
       
    75         global_free = 0
       
    76         for share in self._mounted_shares:
       
    77             capacity, free, used = self._get_capacity_info(share)
       
    78             global_capacity += capacity
       
    79             global_free += free
       
    80 
       
    81         stats['total_capacity_gb'] = global_capacity / float(units.Gi)
       
    82         stats['free_capacity_gb'] = global_free / float(units.Gi)
       
    83         stats['reserved_percentage'] = 0
       
    84         stats['QoS_support'] = False
       
    85         self._stats = stats
       
    86 
       
    87     def _create_sparsed_file(self, path, size):
       
    88         """Creates a sparse file of a given size in GiB."""
       
    89         self._execute('/usr/bin/truncate', '-s', '%sG' % size, path)
       
    90 
       
    91     def _create_regular_file(self, path, size):
       
    92         """Creates a regular file of given size in GiB."""
       
    93 
       
    94         block_size_mb = 1
       
    95         block_count = size * units.Gi / (block_size_mb * units.Mi)
       
    96 
       
    97         self._execute('/usr/bin/dd', 'if=/dev/zero', 'of=%s' % path,
       
    98                       'bs=%dM' % block_size_mb,
       
    99                       'count=%d' % block_count)
       
   100 
       
   101     def _set_rw_permissions(self, path):
       
   102         """Sets access permissions for given NFS path.
       
   103 
       
   104         :param path: the volume file path.
       
   105         """
       
   106         os.chmod(path, 0o660)
       
   107 
       
   108     def _set_rw_permissions_for_all(self, path):
       
   109         """Sets 666 permissions for the path."""
       
   110         mode = os.stat(path).st_mode
       
   111         os.chmod(path, mode | 0o666)
       
   112 
       
   113     def _set_rw_permissions_for_owner(self, path):
       
   114         """Sets read-write permissions to the owner for the path."""
       
   115         mode = os.stat(path).st_mode
       
   116         os.chmod(path, mode | 0o600)
       
   117 
       
   118     def _delete(self, path):
       
   119         os.unlink(path)
       
   120 
       
   121     def _get_capacity_info(self, nfs_share):
       
   122         """Calculate available space on the NFS share.
       
   123 
       
   124         :param nfs_share: example 172.18.194.100:/var/nfs
       
   125         """
       
   126 
       
   127         mount_point = self._get_mount_point_for_share(nfs_share)
       
   128 
       
   129         st = os.statvfs(mount_point)
       
   130         total_available = st.f_frsize * st.f_bavail
       
   131         total_size = st.f_frsize * st.f_blocks
       
   132 
       
   133         du, _ = self._execute('/usr/bin/gdu', '-sb', '--apparent-size',
       
   134                               '--exclude', '*snapshot*', mount_point)
       
   135         total_allocated = float(du.split()[0])
       
   136         return total_size, total_available, total_allocated
       
   137 
       
   138     def _round_robin(self, sharelist):
       
   139         """
       
   140         Implement a round robin generator for share list
       
   141         """
       
   142 
       
   143         mylen = len(sharelist)
       
   144 
       
   145         if self.last_rr_pos is None:
       
   146             start_pos = 0
       
   147         else:
       
   148             start_pos = (self.last_rr_pos + 1) % mylen
       
   149 
       
   150         pos = start_pos
       
   151         while True:
       
   152             yield sharelist[pos], pos
       
   153             pos = (pos + 1) % mylen
       
   154             if pos == start_pos:
       
   155                 break
       
   156 
       
   157     def _find_share(self, volume_size_in_gib):
       
   158         """Choose NFS share among available ones for given volume size.
       
   159 
       
   160         For instances with more than one share that meets the criteria, the
       
   161         share with the least "allocated" space will be selected.
       
   162 
       
   163         :param volume_size_in_gib: int size in GB
       
   164         """
       
   165 
       
   166         if not self._mounted_shares:
       
   167             raise exception.NfsNoSharesMounted()
       
   168 
       
   169         target_share = None
       
   170         if self.configuration.nfs_round_robin:
       
   171             # Round Robin volume placement on shares
       
   172 
       
   173             LOG.debug(_("_find_share using round robin"))
       
   174 
       
   175             for nfs_share, pos in self._round_robin(self._mounted_shares):
       
   176                 if not self._is_share_eligible(nfs_share, volume_size_in_gib):
       
   177                     continue
       
   178                 target_share = nfs_share
       
   179                 self.last_rr_pos = pos
       
   180                 break
       
   181         else:
       
   182             # Place volume on share with the most free space.
       
   183 
       
   184             LOG.debug(_("_find_share using select most free"))
       
   185 
       
   186             target_share_reserved = 0
       
   187 
       
   188             for nfs_share in self._mounted_shares:
       
   189                 if not self._is_share_eligible(nfs_share, volume_size_in_gib):
       
   190                     continue
       
   191                 total_size, total_available, total_allocated = \
       
   192                     self._get_capacity_info(nfs_share)
       
   193                 if target_share is not None:
       
   194                     if target_share_reserved > total_allocated:
       
   195                         target_share = nfs_share
       
   196                         target_share_reserved = total_allocated
       
   197                 else:
       
   198                     target_share = nfs_share
       
   199                     target_share_reserved = total_allocated
       
   200 
       
   201         if target_share is None:
       
   202             raise exception.NfsNoSuitableShareFound(
       
   203                 volume_size=volume_size_in_gib)
       
   204 
       
   205         LOG.debug('Selected %s as target nfs share.', target_share)
       
   206 
       
   207         return target_share
       
   208 
       
   209     def set_nas_security_options(self, is_new_cinder_install):
       
   210         """Secure NAS options.
       
   211 
       
   212         For Solaris we always operate in a secure mode and do not
       
   213         rely on root or any rootwrap utilities.
       
   214 
       
   215         With RBAC we can do what we need as the cinder user.  We
       
   216         set the nas_secure_file.XXX to be true by default.  We ignore
       
   217         any conf file setting for these string vars.
       
   218 
       
   219         We don't ever use these nas_secure_file_XXX vars in this driver
       
   220         but we still set the value to true.  This might prevent admin/users
       
   221         from opening bugs stating we are not running in a secure mode.
       
   222         """
       
   223 
       
   224         self.configuration.nas_secure_file_operations = 'true'
       
   225         self.configuration.nas_secure_file_permissions = 'true'
       
   226         self._execute_as_root = False
       
   227 
       
   228         LOG.debug('NAS variable secure_file_permissions setting is: %s' %
       
   229                   self.configuration.nas_secure_file_permissions)
       
   230 
       
   231         LOG.debug('NAS variable secure_file_operations setting is: %s' %
       
   232                   self.configuration.nas_secure_file_operations)