Coverage for manila/share/drivers/huawei/v3/connection.py: 95%
1120 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 Huawei Technologies Co., Ltd.
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.
16import os
17import random
18import string
19import tempfile
20import time
22from oslo_config import cfg
23from oslo_log import log
24import oslo_messaging as messaging
25from oslo_serialization import jsonutils
26from oslo_utils import excutils
27from oslo_utils import strutils
28from oslo_utils import units
30from manila.common import constants as common_constants
31from manila.data import utils as data_utils
32from manila import exception
33from manila.i18n import _
34from manila import rpc
35from manila.share.drivers.huawei import base as driver
36from manila.share.drivers.huawei import constants
37from manila.share.drivers.huawei import huawei_utils
38from manila.share.drivers.huawei.v3 import helper
39from manila.share.drivers.huawei.v3 import replication
40from manila.share.drivers.huawei.v3 import rpcapi as v3_rpcapi
41from manila.share.drivers.huawei.v3 import smartx
42from manila.share import share_types
43from manila.share import utils as share_utils
44from manila import utils
47CONF = cfg.CONF
49LOG = log.getLogger(__name__)
52class V3StorageConnection(driver.HuaweiBase):
53 """Helper class for Huawei OceanStor V3 storage system."""
55 def __init__(self, configuration, **kwargs):
56 super(V3StorageConnection, self).__init__(configuration)
57 self.helper = helper.RestHelper(self.configuration)
58 self.replica_mgr = replication.ReplicaPairManager(self.helper)
59 self.rpc_client = v3_rpcapi.HuaweiV3API()
60 self.private_storage = kwargs.get('private_storage')
61 self.qos_support = False
62 self.snapshot_support = False
63 self.replication_support = False
65 def _setup_rpc_server(self, endpoints):
66 host = "%s@%s" % (CONF.host, self.configuration.config_group)
67 target = messaging.Target(topic=self.rpc_client.topic, server=host)
68 self.rpc_server = rpc.get_server(target, endpoints)
69 self.rpc_server.start()
71 def connect(self):
72 """Try to connect to V3 server."""
73 self.helper.login()
74 self._setup_rpc_server([self.replica_mgr])
75 self._setup_conf()
77 def _setup_conf(self):
78 root = self.helper._read_xml()
80 snapshot_support = root.findtext('Storage/SnapshotSupport')
81 if snapshot_support:
82 self.snapshot_support = strutils.bool_from_string(
83 snapshot_support, strict=True)
85 replication_support = root.findtext('Storage/ReplicationSupport')
86 if replication_support:
87 self.replication_support = strutils.bool_from_string(
88 replication_support, strict=True)
90 def create_share(self, share, share_server=None):
91 """Create a share."""
92 share_name = share['name']
93 share_proto = share['share_proto']
95 pool_name = share_utils.extract_host(share['host'], level='pool')
97 if not pool_name:
98 msg = _("Pool is not available in the share host field.")
99 raise exception.InvalidHost(reason=msg)
101 result = self.helper._find_all_pool_info()
102 poolinfo = self.helper._find_pool_info(pool_name, result)
103 if not poolinfo:
104 msg = (_("Can not find pool info by pool name: %s.") % pool_name)
105 raise exception.InvalidHost(reason=msg)
107 fs_id = None
108 # We sleep here to ensure the newly created filesystem can be read.
109 wait_interval = self._get_wait_interval()
110 timeout = self._get_timeout()
112 try:
113 fs_id = self.allocate_container(share, poolinfo)
114 fs = self.helper._get_fs_info_by_id(fs_id)
115 end_time = time.time() + timeout
117 while not (self.check_fs_status(fs['HEALTHSTATUS'], 117 ↛ 120line 117 didn't jump to line 120 because the condition on line 117 was never true
118 fs['RUNNINGSTATUS'])
119 or time.time() > end_time):
120 time.sleep(wait_interval)
121 fs = self.helper._get_fs_info_by_id(fs_id)
123 if not self.check_fs_status(fs['HEALTHSTATUS'], 123 ↛ 125line 123 didn't jump to line 125 because the condition on line 123 was never true
124 fs['RUNNINGSTATUS']):
125 raise exception.InvalidShare(
126 reason=(_('Invalid status of filesystem: '
127 'HEALTHSTATUS=%(health)s '
128 'RUNNINGSTATUS=%(running)s.')
129 % {'health': fs['HEALTHSTATUS'],
130 'running': fs['RUNNINGSTATUS']}))
131 except Exception as err:
132 if fs_id is not None: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true
133 qos_id = self.helper.get_qosid_by_fsid(fs_id)
134 if qos_id:
135 self.remove_qos_fs(fs_id, qos_id)
136 self.helper._delete_fs(fs_id)
137 message = (_('Failed to create share %(name)s. '
138 'Reason: %(err)s.')
139 % {'name': share_name,
140 'err': err})
141 raise exception.InvalidShare(reason=message)
143 try:
144 self.helper.create_share(share_name, fs_id, share_proto)
145 except Exception as err:
146 if fs_id is not None: 146 ↛ 151line 146 didn't jump to line 151 because the condition on line 146 was always true
147 qos_id = self.helper.get_qosid_by_fsid(fs_id)
148 if qos_id: 148 ↛ 150line 148 didn't jump to line 150 because the condition on line 148 was always true
149 self.remove_qos_fs(fs_id, qos_id)
150 self.helper._delete_fs(fs_id)
151 raise exception.InvalidShare(
152 reason=(_('Failed to create share %(name)s. Reason: %(err)s.')
153 % {'name': share_name, 'err': err}))
155 ip = self._get_share_ip(share_server)
156 location = self._get_location_path(share_name, share_proto, ip)
157 return location
159 def _get_share_ip(self, share_server):
160 """"Get share logical ip."""
161 if share_server:
162 ip = share_server['backend_details'].get('ip')
163 else:
164 root = self.helper._read_xml()
165 ip = root.findtext('Storage/LogicalPortIP').strip()
167 return ip
169 def extend_share(self, share, new_size, share_server):
170 share_proto = share['share_proto']
171 share_name = share['name']
173 # The unit is in sectors.
174 size = int(new_size) * units.Mi * 2
175 share_url_type = self.helper._get_share_url_type(share_proto)
177 share = self.helper._get_share_by_name(share_name, share_url_type)
178 if not share:
179 err_msg = (_("Can not get share ID by share %s.")
180 % share_name)
181 LOG.error(err_msg)
182 raise exception.InvalidShareAccess(reason=err_msg)
184 fsid = share['FSID']
185 fs_info = self.helper._get_fs_info_by_id(fsid)
187 current_size = int(fs_info['CAPACITY']) / units.Mi / 2
188 if current_size >= new_size:
189 err_msg = (_("New size for extend must be bigger than "
190 "current size on array. (current: %(size)s, "
191 "new: %(new_size)s).")
192 % {'size': current_size, 'new_size': new_size})
194 LOG.error(err_msg)
195 raise exception.InvalidInput(reason=err_msg)
196 self.helper._change_share_size(fsid, size)
198 def shrink_share(self, share, new_size, share_server):
199 """Shrinks size of existing share."""
200 share_proto = share['share_proto']
201 share_name = share['name']
203 # The unit is in sectors.
204 size = int(new_size) * units.Mi * 2
205 share_url_type = self.helper._get_share_url_type(share_proto)
207 share = self.helper._get_share_by_name(share_name, share_url_type)
208 if not share:
209 err_msg = (_("Can not get share ID by share %s.")
210 % share_name)
211 LOG.error(err_msg)
212 raise exception.InvalidShare(reason=err_msg)
214 fsid = share['FSID']
215 fs_info = self.helper._get_fs_info_by_id(fsid)
216 if not fs_info: 216 ↛ 217line 216 didn't jump to line 217 because the condition on line 216 was never true
217 err_msg = (_("Can not get filesystem info by filesystem ID: %s.")
218 % fsid)
219 LOG.error(err_msg)
220 raise exception.InvalidShare(reason=err_msg)
222 current_size = int(fs_info['CAPACITY']) / units.Mi / 2
223 if current_size <= new_size:
224 err_msg = (_("New size for shrink must be less than current "
225 "size on array. (current: %(size)s, "
226 "new: %(new_size)s).")
227 % {'size': current_size, 'new_size': new_size})
228 LOG.error(err_msg)
229 raise exception.InvalidShare(reason=err_msg)
231 if fs_info['ALLOCTYPE'] != constants.ALLOC_TYPE_THIN_FLAG:
232 err_msg = (_("Share (%s) can not be shrunk. only 'Thin' shares "
233 "support shrink.")
234 % share_name)
235 LOG.error(err_msg)
236 raise exception.InvalidShare(reason=err_msg)
238 self.helper._change_share_size(fsid, size)
240 def check_fs_status(self, health_status, running_status):
241 if (health_status == constants.STATUS_FS_HEALTH
242 and running_status == constants.STATUS_FS_RUNNING):
243 return True
244 else:
245 return False
247 def assert_filesystem(self, fsid):
248 fs = self.helper._get_fs_info_by_id(fsid)
249 if not self.check_fs_status(fs['HEALTHSTATUS'],
250 fs['RUNNINGSTATUS']):
251 err_msg = (_('Invalid status of filesystem: '
252 'HEALTHSTATUS=%(health)s '
253 'RUNNINGSTATUS=%(running)s.')
254 % {'health': fs['HEALTHSTATUS'],
255 'running': fs['RUNNINGSTATUS']})
256 raise exception.StorageResourceException(err_msg)
258 def create_snapshot(self, snapshot, share_server=None):
259 """Create a snapshot."""
260 snap_name = snapshot['id']
261 share_proto = snapshot['share']['share_proto']
263 share_url_type = self.helper._get_share_url_type(share_proto)
264 share = self.helper._get_share_by_name(snapshot['share_name'],
265 share_url_type)
267 if not share:
268 err_msg = _('Can not create snapshot,'
269 ' because share id is not provided.')
270 LOG.error(err_msg)
271 raise exception.InvalidInput(reason=err_msg)
273 sharefsid = share['FSID']
274 snapshot_name = "share_snapshot_" + snap_name
275 snap_id = self.helper._create_snapshot(sharefsid,
276 snapshot_name)
277 LOG.info('Creating snapshot id %s.', snap_id)
278 return snapshot_name.replace("-", "_")
280 def delete_snapshot(self, snapshot, share_server=None):
281 """Delete a snapshot."""
282 LOG.debug("Delete a snapshot.")
283 snap_name = snapshot['id']
285 sharefsid = self.helper.get_fsid_by_name(snapshot['share_name'])
287 if sharefsid is None: 287 ↛ 288line 287 didn't jump to line 288 because the condition on line 287 was never true
288 LOG.warning('Delete snapshot share id %s fs has been '
289 'deleted.', snap_name)
290 return
292 snapshot_id = self.helper._get_snapshot_id(sharefsid, snap_name)
293 snapshot_info = self.helper._get_snapshot_by_id(snapshot_id)
294 snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_info)
296 if snapshot_flag:
297 self.helper._delete_snapshot(snapshot_id)
298 else:
299 LOG.warning("Can not find snapshot %s on array.", snap_name)
301 def update_share_stats(self, stats_dict):
302 """Retrieve status info from share group."""
303 root = self.helper._read_xml()
304 all_pool_info = self.helper._find_all_pool_info()
305 stats_dict["pools"] = []
307 pool_name_list = root.findtext('Filesystem/StoragePool')
308 pool_name_list = pool_name_list.split(";")
309 for pool_name in pool_name_list:
310 pool_name = pool_name.strip().strip('\n')
311 capacity = self._get_capacity(pool_name, all_pool_info)
312 disk_type = self._get_disk_type(pool_name, all_pool_info)
314 if capacity:
315 pool = dict(
316 pool_name=pool_name,
317 total_capacity_gb=capacity['TOTALCAPACITY'],
318 free_capacity_gb=capacity['CAPACITY'],
319 provisioned_capacity_gb=(
320 capacity['PROVISIONEDCAPACITYGB']),
321 max_over_subscription_ratio=(
322 self.configuration.safe_get(
323 'max_over_subscription_ratio')),
324 allocated_capacity_gb=capacity['CONSUMEDCAPACITY'],
325 qos=self._get_qos_capability(),
326 reserved_percentage=0,
327 reserved_snapshot_percentage=0,
328 reserved_share_extend_percentage=0,
329 thin_provisioning=[True, False],
330 dedupe=[True, False],
331 compression=[True, False],
332 huawei_smartcache=[True, False],
333 huawei_smartpartition=[True, False],
334 huawei_sectorsize=[True, False],
335 )
337 if disk_type:
338 pool['huawei_disk_type'] = disk_type
340 stats_dict["pools"].append(pool)
342 if not stats_dict["pools"]:
343 err_msg = _("The StoragePool is None.")
344 LOG.error(err_msg)
345 raise exception.InvalidInput(reason=err_msg)
347 def _get_qos_capability(self):
348 version = self.helper.find_array_version()
349 if version.upper() >= constants.MIN_ARRAY_VERSION_FOR_QOS: 349 ↛ 352line 349 didn't jump to line 352 because the condition on line 349 was always true
350 self.qos_support = True
351 else:
352 self.qos_support = False
353 return self.qos_support
355 def delete_share(self, share, share_server=None):
356 """Delete share."""
357 share_name = share['name']
358 share_url_type = self.helper._get_share_url_type(share['share_proto'])
359 share = self.helper._get_share_by_name(share_name, share_url_type)
361 if not share:
362 LOG.warning('The share was not found. Share name:%s',
363 share_name)
364 fsid = self.helper.get_fsid_by_name(share_name)
365 if fsid: 365 ↛ 368line 365 didn't jump to line 368 because the condition on line 365 was always true
366 self.helper._delete_fs(fsid)
367 return
368 LOG.warning('The filesystem was not found.')
369 return
371 share_id = share['ID']
372 share_fs_id = share['FSID']
374 if share_id: 374 ↛ 377line 374 didn't jump to line 377 because the condition on line 374 was always true
375 self.helper._delete_share_by_id(share_id, share_url_type)
377 if share_fs_id: 377 ↛ 384line 377 didn't jump to line 384 because the condition on line 377 was always true
378 if self.qos_support:
379 qos_id = self.helper.get_qosid_by_fsid(share_fs_id)
380 if qos_id: 380 ↛ 382line 380 didn't jump to line 382 because the condition on line 380 was always true
381 self.remove_qos_fs(share_fs_id, qos_id)
382 self.helper._delete_fs(share_fs_id)
384 return share
386 def create_share_from_snapshot(self, share, snapshot,
387 share_server=None, parent_share=None):
388 """Create a share from snapshot."""
389 share_fs_id = self.helper.get_fsid_by_name(snapshot['share_name'])
390 if not share_fs_id:
391 err_msg = (_("The source filesystem of snapshot %s "
392 "does not exist.")
393 % snapshot['snapshot_id'])
394 LOG.error(err_msg)
395 raise exception.StorageResourceNotFound(
396 name=snapshot['share_name'])
398 snapshot_id = self.helper._get_snapshot_id(share_fs_id, snapshot['id'])
399 snapshot_info = self.helper._get_snapshot_by_id(snapshot_id)
400 snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_info)
401 if not snapshot_flag:
402 err_msg = (_("Cannot find snapshot %s on array.")
403 % snapshot['snapshot_id'])
404 LOG.error(err_msg)
405 raise exception.ShareSnapshotNotFound(
406 snapshot_id=snapshot['snapshot_id'])
408 self.assert_filesystem(share_fs_id)
410 old_share_name = self.helper.get_share_name_by_id(
411 snapshot['share_id'])
412 old_share_proto = self._get_share_proto(old_share_name)
413 if not old_share_proto:
414 err_msg = (_("Cannot find source share %(share)s of "
415 "snapshot %(snapshot)s on array.")
416 % {'share': snapshot['share_id'],
417 'snapshot': snapshot['snapshot_id']})
418 LOG.error(err_msg)
419 raise exception.ShareResourceNotFound(
420 share_id=snapshot['share_id'])
422 new_share_path = self.create_share(share)
423 new_share = {
424 "share_proto": share['share_proto'],
425 "size": share['size'],
426 "name": share['name'],
427 "mount_path": new_share_path.replace("\\", "/"),
428 "mount_src":
429 tempfile.mkdtemp(prefix=constants.TMP_PATH_DST_PREFIX),
430 "id": snapshot['share_id'],
431 }
433 old_share_path = self._get_location_path(old_share_name,
434 old_share_proto)
435 old_share = {
436 "share_proto": old_share_proto,
437 "name": old_share_name,
438 "mount_path": old_share_path.replace("\\", "/"),
439 "mount_src":
440 tempfile.mkdtemp(prefix=constants.TMP_PATH_SRC_PREFIX),
441 "snapshot_name": ("share_snapshot_" +
442 snapshot['id'].replace("-", "_")),
443 "id": snapshot['share_id'],
444 }
446 try:
447 self.copy_data_from_parent_share(old_share, new_share)
448 except Exception:
449 with excutils.save_and_reraise_exception():
450 self.delete_share(new_share)
451 finally:
452 for item in (new_share, old_share):
453 try:
454 os.rmdir(item['mount_src'])
455 except Exception as err:
456 LOG.warning('Failed to remove temp file. File path:'
457 '%(file_path)s. Reason: %(err)s.',
458 {'file_path': item['mount_src'],
459 'err': err})
461 return new_share_path
463 def copy_data_from_parent_share(self, old_share, new_share):
464 old_access = self.get_access(old_share)
465 old_access_id = self._get_access_id(old_share, old_access)
466 if not old_access_id:
467 try:
468 self.allow_access(old_share, old_access)
469 except exception.ManilaException as err:
470 with excutils.save_and_reraise_exception():
471 LOG.error('Failed to add access to share %(name)s. '
472 'Reason: %(err)s.',
473 {'name': old_share['name'],
474 'err': err})
476 new_access = self.get_access(new_share)
477 try:
478 try:
479 self.mount_share_to_host(old_share, old_access)
480 except exception.ShareMountException as err:
481 with excutils.save_and_reraise_exception():
482 LOG.error('Failed to mount old share %(name)s. '
483 'Reason: %(err)s.',
484 {'name': old_share['name'],
485 'err': err})
487 try:
488 self.allow_access(new_share, new_access)
489 self.mount_share_to_host(new_share, new_access)
490 except Exception as err:
491 with excutils.save_and_reraise_exception():
492 self.umount_share_from_host(old_share)
493 LOG.error('Failed to mount new share %(name)s. '
494 'Reason: %(err)s.',
495 {'name': new_share['name'],
496 'err': err})
498 copied = self.copy_snapshot_data(old_share, new_share)
500 for item in (new_share, old_share):
501 try:
502 self.umount_share_from_host(item)
503 except exception.ShareUmountException as err:
504 LOG.warning('Failed to unmount share %(name)s. '
505 'Reason: %(err)s.',
506 {'name': item['name'],
507 'err': err})
509 self.deny_access(new_share, new_access)
511 if copied:
512 LOG.debug("Created share from snapshot successfully, "
513 "new_share: %s, old_share: %s.",
514 new_share, old_share)
515 else:
516 message = (_('Failed to copy data from share %(old_share)s '
517 'to share %(new_share)s.')
518 % {'old_share': old_share['name'],
519 'new_share': new_share['name']})
520 raise exception.ShareCopyDataException(reason=message)
521 finally:
522 if not old_access_id:
523 self.deny_access(old_share, old_access)
525 def get_access(self, share):
526 share_proto = share['share_proto']
527 access = {}
528 root = self.helper._read_xml()
530 if share_proto == 'NFS':
531 access['access_to'] = root.findtext('Filesystem/NFSClient/IP')
532 access['access_level'] = common_constants.ACCESS_LEVEL_RW
533 access['access_type'] = 'ip'
534 elif share_proto == 'CIFS': 534 ↛ 542line 534 didn't jump to line 542 because the condition on line 534 was always true
535 access['access_to'] = root.findtext(
536 'Filesystem/CIFSClient/UserName')
537 access['access_password'] = root.findtext(
538 'Filesystem/CIFSClient/UserPassword')
539 access['access_level'] = common_constants.ACCESS_LEVEL_RW
540 access['access_type'] = 'user'
542 LOG.debug("Get access for share: %s, access_type: %s, access_to: %s, "
543 "access_level: %s", share['name'], access['access_type'],
544 access['access_to'], access['access_level'])
545 return access
547 def _get_access_id(self, share, access):
548 """Get access id of the share."""
549 access_id = None
550 share_name = share['name']
551 share_proto = share['share_proto']
552 share_url_type = self.helper._get_share_url_type(share_proto)
553 access_to = access['access_to']
554 share = self.helper._get_share_by_name(share_name, share_url_type)
555 access_id = self.helper._get_access_from_share(share['ID'], access_to,
556 share_proto)
557 if access_id is None: 557 ↛ 561line 557 didn't jump to line 561 because the condition on line 557 was always true
558 LOG.debug('Cannot get access ID from share. '
559 'share_name: %s', share_name)
561 return access_id
563 def copy_snapshot_data(self, old_share, new_share):
564 src_path = '/'.join((old_share['mount_src'], '.snapshot',
565 old_share['snapshot_name']))
566 dst_path = new_share['mount_src']
567 copy_finish = False
568 LOG.debug("Copy data from src_path: %s to dst_path: %s.",
569 src_path, dst_path)
570 try:
571 ignore_list = ''
572 copy = data_utils.Copy(src_path, dst_path, ignore_list)
573 copy.run()
574 if copy.get_progress()['total_progress'] == 100: 574 ↛ 579line 574 didn't jump to line 579 because the condition on line 574 was always true
575 copy_finish = True
576 except Exception as err:
577 LOG.error("Failed to copy data, reason: %s.", err)
579 return copy_finish
581 def umount_share_from_host(self, share):
582 try:
583 utils.execute('umount', share['mount_path'],
584 run_as_root=True)
585 except Exception as err:
586 message = (_("Failed to unmount share %(share)s. "
587 "Reason: %(reason)s.")
588 % {'share': share['name'],
589 'reason': str(err)})
590 raise exception.ShareUmountException(reason=message)
592 def mount_share_to_host(self, share, access):
593 LOG.debug("Mounting share: %s to host, mount_src: %s",
594 share['name'], share['mount_src'])
595 try:
596 if share['share_proto'] == 'NFS':
597 utils.execute('mount', '-t', 'nfs',
598 share['mount_path'], share['mount_src'],
599 run_as_root=True)
601 LOG.debug("Execute mount. mount_src: %s",
602 share['mount_src'])
604 elif share['share_proto'] == 'CIFS': 604 ↛ exitline 604 didn't return from function 'mount_share_to_host' because the condition on line 604 was always true
605 user = ('username=' + access['access_to'] + ',' +
606 'password=' + access['access_password'])
607 utils.execute('mount', '-t', 'cifs',
608 share['mount_path'], share['mount_src'],
609 '-o', user, run_as_root=True)
610 except Exception as err:
611 message = (_('Bad response from mount share: %(share)s. '
612 'Reason: %(reason)s.')
613 % {'share': share['name'],
614 'reason': str(err)})
615 raise exception.ShareMountException(reason=message)
617 def get_network_allocations_number(self):
618 """Get number of network interfaces to be created."""
619 if self.configuration.driver_handles_share_servers:
620 return constants.IP_ALLOCATIONS_DHSS_TRUE
621 else:
622 return constants.IP_ALLOCATIONS_DHSS_FALSE
624 def _get_capacity(self, pool_name, result):
625 """Get free capacity and total capacity of the pools."""
626 poolinfo = self.helper._find_pool_info(pool_name, result)
628 if poolinfo:
629 total = float(poolinfo['TOTALCAPACITY']) / units.Mi / 2
630 free = float(poolinfo['CAPACITY']) / units.Mi / 2
631 consumed = float(poolinfo['CONSUMEDCAPACITY']) / units.Mi / 2
632 poolinfo['TOTALCAPACITY'] = total
633 poolinfo['CAPACITY'] = free
634 poolinfo['CONSUMEDCAPACITY'] = consumed
635 poolinfo['PROVISIONEDCAPACITYGB'] = round(
636 float(total) - float(free), 2)
638 return poolinfo
640 def _get_disk_type(self, pool_name, result):
641 """Get disk type of the pool."""
642 pool_info = self.helper._find_pool_info(pool_name, result)
643 if not pool_info:
644 return None
646 pool_disk = []
647 for i, x in enumerate(['ssd', 'sas', 'nl_sas']):
648 if pool_info['TIER%dCAPACITY' % i] != '0':
649 pool_disk.append(x)
651 if len(pool_disk) > 1:
652 pool_disk = ['mix']
654 return pool_disk[0] if pool_disk else None
656 def _init_filesys_para(self, share, poolinfo, extra_specs):
657 """Init basic filesystem parameters."""
658 name = share['name']
659 size = int(share['size']) * units.Mi * 2
660 fileparam = {
661 "NAME": name.replace("-", "_"),
662 "DESCRIPTION": "",
663 "ALLOCTYPE": extra_specs['LUNType'],
664 "CAPACITY": size,
665 "PARENTID": poolinfo['ID'],
666 "INITIALALLOCCAPACITY": units.Ki * 20,
667 "PARENTTYPE": 216,
668 "SNAPSHOTRESERVEPER": 20,
669 "INITIALDISTRIBUTEPOLICY": 0,
670 "ISSHOWSNAPDIR": True,
671 "RECYCLESWITCH": 0,
672 "RECYCLEHOLDTIME": 15,
673 "RECYCLETHRESHOLD": 0,
674 "RECYCLEAUTOCLEANSWITCH": 0,
675 "ENABLEDEDUP": extra_specs['dedupe'],
676 "ENABLECOMPRESSION": extra_specs['compression'],
677 }
679 if fileparam['ALLOCTYPE'] == constants.ALLOC_TYPE_THICK_FLAG:
680 if (extra_specs['dedupe'] or
681 extra_specs['compression']):
682 err_msg = _(
683 'The filesystem type is "Thick",'
684 ' so dedupe or compression cannot be set.')
685 LOG.error(err_msg)
686 raise exception.InvalidInput(reason=err_msg)
687 if extra_specs['sectorsize']:
688 fileparam['SECTORSIZE'] = extra_specs['sectorsize'] * units.Ki
690 return fileparam
692 def deny_access(self, share, access, share_server=None):
693 """Deny access to share."""
694 share_proto = share['share_proto']
695 share_name = share['name']
696 share_url_type = self.helper._get_share_url_type(share_proto)
697 access_type = access['access_type']
698 if share_proto == 'NFS' and access_type not in ('ip', 'user'):
699 LOG.warning('Only IP or USER access types are allowed for '
700 'NFS shares.')
701 return
702 elif share_proto == 'CIFS' and access_type != 'user':
703 LOG.warning('Only USER access type is allowed for'
704 ' CIFS shares.')
705 return
707 access_to = access['access_to']
708 # Huawei array uses * to represent IP addresses of all clients
709 if (share_proto == 'NFS' and access_type == 'ip' and
710 access_to == '0.0.0.0/0'):
711 access_to = '*'
712 share = self.helper._get_share_by_name(share_name, share_url_type)
713 if not share:
714 LOG.warning('Can not get share %s.', share_name)
715 return
717 access_id = self.helper._get_access_from_share(share['ID'], access_to,
718 share_proto)
719 if not access_id:
720 LOG.warning('Can not get access id from share. '
721 'share_name: %s', share_name)
722 return
724 self.helper._remove_access_from_share(access_id, share_proto)
726 def allow_access(self, share, access, share_server=None):
727 """Allow access to the share."""
728 share_proto = share['share_proto']
729 share_name = share['name']
730 share_url_type = self.helper._get_share_url_type(share_proto)
731 access_type = access['access_type']
732 access_level = access['access_level']
733 access_to = access['access_to']
735 if access_level not in common_constants.ACCESS_LEVELS:
736 raise exception.InvalidShareAccess(
737 reason=(_('Unsupported level of access was provided - %s') %
738 access_level))
740 if share_proto == 'NFS':
741 if access_type == 'user':
742 # Use 'user' as 'netgroup' for NFS.
743 # A group name starts with @.
744 access_to = '@' + access_to
745 elif access_type != 'ip':
746 message = _('Only IP or USER access types '
747 'are allowed for NFS shares.')
748 raise exception.InvalidShareAccess(reason=message)
749 if access_level == common_constants.ACCESS_LEVEL_RW:
750 access_level = constants.ACCESS_NFS_RW
751 else:
752 access_level = constants.ACCESS_NFS_RO
753 # Huawei array uses * to represent IP addresses of all clients
754 if access_to == '0.0.0.0/0':
755 access_to = '*'
757 elif share_proto == 'CIFS': 757 ↛ 768line 757 didn't jump to line 768 because the condition on line 757 was always true
758 if access_type == 'user':
759 if access_level == common_constants.ACCESS_LEVEL_RW:
760 access_level = constants.ACCESS_CIFS_FULLCONTROL
761 else:
762 access_level = constants.ACCESS_CIFS_RO
763 else:
764 message = _('Only USER access type is allowed'
765 ' for CIFS shares.')
766 raise exception.InvalidShareAccess(reason=message)
768 share_stor = self.helper._get_share_by_name(share_name,
769 share_url_type)
770 if not share_stor:
771 err_msg = (_("Share %s does not exist on the backend.")
772 % share_name)
773 LOG.error(err_msg)
774 raise exception.ShareResourceNotFound(share_id=share['id'])
776 share_id = share_stor['ID']
778 # Check if access already exists
779 access_id = self.helper._get_access_from_share(share_id,
780 access_to,
781 share_proto)
782 if access_id:
783 # Check if the access level equal
784 level_exist = self.helper._get_level_by_access_id(access_id,
785 share_proto)
786 if level_exist != access_level: 786 ↛ exitline 786 didn't return from function 'allow_access' because the condition on line 786 was always true
787 # Change the access level
788 self.helper._change_access_rest(access_id,
789 share_proto, access_level)
790 else:
791 # Add this access to share
792 self.helper._allow_access_rest(share_id, access_to,
793 share_proto, access_level)
795 def clear_access(self, share, share_server=None):
796 """Remove all access rules of the share"""
797 share_proto = share['share_proto']
798 share_name = share['name']
799 share_url_type = self.helper._get_share_url_type(share_proto)
800 share_stor = self.helper._get_share_by_name(share_name, share_url_type)
801 if not share_stor:
802 LOG.warning('Cannot get share %s.', share_name)
803 return
804 share_id = share_stor['ID']
805 all_accesses = self.helper._get_all_access_from_share(share_id,
806 share_proto)
807 for access_id in all_accesses:
808 self.helper._remove_access_from_share(access_id,
809 share_proto)
811 def update_access(self, share, access_rules, add_rules,
812 delete_rules, update_rules, share_server=None):
813 """Update access rules list."""
814 if not (add_rules or delete_rules):
815 self.clear_access(share, share_server)
816 for access in access_rules:
817 self.allow_access(share, access, share_server)
818 else:
819 for access in delete_rules:
820 self.deny_access(share, access, share_server)
821 for access in add_rules:
822 self.allow_access(share, access, share_server)
824 def get_pool(self, share):
825 pool_name = share_utils.extract_host(share['host'], level='pool')
826 if pool_name: 826 ↛ 827line 826 didn't jump to line 827 because the condition on line 826 was never true
827 return pool_name
828 share_name = share['name']
829 share_url_type = self.helper._get_share_url_type(share['share_proto'])
830 share = self.helper._get_share_by_name(share_name, share_url_type)
832 pool_name = None
833 if share:
834 pool = self.helper._get_fs_info_by_id(share['FSID'])
835 pool_name = pool['POOLNAME']
837 return pool_name
839 def allocate_container(self, share, poolinfo):
840 """Creates filesystem associated to share by name."""
841 opts = huawei_utils.get_share_extra_specs_params(
842 share['share_type_id'])
844 if opts is None: 844 ↛ 845line 844 didn't jump to line 845 because the condition on line 844 was never true
845 opts = constants.OPTS_CAPABILITIES
846 smart = smartx.SmartX(self.helper)
847 smartx_opts, qos = smart.get_smartx_extra_specs_opts(opts)
849 fileParam = self._init_filesys_para(share, poolinfo, smartx_opts)
850 fsid = self.helper._create_filesystem(fileParam)
852 try:
853 if qos:
854 smart_qos = smartx.SmartQos(self.helper)
855 smart_qos.create_qos(qos, fsid)
857 smartpartition = smartx.SmartPartition(self.helper)
858 smartpartition.add(opts, fsid)
860 smartcache = smartx.SmartCache(self.helper)
861 smartcache.add(opts, fsid)
862 except Exception as err:
863 if fsid is not None: 863 ↛ 868line 863 didn't jump to line 868 because the condition on line 863 was always true
864 qos_id = self.helper.get_qosid_by_fsid(fsid)
865 if qos_id: 865 ↛ 867line 865 didn't jump to line 867 because the condition on line 865 was always true
866 self.remove_qos_fs(fsid, qos_id)
867 self.helper._delete_fs(fsid)
868 message = (_('Failed to add smartx. Reason: %(err)s.')
869 % {'err': err})
870 raise exception.InvalidShare(reason=message)
871 return fsid
873 def manage_existing(self, share, driver_options):
874 """Manage existing share."""
876 share_proto = share['share_proto']
877 share_name = share['name']
878 old_export_location = share['export_locations'][0]['path']
879 pool_name = share_utils.extract_host(share['host'], level='pool')
880 share_url_type = self.helper._get_share_url_type(share_proto)
881 old_share_name = self.helper._get_share_name_by_export_location(
882 old_export_location, share_proto)
884 share_storage = self.helper._get_share_by_name(old_share_name,
885 share_url_type)
886 if not share_storage:
887 err_msg = (_("Can not get share ID by share %s.")
888 % old_export_location)
889 LOG.error(err_msg)
890 raise exception.InvalidShare(reason=err_msg)
892 fs_id = share_storage['FSID']
893 fs = self.helper._get_fs_info_by_id(fs_id)
894 if not self.check_fs_status(fs['HEALTHSTATUS'],
895 fs['RUNNINGSTATUS']):
896 raise exception.InvalidShare(
897 reason=(_('Invalid status of filesystem: '
898 'HEALTHSTATUS=%(health)s '
899 'RUNNINGSTATUS=%(running)s.')
900 % {'health': fs['HEALTHSTATUS'],
901 'running': fs['RUNNINGSTATUS']}))
903 if pool_name and pool_name != fs['POOLNAME']:
904 raise exception.InvalidHost(
905 reason=(_('The current pool(%(fs_pool)s) of filesystem '
906 'does not match the input pool(%(host_pool)s).')
907 % {'fs_pool': fs['POOLNAME'],
908 'host_pool': pool_name}))
910 result = self.helper._find_all_pool_info()
911 poolinfo = self.helper._find_pool_info(pool_name, result)
913 opts = huawei_utils.get_share_extra_specs_params(
914 share['share_type_id'])
915 specs = share_types.get_share_type_extra_specs(share['share_type_id'])
916 if ('capabilities:thin_provisioning' not in specs.keys()
917 and 'thin_provisioning' not in specs.keys()):
918 if fs['ALLOCTYPE'] == constants.ALLOC_TYPE_THIN_FLAG:
919 opts['thin_provisioning'] = constants.THIN_PROVISIONING
920 else:
921 opts['thin_provisioning'] = constants.THICK_PROVISIONING
923 change_opts = self.check_retype_change_opts(opts, poolinfo, fs)
924 LOG.info('Retyping share (%(share)s), changed options are : '
925 '(%(change_opts)s).',
926 {'share': old_share_name, 'change_opts': change_opts})
927 try:
928 self.retype_share(change_opts, fs_id)
929 except Exception as err:
930 message = (_("Retype share error. Share: %(share)s. "
931 "Reason: %(reason)s.")
932 % {'share': old_share_name,
933 'reason': err})
934 raise exception.InvalidShare(reason=message)
936 share_size = int(fs['CAPACITY']) / units.Mi / 2
937 self.helper._change_fs_name(fs_id, share_name)
938 location = self._get_location_path(share_name, share_proto)
939 return (share_size, [location])
941 def _check_snapshot_valid_for_manage(self, snapshot_info):
942 snapshot_name = snapshot_info['data']['NAME']
944 # Check whether the snapshot is normal.
945 if (snapshot_info['data']['HEALTHSTATUS']
946 != constants.STATUS_FSSNAPSHOT_HEALTH):
947 msg = (_("Can't import snapshot %(snapshot)s to Manila. "
948 "Snapshot status is not normal, snapshot status: "
949 "%(status)s.")
950 % {'snapshot': snapshot_name,
951 'status': snapshot_info['data']['HEALTHSTATUS']})
952 raise exception.ManageInvalidShareSnapshot(
953 reason=msg)
955 def manage_existing_snapshot(self, snapshot, driver_options):
956 """Manage existing snapshot."""
958 share_proto = snapshot['share']['share_proto']
959 share_url_type = self.helper._get_share_url_type(share_proto)
960 share_storage = self.helper._get_share_by_name(snapshot['share_name'],
961 share_url_type)
962 if not share_storage:
963 err_msg = (_("Failed to import snapshot %(snapshot)s to Manila. "
964 "Snapshot source share %(share)s doesn't exist "
965 "on array.")
966 % {'snapshot': snapshot['provider_location'],
967 'share': snapshot['share_name']})
968 raise exception.InvalidShare(reason=err_msg)
969 sharefsid = share_storage['FSID']
971 provider_location = snapshot.get('provider_location')
972 snapshot_id = sharefsid + "@" + provider_location
973 snapshot_info = self.helper._get_snapshot_by_id(snapshot_id)
974 snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_info)
975 if not snapshot_flag:
976 err_msg = (_("Cannot find snapshot %s on array.")
977 % snapshot['provider_location'])
978 raise exception.ManageInvalidShareSnapshot(reason=err_msg)
979 else:
980 self._check_snapshot_valid_for_manage(snapshot_info)
981 snapshot_name = ("share_snapshot_"
982 + snapshot['id'].replace("-", "_"))
983 self.helper._rename_share_snapshot(snapshot_id, snapshot_name)
984 return snapshot_name
986 def check_retype_change_opts(self, opts, poolinfo, fs):
987 change_opts = {
988 "partitionid": None,
989 "cacheid": None,
990 "dedupe&compression": None,
991 }
993 # SmartPartition
994 old_partition_id = fs['SMARTPARTITIONID']
995 old_partition_name = None
996 new_partition_id = None
997 new_partition_name = None
998 if strutils.bool_from_string(opts['huawei_smartpartition']):
999 if not opts['partitionname']:
1000 raise exception.InvalidInput(
1001 reason=_('Partition name is None, please set '
1002 'huawei_smartpartition:partitionname in key.'))
1003 new_partition_name = opts['partitionname']
1004 new_partition_id = self.helper._get_partition_id_by_name(
1005 new_partition_name)
1006 if new_partition_id is None:
1007 raise exception.InvalidInput(
1008 reason=(_("Can't find partition name on the array, "
1009 "partition name is: %(name)s.")
1010 % {"name": new_partition_name}))
1012 if old_partition_id != new_partition_id: 1012 ↛ 1023line 1012 didn't jump to line 1023 because the condition on line 1012 was always true
1013 if old_partition_id:
1014 partition_info = self.helper.get_partition_info_by_id(
1015 old_partition_id)
1016 old_partition_name = partition_info['NAME']
1017 change_opts["partitionid"] = ([old_partition_id,
1018 old_partition_name],
1019 [new_partition_id,
1020 new_partition_name])
1022 # SmartCache
1023 old_cache_id = fs['SMARTCACHEID']
1024 old_cache_name = None
1025 new_cache_id = None
1026 new_cache_name = None
1027 if strutils.bool_from_string(opts['huawei_smartcache']):
1028 if not opts['cachename']: 1028 ↛ 1029line 1028 didn't jump to line 1029 because the condition on line 1028 was never true
1029 raise exception.InvalidInput(
1030 reason=_('Cache name is None, please set '
1031 'huawei_smartcache:cachename in key.'))
1032 new_cache_name = opts['cachename']
1033 new_cache_id = self.helper._get_cache_id_by_name(
1034 new_cache_name)
1035 if new_cache_id is None:
1036 raise exception.InvalidInput(
1037 reason=(_("Can't find cache name on the array, "
1038 "cache name is: %(name)s.")
1039 % {"name": new_cache_name}))
1041 if old_cache_id != new_cache_id: 1041 ↛ 1050line 1041 didn't jump to line 1050 because the condition on line 1041 was always true
1042 if old_cache_id:
1043 cache_info = self.helper.get_cache_info_by_id(
1044 old_cache_id)
1045 old_cache_name = cache_info['NAME']
1046 change_opts["cacheid"] = ([old_cache_id, old_cache_name],
1047 [new_cache_id, new_cache_name])
1049 # SmartDedupe&SmartCompression
1050 smartx_opts = constants.OPTS_CAPABILITIES
1051 if opts is not None: 1051 ↛ 1055line 1051 didn't jump to line 1055 because the condition on line 1051 was always true
1052 smart = smartx.SmartX(self.helper)
1053 smartx_opts, qos = smart.get_smartx_extra_specs_opts(opts)
1055 old_compression = fs['COMPRESSION']
1056 new_compression = smartx_opts['compression']
1057 old_dedupe = fs['DEDUP']
1058 new_dedupe = smartx_opts['dedupe']
1060 if fs['ALLOCTYPE'] == constants.ALLOC_TYPE_THIN_FLAG:
1061 fs['ALLOCTYPE'] = constants.ALLOC_TYPE_THIN
1062 else:
1063 fs['ALLOCTYPE'] = constants.ALLOC_TYPE_THICK
1065 if strutils.bool_from_string(opts['thin_provisioning']):
1066 opts['thin_provisioning'] = constants.ALLOC_TYPE_THIN
1067 else:
1068 opts['thin_provisioning'] = constants.ALLOC_TYPE_THICK
1070 if fs['ALLOCTYPE'] != opts['thin_provisioning']: 1070 ↛ 1071line 1070 didn't jump to line 1071 because the condition on line 1070 was never true
1071 msg = (_("Manage existing share "
1072 "fs type and new_share_type mismatch. "
1073 "fs type is: %(fs_type)s, "
1074 "new_share_type is: %(new_share_type)s")
1075 % {"fs_type": fs['ALLOCTYPE'],
1076 "new_share_type": opts['thin_provisioning']})
1077 raise exception.InvalidHost(reason=msg)
1078 else:
1079 if fs['ALLOCTYPE'] == constants.ALLOC_TYPE_THICK:
1080 if new_compression or new_dedupe:
1081 raise exception.InvalidInput(
1082 reason=_("Dedupe or compression cannot be set for "
1083 "thick filesystem."))
1084 else:
1085 if (old_dedupe != new_dedupe 1085 ↛ 1091line 1085 didn't jump to line 1091 because the condition on line 1085 was always true
1086 or old_compression != new_compression):
1087 change_opts["dedupe&compression"] = ([old_dedupe,
1088 old_compression],
1089 [new_dedupe,
1090 new_compression])
1091 return change_opts
1093 def retype_share(self, change_opts, fs_id):
1094 if change_opts.get('partitionid'): 1094 ↛ 1114line 1094 didn't jump to line 1114 because the condition on line 1094 was always true
1095 old, new = change_opts['partitionid']
1096 old_id = old[0]
1097 old_name = old[1]
1098 new_id = new[0]
1099 new_name = new[1]
1101 if old_id:
1102 self.helper._remove_fs_from_partition(fs_id, old_id)
1103 if new_id:
1104 self.helper._add_fs_to_partition(fs_id, new_id)
1105 msg = (_("Retype FS(id: %(fs_id)s) smartpartition from "
1106 "(name: %(old_name)s, id: %(old_id)s) to "
1107 "(name: %(new_name)s, id: %(new_id)s) "
1108 "performed successfully.")
1109 % {"fs_id": fs_id,
1110 "old_id": old_id, "old_name": old_name,
1111 "new_id": new_id, "new_name": new_name})
1112 LOG.info(msg)
1114 if change_opts.get('cacheid'): 1114 ↛ 1133line 1114 didn't jump to line 1133 because the condition on line 1114 was always true
1115 old, new = change_opts['cacheid']
1116 old_id = old[0]
1117 old_name = old[1]
1118 new_id = new[0]
1119 new_name = new[1]
1120 if old_id:
1121 self.helper._remove_fs_from_cache(fs_id, old_id)
1122 if new_id:
1123 self.helper._add_fs_to_cache(fs_id, new_id)
1124 msg = (_("Retype FS(id: %(fs_id)s) smartcache from "
1125 "(name: %(old_name)s, id: %(old_id)s) to "
1126 "(name: %(new_name)s, id: %(new_id)s) "
1127 "performed successfully.")
1128 % {"fs_id": fs_id,
1129 "old_id": old_id, "old_name": old_name,
1130 "new_id": new_id, "new_name": new_name})
1131 LOG.info(msg)
1133 if change_opts.get('dedupe&compression'):
1134 old, new = change_opts['dedupe&compression']
1135 old_dedupe = old[0]
1136 old_compression = old[1]
1137 new_dedupe = new[0]
1138 new_compression = new[1]
1139 if ((old_dedupe != new_dedupe) 1139 ↛ exitline 1139 didn't return from function 'retype_share' because the condition on line 1139 was always true
1140 or (old_compression != new_compression)):
1142 new_smartx_opts = {"dedupe": new_dedupe,
1143 "compression": new_compression}
1145 self.helper._change_extra_specs(fs_id, new_smartx_opts)
1146 msg = (_("Retype FS(id: %(fs_id)s) dedupe from %(old_dedupe)s "
1147 "to %(new_dedupe)s performed successfully, "
1148 "compression from "
1149 "%(old_compression)s to %(new_compression)s "
1150 "performed successfully.")
1151 % {"fs_id": fs_id,
1152 "old_dedupe": old_dedupe,
1153 "new_dedupe": new_dedupe,
1154 "old_compression": old_compression,
1155 "new_compression": new_compression})
1156 LOG.info(msg)
1158 def remove_qos_fs(self, fs_id, qos_id):
1159 fs_list = self.helper.get_fs_list_in_qos(qos_id)
1160 fs_count = len(fs_list)
1161 if fs_count <= 1: 1161 ↛ 1165line 1161 didn't jump to line 1165 because the condition on line 1161 was always true
1162 qos = smartx.SmartQos(self.helper)
1163 qos.delete_qos(qos_id)
1164 else:
1165 self.helper.remove_fs_from_qos(fs_id,
1166 fs_list,
1167 qos_id)
1169 def _get_location_path(self, share_name, share_proto, ip=None):
1170 location = None
1171 if ip is None:
1172 root = self.helper._read_xml()
1173 ip = root.findtext('Storage/LogicalPortIP').strip()
1174 if share_proto == 'NFS':
1175 location = '%s:/%s' % (ip, share_name.replace("-", "_"))
1176 elif share_proto == 'CIFS':
1177 location = '\\\\%s\\%s' % (ip, share_name.replace("-", "_"))
1178 else:
1179 raise exception.InvalidShareAccess(
1180 reason=(_('Invalid NAS protocol supplied: %s.')
1181 % share_proto))
1183 return location
1185 def _get_share_proto(self, share_name):
1186 share_proto = None
1187 for proto in ('NFS', 'CIFS'): 1187 ↛ 1193line 1187 didn't jump to line 1193 because the loop on line 1187 didn't complete
1188 share_url_type = self.helper._get_share_url_type(proto)
1189 share = self.helper._get_share_by_name(share_name, share_url_type)
1190 if share: 1190 ↛ 1187line 1190 didn't jump to line 1187 because the condition on line 1190 was always true
1191 share_proto = proto
1192 break
1193 return share_proto
1195 def _get_wait_interval(self):
1196 """Get wait interval from huawei conf file."""
1197 root = self.helper._read_xml()
1198 wait_interval = root.findtext('Filesystem/WaitInterval')
1199 if wait_interval:
1200 return int(wait_interval)
1201 else:
1202 LOG.info(
1203 "Wait interval is not configured in huawei "
1204 "conf file. Use default: %(default_wait_interval)d.",
1205 {"default_wait_interval": constants.DEFAULT_WAIT_INTERVAL})
1206 return constants.DEFAULT_WAIT_INTERVAL
1208 def _get_timeout(self):
1209 """Get timeout from huawei conf file."""
1210 root = self.helper._read_xml()
1211 timeout = root.findtext('Filesystem/Timeout')
1212 if timeout:
1213 return int(timeout)
1214 else:
1215 LOG.info(
1216 "Timeout is not configured in huawei conf file. "
1217 "Use default: %(default_timeout)d.",
1218 {"default_timeout": constants.DEFAULT_TIMEOUT})
1219 return constants.DEFAULT_TIMEOUT
1221 def check_conf_file(self):
1222 """Check the config file, make sure the essential items are set."""
1223 root = self.helper._read_xml()
1224 resturl = root.findtext('Storage/RestURL')
1225 username = root.findtext('Storage/UserName')
1226 pwd = root.findtext('Storage/UserPassword')
1227 product = root.findtext('Storage/Product')
1228 pool_node = root.findtext('Filesystem/StoragePool')
1229 logical_port_ip = root.findtext('Storage/LogicalPortIP')
1231 if product != "V3":
1232 err_msg = (_(
1233 'check_conf_file: Config file invalid. '
1234 'Product must be set to V3.'))
1235 LOG.error(err_msg)
1236 raise exception.InvalidInput(err_msg)
1238 if not (resturl and username and pwd):
1239 err_msg = (_(
1240 'check_conf_file: Config file invalid. RestURL,'
1241 ' UserName and UserPassword must be set.'))
1242 LOG.error(err_msg)
1243 raise exception.InvalidInput(err_msg)
1245 if not pool_node:
1246 err_msg = (_(
1247 'check_conf_file: Config file invalid. '
1248 'StoragePool must be set.'))
1249 LOG.error(err_msg)
1250 raise exception.InvalidInput(err_msg)
1252 if not (self.configuration.driver_handles_share_servers
1253 or logical_port_ip):
1254 err_msg = (_(
1255 'check_conf_file: Config file invalid. LogicalPortIP '
1256 'must be set when driver_handles_share_servers is False.'))
1257 LOG.error(err_msg)
1258 raise exception.InvalidInput(reason=err_msg)
1260 if self.snapshot_support and self.replication_support:
1261 err_msg = _('Config file invalid. SnapshotSupport and '
1262 'ReplicationSupport can not both be set to True.')
1263 LOG.error(err_msg)
1264 raise exception.BadConfigurationException(reason=err_msg)
1266 def check_service(self):
1267 running_status = self.helper._get_cifs_service_status()
1268 if running_status != constants.STATUS_SERVICE_RUNNING:
1269 self.helper._start_cifs_service_status()
1271 service = self.helper._get_nfs_service_status()
1272 if ((service['RUNNINGSTATUS'] != constants.STATUS_SERVICE_RUNNING) or
1273 (service['SUPPORTV3'] == 'false') or
1274 (service['SUPPORTV4'] == 'false')):
1275 self.helper._start_nfs_service_status()
1277 def setup_server(self, network_info, metadata=None):
1278 """Set up share server with given network parameters."""
1279 self._check_network_type_validate(network_info['network_type'])
1281 vlan_tag = network_info['segmentation_id'] or 0
1282 ip = network_info['network_allocations'][0]['ip_address']
1283 subnet = utils.cidr_to_netmask(network_info['cidr'])
1284 if not utils.is_valid_ip_address(ip, '4'):
1285 err_msg = (_(
1286 "IP (%s) is invalid. Only IPv4 addresses are supported.") % ip)
1287 LOG.error(err_msg)
1288 raise exception.InvalidInput(reason=err_msg)
1290 ad_created = False
1291 ldap_created = False
1292 try:
1293 if network_info.get('security_services'):
1294 active_directory, ldap = self._get_valid_security_service(
1295 network_info.get('security_services'))
1297 # Configure AD or LDAP Domain.
1298 if active_directory:
1299 self._configure_AD_domain(active_directory)
1300 ad_created = True
1301 if ldap:
1302 self._configure_LDAP_domain(ldap)
1303 ldap_created = True
1305 # Create vlan and logical_port.
1306 vlan_id, logical_port_id = (
1307 self._create_vlan_and_logical_port(vlan_tag, ip, subnet))
1308 except exception.ManilaException:
1309 if ad_created:
1310 dns_ip_list = []
1311 user = active_directory['user']
1312 password = active_directory['password']
1313 self.helper.set_DNS_ip_address(dns_ip_list)
1314 self.helper.delete_AD_config(user, password)
1315 self._check_AD_expected_status(constants.STATUS_EXIT_DOMAIN)
1316 if ldap_created:
1317 self.helper.delete_LDAP_config()
1318 raise
1320 return {
1321 'share_server_name': network_info['server_id'],
1322 'share_server_id': network_info['server_id'],
1323 'vlan_id': vlan_id,
1324 'logical_port_id': logical_port_id,
1325 'ip': ip,
1326 'subnet': subnet,
1327 'vlan_tag': vlan_tag,
1328 'ad_created': ad_created,
1329 'ldap_created': ldap_created,
1330 }
1332 def _check_network_type_validate(self, network_type):
1333 if network_type not in ('flat', 'vlan', None):
1334 err_msg = (_(
1335 'Invalid network type. Network type must be flat or vlan.'))
1336 raise exception.NetworkBadConfigurationException(reason=err_msg)
1338 def _get_valid_security_service(self, security_services):
1339 """Validate security services and return AD/LDAP config."""
1340 service_number = len(security_services)
1341 err_msg = _("Unsupported security services. "
1342 "Only AD and LDAP are supported.")
1343 if service_number > 2:
1344 LOG.error(err_msg)
1345 raise exception.InvalidInput(reason=err_msg)
1347 active_directory = None
1348 ldap = None
1349 for ss in security_services:
1350 if ss['type'] == 'active_directory':
1351 active_directory = ss
1352 elif ss['type'] == 'ldap':
1353 ldap = ss
1354 else:
1355 LOG.error(err_msg)
1356 raise exception.InvalidInput(reason=err_msg)
1358 return active_directory, ldap
1360 def _configure_AD_domain(self, active_directory):
1361 dns_ip = active_directory['dns_ip']
1362 user = active_directory['user']
1363 password = active_directory['password']
1364 domain = active_directory['domain']
1365 if not (dns_ip and user and password and domain):
1366 raise exception.InvalidInput(
1367 reason=_("dns_ip or user or password or domain "
1368 "in security_services is None."))
1370 # Check DNS server exists or not.
1371 ip_address = self.helper.get_DNS_ip_address()
1372 if ip_address and ip_address[0]:
1373 err_msg = (_("DNS server (%s) has already been configured.")
1374 % ip_address[0])
1375 LOG.error(err_msg)
1376 raise exception.InvalidInput(reason=err_msg)
1378 # Check AD config exists or not.
1379 ad_exists, AD_domain = self.helper.get_AD_domain_name()
1380 if ad_exists:
1381 err_msg = (_("AD domain (%s) has already been configured.")
1382 % AD_domain)
1383 LOG.error(err_msg)
1384 raise exception.InvalidInput(reason=err_msg)
1386 # Set DNS server ip.
1387 dns_ip_list = dns_ip.split(",")
1388 DNS_config = self.helper.set_DNS_ip_address(dns_ip_list)
1390 # Set AD config.
1391 digits = string.digits
1392 random_id = ''.join([random.choice(digits) for i in range(9)])
1393 system_name = constants.SYSTEM_NAME_PREFIX + random_id
1395 try:
1396 self.helper.add_AD_config(user, password, domain, system_name)
1397 self._check_AD_expected_status(constants.STATUS_JOIN_DOMAIN)
1398 except exception.ManilaException as err:
1399 if DNS_config: 1399 ↛ 1402line 1399 didn't jump to line 1402 because the condition on line 1399 was always true
1400 dns_ip_list = []
1401 self.helper.set_DNS_ip_address(dns_ip_list)
1402 raise exception.InvalidShare(
1403 reason=(_('Failed to add AD config. '
1404 'Reason: %s.') % err))
1406 def _check_AD_expected_status(self, expected_status):
1407 wait_interval = self._get_wait_interval()
1408 timeout = self._get_timeout()
1409 retries = timeout / wait_interval
1410 interval = wait_interval
1411 backoff_rate = 1
1413 @utils.retry(retry_param=exception.InvalidShare,
1414 interval=interval,
1415 retries=retries,
1416 backoff_rate=backoff_rate)
1417 def _check_AD_status():
1418 ad = self.helper.get_AD_config()
1419 if ad['DOMAINSTATUS'] != expected_status:
1420 raise exception.InvalidShare(
1421 reason=(_('AD domain (%s) status is not expected.')
1422 % ad['FULLDOMAINNAME']))
1424 _check_AD_status()
1426 def _configure_LDAP_domain(self, ldap):
1427 server = ldap['server']
1428 domain = ldap['domain']
1429 if not server or not domain:
1430 raise exception.InvalidInput(reason=_("Server or domain is None."))
1432 # Check LDAP config exists or not.
1433 ldap_exists, LDAP_domain = self.helper.get_LDAP_domain_server()
1434 if ldap_exists:
1435 err_msg = (_("LDAP domain (%s) has already been configured.")
1436 % LDAP_domain)
1437 LOG.error(err_msg)
1438 raise exception.InvalidInput(reason=err_msg)
1440 # Set LDAP config.
1441 server_number = len(server.split(','))
1442 if server_number == 1:
1443 server = server + ",,"
1444 elif server_number == 2:
1445 server = server + ","
1446 elif server_number > 3:
1447 raise exception.InvalidInput(
1448 reason=_("Cannot support more than three LDAP servers."))
1450 self.helper.add_LDAP_config(server, domain)
1452 def _create_vlan_and_logical_port(self, vlan_tag, ip, subnet):
1453 optimal_port, port_type = self._get_optimal_port(vlan_tag)
1454 port_id = self.helper.get_port_id(optimal_port, port_type)
1455 home_port_id = port_id
1456 home_port_type = port_type
1457 vlan_id = 0
1458 vlan_exists = True
1460 if port_type is None or port_id is None:
1461 err_msg = _("No appropriate port found to create logical port.")
1462 LOG.error(err_msg)
1463 raise exception.InvalidInput(reason=err_msg)
1464 if vlan_tag:
1465 vlan_exists, vlan_id = self.helper.get_vlan(port_id, vlan_tag)
1466 if not vlan_exists:
1467 # Create vlan.
1468 vlan_id = self.helper.create_vlan(
1469 port_id, port_type, vlan_tag)
1470 home_port_id = vlan_id
1471 home_port_type = constants.PORT_TYPE_VLAN
1473 logical_port_exists, logical_port_id = (
1474 self.helper.get_logical_port(home_port_id, ip, subnet))
1475 if not logical_port_exists:
1476 try:
1477 # Create logical port.
1478 logical_port_id = (
1479 self.helper.create_logical_port(
1480 home_port_id, home_port_type, ip, subnet))
1481 except exception.ManilaException as err:
1482 if not vlan_exists: 1482 ↛ 1484line 1482 didn't jump to line 1484 because the condition on line 1482 was always true
1483 self.helper.delete_vlan(vlan_id)
1484 raise exception.InvalidShare(
1485 reason=(_('Failed to create logical port. '
1486 'Reason: %s.') % err))
1488 return vlan_id, logical_port_id
1490 def _get_optimal_port(self, vlan_tag):
1491 """Get an optimal physical port or bond port."""
1492 root = self.helper._read_xml()
1493 port_info = []
1494 port_list = root.findtext('Storage/Port')
1495 if port_list:
1496 port_list = port_list.split(";")
1497 for port in port_list:
1498 port = port.strip().strip('\n')
1499 if port: 1499 ↛ 1497line 1499 didn't jump to line 1497 because the condition on line 1499 was always true
1500 port_info.append(port)
1502 eth_port, bond_port = self._get_online_port(port_info)
1503 if vlan_tag:
1504 optimal_port, port_type = (
1505 self._get_least_port(eth_port, bond_port,
1506 sort_type=constants.SORT_BY_VLAN))
1507 else:
1508 optimal_port, port_type = (
1509 self._get_least_port(eth_port, bond_port,
1510 sort_type=constants.SORT_BY_LOGICAL))
1512 if not optimal_port:
1513 err_msg = (_("Cannot find optimal port. port_info: %s.")
1514 % port_info)
1515 LOG.error(err_msg)
1516 raise exception.InvalidInput(reason=err_msg)
1518 return optimal_port, port_type
1520 def _get_online_port(self, all_port_list):
1521 eth_port = self.helper.get_all_eth_port()
1522 bond_port = self.helper.get_all_bond_port()
1524 eth_status = constants.STATUS_ETH_RUNNING
1525 online_eth_port = []
1526 for eth in eth_port:
1527 if (eth_status == eth['RUNNINGSTATUS']
1528 and not eth['IPV4ADDR'] and not eth['BONDNAME']):
1529 online_eth_port.append(eth['LOCATION'])
1531 online_bond_port = []
1532 for bond in bond_port:
1533 if eth_status == bond['RUNNINGSTATUS']: 1533 ↛ 1532line 1533 didn't jump to line 1532 because the condition on line 1533 was always true
1534 port_id = jsonutils.loads(bond['PORTIDLIST'])
1535 bond_eth_port = self.helper.get_eth_port_by_id(port_id[0])
1536 if bond_eth_port and not bond_eth_port['IPV4ADDR']: 1536 ↛ 1532line 1536 didn't jump to line 1532 because the condition on line 1536 was always true
1537 online_bond_port.append(bond['NAME'])
1539 filtered_eth_port = []
1540 filtered_bond_port = []
1541 if len(all_port_list) == 0:
1542 filtered_eth_port = online_eth_port
1543 filtered_bond_port = online_bond_port
1544 else:
1545 all_port_list = list(set(all_port_list))
1546 for port in all_port_list:
1547 is_eth_port = False
1548 for eth in online_eth_port:
1549 if port == eth:
1550 filtered_eth_port.append(port)
1551 is_eth_port = True
1552 break
1553 if is_eth_port:
1554 continue
1555 for bond in online_bond_port: 1555 ↛ 1546line 1555 didn't jump to line 1546 because the loop on line 1555 didn't complete
1556 if port == bond: 1556 ↛ 1555line 1556 didn't jump to line 1555 because the condition on line 1556 was always true
1557 filtered_bond_port.append(port)
1558 break
1560 return filtered_eth_port, filtered_bond_port
1562 def _get_least_port(self, eth_port, bond_port, sort_type):
1563 sorted_eth = []
1564 sorted_bond = []
1566 if sort_type == constants.SORT_BY_VLAN:
1567 _get_sorted_least_port = self._get_sorted_least_port_by_vlan
1568 else:
1569 _get_sorted_least_port = self._get_sorted_least_port_by_logical
1571 if eth_port:
1572 sorted_eth = _get_sorted_least_port(eth_port)
1573 if bond_port:
1574 sorted_bond = _get_sorted_least_port(bond_port)
1576 if sorted_eth and sorted_bond:
1577 if sorted_eth[1] >= sorted_bond[1]:
1578 return sorted_bond[0], constants.PORT_TYPE_BOND
1579 else:
1580 return sorted_eth[0], constants.PORT_TYPE_ETH
1581 elif sorted_eth:
1582 return sorted_eth[0], constants.PORT_TYPE_ETH
1583 elif sorted_bond:
1584 return sorted_bond[0], constants.PORT_TYPE_BOND
1585 else:
1586 return None, None
1588 def _get_sorted_least_port_by_vlan(self, port_list):
1589 if not port_list: 1589 ↛ 1590line 1589 didn't jump to line 1590 because the condition on line 1589 was never true
1590 return None
1592 vlan_list = self.helper.get_all_vlan()
1593 count = {}
1594 for item in port_list:
1595 count[item] = 0
1597 for item in port_list:
1598 for vlan in vlan_list:
1599 pos = vlan['NAME'].rfind('.')
1600 if vlan['NAME'][:pos] == item:
1601 count[item] += 1
1603 sort_port = sorted(count.items(), key=lambda count: count[1])
1605 return sort_port[0]
1607 def _get_sorted_least_port_by_logical(self, port_list):
1608 if not port_list: 1608 ↛ 1609line 1608 didn't jump to line 1609 because the condition on line 1608 was never true
1609 return None
1611 logical_list = self.helper.get_all_logical_port()
1612 count = {}
1613 for item in port_list:
1614 count[item] = 0
1615 for logical in logical_list:
1616 if logical['HOMEPORTTYPE'] == constants.PORT_TYPE_VLAN:
1617 pos = logical['HOMEPORTNAME'].rfind('.')
1618 if logical['HOMEPORTNAME'][:pos] == item:
1619 count[item] += 1
1620 else:
1621 if logical['HOMEPORTNAME'] == item:
1622 count[item] += 1
1624 sort_port = sorted(count.items(), key=lambda count: count[1])
1626 return sort_port[0]
1628 def teardown_server(self, server_details, security_services=None):
1629 if not server_details:
1630 LOG.debug('Server details are empty.')
1631 return
1633 logical_port_id = server_details.get('logical_port_id')
1634 vlan_id = server_details.get('vlan_id')
1635 ad_created = server_details.get('ad_created')
1636 ldap_created = server_details.get('ldap_created')
1638 # Delete logical_port.
1639 if logical_port_id: 1639 ↛ 1646line 1639 didn't jump to line 1646 because the condition on line 1639 was always true
1640 logical_port_exists = (
1641 self.helper.check_logical_port_exists_by_id(logical_port_id))
1642 if logical_port_exists:
1643 self.helper.delete_logical_port(logical_port_id)
1645 # Delete vlan.
1646 if vlan_id and vlan_id != '0': 1646 ↛ 1651line 1646 didn't jump to line 1651 because the condition on line 1646 was always true
1647 vlan_exists = self.helper.check_vlan_exists_by_id(vlan_id)
1648 if vlan_exists:
1649 self.helper.delete_vlan(vlan_id)
1651 if security_services:
1652 active_directory, ldap = (
1653 self._get_valid_security_service(security_services))
1655 if ad_created and ad_created == '1' and active_directory: 1655 ↛ 1674line 1655 didn't jump to line 1674 because the condition on line 1655 was always true
1656 dns_ip = active_directory['dns_ip']
1657 user = active_directory['user']
1658 password = active_directory['password']
1659 domain = active_directory['domain']
1661 # Check DNS server exists or not.
1662 ip_address = self.helper.get_DNS_ip_address()
1663 if ip_address and ip_address[0] == dns_ip:
1664 dns_ip_list = []
1665 self.helper.set_DNS_ip_address(dns_ip_list)
1667 # Check AD config exists or not.
1668 ad_exists, AD_domain = self.helper.get_AD_domain_name()
1669 if ad_exists and AD_domain == domain:
1670 self.helper.delete_AD_config(user, password)
1671 self._check_AD_expected_status(
1672 constants.STATUS_EXIT_DOMAIN)
1674 if ldap_created and ldap_created == '1' and ldap: 1674 ↛ exitline 1674 didn't return from function 'teardown_server' because the condition on line 1674 was always true
1675 server = ldap['server']
1676 domain = ldap['domain']
1678 # Check LDAP config exists or not.
1679 ldap_exists, LDAP_domain = (
1680 self.helper.get_LDAP_domain_server())
1681 if ldap_exists:
1682 LDAP_config = self.helper.get_LDAP_config()
1683 if (LDAP_config['LDAPSERVER'] == server 1683 ↛ exitline 1683 didn't return from function 'teardown_server' because the condition on line 1683 was always true
1684 and LDAP_config['BASEDN'] == domain):
1685 self.helper.delete_LDAP_config()
1687 def ensure_share(self, share, share_server=None):
1688 """Ensure that share is exported."""
1689 share_proto = share['share_proto']
1690 share_name = share['name']
1691 share_id = share['id']
1692 share_url_type = self.helper._get_share_url_type(share_proto)
1694 share_storage = self.helper._get_share_by_name(share_name,
1695 share_url_type)
1696 if not share_storage:
1697 raise exception.ShareResourceNotFound(share_id=share_id)
1699 fs_id = share_storage['FSID']
1700 self.assert_filesystem(fs_id)
1702 ip = self._get_share_ip(share_server)
1703 location = self._get_location_path(share_name, share_proto, ip)
1704 return [location]
1706 def create_replica(self, context, replica_list, new_replica,
1707 access_rules, replica_snapshots, share_server=None):
1708 """Create a new share, and create a remote replication pair."""
1710 active_replica = share_utils.get_active_replica(replica_list)
1712 if (self.private_storage.get(active_replica['share_id'],
1713 'replica_pair_id')):
1714 # for huawei array, only one replication can be created for
1715 # each active replica, so if a replica pair id is recorded for
1716 # this share, it means active replica already has a replication,
1717 # can not create anymore.
1718 msg = _('Cannot create more than one replica for share %s.')
1719 LOG.error(msg, active_replica['share_id'])
1720 raise exception.ReplicationException(
1721 reason=msg % active_replica['share_id'])
1723 # Create a new share
1724 new_share_name = new_replica['name']
1725 location = self.create_share(new_replica, share_server)
1727 # create a replication pair.
1728 # replication pair only can be created by master node,
1729 # so here is a remote call to trigger master node to
1730 # start the creating progress.
1731 try:
1732 replica_pair_id = self.rpc_client.create_replica_pair(
1733 context,
1734 active_replica['host'],
1735 local_share_info=active_replica,
1736 remote_device_wwn=self.helper.get_array_wwn(),
1737 remote_fs_id=self.helper.get_fsid_by_name(new_share_name)
1738 )
1739 except Exception:
1740 LOG.exception('Failed to create a replication pair '
1741 'with host %s.',
1742 active_replica['host'])
1743 raise
1745 self.private_storage.update(new_replica['share_id'],
1746 {'replica_pair_id': replica_pair_id})
1748 # Get the state of the new created replica
1749 replica_state = self.replica_mgr.get_replica_state(replica_pair_id)
1750 replica_ref = {
1751 'export_locations': [location],
1752 'replica_state': replica_state,
1753 'access_rules_status': common_constants.STATUS_ACTIVE,
1754 }
1756 return replica_ref
1758 def update_replica_state(self, context, replica_list, replica,
1759 access_rules, replica_snapshots,
1760 share_server=None):
1761 replica_pair_id = self.private_storage.get(replica['share_id'],
1762 'replica_pair_id')
1763 if replica_pair_id is None:
1764 msg = ("No replication pair ID recorded for share %s.")
1765 LOG.error(msg, replica['share_id'])
1766 return common_constants.STATUS_ERROR
1768 self.replica_mgr.update_replication_pair_state(replica_pair_id)
1769 return self.replica_mgr.get_replica_state(replica_pair_id)
1771 def promote_replica(self, context, replica_list, replica, access_rules,
1772 share_server=None, quiesce_wait_time=None):
1773 replica_pair_id = self.private_storage.get(replica['share_id'],
1774 'replica_pair_id')
1775 if replica_pair_id is None:
1776 msg = _("No replication pair ID recorded for share %s.")
1777 LOG.error(msg, replica['share_id'])
1778 raise exception.ReplicationException(
1779 reason=msg % replica['share_id'])
1781 try:
1782 self.replica_mgr.switch_over(replica_pair_id)
1783 except Exception:
1784 LOG.exception('Failed to promote replica %s.',
1785 replica['id'])
1786 raise
1788 updated_new_active_access = True
1789 cleared_old_active_access = True
1791 try:
1792 self.update_access(replica, access_rules,
1793 [], [], [], share_server)
1794 except Exception:
1795 LOG.warning('Failed to set access rules to '
1796 'new active replica %s.',
1797 replica['id'])
1798 updated_new_active_access = False
1800 old_active_replica = share_utils.get_active_replica(replica_list)
1802 try:
1803 self.clear_access(old_active_replica, share_server)
1804 except Exception:
1805 LOG.warning("Failed to clear access rules from "
1806 "old active replica %s.",
1807 old_active_replica['id'])
1808 cleared_old_active_access = False
1810 new_active_update = {
1811 'id': replica['id'],
1812 'replica_state': common_constants.REPLICA_STATE_ACTIVE,
1813 }
1814 new_active_update['access_rules_status'] = (
1815 common_constants.STATUS_ACTIVE
1816 if updated_new_active_access
1817 else common_constants.SHARE_INSTANCE_RULES_SYNCING)
1819 # get replica state for new secondary after switch over
1820 replica_state = self.replica_mgr.get_replica_state(replica_pair_id)
1822 old_active_update = {
1823 'id': old_active_replica['id'],
1824 'replica_state': replica_state,
1825 }
1826 old_active_update['access_rules_status'] = (
1827 common_constants.SHARE_INSTANCE_RULES_SYNCING
1828 if cleared_old_active_access
1829 else common_constants.STATUS_ACTIVE)
1831 return [new_active_update, old_active_update]
1833 def delete_replica(self, context, replica_list, replica_snapshots,
1834 replica, share_server=None):
1835 replica_pair_id = self.private_storage.get(replica['share_id'],
1836 'replica_pair_id')
1837 if replica_pair_id is None:
1838 msg = ("No replication pair ID recorded for share %(share)s. "
1839 "Continue to delete replica %(replica)s.")
1840 LOG.warning(msg, {'share': replica['share_id'],
1841 'replica': replica['id']})
1842 else:
1843 self.replica_mgr.delete_replication_pair(replica_pair_id)
1844 self.private_storage.delete(replica['share_id'])
1846 try:
1847 self.delete_share(replica, share_server)
1848 except Exception:
1849 LOG.exception('Failed to delete replica %s.',
1850 replica['id'])
1851 raise
1853 def revert_to_snapshot(self, context, snapshot, share_access_rules,
1854 snapshot_access_rules, share_server):
1855 fs_id = self.helper.get_fsid_by_name(snapshot['share_name'])
1856 if not fs_id:
1857 msg = _("The source filesystem of snapshot %s "
1858 "not exist.") % snapshot['id']
1859 LOG.error(msg)
1860 raise exception.ShareResourceNotFound(
1861 share_id=snapshot['share_id'])
1863 snapshot_id = self.helper._get_snapshot_id(fs_id, snapshot['id'])
1864 self.helper.rollback_snapshot(snapshot_id)