Coverage for manila/share/manager.py: 86%
2924 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:39 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:39 +0000
1# Copyright (c) 2014 NetApp Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""NAS share manager managers creating shares and access rights.
17**Related Flags**
19:share_driver: Used by :class:`ShareManager`.
20"""
22import copy
23import datetime
24import functools
25import hashlib
26import json
27from operator import xor
29from keystoneauth1 import loading as ks_loading
30from oslo_config import cfg
31from oslo_log import log
32from oslo_serialization import jsonutils
33from oslo_service import periodic_task
34from oslo_utils import excutils
35from oslo_utils import importutils
36from oslo_utils import timeutils
38from manila.common import constants
39from manila import context
40from manila import coordination
41from manila.data import rpcapi as data_rpcapi
42from manila import exception
43from manila.i18n import _
44from manila.keymgr import barbican as barbican_api
45from manila import manager
46from manila.message import api as message_api
47from manila.message import message_field
48from manila import quota
49from manila.share import access
50from manila.share import api
51from manila.share import configuration
52from manila.share import drivers_private_data
53from manila.share import migration
54from manila.share import rpcapi as share_rpcapi
55from manila.share import share_types
56from manila.share import snapshot_access
57from manila.share import utils as share_utils
58from manila.transfer import api as transfer_api
59from manila import utils
61profiler = importutils.try_import('osprofiler.profiler')
63LOG = log.getLogger(__name__)
65share_manager_opts = [
66 cfg.StrOpt('share_driver',
67 default='manila.share.drivers.generic.GenericShareDriver',
68 help='Driver to use for share creation.'),
69 cfg.ListOpt('hook_drivers',
70 default=[],
71 help='Driver(s) to perform some additional actions before and '
72 'after share driver actions and on a periodic basis. '
73 'Default is [].'),
74 cfg.BoolOpt('delete_share_server_with_last_share',
75 default=False,
76 help='Whether share servers will '
77 'be deleted on deletion of the last share.'),
78 cfg.BoolOpt('unmanage_remove_access_rules',
79 default=False,
80 help='If set to True, then manila will deny access and remove '
81 'all access rules on share unmanage.'
82 'If set to False - nothing will be changed.'),
83 cfg.BoolOpt('automatic_share_server_cleanup',
84 default=True,
85 help='If set to True, then Manila will delete all share '
86 'servers which were unused more than specified time .'
87 'If set to False - automatic deletion of share servers '
88 'will be disabled.'),
89 cfg.IntOpt('unused_share_server_cleanup_interval',
90 default=10,
91 help='Unallocated share servers reclamation time interval '
92 '(minutes). Minimum value is 10 minutes, maximum is 720 '
93 'minutes. The reclamation function is run every '
94 '10 minutes and delete share servers which were unused '
95 'more than unused_share_server_cleanup_interval option '
96 'defines. This value reflects the shortest time Manila '
97 'will wait for a share server to go unutilized before '
98 'deleting it.',
99 min=10,
100 max=720),
101 cfg.IntOpt('replica_state_update_interval',
102 default=300,
103 help='This value, specified in seconds, determines how often '
104 'the share manager will poll for the health '
105 '(replica_state) of each replica instance.'),
106 cfg.IntOpt('migration_driver_continue_update_interval',
107 default=60,
108 help='This value, specified in seconds, determines how often '
109 'the share manager will poll the driver to perform the '
110 'next step of migration in the storage backend, for a '
111 'migrating share.'),
112 cfg.IntOpt('server_migration_driver_continue_update_interval',
113 default=900,
114 help='This value, specified in seconds, determines how often '
115 'the share manager will poll the driver to perform the '
116 'next step of migration in the storage backend, for a '
117 'migrating share server.'),
118 cfg.BoolOpt('server_migration_extend_neutron_network',
119 default=False,
120 help='If set to True, neutron network are extended to '
121 'destination host during share server migration. This '
122 'option should only be enabled if using '
123 'NeutronNetworkPlugin or its derivatives and when '
124 'multiple bindings of Manila ports are supported by '
125 'Neutron ML2 plugin.'),
126 cfg.IntOpt('share_usage_size_update_interval',
127 default=300,
128 help='This value, specified in seconds, determines how often '
129 'the share manager will poll the driver to update the '
130 'share usage size in the storage backend, for shares in '
131 'that backend.'),
132 cfg.BoolOpt('enable_gathering_share_usage_size',
133 default=False,
134 help='If set to True, share usage size will be polled for in '
135 'the interval specified with '
136 '"share_usage_size_update_interval". Usage data can be '
137 'consumed by telemetry integration. If telemetry is not '
138 'configured, this option must be set to False. '
139 'If set to False - gathering share usage size will be'
140 ' disabled.'),
141 cfg.BoolOpt('share_service_inithost_offload',
142 default=False,
143 help='Offload pending share ensure during '
144 'share service startup'),
145 cfg.IntOpt('check_for_expired_shares_in_recycle_bin_interval',
146 default=3600,
147 help='This value, specified in seconds, determines how often '
148 'the share manager will check for expired shares and '
149 'delete them from the Recycle bin.'),
150 cfg.IntOpt('check_for_expired_transfers',
151 default=300,
152 help='This value, specified in seconds, determines how often '
153 'the share manager will check for expired transfers and '
154 'destroy them and roll back share state.'),
155 cfg.IntOpt('driver_backup_continue_update_interval',
156 default=60,
157 help='This value, specified in seconds, determines how often '
158 'the share manager will poll to perform the next steps '
159 'of backup such as fetch the progress of backup.'),
160 cfg.IntOpt('driver_restore_continue_update_interval',
161 default=60,
162 help='This value, specified in seconds, determines how often '
163 'the share manager will poll to perform the next steps '
164 'of restore such as fetch the progress of restore.'),
165 cfg.IntOpt('periodic_deferred_delete_interval',
166 default=300,
167 help='This value, specified in seconds, determines how often '
168 'the share manager will try to delete the share and share '
169 'snapshots in backend driver.'),
170]
173ks_opts = [
174 cfg.StrOpt('auth_url',
175 help='Keystone authentication URL.'),
176]
178CONF = cfg.CONF
179CONF.register_opts(share_manager_opts)
180CONF.import_opt('periodic_hooks_interval', 'manila.share.hook')
181CONF.import_opt('periodic_interval', 'manila.service')
183KEYSTONE_AUTHTOKEN_GROUP = 'keystone_authtoken'
184CONF.register_opts(ks_opts, KEYSTONE_AUTHTOKEN_GROUP)
185ks_loading.register_auth_conf_options(CONF, KEYSTONE_AUTHTOKEN_GROUP)
186keystone_url = getattr(CONF.keystone_authtoken, 'auth_url')
188# Drivers that need to change module paths or class names can add their
189# old/new path here to maintain backward compatibility.
190MAPPING = {
191 'manila.share.drivers.netapp.cluster_mode.NetAppClusteredShareDriver':
192 'manila.share.drivers.netapp.common.NetAppDriver',
193 'manila.share.drivers.hp.hp_3par_driver.HP3ParShareDriver':
194 'manila.share.drivers.hpe.hpe_3par_driver.HPE3ParShareDriver',
195 'manila.share.drivers.hitachi.hds_hnas.HDSHNASDriver':
196 'manila.share.drivers.hitachi.hnas.driver.HitachiHNASDriver',
197 'manila.share.drivers.glusterfs_native.GlusterfsNativeShareDriver':
198 'manila.share.drivers.glusterfs.glusterfs_native.'
199 'GlusterfsNativeShareDriver',
200 'manila.share.drivers.emc.driver.EMCShareDriver':
201 'manila.share.drivers.dell_emc.driver.EMCShareDriver',
202 'manila.share.drivers.cephfs.cephfs_native.CephFSNativeDriver':
203 'manila.share.drivers.cephfs.driver.CephFSDriver',
204}
206QUOTAS = quota.QUOTAS
209def locked_share_replica_operation(operation):
210 """Lock decorator for share replica operations.
212 Takes a named lock prior to executing the operation. The lock is named with
213 the id of the share to which the replica belongs.
215 Intended use:
216 If a replica operation uses this decorator, it will block actions on
217 all share replicas of the share until the named lock is free. This is
218 used to protect concurrent operations on replicas of the same share e.g.
219 promote ReplicaA while deleting ReplicaB, both belonging to the same share.
220 """
222 def wrapped(*args, **kwargs):
223 share_id = kwargs.get('share_id')
225 @coordination.synchronized(
226 'locked-share-replica-operation-for-share-%s' % share_id)
227 def locked_replica_operation(*_args, **_kwargs):
228 return operation(*_args, **_kwargs)
229 return locked_replica_operation(*args, **kwargs)
231 return wrapped
234def locked_share_network_operation(operation):
235 """Lock decorator for share network operations.
237 Takes a named lock prior to executing the operation. The lock is named with
238 the id of the share network.
239 """
241 def wrapped(*args, **kwargs):
242 share_network_id = kwargs.get('share_network_id')
244 @coordination.synchronized(
245 'locked-share-network-operation-%s' % share_network_id)
246 def locked_network_operation(*_args, **_kwargs):
247 return operation(*_args, **_kwargs)
248 return locked_network_operation(*args, **kwargs)
250 return wrapped
253def add_hooks(f):
254 """Hook decorator to perform action before and after a share method call
256 The hook decorator can perform actions before some share driver methods
257 calls and after a call with results of driver call and preceding hook call.
258 """
259 @functools.wraps(f)
260 def wrapped(self, *args, **kwargs):
261 if not self.hooks:
262 return f(self, *args, **kwargs)
264 pre_hook_results = []
265 for hook in self.hooks:
266 pre_hook_results.append(
267 hook.execute_pre_hook(
268 func_name=f.__name__,
269 *args, **kwargs))
271 wrapped_func_results = f(self, *args, **kwargs)
273 for i, hook in enumerate(self.hooks):
274 hook.execute_post_hook(
275 func_name=f.__name__,
276 driver_action_results=wrapped_func_results,
277 pre_hook_data=pre_hook_results[i],
278 *args, **kwargs)
280 return wrapped_func_results
282 return wrapped
285class ShareManager(manager.SchedulerDependentManager):
286 """Manages NAS storages."""
288 RPC_API_VERSION = '1.30'
290 def __init__(self, share_driver=None, service_name=None, *args, **kwargs):
291 """Load the driver from args, or from flags."""
292 self.configuration = configuration.Configuration(
293 share_manager_opts,
294 config_group=service_name)
295 super(ShareManager, self).__init__(service_name='share',
296 *args, **kwargs)
298 if not share_driver: 298 ↛ 300line 298 didn't jump to line 300 because the condition on line 298 was always true
299 share_driver = self.configuration.share_driver
300 if share_driver in MAPPING: 300 ↛ 301line 300 didn't jump to line 301 because the condition on line 300 was never true
301 msg_args = {'old': share_driver, 'new': MAPPING[share_driver]}
302 LOG.warning("Driver path %(old)s is deprecated, update your "
303 "configuration to the new path %(new)s",
304 msg_args)
305 share_driver = MAPPING[share_driver]
307 ctxt = context.get_admin_context()
308 private_storage = drivers_private_data.DriverPrivateData(
309 context=ctxt, backend_host=self.host,
310 config_group=self.configuration.config_group
311 )
312 self.driver = importutils.import_object(
313 share_driver, private_storage=private_storage,
314 configuration=self.configuration,
315 )
317 backend_availability_zone = self.driver.configuration.safe_get(
318 'backend_availability_zone')
319 self.availability_zone = (
320 backend_availability_zone or CONF.storage_availability_zone
321 )
323 self.access_helper = access.ShareInstanceAccess(self.db, self.driver)
324 self.snapshot_access_helper = (
325 snapshot_access.ShareSnapshotInstanceAccess(self.db, self.driver))
326 self.migration_wait_access_rules_timeout = (
327 CONF.migration_wait_access_rules_timeout)
329 self.message_api = message_api.API()
330 self.share_api = api.API()
331 self.transfer_api = transfer_api.API()
332 if CONF.profiler.enabled and profiler is not None: 332 ↛ 333line 332 didn't jump to line 333 because the condition on line 332 was never true
333 self.driver = profiler.trace_cls("driver")(self.driver)
334 self.hooks = []
335 self._init_hook_drivers()
336 self.service_id = None
338 def _init_hook_drivers(self):
339 # Try to initialize hook driver(s).
340 hook_drivers = self.configuration.safe_get("hook_drivers")
341 for hook_driver in hook_drivers:
342 self.hooks.append(
343 importutils.import_object(
344 hook_driver,
345 configuration=self.configuration,
346 host=self.host,
347 )
348 )
350 def _ensure_share_instance_has_pool(self, ctxt, share_instance):
351 pool = share_utils.extract_host(share_instance['host'], 'pool')
352 if pool is None:
353 # No pool name encoded in host, so this is a legacy
354 # share created before pool is introduced, ask
355 # driver to provide pool info if it has such
356 # knowledge and update the DB.
357 try:
358 pool = self.driver.get_pool(share_instance)
359 except Exception:
360 LOG.exception("Failed to fetch pool name for share: "
361 "%(share)s.",
362 {'share': share_instance['id']})
363 return
365 if pool:
366 new_host = share_utils.append_host(
367 share_instance['host'], pool)
368 self.db.share_instance_update(
369 ctxt, share_instance['id'], {'host': new_host})
371 return pool
373 @add_hooks
374 def init_host(self, service_id=None):
375 """Initialization for a standalone service."""
377 self.service_id = service_id
378 ctxt = context.get_admin_context()
379 driver_host_pair = "{}@{}".format(
380 self.driver.__class__.__name__,
381 self.host)
383 # we want to retry to setup the driver. In case of a multi-backend
384 # scenario, working backends are usable and the non-working ones (where
385 # do_setup() or check_for_setup_error() fail) retry.
386 @utils.retry(interval=2, backoff_rate=2,
387 infinite=True, backoff_sleep_max=600)
388 def _driver_setup():
389 self.driver.initialized = False
390 LOG.debug("Start initialization of driver: '%s'", driver_host_pair)
391 try:
392 self.driver.do_setup(ctxt)
393 self.driver.check_for_setup_error()
394 except Exception:
395 LOG.exception("Error encountered during initialization of "
396 "driver %s", driver_host_pair)
397 raise
398 else:
399 self.driver.initialized = True
401 _driver_setup()
402 if (self.driver.driver_handles_share_servers and 402 ↛ 407line 402 didn't jump to line 407 because the condition on line 402 was always true
403 hasattr(self.driver, 'service_instance_manager')):
404 (self.driver.service_instance_manager.network_helper.
405 setup_connectivity_with_service_instances())
407 self.ensure_driver_resources(ctxt)
409 self.publish_service_capabilities(ctxt)
410 LOG.info("Finished initialization of driver: '%(driver)s"
411 "@%(host)s'",
412 {"driver": self.driver.__class__.__name__,
413 "host": self.host})
415 def is_service_ready(self):
416 """Return if Manager is ready to accept requests.
418 This is to inform Service class that in case of manila driver
419 initialization failure the manager is actually down and not ready to
420 accept any requests.
422 """
423 return self.driver.initialized
425 def ensure_driver_resources(self, ctxt, skip_backend_info_check=False):
426 update_instances_status = CONF.update_shares_status_on_ensure
427 old_backend_info = self.db.backend_info_get(ctxt, self.host)
428 old_backend_info_hash = (old_backend_info.get('info_hash')
429 if old_backend_info is not None else None)
430 new_backend_info = None
431 new_backend_info_hash = None
432 backend_info_implemented = True
433 update_share_instances = []
434 if not skip_backend_info_check: 434 ↛ 462line 434 didn't jump to line 462 because the condition on line 434 was always true
435 try:
436 new_backend_info = self.driver.get_backend_info(ctxt)
437 except Exception as e:
438 if not isinstance(e, NotImplementedError):
439 LOG.exception(
440 "The backend %(host)s could not get backend info.",
441 {'host': self.host})
442 raise
443 else:
444 backend_info_implemented = False
445 LOG.debug(
446 ("The backend %(host)s does not support get backend"
447 " info method."),
448 {'host': self.host})
450 if new_backend_info:
451 new_backend_info_hash = hashlib.sha1(
452 str(sorted(new_backend_info.items())).encode(
453 'utf-8')).hexdigest()
454 if ((old_backend_info_hash == new_backend_info_hash and
455 backend_info_implemented) and not skip_backend_info_check):
456 LOG.debug(
457 ("Ensure shares is being skipped because the %(host)s's "
458 "old backend info is the same as its new backend info."),
459 {'host': self.host})
460 return
462 share_instances = self.db.share_instance_get_all_by_host(
463 ctxt, self.host)
464 LOG.debug("Re-exporting %s shares", len(share_instances))
466 for share_instance in share_instances:
467 share_ref = self.db.share_get(ctxt, share_instance['share_id'])
469 if share_ref.is_busy:
470 LOG.info(
471 "Share instance %(id)s: skipping export, "
472 "because it is busy with an active task: %(task)s.",
473 {'id': share_instance['id'],
474 'task': share_ref['task_state']},
475 )
476 continue
478 # If the share's status is 'ensuring', we must allow re-doing the
479 # ensuring operation otherwise it will be stuck
480 if (share_instance['status'] not in
481 [constants.STATUS_AVAILABLE, constants.STATUS_ENSURING]):
482 LOG.info(
483 "Share instance %(id)s: skipping export, "
484 "because it has '%(status)s' status.",
485 {'id': share_instance['id'],
486 'status': share_instance['status']},
487 )
488 continue
490 self._ensure_share_instance_has_pool(ctxt, share_instance)
491 share_instance = self.db.share_instance_get(
492 ctxt, share_instance['id'], with_share_data=True)
493 share_instance_dict = self._get_share_instance_dict(
494 ctxt, share_instance)
495 update_share_instances.append(share_instance_dict)
497 do_service_status_update = False
498 if update_share_instances:
499 # No reason to update the shares status if nothing will be done.
500 do_service_status_update = True
501 service = self.db.service_get_by_args(
502 ctxt, self.host, 'manila-share')
503 self.db.service_update(ctxt, service['id'], {'ensuring': True})
504 if update_instances_status: 504 ↛ 510line 504 didn't jump to line 510 because the condition on line 504 was always true
505 for instance in update_share_instances:
506 self.db.share_instance_update(
507 ctxt, instance['id'],
508 {'status': constants.STATUS_ENSURING}
509 )
510 try:
511 update_share_instances = self.driver.ensure_shares(
512 ctxt, update_share_instances) or {}
513 except Exception as e:
514 if not isinstance(e, NotImplementedError):
515 LOG.exception("Caught exception trying ensure "
516 "share instances.")
517 else:
518 for share_instance in update_share_instances:
519 if CONF.share_service_inithost_offload: 519 ↛ 520line 519 didn't jump to line 520 because the condition on line 519 was never true
520 self._add_to_threadpool(self._ensure_share,
521 ctxt, share_instance)
522 else:
523 self._ensure_share(ctxt, share_instance)
524 # At this point, we assume that the ensuring operation is
525 # complete or everything is okay, even though it might be
526 # running on different threads.
527 LOG.debug(
528 "Shares' export locations were ensured individually, "
529 "so triggering the ensure shares operation is "
530 "complete.")
531 self.db.service_update(
532 ctxt, service['id'], {'ensuring': False})
534 if new_backend_info:
535 self.db.backend_info_update(
536 ctxt, self.host, new_backend_info_hash)
538 shares_with_metadata_already_updated = set()
539 for share_instance in share_instances:
540 if share_instance['id'] not in update_share_instances:
541 continue
542 share_instance_update_dict = (
543 update_share_instances[share_instance['id']]
544 )
545 backend_provided_status = share_instance_update_dict.get('status')
546 if backend_provided_status:
547 self.db.share_instance_update(
548 ctxt, share_instance['id'],
549 {'status': backend_provided_status,
550 'host': share_instance['host']}
551 )
552 metadata_updates = share_instance_update_dict.get('metadata')
553 if metadata_updates:
554 share_id = share_instance['share_id']
555 # NOTE(carloss): Multiple instances might exist, and in such
556 # cases, a single share metadata update would be enough.
557 if share_id not in shares_with_metadata_already_updated: 557 ↛ 562line 557 didn't jump to line 562 because the condition on line 557 was always true
558 self.db.share_metadata_update(
559 ctxt, share_id, metadata_updates, False)
560 shares_with_metadata_already_updated.add(share_id)
562 update_export_locations = (
563 share_instance_update_dict.get('export_locations')
564 )
565 if update_export_locations:
566 self.db.export_locations_update(
567 ctxt, share_instance['id'], update_export_locations)
569 share_server = self._get_share_server(ctxt, share_instance)
570 driver_has_to_reapply_access_rules = (
571 share_instance_update_dict.get('reapply_access_rules') is True
572 )
573 share_instance_has_pending_rules = (
574 share_instance['access_rules_status'] !=
575 constants.STATUS_ACTIVE
576 )
577 if (driver_has_to_reapply_access_rules or
578 share_instance_has_pending_rules):
579 try:
580 # Cast any existing 'applying' rules to 'new'
581 self.access_helper.reset_rules_to_queueing_states(
582 ctxt, share_instance['id'],
583 reset_active=driver_has_to_reapply_access_rules)
584 self.access_helper.update_access_rules(
585 ctxt, share_instance['id'], share_server=share_server)
586 except Exception:
587 LOG.exception(
588 ("Unexpected error occurred while updating access "
589 "rules for share instance %(s_id)s."),
590 {'s_id': share_instance['id']},
591 )
593 snapshot_instances = (
594 self.db.share_snapshot_instance_get_all_with_filters(
595 ctxt, {'share_instance_ids': share_instance['id']},
596 with_share_data=True))
598 for snap_instance in snapshot_instances: 598 ↛ 600line 598 didn't jump to line 600 because the loop on line 598 never started
600 rules = (
601 self.db.
602 share_snapshot_access_get_all_for_snapshot_instance(
603 ctxt, snap_instance['id']))
605 # NOTE(ganso): We don't invoke update_access for snapshots if
606 # we don't have invalid rules or pending updates
607 if any(r['state'] in (constants.ACCESS_STATE_DENYING,
608 constants.ACCESS_STATE_QUEUED_TO_DENY,
609 constants.ACCESS_STATE_APPLYING,
610 constants.ACCESS_STATE_QUEUED_TO_APPLY)
611 for r in rules):
612 try:
613 self.snapshot_access_helper.update_access_rules(
614 ctxt, snap_instance['id'], share_server)
615 except Exception:
616 LOG.exception(
617 "Unexpected error occurred while updating "
618 "access rules for snapshot instance %s.",
619 snap_instance['id'])
620 if not backend_provided_status and update_instances_status:
621 self.db.share_instance_update(
622 ctxt, share_instance['id'],
623 {'status': constants.STATUS_AVAILABLE}
624 )
625 if do_service_status_update:
626 self.db.service_update(ctxt, service['id'], {'ensuring': False})
628 def _ensure_share(self, ctxt, share_instance):
629 export_locations = None
630 try:
631 export_locations = self.driver.ensure_share(
632 ctxt, share_instance,
633 share_server=share_instance['share_server'])
634 except Exception:
635 LOG.exception("Caught exception trying ensure "
636 "share '%(s_id)s'.",
637 {'s_id': share_instance['id']})
638 if export_locations:
639 self.db.export_locations_update(
640 ctxt, share_instance['id'], export_locations)
642 # NOTE(carloss): we can't determine if the share is actually alright,
643 # but we expect that after the export location is updated in the
644 # database, everything is okay.
645 self.db.share_instance_update(
646 ctxt, share_instance['id'],
647 {'status': constants.STATUS_AVAILABLE}
648 )
650 def _check_share_server_backend_limits(
651 self, context, available_share_servers, share_instance=None):
652 max_shares_limit = self.driver.max_shares_per_share_server
653 max_server_size = self.driver.max_share_server_size
655 if max_server_size == max_shares_limit == -1:
656 return available_share_servers
658 for ss in available_share_servers[:]:
659 share_instances = self.db.share_instance_get_all_by_share_server(
660 context, ss['id'], with_share_data=True)
661 if not share_instances: 661 ↛ 662line 661 didn't jump to line 662 because the condition on line 661 was never true
662 continue
663 share_instance_ids = [si['id'] for si in share_instances]
664 share_snapshot_instances = (
665 self.db.share_snapshot_instance_get_all_with_filters(
666 context, {"share_instance_ids": share_instance_ids},
667 with_share_data=True))
669 server_instances_size_sum = 0
670 num_instances = 0
672 server_instances_size_sum += sum(
673 instance['size'] for instance in share_instances)
674 server_instances_size_sum += sum(
675 instance['size'] for instance in share_snapshot_instances)
676 num_instances += len(share_instances)
678 # NOTE(carloss): If a share instance was not provided, means that
679 # a share group is being requested and there aren't shares to
680 # be added to to the sum yet.
681 if share_instance:
682 server_instances_size_sum += share_instance['size']
683 num_instances += 1
685 achieved_gigabytes_limit = (
686 max_server_size != -1 and (
687 server_instances_size_sum > max_server_size))
689 achieved_instances_limit = num_instances > max_shares_limit > -1
691 providing_server_for_share_migration = (
692 share_instance and share_instance['status'] ==
693 constants.STATUS_MIGRATING_TO)
695 src_server_id_equals_current_iteration = False
697 if providing_server_for_share_migration:
698 share = self.db.share_get(context, share_instance['share_id'])
699 src_instance_id, dest_instance_id = (
700 self.share_api.get_migrating_instances(share))
701 src_instance = self.db.share_instance_get(
702 context, src_instance_id)
703 src_server_id_equals_current_iteration = (
704 src_instance['share_server_id'] == ss['id'])
706 if (not src_server_id_equals_current_iteration and (
707 achieved_gigabytes_limit or achieved_instances_limit)):
708 available_share_servers.remove(ss)
710 return available_share_servers
712 def _provide_share_server_for_share(self, context, share_network_id,
713 share_instance, snapshot=None,
714 share_group=None,
715 create_on_backend=True):
716 """Gets or creates share_server and updates share with its id.
718 Active share_server can be deleted if there are no dependent shares
719 on it.
720 So we need avoid possibility to delete share_server in time gap
721 between reaching active state for share_server and setting up
722 share_server_id for share. It is possible, for example, with first
723 share creation, which starts share_server creation.
724 For this purpose used shared lock between this method and the one
725 with deletion of share_server.
727 :param context: Current context
728 :param share_network_id: Share network where existing share server
729 should be found or created. If
730 share_network_id is None method use
731 share_network_id from provided snapshot.
732 :param share_instance: Share Instance model
733 :param snapshot: Optional -- Snapshot model
734 :param create_on_backend: Boolean. If True, driver will be asked to
735 create the share server if no share server
736 is available.
738 :returns: dict, dict -- first value is share_server, that
739 has been chosen for share schedule. Second value is
740 share updated with share_server_id.
741 """
742 if not (share_network_id or snapshot):
743 msg = _("'share_network_id' parameter or 'snapshot'"
744 " should be provided. ")
745 raise ValueError(msg)
747 def error(msg, *args):
748 LOG.error(msg, *args)
749 self.db.share_instance_update(context, share_instance['id'],
750 {'status': constants.STATUS_ERROR})
751 parent_share_server = None
752 parent_share_same_dest = False
753 if snapshot:
754 parent_share_server_id = (
755 snapshot['share']['instance']['share_server_id'])
756 try:
757 parent_share_server = self.db.share_server_get(
758 context, parent_share_server_id)
759 except exception.ShareServerNotFound:
760 with excutils.save_and_reraise_exception():
761 error("Parent share server %s does not exist.",
762 parent_share_server_id)
764 if parent_share_server['status'] != constants.STATUS_ACTIVE:
765 error_params = {
766 'id': parent_share_server_id,
767 'status': parent_share_server['status'],
768 }
769 msg = _("Parent share server %(id)s has invalid status "
770 "'%(status)s'.")
771 error(msg, error_params)
772 raise exception.InvalidShareServer(reason=msg % error_params)
773 parent_share_same_dest = (snapshot['share']['instance']['host']
774 == share_instance['host'])
775 share_network_subnets = None
776 if share_network_id:
777 share_network_subnets = (
778 self.db.share_network_subnets_get_all_by_availability_zone_id(
779 context, share_network_id,
780 availability_zone_id=share_instance.get(
781 'availability_zone_id')))
782 if not share_network_subnets:
783 raise exception.ShareNetworkSubnetNotFound(
784 share_network_subnet_id=None)
785 elif parent_share_server: 785 ↛ 793line 785 didn't jump to line 793 because the condition on line 785 was always true
786 share_network_subnets = (
787 parent_share_server['share_network_subnets'])
789 # NOTE(felipe_rodrigues): it can retrieve the available share
790 # servers using one single subnet_id from the availability zone
791 # subnets, because if the share server has one, it will have
792 # all subnets on that availability zone.
793 share_network_subnet_id = share_network_subnets[0]['id']
795 def get_available_share_servers():
796 if parent_share_server and parent_share_same_dest:
797 return [parent_share_server]
798 else:
799 return (
800 self.db
801 .share_server_get_all_by_host_and_share_subnet_valid(
802 context, self.host, share_network_subnet_id)
803 )
805 @utils.synchronized("share_manager_%s" % share_network_subnet_id,
806 external=True)
807 def _wrapped_provide_share_server_for_share():
808 try:
809 available_share_servers = get_available_share_servers()
810 except exception.ShareServerNotFound:
811 available_share_servers = None
813 # creating from snapshot in the same host must reuse the server,
814 # so it ignores the server limits.
815 if available_share_servers and not parent_share_same_dest:
816 available_share_servers = (
817 self._check_share_server_backend_limits(
818 context, available_share_servers,
819 share_instance=share_instance))
821 encryption_key_ref = share_instance.get('encryption_key_ref')
823 compatible_share_server = None
824 if available_share_servers:
825 try:
826 compatible_share_server = (
827 self.driver.choose_share_server_compatible_with_share(
828 context, available_share_servers, share_instance,
829 snapshot=snapshot.instance if snapshot else None,
830 share_group=share_group,
831 encryption_key_ref=encryption_key_ref,
832 )
833 )
834 except Exception as e:
835 with excutils.save_and_reraise_exception():
836 error("Cannot choose compatible share server: %s",
837 e)
839 share_server_should_be_encrypted = (
840 (encryption_key_ref and self.driver.encryption_support) and
841 ("share_server" in self.driver.encryption_support))
843 app_cred = None
844 if not compatible_share_server:
845 if share_server_should_be_encrypted:
846 # Create secret_ref ACL for Barbican User
847 try:
848 barbican_api.create_secret_access(context,
849 encryption_key_ref)
850 LOG.debug('Created Barbican ACL for encryption key '
851 'reference %s.', encryption_key_ref)
852 except Exception as e:
853 self._delete_encryption_keys_quota(context)
854 with excutils.save_and_reraise_exception():
855 error("Cannot create ACL for Barbican user %s", e)
857 # Create application credentials for barbican user
858 try:
859 app_cred = (
860 barbican_api.create_application_credentials(
861 context, encryption_key_ref).to_dict())
862 LOG.debug("Created app cred id %s", app_cred['id'])
863 except Exception as e:
864 self._delete_encryption_keys_quota(context)
865 barbican_api.delete_secret_access(context,
866 encryption_key_ref)
867 with excutils.save_and_reraise_exception():
868 error("Cannot create application credential: "
869 "%s", e)
871 compatible_share_server = self.db.share_server_create(
872 context,
873 {
874 'host': self.host,
875 'share_network_subnets': share_network_subnets,
876 'status': constants.STATUS_CREATING,
877 'security_service_update_support': (
878 self.driver.security_service_update_support),
879 'network_allocation_update_support': (
880 self.driver.network_allocation_update_support),
881 'share_replicas_migration_support': (
882 self.driver.share_replicas_migration_support),
883 'encryption_key_ref': (
884 encryption_key_ref if
885 share_server_should_be_encrypted else None),
886 'application_credential_id': (
887 app_cred['id'] if app_cred else None),
888 }
889 )
890 else:
891 if share_server_should_be_encrypted: 891 ↛ 893line 891 didn't jump to line 893 because the condition on line 891 was never true
892 # Get application credentials for barbican user
893 try:
894 app_cred = barbican_api.get_application_credentials(
895 context,
896 compatible_share_server.get(
897 'application_credential_id')).to_dict()
898 LOG.debug('Got app cred id %s', app_cred['id'])
899 except Exception as e:
900 with excutils.save_and_reraise_exception():
901 error("Cannot get application credential: %s", e)
903 msg = ("Using share_server %(share_server)s for share instance"
904 " %(share_instance_id)s")
905 LOG.debug(msg, {
906 'share_server': compatible_share_server['id'],
907 'share_instance_id': share_instance['id']
908 })
910 share_instance_ref = self.db.share_instance_update(
911 context,
912 share_instance['id'],
913 {'share_server_id': compatible_share_server['id']},
914 with_share_data=True
915 )
917 if create_on_backend: 917 ↛ 929line 917 didn't jump to line 929 because the condition on line 917 was always true
918 metadata = self._build_server_metadata(
919 context,
920 share_instance['host'],
921 share_instance['share_type_id'],
922 app_cred=app_cred,
923 encryption_key_ref=encryption_key_ref
924 )
925 compatible_share_server = (
926 self._create_share_server_in_backend(
927 context, compatible_share_server, metadata))
929 return compatible_share_server, share_instance_ref
931 return _wrapped_provide_share_server_for_share()
933 def _build_server_metadata(self, context, host, share_type_id,
934 app_cred=None,
935 encryption_key_ref=None):
937 encryption_key_href = None
938 if encryption_key_ref:
939 encryption_key_href = barbican_api.get_secret_href(
940 context, encryption_key_ref)
941 LOG.debug("Generated encryption_key_href %s for backend share "
942 "server.", encryption_key_href)
944 metadata = {
945 'request_host': host,
946 'share_type_id': share_type_id,
947 'encryption_key_ref': encryption_key_href,
948 'keystone_url': keystone_url,
949 }
950 if app_cred:
951 metadata.update({
952 'application_credential_id': app_cred.get('id'),
953 'application_credential_secret': encryption_key_ref,
954 })
955 return metadata
957 def _provide_share_server_for_migration(self, context,
958 source_share_server,
959 new_share_network_id,
960 availability_zone_id,
961 destination_host,
962 create_on_backend=True,
963 server_metadata=None):
964 """Gets or creates share_server for a migration procedure.
966 Active share_server can be deleted if there are no dependent shares
967 on it.
968 So we need avoid possibility to delete share_server in time gap
969 between reaching active state for share_server and setting up
970 share_server_id for share. It is possible, for example, with first
971 share creation, which starts share_server creation.
972 For this purpose used shared lock between this method and the one
973 with deletion of share_server.
975 :param context: Current context
976 :param source_share_server: Share server model that will be migrated.
977 :param new_share_network_id: Share network where existing share server
978 should be found or created.
979 :param availability_zone_id: Id of the availability zone where the
980 new share server will be placed.
981 :param destination_host: The destination host where the new share
982 server will be created or retrieved.
983 :param create_on_backend: Boolean. If True, driver will be asked to
984 create the share server if no share server is available.
985 :param server_metadata: dict. Holds some important information that
986 can help drivers whether to create a new share server or not.
987 :returns: Share server that has been chosen for share server
988 migration.
989 """
991 share_network_subnets = (
992 self.db.share_network_subnets_get_all_by_availability_zone_id(
993 context, new_share_network_id,
994 availability_zone_id=availability_zone_id))
995 if not share_network_subnets:
996 raise exception.ShareNetworkSubnetNotFound(
997 share_network_subnet_id=None)
999 server_metadata = {} if not server_metadata else server_metadata
1001 @utils.synchronized(
1002 "share_manager_%s" % share_network_subnets[0]['id'], external=True)
1003 def _wrapped_provide_share_server_for_migration():
1004 destination_share_server = self.db.share_server_create(
1005 context,
1006 {
1007 'host': self.host,
1008 'share_network_subnets': share_network_subnets,
1009 'status': constants.STATUS_CREATING,
1010 'security_service_update_support': (
1011 self.driver.security_service_update_support),
1012 'network_allocation_update_support': (
1013 self.driver.network_allocation_update_support),
1014 'share_replicas_migration_support': (
1015 self.driver.share_replicas_migration_support),
1016 }
1017 )
1019 msg = ("Using share_server %(share_server)s as destination for "
1020 "migration.")
1021 LOG.debug(msg, {
1022 'share_server': destination_share_server['id'],
1023 })
1025 if create_on_backend: 1025 ↛ 1039line 1025 didn't jump to line 1039 because the condition on line 1025 was always true
1026 # NOTE(carloss): adding some information about the request, so
1027 # backends that support share server migration and need to know
1028 # if the request share server is from a share server migration
1029 # request can use this metadata to take actions.
1030 server_metadata['migration_destination'] = True
1031 server_metadata['request_host'] = destination_host
1032 server_metadata['source_share_server'] = (
1033 source_share_server)
1034 destination_share_server = (
1035 self._create_share_server_in_backend(
1036 context, destination_share_server,
1037 metadata=server_metadata))
1039 return destination_share_server
1041 return _wrapped_provide_share_server_for_migration()
1043 def _create_share_server_in_backend(self, context, share_server,
1044 metadata):
1045 """Perform setup_server on backend
1047 :param metadata: A dictionary, to be passed to driver's setup_server()
1048 """
1050 if share_server['status'] == constants.STATUS_CREATING:
1051 # Create share server on backend with data from db.
1052 share_server = self._setup_server(context, share_server, metadata)
1053 LOG.info("Share server created successfully.")
1054 else:
1055 LOG.info("Using preexisting share server: "
1056 "'%(share_server_id)s'",
1057 {'share_server_id': share_server['id']})
1058 return share_server
1060 def create_share_server(
1061 self, context, share_server_id, share_instance_id):
1062 """Invoked to create a share server in this backend.
1064 This method is invoked to create the share server defined in the model
1065 obtained by the supplied id.
1067 :param context: The 'context.RequestContext' object for the request.
1068 :param share_server_id: The id of the server to be created.
1069 :param share_instance_id: The id of the share instance
1070 """
1071 share_server = self.db.share_server_get(context, share_server_id)
1072 share = self.db.share_instance_get(
1073 context, share_instance_id, with_share_data=True)
1074 metadata = self._build_server_metadata(context, share['host'],
1075 share['share_type_id'])
1077 self._create_share_server_in_backend(context, share_server, metadata)
1079 def provide_share_server(self, context, share_instance_id,
1080 share_network_id, snapshot_id=None):
1081 """Invoked to provide a compatible share server.
1083 This method is invoked to find a compatible share server among the
1084 existing ones or create a share server database instance with the share
1085 server properties that will be used to create the share server later.
1087 :param context: The 'context.RequestContext' object for the request.
1088 :param share_instance_id: The id of the share instance whose model
1089 attributes will be used to provide the share server.
1090 :param share_network_id: The id of the share network the share server
1091 to be provided has to be related to.
1092 :param snapshot_id: The id of the snapshot to be used to obtain the
1093 share server if applicable.
1094 :return: The id of the share server that is being provided.
1095 """
1096 share_instance = self.db.share_instance_get(context, share_instance_id,
1097 with_share_data=True)
1098 snapshot_ref = None
1099 if snapshot_id: 1099 ↛ 1102line 1099 didn't jump to line 1102 because the condition on line 1099 was always true
1100 snapshot_ref = self.db.share_snapshot_get(context, snapshot_id)
1102 share_group_ref = None
1103 if share_instance.get('share_group_id'): 1103 ↛ 1107line 1103 didn't jump to line 1107 because the condition on line 1103 was always true
1104 share_group_ref = self.db.share_group_get(
1105 context, share_instance['share_group_id'])
1107 share_server, share_instance = self._provide_share_server_for_share(
1108 context, share_network_id, share_instance, snapshot_ref,
1109 share_group_ref, create_on_backend=False)
1111 return share_server['id']
1113 def _provide_share_server_for_share_group(self, context,
1114 share_network_id,
1115 share_network_subnets,
1116 share_group_ref,
1117 share_group_snapshot=None):
1118 """Gets or creates share_server and updates share group with its id.
1120 Active share_server can be deleted if there are no shares or share
1121 groups dependent on it.
1123 So we need avoid possibility to delete share_server in time gap
1124 between reaching active state for share_server and setting up
1125 share_server_id for share group. It is possible, for example, with
1126 first share group creation, which starts share_server creation.
1127 For this purpose used shared lock between this method and the one
1128 with deletion of share_server.
1130 :param context: Current context
1131 :param share_network_id: Share network where existing share server
1132 should be found or created.
1133 :param share_network_subnets: Share network subnets where existing
1134 share server should be found or created.
1135 :param share_group_ref: Share Group model
1136 :param share_group_snapshot: Optional -- ShareGroupSnapshot model. If
1137 supplied, driver will use it to choose
1138 the appropriate share server.
1140 :returns: dict, dict -- first value is share_server, that
1141 has been chosen for share group schedule.
1142 Second value is share group updated with
1143 share_server_id.
1144 """
1145 if not share_network_id:
1146 msg = _("'share_network_id' parameter should be provided. ")
1147 raise exception.InvalidInput(reason=msg)
1149 def error(msg, *args):
1150 LOG.error(msg, *args)
1151 self.db.share_group_update(
1152 context, share_group_ref['id'],
1153 {'status': constants.STATUS_ERROR})
1155 @utils.synchronized("share_manager_%s" % share_network_id,
1156 external=True)
1157 def _wrapped_provide_share_server_for_share_group():
1158 # NOTE(felipe_rodrigues): it can retrieve the available share
1159 # servers using one single subnet_id from the availability zone
1160 # subnets, because if the share server has one, it will have
1161 # all subnets on that availability zone.
1162 share_network_subnet_id = share_network_subnets[0]['id']
1163 try:
1164 available_share_servers = (
1165 self.db
1166 .share_server_get_all_by_host_and_share_subnet_valid(
1167 context, self.host, share_network_subnet_id))
1168 except exception.ShareServerNotFound:
1169 available_share_servers = None
1171 compatible_share_server = None
1173 if available_share_servers: 1173 ↛ 1178line 1173 didn't jump to line 1178 because the condition on line 1173 was always true
1174 available_share_servers = (
1175 self._check_share_server_backend_limits(
1176 context, available_share_servers))
1178 choose_share_server = (
1179 self.driver.choose_share_server_compatible_with_share_group)
1181 if available_share_servers: 1181 ↛ 1192line 1181 didn't jump to line 1192 because the condition on line 1181 was always true
1182 try:
1183 compatible_share_server = choose_share_server(
1184 context, available_share_servers, share_group_ref,
1185 share_group_snapshot=share_group_snapshot,
1186 )
1187 except Exception as e:
1188 with excutils.save_and_reraise_exception():
1189 error("Cannot choose compatible share-server: %s",
1190 e)
1192 if not compatible_share_server:
1193 compatible_share_server = self.db.share_server_create(
1194 context,
1195 {
1196 'host': self.host,
1197 'share_network_subnets': share_network_subnets,
1198 'status': constants.STATUS_CREATING,
1199 'security_service_update_support': (
1200 self.driver.security_service_update_support),
1201 'network_allocation_update_support': (
1202 self.driver.network_allocation_update_support),
1203 'share_replicas_migration_support': (
1204 self.driver.share_replicas_migration_support),
1205 }
1206 )
1208 msg = ("Using share_server %(share_server)s for share "
1209 "group %(group_id)s")
1210 LOG.debug(msg, {
1211 'share_server': compatible_share_server['id'],
1212 'group_id': share_group_ref['id']
1213 })
1215 updated_share_group = self.db.share_group_update(
1216 context,
1217 share_group_ref['id'],
1218 {'share_server_id': compatible_share_server['id']},
1219 )
1221 if compatible_share_server['status'] == constants.STATUS_CREATING:
1222 # Create share server on backend with data from db.
1223 metadata = self._build_server_metadata(
1224 context,
1225 share_group_ref['host'],
1226 share_group_ref['share_types'][0]['share_type_id'])
1227 compatible_share_server = self._setup_server(
1228 context, compatible_share_server, metadata)
1229 LOG.info("Share server created successfully.")
1230 else:
1231 LOG.info("Used preexisting share server "
1232 "'%(share_server_id)s'",
1233 {'share_server_id': compatible_share_server['id']})
1234 return compatible_share_server, updated_share_group
1236 return _wrapped_provide_share_server_for_share_group()
1238 def _get_share_server(self, context, share_instance):
1239 if share_instance['share_server_id']:
1240 return self.db.share_server_get(
1241 context, share_instance['share_server_id'])
1242 else:
1243 return None
1245 @utils.require_driver_initialized
1246 def connection_get_info(self, context, share_instance_id):
1247 share_instance = self.db.share_instance_get(
1248 context, share_instance_id, with_share_data=True)
1250 share_server = None
1251 if share_instance.get('share_server_id'): 1251 ↛ 1255line 1251 didn't jump to line 1255 because the condition on line 1251 was always true
1252 share_server = self.db.share_server_get(
1253 context, share_instance['share_server_id'])
1255 return self.driver.connection_get_info(context, share_instance,
1256 share_server)
1258 def _migration_start_driver(
1259 self, context, share_ref, src_share_instance, dest_host, writable,
1260 preserve_metadata, nondisruptive, preserve_snapshots,
1261 new_share_network_id, new_az_id, new_share_type_id):
1263 share_server = self._get_share_server(context, src_share_instance)
1265 request_spec, dest_share_instance = (
1266 self.share_api.create_share_instance_and_get_request_spec(
1267 context, share_ref, new_az_id, None, dest_host,
1268 new_share_network_id, new_share_type_id))
1270 self.db.share_instance_update(
1271 context, dest_share_instance['id'],
1272 {'status': constants.STATUS_MIGRATING_TO})
1274 # refresh and obtain proxified properties
1275 dest_share_instance = self.db.share_instance_get(
1276 context, dest_share_instance['id'], with_share_data=True)
1278 helper = migration.ShareMigrationHelper(
1279 context, self.db, self.access_helper)
1281 try:
1282 if dest_share_instance['share_network_id']:
1283 # NOTE(carloss): For a nondisruptive migration request, we must
1284 # not change the share server, otherwise the share's export
1285 # location will change, disconnecting the user. Disruptive
1286 # migration requests the share server from the driver.
1287 if nondisruptive:
1288 dest_share_server = self._get_share_server_dict(
1289 context, share_server)
1290 dest_share_instance = self.db.share_instance_update(
1291 context,
1292 dest_share_instance['id'],
1293 {'share_server_id': dest_share_server['id']},
1294 with_share_data=True
1295 )
1296 else:
1297 rpcapi = share_rpcapi.ShareAPI()
1299 # NOTE(ganso): Obtaining the share_server_id asynchronously
1300 # so we can wait for it to be ready.
1301 dest_share_server_id = rpcapi.provide_share_server(
1302 context, dest_share_instance,
1303 dest_share_instance['share_network_id'])
1305 rpcapi.create_share_server(
1306 context, dest_share_instance, dest_share_server_id)
1308 dest_share_server = helper.wait_for_share_server(
1309 dest_share_server_id)
1311 else:
1312 dest_share_server = None
1314 compatibility = self.driver.migration_check_compatibility(
1315 context, src_share_instance, dest_share_instance,
1316 share_server, dest_share_server)
1318 if not compatibility.get('compatible'):
1319 msg = _("Destination host %(host)s is not compatible with "
1320 "share %(share)s's source backend for driver-assisted "
1321 "migration.") % {
1322 'host': dest_host,
1323 'share': share_ref['id'],
1324 }
1325 raise exception.ShareMigrationFailed(reason=msg)
1327 if (not compatibility.get('nondisruptive') and 1327 ↛ 1329line 1327 didn't jump to line 1329 because the condition on line 1327 was never true
1328 nondisruptive):
1329 msg = _("Driver cannot perform a non-disruptive migration of "
1330 "share %s.") % share_ref['id']
1331 raise exception.ShareMigrationFailed(reason=msg)
1333 if (not compatibility.get('preserve_metadata') and
1334 preserve_metadata):
1335 msg = _("Driver cannot perform migration of share %s while "
1336 "preserving all metadata.") % share_ref['id']
1337 raise exception.ShareMigrationFailed(reason=msg)
1339 if not compatibility.get('writable') and writable:
1340 msg = _("Driver cannot perform migration of share %s while "
1341 "remaining writable.") % share_ref['id']
1342 raise exception.ShareMigrationFailed(reason=msg)
1344 if (not compatibility.get('preserve_snapshots') and
1345 preserve_snapshots):
1346 msg = _("Driver cannot perform migration of share %s while "
1347 "preserving snapshots.") % share_ref['id']
1348 raise exception.ShareMigrationFailed(reason=msg)
1350 snapshot_mapping = {}
1351 src_snap_instances = []
1352 src_snapshots = self.db.share_snapshot_get_all_for_share(
1353 context, share_ref['id'])
1355 if compatibility.get('preserve_snapshots'):
1357 # Make sure all snapshots are 'available'
1358 if any(x['status'] != constants.STATUS_AVAILABLE
1359 for x in src_snapshots):
1360 msg = _(
1361 "All snapshots must have '%(status)s' status to be "
1362 "migrated by the driver along with share "
1363 "%(share)s.") % {
1364 'share': share_ref['id'],
1365 'status': constants.STATUS_AVAILABLE
1366 }
1367 raise exception.ShareMigrationFailed(reason=msg)
1369 src_snap_instances = [x.instance for x in src_snapshots]
1371 dest_snap_instance_data = {
1372 'status': constants.STATUS_MIGRATING_TO,
1373 'progress': '0%',
1374 'share_instance_id': dest_share_instance['id'],
1375 }
1377 for snap_instance in src_snap_instances:
1378 snapshot_mapping[snap_instance['id']] = (
1379 self.db.share_snapshot_instance_create(
1380 context, snap_instance['snapshot_id'],
1381 dest_snap_instance_data))
1382 self.db.share_snapshot_instance_update(
1383 context, snap_instance['id'],
1384 {'status': constants.STATUS_MIGRATING})
1386 else:
1387 if src_snapshots:
1388 msg = _("Driver does not support preserving snapshots, "
1389 "driver-assisted migration cannot proceed while "
1390 "share %s has snapshots.") % share_ref['id']
1391 raise exception.ShareMigrationFailed(reason=msg)
1393 if not compatibility.get('writable'):
1394 self._cast_access_rules_to_readonly(
1395 context, src_share_instance, share_server)
1397 LOG.debug("Initiating driver migration for share %s.",
1398 share_ref['id'])
1400 self.db.share_update(
1401 context, share_ref['id'],
1402 {'task_state': (
1403 constants.TASK_STATE_MIGRATION_DRIVER_STARTING)})
1405 self.driver.migration_start(
1406 context, src_share_instance, dest_share_instance,
1407 src_snap_instances, snapshot_mapping, share_server,
1408 dest_share_server)
1410 self.db.share_update(
1411 context, share_ref['id'],
1412 {'task_state': (
1413 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS)})
1415 except Exception:
1416 # NOTE(ganso): Cleaning up error'ed destination share instance from
1417 # database. It is assumed that driver cleans up leftovers in
1418 # backend when migration fails.
1419 share_types.revert_allocated_share_type_quotas_during_migration(
1420 context, src_share_instance,
1421 src_share_instance['share_type_id'])
1422 self._migration_delete_instance(context, dest_share_instance['id'])
1423 self._restore_migrating_snapshots_status(
1424 context, src_share_instance['id'])
1426 # NOTE(ganso): Read only access rules and share instance status
1427 # will be restored in migration_start's except block.
1429 # NOTE(ganso): For now source share instance should remain in
1430 # migrating status for host-assisted migration.
1431 msg = _("Driver-assisted migration of share %s "
1432 "failed.") % share_ref['id']
1433 LOG.exception(msg)
1434 raise exception.ShareMigrationFailed(reason=msg)
1436 return True
1438 def _cast_access_rules_to_readonly(self, context, src_share_instance,
1439 share_server, dest_host=None):
1440 self._cast_access_rules_to_readonly_for_server(
1441 context, [src_share_instance], share_server, dest_host=dest_host)
1443 def _cast_access_rules_to_readonly_for_server(
1444 self, context, src_share_instances, share_server, dest_host=None):
1445 for src_share_instance in src_share_instances:
1446 self.db.share_instance_update(
1447 context, src_share_instance['id'],
1448 {'cast_rules_to_readonly': True})
1450 # Set all 'applying' or 'active' rules to 'queued_to_apply'. Since
1451 # the share instance has its cast_rules_to_readonly attribute set
1452 # to True, existing rules will be cast to read/only.
1453 acceptable_past_states = (constants.ACCESS_STATE_APPLYING,
1454 constants.ACCESS_STATE_ACTIVE)
1455 new_state = constants.ACCESS_STATE_QUEUED_TO_APPLY
1456 conditionally_change = {k: new_state
1457 for k in acceptable_past_states}
1458 self.access_helper.get_and_update_share_instance_access_rules(
1459 context, share_instance_id=src_share_instance['id'],
1460 conditionally_change=conditionally_change)
1462 src_share_instance_ids = [x.id for x in src_share_instances]
1463 share_server_id = share_server['id'] if share_server else None
1464 if dest_host: 1464 ↛ 1465line 1464 didn't jump to line 1465 because the condition on line 1464 was never true
1465 rpcapi = share_rpcapi.ShareAPI()
1466 rpcapi.update_access_for_instances(context,
1467 dest_host,
1468 src_share_instance_ids,
1469 share_server_id)
1470 else:
1471 self.update_access_for_instances(context, src_share_instance_ids,
1472 share_server_id=share_server_id)
1474 for src_share_instance in src_share_instances:
1475 utils.wait_for_access_update(
1476 context, self.db, src_share_instance,
1477 self.migration_wait_access_rules_timeout)
1479 def _reset_read_only_access_rules(
1480 self, context, share_instance_id, supress_errors=True,
1481 helper=None):
1482 instance = self.db.share_instance_get(context, share_instance_id,
1483 with_share_data=True)
1484 share_server = self._get_share_server(context, instance)
1485 self._reset_read_only_access_rules_for_server(
1486 context, [instance], share_server, supress_errors, helper)
1488 def _reset_read_only_access_rules_for_server(
1489 self, context, share_instances, share_server,
1490 supress_errors=True, helper=None, dest_host=None):
1491 if helper is None: 1491 ↛ 1495line 1491 didn't jump to line 1495 because the condition on line 1491 was always true
1492 helper = migration.ShareMigrationHelper(
1493 context, self.db, self.access_helper)
1495 instances_to_update = []
1496 for share_instance in share_instances:
1497 instance = self.db.share_instance_get(context,
1498 share_instance['id'],
1499 with_share_data=True)
1500 if instance['cast_rules_to_readonly']: 1500 ↛ 1496line 1500 didn't jump to line 1496 because the condition on line 1500 was always true
1501 update = {'cast_rules_to_readonly': False}
1502 instances_to_update.append(share_instance)
1504 self.db.share_instance_update(
1505 context, share_instance['id'], update)
1507 if instances_to_update: 1507 ↛ exitline 1507 didn't return from function '_reset_read_only_access_rules_for_server' because the condition on line 1507 was always true
1508 if supress_errors:
1509 helper.cleanup_access_rules(instances_to_update,
1510 share_server,
1511 dest_host)
1512 else:
1513 helper.revert_access_rules(instances_to_update,
1514 share_server,
1515 dest_host)
1517 @periodic_task.periodic_task(
1518 spacing=CONF.migration_driver_continue_update_interval)
1519 @utils.require_driver_initialized
1520 def migration_driver_continue(self, context):
1521 """Invokes driver to continue migration of shares."""
1523 instances = self.db.share_instance_get_all_by_host(
1524 context, self.host, with_share_data=True)
1526 for instance in instances:
1528 if instance['status'] != constants.STATUS_MIGRATING:
1529 continue
1531 share = self.db.share_get(context, instance['share_id'])
1533 if share['task_state'] == ( 1533 ↛ 1526line 1533 didn't jump to line 1526 because the condition on line 1533 was always true
1534 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS):
1536 src_share_instance_id, dest_share_instance_id = (
1537 self.share_api.get_migrating_instances(share))
1539 src_share_instance = instance
1541 dest_share_instance = self.db.share_instance_get(
1542 context, dest_share_instance_id, with_share_data=True)
1544 src_share_server = self._get_share_server(
1545 context, src_share_instance)
1547 dest_share_server = self._get_share_server(
1548 context, dest_share_instance)
1550 src_snap_instances, snapshot_mappings = (
1551 self._get_migrating_snapshots(context, src_share_instance,
1552 dest_share_instance))
1554 try:
1556 finished = self.driver.migration_continue(
1557 context, src_share_instance, dest_share_instance,
1558 src_snap_instances, snapshot_mappings,
1559 src_share_server, dest_share_server)
1561 if finished:
1562 self.db.share_update(
1563 context, instance['share_id'],
1564 {'task_state':
1565 (constants.
1566 TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE)})
1568 LOG.info("Share Migration for share %s completed "
1569 "first phase successfully.",
1570 share['id'])
1571 else:
1572 share = self.db.share_get(
1573 context, instance['share_id'])
1575 if (share['task_state'] == 1575 ↛ 1526line 1575 didn't jump to line 1526 because the condition on line 1575 was always true
1576 constants.TASK_STATE_MIGRATION_CANCELLED):
1577 LOG.warning(
1578 "Share Migration for share %s was cancelled.",
1579 share['id'])
1581 except Exception:
1583 (share_types.
1584 revert_allocated_share_type_quotas_during_migration(
1585 context, src_share_instance,
1586 dest_share_instance['share_type_id']))
1587 # NOTE(ganso): Cleaning up error'ed destination share
1588 # instance from database. It is assumed that driver cleans
1589 # up leftovers in backend when migration fails.
1590 self._migration_delete_instance(
1591 context, dest_share_instance['id'])
1592 self._restore_migrating_snapshots_status(
1593 context, src_share_instance['id'])
1594 self._reset_read_only_access_rules(
1595 context, src_share_instance_id)
1596 self.db.share_instance_update(
1597 context, src_share_instance_id,
1598 {'status': constants.STATUS_AVAILABLE})
1600 self.db.share_update(
1601 context, instance['share_id'],
1602 {'task_state': constants.TASK_STATE_MIGRATION_ERROR})
1603 msg = _("Driver-assisted migration of share %s "
1604 "failed.") % share['id']
1605 LOG.exception(msg)
1607 def _get_migrating_snapshots(
1608 self, context, src_share_instance, dest_share_instance):
1610 dest_snap_instances = (
1611 self.db.share_snapshot_instance_get_all_with_filters(
1612 context,
1613 {'share_instance_ids': [dest_share_instance['id']]}))
1615 snapshot_mappings = {}
1616 src_snap_instances = []
1617 if len(dest_snap_instances) > 0:
1618 src_snap_instances = (
1619 self.db.share_snapshot_instance_get_all_with_filters(
1620 context,
1621 {'share_instance_ids': [src_share_instance['id']]}))
1622 for snap in src_snap_instances:
1623 dest_snap_instance = next(
1624 x for x in dest_snap_instances
1625 if snap['snapshot_id'] == x['snapshot_id'])
1626 snapshot_mappings[snap['id']] = dest_snap_instance
1628 return src_snap_instances, snapshot_mappings
1630 def _restore_migrating_snapshots_status(
1631 self, context, src_share_instance_id,
1632 errored_dest_instance_id=None):
1633 filters = {'share_instance_ids': [src_share_instance_id]}
1634 status = constants.STATUS_AVAILABLE
1635 if errored_dest_instance_id:
1636 filters['share_instance_ids'].append(errored_dest_instance_id)
1637 status = constants.STATUS_ERROR
1638 snap_instances = (
1639 self.db.share_snapshot_instance_get_all_with_filters(
1640 context, filters)
1641 )
1642 for instance in snap_instances:
1643 if instance['status'] == constants.STATUS_MIGRATING:
1644 self.db.share_snapshot_instance_update(
1645 context, instance['id'], {'status': status})
1646 elif (errored_dest_instance_id and 1646 ↛ 1642line 1646 didn't jump to line 1642 because the condition on line 1646 was always true
1647 instance['status'] == constants.STATUS_MIGRATING_TO):
1648 self.db.share_snapshot_instance_update(
1649 context, instance['id'], {'status': status})
1651 @utils.require_driver_initialized
1652 def migration_start(
1653 self, context, share_id, dest_host, force_host_assisted_migration,
1654 preserve_metadata, writable, nondisruptive, preserve_snapshots,
1655 new_share_network_id=None, new_share_type_id=None):
1656 """Migrates a share from current host to another host."""
1657 LOG.debug("Entered migration_start method for share %s.", share_id)
1659 self.db.share_update(
1660 context, share_id,
1661 {'task_state': constants.TASK_STATE_MIGRATION_IN_PROGRESS})
1663 share_ref = self.db.share_get(context, share_id)
1664 share_instance = self._get_share_instance(context, share_ref)
1665 success = False
1667 host_value = share_utils.extract_host(dest_host)
1668 service = self.db.service_get_by_args(
1669 context, host_value, 'manila-share')
1670 new_az_id = service['availability_zone_id']
1672 if not force_host_assisted_migration:
1674 try:
1675 success = self._migration_start_driver(
1676 context, share_ref, share_instance, dest_host, writable,
1677 preserve_metadata, nondisruptive, preserve_snapshots,
1678 new_share_network_id, new_az_id, new_share_type_id)
1680 except Exception as e:
1681 if not isinstance(e, NotImplementedError): 1681 ↛ 1686line 1681 didn't jump to line 1686 because the condition on line 1681 was always true
1682 LOG.exception(
1683 ("The driver could not migrate the share %(shr)s"),
1684 {'shr': share_id})
1686 try:
1688 if not success:
1689 if (writable or preserve_metadata or nondisruptive or
1690 preserve_snapshots):
1691 msg = _("Migration for share %s could not be "
1692 "performed because host-assisted migration is not "
1693 "allowed when share must remain writable, "
1694 "preserve snapshots and/or file metadata or be "
1695 "performed nondisruptively.") % share_id
1697 raise exception.ShareMigrationFailed(reason=msg)
1699 # We only handle shares without snapshots for now
1700 snaps = self.db.share_snapshot_get_all_for_share(
1701 context, share_id)
1702 if snaps:
1703 msg = _("Share %s must not have snapshots in order to "
1704 "perform a host-assisted migration.") % share_id
1705 raise exception.ShareMigrationFailed(reason=msg)
1707 LOG.debug("Starting host-assisted migration "
1708 "for share %s.", share_id)
1710 self.db.share_update(
1711 context, share_id,
1712 {'task_state': constants.TASK_STATE_MIGRATION_IN_PROGRESS})
1714 self._migration_start_host_assisted(
1715 context, share_ref, share_instance, dest_host,
1716 new_share_network_id, new_az_id, new_share_type_id)
1718 except Exception:
1719 msg = _("Host-assisted migration failed for share %s.") % share_id
1720 LOG.exception(msg)
1721 self.db.share_update(
1722 context, share_id,
1723 {'task_state': constants.TASK_STATE_MIGRATION_ERROR})
1724 self._reset_read_only_access_rules(
1725 context, share_instance['id'])
1726 self.db.share_instance_update(
1727 context, share_instance['id'],
1728 {'status': constants.STATUS_AVAILABLE})
1730 raise exception.ShareMigrationFailed(reason=msg)
1732 def _migration_start_host_assisted(
1733 self, context, share, src_share_instance, dest_host,
1734 new_share_network_id, new_az_id, new_share_type_id):
1736 rpcapi = share_rpcapi.ShareAPI()
1738 helper = migration.ShareMigrationHelper(
1739 context, self.db, self.access_helper)
1741 share_server = self._get_share_server(context.elevated(),
1742 src_share_instance)
1744 self._cast_access_rules_to_readonly(
1745 context, src_share_instance, share_server)
1747 try:
1748 dest_share_instance = helper.create_instance_and_wait(
1749 share, dest_host, new_share_network_id, new_az_id,
1750 new_share_type_id)
1752 self.db.share_instance_update(
1753 context, dest_share_instance['id'],
1754 {'status': constants.STATUS_MIGRATING_TO})
1756 except Exception:
1757 msg = _("Failed to create instance on destination "
1758 "backend during migration of share %s.") % share['id']
1759 LOG.exception(msg)
1760 raise exception.ShareMigrationFailed(reason=msg)
1762 ignore_list = self.driver.configuration.safe_get(
1763 'migration_ignore_files')
1765 data_rpc = data_rpcapi.DataAPI()
1767 try:
1768 src_connection_info = self.driver.connection_get_info(
1769 context, src_share_instance, share_server)
1771 dest_connection_info = rpcapi.connection_get_info(
1772 context, dest_share_instance)
1774 LOG.debug("Time to start copying in migration"
1775 " for share %s.", share['id'])
1777 data_rpc.migration_start(
1778 context, share['id'], ignore_list, src_share_instance['id'],
1779 dest_share_instance['id'], src_connection_info,
1780 dest_connection_info)
1782 except Exception:
1783 msg = _("Failed to obtain migration info from backends or"
1784 " invoking Data Service for migration of "
1785 "share %s.") % share['id']
1786 LOG.exception(msg)
1787 helper.cleanup_new_instance(dest_share_instance)
1788 raise exception.ShareMigrationFailed(reason=msg)
1790 def _migration_complete_driver(
1791 self, context, share_ref, src_share_instance, dest_share_instance):
1793 share_server = self._get_share_server(context, src_share_instance)
1794 dest_share_server = self._get_share_server(
1795 context, dest_share_instance)
1797 self.db.share_update(
1798 context, share_ref['id'],
1799 {'task_state': constants.TASK_STATE_MIGRATION_COMPLETING})
1801 src_snap_instances, snapshot_mappings = (
1802 self._get_migrating_snapshots(context, src_share_instance,
1803 dest_share_instance))
1805 data_updates = self.driver.migration_complete(
1806 context, src_share_instance, dest_share_instance,
1807 src_snap_instances, snapshot_mappings, share_server,
1808 dest_share_server) or {}
1810 if data_updates.get('export_locations'): 1810 ↛ 1815line 1810 didn't jump to line 1815 because the condition on line 1810 was always true
1811 self.db.export_locations_update(
1812 context, dest_share_instance['id'],
1813 data_updates['export_locations'])
1815 snapshot_updates = data_updates.get('snapshot_updates') or {}
1817 dest_extra_specs = self._get_extra_specs_from_share_type(
1818 context, dest_share_instance['share_type_id'])
1820 for src_snap_ins, dest_snap_ins in snapshot_mappings.items():
1821 model_update = snapshot_updates.get(dest_snap_ins['id']) or {}
1822 snapshot_export_locations = model_update.pop(
1823 'export_locations', [])
1825 model_update['status'] = constants.STATUS_AVAILABLE
1826 model_update['progress'] = '100%'
1827 self.db.share_snapshot_instance_update(
1828 context, dest_snap_ins['id'], model_update)
1830 if dest_extra_specs['mount_snapshot_support']:
1832 for el in snapshot_export_locations:
1833 values = {
1834 'share_snapshot_instance_id': dest_snap_ins['id'],
1835 'path': el['path'],
1836 'is_admin_only': el['is_admin_only'],
1837 }
1838 self.db.share_snapshot_instance_export_location_create(
1839 context, values)
1841 helper = migration.ShareMigrationHelper(
1842 context, self.db, self.access_helper)
1844 helper.apply_new_access_rules(dest_share_instance, share_ref['id'])
1846 self._migration_complete_instance(context, share_ref,
1847 src_share_instance['id'],
1848 dest_share_instance['id'])
1850 share_types.revert_allocated_share_type_quotas_during_migration(
1851 context, dest_share_instance, src_share_instance['share_type_id'],
1852 allow_deallocate_from_current_type=True)
1854 self._migration_delete_instance(context, src_share_instance['id'])
1856 def _migration_complete_instance(self, context, share_ref,
1857 src_instance_id, dest_instance_id):
1858 dest_updates = {
1859 'status': constants.STATUS_AVAILABLE,
1860 'progress': '100%'
1861 }
1862 if share_ref.get('replication_type'):
1863 dest_updates['replica_state'] = constants.REPLICA_STATE_ACTIVE
1865 self.db.share_instance_update(context, dest_instance_id, dest_updates)
1867 self.db.share_instance_update(context, src_instance_id,
1868 {'status': constants.STATUS_INACTIVE})
1870 def _migration_delete_instance(self, context, instance_id):
1872 # refresh the share instance model
1873 share_instance = self.db.share_instance_get(
1874 context, instance_id, with_share_data=True)
1876 rules = self.access_helper.get_and_update_share_instance_access_rules(
1877 context, share_instance_id=instance_id)
1879 self.access_helper.delete_share_instance_access_rules(
1880 context, rules, instance_id)
1882 snap_instances = self.db.share_snapshot_instance_get_all_with_filters(
1883 context, {'share_instance_ids': [instance_id]})
1885 for instance in snap_instances:
1886 self.db.share_snapshot_instance_delete(context, instance['id'])
1888 self.db.share_instance_delete(context, instance_id)
1889 LOG.info("Share instance %s: deleted successfully.",
1890 instance_id)
1892 self._check_delete_share_server(context, share_instance=share_instance)
1894 @utils.require_driver_initialized
1895 def migration_complete(self, context, src_instance_id, dest_instance_id):
1897 src_share_instance = self.db.share_instance_get(
1898 context, src_instance_id, with_share_data=True)
1899 dest_share_instance = self.db.share_instance_get(
1900 context, dest_instance_id, with_share_data=True)
1902 share_ref = self.db.share_get(context, src_share_instance['share_id'])
1904 LOG.info("Received request to finish Share Migration for "
1905 "share %s.", share_ref['id'])
1907 if share_ref['task_state'] == (
1908 constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE):
1910 try:
1911 self._migration_complete_driver(
1912 context, share_ref, src_share_instance,
1913 dest_share_instance)
1915 except Exception:
1916 msg = _("Driver migration completion failed for"
1917 " share %s.") % share_ref['id']
1918 LOG.exception(msg)
1920 # NOTE(ganso): If driver fails during migration-complete,
1921 # all instances are set to error and it is up to the admin
1922 # to fix the problem to either complete migration
1923 # manually or clean it up. At this moment, data
1924 # preservation at the source backend cannot be
1925 # guaranteed.
1927 self._restore_migrating_snapshots_status(
1928 context, src_share_instance['id'],
1929 errored_dest_instance_id=dest_share_instance['id'])
1930 self.db.share_instance_update(
1931 context, src_instance_id,
1932 {'status': constants.STATUS_ERROR})
1933 self.db.share_instance_update(
1934 context, dest_instance_id,
1935 {'status': constants.STATUS_ERROR})
1936 self.db.share_update(
1937 context, share_ref['id'],
1938 {'task_state': constants.TASK_STATE_MIGRATION_ERROR})
1939 # NOTE(carloss): No need to deallocate quotas allocated during
1940 # the migration request, since both share instances still exist
1941 # even they are set to an error state.
1942 raise exception.ShareMigrationFailed(reason=msg)
1943 else:
1944 try:
1945 self._migration_complete_host_assisted(
1946 context, share_ref, src_instance_id,
1947 dest_instance_id)
1948 except Exception:
1949 msg = _("Host-assisted migration completion failed for"
1950 " share %s.") % share_ref['id']
1951 LOG.exception(msg)
1952 # NOTE(carloss): No need to deallocate quotas allocated during
1953 # the migration request, since both source and destination
1954 # instances will still exist
1955 self.db.share_update(
1956 context, share_ref['id'],
1957 {'task_state': constants.TASK_STATE_MIGRATION_ERROR})
1958 self.db.share_instance_update(
1959 context, src_instance_id,
1960 {'status': constants.STATUS_AVAILABLE})
1961 raise exception.ShareMigrationFailed(reason=msg)
1963 model_update = self._get_extra_specs_from_share_type(
1964 context, dest_share_instance['share_type_id'])
1966 model_update['task_state'] = constants.TASK_STATE_MIGRATION_SUCCESS
1968 self.db.share_update(
1969 context, dest_share_instance['share_id'], model_update)
1971 LOG.info("Share Migration for share %s"
1972 " completed successfully.", share_ref['id'])
1974 def _get_extra_specs_from_share_type(self, context, share_type_id):
1976 share_type = share_types.get_share_type(context, share_type_id)
1978 return self.share_api.get_share_attributes_from_share_type(share_type)
1980 def _migration_complete_host_assisted(self, context, share_ref,
1981 src_instance_id, dest_instance_id):
1983 src_share_instance = self.db.share_instance_get(
1984 context, src_instance_id, with_share_data=True)
1985 dest_share_instance = self.db.share_instance_get(
1986 context, dest_instance_id, with_share_data=True)
1988 helper = migration.ShareMigrationHelper(
1989 context, self.db, self.access_helper)
1991 task_state = share_ref['task_state']
1992 if task_state in (constants.TASK_STATE_DATA_COPYING_ERROR,
1993 constants.TASK_STATE_DATA_COPYING_CANCELLED):
1994 msg = _("Data copy of host assisted migration for share %s has not"
1995 " completed successfully.") % share_ref['id']
1996 LOG.warning(msg)
1997 helper.cleanup_new_instance(dest_share_instance)
1998 cancelled = (
1999 task_state == constants.TASK_STATE_DATA_COPYING_CANCELLED)
2000 suppress_errors = True
2001 if cancelled:
2002 suppress_errors = False
2003 self._reset_read_only_access_rules(
2004 context, src_instance_id,
2005 supress_errors=suppress_errors, helper=helper)
2006 self.db.share_instance_update(
2007 context, src_instance_id,
2008 {'status': constants.STATUS_AVAILABLE})
2009 if cancelled:
2010 self.db.share_update(
2011 context, share_ref['id'],
2012 {'task_state': constants.TASK_STATE_MIGRATION_CANCELLED})
2014 LOG.info("Share Migration for share %s"
2015 " was cancelled.", share_ref['id'])
2016 return
2017 else:
2018 raise exception.ShareMigrationFailed(reason=msg)
2020 elif task_state != constants.TASK_STATE_DATA_COPYING_COMPLETED:
2021 msg = _("Data copy for migration of share %s has not completed"
2022 " yet.") % share_ref['id']
2023 LOG.error(msg)
2024 raise exception.ShareMigrationFailed(reason=msg)
2026 self.db.share_update(
2027 context, share_ref['id'],
2028 {'task_state': constants.TASK_STATE_MIGRATION_COMPLETING})
2030 try:
2031 helper.apply_new_access_rules(dest_share_instance, share_ref['id'])
2032 except Exception:
2033 msg = _("Failed to apply new access rules during migration "
2034 "of share %s.") % share_ref['id']
2035 LOG.exception(msg)
2036 helper.cleanup_new_instance(dest_share_instance)
2037 self._reset_read_only_access_rules(
2038 context, src_instance_id, helper=helper,
2039 supress_errors=True)
2040 self.db.share_instance_update(
2041 context, src_instance_id,
2042 {'status': constants.STATUS_AVAILABLE})
2044 raise exception.ShareMigrationFailed(reason=msg)
2046 self._migration_complete_instance(context, share_ref,
2047 src_share_instance['id'],
2048 dest_share_instance['id'])
2050 # NOTE(carloss): Won't revert allocated quotas for the share type here
2051 # because the delete_instance_and_wait method will end up calling the
2052 # delete_share_instance method here in the share manager. When the
2053 # share instance deletion is requested in the share manager, Manila
2054 # itself will take care of deallocating the existing quotas for the
2055 # share instance
2056 helper.delete_instance_and_wait(src_share_instance)
2058 @utils.require_driver_initialized
2059 def migration_cancel(self, context, src_instance_id, dest_instance_id):
2061 src_share_instance = self.db.share_instance_get(
2062 context, src_instance_id, with_share_data=True)
2063 dest_share_instance = self.db.share_instance_get(
2064 context, dest_instance_id, with_share_data=True)
2066 share_ref = self.db.share_get(context, src_share_instance['share_id'])
2068 if share_ref['task_state'] not in (
2069 constants.TASK_STATE_DATA_COPYING_COMPLETED,
2070 constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE,
2071 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS):
2072 msg = _("Migration of share %s cannot be cancelled at this "
2073 "moment.") % share_ref['id']
2074 raise exception.InvalidShare(reason=msg)
2076 share_server = self._get_share_server(context, src_share_instance)
2078 dest_share_server = self._get_share_server(
2079 context, dest_share_instance)
2081 helper = migration.ShareMigrationHelper(
2082 context, self.db, self.access_helper)
2084 if share_ref['task_state'] == (
2085 constants.TASK_STATE_DATA_COPYING_COMPLETED):
2087 self.db.share_instance_update(
2088 context, dest_share_instance['id'],
2089 {'status': constants.STATUS_INACTIVE})
2091 helper.cleanup_new_instance(dest_share_instance)
2093 else:
2095 src_snap_instances, snapshot_mappings = (
2096 self._get_migrating_snapshots(context, src_share_instance,
2097 dest_share_instance))
2099 self.driver.migration_cancel(
2100 context, src_share_instance, dest_share_instance,
2101 src_snap_instances, snapshot_mappings, share_server,
2102 dest_share_server)
2104 self._migration_delete_instance(context, dest_share_instance['id'])
2105 self._restore_migrating_snapshots_status(
2106 context, src_share_instance['id'])
2108 self._reset_read_only_access_rules(
2109 context, src_instance_id, supress_errors=False,
2110 helper=helper)
2112 self.db.share_instance_update(
2113 context, src_instance_id,
2114 {'status': constants.STATUS_AVAILABLE})
2116 self.db.share_update(
2117 context, share_ref['id'],
2118 {'task_state': constants.TASK_STATE_MIGRATION_CANCELLED})
2120 share_types.revert_allocated_share_type_quotas_during_migration(
2121 context, src_share_instance, dest_share_instance['share_type_id'])
2123 LOG.info("Share Migration for share %s"
2124 " was cancelled.", share_ref['id'])
2126 @utils.require_driver_initialized
2127 def migration_get_progress(self, context, src_instance_id,
2128 dest_instance_id):
2130 src_share_instance = self.db.share_instance_get(
2131 context, src_instance_id, with_share_data=True)
2132 dest_share_instance = self.db.share_instance_get(
2133 context, dest_instance_id, with_share_data=True)
2135 share_ref = self.db.share_get(context, src_share_instance['share_id'])
2137 # Confirm that it is driver migration scenario
2138 if share_ref['task_state'] != (
2139 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS):
2140 msg = _("Driver is not performing migration for"
2141 " share %s at this moment.") % share_ref['id']
2142 raise exception.InvalidShare(reason=msg)
2144 share_server = None
2145 if share_ref.instance.get('share_server_id'): 2145 ↛ 2149line 2145 didn't jump to line 2149 because the condition on line 2145 was always true
2146 share_server = self.db.share_server_get(
2147 context, src_share_instance['share_server_id'])
2149 dest_share_server = None
2150 if dest_share_instance.get('share_server_id'): 2150 ↛ 2154line 2150 didn't jump to line 2154 because the condition on line 2150 was always true
2151 dest_share_server = self.db.share_server_get(
2152 context, dest_share_instance['share_server_id'])
2154 src_snap_instances, snapshot_mappings = (
2155 self._get_migrating_snapshots(context, src_share_instance,
2156 dest_share_instance))
2158 return self.driver.migration_get_progress(
2159 context, src_share_instance, dest_share_instance,
2160 src_snap_instances, snapshot_mappings, share_server,
2161 dest_share_server)
2163 def _get_share_instance(self, context, share):
2164 if isinstance(share, str):
2165 id = share
2166 else:
2167 id = share.instance['id']
2168 return self.db.share_instance_get(context, id, with_share_data=True)
2170 @add_hooks
2171 @utils.require_driver_initialized
2172 def create_share_instance(self, context, share_instance_id,
2173 request_spec=None, filter_properties=None,
2174 snapshot_id=None):
2175 """Creates a share instance."""
2176 context = context.elevated()
2178 share_instance = self._get_share_instance(context, share_instance_id)
2179 share_id = share_instance.get('share_id')
2180 share_network_id = share_instance.get('share_network_id')
2181 share = self.db.share_get(context, share_id)
2183 self._notify_about_share_usage(context, share,
2184 share_instance, "create.start")
2186 if not share_instance['availability_zone']:
2187 share_instance = self.db.share_instance_update(
2188 context, share_instance_id,
2189 {'availability_zone': self.availability_zone},
2190 with_share_data=True
2191 )
2193 if share_network_id and not self.driver.driver_handles_share_servers:
2194 self.db.share_instance_update(
2195 context, share_instance_id, {'status': constants.STATUS_ERROR})
2196 self.message_api.create(
2197 context,
2198 message_field.Action.CREATE,
2199 share['project_id'],
2200 resource_type=message_field.Resource.SHARE,
2201 resource_id=share_id,
2202 detail=message_field.Detail.UNEXPECTED_NETWORK)
2203 raise exception.ManilaException(_(
2204 "Creation of share instance %s failed: driver does not expect "
2205 "share-network to be provided with current "
2206 "configuration.") % share_instance_id)
2208 if snapshot_id is not None:
2209 snapshot_ref = self.db.share_snapshot_get(context, snapshot_id)
2210 parent_share_server_id = (
2211 snapshot_ref['share']['instance']['share_server_id'])
2212 else:
2213 snapshot_ref = None
2214 parent_share_server_id = None
2216 share_group_ref = None
2217 if share_instance.get('share_group_id'): 2217 ↛ 2218line 2217 didn't jump to line 2218 because the condition on line 2217 was never true
2218 share_group_ref = self.db.share_group_get(
2219 context, share_instance['share_group_id'])
2221 if share_network_id or parent_share_server_id:
2222 try:
2223 share_server, share_instance = (
2224 self._provide_share_server_for_share(
2225 context, share_network_id, share_instance,
2226 snapshot=snapshot_ref,
2227 share_group=share_group_ref,
2228 )
2229 )
2230 except exception.PortLimitExceeded:
2231 with excutils.save_and_reraise_exception():
2232 error = ("Creation of share instance %s failed: "
2233 "failed to allocate network")
2234 LOG.error(error, share_instance_id)
2235 self.db.share_instance_update(
2236 context, share_instance_id,
2237 {'status': constants.STATUS_ERROR}
2238 )
2239 self.message_api.create(
2240 context,
2241 message_field.Action.CREATE,
2242 share['project_id'],
2243 resource_type=message_field.Resource.SHARE,
2244 resource_id=share_id,
2245 detail=(message_field.Detail
2246 .SHARE_NETWORK_PORT_QUOTA_LIMIT_EXCEEDED))
2247 except exception.SecurityServiceFailedAuth:
2248 with excutils.save_and_reraise_exception():
2249 error = ("Provision of share server failed: "
2250 "failed to authenticate user "
2251 "against security server.")
2252 LOG.error(error)
2253 self.db.share_instance_update(
2254 context, share_instance_id,
2255 {'status': constants.STATUS_ERROR}
2256 )
2257 self.message_api.create(
2258 context,
2259 message_field.Action.CREATE,
2260 share['project_id'],
2261 resource_type=message_field.Resource.SHARE,
2262 resource_id=share_id,
2263 detail=(message_field.Detail
2264 .SECURITY_SERVICE_FAILED_AUTH))
2265 except exception.IpAddressGenerationFailureClient:
2266 with excutils.save_and_reraise_exception():
2267 error = ("Creation of share instance %s failed: "
2268 "No Free IP's in neutron subnet.")
2269 LOG.error(error, share_instance_id)
2270 self.db.share_instance_update(
2271 context, share_instance_id,
2272 {'status': constants.STATUS_ERROR}
2273 )
2274 self.message_api.create(
2275 context,
2276 message_field.Action.CREATE,
2277 share['project_id'],
2278 resource_type=message_field.Resource.SHARE,
2279 resource_id=share_id,
2280 detail=message_field.Detail.NEUTRON_SUBNET_FULL)
2281 except Exception:
2282 with excutils.save_and_reraise_exception():
2283 error = ("Creation of share instance %s failed: "
2284 "failed to get share server.")
2285 LOG.error(error, share_instance_id)
2286 self.db.share_instance_update(
2287 context, share_instance_id,
2288 {'status': constants.STATUS_ERROR}
2289 )
2290 self.message_api.create(
2291 context,
2292 message_field.Action.CREATE,
2293 share['project_id'],
2294 resource_type=message_field.Resource.SHARE,
2295 resource_id=share_id,
2296 detail=message_field.Detail.NO_SHARE_SERVER)
2298 else:
2299 share_server = None
2301 if share_network_id and self.driver.driver_handles_share_servers:
2302 proto = share_instance.get('share_proto').lower()
2303 ret_types = (
2304 self.driver.dhss_mandatory_security_service_association.get(
2305 proto))
2306 if ret_types:
2307 share_network = self.db.share_network_get(context,
2308 share_network_id)
2309 share_network_ss = []
2310 for security_service in share_network['security_services']:
2311 share_network_ss.append(security_service['type'].lower())
2312 for types in ret_types: 2312 ↛ 2331line 2312 didn't jump to line 2331 because the loop on line 2312 didn't complete
2313 if types not in share_network_ss:
2314 self.db.share_instance_update(
2315 context, share_instance_id,
2316 {'status': constants.STATUS_ERROR}
2317 )
2318 self.message_api.create(
2319 context,
2320 message_field.Action.CREATE,
2321 share['project_id'],
2322 resource_type=message_field.Resource.SHARE,
2323 resource_id=share_id,
2324 detail=(message_field.Detail
2325 .MISSING_SECURITY_SERVICE))
2326 raise exception.InvalidRequest(_(
2327 "Share network security service association is "
2328 "mandatory for protocol %s.") %
2329 share_instance.get('share_proto'))
2331 status = constants.STATUS_AVAILABLE
2332 try:
2333 if snapshot_ref:
2334 # NOTE(dviroel): we need to provide the parent share info to
2335 # assist drivers that create shares from snapshot in different
2336 # pools or back ends
2337 parent_share_instance = self.db.share_instance_get(
2338 context, snapshot_ref['share']['instance']['id'],
2339 with_share_data=True)
2340 parent_share_dict = self._get_share_instance_dict(
2341 context, parent_share_instance)
2342 model_update = self.driver.create_share_from_snapshot(
2343 context, share_instance, snapshot_ref.instance,
2344 share_server=share_server, parent_share=parent_share_dict)
2345 if isinstance(model_update, list):
2346 # NOTE(dviroel): the driver that doesn't implement the new
2347 # model_update will return only the export locations
2348 export_locations = model_update
2349 else:
2350 # NOTE(dviroel): share status is mandatory when answering
2351 # a model update. If not provided, won't be possible to
2352 # determine if was successfully created.
2353 status = model_update.get('status')
2354 if status is None: 2354 ↛ 2355line 2354 didn't jump to line 2355 because the condition on line 2354 was never true
2355 msg = _("Driver didn't provide a share status.")
2356 raise exception.InvalidShareInstance(reason=msg)
2357 export_locations = model_update.get('export_locations')
2358 else:
2359 export_locations = self.driver.create_share(
2360 context, share_instance, share_server=share_server)
2361 if status not in [constants.STATUS_AVAILABLE,
2362 constants.STATUS_CREATING_FROM_SNAPSHOT]:
2363 msg = _('Driver returned an invalid status: %s') % status
2364 raise exception.InvalidShareInstance(reason=msg)
2366 share_backend_info = (
2367 self.driver.get_optional_share_creation_data(
2368 share, share_server=share_server))
2369 if share_backend_info:
2370 metadata_updates = share_backend_info.get("metadata")
2371 if metadata_updates: 2371 ↛ 2374line 2371 didn't jump to line 2374 because the condition on line 2371 was always true
2372 self.db.share_metadata_update(
2373 context, share_id, metadata_updates, False)
2374 if export_locations:
2375 self.db.export_locations_update(
2376 context, share_instance['id'], export_locations)
2378 except Exception as e:
2379 with excutils.save_and_reraise_exception():
2380 LOG.error("Share instance %s failed on creation.",
2381 share_instance_id)
2382 detail_data = getattr(e, 'detail_data', {})
2384 def get_export_location(details):
2385 if not isinstance(details, dict): 2385 ↛ 2386line 2385 didn't jump to line 2386 because the condition on line 2385 was never true
2386 return None
2387 return details.get('export_locations',
2388 details.get('export_location'))
2390 export_locations = get_export_location(detail_data)
2392 if export_locations:
2393 self.db.export_locations_update(
2394 context, share_instance['id'], export_locations)
2395 else:
2396 LOG.warning('Share instance information in exception '
2397 'can not be written to db because it '
2398 'contains %s and it is not a dictionary.',
2399 detail_data)
2400 self.db.share_instance_update(
2401 context, share_instance_id,
2402 {'status': constants.STATUS_ERROR}
2403 )
2404 self.message_api.create(
2405 context,
2406 message_field.Action.CREATE,
2407 share['project_id'],
2408 resource_type=message_field.Resource.SHARE,
2409 resource_id=share_id,
2410 exception=e)
2411 else:
2412 LOG.info("Share instance %s created successfully.",
2413 share_instance_id)
2414 progress = '100%' if status == constants.STATUS_AVAILABLE else '0%'
2415 updates = {
2416 'status': status,
2417 'launched_at': timeutils.utcnow(),
2418 'progress': progress
2419 }
2420 if share.get('replication_type'):
2421 updates['replica_state'] = constants.REPLICA_STATE_ACTIVE
2423 self.db.share_instance_update(context, share_instance_id, updates)
2425 self._notify_about_share_usage(context, share,
2426 share_instance, "create.end")
2428 def _update_share_instance_access_rules_state(self, context,
2429 share_instance_id, state):
2430 """Update the access_rules_status for the share instance."""
2431 self.access_helper.get_and_update_share_instance_access_rules_status(
2432 context, status=state, share_instance_id=share_instance_id)
2434 def _get_replica_snapshots_for_snapshot(self, context, snapshot_id,
2435 active_replica_id,
2436 share_replica_id,
2437 with_share_data=True):
2438 """Return dict of snapshot instances of active and replica instances.
2440 This method returns a dict of snapshot instances for snapshot
2441 referred to by snapshot_id. The dict contains the snapshot instance
2442 pertaining to the 'active' replica and the snapshot instance
2443 pertaining to the replica referred to by share_replica_id.
2444 """
2445 filters = {
2446 'snapshot_ids': snapshot_id,
2447 'share_instance_ids': (share_replica_id, active_replica_id),
2448 }
2449 instance_list = self.db.share_snapshot_instance_get_all_with_filters(
2450 context, filters, with_share_data=with_share_data)
2452 snapshots = {
2453 'active_replica_snapshot': self._get_snapshot_instance_dict(
2454 context,
2455 list(filter(lambda x:
2456 x['share_instance_id'] == active_replica_id,
2457 instance_list))[0]),
2458 'share_replica_snapshot': self._get_snapshot_instance_dict(
2459 context,
2460 list(filter(lambda x:
2461 x['share_instance_id'] == share_replica_id,
2462 instance_list))[0]),
2463 }
2465 return snapshots
2467 @add_hooks
2468 @utils.require_driver_initialized
2469 @locked_share_replica_operation
2470 def create_share_replica(self, context, share_replica_id, share_id=None,
2471 request_spec=None, filter_properties=None):
2472 """Create a share replica."""
2473 context = context.elevated()
2474 share_replica = self.db.share_replica_get(
2475 context, share_replica_id, with_share_data=True,
2476 with_share_server=True)
2478 if not share_replica['availability_zone']:
2479 share_replica = self.db.share_replica_update(
2480 context, share_replica['id'],
2481 {'availability_zone': self.availability_zone},
2482 with_share_data=True
2483 )
2485 _active_replica = (
2486 self.db.share_replicas_get_available_active_replica(
2487 context, share_replica['share_id'], with_share_data=True,
2488 with_share_server=True))
2489 if not _active_replica:
2490 self.db.share_replica_update(
2491 context, share_replica['id'],
2492 {'status': constants.STATUS_ERROR,
2493 'replica_state': constants.STATUS_ERROR})
2494 self.message_api.create(
2495 context,
2496 message_field.Action.CREATE,
2497 share_replica['project_id'],
2498 resource_type=message_field.Resource.SHARE_REPLICA,
2499 resource_id=share_replica['id'],
2500 detail=message_field.Detail.NO_ACTIVE_REPLICA)
2501 msg = _("An 'active' replica must exist in 'available' "
2502 "state to create a new replica for share %s.")
2503 raise exception.ReplicationException(
2504 reason=msg % share_replica['share_id'])
2506 # We need the share_network_id in case of
2507 # driver_handles_share_server=True
2508 share_network_id = share_replica.get('share_network_id', None)
2509 if xor(bool(share_network_id),
2510 self.driver.driver_handles_share_servers):
2511 self.db.share_replica_update(
2512 context, share_replica['id'],
2513 {'status': constants.STATUS_ERROR,
2514 'replica_state': constants.STATUS_ERROR})
2515 self.message_api.create(
2516 context,
2517 message_field.Action.CREATE,
2518 share_replica['project_id'],
2519 resource_type=message_field.Resource.SHARE_REPLICA,
2520 resource_id=share_replica['id'],
2521 detail=message_field.Detail.UNEXPECTED_NETWORK)
2522 raise exception.InvalidDriverMode(
2523 "The share-network value provided does not match with the "
2524 "current driver configuration.")
2526 if share_network_id: 2526 ↛ 2548line 2526 didn't jump to line 2548 because the condition on line 2526 was always true
2527 try:
2528 share_server, share_replica = (
2529 self._provide_share_server_for_share(
2530 context, share_network_id, share_replica)
2531 )
2532 except Exception:
2533 with excutils.save_and_reraise_exception():
2534 LOG.error("Failed to get share server "
2535 "for share replica creation.")
2536 self.db.share_replica_update(
2537 context, share_replica['id'],
2538 {'status': constants.STATUS_ERROR,
2539 'replica_state': constants.STATUS_ERROR})
2540 self.message_api.create(
2541 context,
2542 message_field.Action.CREATE,
2543 share_replica['project_id'],
2544 resource_type=message_field.Resource.SHARE_REPLICA,
2545 resource_id=share_replica['id'],
2546 detail=message_field.Detail.NO_SHARE_SERVER)
2547 else:
2548 share_server = None
2550 # Map the existing access rules for the share to
2551 # the replica in the DB.
2552 share_access_rules = self.db.share_instance_access_copy(
2553 context, share_replica['share_id'], share_replica['id'])
2555 # Get snapshots for the share.
2556 share_snapshots = self.db.share_snapshot_get_all_for_share(
2557 context, share_id)
2558 # Get the required data for snapshots that have 'aggregate_status'
2559 # set to 'available'.
2560 available_share_snapshots = [
2561 self._get_replica_snapshots_for_snapshot(
2562 context, x['id'], _active_replica['id'], share_replica_id)
2563 for x in share_snapshots
2564 if x['aggregate_status'] == constants.STATUS_AVAILABLE]
2566 replica_list = (
2567 self.db.share_replicas_get_all_by_share(
2568 context, share_replica['share_id'],
2569 with_share_data=True, with_share_server=True)
2570 )
2572 replica_list = [self._get_share_instance_dict(context, r)
2573 for r in replica_list]
2574 share_replica = self._get_share_instance_dict(context, share_replica)
2576 try:
2577 replica_ref = self.driver.create_replica(
2578 context, replica_list, share_replica,
2579 share_access_rules, available_share_snapshots,
2580 share_server=share_server) or {}
2582 except Exception as excep:
2583 with excutils.save_and_reraise_exception():
2584 LOG.error("Share replica %s failed on creation.",
2585 share_replica['id'])
2586 self.db.share_replica_update(
2587 context, share_replica['id'],
2588 {'status': constants.STATUS_ERROR,
2589 'replica_state': constants.STATUS_ERROR})
2590 self._update_share_instance_access_rules_state(
2591 context, share_replica['id'], constants.STATUS_ERROR)
2592 self.message_api.create(
2593 context,
2594 message_field.Action.CREATE,
2595 share_replica['project_id'],
2596 resource_type=message_field.Resource.SHARE_REPLICA,
2597 resource_id=share_replica['id'],
2598 exception=excep)
2600 if replica_ref.get('export_locations'): 2600 ↛ 2610line 2600 didn't jump to line 2610 because the condition on line 2600 was always true
2601 if isinstance(replica_ref.get('export_locations'), list):
2602 self.db.export_locations_update(
2603 context, share_replica['id'],
2604 replica_ref.get('export_locations'))
2605 else:
2606 msg = ('Invalid export locations passed to the share '
2607 'manager.')
2608 LOG.warning(msg)
2610 if replica_ref.get('replica_state'):
2611 self.db.share_replica_update(
2612 context, share_replica['id'],
2613 {'status': constants.STATUS_AVAILABLE,
2614 'replica_state': replica_ref.get('replica_state'),
2615 'progress': '100%'})
2617 reported_access_rules_status = replica_ref.get('access_rules_status')
2618 if reported_access_rules_status in (None, "active"): 2618 ↛ 2628line 2618 didn't jump to line 2628 because the condition on line 2618 was always true
2619 # update all rules to "active"
2620 conditionally_change = {'queued_to_apply': 'active'}
2621 self.access_helper.get_and_update_share_instance_access_rules(
2622 context, share_instance_id=share_replica['id'],
2623 conditionally_change=conditionally_change)
2624 # update "access_rules_status" on the replica
2625 self._update_share_instance_access_rules_state(
2626 context, share_replica['id'],
2627 constants.STATUS_ACTIVE)
2628 elif replica_ref.get('share_access_rules'):
2629 # driver would like to update individual access rules
2630 share_access_rules_dict = {
2631 rule['id']: rule for rule in share_access_rules}
2632 for rule_update in replica_ref.get('share_access_rules'):
2633 self.access_helper.get_and_update_share_instance_access_rule(
2634 context,
2635 rule_update['id'],
2636 {'state': rule_update['state']},
2637 share_instance_id=share_replica['id'])
2638 share_access_rules_dict.pop(rule_update['id'])
2639 for rule_id in share_access_rules_dict:
2640 self.access_helper.get_and_update_share_instance_access_rule(
2641 context,
2642 rule_id,
2643 {'state': 'active'},
2644 share_instance_id=share_replica['id'])
2645 self._update_share_instance_access_rules_state(
2646 context, share_replica['id'],
2647 replica_ref.get('access_rules_status'))
2649 LOG.info("Share replica %s created successfully.",
2650 share_replica['id'])
2652 @add_hooks
2653 @utils.require_driver_initialized
2654 @locked_share_replica_operation
2655 def delete_share_replica(self, context, share_replica_id, share_id=None,
2656 force=False):
2657 """Delete a share replica."""
2658 context = context.elevated()
2659 share_replica = self.db.share_replica_get(
2660 context, share_replica_id, with_share_data=True,
2661 with_share_server=True)
2663 # Grab all the snapshot instances that belong to this replica.
2664 replica_snapshots = (
2665 self.db.share_snapshot_instance_get_all_with_filters(
2666 context, {'share_instance_ids': share_replica_id},
2667 with_share_data=True)
2668 )
2670 replica_list = (
2671 self.db.share_replicas_get_all_by_share(
2672 context, share_replica['share_id'],
2673 with_share_data=True, with_share_server=True)
2674 )
2676 replica_list = [self._get_share_instance_dict(context, r)
2677 for r in replica_list]
2678 replica_snapshots = [self._get_snapshot_instance_dict(context, s)
2679 for s in replica_snapshots]
2680 share_server = self._get_share_server(context, share_replica)
2681 share_replica = self._get_share_instance_dict(context, share_replica)
2683 try:
2684 self.access_helper.update_access_rules(
2685 context,
2686 share_replica_id,
2687 delete_all_rules=True,
2688 share_server=share_server
2689 )
2690 except Exception as excep:
2691 with excutils.save_and_reraise_exception() as exc_context:
2692 # Set status to 'error' from 'deleting' since
2693 # access_rules_status has been set to 'error'.
2694 self.db.share_replica_update(
2695 context, share_replica['id'],
2696 {'status': constants.STATUS_ERROR})
2697 self.message_api.create(
2698 context,
2699 message_field.Action.DELETE_ACCESS_RULES,
2700 share_replica['project_id'],
2701 resource_type=message_field.Resource.SHARE_REPLICA,
2702 resource_id=share_replica['id'],
2703 exception=excep)
2704 if force:
2705 msg = _("The driver was unable to delete access rules "
2706 "for the replica: %s. Will attempt to delete "
2707 "the replica anyway.")
2708 LOG.exception(msg, share_replica['id'])
2709 exc_context.reraise = False
2711 try:
2712 self.driver.delete_replica(
2713 context, replica_list, replica_snapshots, share_replica,
2714 share_server=share_server)
2715 except Exception as excep:
2716 with excutils.save_and_reraise_exception() as exc_context:
2717 if force:
2718 msg = _("The driver was unable to delete the share "
2719 "replica: %s on the backend. Since "
2720 "this operation is forced, the replica will be "
2721 "deleted from Manila's database. A cleanup on "
2722 "the backend may be necessary.")
2723 LOG.exception(msg, share_replica['id'])
2724 exc_context.reraise = False
2725 else:
2726 self.db.share_replica_update(
2727 context, share_replica['id'],
2728 {'status': constants.STATUS_ERROR_DELETING,
2729 'replica_state': constants.STATUS_ERROR})
2730 self.message_api.create(
2731 context,
2732 message_field.Action.DELETE,
2733 share_replica['project_id'],
2734 resource_type=message_field.Resource.SHARE_REPLICA,
2735 resource_id=share_replica['id'],
2736 exception=excep)
2738 for replica_snapshot in replica_snapshots:
2739 self.db.share_snapshot_instance_delete(
2740 context, replica_snapshot['id'])
2742 self.db.share_replica_delete(context, share_replica['id'])
2743 LOG.info("Share replica %s deleted successfully.",
2744 share_replica['id'])
2746 @add_hooks
2747 @utils.require_driver_initialized
2748 @locked_share_replica_operation
2749 def promote_share_replica(self, context, share_replica_id, share_id=None,
2750 quiesce_wait_time=None):
2751 """Promote a share replica to active state."""
2752 context = context.elevated()
2753 share_replica = self.db.share_replica_get(
2754 context, share_replica_id, with_share_data=True,
2755 with_share_server=True)
2756 replication_type = share_replica['replication_type']
2757 if replication_type == constants.REPLICATION_TYPE_READABLE:
2758 ensure_old_active_replica_to_readonly = True
2759 else:
2760 ensure_old_active_replica_to_readonly = False
2761 share_server = self._get_share_server(context, share_replica)
2763 # Get list of all replicas for share
2764 replica_list = (
2765 self.db.share_replicas_get_all_by_share(
2766 context, share_replica['share_id'],
2767 with_share_data=True, with_share_server=True)
2768 )
2770 try:
2771 old_active_replica = list(filter(
2772 lambda r: (
2773 r['replica_state'] == constants.REPLICA_STATE_ACTIVE),
2774 replica_list))[0]
2775 except IndexError:
2776 self.db.share_replica_update(
2777 context, share_replica['id'],
2778 {'status': constants.STATUS_AVAILABLE})
2779 msg = _("Share %(share)s has no replica with 'replica_state' "
2780 "set to %(state)s. Promoting %(replica)s is not "
2781 "possible.")
2782 self.message_api.create(
2783 context,
2784 message_field.Action.PROMOTE,
2785 share_replica['project_id'],
2786 resource_type=message_field.Resource.SHARE_REPLICA,
2787 resource_id=share_replica['id'],
2788 detail=message_field.Detail.NO_ACTIVE_REPLICA)
2789 raise exception.ReplicationException(
2790 reason=msg % {'share': share_replica['share_id'],
2791 'state': constants.REPLICA_STATE_ACTIVE,
2792 'replica': share_replica['id']})
2794 access_rules = self.db.share_access_get_all_for_share(
2795 context, share_replica['share_id'])
2797 replica_list = [self._get_share_instance_dict(context, r)
2798 for r in replica_list]
2799 share_replica = self._get_share_instance_dict(context, share_replica)
2801 try:
2802 updated_replica_list = (
2803 self.driver.promote_replica(
2804 context, replica_list, share_replica, access_rules,
2805 share_server=share_server,
2806 quiesce_wait_time=quiesce_wait_time)
2807 )
2808 except Exception as excep:
2809 with excutils.save_and_reraise_exception():
2810 # (NOTE) gouthamr: If the driver throws an exception at
2811 # this stage, there is a good chance that the replicas are
2812 # somehow altered on the backend. We loop through the
2813 # replicas and set their 'status's to 'error' and
2814 # leave the 'replica_state' unchanged. This also changes the
2815 # 'status' of the replica that failed to promote to 'error' as
2816 # before this operation. The backend may choose to update
2817 # the actual replica_state during the replica_monitoring
2818 # stage.
2819 updates = {'status': constants.STATUS_ERROR}
2820 for replica_ref in replica_list:
2821 self.db.share_replica_update(
2822 context, replica_ref['id'], updates)
2823 self.message_api.create(
2824 context,
2825 message_field.Action.PROMOTE,
2826 replica_ref['project_id'],
2827 resource_type=message_field.Resource.SHARE_REPLICA,
2828 resource_id=replica_ref['id'],
2829 exception=excep)
2831 # Set any 'creating' snapshots on the currently active replica to
2832 # 'error' since we cannot guarantee they will finish 'creating'.
2833 active_replica_snapshot_instances = (
2834 self.db.share_snapshot_instance_get_all_with_filters(
2835 context, {'share_instance_ids': share_replica['id']})
2836 )
2837 for instance in active_replica_snapshot_instances:
2838 if instance['status'] in (constants.STATUS_CREATING,
2839 constants.STATUS_DELETING):
2840 msg = ("The replica snapshot instance %(instance)s was "
2841 "in %(state)s. Since it was not in %(available)s "
2842 "state when the replica was promoted, it will be "
2843 "set to %(error)s.")
2844 payload = {
2845 'instance': instance['id'],
2846 'state': instance['status'],
2847 'available': constants.STATUS_AVAILABLE,
2848 'error': constants.STATUS_ERROR,
2849 }
2850 LOG.info(msg, payload)
2851 self.db.share_snapshot_instance_update(
2852 context, instance['id'],
2853 {'status': constants.STATUS_ERROR})
2855 if not updated_replica_list:
2856 self.db.share_replica_update(
2857 context, old_active_replica['id'],
2858 {'replica_state': constants.REPLICA_STATE_OUT_OF_SYNC,
2859 'cast_rules_to_readonly':
2860 ensure_old_active_replica_to_readonly})
2861 self.db.share_replica_update(
2862 context, share_replica['id'],
2863 {'status': constants.STATUS_AVAILABLE,
2864 'replica_state': constants.REPLICA_STATE_ACTIVE,
2865 'cast_rules_to_readonly': False})
2866 else:
2867 while updated_replica_list:
2868 # NOTE(vponomaryov): update 'active' replica last.
2869 for updated_replica in updated_replica_list: 2869 ↛ 2876line 2869 didn't jump to line 2876 because the loop on line 2869 didn't complete
2870 if (updated_replica['id'] == share_replica['id'] and
2871 len(updated_replica_list) > 1):
2872 continue
2873 updated_replica_list.remove(updated_replica)
2874 break
2876 updated_export_locs = updated_replica.get(
2877 'export_locations')
2878 if updated_export_locs is not None \
2879 and isinstance(updated_export_locs, list):
2880 self.db.export_locations_update(
2881 context, updated_replica['id'],
2882 updated_export_locs)
2884 updated_replica_state = updated_replica.get(
2885 'replica_state')
2886 updates = {}
2887 # Change the promoted replica's status from 'available' to
2888 # 'replication_change' and unset cast_rules_to_readonly
2889 if updated_replica['id'] == share_replica['id']:
2890 updates['cast_rules_to_readonly'] = False
2891 updates['status'] = constants.STATUS_AVAILABLE
2892 elif updated_replica['id'] == old_active_replica['id']:
2893 updates['cast_rules_to_readonly'] = (
2894 ensure_old_active_replica_to_readonly)
2895 if updated_replica_state == constants.STATUS_ERROR: 2895 ↛ 2896line 2895 didn't jump to line 2896 because the condition on line 2895 was never true
2896 updates['status'] = constants.STATUS_ERROR
2897 if updated_replica_state is not None:
2898 updates['replica_state'] = updated_replica_state
2899 if updates:
2900 self.db.share_replica_update(
2901 context, updated_replica['id'], updates)
2903 if updated_replica.get('access_rules_status'): 2903 ↛ 2904line 2903 didn't jump to line 2904 because the condition on line 2903 was never true
2904 self._update_share_instance_access_rules_state(
2905 context, share_replica['id'],
2906 updated_replica.get('access_rules_status'))
2908 LOG.info("Share replica %s: promoted to active state "
2909 "successfully.", share_replica['id'])
2911 @periodic_task.periodic_task(spacing=CONF.replica_state_update_interval)
2912 @utils.require_driver_initialized
2913 def periodic_share_replica_update(self, context):
2914 LOG.debug("Updating status of share replica instances.")
2915 # we will need: id, host, replica_state, share_id
2916 replicas = self.db.share_replicas_get_all(context,
2917 with_share_data=False,
2918 with_share_server=False)
2920 # Filter only non-active replicas belonging to this backend
2921 def qualified_replica(r):
2922 return (share_utils.extract_host(r['host']) ==
2923 share_utils.extract_host(self.host) and
2924 r['replica_state'] != constants.REPLICA_STATE_ACTIVE)
2926 replicas = list(filter(lambda x: qualified_replica(x), replicas))
2927 for replica in replicas:
2928 self._share_replica_update(
2929 context, replica['id'], share_id=replica['share_id'])
2931 @add_hooks
2932 @utils.require_driver_initialized
2933 def update_share_replica(self, context, share_replica_id, share_id=None):
2934 """Initiated by the force_update API."""
2935 self._share_replica_update(
2936 context, share_replica_id, share_id=share_id)
2938 @locked_share_replica_operation
2939 def _share_replica_update(self, context, share_replica_id, share_id=None):
2940 # share_id is used by the locked_share_replica_operation decorator
2941 # Grab the replica:
2942 try:
2943 # _get_share_instance_dict will fetch share server
2944 share_replica = self.db.share_replica_get(
2945 context, share_replica_id, with_share_data=True,
2946 with_share_server=False)
2947 except exception.ShareReplicaNotFound:
2948 # Replica may have been deleted, nothing to do here
2949 return
2951 # We don't poll for replicas that are busy in some operation,
2952 # or if they are the 'active' instance.
2953 if (share_replica['status'] in constants.TRANSITIONAL_STATUSES
2954 or share_replica['status'] == constants.STATUS_ERROR_DELETING
2955 or share_replica['replica_state'] ==
2956 constants.REPLICA_STATE_ACTIVE):
2957 return
2959 share_server = self._get_share_server(context, share_replica)
2961 access_rules = self.db.share_access_get_all_for_share(
2962 context, share_replica['share_id'])
2964 LOG.debug("Updating status of share share_replica %s: ",
2965 share_replica['id'])
2967 # _get_share_instance_dict will fetch share server
2968 replica_list = (
2969 self.db.share_replicas_get_all_by_share(
2970 context, share_replica['share_id'],
2971 with_share_data=True, with_share_server=False)
2972 )
2974 _active_replica = next((x for x in replica_list
2975 if x['replica_state'] ==
2976 constants.REPLICA_STATE_ACTIVE), None)
2978 if _active_replica is None: 2978 ↛ 2979line 2978 didn't jump to line 2979 because the condition on line 2978 was never true
2979 if share_replica['replica_state'] != constants.STATUS_ERROR:
2980 # only log warning if replica_state was not already in error
2981 msg = (("Replica parent share %(id)s has no active "
2982 "replica.") % {'id': share_replica['share_id']})
2983 LOG.warning(msg)
2984 self.db.share_replica_update(context, share_replica['id'],
2985 {'replica_state':
2986 constants.STATUS_ERROR})
2987 # without a related active replica, we cannot act on any
2988 # non-active replica
2989 return
2991 # Get snapshots for the share.
2992 share_snapshots = self.db.share_snapshot_get_all_for_share(
2993 context, share_replica['share_id'])
2995 # Get the required data for snapshots that have 'aggregate_status'
2996 # set to 'available'.
2997 available_share_snapshots = [
2998 self._get_replica_snapshots_for_snapshot(
2999 context, x['id'], _active_replica['id'], share_replica['id'])
3000 for x in share_snapshots
3001 if x['aggregate_status'] == constants.STATUS_AVAILABLE]
3003 replica_list = [self._get_share_instance_dict(context, r)
3004 for r in replica_list]
3006 share_replica = self._get_share_instance_dict(context, share_replica)
3008 try:
3009 replica_state = self.driver.update_replica_state(
3010 context, replica_list, share_replica, access_rules,
3011 available_share_snapshots, share_server=share_server)
3012 except Exception as excep:
3013 msg = ("Driver error when updating replica "
3014 "state for replica %s.")
3015 LOG.exception(msg, share_replica['id'])
3016 self.db.share_replica_update(
3017 context, share_replica['id'],
3018 {'replica_state': constants.STATUS_ERROR,
3019 'status': constants.STATUS_ERROR})
3020 self.message_api.create(
3021 context,
3022 message_field.Action.UPDATE,
3023 share_replica['project_id'],
3024 resource_type=message_field.Resource.SHARE_REPLICA,
3025 resource_id=share_replica['id'],
3026 exception=excep)
3027 return
3029 if replica_state in (constants.REPLICA_STATE_IN_SYNC,
3030 constants.REPLICA_STATE_OUT_OF_SYNC,
3031 constants.STATUS_ERROR):
3032 self.db.share_replica_update(context, share_replica['id'],
3033 {'replica_state': replica_state})
3034 elif replica_state:
3035 msg = (("Replica %(id)s cannot be set to %(state)s "
3036 "through update call.") %
3037 {'id': share_replica['id'], 'state': replica_state})
3038 LOG.warning(msg)
3040 def _validate_share_and_driver_mode(self, share_instance):
3041 driver_dhss = self.driver.driver_handles_share_servers
3043 share_dhss = share_types.parse_boolean_extra_spec(
3044 'driver_handles_share_servers',
3045 share_types.get_share_type_extra_specs(
3046 share_instance['share_type_id'],
3047 constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS))
3049 if driver_dhss != share_dhss:
3050 msg = _("Driver mode of share %(share)s being managed is "
3051 "incompatible with mode DHSS=%(dhss)s configured for"
3052 " this backend.") % {'share': share_instance['share_id'],
3053 'dhss': driver_dhss}
3054 raise exception.InvalidShare(reason=msg)
3056 return driver_dhss
3058 @add_hooks
3059 @utils.require_driver_initialized
3060 def manage_share(self, context, share_id, driver_options):
3061 context = context.elevated()
3062 share_ref = self.db.share_get(context, share_id)
3063 share_instance = self._get_share_instance(context, share_ref)
3064 share_type = share_types.get_share_type(
3065 context, share_instance['share_type_id'])
3066 share_type_extra_specs = self._get_extra_specs_from_share_type(
3067 context, share_instance['share_type_id'])
3068 share_type_supports_replication = share_type_extra_specs.get(
3069 'replication_type', None)
3071 project_id = share_ref['project_id']
3073 try:
3075 driver_dhss = self._validate_share_and_driver_mode(share_instance)
3077 if driver_dhss is True:
3078 share_server = self._get_share_server(context, share_instance)
3080 share_update = (
3081 self.driver.manage_existing_with_server(
3082 share_instance, driver_options, share_server)
3083 or {}
3084 )
3085 else:
3086 share_update = (
3087 self.driver.manage_existing(
3088 share_instance, driver_options)
3089 or {}
3090 )
3092 if not share_update.get('size'):
3093 # NOTE(haixin)if failed to get real size of share, will not
3094 # commit quota usages.
3095 msg = _("Driver cannot calculate share size.")
3096 raise exception.InvalidShare(reason=msg)
3097 else:
3098 share_types.provision_filter_on_size(context,
3099 share_type,
3100 share_update.get('size'))
3101 try:
3102 values = {'per_share_gigabytes': share_update.get('size')}
3103 QUOTAS.limit_check(context, project_id=context.project_id,
3104 **values)
3105 except exception.OverQuota as e:
3106 quotas = e.kwargs['quotas']
3107 LOG.warning("Requested share size %(size)d is larger than "
3108 "maximum allowed limit %(limit)d.",
3109 {'size': share_update.get('size'),
3110 'limit': quotas['per_share_gigabytes']})
3112 deltas = {
3113 'project_id': project_id,
3114 'user_id': context.user_id,
3115 'shares': 1,
3116 'gigabytes': share_update['size'],
3117 'share_type_id': share_instance['share_type_id'],
3118 }
3120 if share_type_supports_replication:
3121 deltas.update({'share_replicas': 1,
3122 'replica_gigabytes': share_update['size']})
3124 # NOTE(carloss): Allowing OverQuota to do not compromise this
3125 # operation. If this hit OverQuota error while managing a share,
3126 # the admin would need to reset the state of the share and
3127 # delete or force delete the share (bug 1863298). Allowing
3128 # OverQuota makes this operation work properly and the admin will
3129 # need to adjust quotas afterwards.
3130 reservations = QUOTAS.reserve(context, overquota_allowed=True,
3131 **deltas)
3132 QUOTAS.commit(
3133 context, reservations, project_id=project_id,
3134 share_type_id=share_instance['share_type_id'],
3135 )
3137 share_update.update({
3138 'status': constants.STATUS_AVAILABLE,
3139 'launched_at': timeutils.utcnow(),
3140 'availability_zone': self.availability_zone,
3141 })
3143 # If the share was managed with `replication_type` extra-spec, the
3144 # instance becomes an `active` replica.
3145 if share_ref.get('replication_type'):
3146 share_update['replica_state'] = constants.REPLICA_STATE_ACTIVE
3148 # NOTE(vponomaryov): we should keep only those export locations
3149 # that driver has calculated to avoid incompatibilities with one
3150 # provided by user.
3151 if 'export_locations' in share_update:
3152 self.db.export_locations_update(
3153 context, share_instance['id'],
3154 share_update.pop('export_locations'),
3155 delete=True)
3157 self.db.share_update(context, share_id, share_update)
3158 except Exception:
3159 # NOTE(haixin) we should set size 0 because we don't know the real
3160 # size of the size, and we will skip quota cuts when
3161 # delete/unmanage share.
3162 self.db.share_update(
3163 context, share_id,
3164 {'status': constants.STATUS_MANAGE_ERROR, 'size': 0})
3165 raise
3167 @add_hooks
3168 @utils.require_driver_initialized
3169 def manage_snapshot(self, context, snapshot_id, driver_options):
3171 context = context.elevated()
3172 snapshot_ref = self.db.share_snapshot_get(context, snapshot_id)
3174 snapshot_instance = self.db.share_snapshot_instance_get(
3175 context, snapshot_ref.instance['id'], with_share_data=True
3176 )
3177 project_id = snapshot_ref['project_id']
3179 driver_dhss = self.driver.driver_handles_share_servers
3181 try:
3182 if driver_dhss is True:
3184 share_server = self._get_share_server(context,
3185 snapshot_ref['share'])
3187 snapshot_update = (
3188 self.driver.manage_existing_snapshot_with_server(
3189 snapshot_instance, driver_options, share_server)
3190 or {}
3191 )
3192 else:
3193 snapshot_update = (
3194 self.driver.manage_existing_snapshot(
3195 snapshot_instance, driver_options)
3196 or {}
3197 )
3199 if not snapshot_update.get('size'): 3199 ↛ 3200line 3199 didn't jump to line 3200 because the condition on line 3199 was never true
3200 snapshot_update['size'] = snapshot_ref['share']['size']
3201 LOG.warning("Cannot get the size of the snapshot "
3202 "%(snapshot_id)s. Using the size of "
3203 "the share instead.",
3204 {'snapshot_id': snapshot_id})
3206 self._update_quota_usages(context, project_id, {
3207 "snapshots": 1,
3208 "snapshot_gigabytes": snapshot_update['size'],
3209 })
3211 snapshot_export_locations = snapshot_update.pop(
3212 'export_locations', [])
3214 if snapshot_instance['share']['mount_snapshot_support']:
3216 for el in snapshot_export_locations:
3217 values = {
3218 'share_snapshot_instance_id': snapshot_instance['id'],
3219 'path': el['path'],
3220 'is_admin_only': el['is_admin_only'],
3221 }
3223 self.db.share_snapshot_instance_export_location_create(
3224 context, values)
3226 snapshot_update.update({
3227 'status': constants.STATUS_AVAILABLE,
3228 'progress': '100%',
3229 })
3230 snapshot_update.pop('id', None)
3231 self.db.share_snapshot_update(context, snapshot_id,
3232 snapshot_update)
3233 except Exception:
3234 # NOTE(vponomaryov): set size as 1 because design expects size
3235 # to be set, it also will allow us to handle delete/unmanage
3236 # operations properly with this errored snapshot according to
3237 # quotas.
3238 self.db.share_snapshot_update(
3239 context, snapshot_id,
3240 {'status': constants.STATUS_MANAGE_ERROR, 'size': 1})
3241 raise
3243 def _update_quota_usages(self, context, project_id, usages):
3244 user_id = context.user_id
3245 for resource, usage in usages.items():
3246 try:
3247 current_usage = self.db.quota_usage_get(
3248 context, project_id, resource, user_id)
3249 self.db.quota_usage_update(
3250 context, project_id, user_id, resource,
3251 in_use=current_usage['in_use'] + usage)
3252 except exception.QuotaUsageNotFound:
3253 self.db.quota_usage_create(context, project_id,
3254 user_id, resource, usage)
3256 @add_hooks
3257 @utils.require_driver_initialized
3258 def unmanage_share(self, context, share_id):
3259 context = context.elevated()
3260 share_ref = self.db.share_get(context, share_id)
3261 share_instance = self._get_share_instance(context, share_ref)
3262 share_server = None
3263 project_id = share_ref['project_id']
3264 replicas = self.db.share_replicas_get_all_by_share(
3265 context, share_id)
3266 supports_replication = len(replicas) > 0
3268 def share_manage_set_error_status(msg, exception):
3269 status = {'status': constants.STATUS_UNMANAGE_ERROR}
3270 self.db.share_update(context, share_id, status)
3271 LOG.error(msg, exception)
3273 dhss = self.driver.driver_handles_share_servers
3275 try:
3276 if dhss is True:
3277 share_server = self._get_share_server(context, share_instance)
3278 self.driver.unmanage_with_server(share_instance, share_server)
3279 else:
3280 self.driver.unmanage(share_instance)
3282 except exception.InvalidShare as e:
3283 share_manage_set_error_status(
3284 ("Share can not be unmanaged: %s."), e)
3285 return
3287 # NOTE(haixin) we will skip quota cuts when unmanag share with
3288 # 'error_manage' status, because we have not commit quota usages when
3289 # we failed to manage the share.
3290 if share_ref['status'] != constants.STATUS_MANAGE_ERROR_UNMANAGING: 3290 ↛ 3316line 3290 didn't jump to line 3316 because the condition on line 3290 was always true
3291 deltas = {
3292 'project_id': project_id,
3293 'shares': -1,
3294 'gigabytes': -share_ref['size'],
3295 'share_type_id': share_instance['share_type_id'],
3296 }
3297 # NOTE(carloss): while unmanaging a share, a share will not
3298 # contain replicas other than the active one. So there is no need
3299 # to recalculate the amount of share replicas to be deallocated.
3300 if supports_replication:
3301 deltas.update({'share_replicas': -1,
3302 'replica_gigabytes': -share_ref['size']})
3303 try:
3304 reservations = QUOTAS.reserve(context, **deltas)
3305 QUOTAS.commit(
3306 context, reservations, project_id=project_id,
3307 share_type_id=share_instance['share_type_id'],
3308 )
3309 except Exception as e:
3310 # Note(imalinovskiy):
3311 # Quota reservation errors here are not fatal, because
3312 # unmanage is administrator API and he/she could update user
3313 # quota usages later if it's required.
3314 LOG.warning("Failed to update quota usages: %s.", e)
3316 if self.configuration.safe_get('unmanage_remove_access_rules'):
3317 try:
3318 self.access_helper.update_access_rules(
3319 context,
3320 share_instance['id'],
3321 delete_all_rules=True,
3322 share_server=share_server
3323 )
3324 except Exception as e:
3325 share_manage_set_error_status(
3326 ("Can not remove access rules of share: %s."), e)
3327 return
3329 self.db.share_instance_delete(context, share_instance['id'])
3331 # NOTE(ganso): Since we are unmanaging a share that is still within a
3332 # share server, we need to prevent the share server from being
3333 # auto-deleted.
3334 if share_server and share_server['is_auto_deletable']:
3335 self.db.share_server_update(context, share_server['id'],
3336 {'is_auto_deletable': False})
3337 msg = ("Since share %(share)s has been un-managed from share "
3338 "server %(server)s. This share server must be removed "
3339 "manually, either by un-managing or by deleting it. The "
3340 "share network subnets %(subnets)s and share network "
3341 "%(network)s cannot be deleted unless this share server "
3342 "has been removed.")
3343 msg_args = {
3344 'share': share_id,
3345 'server': share_server['id'],
3346 'subnets': share_server['share_network_subnet_ids'],
3347 'network': share_instance['share_network_id']
3348 }
3349 LOG.warning(msg, msg_args)
3351 LOG.info("Share %s: unmanaged successfully.", share_id)
3353 @add_hooks
3354 @utils.require_driver_initialized
3355 def unmanage_snapshot(self, context, snapshot_id):
3356 status = {'status': constants.STATUS_UNMANAGE_ERROR}
3358 context = context.elevated()
3359 snapshot_ref = self.db.share_snapshot_get(context, snapshot_id)
3360 share_server = self._get_share_server(context,
3361 snapshot_ref['share'])
3363 snapshot_instance = self.db.share_snapshot_instance_get(
3364 context, snapshot_ref.instance['id'], with_share_data=True
3365 )
3367 project_id = snapshot_ref['project_id']
3369 if self.configuration.safe_get('unmanage_remove_access_rules'):
3370 try:
3371 self.snapshot_access_helper.update_access_rules(
3372 context,
3373 snapshot_instance['id'],
3374 delete_all_rules=True,
3375 share_server=share_server)
3376 except Exception:
3377 LOG.exception(
3378 ("Cannot remove access rules of snapshot %s."),
3379 snapshot_id)
3380 self.db.share_snapshot_update(context, snapshot_id, status)
3381 return
3383 dhss = self.driver.driver_handles_share_servers
3385 try:
3386 if dhss:
3387 self.driver.unmanage_snapshot_with_server(
3388 snapshot_instance, share_server)
3389 else:
3390 self.driver.unmanage_snapshot(snapshot_instance)
3391 except exception.UnmanageInvalidShareSnapshot as e:
3392 self.db.share_snapshot_update(context, snapshot_id, status)
3393 LOG.error("Share snapshot cannot be unmanaged: %s.", e)
3394 return
3396 try:
3397 share_type_id = snapshot_ref['share']['instance']['share_type_id']
3398 reservations = QUOTAS.reserve(
3399 context,
3400 project_id=project_id,
3401 snapshots=-1,
3402 snapshot_gigabytes=-snapshot_ref['size'],
3403 share_type_id=share_type_id,
3404 )
3405 QUOTAS.commit(
3406 context, reservations, project_id=project_id,
3407 share_type_id=share_type_id,
3408 )
3409 except Exception as e:
3410 # Note(imalinovskiy):
3411 # Quota reservation errors here are not fatal, because
3412 # unmanage is administrator API and he/she could update user
3413 # quota usages later if it's required.
3414 LOG.warning("Failed to update quota usages: %s.", e)
3416 self.db.share_snapshot_instance_delete(
3417 context, snapshot_instance['id'])
3419 @add_hooks
3420 @utils.require_driver_initialized
3421 def manage_share_server(self, context, share_server_id, identifier,
3422 driver_opts):
3424 if self.driver.driver_handles_share_servers is False:
3425 msg = _("Cannot manage share server %s in a "
3426 "backend configured with driver_handles_share_servers"
3427 " set to False.") % share_server_id
3428 raise exception.ManageShareServerError(reason=msg)
3430 server = self.db.share_server_get(context, share_server_id)
3432 try:
3433 # NOTE(felipe_rodrigues): Manila does not support manage share
3434 # server with multiple allocations, so it can get the first
3435 # subnet_id element.
3436 share_network_subnet = self.db.share_network_subnet_get(
3437 context, server['share_network_subnet_ids'][0])
3438 share_network = self.db.share_network_get(
3439 context, share_network_subnet['share_network_id'])
3441 number_allocations = (
3442 self.driver.get_network_allocations_number())
3444 if self.driver.admin_network_api:
3445 number_allocations += (
3446 self.driver.get_admin_network_allocations_number())
3448 if number_allocations > 0: 3448 ↛ 3490line 3448 didn't jump to line 3490 because the condition on line 3448 was always true
3450 # allocations obtained from the driver that still need to
3451 # be validated
3452 remaining_allocations = (
3453 self.driver.get_share_server_network_info(
3454 context, server, identifier, driver_opts))
3456 if len(remaining_allocations) > 0:
3458 if self.driver.admin_network_api:
3459 remaining_allocations = (
3460 self.driver.admin_network_api.
3461 manage_network_allocations(
3462 context, remaining_allocations, server))
3464 # allocations that are managed are removed from
3465 # remaining_allocations
3467 remaining_allocations = (
3468 self.driver.network_api.
3469 manage_network_allocations(
3470 context, remaining_allocations, server,
3471 share_network, share_network_subnet))
3473 # We require that all allocations are managed, else we
3474 # may have problems deleting this share server
3475 if len(remaining_allocations) > 0:
3476 msg = ("Failed to manage all allocations. "
3477 "Allocations %s were not "
3478 "managed." % remaining_allocations)
3479 raise exception.ManageShareServerError(reason=msg)
3481 else:
3482 # if there should be allocations, but the driver
3483 # doesn't return any something is wrong
3485 msg = ("Driver did not return required network "
3486 "allocations to be managed. Required number "
3487 "of allocations is %s." % number_allocations)
3488 raise exception.ManageShareServerError(reason=msg)
3490 new_identifier, backend_details = self.driver.manage_server(
3491 context, server, identifier, driver_opts)
3493 if not new_identifier:
3494 new_identifier = server['id']
3496 if backend_details is None or not isinstance(
3497 backend_details, dict):
3498 backend_details = {}
3500 for security_service in share_network['security_services']:
3501 ss_type = security_service['type']
3502 data = {
3503 'name': security_service['name'],
3504 'ou': security_service['ou'],
3505 'default_ad_site': security_service['default_ad_site'],
3506 'domain': security_service['domain'],
3507 'server': security_service['server'],
3508 'dns_ip': security_service['dns_ip'],
3509 'user': security_service['user'],
3510 'type': ss_type,
3511 'password': security_service['password'],
3512 }
3513 backend_details.update({
3514 'security_service_' + ss_type: jsonutils.dumps(data)
3515 })
3517 if backend_details: 3517 ↛ 3521line 3517 didn't jump to line 3521 because the condition on line 3517 was always true
3518 self.db.share_server_backend_details_set(
3519 context, server['id'], backend_details)
3521 self.db.share_server_update(
3522 context, share_server_id,
3523 {'status': constants.STATUS_ACTIVE,
3524 'identifier': new_identifier,
3525 'network_allocation_update_support': (
3526 self.driver.network_allocation_update_support),
3527 'share_replicas_migration_support': (
3528 self.driver.share_replicas_migration_support)})
3530 except Exception:
3531 msg = "Error managing share server %s"
3532 LOG.exception(msg, share_server_id)
3533 self.db.share_server_update(
3534 context, share_server_id,
3535 {'status': constants.STATUS_MANAGE_ERROR})
3536 raise
3538 LOG.info("Share server %s managed successfully.", share_server_id)
3540 @add_hooks
3541 @utils.require_driver_initialized
3542 def unmanage_share_server(self, context, share_server_id, force=False):
3544 server = self.db.share_server_get(
3545 context, share_server_id)
3546 server_details = server['backend_details']
3548 security_services = []
3549 for ss_name in constants.SECURITY_SERVICES_ALLOWED_TYPES:
3550 ss = server_details.get('security_service_' + ss_name)
3551 if ss: 3551 ↛ 3549line 3551 didn't jump to line 3549 because the condition on line 3551 was always true
3552 security_services.append(jsonutils.loads(ss))
3554 try:
3555 self.driver.unmanage_server(server_details, security_services)
3556 except NotImplementedError:
3557 if not force:
3558 LOG.error("Did not unmanage share server %s since the driver "
3559 "does not support managing share servers and no "
3560 "``force`` option was supplied.",
3561 share_server_id)
3562 self.db.share_server_update(
3563 context, share_server_id,
3564 {'status': constants.STATUS_UNMANAGE_ERROR})
3565 return
3567 try:
3569 if self.driver.get_network_allocations_number() > 0:
3570 # NOTE(ganso): This will already remove admin allocations.
3571 self.driver.network_api.unmanage_network_allocations(
3572 context, share_server_id)
3573 elif (self.driver.get_admin_network_allocations_number() > 0
3574 and self.driver.admin_network_api):
3575 # NOTE(ganso): This is here in case there are only admin
3576 # allocations.
3577 self.driver.admin_network_api.unmanage_network_allocations(
3578 context, share_server_id)
3579 self.db.share_server_delete(context, share_server_id)
3580 except Exception:
3581 msg = "Error unmanaging share server %s"
3582 LOG.exception(msg, share_server_id)
3583 self.db.share_server_update(
3584 context, share_server_id,
3585 {'status': constants.STATUS_UNMANAGE_ERROR})
3586 raise
3588 LOG.info("Share server %s unmanaged successfully.", share_server_id)
3590 @add_hooks
3591 @utils.require_driver_initialized
3592 def revert_to_snapshot(self, context, snapshot_id,
3593 reservations):
3594 context = context.elevated()
3595 snapshot = self.db.share_snapshot_get(context, snapshot_id)
3596 share = snapshot['share']
3597 share_id = share['id']
3598 share_instance_id = snapshot.instance.share_instance_id
3599 share_access_rules = (
3600 self.access_helper.get_share_instance_access_rules(
3601 context, filters={'state': constants.STATUS_ACTIVE},
3602 share_instance_id=share_instance_id))
3603 snapshot_access_rules = (
3604 self.snapshot_access_helper.get_snapshot_instance_access_rules(
3605 context, snapshot.instance['id']))
3607 if share.get('has_replicas'):
3608 self._revert_to_replicated_snapshot(
3609 context, share, snapshot, reservations, share_access_rules,
3610 snapshot_access_rules, share_id=share_id)
3611 else:
3612 self._revert_to_snapshot(context, share, snapshot, reservations,
3613 share_access_rules, snapshot_access_rules)
3615 def _revert_to_snapshot(self, context, share, snapshot, reservations,
3616 share_access_rules, snapshot_access_rules):
3618 share_server = self._get_share_server(context, share)
3619 share_id = share['id']
3620 snapshot_id = snapshot['id']
3621 project_id = share['project_id']
3622 user_id = share['user_id']
3624 snapshot_instance = self.db.share_snapshot_instance_get(
3625 context, snapshot.instance['id'], with_share_data=True)
3626 share_type_id = snapshot_instance["share_instance"]["share_type_id"]
3628 # Make primitive to pass the information to the driver
3629 snapshot_instance_dict = self._get_snapshot_instance_dict(
3630 context, snapshot_instance, snapshot=snapshot)
3632 try:
3633 updated_share_size = self.driver.revert_to_snapshot(
3634 context,
3635 snapshot_instance_dict,
3636 share_access_rules,
3637 snapshot_access_rules,
3638 share_server=share_server)
3639 except Exception as excep:
3640 with excutils.save_and_reraise_exception():
3642 msg = ('Share %(share)s could not be reverted '
3643 'to snapshot %(snap)s.')
3644 msg_args = {'share': share_id, 'snap': snapshot_id}
3645 LOG.exception(msg, msg_args)
3647 if reservations:
3648 QUOTAS.rollback(
3649 context, reservations, project_id=project_id,
3650 user_id=user_id, share_type_id=share_type_id,
3651 )
3653 self.db.share_update(
3654 context, share_id,
3655 {'status': constants.STATUS_REVERTING_ERROR})
3656 self.db.share_snapshot_update(
3657 context, snapshot_id,
3658 {'status': constants.STATUS_AVAILABLE})
3659 self.message_api.create(
3660 context,
3661 message_field.Action.REVERT_TO_SNAPSHOT,
3662 share['project_id'],
3663 resource_type=message_field.Resource.SHARE,
3664 resource_id=share_id,
3665 exception=excep)
3667 # fail-safe in case driver returned size is None or invalid
3668 if not updated_share_size:
3669 updated_share_size = snapshot['size']
3670 else:
3671 try:
3672 int(updated_share_size)
3673 except ValueError:
3674 updated_share_size = snapshot['size']
3676 if reservations:
3677 if updated_share_size == snapshot['size']:
3678 QUOTAS.commit(
3679 context, reservations, project_id=project_id,
3680 user_id=user_id, share_type_id=share_type_id,
3681 )
3682 else:
3683 # NOTE(kpdev): The driver tells us that the share size wasn't
3684 # modified to the snapshot's size; so no need to commit quota
3685 # changes
3686 QUOTAS.rollback(
3687 context, reservations, project_id=project_id,
3688 user_id=user_id, share_type_id=share_type_id,
3689 )
3690 if updated_share_size != share['size']: 3690 ↛ 3691line 3690 didn't jump to line 3691 because the condition on line 3690 was never true
3691 LOG.error("Driver returned an unexpected size %d on "
3692 "revert to snapshot operation. You need to "
3693 "adjust the quota", updated_share_size)
3695 self.db.share_update(
3696 context, share_id,
3697 {'status': constants.STATUS_AVAILABLE, 'size': updated_share_size})
3698 self.db.share_snapshot_update(
3699 context, snapshot_id, {'status': constants.STATUS_AVAILABLE})
3701 msg = ('Share %(share)s reverted to snapshot %(snap)s '
3702 'successfully.')
3703 msg_args = {'share': share_id, 'snap': snapshot_id}
3704 LOG.info(msg, msg_args)
3706 def _get_share_details_from_instance(self, context, share_instance_id):
3707 share_instance = self._get_share_instance(context, share_instance_id)
3708 share = self.db.share_get(context, share_instance.get('share_id'))
3709 share_server = self._get_share_server(context, share_instance)
3710 return (share, share_instance, share_server)
3712 @add_hooks
3713 @utils.require_driver_initialized
3714 def delete_share_instance(self, context, share_instance_id, force=False,
3715 deferred_delete=False):
3716 """Delete a share instance."""
3717 context = context.elevated()
3718 share, share_instance, share_server = (
3719 self._get_share_details_from_instance(context, share_instance_id))
3720 self._notify_about_share_usage(context, share,
3721 share_instance, "delete.start")
3723 error_state = None
3724 if deferred_delete:
3725 try:
3726 self.db.update_share_instance_quota_usages(
3727 context, share_instance_id)
3728 LOG.info(
3729 "Share instance %s had its deletion deferred. Quota was "
3730 "reclaimed and the share driver will proceed with the "
3731 "deletion.", share_instance_id
3732 )
3733 except Exception:
3734 LOG.warning(
3735 "Error occurred during quota usage update. Administrator "
3736 "must rectify quotas.")
3738 snap_instances = (
3739 self.db.share_snapshot_instance_get_all_with_filters(
3740 context, {'share_instance_ids': share_instance_id}))
3741 if snap_instances: 3741 ↛ 3748line 3741 didn't jump to line 3748 because the condition on line 3741 was never true
3742 # The share has some snapshot instances whose deletion
3743 # was deferred. We relegate deletion of the share to
3744 # a periodic task so it can be processed after
3745 # all its snapshots are deleted. So we're deliberately
3746 # setting the share instance's status to
3747 # "error_deferred_deleting"
3748 self.db.share_instance_update(
3749 context,
3750 share_instance_id,
3751 {'status': constants.STATUS_ERROR_DEFERRED_DELETING})
3752 return
3754 try:
3755 self.access_helper.update_access_rules(
3756 context,
3757 share_instance_id,
3758 delete_all_rules=True,
3759 share_server=share_server
3760 )
3761 except exception.ShareResourceNotFound:
3762 LOG.warning("Share instance %s does not exist in the "
3763 "backend.", share_instance_id)
3764 except Exception as excep:
3765 with excutils.save_and_reraise_exception() as exc_context:
3766 if force:
3767 msg = ("The driver was unable to delete access rules "
3768 "for the instance: %s. Will attempt to delete "
3769 "the instance anyway.")
3770 LOG.error(msg, share_instance_id)
3771 exc_context.reraise = False
3772 else:
3773 error_state = constants.STATUS_ERROR_DELETING
3774 if deferred_delete: 3774 ↛ 3775line 3774 didn't jump to line 3775 because the condition on line 3774 was never true
3775 error_state = constants.STATUS_ERROR_DEFERRED_DELETING
3776 exc_context.reraise = False
3777 self.db.share_instance_update(
3778 context,
3779 share_instance_id,
3780 {'status': error_state})
3781 self.message_api.create(
3782 context,
3783 message_field.Action.DELETE_ACCESS_RULES,
3784 share_instance['project_id'],
3785 resource_type=message_field.Resource.SHARE,
3786 resource_id=share_instance_id,
3787 exception=excep)
3789 if error_state == constants.STATUS_ERROR_DEFERRED_DELETING and ( 3789 ↛ 3791line 3789 didn't jump to line 3791 because the condition on line 3789 was never true
3790 not force):
3791 return
3793 try:
3794 self.driver.delete_share(context, share_instance,
3795 share_server=share_server)
3796 except exception.ShareResourceNotFound:
3797 LOG.warning("Share instance %s does not exist in the "
3798 "backend.", share_instance_id)
3799 except Exception as excep:
3800 with excutils.save_and_reraise_exception() as exc_context:
3801 if force:
3802 msg = ("The driver was unable to delete the share "
3803 "instance: %s on the backend. Since this "
3804 "operation is forced, the instance will be "
3805 "deleted from Manila's database. A cleanup on "
3806 "the backend may be necessary.")
3807 LOG.error(msg, share_instance_id)
3808 exc_context.reraise = False
3809 else:
3810 error_state = constants.STATUS_ERROR_DELETING
3811 if deferred_delete: 3811 ↛ 3812line 3811 didn't jump to line 3812 because the condition on line 3811 was never true
3812 error_state = constants.STATUS_ERROR_DEFERRED_DELETING
3813 exc_context.reraise = False
3814 self.db.share_instance_update(
3815 context,
3816 share_instance_id,
3817 {'status': error_state})
3818 self.message_api.create(
3819 context,
3820 message_field.Action.DELETE,
3821 share_instance['project_id'],
3822 resource_type=message_field.Resource.SHARE,
3823 resource_id=share_instance_id,
3824 exception=excep)
3826 if error_state == constants.STATUS_ERROR_DEFERRED_DELETING and ( 3826 ↛ 3828line 3826 didn't jump to line 3828 because the condition on line 3826 was never true
3827 not force):
3828 return
3830 need_to_update_usages = True
3831 if share_instance['status'] in (
3832 constants.STATUS_DEFERRED_DELETING,
3833 constants.STATUS_ERROR_DEFERRED_DELETING
3834 ):
3835 need_to_update_usages = False
3837 self.db.share_instance_delete(
3838 context, share_instance_id,
3839 need_to_update_usages=need_to_update_usages)
3841 LOG.info("Share instance %s: deleted successfully.",
3842 share_instance_id)
3844 self._check_delete_share_server(context, share_instance=share_instance)
3846 self._notify_about_share_usage(context, share,
3847 share_instance, "delete.end")
3849 def _check_delete_share_server(self, context, share_instance=None,
3850 share_server=None, remote_host=False):
3852 if CONF.delete_share_server_with_last_share:
3853 if share_instance and not share_server: 3853 ↛ 3855line 3853 didn't jump to line 3855 because the condition on line 3853 was always true
3854 share_server = self._get_share_server(context, share_instance)
3855 if (share_server and len(share_server.share_instances) == 0
3856 and share_server.is_auto_deletable is True):
3857 LOG.debug("Scheduled deletion of share-server "
3858 "with id '%s' automatically by "
3859 "deletion of last share.", share_server['id'])
3860 if remote_host: 3860 ↛ 3861line 3860 didn't jump to line 3861 because the condition on line 3860 was never true
3861 rpcapi = share_rpcapi.ShareAPI()
3862 rpcapi.delete_share_server(context, share_server)
3863 else:
3864 self.delete_share_server(context, share_server)
3866 @periodic_task.periodic_task(
3867 spacing=CONF.periodic_deferred_delete_interval)
3868 @utils.require_driver_initialized
3869 def do_deferred_share_deletion(self, ctxt):
3870 LOG.debug("Checking for shares in 'deferred_deleting' status to "
3871 "process their deletion.")
3872 ctxt = ctxt.elevated()
3873 share_instances = self.db.share_instance_get_all(
3874 ctxt,
3875 filters={
3876 'status': constants.STATUS_ERROR_DEFERRED_DELETING,
3877 'host': self.host,
3878 },
3879 )
3881 for share_instance in share_instances:
3882 share_instance_id = share_instance['id']
3883 share, share_instance, share_server = (
3884 self._get_share_details_from_instance(
3885 ctxt,
3886 share_instance_id
3887 )
3888 )
3890 snap_instances = (
3891 self.db.share_snapshot_instance_get_all_with_filters(
3892 ctxt, {'share_instance_ids': share_instance_id}))
3893 if snap_instances: 3893 ↛ 3894line 3893 didn't jump to line 3894 because the condition on line 3893 was never true
3894 LOG.warning("Snapshot instances are present for the "
3895 "share instance: %s.", share_instance_id)
3896 continue
3898 try:
3899 self.access_helper.update_access_rules(
3900 ctxt,
3901 share_instance_id,
3902 delete_all_rules=True,
3903 share_server=share_server
3904 )
3905 except Exception:
3906 msg = ("The driver was unable to delete access rules "
3907 "for the instance: %s.")
3908 LOG.error(msg, share_instance_id)
3909 continue
3911 try:
3912 self.driver.delete_share(ctxt, share_instance,
3913 share_server=share_server)
3914 except exception.ShareResourceNotFound:
3915 LOG.warning("Share instance %s does not exist in the "
3916 "backend.", share_instance_id)
3917 except Exception:
3918 msg = ("The driver was unable to delete the share "
3919 "instance: %s on the backend. ")
3920 LOG.error(msg, share_instance_id)
3921 continue
3923 self.db.share_instance_delete(ctxt, share_instance_id)
3924 LOG.info("Share instance %s: deferred deleted successfully.",
3925 share_instance_id)
3926 self._check_delete_share_server(ctxt,
3927 share_instance=share_instance)
3928 self._notify_about_share_usage(ctxt, share,
3929 share_instance, "delete.end")
3931 @periodic_task.periodic_task(spacing=600)
3932 @utils.require_driver_initialized
3933 def delete_free_share_servers(self, ctxt):
3934 if not (self.driver.driver_handles_share_servers and
3935 self.configuration.automatic_share_server_cleanup):
3936 return
3937 LOG.info("Check for unused share servers to delete.")
3938 updated_before = timeutils.utcnow() - datetime.timedelta(
3939 minutes=self.configuration.unused_share_server_cleanup_interval)
3940 servers = self.db.share_server_get_all_unused_deletable(ctxt,
3941 self.host,
3942 updated_before)
3943 for server in servers:
3944 try:
3945 self.delete_share_server(ctxt, server)
3946 except exception.ShareServerNotFound:
3947 continue
3948 except Exception:
3949 LOG.exception(
3950 "Unable to delete share server %s, will retry in the next "
3951 "run.", server['id'])
3953 @periodic_task.periodic_task(
3954 spacing=CONF.check_for_expired_shares_in_recycle_bin_interval)
3955 @utils.require_driver_initialized
3956 def delete_expired_share(self, ctxt):
3957 LOG.debug("Check for expired share in recycle bin to delete.")
3958 expired_shares = self.db.share_get_all_expired(ctxt)
3960 for share in expired_shares:
3961 if share['status'] == constants.STATUS_ERROR_DELETING:
3962 LOG.info("Share %s was soft-deleted but a prior deletion "
3963 "attempt failed. Resetting status and re-attempting "
3964 "deletion", share['id'])
3965 # reset share status to error in order to try deleting again
3966 update_data = {'status': constants.STATUS_ERROR}
3967 self.db.share_update(ctxt, share['id'], update_data)
3968 else:
3969 LOG.info("share %s has expired, will be deleted", share['id'])
3970 self.share_api.delete(ctxt, share)
3972 @periodic_task.periodic_task(
3973 spacing=CONF.check_for_expired_transfers)
3974 def delete_expired_transfers(self, ctxt):
3975 LOG.info("Checking for expired transfers.")
3976 expired_transfers = self.db.transfer_get_all_expired(ctxt)
3978 for transfer in expired_transfers:
3979 LOG.debug("Transfer %s has expired, will be destroyed.",
3980 transfer['id'])
3981 self.transfer_api.delete(ctxt, transfer_id=transfer['id'])
3983 @utils.require_driver_initialized
3984 def transfer_accept(self, context, share_id, new_user,
3985 new_project, clear_rules):
3986 # need elevated context as we haven't "given" the share yet
3987 elevated_context = context.elevated()
3988 share_ref = self.db.share_get(elevated_context, share_id)
3989 access_rules = self.db.share_access_get_all_for_share(
3990 elevated_context, share_id)
3991 share_instances = self.db.share_instance_get_all_by_share(
3992 elevated_context, share_id)
3993 share_server = self._get_share_server(context, share_ref)
3995 for share_instance in share_instances:
3996 share_instance = self.db.share_instance_get(context,
3997 share_instance['id'],
3998 with_share_data=True)
3999 if clear_rules and access_rules:
4000 try:
4001 self.access_helper.update_access_rules(
4002 context,
4003 share_instance['id'],
4004 delete_all_rules=True
4005 )
4006 access_rules = []
4007 except Exception:
4008 with excutils.save_and_reraise_exception():
4009 msg = (
4010 "Can not remove access rules for share "
4011 "instance %(si)s belonging to share %(shr)s.")
4012 msg_payload = {
4013 'si': share_instance['id'],
4014 'shr': share_id,
4015 }
4016 LOG.error(msg, msg_payload)
4017 try:
4018 self.driver.transfer_accept(context, share_instance,
4019 new_user,
4020 new_project,
4021 access_rules=access_rules,
4022 share_server=share_server)
4023 except exception.DriverCannotTransferShareWithRules as e:
4024 with excutils.save_and_reraise_exception():
4025 self.message_api.create(
4026 context,
4027 message_field.Action.TRANSFER_ACCEPT,
4028 new_project,
4029 resource_type=message_field.Resource.SHARE,
4030 resource_id=share_id,
4031 detail=(message_field.Detail.
4032 DRIVER_FAILED_TRANSFER_ACCEPT))
4033 msg = _("The backend failed to accept the share: %s.")
4034 LOG.error(msg, e)
4036 msg = ('Share %(share_id)s has transfer from %(old_project_id)s to '
4037 '%(new_project_id)s completed successfully.')
4038 msg_args = {
4039 "share_id": share_id,
4040 "old_project_id": share_ref['project_id'],
4041 "new_project_id": context.project_id
4042 }
4043 LOG.info(msg, msg_args)
4045 @add_hooks
4046 @utils.require_driver_initialized
4047 def create_snapshot(self, context, share_id, snapshot_id):
4048 """Create snapshot for share."""
4049 snapshot_ref = self.db.share_snapshot_get(context, snapshot_id)
4050 share_server = self._get_share_server(
4051 context, snapshot_ref['share']['instance'])
4052 snapshot_instance = self.db.share_snapshot_instance_get(
4053 context, snapshot_ref.instance['id'], with_share_data=True
4054 )
4055 snapshot_instance_id = snapshot_instance['id']
4057 snapshot_instance = self._get_snapshot_instance_dict(
4058 context, snapshot_instance)
4060 try:
4062 model_update = self.driver.create_snapshot(
4063 context, snapshot_instance, share_server=share_server) or {}
4065 except Exception as excep:
4066 with excutils.save_and_reraise_exception():
4067 self.db.share_snapshot_instance_update(
4068 context,
4069 snapshot_instance_id,
4070 {'status': constants.STATUS_ERROR})
4071 self.message_api.create(
4072 context,
4073 message_field.Action.CREATE,
4074 snapshot_ref['project_id'],
4075 resource_type=message_field.Resource.SHARE_SNAPSHOT,
4076 resource_id=snapshot_instance_id,
4077 exception=excep)
4079 snapshot_export_locations = model_update.pop('export_locations', [])
4081 if snapshot_instance['share']['mount_snapshot_support']:
4083 for el in snapshot_export_locations:
4084 values = {
4085 'share_snapshot_instance_id': snapshot_instance_id,
4086 'path': el['path'],
4087 'is_admin_only': el['is_admin_only'],
4088 }
4090 self.db.share_snapshot_instance_export_location_create(context,
4091 values)
4093 if model_update.get('status') in (None, constants.STATUS_AVAILABLE): 4093 ↛ 4097line 4093 didn't jump to line 4097 because the condition on line 4093 was always true
4094 model_update['status'] = constants.STATUS_AVAILABLE
4095 model_update['progress'] = '100%'
4097 self.db.share_snapshot_instance_update(
4098 context, snapshot_instance_id, model_update)
4100 def _delete_snapshot_quota(self, context, snapshot):
4101 share_type_id = snapshot['share']['instance']['share_type_id']
4102 reservations = None
4103 try:
4104 reservations = QUOTAS.reserve(
4105 context, project_id=snapshot['project_id'], snapshots=-1,
4106 snapshot_gigabytes=-snapshot['size'],
4107 user_id=snapshot['user_id'],
4108 share_type_id=share_type_id,
4109 )
4110 except Exception:
4111 LOG.exception("Failed to update quota usages while deleting "
4112 "snapshot %s.", snapshot['id'])
4114 if reservations:
4115 QUOTAS.commit(
4116 context, reservations, project_id=snapshot['project_id'],
4117 user_id=snapshot['user_id'],
4118 share_type_id=share_type_id,
4119 )
4121 @add_hooks
4122 @utils.require_driver_initialized
4123 def delete_snapshot(self, context, snapshot_id, force=False,
4124 deferred_delete=False):
4125 """Delete share snapshot."""
4126 context = context.elevated()
4127 snapshot_ref = self.db.share_snapshot_get(context, snapshot_id)
4129 share_server = self._get_share_server(
4130 context, snapshot_ref['share']['instance'])
4131 snapshot_instance = self.db.share_snapshot_instance_get(
4132 context, snapshot_ref.instance['id'], with_share_data=True)
4133 snapshot_instance_id = snapshot_instance['id']
4135 snapshot_instance = self._get_snapshot_instance_dict(
4136 context, snapshot_instance)
4138 share_ref = self.db.share_get(context, snapshot_ref['share_id'])
4140 if deferred_delete:
4141 try:
4142 self._delete_snapshot_quota(context, snapshot_ref)
4143 LOG.info(
4144 "Snapshot instance %s had its deletion deferred. Quota "
4145 "was reclaimed and the share driver will proceed with "
4146 "the deletion.", snapshot_instance['id']
4147 )
4148 except Exception:
4149 LOG.warning(
4150 "Error occured during quota usage update. Administrator "
4151 "must rectify quotas.")
4153 if share_ref['mount_snapshot_support']:
4154 try:
4155 self.snapshot_access_helper.update_access_rules(
4156 context, snapshot_instance['id'], delete_all_rules=True,
4157 share_server=share_server)
4158 except Exception:
4159 LOG.exception(
4160 ("Failed to remove access rules for snapshot %s."),
4161 snapshot_instance['id'])
4162 LOG.warning("The driver was unable to remove access rules "
4163 "for snapshot %s. Moving on.",
4164 snapshot_instance['snapshot_id'])
4166 try:
4167 self.driver.delete_snapshot(context, snapshot_instance,
4168 share_server=share_server)
4169 except Exception as excep:
4170 with excutils.save_and_reraise_exception() as exc:
4171 if force:
4172 msg = _("The driver was unable to delete the "
4173 "snapshot %s on the backend. Since this "
4174 "operation is forced, the snapshot will "
4175 "be deleted from Manila's database. A cleanup on "
4176 "the backend may be necessary.")
4177 LOG.exception(msg, snapshot_id)
4178 exc.reraise = False
4179 else:
4180 error_state = constants.STATUS_ERROR_DELETING
4181 if deferred_delete: 4181 ↛ 4182line 4181 didn't jump to line 4182 because the condition on line 4181 was never true
4182 error_state = constants.STATUS_ERROR_DEFERRED_DELETING
4183 exc.reraise = False
4184 self.db.share_snapshot_instance_update(
4185 context,
4186 snapshot_instance_id,
4187 {'status': error_state})
4188 self.message_api.create(
4189 context,
4190 message_field.Action.DELETE,
4191 snapshot_ref['project_id'],
4192 resource_type=message_field.Resource.SHARE_SNAPSHOT,
4193 resource_id=snapshot_instance_id,
4194 exception=excep)
4196 snapshot_instance = self.db.share_snapshot_instance_get(
4197 context, snapshot_ref.instance['id'])
4198 if snapshot_instance['status'] == ( 4198 ↛ 4200line 4198 didn't jump to line 4200 because the condition on line 4198 was never true
4199 constants.STATUS_ERROR_DEFERRED_DELETING) and not force:
4200 return
4202 self.db.share_snapshot_instance_delete(context, snapshot_instance_id)
4203 if snapshot_instance['status'] not in (
4204 constants.STATUS_DEFERRED_DELETING,
4205 constants.STATUS_ERROR_DEFERRED_DELETING
4206 ):
4207 self._delete_snapshot_quota(context, snapshot_ref)
4209 @periodic_task.periodic_task(
4210 spacing=CONF.periodic_deferred_delete_interval)
4211 @utils.require_driver_initialized
4212 def do_deferred_snapshot_deletion(self, ctxt):
4213 LOG.debug("Checking for snapshots in 'deferred_deleting' status to "
4214 "process their deletion.")
4215 ctxt = ctxt.elevated()
4216 snapshot_instances = (
4217 self.db.share_snapshot_instance_get_all_with_filters(
4218 ctxt, {'statuses': constants.STATUS_ERROR_DEFERRED_DELETING}))
4220 for snapshot_instance in snapshot_instances:
4221 snapshot_instance_id = snapshot_instance['id']
4222 share_server = self._get_share_server(
4223 ctxt, snapshot_instance['share_instance'])
4224 snapshot_instance = self._get_snapshot_instance_dict(
4225 ctxt, snapshot_instance)
4227 try:
4228 self.driver.delete_snapshot(ctxt, snapshot_instance,
4229 share_server=share_server)
4230 except Exception:
4231 continue
4232 self.db.share_snapshot_instance_delete(ctxt,
4233 snapshot_instance_id)
4235 @add_hooks
4236 @utils.require_driver_initialized
4237 @locked_share_replica_operation
4238 def create_replicated_snapshot(self, context, snapshot_id, share_id=None):
4239 """Create a snapshot for a replicated share."""
4240 # Grab the snapshot and replica information from the DB.
4241 snapshot = self.db.share_snapshot_get(context, snapshot_id)
4242 share_server = self._get_share_server(context, snapshot['share'])
4243 replica_snapshots = (
4244 self.db.share_snapshot_instance_get_all_with_filters(
4245 context, {'snapshot_ids': snapshot['id']},
4246 with_share_data=True)
4247 )
4248 replica_list = (
4249 self.db.share_replicas_get_all_by_share(
4250 context, share_id, with_share_data=True,
4251 with_share_server=True)
4252 )
4254 # Make primitives to pass the information to the driver.
4256 replica_list = [self._get_share_instance_dict(context, r)
4257 for r in replica_list]
4258 replica_snapshots = [self._get_snapshot_instance_dict(context, s)
4259 for s in replica_snapshots]
4260 updated_instances = []
4262 try:
4263 updated_instances = self.driver.create_replicated_snapshot(
4264 context, replica_list, replica_snapshots,
4265 share_server=share_server) or []
4266 except Exception:
4267 with excutils.save_and_reraise_exception():
4268 for instance in replica_snapshots:
4269 self.db.share_snapshot_instance_update(
4270 context, instance['id'],
4271 {'status': constants.STATUS_ERROR})
4273 for instance in updated_instances:
4274 if instance['status'] == constants.STATUS_AVAILABLE: 4274 ↛ 4276line 4274 didn't jump to line 4276 because the condition on line 4274 was always true
4275 instance.update({'progress': '100%'})
4276 self.db.share_snapshot_instance_update(
4277 context, instance['id'], instance)
4279 def _find_active_replica_on_host(self, replica_list):
4280 """Find the active replica matching this manager's host."""
4281 for replica in replica_list: 4281 ↛ exitline 4281 didn't return from function '_find_active_replica_on_host' because the loop on line 4281 didn't complete
4282 if (replica['replica_state'] == constants.REPLICA_STATE_ACTIVE and 4282 ↛ 4281line 4282 didn't jump to line 4281 because the condition on line 4282 was always true
4283 share_utils.extract_host(replica['host']) == self.host):
4284 return replica
4286 @locked_share_replica_operation
4287 def _revert_to_replicated_snapshot(self, context, share, snapshot,
4288 reservations, share_access_rules,
4289 snapshot_access_rules, share_id=None):
4291 share_server = self._get_share_server(context, share)
4292 snapshot_id = snapshot['id']
4293 project_id = share['project_id']
4294 user_id = share['user_id']
4296 # Get replicas, including an active replica
4297 replica_list = self.db.share_replicas_get_all_by_share(
4298 context, share_id, with_share_data=True, with_share_server=True)
4299 active_replica = self._find_active_replica_on_host(replica_list)
4301 # Get snapshot instances, including one on an active replica
4302 replica_snapshots = (
4303 self.db.share_snapshot_instance_get_all_with_filters(
4304 context, {'snapshot_ids': snapshot_id},
4305 with_share_data=True))
4306 snapshot_instance_filters = {
4307 'share_instance_ids': active_replica['id'],
4308 'snapshot_ids': snapshot_id,
4309 }
4310 active_replica_snapshot = (
4311 self.db.share_snapshot_instance_get_all_with_filters(
4312 context, snapshot_instance_filters))[0]
4314 # Make primitives to pass the information to the driver
4315 replica_list = [self._get_share_instance_dict(context, replica)
4316 for replica in replica_list]
4317 active_replica = self._get_share_instance_dict(context, active_replica)
4318 replica_snapshots = [self._get_snapshot_instance_dict(context, s)
4319 for s in replica_snapshots]
4320 active_replica_snapshot = self._get_snapshot_instance_dict(
4321 context, active_replica_snapshot, snapshot=snapshot)
4323 try:
4324 self.driver.revert_to_replicated_snapshot(
4325 context, active_replica, replica_list, active_replica_snapshot,
4326 replica_snapshots, share_access_rules,
4327 snapshot_access_rules, share_server=share_server)
4328 except Exception:
4329 with excutils.save_and_reraise_exception():
4331 msg = ('Share %(share)s could not be reverted '
4332 'to snapshot %(snap)s.')
4333 msg_args = {'share': share_id, 'snap': snapshot_id}
4334 LOG.exception(msg, msg_args)
4336 if reservations:
4337 QUOTAS.rollback(
4338 context, reservations, project_id=project_id,
4339 user_id=user_id,
4340 share_type_id=active_replica['share_type_id'],
4341 )
4343 self.db.share_replica_update(
4344 context, active_replica['id'],
4345 {'status': constants.STATUS_REVERTING_ERROR})
4346 self.db.share_snapshot_instance_update(
4347 context, active_replica_snapshot['id'],
4348 {'status': constants.STATUS_AVAILABLE})
4350 if reservations:
4351 QUOTAS.commit(
4352 context, reservations, project_id=project_id, user_id=user_id,
4353 share_type_id=active_replica['share_type_id'],
4354 )
4356 self.db.share_update(context, share_id, {'size': snapshot['size']})
4357 self.db.share_replica_update(
4358 context, active_replica['id'],
4359 {'status': constants.STATUS_AVAILABLE})
4360 self.db.share_snapshot_instance_update(
4361 context, active_replica_snapshot['id'],
4362 {'status': constants.STATUS_AVAILABLE})
4364 msg = ('Share %(share)s reverted to snapshot %(snap)s '
4365 'successfully.')
4366 msg_args = {'share': share_id, 'snap': snapshot_id}
4367 LOG.info(msg, msg_args)
4369 @add_hooks
4370 @utils.require_driver_initialized
4371 @locked_share_replica_operation
4372 def delete_replicated_snapshot(self, context, snapshot_id,
4373 share_id=None, force=False):
4374 """Delete a snapshot from a replicated share."""
4375 # Grab the replica and snapshot information from the DB.
4376 snapshot = self.db.share_snapshot_get(context, snapshot_id)
4377 share_server = self._get_share_server(context, snapshot['share'])
4378 replica_snapshots = (
4379 self.db.share_snapshot_instance_get_all_with_filters(
4380 context, {'snapshot_ids': snapshot['id']},
4381 with_share_data=True)
4382 )
4383 replica_list = (
4384 self.db.share_replicas_get_all_by_share(
4385 context, share_id, with_share_data=True,
4386 with_share_server=True)
4387 )
4389 replica_list = [self._get_share_instance_dict(context, r)
4390 for r in replica_list]
4391 replica_snapshots = [self._get_snapshot_instance_dict(context, s)
4392 for s in replica_snapshots]
4393 deleted_instances = []
4394 updated_instances = []
4395 db_force_delete_msg = _('The driver was unable to delete some or all '
4396 'of the share replica snapshots on the '
4397 'backend/s. Since this operation is forced, '
4398 'the replica snapshots will be deleted from '
4399 'Manila.')
4401 try:
4403 updated_instances = self.driver.delete_replicated_snapshot(
4404 context, replica_list, replica_snapshots,
4405 share_server=share_server) or []
4407 except Exception:
4408 with excutils.save_and_reraise_exception() as e:
4409 if force:
4410 # Can delete all instances if forced.
4411 deleted_instances = replica_snapshots
4412 LOG.exception(db_force_delete_msg)
4413 e.reraise = False
4414 else:
4415 for instance in replica_snapshots:
4416 self.db.share_snapshot_instance_update(
4417 context, instance['id'],
4418 {'status': constants.STATUS_ERROR_DELETING})
4420 if not deleted_instances:
4421 if force:
4422 # Ignore model updates on 'force' delete.
4423 LOG.warning(db_force_delete_msg)
4424 deleted_instances = replica_snapshots
4425 else:
4426 deleted_instances = list(filter(
4427 lambda x: x['status'] == constants.STATUS_DELETED,
4428 updated_instances))
4429 updated_instances = list(filter(
4430 lambda x: x['status'] != constants.STATUS_DELETED,
4431 updated_instances))
4433 for instance in deleted_instances:
4434 self.db.share_snapshot_instance_delete(context, instance['id'])
4436 for instance in updated_instances:
4437 self.db.share_snapshot_instance_update(
4438 context, instance['id'], instance)
4440 @periodic_task.periodic_task(spacing=CONF.replica_state_update_interval)
4441 @utils.require_driver_initialized
4442 def periodic_share_replica_snapshot_update(self, context):
4443 LOG.debug("Updating status of share replica snapshots.")
4444 transitional_statuses = (constants.STATUS_CREATING,
4445 constants.STATUS_DELETING)
4446 # we will need: id, host, replica_state
4447 replicas = self.db.share_replicas_get_all(context,
4448 with_share_data=False,
4449 with_share_server=False)
4451 def qualified_replica(r):
4452 # Filter non-active replicas belonging to this backend
4453 return (share_utils.extract_host(r['host']) ==
4454 share_utils.extract_host(self.host) and
4455 r['replica_state'] != constants.REPLICA_STATE_ACTIVE)
4457 host_replicas = list(filter(
4458 lambda x: qualified_replica(x), replicas))
4459 transitional_replica_snapshots = []
4461 # Get snapshot instances for each replica that are in 'creating' or
4462 # 'deleting' states.
4463 for replica in host_replicas: 4463 ↛ 4464line 4463 didn't jump to line 4464 because the loop on line 4463 never started
4464 filters = {
4465 'share_instance_ids': replica['id'],
4466 'statuses': transitional_statuses,
4467 }
4468 # we will need: id, snapshot_id, share_instance_id and
4469 # share['share_id']
4470 replica_snapshots = (
4471 self.db.share_snapshot_instance_get_all_with_filters(
4472 context, filters, with_share_data=True)
4473 )
4474 transitional_replica_snapshots.extend(replica_snapshots)
4476 for replica_snapshot in transitional_replica_snapshots: 4476 ↛ 4477line 4476 didn't jump to line 4477 because the loop on line 4476 never started
4477 replica_snapshots = (
4478 self.db.share_snapshot_instance_get_all_with_filters(
4479 context,
4480 {'snapshot_ids': replica_snapshot['snapshot_id']},
4481 with_share_data=False)
4482 )
4483 share_id = replica_snapshot['share']['share_id']
4484 self._update_replica_snapshot(
4485 context, replica_snapshot,
4486 replica_snapshots=replica_snapshots, share_id=share_id)
4488 @locked_share_replica_operation
4489 def _update_replica_snapshot(self, context, replica_snapshot,
4490 replica_snapshots=None, share_id=None):
4491 # share_id is used by the locked_share_replica_operation decorator
4492 # Re-grab the replica, now with share data:
4493 try:
4494 # _get_share_instance_dict will fetch share server
4495 share_replica = self.db.share_replica_get(
4496 context, replica_snapshot['share_instance_id'],
4497 with_share_data=True, with_share_server=False)
4498 replica_snapshot = self.db.share_snapshot_instance_get(
4499 context, replica_snapshot['id'], with_share_data=True)
4500 except exception.NotFound:
4501 # Replica may have been deleted, try to cleanup the snapshot
4502 # instance
4503 try:
4504 self.db.share_snapshot_instance_delete(
4505 context, replica_snapshot['id'])
4506 except exception.ShareSnapshotInstanceNotFound:
4507 # snapshot instance has been deleted, nothing to do here
4508 pass
4509 return
4511 msg_payload = {
4512 'snapshot_instance': replica_snapshot['id'],
4513 'replica': share_replica['id'],
4514 }
4516 LOG.debug("Updating status of replica snapshot %(snapshot_instance)s: "
4517 "on replica: %(replica)s", msg_payload)
4519 # Grab all the replica and snapshot information.
4520 # _get_share_instance_dict will fetch share server
4521 replica_list = (
4522 self.db.share_replicas_get_all_by_share(
4523 context, share_replica['share_id'],
4524 with_share_data=True, with_share_server=False)
4525 )
4527 replica_list = [self._get_share_instance_dict(context, r)
4528 for r in replica_list]
4529 replica_snapshots = replica_snapshots or []
4531 # Convert data to primitives to send to the driver.
4533 replica_snapshots = [self._get_snapshot_instance_dict(context, s)
4534 for s in replica_snapshots]
4535 replica_snapshot = self._get_snapshot_instance_dict(
4536 context, replica_snapshot)
4537 share_replica = self._get_share_instance_dict(context, share_replica)
4538 share_server = share_replica['share_server']
4539 snapshot_update = None
4541 try:
4543 snapshot_update = self.driver.update_replicated_snapshot(
4544 context, replica_list, share_replica, replica_snapshots,
4545 replica_snapshot, share_server=share_server) or {}
4547 except exception.SnapshotResourceNotFound:
4548 if replica_snapshot['status'] == constants.STATUS_DELETING: 4548 ↛ 4554line 4548 didn't jump to line 4554 because the condition on line 4548 was always true
4549 LOG.info('Snapshot %(snapshot_instance)s on replica '
4550 '%(replica)s has been deleted.', msg_payload)
4551 self.db.share_snapshot_instance_delete(
4552 context, replica_snapshot['id'])
4553 else:
4554 LOG.exception("Replica snapshot %s was not found on "
4555 "the backend.", replica_snapshot['id'])
4556 self.db.share_snapshot_instance_update(
4557 context, replica_snapshot['id'],
4558 {'status': constants.STATUS_ERROR})
4559 except Exception:
4560 LOG.exception("Driver error while updating replica snapshot: "
4561 "%s", replica_snapshot['id'])
4562 self.db.share_snapshot_instance_update(
4563 context, replica_snapshot['id'],
4564 {'status': constants.STATUS_ERROR})
4566 if snapshot_update:
4567 snapshot_status = snapshot_update.get('status')
4568 if snapshot_status == constants.STATUS_AVAILABLE: 4568 ↛ 4570line 4568 didn't jump to line 4570 because the condition on line 4568 was always true
4569 snapshot_update['progress'] = '100%'
4570 self.db.share_snapshot_instance_update(
4571 context, replica_snapshot['id'], snapshot_update)
4573 @add_hooks
4574 @utils.require_driver_initialized
4575 def update_access(self, context, share_instance_id):
4576 """Allow/Deny/Update access to some share."""
4577 share_instance = self._get_share_instance(context, share_instance_id)
4578 share_server_id = share_instance.get('share_server_id')
4580 self.update_access_for_instances(context, [share_instance_id],
4581 share_server_id=share_server_id)
4583 def update_access_for_instances(self, context, share_instance_ids,
4584 share_server_id=None):
4585 """Allow/Deny access to shares that belong to the same share server."""
4586 share_server = None
4587 if share_server_id: 4587 ↛ 4590line 4587 didn't jump to line 4590 because the condition on line 4587 was always true
4588 share_server = self.db.share_server_get(context, share_server_id)
4590 for instance_id in share_instance_ids:
4591 LOG.debug("Received request to update access for share instance"
4592 " %s.", instance_id)
4594 self.access_helper.update_access_rules(
4595 context,
4596 instance_id,
4597 share_server=share_server)
4599 @periodic_task.periodic_task(spacing=CONF.periodic_interval)
4600 @utils.require_driver_initialized
4601 def _report_driver_status(self, context):
4602 LOG.info('Updating share status')
4603 share_stats = self.driver.get_share_stats(refresh=True)
4605 if not share_stats:
4606 return
4608 if self.driver.driver_handles_share_servers:
4609 share_stats['server_pools_mapping'] = (
4610 self._get_servers_pool_mapping(context)
4611 )
4613 self.update_service_capabilities(share_stats)
4615 @periodic_task.periodic_task(spacing=CONF.periodic_hooks_interval)
4616 @utils.require_driver_initialized
4617 def _execute_periodic_hook(self, context):
4618 """Executes periodic-based hooks."""
4619 # TODO(vponomaryov): add also access rules and share servers
4620 share_instances = (
4621 self.db.share_instance_get_all_by_host(
4622 context=context, host=self.host))
4623 periodic_hook_data = self.driver.get_periodic_hook_data(
4624 context=context, share_instances=share_instances)
4625 for hook in self.hooks:
4626 hook.execute_periodic_hook(
4627 context=context, periodic_hook_data=periodic_hook_data)
4629 def _get_servers_pool_mapping(self, context):
4630 """Get info about relationships between pools and share_servers."""
4631 share_servers = self.db.share_server_get_all_by_host(context,
4632 self.host)
4633 return {server['id']: self.driver.get_share_server_pools(server)
4634 for server in share_servers}
4636 @add_hooks
4637 @utils.require_driver_initialized
4638 def publish_service_capabilities(self, context):
4639 """Collect driver status and then publish it."""
4640 self._report_driver_status(context)
4641 self._publish_service_capabilities(context)
4643 def _form_server_setup_info(self, context, share_server, share_network,
4644 share_network_subnets):
4645 share_server_id = share_server['id']
4646 # Network info is used by driver for setting up share server
4647 # and getting server info on share creation.
4648 admin_network_allocations = (
4649 self.db.network_allocations_get_for_share_server(
4650 context, share_server_id, label='admin'))
4652 # NOTE(felipe_rodrigues): items in the network_info list contain
4653 # same values for the keys: server_id, admin_network_allocations,
4654 # security_services and backend_details.
4655 network_info = []
4656 for share_network_subnet in share_network_subnets:
4657 network_allocations = (
4658 self.db.network_allocations_get_for_share_server(
4659 context, share_server_id, label='user',
4660 subnet_id=share_network_subnet['id']))
4661 # NOTE(vponomaryov): following network_info fields are deprecated:
4662 # 'segmentation_id', 'cidr' and 'network_type'.
4663 # And they should be used from network allocations directly.
4664 # They should be removed right after no one uses them.
4665 network_info.append({
4666 'server_id': share_server['id'],
4667 'segmentation_id': share_network_subnet['segmentation_id'],
4668 'cidr': share_network_subnet['cidr'],
4669 'neutron_net_id': share_network_subnet['neutron_net_id'],
4670 'neutron_subnet_id': share_network_subnet['neutron_subnet_id'],
4671 'security_services': share_network['security_services'],
4672 'network_allocations': network_allocations,
4673 'admin_network_allocations': admin_network_allocations,
4674 'backend_details': share_server.get('backend_details'),
4675 'network_type': share_network_subnet['network_type'],
4676 'subnet_metadata': share_network_subnet['subnet_metadata']
4677 })
4678 return network_info
4680 def _handle_setup_server_error(self, context, share_server_id, e):
4681 details = getattr(e, "detail_data", {})
4682 if isinstance(details, dict):
4683 server_details = details.get("server_details", {})
4684 if not isinstance(server_details, dict):
4685 LOG.debug(
4686 ("Cannot save non-dict data (%(data)s) provided as "
4687 "'server details' of failed share server '%(server)s'."),
4688 {"server": share_server_id, "data": server_details})
4689 else:
4690 invalid_details = []
4691 for key, value in server_details.items():
4692 try:
4693 self.db.share_server_backend_details_set(
4694 context, share_server_id, {key: value})
4695 except Exception:
4696 invalid_details.append("%(key)s: %(value)s" % {
4697 'key': str(key),
4698 'value': str(value)
4699 })
4700 if invalid_details:
4701 LOG.debug(
4702 ("Following server details cannot be written to db : "
4703 "%s"), str("\n".join(invalid_details)))
4704 else:
4705 LOG.debug(
4706 ("Cannot save non-dict data (%(data)s) provided as 'detail "
4707 "data' of failed share server '%(server)s'."),
4708 {"server": share_server_id, "data": details})
4710 self.db.share_server_update(
4711 context, share_server_id, {'status': constants.STATUS_ERROR})
4713 def _setup_server(self, context, share_server, metadata):
4714 subnets = share_server['share_network_subnets']
4715 if not subnets: 4715 ↛ 4716line 4715 didn't jump to line 4716 because the condition on line 4715 was never true
4716 raise exception.NetworkBadConfigurationException(
4717 reason="share server does not have subnet")
4719 # all subnets reside on same share network, get it from the first one.
4720 share_network_id = subnets[0]['share_network_id']
4721 try:
4722 share_network = self.db.share_network_get(context,
4723 share_network_id)
4724 for share_network_subnet in subnets:
4725 self.driver.allocate_network(
4726 context, share_server, share_network, share_network_subnet)
4727 self.driver.allocate_admin_network(context, share_server)
4729 # Get share_network_subnets in case they were updated.
4730 share_network_subnets = (
4731 self.db.share_network_subnet_get_all_by_share_server_id(
4732 context, share_server['id']))
4734 network_info_list = self._form_server_setup_info(
4735 context, share_server, share_network, share_network_subnets)
4736 for network_info in network_info_list:
4737 self._validate_segmentation_id(network_info)
4739 # NOTE(vponomaryov): Save security services data to share server
4740 # details table to remove dependency from share network after
4741 # creation operation. It will allow us to delete share server and
4742 # share network separately without dependency on each other.
4743 for security_service in network_info_list[0]['security_services']:
4744 ss_type = security_service['type']
4745 data = {
4746 'name': security_service['name'],
4747 'ou': security_service['ou'],
4748 'domain': security_service['domain'],
4749 'server': security_service['server'],
4750 'dns_ip': security_service['dns_ip'],
4751 'user': security_service['user'],
4752 'type': ss_type,
4753 'password': security_service['password'],
4754 'default_ad_site': security_service['default_ad_site'],
4755 }
4756 self.db.share_server_backend_details_set(
4757 context, share_server['id'],
4758 {'security_service_' + ss_type: jsonutils.dumps(data)})
4760 server_info = self.driver.setup_server(
4761 network_info_list, metadata=metadata)
4763 self.driver.update_network_allocation(context, share_server)
4764 self.driver.update_admin_network_allocation(context, share_server)
4766 if server_info and isinstance(server_info, dict):
4767 self.db.share_server_backend_details_set(
4768 context, share_server['id'], server_info)
4769 return self.db.share_server_update(
4770 context, share_server['id'],
4771 {'status': constants.STATUS_ACTIVE,
4772 'identifier': server_info.get(
4773 'identifier', share_server['id'])})
4774 except Exception as e:
4775 with excutils.save_and_reraise_exception():
4776 self._handle_setup_server_error(context, share_server['id'], e)
4777 self.driver.deallocate_network(context, share_server['id'])
4779 def _validate_segmentation_id(self, network_info):
4780 """Raises exception if the segmentation type is incorrect."""
4781 if (network_info['network_type'] in (None, 'flat') and
4782 network_info['segmentation_id']):
4783 msg = _('A segmentation ID %(vlan_id)s was specified but can not '
4784 'be used with a network of type %(seg_type)s; the '
4785 'segmentation ID option must be omitted or set to 0')
4786 raise exception.NetworkBadConfigurationException(
4787 reason=msg % {'vlan_id': network_info['segmentation_id'],
4788 'seg_type': network_info['network_type']})
4789 elif (network_info['network_type'] == 'vlan'
4790 and (network_info['segmentation_id'] is None
4791 or int(network_info['segmentation_id']) > 4094
4792 or int(network_info['segmentation_id']) < 1)):
4793 msg = _('A segmentation ID %s was specified but is not valid for '
4794 'a VLAN network type; the segmentation ID must be an '
4795 'integer value in the range of [1,4094]')
4796 raise exception.NetworkBadConfigurationException(
4797 reason=msg % network_info['segmentation_id'])
4798 elif (network_info['network_type'] == 'vxlan'
4799 and (network_info['segmentation_id'] is None
4800 or int(network_info['segmentation_id']) > 16777215
4801 or int(network_info['segmentation_id']) < 1)):
4802 msg = _('A segmentation ID %s was specified but is not valid for '
4803 'a VXLAN network type; the segmentation ID must be an '
4804 'integer value in the range of [1,16777215]')
4805 raise exception.NetworkBadConfigurationException(
4806 reason=msg % network_info['segmentation_id'])
4807 elif (network_info['network_type'] == 'gre'
4808 and (network_info['segmentation_id'] is None
4809 or int(network_info['segmentation_id']) > 4294967295
4810 or int(network_info['segmentation_id']) < 1)):
4811 msg = _('A segmentation ID %s was specified but is not valid for '
4812 'a GRE network type; the segmentation ID must be an '
4813 'integer value in the range of [1, 4294967295]')
4814 raise exception.NetworkBadConfigurationException(
4815 reason=msg % network_info['segmentation_id'])
4817 @add_hooks
4818 @utils.require_driver_initialized
4819 def delete_share_server(self, context, share_server):
4821 subnet_id = (share_server['share_network_subnet_ids'][0]
4822 if share_server['share_network_subnet_ids'] else None)
4824 @utils.synchronized(
4825 "share_manager_%s" % subnet_id)
4826 def _wrapped_delete_share_server():
4827 # NOTE(vponomaryov): Verify that there are no dependent shares.
4828 # Without this verification we can get here exception in next case:
4829 # share-server-delete API was called after share creation scheduled
4830 # and share_server reached ACTIVE status, but before update
4831 # of share_server_id field for share. If so, after lock realese
4832 # this method starts executing when amount of dependent shares
4833 # has been changed.
4834 server_id = share_server['id']
4835 try:
4836 server = self.db.share_server_get(
4837 context, server_id)
4838 except exception.ShareServerNotFound:
4839 raise
4841 shares = self.db.share_instance_get_all_by_share_server(
4842 context, server_id)
4844 if shares:
4845 raise exception.ShareServerInUse(share_server_id=server_id)
4847 server_details = share_server['backend_details']
4849 self.db.share_server_update(context, server_id,
4850 {'status': constants.STATUS_DELETING})
4851 try:
4852 LOG.debug("Deleting network of share server '%s'", server_id)
4853 share_net = None
4854 share_net_subnet = None
4855 if subnet_id:
4856 try:
4857 share_net_subnet = self.db.share_network_subnet_get(
4858 context, subnet_id)
4859 share_net = self.db.share_network_get(
4860 context, share_net_subnet['share_network_id'])
4861 except Exception:
4862 LOG.warning('Share network subnet not found during '
4863 'deletion of share server.')
4864 self.driver.deallocate_network(context, share_server['id'],
4865 share_net,
4866 share_net_subnet)
4868 application_credential_id = server.get(
4869 'application_credential_id')
4870 if application_credential_id: 4870 ↛ 4872line 4870 didn't jump to line 4872 because the condition on line 4870 was never true
4871 # Delete application credentials for barbican user
4872 try:
4873 barbican_api.delete_application_credentials(
4874 context, application_credential_id)
4875 except Exception:
4876 LOG.warning('Application credentials not found '
4877 'during deletion of share server.')
4879 encryption_key_ref = server.get('encryption_key_ref')
4880 barbican_api.delete_secret_access(context,
4881 encryption_key_ref)
4883 LOG.debug("Deleting share server '%s'", server_id)
4884 security_services = []
4885 for ss_name in constants.SECURITY_SERVICES_ALLOWED_TYPES:
4886 ss = server_details.get('security_service_' + ss_name)
4887 if ss:
4888 security_services.append(jsonutils.loads(ss))
4890 self.driver.teardown_server(
4891 server_details=server_details,
4892 security_services=security_services)
4893 except Exception:
4894 with excutils.save_and_reraise_exception():
4895 LOG.error(
4896 "Share server '%s' failed on deletion.",
4897 server_id)
4898 self.db.share_server_update(
4899 context, server_id, {'status': constants.STATUS_ERROR})
4900 else:
4901 encryption_key_ref = server.get('encryption_key_ref')
4902 if encryption_key_ref: 4902 ↛ 4903line 4902 didn't jump to line 4903 because the condition on line 4902 was never true
4903 self._delete_encryption_keys_quota(context)
4904 self.db.share_server_delete(context, share_server['id'])
4906 _wrapped_delete_share_server()
4907 LOG.info(
4908 "Share server '%s' has been deleted successfully.",
4909 share_server['id'])
4911 def _delete_encryption_keys_quota(self, context):
4912 reservations = None
4913 try:
4914 reservations = QUOTAS.reserve(
4915 context, project_id=context.project_id,
4916 encryption_keys=-1,
4917 )
4918 except Exception:
4919 LOG.exception("Failed to update encryption_keys quota "
4920 "usages while deleting share server.")
4922 if reservations: 4922 ↛ exitline 4922 didn't return from function '_delete_encryption_keys_quota' because the condition on line 4922 was always true
4923 QUOTAS.commit(
4924 context, reservations, project_id=context.project_id,
4925 )
4927 @add_hooks
4928 @utils.require_driver_initialized
4929 def extend_share(self, context, share_id, new_size, reservations):
4930 context = context.elevated()
4931 share = self.db.share_get(context, share_id)
4932 share_instance = self._get_share_instance(context, share)
4933 share_server = self._get_share_server(context, share_instance)
4934 project_id = share['project_id']
4935 user_id = share['user_id']
4937 self._notify_about_share_usage(context, share,
4938 share_instance, "extend.start")
4940 try:
4941 self.driver.extend_share(
4942 share_instance, new_size, share_server=share_server)
4943 except Exception as e:
4944 LOG.exception("Extend share failed.", resource=share)
4945 self.message_api.create(
4946 context,
4947 message_field.Action.EXTEND,
4948 project_id,
4949 resource_type=message_field.Resource.SHARE,
4950 resource_id=share_id,
4951 detail=message_field.Detail.DRIVER_FAILED_EXTEND)
4952 try:
4953 self.db.share_update(
4954 context, share['id'],
4955 {'status': constants.STATUS_EXTENDING_ERROR}
4956 )
4957 raise exception.ShareExtendingError(
4958 reason=str(e), share_id=share_id)
4959 finally:
4960 QUOTAS.rollback(
4961 context, reservations, project_id=project_id,
4962 user_id=user_id,
4963 share_type_id=share_instance['share_type_id'],
4964 )
4966 # we give the user_id of the share, to update the quota usage
4967 # for the user, who created the share, because on share delete
4968 # only this quota will be decreased
4969 QUOTAS.commit(
4970 context, reservations, project_id=project_id,
4971 user_id=user_id, share_type_id=share_instance['share_type_id'],
4972 )
4974 share_update = {
4975 'size': int(new_size),
4976 # NOTE(u_glide): translation to lower case should be removed in
4977 # a row with usage of upper case of share statuses in all places
4978 'status': constants.STATUS_AVAILABLE.lower()
4979 }
4980 share = self.db.share_update(context, share['id'], share_update)
4982 LOG.info("Extend share completed successfully.", resource=share)
4984 self._notify_about_share_usage(context, share,
4985 share_instance, "extend.end")
4987 @add_hooks
4988 @utils.require_driver_initialized
4989 def shrink_share(self, context, share_id, new_size):
4990 context = context.elevated()
4991 share = self.db.share_get(context, share_id)
4992 share_instance = self._get_share_instance(context, share)
4993 share_server = self._get_share_server(context, share_instance)
4994 project_id = share['project_id']
4995 user_id = share['user_id']
4996 new_size = int(new_size)
4997 replicas = self.db.share_replicas_get_all_by_share(
4998 context, share['id'])
4999 supports_replication = len(replicas) > 0
5001 self._notify_about_share_usage(context, share,
5002 share_instance, "shrink.start")
5004 def error_occurred(exc, msg, status=constants.STATUS_SHRINKING_ERROR):
5005 if isinstance(exc, NotImplementedError):
5006 msg = _("Shrink share operation not supported.")
5007 status = constants.STATUS_AVAILABLE
5008 self.message_api.create(
5009 context,
5010 message_field.Action.SHRINK,
5011 share['project_id'],
5012 resource_type=message_field.Resource.SHARE,
5013 resource_id=share['id'],
5014 detail=message_field.Detail.DRIVER_FAILED_SHRINK)
5015 LOG.exception(msg, resource=share)
5016 self.db.share_update(context, share['id'], {'status': status})
5018 raise exception.ShareShrinkingError(
5019 reason=str(exc), share_id=share_id)
5021 reservations = None
5023 try:
5024 size_decrease = int(share['size']) - new_size
5025 # we give the user_id of the share, to update the quota usage
5026 # for the user, who created the share, because on share delete
5027 # only this quota will be decreased
5028 deltas = {
5029 'project_id': project_id,
5030 'user_id': user_id,
5031 'share_type_id': share_instance['share_type_id'],
5032 'gigabytes': -size_decrease,
5033 }
5034 # NOTE(carloss): if the share supports replication we need
5035 # to query all its replicas and calculate the final size to
5036 # deallocate (amount of replicas * size to decrease).
5037 if supports_replication:
5038 replica_gigs_to_deallocate = len(replicas) * size_decrease
5039 deltas.update(
5040 {'replica_gigabytes': -replica_gigs_to_deallocate})
5041 reservations = QUOTAS.reserve(context, **deltas)
5042 except Exception as e:
5043 error_occurred(
5044 e, ("Failed to update quota on share shrinking."))
5046 try:
5047 self.driver.shrink_share(
5048 share_instance, new_size, share_server=share_server)
5049 # NOTE(u_glide): Replace following except block by error notification
5050 # when Manila has such mechanism. It's possible because drivers
5051 # shouldn't shrink share when this validation error occurs.
5052 except Exception as e:
5053 if isinstance(e, exception.ShareShrinkingPossibleDataLoss):
5054 msg = ("Shrink share failed due to possible data loss.")
5055 status = constants.STATUS_AVAILABLE
5056 error_params = {'msg': msg, 'status': status}
5057 self.message_api.create(
5058 context,
5059 message_field.Action.SHRINK,
5060 share['project_id'],
5061 resource_type=message_field.Resource.SHARE,
5062 resource_id=share_id,
5063 detail=message_field.Detail.DRIVER_REFUSED_SHRINK)
5064 else:
5065 error_params = {'msg': ("Shrink share failed.")}
5067 try:
5068 error_occurred(e, **error_params)
5069 finally:
5070 QUOTAS.rollback(
5071 context, reservations, project_id=project_id,
5072 user_id=user_id,
5073 share_type_id=share_instance['share_type_id'],
5074 )
5076 QUOTAS.commit(
5077 context, reservations, project_id=project_id,
5078 user_id=user_id, share_type_id=share_instance['share_type_id'],
5079 )
5081 share_update = {
5082 'size': new_size,
5083 'status': constants.STATUS_AVAILABLE
5084 }
5085 share = self.db.share_update(context, share['id'], share_update)
5087 LOG.info("Shrink share completed successfully.", resource=share)
5089 self._notify_about_share_usage(context, share,
5090 share_instance, "shrink.end")
5092 @utils.require_driver_initialized
5093 def create_share_group(self, context, share_group_id):
5094 context = context.elevated()
5095 share_group_ref = self.db.share_group_get(context, share_group_id)
5096 share_group_ref['host'] = self.host
5097 shares = self.db.share_instance_get_all_by_share_group_id(
5098 context, share_group_id)
5100 source_share_group_snapshot_id = share_group_ref.get(
5101 "source_share_group_snapshot_id")
5102 snap_ref = None
5103 parent_share_server_id = None
5104 if source_share_group_snapshot_id:
5105 snap_ref = self.db.share_group_snapshot_get(
5106 context, source_share_group_snapshot_id)
5107 for member in snap_ref['share_group_snapshot_members']: 5107 ↛ 5108line 5107 didn't jump to line 5108 because the loop on line 5107 never started
5108 member['share'] = self.db.share_instance_get(
5109 context, member['share_instance_id'], with_share_data=True)
5110 if 'share_group' in snap_ref:
5111 parent_share_server_id = snap_ref['share_group'][
5112 'share_server_id']
5114 status = constants.STATUS_AVAILABLE
5116 share_network_id = share_group_ref.get('share_network_id')
5117 share_server = None
5119 if parent_share_server_id and self.driver.driver_handles_share_servers:
5120 share_server = self.db.share_server_get(context,
5121 parent_share_server_id)
5122 share_network_id = (
5123 share_server['share_network_id'])
5125 if share_network_id and not self.driver.driver_handles_share_servers:
5126 self.db.share_group_update(
5127 context, share_group_id, {'status': constants.STATUS_ERROR})
5128 msg = _("Driver does not expect share-network to be provided "
5129 "with current configuration.")
5130 raise exception.InvalidInput(reason=msg)
5132 if not share_server and share_network_id:
5134 availability_zone_id = self._get_az_for_share_group(
5135 context, share_group_ref)
5136 subnets = (
5137 self.db.share_network_subnets_get_all_by_availability_zone_id(
5138 context, share_network_id, availability_zone_id))
5140 if not subnets:
5141 raise exception.ShareNetworkSubnetNotFound(
5142 share_network_subnet_id=None)
5143 try:
5144 share_server, share_group_ref = (
5145 self._provide_share_server_for_share_group(
5146 context, share_network_id, subnets, share_group_ref,
5147 share_group_snapshot=snap_ref,
5148 )
5149 )
5150 except Exception:
5151 with excutils.save_and_reraise_exception():
5152 LOG.error("Failed to get share server"
5153 " for share group creation.")
5154 self.db.share_group_update(
5155 context, share_group_id,
5156 {'status': constants.STATUS_ERROR})
5157 self.message_api.create(
5158 context,
5159 message_field.Action.CREATE,
5160 share_group_ref['project_id'],
5161 resource_type=message_field.Resource.SHARE_GROUP,
5162 resource_id=share_group_id,
5163 detail=message_field.Detail.NO_SHARE_SERVER)
5165 try:
5166 # TODO(ameade): Add notification for create.start
5167 LOG.info("Share group %s: creating", share_group_id)
5169 model_update, share_update_list = None, None
5171 share_group_ref['shares'] = shares
5172 if snap_ref:
5173 model_update, share_update_list = (
5174 self.driver.create_share_group_from_share_group_snapshot(
5175 context, share_group_ref, snap_ref,
5176 share_server=share_server))
5177 else:
5178 model_update = self.driver.create_share_group(
5179 context, share_group_ref, share_server=share_server)
5181 if model_update:
5182 share_group_ref = self.db.share_group_update(
5183 context, share_group_ref['id'], model_update)
5185 if share_update_list:
5186 for share in share_update_list:
5187 values = copy.deepcopy(share)
5188 # NOTE(dviroel): To keep backward compatibility we can't
5189 # keep 'status' as a mandatory parameter. We'll set its
5190 # value to 'available' as default.
5191 i_status = values.get('status', constants.STATUS_AVAILABLE)
5192 if i_status not in [
5193 constants.STATUS_AVAILABLE,
5194 constants.STATUS_CREATING_FROM_SNAPSHOT]:
5195 msg = _(
5196 'Driver returned an invalid status %s') % i_status
5197 raise exception.InvalidShareInstance(reason=msg)
5198 values['status'] = i_status
5199 values['progress'] = (
5200 '100%' if i_status == constants.STATUS_AVAILABLE
5201 else '0%')
5202 values.pop('id')
5203 export_locations = values.pop('export_locations')
5204 self.db.share_instance_update(context, share['id'], values)
5205 self.db.export_locations_update(
5206 context, share['id'], export_locations)
5208 except Exception:
5209 with excutils.save_and_reraise_exception():
5210 self.db.share_group_update(
5211 context,
5212 share_group_ref['id'],
5213 {'status': constants.STATUS_ERROR,
5214 'availability_zone_id': self._get_az_for_share_group(
5215 context, share_group_ref),
5216 'consistent_snapshot_support': self.driver._stats[
5217 'share_group_stats'].get(
5218 'consistent_snapshot_support')})
5219 for share in shares:
5220 self.db.share_instance_update(
5221 context, share['id'],
5222 {'status': constants.STATUS_ERROR})
5223 LOG.error("Share group %s: create failed", share_group_id)
5225 now = timeutils.utcnow()
5226 for share in shares: 5226 ↛ 5227line 5226 didn't jump to line 5227 because the loop on line 5226 never started
5227 self.db.share_instance_update(
5228 context, share['id'], {'status': constants.STATUS_AVAILABLE})
5229 self.db.share_group_update(
5230 context,
5231 share_group_ref['id'],
5232 {'status': status,
5233 'created_at': now,
5234 'availability_zone_id': self._get_az_for_share_group(
5235 context, share_group_ref),
5236 'consistent_snapshot_support': self.driver._stats[
5237 'share_group_stats'].get('consistent_snapshot_support')})
5238 LOG.info("Share group %s: created successfully", share_group_id)
5240 # TODO(ameade): Add notification for create.end
5242 return share_group_ref['id']
5244 def _get_az_for_share_group(self, context, share_group_ref):
5245 if not share_group_ref['availability_zone_id']: 5245 ↛ 5246line 5245 didn't jump to line 5246 because the condition on line 5245 was never true
5246 return self.db.availability_zone_get(
5247 context, self.availability_zone)['id']
5248 return share_group_ref['availability_zone_id']
5250 @utils.require_driver_initialized
5251 def delete_share_group(self, context, share_group_id):
5252 context = context.elevated()
5253 share_group_ref = self.db.share_group_get(context, share_group_id)
5254 share_group_ref['host'] = self.host
5255 share_group_ref['shares'] = (
5256 self.db.share_instance_get_all_by_share_group_id(
5257 context, share_group_id))
5259 # TODO(ameade): Add notification for delete.start
5261 try:
5262 LOG.info("Share group %s: deleting", share_group_id)
5263 share_server = None
5264 if share_group_ref.get('share_server_id'): 5264 ↛ 5265line 5264 didn't jump to line 5265 because the condition on line 5264 was never true
5265 share_server = self.db.share_server_get(
5266 context, share_group_ref['share_server_id'])
5267 model_update = self.driver.delete_share_group(
5268 context, share_group_ref, share_server=share_server)
5270 if model_update:
5271 share_group_ref = self.db.share_group_update(
5272 context, share_group_ref['id'], model_update)
5274 except Exception:
5275 with excutils.save_and_reraise_exception():
5276 self.db.share_group_update(
5277 context,
5278 share_group_ref['id'],
5279 {'status': constants.STATUS_ERROR})
5280 LOG.error("Share group %s: delete failed",
5281 share_group_ref['id'])
5283 self.db.share_group_destroy(context, share_group_id)
5284 LOG.info("Share group %s: deleted successfully", share_group_id)
5286 # TODO(ameade): Add notification for delete.end
5288 @utils.require_driver_initialized
5289 def create_share_group_snapshot(self, context, share_group_snapshot_id):
5290 context = context.elevated()
5291 snap_ref = self.db.share_group_snapshot_get(
5292 context, share_group_snapshot_id)
5293 for member in snap_ref['share_group_snapshot_members']:
5294 member['share'] = self.db.share_instance_get(
5295 context, member['share_instance_id'], with_share_data=True)
5297 status = constants.STATUS_AVAILABLE
5298 now = timeutils.utcnow()
5299 updated_members_ids = []
5301 try:
5302 LOG.info("Share group snapshot %s: creating",
5303 share_group_snapshot_id)
5304 share_server = None
5305 if snap_ref['share_group'].get('share_server_id'): 5305 ↛ 5306line 5305 didn't jump to line 5306 because the condition on line 5305 was never true
5306 share_server = self.db.share_server_get(
5307 context, snap_ref['share_group']['share_server_id'])
5308 snapshot_update, member_update_list = (
5309 self.driver.create_share_group_snapshot(
5310 context, snap_ref, share_server=share_server))
5312 for update in (member_update_list or []):
5313 # NOTE(vponomaryov): we expect that each member is a dict
5314 # and has required 'id' key and some optional keys
5315 # to be updated such as 'provider_location'. It is planned
5316 # to have here also 'export_locations' when it is supported.
5317 member_id = update.pop('id', None)
5318 if not member_id:
5319 LOG.warning(
5320 "One of share group snapshot '%s' members does not "
5321 "have reference ID. Its update was skipped.",
5322 share_group_snapshot_id)
5323 continue
5324 # TODO(vponomaryov): remove following condition when
5325 # sgs members start supporting export locations.
5326 if 'export_locations' in update: 5326 ↛ 5334line 5326 didn't jump to line 5334 because the condition on line 5326 was always true
5327 LOG.debug(
5328 "Removing 'export_locations' data from "
5329 "share group snapshot member '%s' update because "
5330 "export locations are not supported.",
5331 member_id)
5332 update.pop('export_locations')
5334 db_update = {
5335 'updated_at': now,
5336 'status': update.pop('status', status)
5337 }
5338 if 'provider_location' in update: 5338 ↛ 5341line 5338 didn't jump to line 5341 because the condition on line 5338 was always true
5339 db_update['provider_location'] = (
5340 update.pop('provider_location'))
5341 if 'size' in update: 5341 ↛ 5344line 5341 didn't jump to line 5344 because the condition on line 5341 was always true
5342 db_update['size'] = int(update.pop('size'))
5344 updated_members_ids.append(member_id)
5345 self.db.share_group_snapshot_member_update(
5346 context, member_id, db_update)
5348 if update: 5348 ↛ 5312line 5348 didn't jump to line 5312 because the condition on line 5348 was always true
5349 LOG.debug(
5350 "Share group snapshot ID='%(sgs_id)s', "
5351 "share group snapshot member ID='%(sgsm_id)s'. "
5352 "Following keys of sgs member were not updated "
5353 "as not allowed: %(keys)s.",
5354 {'sgs_id': share_group_snapshot_id,
5355 'sgsm_id': member_id,
5356 'keys': ', '.join(update)})
5358 if snapshot_update:
5359 snap_ref = self.db.share_group_snapshot_update(
5360 context, snap_ref['id'], snapshot_update)
5362 except Exception:
5363 with excutils.save_and_reraise_exception():
5364 self.db.share_group_snapshot_update(
5365 context,
5366 snap_ref['id'],
5367 {'status': constants.STATUS_ERROR})
5368 LOG.error("Share group snapshot %s: create failed",
5369 share_group_snapshot_id)
5371 for member in (snap_ref.get('share_group_snapshot_members') or []):
5372 if member['id'] in updated_members_ids:
5373 continue
5374 update = {'status': status, 'updated_at': now}
5375 self.db.share_group_snapshot_member_update(
5376 context, member['id'], update)
5378 self.db.share_group_snapshot_update(
5379 context, snap_ref['id'],
5380 {'status': status, 'updated_at': now})
5381 LOG.info("Share group snapshot %s: created successfully",
5382 share_group_snapshot_id)
5384 return snap_ref['id']
5386 @utils.require_driver_initialized
5387 def delete_share_group_snapshot(self, context, share_group_snapshot_id):
5388 context = context.elevated()
5389 snap_ref = self.db.share_group_snapshot_get(
5390 context, share_group_snapshot_id)
5391 for member in snap_ref['share_group_snapshot_members']:
5392 member['share'] = self.db.share_instance_get(
5393 context, member['share_instance_id'], with_share_data=True)
5395 snapshot_update = False
5397 try:
5398 LOG.info("Share group snapshot %s: deleting",
5399 share_group_snapshot_id)
5401 share_server = None
5402 if snap_ref['share_group'].get('share_server_id'):
5403 share_server = self.db.share_server_get(
5404 context, snap_ref['share_group']['share_server_id'])
5406 snapshot_update, member_update_list = (
5407 self.driver.delete_share_group_snapshot(
5408 context, snap_ref, share_server=share_server))
5410 if member_update_list:
5411 snapshot_update = snapshot_update or {}
5412 snapshot_update['share_group_snapshot_members'] = []
5413 for update in (member_update_list or []):
5414 snapshot_update['share_group_snapshot_members'].append(update)
5416 if snapshot_update:
5417 snap_ref = self.db.share_group_snapshot_update(
5418 context, snap_ref['id'], snapshot_update)
5420 except Exception:
5421 with excutils.save_and_reraise_exception():
5422 self.db.share_group_snapshot_update(
5423 context,
5424 snap_ref['id'],
5425 {'status': constants.STATUS_ERROR})
5426 LOG.error("Share group snapshot %s: delete failed",
5427 snap_ref['name'])
5429 self.db.share_group_snapshot_destroy(context, share_group_snapshot_id)
5431 LOG.info("Share group snapshot %s: deleted successfully",
5432 share_group_snapshot_id)
5434 def _get_share_server_dict(self, context, share_server):
5435 share_server_ref = {
5436 'id': share_server.get('id'),
5437 'project_id': share_server.get('project_id'),
5438 'updated_at': share_server.get('updated_at'),
5439 'status': share_server.get('status'),
5440 'host': share_server.get('host'),
5441 'share_network_name': share_server.get('share_network_name'),
5442 'share_network_id': share_server.get('share_network_id'),
5443 'created_at': share_server.get('created_at'),
5444 'backend_details': share_server.get('backend_details'),
5445 'share_network_subnet_ids':
5446 share_server.get('share_network_subnet_ids', []),
5447 'is_auto_deletable': share_server.get('is_auto_deletable', None),
5448 'identifier': share_server.get('identifier', None),
5449 'network_allocations': share_server.get('network_allocations',
5450 None),
5451 }
5452 return share_server_ref
5454 def _get_export_location_dict(self, context, export_location):
5455 export_location_ref = {
5456 'id': export_location.get('uuid'),
5457 'path': export_location.get('path'),
5458 'created_at': export_location.get('created_at'),
5459 'updated_at': export_location.get('updated_at'),
5460 'share_instance_id':
5461 export_location.get('share_instance_id', None),
5462 'is_admin_only': export_location.get('is_admin_only', None),
5463 }
5464 return export_location_ref
5466 def _get_share_instance_dict(self, context, share_instance):
5467 # TODO(gouthamr): remove method when the db layer returns primitives
5468 share_instance_ref = {
5469 'id': share_instance.get('id'),
5470 'name': share_instance.get('name'),
5471 'share_id': share_instance.get('share_id'),
5472 'host': share_instance.get('host'),
5473 'status': share_instance.get('status'),
5474 'replica_state': share_instance.get('replica_state'),
5475 'availability_zone_id': share_instance.get('availability_zone_id'),
5476 'share_network_id': share_instance.get('share_network_id'),
5477 'share_server_id': share_instance.get('share_server_id'),
5478 'deleted': share_instance.get('deleted'),
5479 'terminated_at': share_instance.get('terminated_at'),
5480 'launched_at': share_instance.get('launched_at'),
5481 'scheduled_at': share_instance.get('scheduled_at'),
5482 'updated_at': share_instance.get('updated_at'),
5483 'deleted_at': share_instance.get('deleted_at'),
5484 'created_at': share_instance.get('created_at'),
5485 'share_server': self._get_share_server(context, share_instance),
5486 'access_rules_status': share_instance.get('access_rules_status'),
5487 # Share details
5488 'user_id': share_instance.get('user_id'),
5489 'project_id': share_instance.get('project_id'),
5490 'size': share_instance.get('size'),
5491 'display_name': share_instance.get('display_name'),
5492 'display_description': share_instance.get('display_description'),
5493 'snapshot_id': share_instance.get('snapshot_id'),
5494 'share_proto': share_instance.get('share_proto'),
5495 'share_type_id': share_instance.get('share_type_id'),
5496 'is_public': share_instance.get('is_public'),
5497 'share_group_id': share_instance.get('share_group_id'),
5498 'source_share_group_snapshot_member_id': share_instance.get(
5499 'source_share_group_snapshot_member_id'),
5500 'availability_zone': share_instance.get('availability_zone'),
5501 'mount_point_name': share_instance.get('mount_point_name'),
5502 }
5503 if share_instance_ref['share_server']:
5504 share_instance_ref['share_server'] = self._get_share_server_dict(
5505 context, share_instance_ref['share_server']
5506 )
5507 share_instance_ref['export_locations'] = [
5508 self._get_export_location_dict(context, el) for
5509 el in share_instance.get('export_locations') or []
5510 ]
5511 return share_instance_ref
5513 def _get_snapshot_instance_dict(self, context, snapshot_instance,
5514 snapshot=None):
5515 # TODO(gouthamr): remove method when the db layer returns primitives
5516 snapshot_instance_ref = {
5517 'name': snapshot_instance.get('name'),
5518 'share_id': snapshot_instance.get('share_id'),
5519 'share_name': snapshot_instance.get('share_name'),
5520 'status': snapshot_instance.get('status'),
5521 'id': snapshot_instance.get('id'),
5522 'deleted': snapshot_instance.get('deleted') or False,
5523 'created_at': snapshot_instance.get('created_at'),
5524 'share': snapshot_instance.get('share'),
5525 'updated_at': snapshot_instance.get('updated_at'),
5526 'share_instance_id': snapshot_instance.get('share_instance_id'),
5527 'snapshot_id': snapshot_instance.get('snapshot_id'),
5528 'progress': snapshot_instance.get('progress'),
5529 'deleted_at': snapshot_instance.get('deleted_at'),
5530 'provider_location': snapshot_instance.get('provider_location'),
5531 }
5533 if snapshot:
5534 snapshot_instance_ref.update({
5535 'size': snapshot.get('size'),
5536 })
5538 return snapshot_instance_ref
5540 def snapshot_update_access(self, context, snapshot_instance_id):
5541 snapshot_instance = self.db.share_snapshot_instance_get(
5542 context, snapshot_instance_id, with_share_data=True)
5544 share_server = self._get_share_server(
5545 context, snapshot_instance['share_instance'])
5547 self.snapshot_access_helper.update_access_rules(
5548 context, snapshot_instance['id'], share_server=share_server)
5550 def _notify_about_share_usage(self, context, share, share_instance,
5551 event_suffix, extra_usage_info=None):
5552 share_utils.notify_about_share_usage(
5553 context, share, share_instance, event_suffix,
5554 extra_usage_info=extra_usage_info, host=self.host)
5556 @utils.require_driver_initialized
5557 def create_backup(self, context, backup):
5558 share_id = backup['share_id']
5559 backup_id = backup['id']
5560 share = self.db.share_get(context, share_id)
5561 share_instance = self._get_share_instance(context, share)
5563 LOG.info('Create backup started, backup: %(backup)s share: '
5564 '%(share)s.', {'backup': backup_id, 'share': share_id})
5565 try:
5566 share_server = self._get_share_server(context, share)
5567 self.driver.create_backup(context, share_instance, backup,
5568 share_server=share_server)
5569 except Exception as err:
5570 with excutils.save_and_reraise_exception():
5571 LOG.error("Failed to create share backup %s by driver.",
5572 backup_id)
5573 self.db.share_update(
5574 context, share_id,
5575 {'status': constants.STATUS_AVAILABLE})
5576 self.db.share_backup_update(
5577 context, backup_id,
5578 {'status': constants.STATUS_ERROR, 'fail_reason': err})
5580 @periodic_task.periodic_task(
5581 spacing=CONF.driver_backup_continue_update_interval)
5582 @utils.require_driver_initialized
5583 def create_backup_continue(self, context):
5584 """Invokes driver to continue backup of share."""
5585 filters = {'status': constants.STATUS_CREATING,
5586 'host': self.host,
5587 'topic': CONF.share_topic}
5588 backups = self.db.share_backups_get_all(context, filters)
5589 for backup in backups: 5589 ↛ 5590line 5589 didn't jump to line 5590 because the loop on line 5589 never started
5590 backup_id = backup['id']
5591 share_id = backup['share_id']
5592 share = self.db.share_get(context, share_id)
5593 share_instance = self._get_share_instance(context, share)
5594 result = {}
5595 try:
5596 share_server = self._get_share_server(context, share_instance)
5597 result = self.driver.create_backup_continue(
5598 context, share_instance, backup, share_server=share_server)
5599 progress = result.get('total_progress', '0')
5600 self.db.share_backup_update(context, backup_id,
5601 {'progress': progress})
5602 if progress == '100':
5603 self.db.share_update(
5604 context, share_id,
5605 {'status': constants.STATUS_AVAILABLE})
5606 self.db.share_backup_update(
5607 context, backup_id,
5608 {'status': constants.STATUS_AVAILABLE})
5609 LOG.info("Created share backup %s successfully.",
5610 backup_id)
5611 except Exception:
5612 LOG.warning("Failed to get progress of share %(share)s "
5613 "backing up in share_backup %(backup).",
5614 {'share': share_id, 'backup': backup_id})
5615 self.db.share_update(
5616 context, share_id,
5617 {'status': constants.STATUS_AVAILABLE})
5618 self.db.share_backup_update(
5619 context, backup_id,
5620 {'status': constants.STATUS_ERROR, 'progress': '0'})
5622 def delete_backup(self, context, backup):
5623 LOG.info('Delete backup started, backup: %s.', backup['id'])
5625 backup_id = backup['id']
5626 project_id = backup['project_id']
5627 share_id = backup['share_id']
5628 share = self.db.share_get(context, share_id)
5629 share_instance = self._get_share_instance(context, share)
5630 try:
5631 share_server = self._get_share_server(context, share_instance)
5632 self.driver.delete_backup(context, backup, share_instance,
5633 share_server=share_server)
5634 except Exception:
5635 with excutils.save_and_reraise_exception():
5636 LOG.error("Failed to delete share backup %s.", backup_id)
5637 self.db.share_backup_update(
5638 context, backup_id,
5639 {'status': constants.STATUS_ERROR_DELETING})
5641 try:
5642 reserve_opts = {
5643 'backups': -1,
5644 'backup_gigabytes': -backup['size'],
5645 }
5646 reservations = QUOTAS.reserve(
5647 context, project_id=project_id, **reserve_opts)
5648 except Exception as e:
5649 reservations = None
5650 LOG.warning("Failed to update backup quota for %(pid)s: %(err)s.",
5651 {'pid': project_id, 'err': e})
5653 if reservations:
5654 QUOTAS.commit(context, reservations, project_id=project_id)
5656 self.db.share_backup_delete(context, backup_id)
5657 LOG.info("Share backup %s deleted successfully.", backup_id)
5659 def restore_backup(self, context, backup, share_id):
5660 LOG.info('Restore backup started, backup: %(backup_id)s '
5661 'share: %(share_id)s.',
5662 {'backup_id': backup['id'], 'share_id': share_id})
5664 backup_id = backup['id']
5665 backup_share_id = backup['share_id']
5666 share = self.db.share_get(context, share_id)
5667 share_instance = self._get_share_instance(context, share)
5669 try:
5670 if (self.driver.restore_to_target_support is False and 5670 ↛ 5689line 5670 didn't jump to line 5689 because the condition on line 5670 was always true
5671 share_id != backup_share_id):
5673 self.message_api.create(
5674 context,
5675 message_field.Action.RESTORE_BACKUP,
5676 share['project_id'],
5677 resource_type=message_field.Resource.SHARE,
5678 resource_id=share['id'],
5679 detail=message_field.Detail.TARGETED_RESTORE_UNSUPPORTED
5680 )
5682 msg = _("Cannot restore backup %(backup)s to target share "
5683 "%(share)s as share driver does not provide support "
5684 " for targeted restores") % (
5685 {'backup': backup_id, 'share': share_id})
5686 LOG.exception(msg)
5687 raise exception.BackupException(reason=msg)
5689 share_server = self._get_share_server(context, share_instance)
5690 self.driver.restore_backup(context, backup, share_instance,
5691 share_server=share_server)
5692 except Exception:
5693 with excutils.save_and_reraise_exception():
5694 LOG.error("Failed to restore backup %(backup)s to share "
5695 "%(share)s by driver.",
5696 {'backup': backup_id, 'share': share_id})
5697 self.db.share_update(
5698 context, share_id,
5699 {'status': constants.STATUS_BACKUP_RESTORING_ERROR})
5700 self.db.share_backup_update(
5701 context, backup['id'],
5702 {'status': constants.STATUS_ERROR})
5704 @periodic_task.periodic_task(
5705 spacing=CONF.driver_restore_continue_update_interval)
5706 @utils.require_driver_initialized
5707 def restore_backup_continue(self, context):
5708 filters = {'status': constants.STATUS_RESTORING,
5709 'host': self.host,
5710 'topic': CONF.share_topic}
5711 backups = self.db.share_backups_get_all(context, filters)
5712 for backup in backups: 5712 ↛ 5713line 5712 didn't jump to line 5713 because the loop on line 5712 never started
5713 backup_id = backup['id']
5714 try:
5715 filters = {
5716 'source_backup_id': backup_id,
5717 }
5718 shares = self.db.share_get_all(context, filters)
5719 except Exception:
5720 LOG.warning('Failed to get shares for backup %s', backup_id)
5721 continue
5723 for share in shares:
5724 if share['status'] != constants.STATUS_BACKUP_RESTORING:
5725 continue
5727 share_id = share['id']
5728 share_instance = self._get_share_instance(context, share)
5729 try:
5730 share_server = self._get_share_server(
5731 context, share_instance)
5732 result = self.driver.restore_backup_continue(
5733 context, backup, share_instance,
5734 share_server=share_server)
5735 progress = result.get('total_progress', '0')
5736 self.db.share_backup_update(
5737 context, backup_id, {'restore_progress': progress})
5739 if progress == '100':
5740 self.db.share_update(
5741 context, share_id,
5742 {'status': constants.STATUS_AVAILABLE})
5743 self.db.share_backup_update(
5744 context, backup_id,
5745 {'status': constants.STATUS_AVAILABLE})
5746 LOG.info("Share backup %s restored successfully.",
5747 backup_id)
5748 except Exception:
5749 LOG.exception("Failed to get progress of share_backup "
5750 "%(backup)s restoring in share %(share).",
5751 {'share': share_id, 'backup': backup_id})
5752 self.db.share_update(
5753 context, share_id,
5754 {'status': constants.STATUS_BACKUP_RESTORING_ERROR})
5755 self.db.share_backup_update(
5756 context, backup_id,
5757 {'status': constants.STATUS_AVAILABLE,
5758 'restore_progress': '0'})
5760 @periodic_task.periodic_task(
5761 spacing=CONF.share_usage_size_update_interval,
5762 enabled=CONF.enable_gathering_share_usage_size)
5763 @utils.require_driver_initialized
5764 def update_share_usage_size(self, context):
5765 """Invokes driver to gather usage size of shares."""
5766 updated_share_instances = []
5767 share_instances = self.db.share_instance_get_all_by_host(
5768 context, host=self.host, with_share_data=True)
5770 if share_instances: 5770 ↛ 5777line 5770 didn't jump to line 5777 because the condition on line 5770 was always true
5771 try:
5772 updated_share_instances = self.driver.update_share_usage_size(
5773 context, share_instances)
5774 except Exception:
5775 LOG.exception("Gather share usage size failure.")
5777 for si in updated_share_instances:
5778 share_instance = self._get_share_instance(context, si['id'])
5779 share = self.db.share_get(context, share_instance['share_id'])
5780 self._notify_about_share_usage(
5781 context, share, share_instance, "consumed.size",
5782 extra_usage_info={'used_size': si['used_size'],
5783 'gathered_at': si['gathered_at']})
5785 @periodic_task.periodic_task(spacing=CONF.periodic_interval)
5786 @utils.require_driver_initialized
5787 def periodic_share_status_update(self, context):
5788 """Invokes share driver to update shares status."""
5789 LOG.debug("Updating status of share instances.")
5790 share_instances = self.db.share_instance_get_all_by_host(
5791 context, self.host, with_share_data=True,
5792 status=constants.STATUS_CREATING_FROM_SNAPSHOT)
5794 for si in share_instances:
5795 si_dict = self._get_share_instance_dict(context, si)
5796 self._update_share_status(context, si_dict)
5798 def _update_share_status(self, context, share_instance):
5799 share_server = self._get_share_server(context, share_instance)
5800 if share_server is not None: 5800 ↛ 5801line 5800 didn't jump to line 5801 because the condition on line 5800 was never true
5801 share_server = self._get_share_server_dict(context,
5802 share_server)
5803 try:
5804 data_updates = self.driver.get_share_status(share_instance,
5805 share_server)
5806 except Exception:
5807 LOG.exception(
5808 ("Unexpected driver error occurred while updating status for "
5809 "share instance %(id)s that belongs to share '%(share_id)s'"),
5810 {'id': share_instance['id'],
5811 'share_id': share_instance['share_id']}
5812 )
5813 data_updates = {
5814 'status': constants.STATUS_ERROR
5815 }
5817 status = data_updates.get('status')
5818 if status == constants.STATUS_ERROR:
5819 msg = ("Status of share instance %(id)s that belongs to share "
5820 "%(share_id)s was updated to '%(status)s'."
5821 % {'id': share_instance['id'],
5822 'share_id': share_instance['share_id'],
5823 'status': status})
5824 LOG.error(msg)
5825 self.db.share_instance_update(
5826 context, share_instance['id'],
5827 {'status': constants.STATUS_ERROR,
5828 'progress': None})
5829 self.message_api.create(
5830 context,
5831 message_field.Action.UPDATE,
5832 share_instance['project_id'],
5833 resource_type=message_field.Resource.SHARE,
5834 resource_id=share_instance['share_id'],
5835 detail=message_field.Detail.DRIVER_FAILED_CREATING_FROM_SNAP)
5836 return
5838 export_locations = data_updates.get('export_locations')
5839 progress = data_updates.get('progress')
5841 statuses_requiring_update = [
5842 constants.STATUS_AVAILABLE,
5843 constants.STATUS_CREATING_FROM_SNAPSHOT]
5845 if status in statuses_requiring_update: 5845 ↛ 5861line 5845 didn't jump to line 5861 because the condition on line 5845 was always true
5846 si_updates = {
5847 'status': status,
5848 }
5849 progress = ('100%' if status == constants.STATUS_AVAILABLE
5850 else progress)
5851 if progress is not None: 5851 ↛ 5853line 5851 didn't jump to line 5853 because the condition on line 5851 was always true
5852 si_updates.update({'progress': progress})
5853 self.db.share_instance_update(
5854 context, share_instance['id'], si_updates)
5855 msg = ("Status of share instance %(id)s that belongs to share "
5856 "%(share_id)s was updated to '%(status)s'."
5857 % {'id': share_instance['id'],
5858 'share_id': share_instance['share_id'],
5859 'status': status})
5860 LOG.debug(msg)
5861 if export_locations: 5861 ↛ exitline 5861 didn't return from function '_update_share_status' because the condition on line 5861 was always true
5862 self.db.export_locations_update(
5863 context, share_instance['id'], export_locations)
5865 def _validate_check_compatibility_result(
5866 self, context, resource_id, share_instances, snapshot_instances,
5867 driver_compatibility, dest_host, nondisruptive, writable,
5868 preserve_snapshots, resource_type='share'):
5869 resource_exception = (
5870 exception.ShareMigrationFailed
5871 if resource_type == 'share'
5872 else exception.ShareServerMigrationFailed)
5873 if not driver_compatibility.get('compatible'):
5874 msg = _("Destination host %(host)s is not compatible with "
5875 "%(resource_type)s %(resource_id)s's source backend for "
5876 "driver-assisted migration.") % {
5877 'host': dest_host,
5878 'resource_id': resource_id,
5879 'resource_type': resource_type,
5880 }
5881 raise resource_exception(reason=msg)
5883 if (not driver_compatibility.get('nondisruptive') and
5884 nondisruptive):
5885 msg = _("Driver cannot perform a non-disruptive migration of "
5886 "%(resource_type)s %(resource_id)s.") % {
5887 'resource_type': resource_type,
5888 'resource_id': resource_id
5889 }
5890 raise resource_exception(reason=msg)
5892 if not driver_compatibility.get('writable') and writable:
5893 msg = _("Driver cannot perform migration of %(resource_type)s "
5894 "%(resource_id)s while remaining writable.") % {
5895 'resource_type': resource_type,
5896 'resource_id': resource_id
5897 }
5898 raise resource_exception(reason=msg)
5900 if (not driver_compatibility.get('preserve_snapshots')
5901 and preserve_snapshots):
5902 msg = _("Driver cannot perform migration of %(resource_type)s "
5903 "%(resource_id)s while preserving snapshots.") % {
5904 'resource_type': resource_type,
5905 'resource_id': resource_id
5906 }
5907 raise resource_exception(reason=msg)
5909 if (not driver_compatibility.get('preserve_snapshots') 5909 ↛ exitline 5909 didn't return from function '_validate_check_compatibility_result' because the condition on line 5909 was always true
5910 and snapshot_instances):
5911 msg = _("Driver does not support preserving snapshots. The "
5912 "migration of the %(resource_type)s %(resource_id)s "
5913 "cannot proceed while it has snapshots.") % {
5914 'resource_type': resource_type,
5915 'resource_id': resource_id
5916 }
5917 raise resource_exception(reason=msg)
5919 def _update_resource_status(self, context, status, task_state=None,
5920 share_instance_ids=None,
5921 snapshot_instance_ids=None):
5922 fields = {'status': status}
5923 if task_state:
5924 fields['task_state'] = task_state
5925 if share_instance_ids:
5926 self.db.share_instance_status_update(
5927 context, share_instance_ids, fields)
5928 if snapshot_instance_ids:
5929 self.db.share_snapshot_instances_status_update(
5930 context, snapshot_instance_ids, fields)
5932 def _share_server_migration_start_driver(
5933 self, context, source_share_server, dest_host, writable,
5934 nondisruptive, preserve_snapshots, new_share_network_id):
5936 share_instances = self.db.share_instance_get_all_by_share_server(
5937 context, source_share_server['id'], with_share_data=True)
5938 share_instance_ids = [x.id for x in share_instances]
5940 snapshot_instances = (
5941 self.db.share_snapshot_instance_get_all_with_filters(
5942 context, {'share_instance_ids': share_instance_ids}))
5943 snapshot_instance_ids = [x.id for x in snapshot_instances]
5945 old_share_network = self.db.share_network_get(
5946 context, share_instances[0]['share_network_id'])
5947 new_share_network = self.db.share_network_get(
5948 context, new_share_network_id)
5950 service_host = share_utils.extract_host(dest_host)
5951 service = self.db.service_get_by_args(
5952 context, service_host, 'manila-share')
5954 # NOTE(dviroel): We'll build a list of request specs and send it to
5955 # the driver so vendors have a chance to validate if the destination
5956 # host meets the requirements before starting the migration.
5957 shares_request_spec = (
5958 self.share_api.get_share_server_migration_request_spec_dict(
5959 context,
5960 share_instances,
5961 snapshot_instances,
5962 availability_zone_id=service['availability_zone_id'],
5963 share_network_id=new_share_network_id))
5965 extended_allocs = None
5966 dest_share_server = None
5967 try:
5968 # NOTE: Extend network allocations to destination host, i.e. create
5969 # inactive port bindings on the destination host. Refresh
5970 # network_allocations field in source_share_server with the new
5971 # bindings, so that correct segmentation id is used during
5972 # compatibility check and migration.
5973 if CONF.server_migration_extend_neutron_network: 5973 ↛ 5974line 5973 didn't jump to line 5974 because the condition on line 5973 was never true
5974 extended_allocs = (
5975 self.driver.network_api.extend_network_allocations(
5976 context, source_share_server))
5977 source_share_server['network_allocations'] = extended_allocs
5979 compatibility = (
5980 self.driver.share_server_migration_check_compatibility(
5981 context, source_share_server, dest_host, old_share_network,
5982 new_share_network, shares_request_spec))
5984 self._validate_check_compatibility_result(
5985 context, source_share_server, share_instances,
5986 snapshot_instances, compatibility, dest_host, nondisruptive,
5987 writable, preserve_snapshots, resource_type='share server')
5989 create_server_on_backend = not compatibility.get('nondisruptive')
5990 dest_share_server = self._provide_share_server_for_migration(
5991 context, source_share_server, new_share_network_id,
5992 service['availability_zone_id'], dest_host,
5993 create_on_backend=create_server_on_backend)
5995 net_changes_identified = False
5996 if not create_server_on_backend:
5997 dest_share_server = self.db.share_server_get(
5998 context, dest_share_server['id'])
5999 net_changes_identified = (
6000 not share_utils.is_az_subnets_compatible(
6001 dest_share_server['share_network_subnets'],
6002 source_share_server['share_network_subnets']))
6004 # NOTE(carloss): Even though the share back end won't need to
6005 # create a share server, if a network change was identified,
6006 # there is need to allocate new interfaces to the share server,
6007 # so the back end can set up the new ips considering
6008 # the new networking parameters when completing the migration.
6009 # In such case, the migration will be disruptive, since the old
6010 # allocations will be replaced by the new ones.
6011 if net_changes_identified: 6011 ↛ 6027line 6011 didn't jump to line 6027 because the condition on line 6011 was always true
6012 share_network_subnets = (
6013 self.db.
6014 share_network_subnet_get_all_by_share_server_id(
6015 context, dest_share_server['id']))
6016 for share_network_subnet in share_network_subnets:
6017 self.driver.allocate_network(
6018 context, dest_share_server, new_share_network,
6019 share_network_subnet)
6020 self.driver.allocate_admin_network(
6021 context, dest_share_server)
6022 # Refresh the share server so it will have the network
6023 # allocations when sent to the driver
6024 dest_share_server = self.db.share_server_get(
6025 context, dest_share_server['id'])
6027 self.db.share_server_update(
6028 context, dest_share_server['id'],
6029 {'status': constants.STATUS_SERVER_MIGRATING_TO,
6030 'task_state': constants.TASK_STATE_MIGRATION_IN_PROGRESS,
6031 'source_share_server_id': source_share_server['id']})
6033 if not compatibility.get('writable'):
6034 # NOTE(dviroel): Only modify access rules to read-only if the
6035 # driver doesn't support 'writable'.
6036 self._cast_access_rules_to_readonly_for_server(
6037 context, share_instances, source_share_server,
6038 dest_host=source_share_server['host'])
6040 LOG.debug("Initiating driver migration for share server %s.",
6041 source_share_server['id'])
6043 self.db.share_server_update(
6044 context, source_share_server['id'],
6045 {'task_state': (
6046 constants.TASK_STATE_MIGRATION_DRIVER_STARTING)})
6047 self.db.share_server_update(
6048 context, dest_share_server['id'],
6049 {'task_state': (
6050 constants.TASK_STATE_MIGRATION_DRIVER_STARTING)})
6052 server_info = self.driver.share_server_migration_start(
6053 context, source_share_server, dest_share_server,
6054 share_instances, snapshot_instances)
6056 backend_details = (
6057 server_info.get('backend_details') if server_info else None)
6058 if extended_allocs: 6058 ↛ 6059line 6058 didn't jump to line 6059 because the condition on line 6058 was never true
6059 backend_details = backend_details or {}
6060 backend_details['segmentation_id'] = (
6061 extended_allocs[0]['segmentation_id'])
6062 if backend_details: 6062 ↛ 6066line 6062 didn't jump to line 6066 because the condition on line 6062 was always true
6063 self.db.share_server_backend_details_set(
6064 context, dest_share_server['id'], backend_details)
6066 self.db.share_server_update(
6067 context, source_share_server['id'],
6068 {'task_state': (
6069 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS)})
6070 self.db.share_server_update(
6071 context, dest_share_server['id'],
6072 {'task_state': (
6073 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS)})
6075 except Exception:
6076 # Rollback status changes for affected resources
6077 self._update_resource_status(
6078 context, constants.STATUS_AVAILABLE,
6079 share_instance_ids=share_instance_ids,
6080 snapshot_instance_ids=snapshot_instance_ids)
6081 # Rollback port bindings on destination host
6082 if extended_allocs: 6082 ↛ 6083line 6082 didn't jump to line 6083 because the condition on line 6082 was never true
6083 self.driver.network_api.delete_extended_allocations(
6084 context, source_share_server, dest_share_server)
6085 # Rollback read only access rules
6086 self._reset_read_only_access_rules_for_server(
6087 context, share_instances, source_share_server,
6088 dest_host=source_share_server['host'])
6089 if dest_share_server: 6089 ↛ 6102line 6089 didn't jump to line 6102 because the condition on line 6089 was always true
6090 self.db.share_server_update(
6091 context, dest_share_server['id'],
6092 {'task_state': constants.TASK_STATE_MIGRATION_ERROR,
6093 'status': constants.STATUS_ERROR})
6094 if not create_server_on_backend: 6094 ↛ 6095line 6094 didn't jump to line 6095 because the condition on line 6094 was never true
6095 if net_changes_identified:
6096 self.driver.deallocate_network(
6097 context, dest_share_server['id'])
6098 self.db.share_server_delete(
6099 context, dest_share_server['id'])
6100 else:
6101 self.delete_share_server(context, dest_share_server)
6102 msg = _("Driver-assisted migration of share server %s "
6103 "failed.") % source_share_server['id']
6104 LOG.exception(msg)
6105 raise exception.ShareServerMigrationFailed(reason=msg)
6107 return True
6109 @add_hooks
6110 @utils.require_driver_initialized
6111 def share_server_migration_check(self, context, share_server_id, dest_host,
6112 writable, nondisruptive,
6113 preserve_snapshots, new_share_network_id):
6114 driver_result = {}
6115 result = {
6116 'compatible': False,
6117 'writable': None,
6118 'preserve_snapshots': None,
6119 'nondisruptive': None,
6120 'share_network_id': new_share_network_id,
6121 'migration_cancel': None,
6122 'migration_get_progress': None
6123 }
6125 if not self.driver.driver_handles_share_servers:
6126 LOG.error('This operation is supported only on backends that '
6127 'handles share servers.')
6128 return result
6130 share_server = self.db.share_server_get(context, share_server_id)
6131 share_instances = self.db.share_instance_get_all_by_share_server(
6132 context, share_server_id, with_share_data=True)
6133 share_instance_ids = [x.id for x in share_instances]
6135 snapshot_instances = (
6136 self.db.share_snapshot_instance_get_all_with_filters(
6137 context, {'share_instance_ids': share_instance_ids}))
6139 old_share_network = self.db.share_network_get(
6140 context, share_instances[0]['share_network_id'])
6141 new_share_network = self.db.share_network_get(
6142 context, new_share_network_id)
6144 service_host = share_utils.extract_host(dest_host)
6145 service = self.db.service_get_by_args(
6146 context, service_host, 'manila-share')
6148 # NOTE: Extend network allocations to destination host, i.e. create
6149 # inactive port bindings on destination host with the same ports in the
6150 # share network subnet. Refresh share_server with new network
6151 # allocations, so that correct segmentation id is used in the
6152 # compatibility check.
6153 if CONF.server_migration_extend_neutron_network: 6153 ↛ 6154line 6153 didn't jump to line 6154 because the condition on line 6153 was never true
6154 try:
6155 allocs = self.driver.network_api.extend_network_allocations(
6156 context, share_server)
6157 share_server['network_allocations'] = allocs
6158 except Exception:
6159 LOG.warning(
6160 'Failed to extend network allocations for '
6161 'share server %s.', share_server['id'])
6162 return result
6164 # NOTE(dviroel): We'll build a list of request specs and send it to
6165 # the driver so vendors have a chance to validate if the destination
6166 # host meets the requirements before starting the migration.
6167 shares_request_spec = (
6168 self.share_api.get_share_server_migration_request_spec_dict(
6169 context,
6170 share_instances,
6171 snapshot_instances,
6172 availability_zone_id=service['availability_zone_id'],
6173 share_network_id=new_share_network_id))
6175 try:
6176 driver_result = (
6177 self.driver.share_server_migration_check_compatibility(
6178 context, share_server, dest_host, old_share_network,
6179 new_share_network, shares_request_spec))
6181 self._validate_check_compatibility_result(
6182 context, share_server, share_instances,
6183 snapshot_instances, driver_result, dest_host, nondisruptive,
6184 writable, preserve_snapshots, resource_type='share server')
6186 except Exception:
6187 # Update driver result to not compatible since it didn't pass in
6188 # the validations.
6189 driver_result['compatible'] = False
6191 # NOTE: Delete port bindings on destination host after compatibility
6192 # check
6193 if CONF.server_migration_extend_neutron_network: 6193 ↛ 6194line 6193 didn't jump to line 6194 because the condition on line 6193 was never true
6194 self.driver.network_api.delete_extended_allocations(
6195 context, share_server)
6197 result.update(driver_result)
6199 return result
6201 @add_hooks
6202 @utils.require_driver_initialized
6203 def share_server_migration_start(
6204 self, context, share_server_id, dest_host, writable,
6205 nondisruptive, preserve_snapshots, new_share_network_id=None):
6206 """Migrates a share server from current host to another host."""
6207 LOG.debug("Entered share_server_migration_start method for share "
6208 "server %s.", share_server_id)
6210 self.db.share_server_update(
6211 context, share_server_id,
6212 {'task_state': constants.TASK_STATE_MIGRATION_IN_PROGRESS})
6214 share_server = self.db.share_server_get(context, share_server_id)
6216 try:
6217 if not self.driver.driver_handles_share_servers:
6218 LOG.error('This operation is supported only on backends that '
6219 'handle share servers.')
6220 raise exception.OperationNotSupportedByDriverMode()
6222 self._share_server_migration_start_driver(
6223 context, share_server, dest_host, writable, nondisruptive,
6224 preserve_snapshots, new_share_network_id)
6225 except Exception:
6226 LOG.exception(
6227 ("The driver could not migrate the share server "
6228 "%(server)s"), {'server': share_server_id})
6229 self.db.share_server_update(
6230 context, share_server_id,
6231 {'task_state': constants.TASK_STATE_MIGRATION_ERROR,
6232 'status': constants.STATUS_ACTIVE})
6234 @periodic_task.periodic_task(
6235 spacing=CONF.server_migration_driver_continue_update_interval)
6236 @add_hooks
6237 @utils.require_driver_initialized
6238 def share_server_migration_driver_continue(self, context):
6239 """Invokes driver to continue migration of share server."""
6241 # Searching for destination share servers
6242 share_servers = self.db.share_server_get_all_by_host(
6243 context, self.host,
6244 filters={'status': constants.STATUS_SERVER_MIGRATING_TO})
6246 dest_updates_on_error = {
6247 'task_state': constants.TASK_STATE_MIGRATION_ERROR,
6248 'status': constants.STATUS_ERROR,
6249 }
6250 src_updates_on_error = {
6251 'task_state': constants.TASK_STATE_MIGRATION_ERROR,
6252 'status': constants.STATUS_ACTIVE,
6253 }
6254 updates_on_finished = {
6255 'task_state': constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE
6256 }
6257 for dest_share_server in share_servers:
6258 if dest_share_server['task_state'] == ( 6258 ↛ 6257line 6258 didn't jump to line 6257 because the condition on line 6258 was always true
6259 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS):
6260 src_share_server_id = dest_share_server.get(
6261 'source_share_server_id')
6262 if src_share_server_id is None: 6262 ↛ 6263line 6262 didn't jump to line 6263 because the condition on line 6262 was never true
6263 msg = _('Destination share server %s does not have a '
6264 'source share server id.'
6265 ) % dest_share_server['id']
6266 LOG.error(msg)
6267 self.db.share_server_update(
6268 context, dest_share_server['id'],
6269 dest_updates_on_error)
6270 continue
6271 msg_args = {
6272 'src_id': src_share_server_id,
6273 'dest_id': dest_share_server['id']
6274 }
6275 src_share_server = self.db.share_server_get(
6276 context, src_share_server_id)
6277 if not src_share_server:
6278 msg = _('Destination share server %(dest_id)s refers to '
6279 'a source share server %(src_id)s that does not '
6280 'exists.') % msg_args
6281 LOG.error(msg)
6282 self.db.share_server_update(
6283 context, dest_share_server['id'],
6284 dest_updates_on_error)
6285 continue
6286 if (src_share_server['status'] != 6286 ↛ 6288line 6286 didn't jump to line 6288 because the condition on line 6286 was never true
6287 constants.STATUS_SERVER_MIGRATING):
6288 msg = _('Destination share server %(dest_id)s refers to '
6289 'a source share server %(src_id)s that is not '
6290 ' being migrated.') % msg_args
6291 LOG.error(msg)
6292 self.db.share_server_update(
6293 context, dest_share_server['id'],
6294 dest_updates_on_error)
6295 continue
6297 share_instances = (
6298 self.db.share_instance_get_all_by_share_server(
6299 context, src_share_server_id, with_share_data=True))
6300 share_instance_ids = [x.id for x in share_instances]
6302 snapshot_instances = (
6303 self.db.share_snapshot_instance_get_all_with_filters(
6304 context,
6305 {'share_instance_ids': share_instance_ids}))
6306 snapshot_instance_ids = [x.id for x in snapshot_instances]
6308 try:
6309 finished = self.driver.share_server_migration_continue(
6310 context, src_share_server, dest_share_server,
6311 share_instances, snapshot_instances)
6313 if finished:
6314 self.db.share_server_update(
6315 context, src_share_server['id'],
6316 updates_on_finished)
6317 self.db.share_server_update(
6318 context, dest_share_server['id'],
6319 updates_on_finished)
6320 msg = _("Share server migration for share %s "
6321 "completed first phase successfully."
6322 ) % src_share_server['id']
6323 LOG.info(msg)
6324 else:
6325 src_share_server = self.db.share_server_get(
6326 context, src_share_server['id'])
6327 if (src_share_server['task_state'] == 6327 ↛ 6329line 6327 didn't jump to line 6329 because the condition on line 6327 was never true
6328 constants.TASK_STATE_MIGRATION_CANCELLED):
6329 msg = _("Share server migration for share %s was "
6330 "cancelled.") % src_share_server['id']
6331 LOG.warning(msg)
6332 except Exception:
6333 self._update_resource_status(
6334 context, constants.STATUS_AVAILABLE,
6335 share_instance_ids=share_instance_ids,
6336 snapshot_instance_ids=snapshot_instance_ids)
6337 self._reset_read_only_access_rules_for_server(
6338 context, share_instances, src_share_server,
6339 dest_host=dest_share_server['host'])
6340 self.db.share_server_update(
6341 context, dest_share_server['id'],
6342 dest_updates_on_error)
6343 if src_share_server: 6343 ↛ 6348line 6343 didn't jump to line 6348 because the condition on line 6343 was always true
6344 self.db.share_server_update(
6345 context, src_share_server['id'],
6346 src_updates_on_error)
6348 msg = _("Migration of share server %s has failed.")
6349 LOG.exception(msg, src_share_server['id'])
6351 @add_hooks
6352 @utils.require_driver_initialized
6353 def share_server_migration_complete(self, context, src_share_server_id,
6354 dest_share_server_id):
6355 """Invokes driver to complete the migration of share server."""
6356 dest_server = self.db.share_server_get(context, dest_share_server_id)
6357 src_server = self.db.share_server_get(context, src_share_server_id)
6359 share_instances = (
6360 self.db.share_instance_get_all_by_share_server(
6361 context, src_share_server_id, with_share_data=True))
6362 share_instance_ids = [x.id for x in share_instances]
6364 snapshot_instances = (
6365 self.db.share_snapshot_instance_get_all_with_filters(
6366 context,
6367 {'share_instance_ids': share_instance_ids}))
6368 snapshot_instance_ids = [x.id for x in snapshot_instances]
6370 updates_on_error = {
6371 'task_state': constants.TASK_STATE_MIGRATION_ERROR,
6372 'status': constants.STATUS_ERROR,
6373 }
6374 try:
6375 self._server_migration_complete_driver(context,
6376 src_server,
6377 share_instances,
6378 snapshot_instances,
6379 dest_server)
6380 except Exception:
6381 msg = _("Driver migration completion failed for"
6382 " share server %s.") % src_share_server_id
6383 LOG.exception(msg)
6384 self._update_resource_status(
6385 context, constants.STATUS_ERROR,
6386 share_instance_ids=share_instance_ids,
6387 snapshot_instance_ids=snapshot_instance_ids)
6388 self.db.share_server_update(
6389 context, src_share_server_id, updates_on_error)
6390 self.db.share_server_update(
6391 context, dest_share_server_id, updates_on_error)
6392 msg_args = {
6393 'source_id': src_share_server_id,
6394 'dest_id': dest_share_server_id
6395 }
6396 msg = _('Share server migration from %(source_id)s to %(dest_id)s '
6397 'has failed in migration-complete phase.') % msg_args
6398 raise exception.ShareServerMigrationFailed(reason=msg)
6400 server_update_args = {
6401 'task_state': constants.TASK_STATE_MIGRATION_SUCCESS,
6402 'status': constants.STATUS_ACTIVE
6403 }
6405 # Migration mechanism reused the share server
6406 if not dest_server['identifier']:
6407 server_update_args['identifier'] = src_server['identifier']
6409 # Update share server status for success scenario
6410 self.db.share_server_update(
6411 context, dest_share_server_id, server_update_args)
6412 self._update_resource_status(
6413 context, constants.STATUS_AVAILABLE,
6414 share_instance_ids=share_instance_ids,
6415 snapshot_instance_ids=snapshot_instance_ids)
6417 LOG.info("Share Server Migration for share server %s was completed "
6418 "with success.", src_share_server_id)
6420 def _server_migration_complete_driver(self, context, source_share_server,
6421 share_instances,
6422 snapshot_instances,
6423 dest_share_server):
6425 self.db.share_server_update(
6426 context, source_share_server['id'],
6427 {'task_state': constants.TASK_STATE_MIGRATION_COMPLETING})
6428 self.db.share_server_update(
6429 context, dest_share_server['id'],
6430 {'task_state': constants.TASK_STATE_MIGRATION_COMPLETING})
6432 # Retrieve network allocations reserved for the new share server
6433 dest_snss = dest_share_server['share_network_subnets']
6434 dest_sn_id = dest_snss[0]['share_network_id']
6435 dest_sn = self.db.share_network_get(context, dest_sn_id)
6436 dest_snss = self.db.share_network_subnet_get_all_by_share_server_id(
6437 context, dest_share_server['id'])
6439 existing_allocations = (
6440 self.db.network_allocations_get_for_share_server(
6441 context, dest_share_server['id']))
6442 migration_reused_network_allocations = len(existing_allocations) == 0
6443 migration_extended_network_allocations = (
6444 CONF.server_migration_extend_neutron_network)
6446 server_to_get_allocations = (
6447 dest_share_server
6448 if not migration_reused_network_allocations
6449 else source_share_server)
6451 new_network_allocations = self._form_server_setup_info(
6452 context, server_to_get_allocations, dest_sn, dest_snss)
6454 model_update = self.driver.share_server_migration_complete(
6455 context, source_share_server, dest_share_server, share_instances,
6456 snapshot_instances, new_network_allocations)
6458 alloc_update = {
6459 'share_server_id': dest_share_server['id']
6460 }
6461 subnet_update = {}
6463 if migration_extended_network_allocations: 6463 ↛ 6470line 6463 didn't jump to line 6470 because the condition on line 6463 was never true
6464 # NOTE: Network allocations are extended to the destination host on
6465 # previous (migration_start) step, i.e. port bindings are created
6466 # on destination host with existing ports. The network allocations
6467 # will be cut over on this (migration_complete) step, i.e. port
6468 # bindings on destination host will be activated and bindings on
6469 # source host will be deleted.
6470 updated_allocations = (
6471 self.driver.network_api.cutover_network_allocations(
6472 context, source_share_server))
6473 segmentation_id = self.db.share_server_backend_details_get_item(
6474 context, dest_share_server['id'], 'segmentation_id')
6475 alloc_update.update({
6476 'segmentation_id': segmentation_id
6477 })
6478 subnet_update.update({
6479 'segmentation_id': segmentation_id,
6480 })
6481 elif migration_reused_network_allocations:
6482 updated_allocations = (
6483 self.db.network_allocations_get_for_share_server(
6484 context, source_share_server["id"]))
6485 else:
6486 network_allocations = []
6487 for net_allocation in new_network_allocations:
6488 network_allocations += net_allocation['network_allocations']
6490 updated_allocations = [
6491 *network_allocations,
6492 *new_network_allocations[0]['admin_network_allocations']
6493 ]
6495 for allocation in updated_allocations:
6496 allocation_id = allocation['id']
6497 self.db.network_allocation_update(
6498 context, allocation_id, alloc_update)
6500 if subnet_update: 6500 ↛ 6501line 6500 didn't jump to line 6501 because the condition on line 6500 was never true
6501 for subnet in dest_snss:
6502 self.db.share_network_subnet_update(context, subnet['id'],
6503 subnet_update)
6505 # If share server doesn't have an identifier, we didn't ask the driver
6506 # to create a brand new server - this was a nondisruptive migration
6507 share_server_was_reused = not dest_share_server['identifier']
6508 if share_server_was_reused: 6508 ↛ 6523line 6508 didn't jump to line 6523 because the condition on line 6508 was always true
6509 driver_backend_details = model_update.get(
6510 'server_backend_details')
6511 # Clean up the previous backend details set for migration details
6512 if driver_backend_details: 6512 ↛ 6513line 6512 didn't jump to line 6513 because the condition on line 6512 was never true
6513 self.db.share_server_backend_details_delete(
6514 context, dest_share_server['id'])
6515 backend_details = (
6516 driver_backend_details
6517 or source_share_server.get("backend_details"))
6518 if backend_details: 6518 ↛ 6519line 6518 didn't jump to line 6519 because the condition on line 6518 was never true
6519 for k, v in backend_details.items():
6520 self.db.share_server_backend_details_set(
6521 context, dest_share_server['id'], {k: v})
6523 host_value = share_utils.extract_host(dest_share_server['host'])
6524 service = self.db.service_get_by_args(
6525 context, host_value, 'manila-share')
6526 new_az_id = service['availability_zone_id']
6528 share_updates = model_update.get('share_updates', {})
6529 for share_instance in share_instances:
6530 share_update = share_updates.get(share_instance['id'], {})
6531 new_share_host = share_utils.append_host(
6532 dest_share_server['host'], share_update.get('pool_name'))
6533 # Update share instance with new values
6534 instance_update = {
6535 'share_server_id': dest_share_server['id'],
6536 'host': new_share_host,
6537 'share_network_id': dest_sn_id,
6538 'availability_zone_id': new_az_id,
6539 }
6540 self.db.share_instance_update(
6541 context, share_instance['id'], instance_update)
6542 # Try to update info returned in the model update
6543 if not share_update: 6543 ↛ 6544line 6543 didn't jump to line 6544 because the condition on line 6543 was never true
6544 continue
6545 # Update export locations
6546 update_export_location = (
6547 share_updates[share_instance['id']].get('export_locations'))
6548 if update_export_location: 6548 ↛ 6529line 6548 didn't jump to line 6529 because the condition on line 6548 was always true
6549 self.db.export_locations_update(
6550 context, share_instance['id'], update_export_location)
6552 snapshot_updates = model_update.get('snapshot_updates', {})
6553 for snap_instance in snapshot_instances:
6554 model_update = snapshot_updates.get(snap_instance['id'], {})
6555 snapshot_export_locations = model_update.pop(
6556 'export_locations', [])
6557 if model_update: 6557 ↛ 6558line 6557 didn't jump to line 6558 because the condition on line 6557 was never true
6558 self.db.share_snapshot_instance_update(
6559 context, snap_instance['id'], model_update)
6561 if snapshot_export_locations: 6561 ↛ 6553line 6561 didn't jump to line 6553 because the condition on line 6561 was always true
6562 export_locations_update = []
6563 for exp_location in snapshot_export_locations:
6564 updated_el = {
6565 'path': exp_location['path'],
6566 'is_admin_only': exp_location['is_admin_only'],
6567 }
6568 export_locations_update.append(updated_el)
6569 self.db.share_snapshot_instance_export_locations_update(
6570 context, snap_instance['id'], export_locations_update)
6572 # Reset read only access since migration has finished
6573 self._reset_read_only_access_rules_for_server(
6574 context, share_instances, source_share_server,
6575 dest_host=source_share_server['host'])
6577 # NOTE(dviroel): Setting the source share server to INACTIVE to avoid
6578 # being reused for new shares, since it may have some invalid
6579 # configurations and most of the drivers don't check for compatible
6580 # share servers on share creation.
6581 self.db.share_server_update(
6582 context, source_share_server['id'],
6583 {'task_state': constants.TASK_STATE_MIGRATION_SUCCESS,
6584 'status': constants.STATUS_INACTIVE})
6586 if share_server_was_reused: 6586 ↛ 6590line 6586 didn't jump to line 6590 because the condition on line 6586 was always true
6587 self.driver.deallocate_network(context, source_share_server['id'])
6588 self.db.share_server_delete(context, source_share_server['id'])
6589 else:
6590 source_share_server = self._get_share_server_dict(
6591 context, source_share_server)
6592 rpcapi = share_rpcapi.ShareAPI()
6593 rpcapi.delete_share_server(context, source_share_server)
6595 @add_hooks
6596 @utils.require_driver_initialized
6597 def share_server_migration_cancel(self, context, src_share_server_id,
6598 dest_share_server_id):
6599 share_server = self.db.share_server_get(context, src_share_server_id)
6600 dest_share_server = self.db.share_server_get(context,
6601 dest_share_server_id)
6603 if share_server['task_state'] not in (
6604 constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE,
6605 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS):
6606 msg = _("Migration of share server %s cannot be cancelled at this "
6607 "moment.") % src_share_server_id
6608 raise exception.InvalidShareServer(reason=msg)
6610 share_instances = (
6611 self.db.share_instance_get_all_by_share_server(
6612 context, src_share_server_id, with_share_data=True))
6613 share_instance_ids = [x.id for x in share_instances]
6615 snapshot_instances = (
6616 self.db.share_snapshot_instance_get_all_with_filters(
6617 context,
6618 {'share_instance_ids': share_instance_ids}))
6619 snapshot_instance_ids = [x.id for x in snapshot_instances]
6621 # Avoid new migration continue and cancel calls while cancelling the
6622 # migration, which can take some time to finish. The cancel in progress
6623 # state will help administrator to identify if the operation is still
6624 # in progress.
6625 self.db.share_server_update(
6626 context, share_server['id'],
6627 {'task_state': constants.TASK_STATE_MIGRATION_CANCEL_IN_PROGRESS})
6629 self.driver.share_server_migration_cancel(
6630 context, share_server, dest_share_server,
6631 share_instances, snapshot_instances)
6633 if CONF.server_migration_extend_neutron_network: 6633 ↛ 6634line 6633 didn't jump to line 6634 because the condition on line 6633 was never true
6634 self.driver.network_api.delete_extended_allocations(
6635 context, share_server)
6637 # NOTE(dviroel): After cancelling the migration we should set the new
6638 # share server to INVALID since it may contain an invalid configuration
6639 # to be reused. We also cleanup the source_share_server_id to unblock
6640 # new migrations.
6641 self.db.share_server_update(
6642 context, dest_share_server_id,
6643 {'task_state': constants.TASK_STATE_MIGRATION_CANCELLED,
6644 'status': constants.STATUS_INACTIVE})
6646 self._check_delete_share_server(context,
6647 share_server=dest_share_server)
6649 self._update_resource_status(
6650 context, constants.STATUS_AVAILABLE,
6651 share_instance_ids=share_instance_ids,
6652 snapshot_instance_ids=snapshot_instance_ids)
6654 self._reset_read_only_access_rules_for_server(
6655 context, share_instances, share_server,
6656 dest_host=share_server['host'])
6658 self.db.share_server_update(
6659 context, share_server['id'],
6660 {'task_state': constants.TASK_STATE_MIGRATION_CANCELLED,
6661 'status': constants.STATUS_ACTIVE})
6663 LOG.info("Share Server Migration for share server %s was cancelled.",
6664 share_server['id'])
6666 @add_hooks
6667 @utils.require_driver_initialized
6668 def share_server_migration_get_progress(
6669 self, context, src_share_server_id, dest_share_server_id):
6671 src_share_server = self.db.share_server_get(context,
6672 src_share_server_id)
6673 if src_share_server['task_state'] != ( 6673 ↛ 6675line 6673 didn't jump to line 6675 because the condition on line 6673 was never true
6674 constants.TASK_STATE_MIGRATION_DRIVER_IN_PROGRESS):
6675 msg = _("Driver is not performing migration for"
6676 " share server %s at this moment.") % src_share_server_id
6677 raise exception.InvalidShareServer(reason=msg)
6679 dest_share_server = self.db.share_server_get(context,
6680 dest_share_server_id)
6681 share_instances = (
6682 self.db.share_instance_get_all_by_share_server(
6683 context, src_share_server_id, with_share_data=True))
6684 share_instance_ids = [x.id for x in share_instances]
6686 snapshot_instances = (
6687 self.db.share_snapshot_instance_get_all_with_filters(
6688 context,
6689 {'share_instance_ids': share_instance_ids}))
6691 return self.driver.share_server_migration_get_progress(
6692 context, src_share_server, dest_share_server, share_instances,
6693 snapshot_instances)
6695 @locked_share_network_operation
6696 def _check_share_network_update_finished(
6697 self, context, share_network_id=None):
6698 # Check if this share network is already active
6699 share_network = self.db.share_network_get(context, share_network_id)
6700 if share_network['status'] == constants.STATUS_NETWORK_ACTIVE:
6701 return
6703 share_servers = self.db.share_server_get_all_with_filters(
6704 context, {'share_network_id': share_network_id}
6705 )
6707 if all([ss['status'] != constants.STATUS_SERVER_NETWORK_CHANGE 6707 ↛ exitline 6707 didn't return from function '_check_share_network_update_finished' because the condition on line 6707 was always true
6708 for ss in share_servers]):
6709 # All share servers have updated their configuration
6710 self.db.share_network_update(
6711 context, share_network_id,
6712 {'status': constants.STATUS_NETWORK_ACTIVE})
6714 def _update_share_network_security_service(
6715 self, context, share_network_id, new_security_service_id,
6716 current_security_service_id=None, check_only=False):
6718 new_security_service = self.db.security_service_get(
6719 context, new_security_service_id)
6721 current_security_service = None
6722 if current_security_service_id: 6722 ↛ 6726line 6722 didn't jump to line 6726 because the condition on line 6722 was always true
6723 current_security_service = self.db.security_service_get(
6724 context, current_security_service_id)
6726 new_ss_type = new_security_service['type']
6727 backend_details_data = {
6728 'name': new_security_service['name'],
6729 'ou': new_security_service['ou'],
6730 'default_ad_site': new_security_service['default_ad_site'],
6731 'domain': new_security_service['domain'],
6732 'server': new_security_service['server'],
6733 'dns_ip': new_security_service['dns_ip'],
6734 'user': new_security_service['user'],
6735 'type': new_ss_type,
6736 'password': new_security_service['password'],
6737 }
6739 share_network = self.db.share_network_get(
6740 context, share_network_id)
6742 share_servers = self.db.share_server_get_all_by_host(
6743 context, self.host,
6744 filters={'share_network_id': share_network_id})
6746 for share_server in share_servers:
6748 # Get share_network_subnet in case it was updated.
6749 share_network_subnets = (
6750 self.db.share_network_subnet_get_all_by_share_server_id(
6751 context, share_server['id']))
6753 network_info = self._form_server_setup_info(
6754 context, share_server, share_network, share_network_subnets)
6756 share_instances = (
6757 self.db.share_instance_get_all_by_share_server(
6758 context, share_server['id'], with_share_data=True))
6759 share_instance_ids = [sn.id for sn in share_instances]
6761 share_instances_rules = []
6762 for share_instance_id in share_instance_ids:
6763 instance_rules = {
6764 'share_instance_id': share_instance_id,
6765 'access_rules': (
6766 self.db.share_access_get_all_for_instance(
6767 context, share_instance_id))
6768 }
6769 share_instances_rules.append(instance_rules)
6771 # Only check if the driver supports this kind of update.
6772 if check_only:
6773 if self.driver.check_update_share_server_security_service(
6774 context, share_server, network_info,
6775 share_instances, share_instances_rules,
6776 new_security_service,
6777 current_security_service=current_security_service):
6778 # Check the next share server.
6779 continue
6780 else:
6781 # At least one share server doesn't support this update
6782 return False
6784 # NOTE(dviroel): We always do backend details update since it
6785 # should be the expected configuration for this share server. Any
6786 # issue with this operation should be fixed by the admin which will
6787 # guarantee that storage and backend_details configurations match.
6788 self.db.share_server_backend_details_set(
6789 context, share_server['id'],
6790 {'security_service_' + new_ss_type: jsonutils.dumps(
6791 backend_details_data)})
6792 try:
6793 updates = self.driver.update_share_server_security_service(
6794 context, share_server, network_info,
6795 share_instances, share_instances_rules,
6796 new_security_service,
6797 current_security_service=current_security_service) or {}
6798 except Exception:
6799 operation = 'add'
6800 sec_serv_info = ('new security service %s'
6801 % new_security_service_id)
6802 if current_security_service_id: 6802 ↛ 6807line 6802 didn't jump to line 6807 because the condition on line 6802 was always true
6803 operation = 'update'
6804 sec_serv_info = ('current security service %s and '
6805 % current_security_service_id +
6806 sec_serv_info)
6807 msg = _("Share server %(server_id)s has failed on security "
6808 "service %(operation)s operation for "
6809 "%(sec_serv_ids)s.") % {
6810 'server_id': share_server['id'],
6811 'operation': operation,
6812 'sec_serv_ids': sec_serv_info,
6813 }
6814 LOG.exception(msg)
6815 # Set share server to error. Security service configuration
6816 # must be fixed before restoring it to active again.
6817 self.db.share_server_update(
6818 context, share_server['id'],
6819 {'status': constants.STATUS_ERROR})
6821 if current_security_service: 6821 ↛ 6835line 6821 didn't jump to line 6835 because the condition on line 6821 was always true
6822 # NOTE(dviroel): An already configured security service has
6823 # failed on update operation. We will set all share
6824 # instances to 'error'.
6825 if share_instance_ids: 6825 ↛ 6835line 6825 didn't jump to line 6835 because the condition on line 6825 was always true
6826 self.db.share_instance_status_update(
6827 context, share_instance_ids,
6828 {'status': constants.STATUS_ERROR})
6829 # Update share instance access rules status
6830 (self.access_helper
6831 .update_share_instances_access_rules_status(
6832 context, constants.SHARE_INSTANCE_RULES_ERROR,
6833 share_instance_ids))
6834 # Go to the next share server
6835 continue
6837 # Update access rules based on drivers updates
6838 for instance_id, rules_updates in updates.items():
6839 self.access_helper.process_driver_rule_updates(
6840 context, rules_updates, instance_id)
6842 msg = _("Security service was successfully updated on share "
6843 "server %s.") % share_server['id']
6844 LOG.info(msg)
6845 self.db.share_server_update(
6846 context, share_server['id'],
6847 {'status': constants.STATUS_ACTIVE})
6849 if check_only:
6850 # All share servers support the requested update
6851 return True
6853 # Check if all share servers have already finished their updates in
6854 # order to properly update share network status
6855 self._check_share_network_update_finished(
6856 context, share_network_id=share_network['id'])
6858 def update_share_network_security_service(
6859 self, context, share_network_id, new_security_service_id,
6860 current_security_service_id=None):
6861 self._update_share_network_security_service(
6862 context, share_network_id, new_security_service_id,
6863 current_security_service_id=current_security_service_id,
6864 check_only=False)
6866 def check_update_share_network_security_service(
6867 self, context, share_network_id, new_security_service_id,
6868 current_security_service_id=None):
6869 is_supported = self._update_share_network_security_service(
6870 context, share_network_id, new_security_service_id,
6871 current_security_service_id=current_security_service_id,
6872 check_only=True)
6873 self._update_share_network_security_service_operations(
6874 context, share_network_id, is_supported,
6875 new_security_service_id=new_security_service_id,
6876 current_security_service_id=current_security_service_id)
6878 @api.locked_security_service_update_operation
6879 def _update_share_network_security_service_operations(
6880 self, context, share_network_id, is_supported,
6881 new_security_service_id=None,
6882 current_security_service_id=None):
6883 update_check_key = self.share_api.get_security_service_update_key(
6884 'hosts_check', new_security_service_id,
6885 current_security_service_id)
6886 current_hosts_info = self.db.async_operation_data_get(
6887 context, share_network_id, update_check_key)
6888 if current_hosts_info: 6888 ↛ 6889line 6888 didn't jump to line 6889 because the condition on line 6888 was never true
6889 current_hosts = json.loads(current_hosts_info)
6890 current_hosts[self.host] = is_supported
6891 self.db.async_operation_data_update(
6892 context, share_network_id,
6893 {update_check_key: json.dumps(current_hosts)})
6894 else:
6895 LOG.debug('A share network security service check was requested '
6896 'but no entries were found in database. Ignoring call '
6897 'and returning.')
6899 @api.locked_share_server_update_allocations_operation
6900 def _update_share_server_allocations_check_operation(
6901 self, context, is_supported, share_network_id=None,
6902 availability_zone_id=None):
6903 update_key = self.share_api.get_share_server_update_allocations_key(
6904 share_network_id, availability_zone_id)
6905 current_hosts_info = self.db.async_operation_data_get(
6906 context, share_network_id, update_key)
6907 if current_hosts_info:
6908 current_hosts = json.loads(current_hosts_info)
6909 current_hosts[self.host] = is_supported
6910 self.db.async_operation_data_update(
6911 context, share_network_id,
6912 {update_key: json.dumps(current_hosts)})
6913 else:
6914 LOG.debug('A share network subnet create check was requested '
6915 'but no entries were found in database. Ignoring call '
6916 'and returning.')
6918 def _get_subnet_allocations(self, context, share_server_id,
6919 share_network_subnet):
6921 network_allocations = (
6922 self.db.network_allocations_get_for_share_server(
6923 context, share_server_id, label='user',
6924 subnet_id=share_network_subnet['id']))
6926 return {
6927 'share_network_subnet_id': share_network_subnet['id'],
6928 'neutron_net_id': share_network_subnet['neutron_net_id'],
6929 'neutron_subnet_id': share_network_subnet['neutron_subnet_id'],
6930 'network_allocations': network_allocations,
6931 }
6933 def _form_network_allocations(self, context, share_server_id,
6934 share_network_subnets):
6936 subnet_allocations = []
6937 for share_network_subnet in share_network_subnets:
6938 subnet_allocations.append(self._get_subnet_allocations(
6939 context, share_server_id, share_network_subnet))
6941 admin_network_allocations = (
6942 self.db.network_allocations_get_for_share_server(
6943 context, share_server_id, label='admin'))
6945 return {
6946 'admin_network_allocations': admin_network_allocations,
6947 'subnets': subnet_allocations,
6948 }
6950 def check_update_share_server_network_allocations(
6951 self, context, share_network_id, new_share_network_subnet):
6953 share_network = self.db.share_network_get(
6954 context, share_network_id)
6955 az_subnets = (
6956 self.db.share_network_subnets_get_all_by_availability_zone_id(
6957 context, share_network_id,
6958 new_share_network_subnet['availability_zone_id'],
6959 fallback_to_default=False)
6960 )
6961 self.driver.network_api.include_network_info(new_share_network_subnet)
6963 # all subnets have the same set of share servers, so do the check from
6964 # servers in the first subnet.
6965 share_servers = az_subnets[0]['share_servers'] if az_subnets else []
6966 is_supported = True
6967 for share_server in share_servers:
6969 current_network_allocations = self._form_network_allocations(
6970 context, share_server['id'], az_subnets)
6972 share_instances = (
6973 self.db.share_instance_get_all_by_share_server(
6974 context, share_server['id'], with_share_data=True))
6975 share_instance_ids = [sn.id for sn in share_instances]
6977 share_instances_rules = []
6978 for share_instance_id in share_instance_ids:
6979 instance_rules = {
6980 'share_instance_id': share_instance_id,
6981 'access_rules': (
6982 self.db.share_access_get_all_for_instance(
6983 context, share_instance_id))
6984 }
6985 share_instances_rules.append(instance_rules)
6987 if self.driver.check_update_share_server_network_allocations(
6988 context, share_server, current_network_allocations,
6989 new_share_network_subnet,
6990 share_network['security_services'],
6991 share_instances, share_instances_rules):
6992 # Check the next share server.
6993 continue
6994 else:
6995 # At least one share server doesn't support this update.
6996 is_supported = False
6997 break
6999 self._update_share_server_allocations_check_operation(
7000 context, is_supported, share_network_id=share_network_id,
7001 availability_zone_id=(
7002 new_share_network_subnet['availability_zone_id']))
7004 def _do_update_share_server_network_allocations(
7005 self, context, share_server, share_network, new_subnet,
7006 current_network_allocations, share_instances,
7007 snapshot_instance_ids):
7009 self.driver.allocate_network(
7010 context, share_server, share_network, new_subnet)
7011 new_network_allocations = self._get_subnet_allocations(
7012 context, share_server['id'], new_subnet)
7013 if not new_network_allocations['network_allocations']:
7014 raise exception.AllocationsNotFoundForShareServer(
7015 share_server_id=share_server['id'])
7017 # NOTE(felipe_rodrigues): all allocations have the same network
7018 # segmentation info, so validation from the first one.
7019 self._validate_segmentation_id(
7020 new_network_allocations['network_allocations'][0])
7022 model_update = self.driver.update_share_server_network_allocations(
7023 context, share_server, current_network_allocations,
7024 new_network_allocations, share_network['security_services'],
7025 share_instances, snapshot_instance_ids)
7027 self.driver.update_network_allocation(context, share_server)
7029 driver_backend_details = model_update.get('server_details')
7030 if driver_backend_details: 7030 ↛ 7034line 7030 didn't jump to line 7034 because the condition on line 7030 was always true
7031 self.db.share_server_backend_details_set(
7032 context, share_server['id'], driver_backend_details)
7034 share_updates = model_update.get('share_updates', {})
7035 for share_instance_id, export_locations in share_updates.items():
7036 self.db.export_locations_update(
7037 context, share_instance_id, export_locations)
7039 snapshot_updates = model_update.get('snapshot_updates', {})
7040 for snap_instance_id, model_update in snapshot_updates.items():
7041 snapshot_export_locations = model_update.pop(
7042 'export_locations', [])
7043 if model_update: 7043 ↛ 7047line 7043 didn't jump to line 7047 because the condition on line 7043 was always true
7044 self.db.share_snapshot_instance_update(
7045 context, snap_instance_id, model_update)
7047 if snapshot_export_locations: 7047 ↛ 7040line 7047 didn't jump to line 7040 because the condition on line 7047 was always true
7048 export_locations_update = []
7049 for exp_location in snapshot_export_locations:
7050 updated_el = {
7051 'path': exp_location['path'],
7052 'is_admin_only': exp_location['is_admin_only'],
7053 }
7054 export_locations_update.append(updated_el)
7055 self.db.share_snapshot_instance_export_locations_update(
7056 context, snap_instance_id, export_locations_update)
7058 def update_share_server_network_allocations(
7059 self, context, share_network_id, new_share_network_subnet_id):
7061 share_network = self.db.share_network_get(
7062 context, share_network_id)
7063 new_subnet = self.db.share_network_subnet_get(
7064 context, new_share_network_subnet_id)
7065 current_subnets = (
7066 self.db.share_network_subnets_get_all_by_availability_zone_id(
7067 context, share_network_id,
7068 new_subnet['availability_zone_id'],
7069 fallback_to_default=False)
7070 )
7071 current_subnets = [subnet for subnet in current_subnets
7072 if subnet['id'] != new_share_network_subnet_id]
7073 share_servers = (
7074 self.db.share_server_get_all_by_host_and_or_share_subnet(
7075 context, host=self.host,
7076 share_subnet_id=new_share_network_subnet_id))
7077 for share_server in share_servers:
7079 share_server_id = share_server['id']
7080 current_network_allocations = self._form_network_allocations(
7081 context, share_server_id, current_subnets)
7082 share_instances = (
7083 self.db.share_instance_get_all_by_share_server(
7084 context, share_server_id, with_share_data=True))
7085 share_instance_ids = [x['id'] for x in share_instances]
7086 snapshot_instances = (
7087 self.db.share_snapshot_instance_get_all_with_filters(
7088 context,
7089 {'share_instance_ids': share_instance_ids}))
7090 snapshot_instance_ids = [x['id'] for x in snapshot_instances]
7092 try:
7093 self._do_update_share_server_network_allocations(
7094 context, share_server, share_network, new_subnet,
7095 current_network_allocations, share_instances,
7096 snapshot_instances)
7097 except Exception as e:
7098 msg = ('Failed to update allocations of share server '
7099 '%(server_id)s on subnet %(subnet_id)s: %(e)s.')
7100 data = {
7101 'server_id': share_server_id,
7102 'subnet_id': new_share_network_subnet_id,
7103 'e': str(e),
7104 }
7105 LOG.exception(msg, data)
7107 # Set resources to error. Allocations configuration must be
7108 # fixed before restoring it to active again.
7109 self._handle_setup_server_error(context, share_server_id, e)
7110 self._update_resource_status(
7111 context, constants.STATUS_ERROR,
7112 share_instance_ids=share_instance_ids,
7113 snapshot_instance_ids=snapshot_instance_ids)
7115 continue
7117 msg = _(
7118 "Network allocations was successfully updated on share "
7119 "server %s.") % share_server['id']
7120 LOG.info(msg)
7121 self.db.share_server_update(
7122 context, share_server['id'],
7123 {'status': constants.STATUS_ACTIVE})
7125 # Check if all share servers have already finished their updates in
7126 # order to properly update share network status.
7127 self._check_share_network_update_finished(
7128 context, share_network_id=share_network['id'])
7130 def update_share_from_metadata(self, context, share_id, metadata):
7131 share = self.db.share_get(context, share_id)
7132 share_instance = self._get_share_instance(context, share)
7133 share_server = self._get_share_server(context, share_instance)
7134 try:
7135 self.driver.update_share_from_metadata(context, share_instance,
7136 metadata, share_server)
7137 self.message_api.create(
7138 context,
7139 message_field.Action.UPDATE_METADATA,
7140 share['project_id'],
7141 resource_type=message_field.Resource.SHARE,
7142 resource_id=share_id,
7143 detail=message_field.Detail.UPDATE_METADATA_SUCCESS)
7144 except Exception:
7145 self.message_api.create(
7146 context,
7147 message_field.Action.UPDATE_METADATA,
7148 share['project_id'],
7149 resource_type=message_field.Resource.SHARE,
7150 resource_id=share_id,
7151 detail=message_field.Detail.UPDATE_METADATA_FAILURE)
7153 def update_share_network_subnet_from_metadata(self, context,
7154 share_network_id,
7155 share_network_subnet_id,
7156 share_server_id,
7157 metadata):
7158 share_network = self.db.share_network_get(context, share_network_id)
7159 share_network_subnet = self.db.share_network_subnet_get(
7160 context, share_network_subnet_id)
7161 share_server = self.db.share_server_get(context, share_server_id)
7163 try:
7164 self.driver.update_share_network_subnet_from_metadata(
7165 context,
7166 share_network,
7167 share_network_subnet,
7168 share_server,
7169 metadata)
7170 self.message_api.create(
7171 context,
7172 message_field.Action.UPDATE_METADATA,
7173 share_network['project_id'],
7174 resource_type=message_field.Resource.SHARE_NETWORK_SUBNET,
7175 resource_id=share_network_subnet_id,
7176 detail=message_field.Detail.UPDATE_METADATA_SUCCESS)
7177 except Exception as e:
7178 if isinstance(e, NotImplementedError):
7179 LOG.debug("Not passing the updates of share network subnet "
7180 "metadata to share driver since the required driver "
7181 "interface is not implemented.")
7182 self.message_api.create(
7183 context,
7184 message_field.Action.UPDATE_METADATA,
7185 share_network['project_id'],
7186 resource_type=message_field.Resource.SHARE_NETWORK_SUBNET,
7187 resource_id=share_network_subnet_id,
7188 detail=message_field.Detail.UPDATE_METADATA_FAILURE)