Coverage for manila/share/drivers/hpe/hpe_3par_mediator.py: 95%
825 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 2015 Hewlett Packard Enterprise Development LP
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.
15"""HPE 3PAR Mediator for OpenStack Manila.
17This 'mediator' de-couples the 3PAR focused client from the OpenStack focused
18driver.
19"""
21from oslo_log import log
22from oslo_utils import importutils
23from oslo_utils import units
25from manila.data import utils as data_utils
26from manila import exception
27from manila.i18n import _
28from manila import utils
30hpe3parclient = importutils.try_import("hpe3parclient")
31if hpe3parclient: 31 ↛ 35line 31 didn't jump to line 35 because the condition on line 31 was always true
32 from hpe3parclient import file_client # pylint: disable=import-error
35LOG = log.getLogger(__name__)
36MIN_CLIENT_VERSION = (4, 0, 0)
37DENY = '-'
38ALLOW = '+'
39OPEN_STACK_MANILA = 'OpenStack Manila'
40FULL = 1
41THIN = 2
42DEDUPE = 6
43ENABLED = 1
44DISABLED = 2
45CACHE = 'cache'
46CONTINUOUS_AVAIL = 'continuous_avail'
47ACCESS_BASED_ENUM = 'access_based_enum'
48SMB_EXTRA_SPECS_MAP = {
49 CACHE: CACHE,
50 CONTINUOUS_AVAIL: 'ca',
51 ACCESS_BASED_ENUM: 'abe',
52}
53IP_ALREADY_EXISTS = 'IP address %s already exists'
54USER_ALREADY_EXISTS = '"allow" permission already exists for "%s"'
55DOES_NOT_EXIST = 'does not exist, cannot'
56LOCAL_IP = '127.0.0.1'
57LOCAL_IP_RO = '127.0.0.2'
58SUPER_SHARE = 'OPENSTACK_SUPER_SHARE'
59TMP_RO_SNAP_EXPORT = "Temp RO snapshot export as source for creating RW share."
62class HPE3ParMediator(object):
63 """3PAR client-facing code for the 3PAR driver.
65 Version history:
66 1.0.0 - Begin Liberty development (post-Kilo)
67 1.0.1 - Report thin/dedup/hp_flash_cache capabilities
68 1.0.2 - Add share server/share network support
69 1.0.3 - Use hp3par prefix for share types and capabilities
70 2.0.0 - Rebranded HP to HPE
71 2.0.1 - Add access_level (e.g. read-only support)
72 2.0.2 - Add extend/shrink
73 2.0.3 - Fix SMB read-only access (added in 2.0.1)
74 2.0.4 - Remove file tree on delete when using nested shares #1538800
75 2.0.5 - Reduce the fsquota by share size
76 when a share is deleted #1582931
77 2.0.6 - Read-write share from snapshot (using driver mount and copy)
78 2.0.7 - Add update_access support
79 2.0.8 - Multi pools support per backend
80 2.0.9 - Fix get_vfs() to correctly validate conf IP addresses at
81 boot up #1621016
83 """
85 VERSION = "2.0.9"
87 def __init__(self, **kwargs):
89 self.hpe3par_username = kwargs.get('hpe3par_username')
90 self.hpe3par_password = kwargs.get('hpe3par_password')
91 self.hpe3par_api_url = kwargs.get('hpe3par_api_url')
92 self.hpe3par_debug = kwargs.get('hpe3par_debug')
93 self.hpe3par_san_ip = kwargs.get('hpe3par_san_ip')
94 self.hpe3par_san_login = kwargs.get('hpe3par_san_login')
95 self.hpe3par_san_password = kwargs.get('hpe3par_san_password')
96 self.hpe3par_san_ssh_port = kwargs.get('hpe3par_san_ssh_port')
97 self.hpe3par_san_private_key = kwargs.get('hpe3par_san_private_key')
98 self.hpe3par_fstore_per_share = kwargs.get('hpe3par_fstore_per_share')
99 self.hpe3par_require_cifs_ip = kwargs.get('hpe3par_require_cifs_ip')
100 self.hpe3par_cifs_admin_access_username = (
101 kwargs.get('hpe3par_cifs_admin_access_username'))
102 self.hpe3par_cifs_admin_access_password = (
103 kwargs.get('hpe3par_cifs_admin_access_password'))
104 self.hpe3par_cifs_admin_access_domain = (
105 kwargs.get('hpe3par_cifs_admin_access_domain'))
106 self.hpe3par_share_mount_path = kwargs.get('hpe3par_share_mount_path')
107 self.my_ip = kwargs.get('my_ip')
109 self.ssh_conn_timeout = kwargs.get('ssh_conn_timeout')
110 self._client = None
111 self.client_version = None
113 @staticmethod
114 def no_client():
115 return hpe3parclient is None
117 def do_setup(self):
119 if self.no_client():
120 msg = _('You must install hpe3parclient before using the 3PAR '
121 'driver. Run "pip install --upgrade python-3parclient" '
122 'to upgrade the hpe3parclient.')
123 LOG.error(msg)
124 raise exception.HPE3ParInvalidClient(message=msg)
126 self.client_version = hpe3parclient.version_tuple
127 if self.client_version < MIN_CLIENT_VERSION:
128 msg = (_('Invalid hpe3parclient version found (%(found)s). '
129 'Version %(minimum)s or greater required. Run "pip'
130 ' install --upgrade python-3parclient" to upgrade'
131 ' the hpe3parclient.') %
132 {'found': '.'.join(map(str, self.client_version)),
133 'minimum': '.'.join(map(str,
134 MIN_CLIENT_VERSION))})
135 LOG.error(msg)
136 raise exception.HPE3ParInvalidClient(message=msg)
138 try:
139 self._client = file_client.HPE3ParFilePersonaClient(
140 self.hpe3par_api_url)
141 except Exception as e:
142 msg = (_('Failed to connect to HPE 3PAR File Persona Client: %s') %
143 str(e))
144 LOG.exception(msg)
145 raise exception.ShareBackendException(message=msg)
147 try:
148 ssh_kwargs = {}
149 if self.hpe3par_san_ssh_port: 149 ↛ 151line 149 didn't jump to line 151 because the condition on line 149 was always true
150 ssh_kwargs['port'] = self.hpe3par_san_ssh_port
151 if self.ssh_conn_timeout: 151 ↛ 153line 151 didn't jump to line 153 because the condition on line 151 was always true
152 ssh_kwargs['conn_timeout'] = self.ssh_conn_timeout
153 if self.hpe3par_san_private_key: 153 ↛ 154line 153 didn't jump to line 154 because the condition on line 153 was never true
154 ssh_kwargs['privatekey'] = self.hpe3par_san_private_key
156 self._client.setSSHOptions(
157 self.hpe3par_san_ip,
158 self.hpe3par_san_login,
159 self.hpe3par_san_password,
160 **ssh_kwargs
161 )
163 except Exception as e:
164 msg = (_('Failed to set SSH options for HPE 3PAR File Persona '
165 'Client: %s') % str(e))
166 LOG.exception(msg)
167 raise exception.ShareBackendException(message=msg)
169 LOG.info("HPE3ParMediator %(version)s, "
170 "hpe3parclient %(client_version)s",
171 {"version": self.VERSION,
172 "client_version": hpe3parclient.get_version_string()})
174 try:
175 wsapi_version = self._client.getWsApiVersion()['build']
176 LOG.info("3PAR WSAPI %s", wsapi_version)
177 except Exception as e:
178 msg = (_('Failed to get 3PAR WSAPI version: %s') %
179 str(e))
180 LOG.exception(msg)
181 raise exception.ShareBackendException(message=msg)
183 if self.hpe3par_debug: 183 ↛ exitline 183 didn't return from function 'do_setup' because the condition on line 183 was always true
184 self._client.debug_rest(True) # Includes SSH debug (setSSH above)
186 def _wsapi_login(self):
187 try:
188 self._client.login(self.hpe3par_username, self.hpe3par_password)
189 except Exception as e:
190 msg = (_("Failed to Login to 3PAR (%(url)s) as %(user)s "
191 "because: %(err)s") %
192 {'url': self.hpe3par_api_url,
193 'user': self.hpe3par_username,
194 'err': str(e)})
195 LOG.error(msg)
196 raise exception.ShareBackendException(msg=msg)
198 def _wsapi_logout(self):
199 try:
200 self._client.http.unauthenticate()
201 except Exception as e:
202 msg = ("Failed to Logout from 3PAR (%(url)s) because %(err)s")
203 LOG.warning(msg, {'url': self.hpe3par_api_url,
204 'err': str(e)})
205 # don't raise exception on logout()
207 @staticmethod
208 def build_export_locations(protocol, ips, path):
210 if not ips:
211 message = _('Failed to build export location due to missing IP.')
212 raise exception.InvalidInput(reason=message)
214 if not path:
215 message = _('Failed to build export location due to missing path.')
216 raise exception.InvalidInput(reason=message)
218 share_proto = HPE3ParMediator.ensure_supported_protocol(protocol)
219 if share_proto == 'nfs':
220 return ['%s:%s' % (ip, path) for ip in ips]
221 else:
222 return [r'\\%s\%s' % (ip, path) for ip in ips]
224 def get_provisioned_gb(self, fpg):
225 total_mb = 0
226 try:
227 result = self._client.getfsquota(fpg=fpg)
228 except Exception as e:
229 result = {'message': str(e)}
231 error_msg = result.get('message')
232 if error_msg:
233 message = (_('Error while getting fsquotas for FPG '
234 '%(fpg)s: %(msg)s') %
235 {'fpg': fpg, 'msg': error_msg})
236 LOG.error(message)
237 raise exception.ShareBackendException(msg=message)
239 for fsquota in result['members']:
240 total_mb += float(fsquota['hardBlock'])
241 return total_mb / units.Ki
243 def get_fpg_status(self, fpg):
244 """Get capacity and capabilities for FPG."""
246 try:
247 result = self._client.getfpg(fpg)
248 except Exception as e:
249 msg = (_('Failed to get capacity for fpg %(fpg)s: %(e)s') %
250 {'fpg': fpg, 'e': str(e)})
251 LOG.error(msg)
252 raise exception.ShareBackendException(msg=msg)
254 if result['total'] != 1:
255 msg = (_('Failed to get capacity for fpg %s.') % fpg)
256 LOG.error(msg)
257 raise exception.ShareBackendException(msg=msg)
259 member = result['members'][0]
260 total_capacity_gb = float(member['capacityKiB']) / units.Mi
261 free_capacity_gb = float(member['availCapacityKiB']) / units.Mi
263 volumes = member['vvs']
264 if isinstance(volumes, list):
265 volume = volumes[0] # Use first name from list
266 else:
267 volume = volumes # There is just a name
269 self._wsapi_login()
270 try:
271 volume_info = self._client.getVolume(volume)
272 volume_set = self._client.getVolumeSet(fpg)
273 finally:
274 self._wsapi_logout()
276 provisioning_type = volume_info['provisioningType']
277 if provisioning_type not in (THIN, FULL, DEDUPE):
278 msg = (_('Unexpected provisioning type for FPG %(fpg)s: '
279 '%(ptype)s.') % {'fpg': fpg, 'ptype': provisioning_type})
280 LOG.error(msg)
281 raise exception.ShareBackendException(msg=msg)
283 dedupe = provisioning_type == DEDUPE
284 thin_provisioning = provisioning_type in (THIN, DEDUPE)
286 flash_cache_policy = volume_set.get('flashCachePolicy', DISABLED)
287 hpe3par_flash_cache = flash_cache_policy == ENABLED
289 status = {
290 'pool_name': fpg,
291 'total_capacity_gb': total_capacity_gb,
292 'free_capacity_gb': free_capacity_gb,
293 'thin_provisioning': thin_provisioning,
294 'dedupe': dedupe,
295 'hpe3par_flash_cache': hpe3par_flash_cache,
296 'hp3par_flash_cache': hpe3par_flash_cache,
297 }
299 if thin_provisioning: 299 ↛ 302line 299 didn't jump to line 302 because the condition on line 299 was always true
300 status['provisioned_capacity_gb'] = self.get_provisioned_gb(fpg)
302 return status
304 @staticmethod
305 def ensure_supported_protocol(share_proto):
306 protocol = share_proto.lower()
307 if protocol == 'cifs':
308 protocol = 'smb'
309 if protocol not in ['smb', 'nfs']:
310 message = (_('Invalid protocol. Expected nfs or smb. Got %s.') %
311 protocol)
312 LOG.error(message)
313 raise exception.InvalidShareAccess(reason=message)
314 return protocol
316 @staticmethod
317 def other_protocol(share_proto):
318 """Given 'nfs' or 'smb' (or equivalent) return the other one."""
319 protocol = HPE3ParMediator.ensure_supported_protocol(share_proto)
320 return 'nfs' if protocol == 'smb' else 'smb'
322 @staticmethod
323 def ensure_prefix(uid, protocol=None, readonly=False):
324 if uid.startswith('osf-'):
325 return uid
327 if protocol:
328 proto = '-%s' % HPE3ParMediator.ensure_supported_protocol(protocol)
329 else:
330 proto = ''
332 if readonly:
333 ro = '-ro'
334 else:
335 ro = ''
337 # Format is osf[-ro]-{nfs|smb}-uid
338 return 'osf%s%s-%s' % (proto, ro, uid)
340 @staticmethod
341 def _get_nfs_options(extra_specs, readonly):
342 """Validate the NFS extra_specs and return the options to use."""
344 nfs_options = extra_specs.get('hpe3par:nfs_options')
345 if nfs_options is None:
346 nfs_options = extra_specs.get('hp3par:nfs_options')
347 if nfs_options: 347 ↛ 348line 347 didn't jump to line 348 because the condition on line 347 was never true
348 msg = ("hp3par:nfs_options is deprecated. Use "
349 "hpe3par:nfs_options instead.")
350 LOG.warning(msg)
352 if nfs_options:
353 options = nfs_options.split(',')
354 else:
355 options = []
357 # rw, ro, and (no)root_squash (in)secure options are not allowed in
358 # extra_specs because they will be forcibly set below.
359 # no_subtree_check and fsid are not allowed per 3PAR support.
360 # Other strings will be allowed to be sent to the 3PAR which will do
361 # further validation.
362 options_not_allowed = ['ro', 'rw',
363 'no_root_squash', 'root_squash',
364 'secure', 'insecure',
365 'no_subtree_check', 'fsid']
367 invalid_options = [
368 option for option in options if option in options_not_allowed
369 ]
371 if invalid_options:
372 raise exception.InvalidInput(_('Invalid hp3par:nfs_options or '
373 'hpe3par:nfs_options in '
374 'extra-specs. The following '
375 'options are not allowed: %s') %
376 invalid_options)
378 options.append('ro' if readonly else 'rw')
379 options.append('no_root_squash')
380 options.append('insecure')
382 return ','.join(options)
384 def _build_createfshare_kwargs(self, protocol, fpg, fstore, readonly,
385 sharedir, extra_specs, comment,
386 client_ip=None):
387 createfshare_kwargs = dict(fpg=fpg,
388 fstore=fstore,
389 sharedir=sharedir,
390 comment=comment)
392 if 'hp3par_flash_cache' in extra_specs: 392 ↛ 393line 392 didn't jump to line 393 because the condition on line 392 was never true
393 msg = ("hp3par_flash_cache is deprecated. Use "
394 "hpe3par_flash_cache instead.")
395 LOG.warning(msg)
397 if protocol == 'nfs':
398 if client_ip:
399 createfshare_kwargs['clientip'] = client_ip
400 else:
401 # New NFS shares needs seed IP to prevent "all" access.
402 # Readonly and readwrite NFS shares client IPs cannot overlap.
403 if readonly:
404 createfshare_kwargs['clientip'] = LOCAL_IP_RO
405 else:
406 createfshare_kwargs['clientip'] = LOCAL_IP
407 options = self._get_nfs_options(extra_specs, readonly)
408 createfshare_kwargs['options'] = options
409 else:
411 # To keep the original (Kilo, Liberty) behavior where CIFS IP
412 # access rules were required in addition to user rules enable
413 # this to use a seed IP instead of the default (all allowed).
414 if self.hpe3par_require_cifs_ip:
415 if client_ip: 415 ↛ 418line 415 didn't jump to line 418 because the condition on line 415 was always true
416 createfshare_kwargs['allowip'] = client_ip
417 else:
418 createfshare_kwargs['allowip'] = LOCAL_IP
420 smb_opts = (ACCESS_BASED_ENUM, CONTINUOUS_AVAIL, CACHE)
422 for smb_opt in smb_opts:
423 opt_value = extra_specs.get('hpe3par:smb_%s' % smb_opt)
424 if opt_value is None:
425 opt_value = extra_specs.get('hp3par:smb_%s' % smb_opt)
426 if opt_value: 426 ↛ 427line 426 didn't jump to line 427 because the condition on line 426 was never true
427 msg = ("hp3par:smb_* is deprecated. Use "
428 "hpe3par:smb_* instead.")
429 LOG.warning(msg)
431 if opt_value:
432 opt_key = SMB_EXTRA_SPECS_MAP[smb_opt]
433 createfshare_kwargs[opt_key] = opt_value
434 return createfshare_kwargs
436 def _update_capacity_quotas(self, fstore, new_size, old_size, fpg, vfs):
438 @utils.synchronized('hpe3par-update-quota-' + fstore)
439 def _sync_update_capacity_quotas(fstore, new_size, old_size, fpg, vfs):
440 """Update 3PAR quotas and return setfsquota output."""
442 if self.hpe3par_fstore_per_share:
443 hcapacity = str(new_size * units.Ki)
444 scapacity = hcapacity
445 else:
446 hard_size_mb = (new_size - old_size) * units.Ki
447 soft_size_mb = hard_size_mb
448 result = self._client.getfsquota(
449 fpg=fpg, vfs=vfs, fstore=fstore)
450 LOG.debug("getfsquota result=%s", result)
451 quotas = result['members']
452 if len(quotas) == 1: 452 ↛ 455line 452 didn't jump to line 455 because the condition on line 452 was always true
453 hard_size_mb += int(quotas[0].get('hardBlock', '0'))
454 soft_size_mb += int(quotas[0].get('softBlock', '0'))
455 hcapacity = str(hard_size_mb)
456 scapacity = str(soft_size_mb)
458 return self._client.setfsquota(vfs,
459 fpg=fpg,
460 fstore=fstore,
461 scapacity=scapacity,
462 hcapacity=hcapacity)
464 try:
465 result = _sync_update_capacity_quotas(
466 fstore, new_size, old_size, fpg, vfs)
467 LOG.debug("setfsquota result=%s", result)
468 except Exception as e:
469 msg = (_('Failed to update capacity quota '
470 '%(size)s on %(fstore)s with exception: %(e)s') %
471 {'size': new_size - old_size,
472 'fstore': fstore,
473 'e': str(e)})
474 LOG.error(msg)
475 raise exception.ShareBackendException(msg=msg)
477 # Non-empty result is an error message returned from the 3PAR
478 if result:
479 msg = (_('Failed to update capacity quota '
480 '%(size)s on %(fstore)s with error: %(error)s') %
481 {'size': new_size - old_size,
482 'fstore': fstore,
483 'error': result})
484 LOG.error(msg)
485 raise exception.ShareBackendException(msg=msg)
487 def _create_share(self, project_id, share_id, protocol, extra_specs,
488 fpg, vfs, fstore, sharedir, readonly, size, comment,
489 client_ip=None):
490 share_name = self.ensure_prefix(share_id, readonly=readonly)
492 if not (sharedir or self.hpe3par_fstore_per_share):
493 sharedir = share_name
495 if fstore:
496 use_existing_fstore = True
497 else:
498 use_existing_fstore = False
499 if self.hpe3par_fstore_per_share: 499 ↛ 501line 499 didn't jump to line 501 because the condition on line 499 was never true
500 # Do not use -ro in the fstore name.
501 fstore = self.ensure_prefix(share_id, readonly=False)
502 else:
503 fstore = self.ensure_prefix(project_id, protocol)
505 createfshare_kwargs = self._build_createfshare_kwargs(
506 protocol,
507 fpg,
508 fstore,
509 readonly,
510 sharedir,
511 extra_specs,
512 comment,
513 client_ip=client_ip)
515 if not use_existing_fstore:
517 try:
518 result = self._client.createfstore(
519 vfs, fstore, fpg=fpg,
520 comment=comment)
521 LOG.debug("createfstore result=%s", result)
522 except Exception as e:
523 msg = (_('Failed to create fstore %(fstore)s: %(e)s') %
524 {'fstore': fstore, 'e': str(e)})
525 LOG.exception(msg)
526 raise exception.ShareBackendException(msg=msg)
528 if size: 528 ↛ 531line 528 didn't jump to line 531 because the condition on line 528 was always true
529 self._update_capacity_quotas(fstore, size, 0, fpg, vfs)
531 try:
533 if readonly and protocol == 'nfs':
534 # For NFS, RO is a 2nd 3PAR share pointing to same sharedir
535 share_name = self.ensure_prefix(share_id, readonly=readonly)
537 result = self._client.createfshare(protocol,
538 vfs,
539 share_name,
540 **createfshare_kwargs)
542 LOG.debug("createfshare result=%s", result)
544 except Exception as e:
545 msg = (_('Failed to create share %(share_name)s: %(e)s') %
546 {'share_name': share_name, 'e': str(e)})
547 LOG.exception(msg)
548 raise exception.ShareBackendException(msg=msg)
550 try:
551 result = self._client.getfshare(
552 protocol, share_name,
553 fpg=fpg, vfs=vfs, fstore=fstore)
554 LOG.debug("getfshare result=%s", result)
556 except Exception as e:
557 msg = (_('Failed to get fshare %(share_name)s after creating it: '
558 '%(e)s') % {'share_name': share_name,
559 'e': str(e)})
560 LOG.exception(msg)
561 raise exception.ShareBackendException(msg=msg)
563 if result['total'] != 1:
564 msg = (_('Failed to get fshare %(share_name)s after creating it. '
565 'Expected to get 1 fshare. Got %(total)s.') %
566 {'share_name': share_name, 'total': result['total']})
567 LOG.error(msg)
568 raise exception.ShareBackendException(msg=msg)
569 return result['members'][0]
571 def create_share(self, project_id, share_id, share_proto, extra_specs,
572 fpg, vfs,
573 fstore=None, sharedir=None, readonly=False, size=None,
574 comment=OPEN_STACK_MANILA,
575 client_ip=None):
576 """Create the share and return its path.
578 This method can create a share when called by the driver or when
579 called locally from create_share_from_snapshot(). The optional
580 parameters allow re-use.
582 :param project_id: The tenant ID.
583 :param share_id: The share-id with or without osf- prefix.
584 :param share_proto: The protocol (to map to smb or nfs)
585 :param extra_specs: The share type extra-specs
586 :param fpg: The file provisioning group
587 :param vfs: The virtual file system
588 :param fstore: (optional) The file store. When provided, an existing
589 file store is used. Otherwise one is created.
590 :param sharedir: (optional) Share directory.
591 :param readonly: (optional) Create share as read-only.
592 :param size: (optional) Size limit for file store if creating one.
593 :param comment: (optional) Comment to set on the share.
594 :param client_ip: (optional) IP address to give access to.
595 :return: share path string
596 """
598 protocol = self.ensure_supported_protocol(share_proto)
599 share = self._create_share(project_id,
600 share_id,
601 protocol,
602 extra_specs,
603 fpg,
604 vfs,
605 fstore,
606 sharedir,
607 readonly,
608 size,
609 comment,
610 client_ip=client_ip)
612 if protocol == 'nfs':
613 return share['sharePath']
614 else:
615 return share['shareName']
617 def create_share_from_snapshot(self, share_id, share_proto, extra_specs,
618 orig_project_id, orig_share_id,
619 snapshot_id, fpg, vfs, ips,
620 size=None,
621 comment=OPEN_STACK_MANILA):
623 protocol = self.ensure_supported_protocol(share_proto)
624 snapshot_tag = self.ensure_prefix(snapshot_id)
625 orig_share_name = self.ensure_prefix(orig_share_id)
627 snapshot = self._find_fsnap(orig_project_id,
628 orig_share_name,
629 protocol,
630 snapshot_tag,
631 fpg,
632 vfs)
634 if not snapshot:
635 msg = (_('Failed to create share from snapshot for '
636 'FPG/VFS/tag %(fpg)s/%(vfs)s/%(tag)s. '
637 'Snapshot not found.') %
638 {
639 'fpg': fpg,
640 'vfs': vfs,
641 'tag': snapshot_tag})
642 LOG.error(msg)
643 raise exception.ShareBackendException(msg=msg)
645 fstore = snapshot['fstoreName']
646 if fstore == orig_share_name: 646 ↛ 648line 646 didn't jump to line 648 because the condition on line 646 was never true
647 # No subdir for original share created with fstore_per_share
648 sharedir = '.snapshot/%s' % snapshot['snapName']
649 else:
650 sharedir = '.snapshot/%s/%s' % (snapshot['snapName'],
651 orig_share_name)
653 if protocol == "smb" and (not self.hpe3par_cifs_admin_access_username
654 or not self.hpe3par_cifs_admin_access_password):
655 LOG.warning("hpe3par_cifs_admin_access_username and "
656 "hpe3par_cifs_admin_access_password must be "
657 "provided in order for CIFS shares created from "
658 "snapshots to be writable.")
659 return self.create_share(
660 orig_project_id,
661 share_id,
662 protocol,
663 extra_specs,
664 fpg,
665 vfs,
666 fstore=fstore,
667 sharedir=sharedir,
668 readonly=True,
669 comment=comment,
670 )
672 # Export the snapshot as read-only to copy from.
673 temp = ' '.join((comment, TMP_RO_SNAP_EXPORT))
674 source_path = self.create_share(
675 orig_project_id,
676 share_id,
677 protocol,
678 extra_specs,
679 fpg,
680 vfs,
681 fstore=fstore,
682 sharedir=sharedir,
683 readonly=True,
684 comment=temp,
685 client_ip=self.my_ip
686 )
688 try:
689 share_name = self.ensure_prefix(share_id)
690 dest_path = self.create_share(
691 orig_project_id,
692 share_id,
693 protocol,
694 extra_specs,
695 fpg,
696 vfs,
697 fstore=fstore,
698 readonly=False,
699 size=size,
700 comment=comment,
701 client_ip=','.join((self.my_ip, LOCAL_IP))
702 )
704 try:
705 if protocol == 'smb':
706 self._grant_admin_smb_access(
707 protocol, fpg, vfs, fstore, comment, share=share_name)
709 ro_share_name = self.ensure_prefix(share_id, readonly=True)
710 self._grant_admin_smb_access(
711 protocol, fpg, vfs, fstore, temp, share=ro_share_name)
713 source_locations = self.build_export_locations(
714 protocol, ips, source_path)
715 dest_locations = self.build_export_locations(
716 protocol, ips, dest_path)
718 self._copy_share_data(
719 share_id, source_locations[0], dest_locations[0], protocol)
721 # Revoke the admin access that was needed to copy to the dest.
722 if protocol == 'nfs':
723 self._change_access(DENY,
724 orig_project_id,
725 share_id,
726 protocol,
727 'ip',
728 self.my_ip,
729 'rw',
730 fpg,
731 vfs)
732 else:
733 self._revoke_admin_smb_access(
734 protocol, fpg, vfs, fstore, comment)
736 except Exception as e:
737 msg = ('Exception during mount and copy from RO snapshot '
738 'to RW share: %s')
739 LOG.error(msg, e)
740 self._delete_share(share_name, protocol, fpg, vfs, fstore)
741 raise
743 finally:
744 self._delete_ro_share(
745 orig_project_id, share_id, protocol, fpg, vfs, fstore)
747 return dest_path
749 def _copy_share_data(self, dest_id, source_location, dest_location,
750 protocol):
752 mount_location = "%s%s" % (self.hpe3par_share_mount_path, dest_id)
753 source_share_dir = '/'.join((mount_location, "source_snap"))
754 dest_share_dir = '/'.join((mount_location, "dest_share"))
756 dirs_to_remove = []
757 dirs_to_unmount = []
758 try:
759 utils.execute('mkdir', '-p', source_share_dir, run_as_root=True)
760 dirs_to_remove.append(source_share_dir)
761 self._mount_share(protocol, source_location, source_share_dir)
762 dirs_to_unmount.append(source_share_dir)
764 utils.execute('mkdir', dest_share_dir, run_as_root=True)
765 dirs_to_remove.append(dest_share_dir)
766 self._mount_share(protocol, dest_location, dest_share_dir)
767 dirs_to_unmount.append(dest_share_dir)
769 self._copy_data(source_share_dir, dest_share_dir)
770 finally:
771 for d in dirs_to_unmount:
772 self._unmount_share(d)
774 if dirs_to_remove: 774 ↛ exitline 774 didn't return from function '_copy_share_data' because the condition on line 774 was always true
775 dirs_to_remove.append(mount_location)
776 utils.execute('rmdir', *dirs_to_remove, run_as_root=True)
778 def _copy_data(self, source_share_dir, dest_share_dir):
780 err_msg = None
781 err_data = None
782 try:
783 copy = data_utils.Copy(source_share_dir, dest_share_dir, '')
784 copy.run()
785 progress = copy.get_progress()['total_progress']
786 if progress != 100:
787 err_msg = _("Failed to copy data, reason: "
788 "Total progress %d != 100.")
789 err_data = progress
790 except Exception as err:
791 err_msg = _("Failed to copy data, reason: %s.")
792 err_data = str(err)
794 if err_msg:
795 raise exception.ShareBackendException(msg=err_msg % err_data)
797 def _delete_share(self, share_name, protocol, fpg, vfs, fstore):
798 try:
799 self._client.removefshare(
800 protocol, vfs, share_name, fpg=fpg, fstore=fstore)
802 except Exception as e:
803 msg = (_('Failed to remove share %(share_name)s: %(e)s') %
804 {'share_name': share_name, 'e': str(e)})
805 LOG.exception(msg)
806 raise exception.ShareBackendException(msg=msg)
808 def _delete_ro_share(self, project_id, share_id, protocol,
809 fpg, vfs, fstore):
810 share_name_ro = self.ensure_prefix(share_id, readonly=True)
811 if not fstore:
812 fstore = self._find_fstore(project_id,
813 share_name_ro,
814 protocol,
815 fpg,
816 vfs,
817 allow_cross_protocol=True)
818 if fstore:
819 self._delete_share(share_name_ro, protocol, fpg, vfs, fstore)
820 return fstore
822 def delete_share(self, project_id, share_id, share_size, share_proto,
823 fpg, vfs, share_ip):
825 protocol = self.ensure_supported_protocol(share_proto)
826 share_name = self.ensure_prefix(share_id)
827 fstore = self._find_fstore(project_id,
828 share_name,
829 protocol,
830 fpg,
831 vfs,
832 allow_cross_protocol=True)
834 removed_writable = False
835 if fstore:
836 self._delete_share(share_name, protocol, fpg, vfs, fstore)
837 removed_writable = True
839 # Try to delete the read-only twin share, too.
840 fstore = self._delete_ro_share(
841 project_id, share_id, protocol, fpg, vfs, fstore)
843 if fstore == share_name:
844 try:
845 self._client.removefstore(vfs, fstore, fpg=fpg)
846 except Exception as e:
847 msg = (_('Failed to remove fstore %(fstore)s: %(e)s') %
848 {'fstore': fstore, 'e': str(e)})
849 LOG.exception(msg)
850 raise exception.ShareBackendException(msg=msg)
852 elif removed_writable:
853 try:
854 # Attempt to remove file tree on delete when using nested
855 # shares. If the file tree cannot be removed for whatever
856 # reason, we will not treat this as an error_deleting
857 # issue. We will allow the delete to continue as requested.
858 self._delete_file_tree(
859 share_name, protocol, fpg, vfs, fstore, share_ip)
860 # reduce the fsquota by share size when a tree is deleted.
861 self._update_capacity_quotas(
862 fstore, 0, share_size, fpg, vfs)
863 except Exception as e:
864 msg = ('Exception during cleanup of deleted '
865 'share %(share)s in filestore %(fstore)s: %(e)s')
866 data = {
867 'fstore': fstore,
868 'share': share_name,
869 'e': str(e),
870 }
871 LOG.warning(msg, data)
873 def _delete_file_tree(self, share_name, protocol, fpg, vfs, fstore,
874 share_ip):
875 # If the share protocol is CIFS, we need to make sure the admin
876 # provided the proper config values. If they have not, we can simply
877 # return out and log a warning.
878 if protocol == "smb" and (not self.hpe3par_cifs_admin_access_username
879 or not self.hpe3par_cifs_admin_access_password):
880 LOG.warning("hpe3par_cifs_admin_access_username and "
881 "hpe3par_cifs_admin_access_password must be "
882 "provided in order for the file tree to be "
883 "properly deleted.")
884 return
886 mount_location = "%s%s" % (self.hpe3par_share_mount_path, share_name)
887 share_dir = mount_location + "/%s" % share_name
889 # Create the super share.
890 self._create_super_share(protocol, fpg, vfs, fstore)
892 # Create the mount directory.
893 self._create_mount_directory(mount_location)
895 # Mount the super share.
896 self._mount_super_share(protocol, mount_location, fpg, vfs, fstore,
897 share_ip)
899 # Delete the share from the super share.
900 self._delete_share_directory(share_dir)
902 # Unmount the super share.
903 self._unmount_share(mount_location)
905 # Delete the mount directory.
906 self._delete_share_directory(mount_location)
908 def _grant_admin_smb_access(self, protocol, fpg, vfs, fstore, comment,
909 share=SUPER_SHARE):
910 user = '+%s:fullcontrol' % self.hpe3par_cifs_admin_access_username
911 setfshare_kwargs = {
912 'fpg': fpg,
913 'fstore': fstore,
914 'comment': comment,
915 'allowperm': user,
916 }
917 try:
918 self._client.setfshare(
919 protocol, vfs, share, **setfshare_kwargs)
920 except Exception as err:
921 raise exception.ShareBackendException(
922 msg=_("There was an error adding permissions: %s") % err)
924 def _revoke_admin_smb_access(self, protocol, fpg, vfs, fstore, comment,
925 share=SUPER_SHARE):
926 user = '-%s:fullcontrol' % self.hpe3par_cifs_admin_access_username
927 setfshare_kwargs = {
928 'fpg': fpg,
929 'fstore': fstore,
930 'comment': comment,
931 'allowperm': user,
932 }
933 try:
934 self._client.setfshare(
935 protocol, vfs, share, **setfshare_kwargs)
936 except Exception as err:
937 raise exception.ShareBackendException(
938 msg=_("There was an error revoking permissions: %s") % err)
940 def _create_super_share(self, protocol, fpg, vfs, fstore, readonly=False):
941 sharedir = ''
942 extra_specs = {}
943 comment = 'OpenStack super share used to delete nested shares.'
944 createfshare_kwargs = self._build_createfshare_kwargs(protocol,
945 fpg,
946 fstore,
947 readonly,
948 sharedir,
949 extra_specs,
950 comment)
952 # If the share is NFS, we need to give the host access to the share in
953 # order to properly mount it.
954 if protocol == 'nfs':
955 createfshare_kwargs['clientip'] = self.my_ip
956 else:
957 createfshare_kwargs['allowip'] = self.my_ip
959 try:
960 result = self._client.createfshare(protocol,
961 vfs,
962 SUPER_SHARE,
963 **createfshare_kwargs)
964 LOG.debug("createfshare for %(name)s, result=%(result)s",
965 {'name': SUPER_SHARE, 'result': result})
966 except Exception as e:
967 msg = (_('Failed to create share %(share_name)s: %(e)s'),
968 {'share_name': SUPER_SHARE, 'e': str(e)})
969 LOG.exception(msg)
970 raise exception.ShareBackendException(msg=msg)
972 # If the share is CIFS, we need to grant access to the specified admin.
973 if protocol == 'smb': 973 ↛ exitline 973 didn't return from function '_create_super_share' because the condition on line 973 was always true
974 self._grant_admin_smb_access(protocol, fpg, vfs, fstore, comment)
976 def _create_mount_directory(self, mount_location):
977 try:
978 utils.execute('mkdir', mount_location, run_as_root=True)
979 except Exception as err:
980 message = ("There was an error creating mount directory: "
981 "%s. The nested file tree will not be deleted.",
982 str(err))
983 LOG.warning(message)
985 def _mount_share(self, protocol, export_location, mount_dir):
986 if protocol == 'nfs':
987 cmd = ('mount', '-t', 'nfs', export_location, mount_dir)
988 utils.execute(*cmd, run_as_root=True)
989 else:
990 export_location = export_location.replace('\\', '/')
991 cred = ('username=' + self.hpe3par_cifs_admin_access_username +
992 ',password=' +
993 self.hpe3par_cifs_admin_access_password +
994 ',domain=' + self.hpe3par_cifs_admin_access_domain)
995 cmd = ('mount', '-t', 'cifs', export_location, mount_dir,
996 '-o', cred)
997 utils.execute(*cmd, run_as_root=True)
999 def _mount_super_share(self, protocol, mount_dir, fpg, vfs, fstore,
1000 share_ip):
1001 try:
1002 mount_location = self._generate_mount_path(
1003 protocol, fpg, vfs, fstore, share_ip)
1004 self._mount_share(protocol, mount_location, mount_dir)
1005 except Exception as err:
1006 message = ("There was an error mounting the super share: "
1007 "%s. The nested file tree will not be deleted.",
1008 str(err))
1009 LOG.warning(message)
1011 def _unmount_share(self, mount_location):
1012 try:
1013 utils.execute('umount', mount_location, run_as_root=True)
1014 except Exception as err:
1015 message = ("There was an error unmounting the share at "
1016 "%(mount_location)s: %(error)s")
1017 msg_data = {
1018 'mount_location': mount_location,
1019 'error': str(err),
1020 }
1021 LOG.warning(message, msg_data)
1023 def _delete_share_directory(self, directory):
1024 try:
1025 utils.execute('rm', '-rf', directory, run_as_root=True)
1026 except Exception as err:
1027 message = ("There was an error removing the share: "
1028 "%s. The nested file tree will not be deleted.",
1029 str(err))
1030 LOG.warning(message)
1032 def _generate_mount_path(self, protocol, fpg, vfs, fstore, share_ip):
1033 path = None
1034 if protocol == 'nfs':
1035 path = (("%(share_ip)s:/%(fpg)s/%(vfs)s/%(fstore)s/") %
1036 {'share_ip': share_ip,
1037 'fpg': fpg,
1038 'vfs': vfs,
1039 'fstore': fstore})
1040 else:
1041 path = (("//%(share_ip)s/%(share_name)s/") %
1042 {'share_ip': share_ip,
1043 'share_name': SUPER_SHARE})
1044 return path
1046 def get_vfs(self, fpg, vfs=None):
1047 """Get the VFS or raise an exception."""
1049 try:
1050 result = self._client.getvfs(fpg=fpg, vfs=vfs)
1051 except Exception as e:
1052 msg = (_('Exception during getvfs %(vfs)s: %(e)s') %
1053 {'vfs': vfs, 'e': str(e)})
1054 LOG.exception(msg)
1055 raise exception.ShareBackendException(msg=msg)
1057 if result['total'] != 1:
1058 error_msg = result.get('message')
1059 if error_msg: 1059 ↛ 1060line 1059 didn't jump to line 1060 because the condition on line 1059 was never true
1060 message = (_('Error while validating FPG/VFS '
1061 '(%(fpg)s/%(vfs)s): %(msg)s') %
1062 {'fpg': fpg, 'vfs': vfs, 'msg': error_msg})
1063 LOG.error(message)
1064 raise exception.ShareBackendException(msg=message)
1065 else:
1066 message = (_('Error while validating FPG/VFS '
1067 '(%(fpg)s/%(vfs)s): Expected 1, '
1068 'got %(total)s.') %
1069 {'fpg': fpg, 'vfs': vfs,
1070 'total': result['total']})
1072 LOG.error(message)
1073 raise exception.ShareBackendException(msg=message)
1075 value = result['members'][0]
1076 if isinstance(value['vfsip'], dict):
1077 # This is for 3parclient returning only one VFS entry
1078 LOG.debug("3parclient version up to 4.2.1 is in use. Client "
1079 "upgrade may be needed if using a VFS with multiple "
1080 "IP addresses.")
1081 value['vfsip']['address'] = [value['vfsip']['address']]
1082 else:
1083 # This is for 3parclient returning list of VFS entries
1084 # Format get_vfs ret value to combine all IP addresses
1085 discovered_vfs_ips = []
1086 for vfs_entry in value['vfsip']:
1087 if vfs_entry['address']: 1087 ↛ 1086line 1087 didn't jump to line 1086 because the condition on line 1087 was always true
1088 discovered_vfs_ips.append(vfs_entry['address'])
1089 value['vfsip'] = value['vfsip'][0]
1090 value['vfsip']['address'] = discovered_vfs_ips
1091 return value
1093 @staticmethod
1094 def _is_share_from_snapshot(fshare):
1096 path = fshare.get('shareDir')
1097 if path:
1098 return '.snapshot' in path.split('/')
1100 path = fshare.get('sharePath')
1101 return path and '.snapshot' in path.split('/')
1103 def create_snapshot(self, orig_project_id, orig_share_id, orig_share_proto,
1104 snapshot_id, fpg, vfs):
1105 """Creates a snapshot of a share."""
1107 fshare = self._find_fshare(orig_project_id,
1108 orig_share_id,
1109 orig_share_proto,
1110 fpg,
1111 vfs)
1113 if not fshare:
1114 msg = (_('Failed to create snapshot for FPG/VFS/fshare '
1115 '%(fpg)s/%(vfs)s/%(fshare)s: Failed to find fshare.') %
1116 {'fpg': fpg, 'vfs': vfs, 'fshare': orig_share_id})
1117 LOG.error(msg)
1118 raise exception.ShareBackendException(msg=msg)
1120 if self._is_share_from_snapshot(fshare):
1121 msg = (_('Failed to create snapshot for FPG/VFS/fshare '
1122 '%(fpg)s/%(vfs)s/%(fshare)s: Share is a read-only '
1123 'share of an existing snapshot.') %
1124 {'fpg': fpg, 'vfs': vfs, 'fshare': orig_share_id})
1125 LOG.error(msg)
1126 raise exception.ShareBackendException(msg=msg)
1128 fstore = fshare.get('fstoreName')
1129 snapshot_tag = self.ensure_prefix(snapshot_id)
1130 try:
1131 result = self._client.createfsnap(
1132 vfs, fstore, snapshot_tag, fpg=fpg)
1134 LOG.debug("createfsnap result=%s", result)
1136 except Exception as e:
1137 msg = (_('Failed to create snapshot for FPG/VFS/fstore '
1138 '%(fpg)s/%(vfs)s/%(fstore)s: %(e)s') %
1139 {'fpg': fpg, 'vfs': vfs, 'fstore': fstore,
1140 'e': str(e)})
1141 LOG.exception(msg)
1142 raise exception.ShareBackendException(msg=msg)
1144 def delete_snapshot(self, orig_project_id, orig_share_id, orig_proto,
1145 snapshot_id, fpg, vfs):
1146 """Deletes a snapshot of a share."""
1148 snapshot_tag = self.ensure_prefix(snapshot_id)
1150 snapshot = self._find_fsnap(orig_project_id, orig_share_id, orig_proto,
1151 snapshot_tag, fpg, vfs)
1153 if not snapshot:
1154 return
1156 fstore = snapshot.get('fstoreName')
1158 for protocol in ('nfs', 'smb'):
1159 try:
1160 shares = self._client.getfshare(protocol,
1161 fpg=fpg,
1162 vfs=vfs,
1163 fstore=fstore)
1164 except Exception as e:
1165 msg = (_('Unexpected exception while getting share list. '
1166 'Cannot delete snapshot without checking for '
1167 'dependent shares first: %s') % str(e))
1168 LOG.exception(msg)
1169 raise exception.ShareBackendException(msg=msg)
1171 for share in shares['members']:
1172 if protocol == 'nfs':
1173 path = share['sharePath'][1:].split('/')
1174 dot_snapshot_index = 3
1175 else:
1176 if share['shareDir']:
1177 path = share['shareDir'].split('/')
1178 else:
1179 path = None
1180 dot_snapshot_index = 0
1182 snapshot_index = dot_snapshot_index + 1
1183 if path and len(path) > snapshot_index:
1184 if (path[dot_snapshot_index] == '.snapshot' and 1184 ↛ 1171line 1184 didn't jump to line 1171 because the condition on line 1184 was always true
1185 path[snapshot_index].endswith(snapshot_tag)):
1186 msg = (_('Cannot delete snapshot because it has a '
1187 'dependent share.'))
1188 raise exception.Invalid(msg)
1190 snapname = snapshot['snapName']
1191 try:
1192 result = self._client.removefsnap(
1193 vfs, fstore, snapname=snapname, fpg=fpg)
1195 LOG.debug("removefsnap result=%s", result)
1197 except Exception as e:
1198 msg = (_('Failed to delete snapshot for FPG/VFS/fstore/snapshot '
1199 '%(fpg)s/%(vfs)s/%(fstore)s/%(snapname)s: %(e)s') %
1200 {
1201 'fpg': fpg,
1202 'vfs': vfs,
1203 'fstore': fstore,
1204 'snapname': snapname,
1205 'e': str(e)})
1206 LOG.exception(msg)
1207 raise exception.ShareBackendException(msg=msg)
1209 # Try to reclaim the space
1210 try:
1211 self._client.startfsnapclean(fpg, reclaimStrategy='maxspeed')
1212 except Exception:
1213 # Remove already happened so only log this.
1214 LOG.exception('Unexpected exception calling startfsnapclean '
1215 'for FPG %(fpg)s.', {'fpg': fpg})
1217 @staticmethod
1218 def _validate_access_type(protocol, access_type):
1220 if access_type not in ('ip', 'user'):
1221 msg = (_("Invalid access type. Expected 'ip' or 'user'. "
1222 "Actual '%s'.") % access_type)
1223 LOG.error(msg)
1224 raise exception.InvalidInput(reason=msg)
1226 if protocol == 'nfs' and access_type != 'ip':
1227 msg = (_("Invalid NFS access type. HPE 3PAR NFS supports 'ip'. "
1228 "Actual '%s'.") % access_type)
1229 LOG.error(msg)
1230 raise exception.HPE3ParInvalid(err=msg)
1232 return protocol
1234 @staticmethod
1235 def _validate_access_level(protocol, access_type, access_level, fshare):
1237 readonly = access_level == 'ro'
1238 snapshot = HPE3ParMediator._is_share_from_snapshot(fshare)
1240 if snapshot and not readonly:
1241 reason = _('3PAR shares from snapshots require read-only access')
1242 LOG.error(reason)
1243 raise exception.InvalidShareAccess(reason=reason)
1245 if protocol == 'smb' and access_type == 'ip' and snapshot != readonly:
1246 msg = (_("Invalid CIFS access rule. HPE 3PAR optionally supports "
1247 "IP access rules for CIFS shares, but they must be "
1248 "read-only for shares from snapshots and read-write for "
1249 "other shares. Use the required CIFS 'user' access rules "
1250 "to refine access."))
1251 LOG.error(msg)
1252 raise exception.InvalidShareAccess(reason=msg)
1254 @staticmethod
1255 def ignore_benign_access_results(plus_or_minus, access_type, access_to,
1256 result):
1258 # TODO(markstur): Remove the next line when hpe3parclient is fixed.
1259 result = [x for x in result if x != '\r']
1261 if result:
1262 if plus_or_minus == DENY:
1263 if DOES_NOT_EXIST in result[0]:
1264 return None
1265 else:
1266 if access_type == 'user':
1267 if USER_ALREADY_EXISTS % access_to in result[0]: 1267 ↛ 1271line 1267 didn't jump to line 1271 because the condition on line 1267 was always true
1268 return None
1269 elif IP_ALREADY_EXISTS % access_to in result[0]:
1270 return None
1271 return result
1273 def _change_access(self, plus_or_minus, project_id, share_id, share_proto,
1274 access_type, access_to, access_level,
1275 fpg, vfs, extra_specs=None):
1276 """Allow or deny access to a share.
1278 Plus_or_minus character indicates add to allow list (+) or remove from
1279 allow list (-).
1280 """
1282 readonly = access_level == 'ro'
1283 protocol = self.ensure_supported_protocol(share_proto)
1285 try:
1286 self._validate_access_type(protocol, access_type)
1287 except Exception:
1288 if plus_or_minus == DENY: 1288 ↛ 1290line 1288 didn't jump to line 1290 because the condition on line 1288 was never true
1289 # Catch invalid rules for deny. Allow them to be deleted.
1290 return
1291 else:
1292 raise
1294 fshare = self._find_fshare(project_id,
1295 share_id,
1296 protocol,
1297 fpg,
1298 vfs,
1299 readonly=readonly)
1300 if not fshare:
1301 # Change access might apply to the share with the name that
1302 # does not match the access_level prefix.
1303 other_fshare = self._find_fshare(project_id,
1304 share_id,
1305 protocol,
1306 fpg,
1307 vfs,
1308 readonly=not readonly)
1309 if other_fshare:
1311 if plus_or_minus == DENY:
1312 # Try to deny rule from 'other' share for SMB or legacy.
1313 fshare = other_fshare
1315 elif self._is_share_from_snapshot(other_fshare): 1315 ↛ 1318line 1315 didn't jump to line 1318 because the condition on line 1315 was never true
1316 # Found a share-from-snapshot from before
1317 # "-ro" was added to the name. Use it.
1318 fshare = other_fshare
1320 elif protocol == 'nfs':
1321 # We don't have the RO|RW share we need, but the
1322 # opposite one already exists. It is OK to create
1323 # the one we need for ALLOW with NFS (not from snapshot).
1324 fstore = other_fshare.get('fstoreName')
1325 sharedir = other_fshare.get('shareDir')
1326 comment = other_fshare.get('comment')
1328 fshare = self._create_share(project_id,
1329 share_id,
1330 protocol,
1331 extra_specs,
1332 fpg,
1333 vfs,
1334 fstore=fstore,
1335 sharedir=sharedir,
1336 readonly=readonly,
1337 size=None,
1338 comment=comment)
1339 else:
1340 # SMB only has one share for RO and RW. Try to use it.
1341 fshare = other_fshare
1343 if not fshare:
1344 msg = _('Failed to change (%(change)s) access '
1345 'to FPG/share %(fpg)s/%(share)s '
1346 'for %(type)s %(to)s %(level)s): '
1347 'Share does not exist on 3PAR.')
1348 msg_data = {
1349 'change': plus_or_minus,
1350 'fpg': fpg,
1351 'share': share_id,
1352 'type': access_type,
1353 'to': access_to,
1354 'level': access_level,
1355 }
1357 if plus_or_minus == DENY:
1358 LOG.warning(msg, msg_data)
1359 return
1360 else:
1361 raise exception.HPE3ParInvalid(err=msg % msg_data)
1363 try:
1364 self._validate_access_level(
1365 protocol, access_type, access_level, fshare)
1366 except exception.InvalidShareAccess as e:
1367 if plus_or_minus == DENY:
1368 # Allow invalid access rules to be deleted.
1369 msg = _('Ignoring deny invalid access rule '
1370 'for FPG/share %(fpg)s/%(share)s '
1371 'for %(type)s %(to)s %(level)s): %(e)s')
1372 msg_data = {
1373 'change': plus_or_minus,
1374 'fpg': fpg,
1375 'share': share_id,
1376 'type': access_type,
1377 'to': access_to,
1378 'level': access_level,
1379 'e': str(e),
1380 }
1381 LOG.info(msg, msg_data)
1382 return
1383 else:
1384 raise
1386 share_name = fshare.get('shareName')
1387 setfshare_kwargs = {
1388 'fpg': fpg,
1389 'fstore': fshare.get('fstoreName'),
1390 'comment': fshare.get('comment'),
1391 }
1393 if protocol == 'nfs':
1394 access_change = '%s%s' % (plus_or_minus, access_to)
1395 setfshare_kwargs['clientip'] = access_change
1397 elif protocol == 'smb': 1397 ↛ 1408line 1397 didn't jump to line 1408 because the condition on line 1397 was always true
1399 if access_type == 'ip':
1400 access_change = '%s%s' % (plus_or_minus, access_to)
1401 setfshare_kwargs['allowip'] = access_change
1403 else:
1404 access_str = 'read' if readonly else 'fullcontrol'
1405 perm = '%s%s:%s' % (plus_or_minus, access_to, access_str)
1406 setfshare_kwargs['allowperm'] = perm
1408 try:
1409 result = self._client.setfshare(
1410 protocol, vfs, share_name, **setfshare_kwargs)
1412 result = self.ignore_benign_access_results(
1413 plus_or_minus, access_type, access_to, result)
1415 except Exception as e:
1416 result = str(e)
1418 LOG.debug("setfshare result=%s", result)
1419 if result: 1419 ↛ 1420line 1419 didn't jump to line 1420 because the condition on line 1419 was never true
1420 msg = (_('Failed to change (%(change)s) access to FPG/share '
1421 '%(fpg)s/%(share)s for %(type)s %(to)s %(level)s: '
1422 '%(error)s') %
1423 {'change': plus_or_minus,
1424 'fpg': fpg,
1425 'share': share_id,
1426 'type': access_type,
1427 'to': access_to,
1428 'level': access_level,
1429 'error': result})
1430 raise exception.ShareBackendException(msg=msg)
1432 def _find_fstore(self, project_id, share_id, share_proto, fpg, vfs,
1433 allow_cross_protocol=False):
1435 share = self._find_fshare(project_id,
1436 share_id,
1437 share_proto,
1438 fpg,
1439 vfs,
1440 allow_cross_protocol=allow_cross_protocol)
1442 return share.get('fstoreName') if share else None
1444 def _find_fshare(self, project_id, share_id, share_proto, fpg, vfs,
1445 allow_cross_protocol=False, readonly=False):
1447 share = self._find_fshare_with_proto(project_id,
1448 share_id,
1449 share_proto,
1450 fpg,
1451 vfs,
1452 readonly=readonly)
1454 if not share and allow_cross_protocol:
1455 other_proto = self.other_protocol(share_proto)
1456 share = self._find_fshare_with_proto(project_id,
1457 share_id,
1458 other_proto,
1459 fpg,
1460 vfs,
1461 readonly=readonly)
1462 return share
1464 def _find_fshare_with_proto(self, project_id, share_id, share_proto,
1465 fpg, vfs, readonly=False):
1467 protocol = self.ensure_supported_protocol(share_proto)
1468 share_name = self.ensure_prefix(share_id, readonly=readonly)
1470 project_fstore = self.ensure_prefix(project_id, share_proto)
1471 search_order = [
1472 {'fpg': fpg, 'vfs': vfs, 'fstore': project_fstore},
1473 {'fpg': fpg, 'vfs': vfs, 'fstore': share_name},
1474 {'fpg': fpg},
1475 {}
1476 ]
1478 try:
1479 for search_params in search_order:
1480 result = self._client.getfshare(protocol, share_name,
1481 **search_params)
1482 shares = result.get('members', [])
1483 if len(shares) == 1:
1484 return shares[0]
1485 except Exception as e:
1486 msg = (_('Unexpected exception while getting share list: %s') %
1487 str(e))
1488 raise exception.ShareBackendException(msg=msg)
1490 def _find_fsnap(self, project_id, share_id, orig_proto, snapshot_tag,
1491 fpg, vfs):
1493 share_name = self.ensure_prefix(share_id)
1494 osf_project_id = self.ensure_prefix(project_id, orig_proto)
1495 pattern = '*_%s' % self.ensure_prefix(snapshot_tag)
1497 search_order = [
1498 {'pat': True, 'fpg': fpg, 'vfs': vfs, 'fstore': osf_project_id},
1499 {'pat': True, 'fpg': fpg, 'vfs': vfs, 'fstore': share_name},
1500 {'pat': True, 'fpg': fpg},
1501 {'pat': True},
1502 ]
1504 try:
1505 for search_params in search_order:
1506 result = self._client.getfsnap(pattern, **search_params)
1507 snapshots = result.get('members', [])
1508 if len(snapshots) == 1:
1509 return snapshots[0]
1510 except Exception as e:
1511 msg = (_('Unexpected exception while getting snapshots: %s') %
1512 str(e))
1513 raise exception.ShareBackendException(msg=msg)
1515 def update_access(self, project_id, share_id, share_proto, extra_specs,
1516 access_rules, add_rules, delete_rules, fpg, vfs):
1517 """Update access to a share."""
1518 protocol = self.ensure_supported_protocol(share_proto)
1520 if not (delete_rules or add_rules):
1521 # We need to re add all the rules. Check with 3PAR on it's current
1522 # list and only add the deltas.
1523 share = self._find_fshare(project_id,
1524 share_id,
1525 share_proto,
1526 fpg,
1527 vfs)
1529 ref_users = []
1530 ro_ref_rules = []
1531 if protocol == 'nfs':
1532 ref_rules = share['clients']
1534 # Check for RO rules.
1535 ro_share = self._find_fshare(project_id,
1536 share_id,
1537 share_proto,
1538 fpg,
1539 vfs,
1540 readonly=True)
1541 if ro_share: 1541 ↛ 1555line 1541 didn't jump to line 1555 because the condition on line 1541 was always true
1542 ro_ref_rules = ro_share['clients']
1543 else:
1544 ref_rules = [x[0] for x in share['allowPerm']]
1545 ref_users = ref_rules[:]
1546 # Get IP access as well
1547 ips = share['allowIP']
1548 if not isinstance(ips, list): 1548 ↛ 1552line 1548 didn't jump to line 1552 because the condition on line 1548 was always true
1549 # If there is only one IP, the API returns a string
1550 # rather than a list. We need to account for that.
1551 ips = [ips]
1552 ref_rules += ips
1554 # Retrieve base rules.
1555 base_rules = []
1556 for rule in access_rules:
1557 base_rules.append(rule['access_to'])
1559 # Check if we need to remove any rules from 3PAR.
1560 for rule in ref_rules:
1561 if rule in ref_users:
1562 rule_type = 'user'
1563 else:
1564 rule_type = 'ip'
1566 if rule not in base_rules + [LOCAL_IP, LOCAL_IP_RO]:
1567 self._change_access(DENY,
1568 project_id,
1569 share_id,
1570 share_proto,
1571 rule_type,
1572 rule,
1573 None,
1574 fpg,
1575 vfs)
1577 # Check to see if there are any RO rules to remove.
1578 for rule in ro_ref_rules:
1579 if rule not in base_rules + [LOCAL_IP, LOCAL_IP_RO]: 1579 ↛ 1580line 1579 didn't jump to line 1580 because the condition on line 1579 was never true
1580 self._change_access(DENY,
1581 project_id,
1582 share_id,
1583 share_proto,
1584 rule_type,
1585 rule,
1586 'ro',
1587 fpg,
1588 vfs)
1590 # Check the rules we need to add.
1591 for rule in access_rules:
1592 if rule['access_to'] not in ref_rules and ( 1592 ↛ 1591line 1592 didn't jump to line 1591 because the condition on line 1592 was always true
1593 rule['access_to'] not in ro_ref_rules):
1594 # Rule does not exist, we need to add it
1595 self._change_access(ALLOW,
1596 project_id,
1597 share_id,
1598 share_proto,
1599 rule['access_type'],
1600 rule['access_to'],
1601 rule['access_level'],
1602 fpg,
1603 vfs,
1604 extra_specs=extra_specs)
1605 else:
1606 # We have deltas of the rules that need to be added and deleted.
1607 for rule in delete_rules:
1608 self._change_access(DENY,
1609 project_id,
1610 share_id,
1611 share_proto,
1612 rule['access_type'],
1613 rule['access_to'],
1614 rule['access_level'],
1615 fpg,
1616 vfs)
1617 for rule in add_rules:
1618 self._change_access(ALLOW,
1619 project_id,
1620 share_id,
1621 share_proto,
1622 rule['access_type'],
1623 rule['access_to'],
1624 rule['access_level'],
1625 fpg,
1626 vfs,
1627 extra_specs=extra_specs)
1629 def resize_share(self, project_id, share_id, share_proto,
1630 new_size, old_size, fpg, vfs):
1631 """Extends or shrinks size of existing share."""
1633 share_name = self.ensure_prefix(share_id)
1634 fstore = self._find_fstore(project_id,
1635 share_name,
1636 share_proto,
1637 fpg,
1638 vfs,
1639 allow_cross_protocol=False)
1641 if not fstore:
1642 msg = (_('Cannot resize share because it was not found.'))
1643 raise exception.InvalidShare(reason=msg)
1645 self._update_capacity_quotas(fstore, new_size, old_size, fpg, vfs)
1647 def fsip_exists(self, fsip):
1648 """Try to get FSIP. Return True if it exists."""
1650 vfs = fsip['vfs']
1651 fpg = fsip['fspool']
1653 try:
1654 result = self._client.getfsip(vfs, fpg=fpg)
1655 LOG.debug("getfsip result: %s", result)
1656 except Exception:
1657 msg = (_('Failed to get FSIPs for FPG/VFS %(fspool)s/%(vfs)s.') %
1658 fsip)
1659 LOG.exception(msg)
1660 raise exception.ShareBackendException(msg=msg)
1662 for member in result['members']:
1663 if all(item in member.items() for item in fsip.items()):
1664 return True
1666 return False
1668 def create_fsip(self, ip, subnet, vlantag, fpg, vfs):
1670 vlantag_str = str(vlantag) if vlantag else '0'
1672 # Try to create it. It's OK if it already exists.
1673 try:
1674 result = self._client.createfsip(ip,
1675 subnet,
1676 vfs,
1677 fpg=fpg,
1678 vlantag=vlantag_str)
1679 LOG.debug("createfsip result: %s", result)
1681 except Exception:
1682 msg = (_('Failed to create FSIP for %s') % ip)
1683 LOG.exception(msg)
1684 raise exception.ShareBackendException(msg=msg)
1686 # Verify that it really exists.
1687 fsip = {
1688 'fspool': fpg,
1689 'vfs': vfs,
1690 'address': ip,
1691 'prefixLen': subnet,
1692 'vlanTag': vlantag_str,
1693 }
1694 if not self.fsip_exists(fsip):
1695 msg = (_('Failed to get FSIP after creating it for '
1696 'FPG/VFS/IP/subnet/VLAN '
1697 '%(fspool)s/%(vfs)s/'
1698 '%(address)s/%(prefixLen)s/%(vlanTag)s.') % fsip)
1699 LOG.error(msg)
1700 raise exception.ShareBackendException(msg=msg)
1702 def remove_fsip(self, ip, fpg, vfs):
1704 if not (vfs and ip):
1705 # If there is no VFS and/or IP, then there is no FSIP to remove.
1706 return
1708 try:
1709 result = self._client.removefsip(vfs, ip, fpg=fpg)
1710 LOG.debug("removefsip result: %s", result)
1712 except Exception:
1713 msg = (_('Failed to remove FSIP %s') % ip)
1714 LOG.exception(msg)
1715 raise exception.ShareBackendException(msg=msg)
1717 # Verify that it really no longer exists.
1718 fsip = {
1719 'fspool': fpg,
1720 'vfs': vfs,
1721 'address': ip,
1722 }
1723 if self.fsip_exists(fsip):
1724 msg = (_('Failed to remove FSIP for FPG/VFS/IP '
1725 '%(fspool)s/%(vfs)s/%(address)s.') % fsip)
1726 LOG.error(msg)
1727 raise exception.ShareBackendException(msg=msg)