manila/manila/share/drivers/netapp/dataontap/protocols/cifs_cmode.py

176 lines
7.1 KiB
Python

# Copyright (c) 2015 Clinton Knight. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
NetApp cDOT CIFS protocol helper class.
"""
import re
from manila.common import constants
from manila import exception
from manila.i18n import _
from manila.share.drivers.netapp.dataontap.protocols import base
from manila.share.drivers.netapp import utils as na_utils
class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
"""NetApp cDOT CIFS protocol helper class."""
@na_utils.trace
def create_share(self, share, share_name,
clear_current_export_policy=True):
"""Creates CIFS share on Data ONTAP Vserver."""
self._client.create_cifs_share(share_name)
if clear_current_export_policy:
self._client.remove_cifs_share_access(share_name, 'Everyone')
# Ensure 'ntfs' security style
self._client.set_volume_security_style(share_name,
security_style='ntfs')
# Return a callback that may be used for generating export paths
# for this share.
return (lambda export_address, share_name=share_name:
r'\\%s\%s' % (export_address, share_name))
@na_utils.trace
def delete_share(self, share, share_name):
"""Deletes CIFS share on Data ONTAP Vserver."""
host_ip, share_name = self._get_export_location(share)
self._client.remove_cifs_share(share_name)
@na_utils.trace
@base.access_rules_synchronized
def update_access(self, share, share_name, rules):
"""Replaces the list of access rules known to the backend storage."""
# Ensure rules are valid
for rule in rules:
self._validate_access_rule(rule)
new_rules = {rule['access_to']: rule['access_level'] for rule in rules}
# Get rules from share
existing_rules = self._get_access_rules(share, share_name)
# Update rules in an order that will prevent transient disruptions
self._handle_added_rules(share_name, existing_rules, new_rules)
self._handle_ro_to_rw_rules(share_name, existing_rules, new_rules)
self._handle_rw_to_ro_rules(share_name, existing_rules, new_rules)
self._handle_deleted_rules(share_name, existing_rules, new_rules)
@na_utils.trace
def _validate_access_rule(self, rule):
"""Checks whether access rule type and level are valid."""
if rule['access_type'] != 'user':
msg = _("Clustered Data ONTAP supports only 'user' type for "
"share access rules with CIFS protocol.")
raise exception.InvalidShareAccess(reason=msg)
if rule['access_level'] not in constants.ACCESS_LEVELS:
raise exception.InvalidShareAccessLevel(level=rule['access_level'])
@na_utils.trace
def _handle_added_rules(self, share_name, existing_rules, new_rules):
"""Updates access rules added between two rule sets."""
added_rules = {
user_or_group: permission
for user_or_group, permission in new_rules.items()
if user_or_group not in existing_rules
}
for user_or_group, permission in added_rules.items():
self._client.add_cifs_share_access(
share_name, user_or_group, self._is_readonly(permission))
@na_utils.trace
def _handle_ro_to_rw_rules(self, share_name, existing_rules, new_rules):
"""Updates access rules modified (RO-->RW) between two rule sets."""
modified_rules = {
user_or_group: permission
for user_or_group, permission in new_rules.items()
if (user_or_group in existing_rules and
permission == constants.ACCESS_LEVEL_RW and
existing_rules[user_or_group] != 'full_control')
}
for user_or_group, permission in modified_rules.items():
self._client.modify_cifs_share_access(
share_name, user_or_group, self._is_readonly(permission))
@na_utils.trace
def _handle_rw_to_ro_rules(self, share_name, existing_rules, new_rules):
"""Returns access rules modified (RW-->RO) between two rule sets."""
modified_rules = {
user_or_group: permission
for user_or_group, permission in new_rules.items()
if (user_or_group in existing_rules and
permission == constants.ACCESS_LEVEL_RO and
existing_rules[user_or_group] != 'read')
}
for user_or_group, permission in modified_rules.items():
self._client.modify_cifs_share_access(
share_name, user_or_group, self._is_readonly(permission))
@na_utils.trace
def _handle_deleted_rules(self, share_name, existing_rules, new_rules):
"""Returns access rules deleted between two rule sets."""
deleted_rules = {
user_or_group: permission
for user_or_group, permission in existing_rules.items()
if user_or_group not in new_rules
}
for user_or_group, permission in deleted_rules.items():
self._client.remove_cifs_share_access(share_name, user_or_group)
@na_utils.trace
def _get_access_rules(self, share, share_name):
"""Returns the list of access rules known to the backend storage."""
return self._client.get_cifs_share_access(share_name)
@na_utils.trace
def get_target(self, share):
"""Returns OnTap target IP based on share export location."""
return self._get_export_location(share)[0]
@na_utils.trace
def get_share_name_for_share(self, share):
"""Returns the flexvol name that hosts a share."""
_, share_name = self._get_export_location(share)
return share_name
@staticmethod
def _get_export_location(share):
"""Returns host ip and share name for a given CIFS share."""
export_location = share['export_location'] or '\\\\\\'
regex = r'^(?:\\\\|//)(?P<host_ip>.*)(?:\\|/)(?P<share_name>.*)$'
match = re.match(regex, export_location)
if match:
return match.group('host_ip'), match.group('share_name')
else:
return '', ''
@na_utils.trace
def cleanup_demoted_replica(self, share, share_name):
"""Cleans up some things regarding a demoted replica."""
# NOTE(carloss): This is necessary due to bug 1879368. If we do not
# remove this CIFS share, in case the demoted replica is promoted
# back, the promotion will fail due to a duplicated entry for the
# share, since a create share request is sent to the backend every
# time a promotion occurs.
self._client.remove_cifs_share(share_name)