"Fossies" - the Fresh Open Source Software Archive

Member "manila-8.1.3/manila/share/drivers/helpers.py" (20 Jul 2020, 25035 Bytes) of package /linux/misc/openstack/manila-8.1.3.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 "helpers.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 8.1.2_vs_8.1.3.

    1 # Copyright 2015 Mirantis Inc.
    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 import copy
   17 import ipaddress
   18 import os
   19 import re
   20 import six
   21 
   22 from oslo_log import log
   23 
   24 from manila.common import constants as const
   25 from manila import exception
   26 from manila.i18n import _
   27 from manila import utils
   28 
   29 LOG = log.getLogger(__name__)
   30 
   31 
   32 class NASHelperBase(object):
   33     """Interface to work with share."""
   34 
   35     def __init__(self, execute, ssh_execute, config_object):
   36         self.configuration = config_object
   37         self._execute = execute
   38         self._ssh_exec = ssh_execute
   39 
   40     def init_helper(self, server):
   41         pass
   42 
   43     def create_exports(self, server, share_name, recreate=False):
   44         """Create new exports, delete old ones if exist."""
   45         raise NotImplementedError()
   46 
   47     def remove_exports(self, server, share_name):
   48         """Remove exports."""
   49         raise NotImplementedError()
   50 
   51     def configure_access(self, server, share_name):
   52         """Configure server before allowing access."""
   53         pass
   54 
   55     def update_access(self, server, share_name, access_rules, add_rules,
   56                       delete_rules):
   57         """Update access rules for given share.
   58 
   59         This driver has two different behaviors according to parameters:
   60         1. Recovery after error - 'access_rules' contains all access_rules,
   61         'add_rules' and 'delete_rules' shall be empty. Previously existing
   62         access rules are cleared and then added back according
   63         to 'access_rules'.
   64 
   65         2. Adding/Deleting of several access rules - 'access_rules' contains
   66         all access_rules, 'add_rules' and 'delete_rules' contain rules which
   67         should be added/deleted. Rules in 'access_rules' are ignored and
   68         only rules from 'add_rules' and 'delete_rules' are applied.
   69 
   70         :param server: None or Share server's backend details
   71         :param share_name: Share's path according to id.
   72         :param access_rules: All access rules for given share
   73         :param add_rules: Empty List or List of access rules which should be
   74                added. access_rules already contains these rules.
   75         :param delete_rules: Empty List or List of access rules which should be
   76                removed. access_rules doesn't contain these rules.
   77         """
   78         raise NotImplementedError()
   79 
   80     @staticmethod
   81     def _verify_server_has_public_address(server):
   82         if 'public_address' in server:
   83             pass
   84         elif 'public_addresses' in server:
   85             if not isinstance(server['public_addresses'], list):
   86                 raise exception.ManilaException(_("public_addresses must be "
   87                                                   "a list"))
   88         else:
   89             raise exception.ManilaException(
   90                 _("Can not get public_address(es) for generation of export."))
   91 
   92     def _get_export_location_template(self, export_location_or_path):
   93         """Returns template of export location.
   94 
   95         Example for NFS:
   96             %s:/path/to/share
   97         Example for CIFS:
   98             \\\\%s\\cifs_share_name
   99         """
  100         raise NotImplementedError()
  101 
  102     def get_exports_for_share(self, server, export_location_or_path):
  103         """Returns list of exports based on server info."""
  104         self._verify_server_has_public_address(server)
  105         export_location_template = self._get_export_location_template(
  106             export_location_or_path)
  107         export_locations = []
  108 
  109         if 'public_addresses' in server:
  110             pairs = list(map(lambda addr: (addr, False),
  111                              server['public_addresses']))
  112         else:
  113             pairs = [(server['public_address'], False)]
  114 
  115         # NOTE(vponomaryov):
  116         # Generic driver case: 'admin_ip' exists only in case of DHSS=True
  117         # mode and 'ip' exists in case of DHSS=False mode.
  118         # Use one of these for creation of export location for service needs.
  119         service_address = server.get("admin_ip", server.get("ip"))
  120         if service_address:
  121             pairs.append((service_address, True))
  122         for ip, is_admin in pairs:
  123             export_locations.append({
  124                 "path": export_location_template % ip,
  125                 "is_admin_only": is_admin,
  126                 "metadata": {
  127                     # TODO(vponomaryov): remove this fake metadata when
  128                     # proper appears.
  129                     "export_location_metadata_example": "example",
  130                 },
  131             })
  132         return export_locations
  133 
  134     def get_share_path_by_export_location(self, server, export_location):
  135         """Returns share path by its export location."""
  136         raise NotImplementedError()
  137 
  138     def disable_access_for_maintenance(self, server, share_name):
  139         """Disables access to share to perform maintenance operations."""
  140 
  141     def restore_access_after_maintenance(self, server, share_name):
  142         """Enables access to share after maintenance operations were done."""
  143 
  144     @staticmethod
  145     def validate_access_rules(access_rules, allowed_types, allowed_levels):
  146         """Validates access rules according to access_type and access_level.
  147 
  148         :param access_rules: List of access rules to be validated.
  149         :param allowed_types: tuple of allowed type values.
  150         :param allowed_levels: tuple of allowed level values.
  151         """
  152         for access in (access_rules or []):
  153             access_type = access['access_type']
  154             access_level = access['access_level']
  155             if access_type not in allowed_types:
  156                 reason = _("Only %s access type allowed.") % (
  157                     ', '.join(tuple(["'%s'" % x for x in allowed_types])))
  158                 raise exception.InvalidShareAccess(reason=reason)
  159             if access_level not in allowed_levels:
  160                 raise exception.InvalidShareAccessLevel(level=access_level)
  161 
  162     def _get_maintenance_file_path(self, share_name):
  163         return os.path.join(self.configuration.share_mount_path,
  164                             "%s.maintenance" % share_name)
  165 
  166 
  167 def nfs_synchronized(f):
  168 
  169     def wrapped_func(self, *args, **kwargs):
  170         key = "nfs-%s" % args[0].get("lock_name", args[0]["instance_id"])
  171 
  172         # NOTE(vponomaryov): 'external' lock is required for DHSS=False
  173         # mode of LVM and Generic drivers, that may have lots of
  174         # driver instances on single host.
  175         @utils.synchronized(key, external=True)
  176         def source_func(self, *args, **kwargs):
  177             return f(self, *args, **kwargs)
  178 
  179         return source_func(self, *args, **kwargs)
  180 
  181     return wrapped_func
  182 
  183 
  184 class NFSHelper(NASHelperBase):
  185     """Interface to work with share."""
  186 
  187     def create_exports(self, server, share_name, recreate=False):
  188         path = os.path.join(self.configuration.share_mount_path, share_name)
  189         server_copy = copy.copy(server)
  190         public_addresses = []
  191         if 'public_addresses' in server_copy:
  192             for address in server_copy['public_addresses']:
  193                 public_addresses.append(
  194                     self._escaped_address(address))
  195             server_copy['public_addresses'] = public_addresses
  196 
  197         for t in ['public_address', 'admin_ip', 'ip']:
  198             address = server_copy.get(t)
  199             if address is not None:
  200                 server_copy[t] = self._escaped_address(address)
  201 
  202         return self.get_exports_for_share(server_copy, path)
  203 
  204     @staticmethod
  205     def _escaped_address(address):
  206         addr = ipaddress.ip_address(six.text_type(address))
  207         if addr.version == 4:
  208             return six.text_type(addr)
  209         else:
  210             return '[%s]' % six.text_type(addr)
  211 
  212     def init_helper(self, server):
  213         try:
  214             self._ssh_exec(server, ['sudo', 'exportfs'])
  215         except exception.ProcessExecutionError as e:
  216             if 'command not found' in e.stderr:
  217                 raise exception.ManilaException(
  218                     _('NFS server is not installed on %s')
  219                     % server['instance_id'])
  220             LOG.error(e.stderr)
  221 
  222     def remove_exports(self, server, share_name):
  223         """Remove exports."""
  224 
  225     @nfs_synchronized
  226     def update_access(self, server, share_name, access_rules, add_rules,
  227                       delete_rules):
  228         """Update access rules for given share.
  229 
  230         Please refer to base class for a more in-depth description.
  231         """
  232         local_path = os.path.join(self.configuration.share_mount_path,
  233                                   share_name)
  234         out, err = self._ssh_exec(server, ['sudo', 'exportfs'])
  235         # Recovery mode
  236         if not (add_rules or delete_rules):
  237 
  238             self.validate_access_rules(
  239                 access_rules, ('ip',),
  240                 (const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
  241 
  242             hosts = self.get_host_list(out, local_path)
  243             for host in hosts:
  244                 parsed_host = self._get_parsed_address_or_cidr(host)
  245                 self._ssh_exec(server, ['sudo', 'exportfs', '-u',
  246                                         ':'.join((parsed_host, local_path))])
  247             self._sync_nfs_temp_and_perm_files(server)
  248             for access in access_rules:
  249                 rules_options = '%s,no_subtree_check,no_root_squash'
  250                 access_to = self._get_parsed_address_or_cidr(
  251                     access['access_to'])
  252                 self._ssh_exec(
  253                     server,
  254                     ['sudo', 'exportfs', '-o',
  255                      rules_options % access['access_level'],
  256                      ':'.join((access_to, local_path))])
  257             self._sync_nfs_temp_and_perm_files(server)
  258         # Adding/Deleting specific rules
  259         else:
  260 
  261             self.validate_access_rules(
  262                 add_rules, ('ip',),
  263                 (const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
  264 
  265             for access in delete_rules:
  266                 try:
  267                     self.validate_access_rules(
  268                         [access], ('ip',),
  269                         (const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
  270                 except (exception.InvalidShareAccess,
  271                         exception.InvalidShareAccessLevel):
  272                     LOG.warning(
  273                         "Unsupported access level %(level)s or access type "
  274                         "%(type)s, skipping removal of access rule to "
  275                         "%(to)s.", {'level': access['access_level'],
  276                                     'type': access['access_type'],
  277                                     'to': access['access_to']})
  278                     continue
  279                 access_to = self._get_parsed_address_or_cidr(
  280                     access['access_to'])
  281                 self._ssh_exec(server, ['sudo', 'exportfs', '-u',
  282                                ':'.join((access_to, local_path))])
  283             if delete_rules:
  284                 self._sync_nfs_temp_and_perm_files(server)
  285             for access in add_rules:
  286                 access_to = self._get_parsed_address_or_cidr(
  287                     access['access_to'])
  288                 found_item = re.search(
  289                     re.escape(local_path) + '[\s\n]*' + re.escape(access_to),
  290                     out)
  291                 if found_item is not None:
  292                     LOG.warning("Access rule %(type)s:%(to)s already "
  293                                 "exists for share %(name)s", {
  294                                     'to': access['access_to'],
  295                                     'type': access['access_type'],
  296                                     'name': share_name
  297                                 })
  298                 else:
  299                     rules_options = '%s,no_subtree_check,no_root_squash'
  300                     self._ssh_exec(
  301                         server,
  302                         ['sudo', 'exportfs', '-o',
  303                          rules_options % access['access_level'],
  304                          ':'.join((access_to, local_path))])
  305             if add_rules:
  306                 self._sync_nfs_temp_and_perm_files(server)
  307 
  308     @staticmethod
  309     def _get_parsed_address_or_cidr(access_to):
  310         network = ipaddress.ip_network(six.text_type(access_to))
  311         mask_length = network.prefixlen
  312         address = six.text_type(network.network_address)
  313         if mask_length == 0:
  314             # Special case because Linux exports don't support /0 netmasks
  315             return '*'
  316         if network.version == 4:
  317             if mask_length == 32:
  318                 return address
  319             return '%s/%s' % (address, mask_length)
  320         if mask_length == 128:
  321             return "[%s]" % address
  322         return "[%s]/%s" % (address, mask_length)
  323 
  324     @staticmethod
  325     def get_host_list(output, local_path):
  326         entries = []
  327         output = output.replace('\n\t\t', ' ')
  328         lines = output.split('\n')
  329         for line in lines:
  330             items = line.split(' ')
  331             if local_path == items[0]:
  332                 entries.append(items[1])
  333         return entries
  334 
  335     def _sync_nfs_temp_and_perm_files(self, server):
  336         """Sync changes of exports with permanent NFS config file.
  337 
  338         This is required to ensure, that after share server reboot, exports
  339         still exist.
  340         """
  341         sync_cmd = [
  342             'sudo', 'cp', const.NFS_EXPORTS_FILE_TEMP, const.NFS_EXPORTS_FILE
  343         ]
  344         self._ssh_exec(server, sync_cmd)
  345         self._ssh_exec(server, ['sudo', 'exportfs', '-a'])
  346         out, _ = self._ssh_exec(
  347             server, ['sudo', 'service', 'nfs-kernel-server', 'status'],
  348             check_exit_code=False)
  349         if "not" in out:
  350             self._ssh_exec(
  351                 server, ['sudo', 'service', 'nfs-kernel-server', 'restart'])
  352 
  353     def _get_export_location_template(self, export_location_or_path):
  354         path = export_location_or_path.split(':')[-1]
  355         return '%s:' + path
  356 
  357     def get_share_path_by_export_location(self, server, export_location):
  358         return export_location.split(':')[-1]
  359 
  360     @nfs_synchronized
  361     def disable_access_for_maintenance(self, server, share_name):
  362         maintenance_file = self._get_maintenance_file_path(share_name)
  363         backup_exports = [
  364             'cat', const.NFS_EXPORTS_FILE,
  365             '|', 'grep', share_name,
  366             '|', 'sudo', 'tee', maintenance_file
  367         ]
  368         self._ssh_exec(server, backup_exports)
  369 
  370         local_path = os.path.join(self.configuration.share_mount_path,
  371                                   share_name)
  372         out, err = self._ssh_exec(server, ['sudo', 'exportfs'])
  373         hosts = self.get_host_list(out, local_path)
  374         for host in hosts:
  375             self._ssh_exec(server, ['sudo', 'exportfs', '-u',
  376                                     ':'.join((host, local_path))])
  377         self._sync_nfs_temp_and_perm_files(server)
  378 
  379     @nfs_synchronized
  380     def restore_access_after_maintenance(self, server, share_name):
  381         maintenance_file = self._get_maintenance_file_path(share_name)
  382         restore_exports = [
  383             'cat', maintenance_file,
  384             '|', 'sudo', 'tee', '-a', const.NFS_EXPORTS_FILE,
  385             '&&', 'sudo', 'exportfs', '-r',
  386             '&&', 'sudo', 'rm', '-f', maintenance_file
  387         ]
  388         self._ssh_exec(server, restore_exports)
  389 
  390 
  391 class CIFSHelperBase(NASHelperBase):
  392     @staticmethod
  393     def _get_share_group_name_from_export_location(export_location):
  394         if '/' in export_location and '\\' in export_location:
  395             pass
  396         elif export_location.startswith('\\\\'):
  397             return export_location.split('\\')[-1]
  398         elif export_location.startswith('//'):
  399             return export_location.split('/')[-1]
  400 
  401         msg = _("Got incorrect CIFS export location '%s'.") % export_location
  402         raise exception.InvalidShare(reason=msg)
  403 
  404     def _get_export_location_template(self, export_location_or_path):
  405         group_name = self._get_share_group_name_from_export_location(
  406             export_location_or_path)
  407         return ('\\\\%s' + ('\\%s' % group_name))
  408 
  409 
  410 class CIFSHelperIPAccess(CIFSHelperBase):
  411     """Manage shares in samba server by net conf tool.
  412 
  413     Class provides functionality to operate with CIFS shares.
  414     Samba server should be configured to use registry as configuration
  415     backend to allow dynamically share managements. This class allows
  416     to define access to shares by IPs with RW access level.
  417     """
  418     def __init__(self, *args):
  419         super(CIFSHelperIPAccess, self).__init__(*args)
  420         self.parameters = {
  421             'browseable': 'yes',
  422             'create mask': '0755',
  423             'hosts deny': '0.0.0.0/0',  # deny all by default
  424             'hosts allow': '127.0.0.1',
  425             'read only': 'no',
  426         }
  427 
  428     def init_helper(self, server):
  429         # This is smoke check that we have required dependency
  430         self._ssh_exec(server, ['sudo', 'net', 'conf', 'list'])
  431 
  432     def create_exports(self, server, share_name, recreate=False):
  433         """Create share at samba server."""
  434         share_path = os.path.join(self.configuration.share_mount_path,
  435                                   share_name)
  436         create_cmd = [
  437             'sudo', 'net', 'conf', 'addshare', share_name, share_path,
  438             'writeable=y', 'guest_ok=y',
  439         ]
  440         try:
  441             self._ssh_exec(
  442                 server, ['sudo', 'net', 'conf', 'showshare', share_name, ])
  443         except exception.ProcessExecutionError:
  444             # Share does not exist, create it
  445             try:
  446                 self._ssh_exec(server, create_cmd)
  447             except Exception:
  448                 msg = _("Could not create CIFS export %s.") % share_name
  449                 LOG.exception(msg)
  450                 raise exception.ManilaException(reason=msg)
  451         else:
  452             # Share exists
  453             if recreate:
  454                 self._ssh_exec(
  455                     server, ['sudo', 'net', 'conf', 'delshare', share_name, ])
  456                 try:
  457                     self._ssh_exec(server, create_cmd)
  458                 except Exception:
  459                     msg = _("Could not create CIFS export %s.") % share_name
  460                     LOG.exception(msg)
  461                     raise exception.ManilaException(reason=msg)
  462             else:
  463                 msg = _('Share section %s already defined.') % share_name
  464                 raise exception.ShareBackendException(msg=msg)
  465 
  466         for param, value in self.parameters.items():
  467             self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm',
  468                            share_name, param, value])
  469 
  470         return self.get_exports_for_share(server, '\\\\%s\\' + share_name)
  471 
  472     def remove_exports(self, server, share_name):
  473         """Remove share definition from samba server."""
  474         try:
  475             self._ssh_exec(
  476                 server, ['sudo', 'net', 'conf', 'delshare', share_name])
  477         except exception.ProcessExecutionError as e:
  478             LOG.warning("Caught error trying delete share: %(error)s, try"
  479                         "ing delete it forcibly.", {'error': e.stderr})
  480             self._ssh_exec(server, ['sudo', 'smbcontrol', 'all', 'close-share',
  481                                     share_name])
  482 
  483     def update_access(self, server, share_name, access_rules, add_rules,
  484                       delete_rules):
  485         """Update access rules for given share.
  486 
  487         Please refer to base class for a more in-depth description. For this
  488         specific implementation, add_rules and delete_rules parameters are not
  489         used.
  490         """
  491         hosts = []
  492 
  493         self.validate_access_rules(
  494             access_rules, ('ip',), (const.ACCESS_LEVEL_RW,))
  495 
  496         for access in access_rules:
  497             hosts.append(access['access_to'])
  498         self._set_allow_hosts(server, hosts, share_name)
  499 
  500     def _get_allow_hosts(self, server, share_name):
  501         (out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf', 'getparm',
  502                                            share_name, 'hosts allow'])
  503         return out.split()
  504 
  505     def _set_allow_hosts(self, server, hosts, share_name):
  506         value = ' '.join(hosts) or ' '
  507         self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm', share_name,
  508                                 'hosts allow', value])
  509 
  510     def get_share_path_by_export_location(self, server, export_location):
  511         # Get name of group that contains share data on CIFS server
  512         group_name = self._get_share_group_name_from_export_location(
  513             export_location)
  514 
  515         # Get parameter 'path' from group that belongs to current share
  516         (out, __) = self._ssh_exec(
  517             server, ['sudo', 'net', 'conf', 'getparm', group_name, 'path'])
  518 
  519         # Remove special symbols from response and return path
  520         return out.strip()
  521 
  522     def disable_access_for_maintenance(self, server, share_name):
  523         maintenance_file = self._get_maintenance_file_path(share_name)
  524         allowed_hosts = " ".join(self._get_allow_hosts(server, share_name))
  525 
  526         backup_exports = [
  527             'echo', "'%s'" % allowed_hosts, '|', 'sudo', 'tee',
  528             maintenance_file
  529         ]
  530         self._ssh_exec(server, backup_exports)
  531         self._set_allow_hosts(server, [], share_name)
  532         self._kick_out_users(server, share_name)
  533 
  534     def _kick_out_users(self, server, share_name):
  535         """Kick out all users of share"""
  536         (out, _) = self._ssh_exec(server, ['sudo', 'smbstatus', '-S'])
  537 
  538         shares = []
  539         header = True
  540         regexp = r"^(?P<share>[^ ]+)\s+(?P<pid>[0-9]+)\s+(?P<machine>[^ ]+).*"
  541         for line in out.splitlines():
  542             line = line.strip()
  543             if not header and line:
  544                 match = re.match(regexp, line)
  545                 if match:
  546                     shares.append(match.groupdict())
  547                 else:
  548                     raise exception.ShareBackendException(
  549                         msg="Failed to obtain smbstatus for %s!" % share_name)
  550             elif line.startswith('----'):
  551                 header = False
  552         to_kill = [s['pid'] for s in shares if
  553                    share_name == s['share'] or share_name is None]
  554         if to_kill:
  555             self._ssh_exec(server, ['sudo', 'kill', '-15'] + to_kill)
  556 
  557     def restore_access_after_maintenance(self, server, share_name):
  558         maintenance_file = self._get_maintenance_file_path(share_name)
  559         (exports, __) = self._ssh_exec(server, ['cat', maintenance_file])
  560         self._set_allow_hosts(server, exports.split(), share_name)
  561         self._ssh_exec(server, ['sudo', 'rm', '-f', maintenance_file])
  562 
  563 
  564 class CIFSHelperUserAccess(CIFSHelperIPAccess):
  565     """Manage shares in samba server by net conf tool.
  566 
  567     Class provides functionality to operate with CIFS shares.
  568     Samba server should be configured to use registry as configuration
  569     backend to allow dynamically share managements. This class allows
  570     to define access to shares by usernames with either RW or RO access levels.
  571     """
  572     def __init__(self, *args):
  573         super(CIFSHelperUserAccess, self).__init__(*args)
  574         self.parameters = {
  575             'browseable': 'yes',
  576             'create mask': '0755',
  577             'hosts allow': '0.0.0.0/0',
  578             'read only': 'no',
  579         }
  580 
  581     def update_access(self, server, share_name, access_rules, add_rules,
  582                       delete_rules):
  583         """Update access rules for given share.
  584 
  585         Please refer to base class for a more in-depth description. For this
  586         specific implementation, add_rules and delete_rules parameters are not
  587         used.
  588         """
  589         all_users_rw = []
  590         all_users_ro = []
  591 
  592         self.validate_access_rules(
  593             access_rules, ('user',),
  594             (const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
  595 
  596         for access in access_rules:
  597             if access['access_level'] == const.ACCESS_LEVEL_RW:
  598                 all_users_rw.append(access['access_to'])
  599             else:
  600                 all_users_ro.append(access['access_to'])
  601         self._set_valid_users(
  602             server, all_users_rw, share_name, const.ACCESS_LEVEL_RW)
  603         self._set_valid_users(
  604             server, all_users_ro, share_name, const.ACCESS_LEVEL_RO)
  605 
  606     def _get_conf_param(self, access_level):
  607         if access_level == const.ACCESS_LEVEL_RW:
  608             return 'valid users'
  609         else:
  610             return 'read list'
  611 
  612     def _set_valid_users(self, server, users, share_name, access_level):
  613         value = ' '.join(users)
  614         param = self._get_conf_param(access_level)
  615         self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm', share_name,
  616                                 param, value])