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

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. 

15 

16from oslo_log import log 

17from oslo_utils import excutils 

18 

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 

24 

25LOG = log.getLogger(__name__) 

26NOT_EXIST = 'does not exist' 

27DEP_CLONES = 'has dependent clones' 

28 

29 

30class NFSHelper(object): 

31 

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' 

49 

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) 

59 

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) 

74 

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'] 

84 

85 parent_path = '%s/%s' % (self.volume, self.share) 

86 self.nms.folder.create_with_props( 

87 parent_path, share['name'], create_folder_props) 

88 

89 path = self._get_share_path(share['name']) 

90 return [self._get_location_path(path, share['share_proto'])] 

91 

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) 

97 

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 

106 

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 

118 

119 def _get_share_path(self, share_name): 

120 return '%s/%s/%s' % (self.volume, self.share, share_name) 

121 

122 def _get_snapshot_name(self, snapshot_name): 

123 return 'snapshot-%s' % snapshot_name 

124 

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 

131 

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 

155 

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'])] 

164 

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']) 

178 

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) 

191 

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 

199 

200 def update_share_stats(self): 

201 """Update driver capabilities. 

202 

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 }