Coverage for manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py: 99%
102 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
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"""
15NetApp cDOT NFS protocol helper class.
16"""
18import uuid
20from oslo_log import log
21from oslo_utils import netutils
23from manila.common import constants
24from manila import exception
25from manila.share.drivers.netapp.dataontap.protocols import base
26from manila.share.drivers.netapp import utils as na_utils
29LOG = log.getLogger(__name__)
32class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
33 """NetApp cDOT NFS protocol helper class."""
35 @staticmethod
36 def _escaped_address(address):
37 return netutils.escape_ipv6(address)
39 @na_utils.trace
40 def create_share(self, share, share_name,
41 clear_current_export_policy=True,
42 ensure_share_already_exists=False, replica=False,
43 is_flexgroup=False):
44 """Ensures the share export policy is set correctly.
46 The export policy must have the same name as the share. If it matches,
47 nothing is done. Otherwise, the possible scenarios:
49 1. policy as 'default': a new export policy is created.
50 2. policy as any name: renames the assigned policy to match the name.
52 :param share: share entity.
53 :param share_name: share name that must be the export policy name.
54 :param clear_current_export_policy: set the policy to 'default' before
55 the check.
56 :param ensure_share_already_exists: ignored, CIFS only.
57 :param replica: it is a replica volume (DP type).
58 :param is_flexgroup: whether the share is a FlexGroup or not.
59 """
61 if clear_current_export_policy: 61 ↛ 63line 61 didn't jump to line 63 because the condition on line 61 was always true
62 self._client.clear_nfs_export_policy_for_volume(share_name)
63 self._ensure_export_policy(share, share_name)
65 if is_flexgroup:
66 volume_info = self._client.get_volume(share_name)
67 export_path = volume_info['junction-path']
68 else:
69 export_path = self._client.get_volume_junction_path(share_name)
71 # Return a callback that may be used for generating export paths
72 # for this share.
73 return (lambda export_address, export_path=export_path:
74 ':'.join([self._escaped_address(export_address),
75 export_path]))
77 @na_utils.trace
78 @base.access_rules_synchronized
79 def delete_share(self, share, share_name):
80 """Deletes NFS share."""
81 LOG.debug('Deleting NFS export policy for share %s', share['id'])
82 export_policy_name = self._get_export_policy_name(share)
83 self._client.clear_nfs_export_policy_for_volume(share_name)
84 self._client.soft_delete_nfs_export_policy(export_policy_name)
86 @na_utils.trace
87 @base.access_rules_synchronized
88 def update_access(self, share, share_name, rules):
89 """Replaces the list of access rules known to the backend storage."""
91 # Ensure rules are valid
92 for rule in rules:
93 self._validate_access_rule(rule)
95 # Sort rules by ascending network size
96 new_rules = {rule['access_to']: rule['access_level'] for rule in rules}
97 addresses = sorted(new_rules, reverse=True)
99 # Ensure current export policy has the name we expect
100 self._ensure_export_policy(share, share_name)
101 export_policy_name = self._get_export_policy_name(share)
103 # Make temp policy names so this non-atomic workflow remains resilient
104 # across process interruptions.
105 temp_new_export_policy_name = self._get_temp_export_policy_name()
106 temp_old_export_policy_name = self._get_temp_export_policy_name()
108 # Create new export policy
109 self._client.create_nfs_export_policy(temp_new_export_policy_name)
111 # Get authentication methods, based on Vserver configuration
112 auth_methods = self._get_auth_methods()
114 metadata = share.get('metadata', None)
115 all_squash = (
116 (metadata.get('all_squash', 'false').lower() == 'true') if
117 metadata else False)
119 # Add new rules to new policy
120 for address in addresses:
121 readonly = self._is_readonly(new_rules[address])
122 if all_squash and not readonly:
123 self._client.add_nfs_export_rule(
124 temp_new_export_policy_name, address, False, ['none'])
125 else:
126 self._client.add_nfs_export_rule(
127 temp_new_export_policy_name, address,
128 self._is_readonly(new_rules[address]), auth_methods)
130 # Rename policy currently in force
131 LOG.info('Renaming NFS export policy for share %(share)s to '
132 '%(policy)s.',
133 {'share': share_name, 'policy': temp_old_export_policy_name})
134 self._client.rename_nfs_export_policy(export_policy_name,
135 temp_old_export_policy_name)
137 # Switch share to the new policy
138 LOG.info('Setting NFS export policy for share %(share)s to '
139 '%(policy)s.',
140 {'share': share_name, 'policy': temp_new_export_policy_name})
141 self._client.set_nfs_export_policy_for_volume(
142 share_name, temp_new_export_policy_name)
144 # Delete old policy
145 self._client.soft_delete_nfs_export_policy(temp_old_export_policy_name)
147 # Rename new policy to its final name
148 LOG.info('Renaming NFS export policy for share %(share)s to '
149 '%(policy)s.',
150 {'share': share_name, 'policy': export_policy_name})
151 self._client.rename_nfs_export_policy(temp_new_export_policy_name,
152 export_policy_name)
154 @na_utils.trace
155 def _validate_access_rule(self, rule):
156 """Checks whether access rule type and level are valid."""
158 if rule['access_type'] != 'ip':
159 msg = ("Clustered Data ONTAP supports only 'ip' type for share "
160 "access rules with NFS protocol.")
161 raise exception.InvalidShareAccess(reason=msg)
163 if rule['access_level'] not in constants.ACCESS_LEVELS:
164 raise exception.InvalidShareAccessLevel(level=rule['access_level'])
166 @na_utils.trace
167 def get_target(self, share):
168 """Returns ID of target ONTAP device based on export location."""
169 return self._get_export_location(share)[0]
171 @na_utils.trace
172 def get_share_name_for_share(self, share):
173 """Returns the flexvol name that hosts a share."""
174 _, volume_junction_path = self._get_export_location(share)
175 volume = self._client.get_volume_at_junction_path(volume_junction_path)
176 return volume.get('name') if volume else None
178 @na_utils.trace
179 def _get_export_location(self, share):
180 """Returns IP address and export location of an NFS share."""
181 export_location = self._get_share_export_location(share) or ':'
182 result = export_location.rsplit(':', 1)
183 if len(result) != 2:
184 return ['', '']
185 return result
187 @staticmethod
188 def _get_temp_export_policy_name():
189 """Builds export policy name for an NFS share."""
190 return 'temp_' + str(uuid.uuid1()).replace('-', '_')
192 @staticmethod
193 def _get_export_policy_name(share):
194 """Builds export policy name for an NFS share."""
195 return 'policy_' + share['id'].replace('-', '_')
197 @na_utils.trace
198 def _ensure_export_policy(self, share, share_name):
199 """Ensures a flexvol/share has an export policy.
201 This method ensures a flexvol has an export policy with a name
202 containing the share ID. For legacy reasons, this may not
203 always be the case.
204 """
205 expected_export_policy = self._get_export_policy_name(share)
206 actual_export_policy = self._client.get_nfs_export_policy_for_volume(
207 share_name)
209 if actual_export_policy == expected_export_policy:
210 return
211 elif actual_export_policy == 'default':
212 self._client.create_nfs_export_policy(expected_export_policy)
213 self._client.set_nfs_export_policy_for_volume(
214 share_name, expected_export_policy)
215 else:
216 self._client.rename_nfs_export_policy(actual_export_policy,
217 expected_export_policy)
219 @na_utils.trace
220 def _get_auth_methods(self):
221 """Returns authentication methods for export policy rules.
223 This method returns the authentication methods to be configure in an
224 export policy rule, based on security services configuration set in
225 the current Vserver. If Kerberos is enabled in vServer LIFs, the auth
226 methods will be configure to support 'krb5', 'krb5i' and 'krb5p'. The
227 default authentication method is 'sys' (AUTH_SYS).
228 """
229 kerberos_enabled = self._client.is_kerberos_enabled()
230 return ['krb5', 'krb5i', 'krb5p'] if kerberos_enabled else ['sys']
232 @na_utils.trace
233 def cleanup_demoted_replica(self, share, share_name):
234 """Cleans up export NFS policy for a demoted replica."""
235 self.delete_share(share, share_name)
236 return