Coverage for manila/share/drivers/nexenta/ns4/nexenta_nfs_helper.py: 94%
113 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 2016 Nexenta Systems, 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.
16from oslo_log import log
17from oslo_utils import excutils
19from manila.common import constants as common
20from manila import exception
21from manila.i18n import _
22from manila.share.drivers.nexenta.ns4 import jsonrpc
23from manila.share.drivers.nexenta import utils
25LOG = log.getLogger(__name__)
26NOT_EXIST = 'does not exist'
27DEP_CLONES = 'has dependent clones'
30class NFSHelper(object):
32 def __init__(self, configuration):
33 self.configuration = configuration
34 self.nfs_mount_point_base = (
35 self.configuration.nexenta_mount_point_base)
36 self.dataset_compression = (
37 self.configuration.nexenta_dataset_compression)
38 self.dataset_dedupe = self.configuration.nexenta_dataset_dedupe
39 self.nms = None
40 self.nms_protocol = self.configuration.nexenta_rest_protocol
41 self.nms_host = self.configuration.nexenta_nas_host
42 self.volume = self.configuration.nexenta_volume
43 self.share = self.configuration.nexenta_nfs_share
44 self.nms_port = self.configuration.nexenta_rest_port
45 self.nms_user = self.configuration.nexenta_user
46 self.nfs = self.configuration.nexenta_nfs
47 self.nms_password = self.configuration.nexenta_password
48 self.storage_protocol = 'NFS'
50 def do_setup(self):
51 if self.nms_protocol == 'auto': 51 ↛ 54line 51 didn't jump to line 54 because the condition on line 51 was always true
52 protocol, auto = 'http', True
53 else:
54 protocol, auto = self.nms_protocol, False
55 path = '/rest/nms/'
56 self.nms = jsonrpc.NexentaJSONProxy(
57 protocol, self.nms_host, self.nms_port, path, self.nms_user,
58 self.nms_password, auto=auto)
60 def check_for_setup_error(self):
61 if not self.nms.volume.object_exists(self.volume):
62 raise exception.NexentaException(reason=_(
63 "Volume %s does not exist in NexentaStor appliance.") %
64 self.volume)
65 folder = '%s/%s' % (self.volume, self.share)
66 create_folder_props = {
67 'recordsize': '4K',
68 'quota': 'none',
69 'compression': self.dataset_compression,
70 }
71 if not self.nms.folder.object_exists(folder): 71 ↛ exitline 71 didn't return from function 'check_for_setup_error' because the condition on line 71 was always true
72 self.nms.folder.create_with_props(
73 self.volume, self.share, create_folder_props)
75 def create_filesystem(self, share):
76 """Create file system."""
77 create_folder_props = {
78 'recordsize': '4K',
79 'quota': '%sG' % share['size'],
80 'compression': self.dataset_compression,
81 }
82 if not self.configuration.nexenta_thin_provisioning:
83 create_folder_props['reservation'] = '%sG' % share['size']
85 parent_path = '%s/%s' % (self.volume, self.share)
86 self.nms.folder.create_with_props(
87 parent_path, share['name'], create_folder_props)
89 path = self._get_share_path(share['name'])
90 return [self._get_location_path(path, share['share_proto'])]
92 def set_quota(self, share_name, new_size):
93 if self.configuration.nexenta_thin_provisioning:
94 quota = '%sG' % new_size
95 self.nms.folder.set_child_prop(
96 self._get_share_path(share_name), 'quota', quota)
98 def _get_location_path(self, path, protocol):
99 location = None
100 if protocol == 'NFS':
101 location = {'path': '%s:/volumes/%s' % (self.nms_host, path)}
102 else:
103 raise exception.InvalidShare(
104 reason=(_('Only NFS protocol is currently supported.')))
105 return location
107 def delete_share(self, share_name):
108 """Delete share."""
109 folder = self._get_share_path(share_name)
110 try:
111 self.nms.folder.destroy(folder.strip(), '-r')
112 except exception.NexentaException as e:
113 with excutils.save_and_reraise_exception() as exc:
114 if NOT_EXIST in e.args[0]: 114 ↛ exitline 114 didn't jump to the function exit
115 LOG.info('Folder %s does not exist, it was '
116 'already deleted.', folder)
117 exc.reraise = False
119 def _get_share_path(self, share_name):
120 return '%s/%s/%s' % (self.volume, self.share, share_name)
122 def _get_snapshot_name(self, snapshot_name):
123 return 'snapshot-%s' % snapshot_name
125 def create_snapshot(self, share_name, snapshot_name):
126 """Create a snapshot."""
127 folder = self._get_share_path(share_name)
128 self.nms.folder.create_snapshot(folder, snapshot_name, '-r')
129 model_update = {'provider_location': '%s@%s' % (folder, snapshot_name)}
130 return model_update
132 def delete_snapshot(self, share_name, snapshot_name):
133 """Deletes snapshot."""
134 try:
135 self.nms.snapshot.destroy('%s@%s' % (
136 self._get_share_path(share_name), snapshot_name), '')
137 except exception.NexentaException as e:
138 with excutils.save_and_reraise_exception() as exc:
139 if NOT_EXIST in e.args[0]:
140 LOG.info('Snapshot %(folder)s@%(snapshot)s does not '
141 'exist, it was already deleted.',
142 {
143 'folder': share_name,
144 'snapshot': snapshot_name,
145 })
146 exc.reraise = False
147 elif DEP_CLONES in e.args[0]: 147 ↛ exitline 147 didn't jump to the function exit
148 LOG.info(
149 'Snapshot %(folder)s@%(snapshot)s has dependent '
150 'clones, it will be deleted later.', {
151 'folder': share_name,
152 'snapshot': snapshot_name
153 })
154 exc.reraise = False
156 def create_share_from_snapshot(self, share, snapshot):
157 snapshot_name = '%s/%s/%s@%s' % (
158 self.volume, self.share, snapshot['share_name'], snapshot['name'])
159 self.nms.folder.clone(
160 snapshot_name,
161 '%s/%s/%s' % (self.volume, self.share, share['name']))
162 path = self._get_share_path(share['name'])
163 return [self._get_location_path(path, share['share_proto'])]
165 def update_access(self, share_name, access_rules):
166 """Update access to the share."""
167 rw_list = []
168 ro_list = []
169 for rule in access_rules:
170 if rule['access_type'].lower() != 'ip':
171 msg = _('Only IP access type is supported.')
172 raise exception.InvalidShareAccess(reason=msg)
173 else:
174 if rule['access_level'] == common.ACCESS_LEVEL_RW: 174 ↛ 177line 174 didn't jump to line 177 because the condition on line 174 was always true
175 rw_list.append(rule['access_to'])
176 else:
177 ro_list.append(rule['access_to'])
179 share_opts = {
180 'auth_type': 'none',
181 'read_write': ':'.join(rw_list),
182 'read_only': ':'.join(ro_list),
183 'recursive': 'true',
184 'anonymous_rw': 'true',
185 'anonymous': 'true',
186 'extra_options': 'anon=0',
187 }
188 self.nms.netstorsvc.share_folder(
189 'svc:/network/nfs/server:default',
190 self._get_share_path(share_name), share_opts)
192 def _get_capacity_info(self):
193 """Calculate available space on the NFS share."""
194 folder_props = self.nms.folder.get_child_props(
195 '%s/%s' % (self.volume, self.share), 'used|available')
196 free = utils.str2gib_size(folder_props['available'])
197 allocated = utils.str2gib_size(folder_props['used'])
198 return free + allocated, free, allocated
200 def update_share_stats(self):
201 """Update driver capabilities.
203 No way of tracking provisioned capacity on this appliance,
204 not returning any to let the scheduler estimate it.
205 """
206 total, free, allocated = self._get_capacity_info()
207 compression = not self.dataset_compression == 'off'
208 dedupe = not self.dataset_dedupe == 'off'
209 return {
210 'vendor_name': 'Nexenta',
211 'storage_protocol': self.storage_protocol,
212 'nfs_mount_point_base': self.nfs_mount_point_base,
213 'pools': [{
214 'pool_name': self.volume,
215 'total_capacity_gb': total,
216 'free_capacity_gb': free,
217 'reserved_percentage':
218 self.configuration.reserved_share_percentage,
219 'reserved_snapshot_percentage':
220 (self.configuration.reserved_share_from_snapshot_percentage
221 or self.configuration.reserved_share_percentage),
222 'reserved_share_extend_percentage':
223 (self.configuration.reserved_share_extend_percentage
224 or self.configuration.reserved_share_percentage),
225 'compression': compression,
226 'dedupe': dedupe,
227 'max_over_subscription_ratio': (
228 self.configuration.safe_get(
229 'max_over_subscription_ratio')),
230 'thin_provisioning':
231 self.configuration.nexenta_thin_provisioning,
232 }],
233 }