ed02273d2f
This patch adds the option to split retrieved volumes and snapshots by chunks during Volume Manager host initialization. Query results will be obtained in batches from the database and not in one shot to avoid extreme memory usage. Max number of volumes and snapshots per batch is controlled by option init_host_max_objects_retrieval. Option is disabled by default for backward compatibility. To migrate any ConfKeyManager keys based on fixed_key to the currently configured key manager Volume Manager will use special lightweight object - VolumeMigration. Change-Id: I53eccc77fdc2c35b27ed430af62dc18e7d1bde69 Closes-Bug: 1681374
368 lines
15 KiB
Python
368 lines
15 KiB
Python
# Copyright 2015 SimpliVity Corp.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from oslo_config import cfg
|
|
from oslo_utils import versionutils
|
|
from oslo_versionedobjects import fields
|
|
|
|
from cinder import db
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder import objects
|
|
from cinder.objects import base
|
|
from cinder.objects import cleanable
|
|
from cinder.objects import fields as c_fields
|
|
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
@base.CinderObjectRegistry.register
|
|
class Snapshot(cleanable.CinderCleanableObject, base.CinderObject,
|
|
base.CinderObjectDictCompat, base.CinderComparableObject,
|
|
base.ClusteredObject):
|
|
# Version 1.0: Initial version
|
|
# Version 1.1: Changed 'status' field to use SnapshotStatusField
|
|
# Version 1.2: This object is now cleanable (adds rows to workers table)
|
|
# Version 1.3: SnapshotStatusField now includes "unmanaging"
|
|
# Version 1.4: SnapshotStatusField now includes "backing-up"
|
|
# Version 1.5: SnapshotStatusField now includes "restoring"
|
|
VERSION = '1.5'
|
|
|
|
# NOTE(thangp): OPTIONAL_FIELDS are fields that would be lazy-loaded. They
|
|
# are typically the relationship in the sqlalchemy object.
|
|
OPTIONAL_FIELDS = ('volume', 'metadata', 'cgsnapshot', 'group_snapshot')
|
|
|
|
fields = {
|
|
'id': fields.UUIDField(),
|
|
|
|
'user_id': fields.StringField(nullable=True),
|
|
'project_id': fields.StringField(nullable=True),
|
|
|
|
'volume_id': fields.UUIDField(nullable=True),
|
|
'cgsnapshot_id': fields.UUIDField(nullable=True),
|
|
'group_snapshot_id': fields.UUIDField(nullable=True),
|
|
'status': c_fields.SnapshotStatusField(nullable=True),
|
|
'progress': fields.StringField(nullable=True),
|
|
'volume_size': fields.IntegerField(nullable=True),
|
|
|
|
'display_name': fields.StringField(nullable=True),
|
|
'display_description': fields.StringField(nullable=True),
|
|
|
|
'encryption_key_id': fields.UUIDField(nullable=True),
|
|
'volume_type_id': fields.UUIDField(nullable=True),
|
|
|
|
'provider_location': fields.StringField(nullable=True),
|
|
'provider_id': fields.StringField(nullable=True),
|
|
'metadata': fields.DictOfStringsField(),
|
|
'provider_auth': fields.StringField(nullable=True),
|
|
|
|
'volume': fields.ObjectField('Volume', nullable=True),
|
|
'cgsnapshot': fields.ObjectField('CGSnapshot', nullable=True),
|
|
'group_snapshot': fields.ObjectField('GroupSnapshot', nullable=True),
|
|
}
|
|
|
|
@property
|
|
def cluster_name(self):
|
|
return self.volume.cluster_name
|
|
|
|
@classmethod
|
|
def _get_expected_attrs(cls, context, *args, **kwargs):
|
|
return 'metadata',
|
|
|
|
# NOTE(thangp): obj_extra_fields is used to hold properties that are not
|
|
# usually part of the model
|
|
obj_extra_fields = ['name', 'volume_name']
|
|
|
|
@property
|
|
def name(self):
|
|
return CONF.snapshot_name_template % self.id
|
|
|
|
@property
|
|
def volume_name(self):
|
|
return self.volume.name
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(Snapshot, self).__init__(*args, **kwargs)
|
|
self._orig_metadata = {}
|
|
|
|
self._reset_metadata_tracking()
|
|
|
|
def obj_reset_changes(self, fields=None):
|
|
super(Snapshot, self).obj_reset_changes(fields)
|
|
self._reset_metadata_tracking(fields=fields)
|
|
|
|
def _reset_metadata_tracking(self, fields=None):
|
|
if fields is None or 'metadata' in fields:
|
|
self._orig_metadata = (dict(self.metadata)
|
|
if self.obj_attr_is_set('metadata') else {})
|
|
|
|
def obj_what_changed(self):
|
|
changes = super(Snapshot, self).obj_what_changed()
|
|
if hasattr(self, 'metadata') and self.metadata != self._orig_metadata:
|
|
changes.add('metadata')
|
|
|
|
return changes
|
|
|
|
def obj_make_compatible(self, primitive, target_version):
|
|
"""Make an object representation compatible with a target version."""
|
|
super(Snapshot, self).obj_make_compatible(primitive, target_version)
|
|
target_version = versionutils.convert_version_to_tuple(target_version)
|
|
|
|
backport_statuses = (((1, 3),
|
|
(c_fields.SnapshotStatus.UNMANAGING,
|
|
c_fields.SnapshotStatus.DELETING)),
|
|
((1, 4),
|
|
(c_fields.SnapshotStatus.BACKING_UP,
|
|
c_fields.SnapshotStatus.AVAILABLE)),
|
|
((1, 5),
|
|
(c_fields.SnapshotStatus.RESTORING,
|
|
c_fields.SnapshotStatus.AVAILABLE)))
|
|
for version, status in backport_statuses:
|
|
if target_version < version:
|
|
if primitive.get('status') == status[0]:
|
|
primitive['status'] = status[1]
|
|
|
|
@classmethod
|
|
def _from_db_object(cls, context, snapshot, db_snapshot,
|
|
expected_attrs=None):
|
|
if expected_attrs is None:
|
|
expected_attrs = []
|
|
for name, field in snapshot.fields.items():
|
|
if name in cls.OPTIONAL_FIELDS:
|
|
continue
|
|
value = db_snapshot.get(name)
|
|
if isinstance(field, fields.IntegerField):
|
|
value = value if value is not None else 0
|
|
setattr(snapshot, name, value)
|
|
|
|
if 'volume' in expected_attrs:
|
|
volume = objects.Volume(context)
|
|
volume._from_db_object(context, volume, db_snapshot['volume'])
|
|
snapshot.volume = volume
|
|
if snapshot.cgsnapshot_id and 'cgsnapshot' in expected_attrs:
|
|
cgsnapshot = objects.CGSnapshot(context)
|
|
cgsnapshot._from_db_object(context, cgsnapshot,
|
|
db_snapshot['cgsnapshot'])
|
|
snapshot.cgsnapshot = cgsnapshot
|
|
if snapshot.group_snapshot_id and 'group_snapshot' in expected_attrs:
|
|
group_snapshot = objects.GroupSnapshot(context)
|
|
group_snapshot._from_db_object(context, group_snapshot,
|
|
db_snapshot['group_snapshot'])
|
|
snapshot.group_snapshot = group_snapshot
|
|
|
|
if 'metadata' in expected_attrs:
|
|
metadata = db_snapshot.get('snapshot_metadata')
|
|
if metadata is None:
|
|
raise exception.MetadataAbsent()
|
|
snapshot.metadata = {item['key']: item['value']
|
|
for item in metadata}
|
|
snapshot._context = context
|
|
snapshot.obj_reset_changes()
|
|
return snapshot
|
|
|
|
def create(self):
|
|
if self.obj_attr_is_set('id'):
|
|
raise exception.ObjectActionError(action='create',
|
|
reason=_('already created'))
|
|
updates = self.cinder_obj_get_changes()
|
|
|
|
if 'volume' in updates:
|
|
raise exception.ObjectActionError(action='create',
|
|
reason=_('volume assigned'))
|
|
if 'cgsnapshot' in updates:
|
|
raise exception.ObjectActionError(action='create',
|
|
reason=_('cgsnapshot assigned'))
|
|
if 'cluster' in updates:
|
|
raise exception.ObjectActionError(
|
|
action='create', reason=_('cluster assigned'))
|
|
if 'group_snapshot' in updates:
|
|
raise exception.ObjectActionError(
|
|
action='create',
|
|
reason=_('group_snapshot assigned'))
|
|
|
|
db_snapshot = db.snapshot_create(self._context, updates)
|
|
self._from_db_object(self._context, self, db_snapshot)
|
|
|
|
def save(self):
|
|
updates = self.cinder_obj_get_changes()
|
|
if updates:
|
|
if 'volume' in updates:
|
|
raise exception.ObjectActionError(action='save',
|
|
reason=_('volume changed'))
|
|
if 'cgsnapshot' in updates:
|
|
# NOTE(xyang): Allow this to pass if 'cgsnapshot' is
|
|
# set to None. This is to support backward compatibility.
|
|
if updates.get('cgsnapshot'):
|
|
raise exception.ObjectActionError(
|
|
action='save', reason=_('cgsnapshot changed'))
|
|
if 'group_snapshot' in updates:
|
|
raise exception.ObjectActionError(
|
|
action='save', reason=_('group_snapshot changed'))
|
|
|
|
if 'cluster' in updates:
|
|
raise exception.ObjectActionError(
|
|
action='save', reason=_('cluster changed'))
|
|
|
|
if 'metadata' in updates:
|
|
# Metadata items that are not specified in the
|
|
# self.metadata will be deleted
|
|
metadata = updates.pop('metadata', None)
|
|
self.metadata = db.snapshot_metadata_update(self._context,
|
|
self.id, metadata,
|
|
True)
|
|
|
|
db.snapshot_update(self._context, self.id, updates)
|
|
|
|
self.obj_reset_changes()
|
|
|
|
def destroy(self):
|
|
with self.obj_as_admin():
|
|
updated_values = db.snapshot_destroy(self._context, self.id)
|
|
self.update(updated_values)
|
|
self.obj_reset_changes(updated_values.keys())
|
|
|
|
def obj_load_attr(self, attrname):
|
|
if attrname not in self.OPTIONAL_FIELDS:
|
|
raise exception.ObjectActionError(
|
|
action='obj_load_attr',
|
|
reason=_('attribute %s not lazy-loadable') % attrname)
|
|
if not self._context:
|
|
raise exception.OrphanedObjectError(method='obj_load_attr',
|
|
objtype=self.obj_name())
|
|
|
|
if attrname == 'volume':
|
|
self.volume = objects.Volume.get_by_id(self._context,
|
|
self.volume_id)
|
|
|
|
if attrname == 'cgsnapshot':
|
|
if self.cgsnapshot_id is None:
|
|
self.cgsnapshot = None
|
|
else:
|
|
self.cgsnapshot = objects.CGSnapshot.get_by_id(
|
|
self._context, self.cgsnapshot_id)
|
|
if attrname == 'group_snapshot':
|
|
if self.group_snapshot_id is None:
|
|
self.group_snapshot = None
|
|
else:
|
|
self.group_snapshot = objects.GroupSnapshot.get_by_id(
|
|
self._context,
|
|
self.group_snapshot_id)
|
|
|
|
self.obj_reset_changes(fields=[attrname])
|
|
|
|
def delete_metadata_key(self, context, key):
|
|
db.snapshot_metadata_delete(context, self.id, key)
|
|
md_was_changed = 'metadata' in self.obj_what_changed()
|
|
|
|
del self.metadata[key]
|
|
self._orig_metadata.pop(key, None)
|
|
|
|
if not md_was_changed:
|
|
self.obj_reset_changes(['metadata'])
|
|
|
|
@classmethod
|
|
def snapshot_data_get_for_project(cls, context, project_id,
|
|
volume_type_id=None, host=None):
|
|
return db.snapshot_data_get_for_project(context, project_id,
|
|
volume_type_id,
|
|
host=host)
|
|
|
|
@staticmethod
|
|
def _is_cleanable(status, obj_version):
|
|
# Before 1.2 we didn't have workers table, so cleanup wasn't supported.
|
|
if obj_version and obj_version < 1.2:
|
|
return False
|
|
return status == 'creating'
|
|
|
|
@property
|
|
def host(self):
|
|
"""All cleanable VO must have a host property/attribute."""
|
|
return self.volume.host
|
|
|
|
|
|
@base.CinderObjectRegistry.register
|
|
class SnapshotList(base.ObjectListBase, base.CinderObject):
|
|
VERSION = '1.0'
|
|
|
|
fields = {
|
|
'objects': fields.ListOfObjectsField('Snapshot'),
|
|
}
|
|
|
|
@classmethod
|
|
def get_all(cls, context, filters, marker=None, limit=None,
|
|
sort_keys=None, sort_dirs=None, offset=None):
|
|
"""Get all snapshot given some search_opts (filters).
|
|
|
|
Special filters accepted are host and cluster_name, that refer to the
|
|
volume's fields.
|
|
"""
|
|
snapshots = db.snapshot_get_all(context, filters, marker, limit,
|
|
sort_keys, sort_dirs, offset)
|
|
expected_attrs = Snapshot._get_expected_attrs(context)
|
|
return base.obj_make_list(context, cls(context), objects.Snapshot,
|
|
snapshots, expected_attrs=expected_attrs)
|
|
|
|
@classmethod
|
|
def get_by_host(cls, context, host, filters=None):
|
|
snapshots = db.snapshot_get_all_by_host(context, host, filters)
|
|
expected_attrs = Snapshot._get_expected_attrs(context)
|
|
return base.obj_make_list(context, cls(context), objects.Snapshot,
|
|
snapshots, expected_attrs=expected_attrs)
|
|
|
|
@classmethod
|
|
def get_all_by_project(cls, context, project_id, search_opts, marker=None,
|
|
limit=None, sort_keys=None, sort_dirs=None,
|
|
offset=None):
|
|
snapshots = db.snapshot_get_all_by_project(
|
|
context, project_id, search_opts, marker, limit, sort_keys,
|
|
sort_dirs, offset)
|
|
expected_attrs = Snapshot._get_expected_attrs(context)
|
|
return base.obj_make_list(context, cls(context), objects.Snapshot,
|
|
snapshots, expected_attrs=expected_attrs)
|
|
|
|
@classmethod
|
|
def get_all_for_volume(cls, context, volume_id):
|
|
snapshots = db.snapshot_get_all_for_volume(context, volume_id)
|
|
expected_attrs = Snapshot._get_expected_attrs(context)
|
|
return base.obj_make_list(context, cls(context), objects.Snapshot,
|
|
snapshots, expected_attrs=expected_attrs)
|
|
|
|
@classmethod
|
|
def get_all_active_by_window(cls, context, begin, end):
|
|
snapshots = db.snapshot_get_all_active_by_window(context, begin, end)
|
|
expected_attrs = Snapshot._get_expected_attrs(context)
|
|
return base.obj_make_list(context, cls(context), objects.Snapshot,
|
|
snapshots, expected_attrs=expected_attrs)
|
|
|
|
@classmethod
|
|
def get_all_for_cgsnapshot(cls, context, cgsnapshot_id):
|
|
snapshots = db.snapshot_get_all_for_cgsnapshot(context, cgsnapshot_id)
|
|
expected_attrs = Snapshot._get_expected_attrs(context)
|
|
return base.obj_make_list(context, cls(context), objects.Snapshot,
|
|
snapshots, expected_attrs=expected_attrs)
|
|
|
|
@classmethod
|
|
def get_all_for_group_snapshot(cls, context, group_snapshot_id):
|
|
snapshots = db.snapshot_get_all_for_group_snapshot(
|
|
context, group_snapshot_id)
|
|
expected_attrs = Snapshot._get_expected_attrs(context)
|
|
return base.obj_make_list(context, cls(context), objects.Snapshot,
|
|
snapshots, expected_attrs=expected_attrs)
|
|
|
|
@classmethod
|
|
def get_snapshot_summary(cls, context, project_only, filters=None):
|
|
summary = db.get_snapshot_summary(context, project_only, filters)
|
|
return summary
|