Source code for cinderlib.objects

# Copyright (c) 2018, Red Hat, Inc.
# All Rights Reserved.
#
#    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.
import json as json_lib
import sys
import uuid

from cinder import context
from cinder import exception as cinder_exception
from cinder import objects as cinder_objs
from cinder.objects import base as cinder_base_ovo
from os_brick import exception as brick_exception
from os_brick import initiator as brick_initiator
from os_brick.initiator import connector as brick_connector
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import timeutils
import six

from cinderlib import exception
from cinderlib import utils


LOG = logging.getLogger(__name__)
DEFAULT_PROJECT_ID = 'cinderlib'
DEFAULT_USER_ID = 'cinderlib'
BACKEND_NAME_SNAPSHOT_FIELD = 'progress'
CONNECTIONS_OVO_FIELD = 'volume_attachment'
GB = 1024 ** 3

# This cannot go in the setup method because cinderlib objects need them to
# be setup to set OVO_CLASS
cinder_objs.register_all()


[docs]class KeyValue(object): def __init__(self, key=None, value=None): self.key = key self.value = value def __eq__(self, other): return (self.key, self.value) == (other.key, other.value)
[docs]class Object(object): """Base class for our resource representation objects.""" SIMPLE_JSON_IGNORE = tuple() DEFAULT_FIELDS_VALUES = {} LAZY_PROPERTIES = tuple() backend_class = None CONTEXT = context.RequestContext(user_id=DEFAULT_USER_ID, project_id=DEFAULT_PROJECT_ID, is_admin=True, overwrite=False) def _get_backend(self, backend_name_or_obj): if isinstance(backend_name_or_obj, six.string_types): try: return self.backend_class.backends[backend_name_or_obj] except KeyError: if self.backend_class.fail_on_missing_backend: raise return backend_name_or_obj def __init__(self, backend, **fields_data): self.backend = self._get_backend(backend) __ovo = fields_data.get('__ovo') if __ovo: self._ovo = __ovo else: self._ovo = self._create_ovo(**fields_data) # Store a reference to the cinderlib obj in the OVO for serialization self._ovo._cl_obj_ = self
[docs] @classmethod def setup(cls, persistence_driver, backend_class, project_id, user_id, non_uuid_ids): cls.persistence = persistence_driver cls.backend_class = backend_class # Set the global context if we aren't using the default project_id = project_id or DEFAULT_PROJECT_ID user_id = user_id or DEFAULT_USER_ID if (project_id != cls.CONTEXT.project_id or user_id != cls.CONTEXT.user_id): cls.CONTEXT.user_id = user_id cls.CONTEXT.project_id = project_id Volume.DEFAULT_FIELDS_VALUES['user_id'] = user_id Volume.DEFAULT_FIELDS_VALUES['project_id'] = project_id # Configure OVOs to support non_uuid_ids if non_uuid_ids: for ovo_name in cinder_base_ovo.CinderObjectRegistry.obj_classes(): ovo_cls = getattr(cinder_objs, ovo_name) if 'id' in ovo_cls.fields: ovo_cls.fields['id'] = cinder_base_ovo.fields.StringField()
def _to_primitive(self): """Return custom cinderlib data for serialization.""" return None def _create_ovo(self, **fields_data): # The base are the default values we define on our own classes fields_values = self.DEFAULT_FIELDS_VALUES.copy() # Apply the values defined by the caller fields_values.update(fields_data) # We support manually setting the id, so set only if not already set # or if set to None if not fields_values.get('id'): fields_values['id'] = self.new_uuid() # Set non set field values based on OVO's default value and on whether # it is nullable or not. for field_name, field in self.OVO_CLASS.fields.items(): if field.default != cinder_base_ovo.fields.UnspecifiedDefault: fields_values.setdefault(field_name, field.default) elif field.nullable: fields_values.setdefault(field_name, None) if ('created_at' in self.OVO_CLASS.fields and not fields_values.get('created_at')): fields_values['created_at'] = timeutils.utcnow() return self.OVO_CLASS(context=self.CONTEXT, **fields_values) @property def json(self): return self.to_json(simplified=False)
[docs] def to_json(self, simplified=True): visited = set() if simplified: for field in self.SIMPLE_JSON_IGNORE: if self._ovo.obj_attr_is_set(field): visited.add(id(getattr(self._ovo, field))) ovo = self._ovo.obj_to_primitive(visited=visited) return {'class': type(self).__name__, # If no driver loaded, just return the name of the backend 'backend': getattr(self.backend, 'config', {'volume_backend_name': self.backend}), 'ovo': ovo}
@property def jsons(self): return self.to_jsons(simplified=False)
[docs] def to_jsons(self, simplified=True): json_data = self.to_json(simplified) return json_lib.dumps(json_data, separators=(',', ':'))
def _only_ovo_data(self, ovo): if isinstance(ovo, dict): if 'versioned_object.data' in ovo: value = ovo['versioned_object.data'] if ['objects'] == value.keys(): return self._only_ovo_data(value['objects']) key = ovo['versioned_object.name'].lower() return {key: self._only_ovo_data(value)} for key in ovo.keys(): ovo[key] = self._only_ovo_data(ovo[key]) if isinstance(ovo, list) and ovo: return [self._only_ovo_data(e) for e in ovo] return ovo
[docs] def to_dict(self): json_ovo = self.json return self._only_ovo_data(json_ovo['ovo'])
@property def dump(self): # Make sure we load lazy loading properties for lazy_property in self.LAZY_PROPERTIES: getattr(self, lazy_property) return self.json @property def dumps(self): return json_lib.dumps(self.dump, separators=(',', ':')) def __repr__(self): backend = self.backend if isinstance(self.backend, self.backend_class): backend = backend.id return ('<cinderlib.%s object %s on backend %s>' % (type(self).__name__, self.id, backend))
[docs] @classmethod def load(cls, json_src, save=False): backend = cls.backend_class.load_backend(json_src['backend']) ovo = cinder_base_ovo.CinderObject.obj_from_primitive(json_src['ovo'], cls.CONTEXT) return cls._load(backend, ovo, save=save)
[docs] @staticmethod def new_uuid(): return str(uuid.uuid4())
def __getattr__(self, name): if name == '_ovo': raise AttributeError('Attribute _ovo is not yet set') return getattr(self._ovo, name) def _raise_with_resource(self): exc_info = sys.exc_info() exc_info[1].resource = self six.reraise(*exc_info)
[docs]class NamedObject(Object): def __init__(self, backend, **fields_data): if 'description' in fields_data: fields_data['display_description'] = fields_data.pop('description') if 'name' in fields_data: fields_data['display_name'] = fields_data.pop('name') super(NamedObject, self).__init__(backend, **fields_data) @property def name(self): return self._ovo.display_name @property def description(self): return self._ovo.display_description @property def name_in_storage(self): return self._ovo.name
[docs]class LazyVolumeAttr(object): LAZY_PROPERTIES = ('volume',) _volume = None def __init__(self, volume): if volume: self._volume = volume # Ensure circular reference is set self._ovo.volume = volume._ovo self._ovo.volume_id = volume._ovo.id elif self._ovo.obj_attr_is_set('volume'): self._volume = Volume._load(self.backend, self._ovo.volume) @property def volume(self): # Lazy loading if self._volume is None: self._volume = Volume.get_by_id(self.volume_id) self._ovo.volume = self._volume._ovo return self._volume @volume.setter def volume(self, value): self._volume = value self._ovo.volume = value._ovo
[docs] def refresh(self): last_self = self.get_by_id(self.id) if self._volume is not None: last_self.volume vars(self).clear() vars(self).update(vars(last_self))
[docs]class Volume(NamedObject): OVO_CLASS = cinder_objs.Volume SIMPLE_JSON_IGNORE = ('snapshots', 'volume_attachment') DEFAULT_FIELDS_VALUES = { 'size': 1, 'user_id': Object.CONTEXT.user_id, 'project_id': Object.CONTEXT.project_id, 'status': 'creating', 'attach_status': 'detached', 'metadata': {}, 'admin_metadata': {}, 'glance_metadata': {}, } LAZY_PROPERTIES = ('snapshots', 'connections') _ignore_keys = ('id', CONNECTIONS_OVO_FIELD, 'snapshots', 'volume_type') def __init__(self, backend_or_vol, pool_name=None, **kwargs): # Accept backend name for convenience if isinstance(backend_or_vol, six.string_types): backend_name = backend_or_vol backend_or_vol = self._get_backend(backend_or_vol) elif isinstance(backend_or_vol, self.backend_class): backend_name = backend_or_vol.id elif isinstance(backend_or_vol, Volume): backend_str, pool = backend_or_vol._ovo.host.split('#') backend_name = backend_str.split('@')[-1] pool_name = pool_name or pool for key in backend_or_vol._ovo.fields: if (backend_or_vol._ovo.obj_attr_is_set(key) and key not in self._ignore_keys): kwargs.setdefault(key, getattr(backend_or_vol._ovo, key)) if backend_or_vol.volume_type: kwargs.setdefault('extra_specs', backend_or_vol.volume_type.extra_specs) if backend_or_vol.volume_type.qos_specs: kwargs.setdefault( 'qos_specs', backend_or_vol.volume_type.qos_specs.specs) backend_or_vol = backend_or_vol.backend if '__ovo' not in kwargs: kwargs[CONNECTIONS_OVO_FIELD] = ( cinder_objs.VolumeAttachmentList(context=self.CONTEXT)) kwargs['snapshots'] = ( cinder_objs.SnapshotList(context=self.CONTEXT)) self._snapshots = [] self._connections = [] qos_specs = kwargs.pop('qos_specs', None) extra_specs = kwargs.pop('extra_specs', {}) super(Volume, self).__init__(backend_or_vol, **kwargs) self._populate_data() self.local_attach = None # If we overwrote the host, then we ignore pool_name and don't set a # default value or copy the one from the source either. if 'host' not in kwargs and '__ovo' not in kwargs: # TODO(geguileo): Add pool support pool_name = pool_name or backend_or_vol.pool_names[0] self._ovo.host = ('%s@%s#%s' % (cfg.CONF.host, backend_name, pool_name)) if qos_specs or extra_specs: if qos_specs: qos_specs = cinder_objs.QualityOfServiceSpecs( id=self.id, name=self.id, consumer='back-end', specs=qos_specs) qos_specs_id = self.id else: qos_specs = qos_specs_id = None self._ovo.volume_type = cinder_objs.VolumeType( context=self.CONTEXT, is_public=True, id=self.id, name=self.id, qos_specs_id=qos_specs_id, extra_specs=extra_specs, qos_specs=qos_specs) self._ovo.volume_type_id = self.id @property def snapshots(self): # Lazy loading if self._snapshots is None: self._snapshots = self.persistence.get_snapshots(volume_id=self.id) for snap in self._snapshots: snap.volume = self ovos = [snap._ovo for snap in self._snapshots] self._ovo.snapshots = cinder_objs.SnapshotList(objects=ovos) self._ovo.obj_reset_changes(('snapshots',)) return self._snapshots @property def connections(self): # Lazy loading if self._connections is None: # Check if the driver has already lazy loaded it using OVOs if self._ovo.obj_attr_is_set(CONNECTIONS_OVO_FIELD): conns = [Connection(None, volume=self, __ovo=ovo) for ovo in getattr(self._ovo, CONNECTIONS_OVO_FIELD).objects] # Retrieve data from persistence storage else: conns = self.persistence.get_connections(volume_id=self.id) for conn in conns: conn.volume = self ovos = [conn._ovo for conn in conns] setattr(self._ovo, CONNECTIONS_OVO_FIELD, cinder_objs.VolumeAttachmentList(objects=ovos)) self._ovo.obj_reset_changes((CONNECTIONS_OVO_FIELD,)) self._connections = conns return self._connections
[docs] @classmethod def get_by_id(cls, volume_id): result = cls.persistence.get_volumes(volume_id=volume_id) if not result: raise exception.VolumeNotFound(volume_id=volume_id) return result[0]
[docs] @classmethod def get_by_name(cls, volume_name): return cls.persistence.get_volumes(volume_name=volume_name)
def _populate_data(self): if self._ovo.obj_attr_is_set('snapshots'): self._snapshots = [] for snap_ovo in self._ovo.snapshots: # Set circular reference snap_ovo.volume = self._ovo Snapshot._load(self.backend, snap_ovo, self) else: self._snapshots = None if self._ovo.obj_attr_is_set(CONNECTIONS_OVO_FIELD): self._connections = [] for conn_ovo in getattr(self._ovo, CONNECTIONS_OVO_FIELD): # Set circular reference conn_ovo.volume = self._ovo Connection._load(self.backend, conn_ovo, self) else: self._connections = None @classmethod def _load(cls, backend, ovo, save=None): vol = cls(backend, __ovo=ovo) if save: vol.save() if vol._snapshots: for s in vol._snapshots: s.obj_reset_changes() s.save() if vol._connections: for c in vol._connections: c.obj_reset_changes() c.save() return vol
[docs] def create(self): self.backend._start_creating_volume(self) try: model_update = self.backend.driver.create_volume(self._ovo) self._ovo.status = 'available' if model_update: self._ovo.update(model_update) self.backend._volume_created(self) except Exception: self._ovo.status = 'error' self._raise_with_resource() finally: self.save()
def _snapshot_removed(self, snapshot): # The snapshot instance in memory could be out of sync and not be # identical, so check by ID. i, snap = utils.find_by_id(snapshot.id, self._snapshots) if snap: del self._snapshots[i] i, ovo = utils.find_by_id(snapshot.id, self._ovo.snapshots.objects) if ovo: del self._ovo.snapshots.objects[i] def _connection_removed(self, connection): # The connection instance in memory could be out of sync and not be # identical, so check by ID. i, conn = utils.find_by_id(connection.id, self._connections) if conn: del self._connections[i] ovo_conns = getattr(self._ovo, CONNECTIONS_OVO_FIELD).objects i, ovo_conn = utils.find_by_id(connection.id, ovo_conns) if ovo_conn: del ovo_conns[i]
[docs] def delete(self): if self.snapshots: msg = 'Cannot delete volume %s with snapshots' % self.id raise exception.InvalidVolume(reason=msg) try: self.backend.driver.delete_volume(self._ovo) self.persistence.delete_volume(self) self.backend._volume_removed(self) self._ovo.status = 'deleted' except Exception: self._ovo.status = 'error_deleting' self.save() self._raise_with_resource()
[docs] def extend(self, size): volume = self._ovo volume.previous_status = volume.status volume.status = 'extending' try: self.backend.driver.extend_volume(volume, size) volume.size = size volume.status = volume.previous_status volume.previous_status = None except Exception: volume.status = 'error' self._raise_with_resource() finally: self.save() if volume.status == 'in-use' and self.local_attach: return self.local_attach.extend() # Must return size in bytes return size * GB
[docs] def clone(self, **new_vol_attrs): new_vol_attrs['source_volid'] = self.id new_vol = Volume(self, **new_vol_attrs) self.backend._start_creating_volume(new_vol) try: model_update = self.backend.driver.create_cloned_volume( new_vol._ovo, self._ovo) new_vol._ovo.status = 'available' if model_update: new_vol._ovo.update(model_update) self.backend._volume_created(new_vol) except Exception: new_vol._ovo.status = 'error' new_vol._raise_with_resource() finally: new_vol.save() return new_vol
[docs] def create_snapshot(self, name='', description='', **kwargs): snap = Snapshot(self, name=name, description=description, **kwargs) try: snap.create() finally: if self._snapshots is not None: self._snapshots.append(snap) self._ovo.snapshots.objects.append(snap._ovo) return snap
[docs] def attach(self): connector_dict = brick_connector.get_connector_properties( self.backend_class.root_helper, cfg.CONF.my_ip, self.backend.configuration.use_multipath_for_image_xfer, self.backend.configuration.enforce_multipath_for_image_xfer) conn = self.connect(connector_dict) try: conn.attach() except Exception: self.disconnect(conn) raise return conn
[docs] def detach(self, force=False, ignore_errors=False): if not self.local_attach: raise exception.NotLocal(self.id) exc = brick_exception.ExceptionChainer() conn = self.local_attach try: conn.detach(force, ignore_errors, exc) except Exception: if not force: raise with exc.context(force, 'Unable to disconnect'): conn.disconnect(force) if exc and not ignore_errors: raise exc
[docs] def connect(self, connector_dict, **ovo_fields): model_update = self.backend.driver.create_export(self.CONTEXT, self._ovo, connector_dict) if model_update: self._ovo.update(model_update) self.save() try: conn = Connection.connect(self, connector_dict, **ovo_fields) if self._connections is not None: self._connections.append(conn) ovo_conns = getattr(self._ovo, CONNECTIONS_OVO_FIELD).objects ovo_conns.append(conn._ovo) self._ovo.status = 'in-use' self.save() except Exception: self._remove_export() self._raise_with_resource() return conn
def _disconnect(self, connection): self._remove_export() self._connection_removed(connection) if not self.connections: self._ovo.status = 'available' self.save()
[docs] def disconnect(self, connection, force=False): connection._disconnect(force) self._disconnect(connection)
[docs] def cleanup(self): for attach in self.connections: attach.detach() self._remove_export()
def _remove_export(self): self.backend.driver.remove_export(self._context, self._ovo)
[docs] def refresh(self): last_self = self.get_by_id(self.id) if self._snapshots is not None: last_self.snapshots if self._connections is not None: last_self.connections last_self.local_attach = self.local_attach vars(self).clear() vars(self).update(vars(last_self))
[docs] def save(self): self.persistence.set_volume(self)
[docs]class Connection(Object, LazyVolumeAttr): """Cinderlib Connection info that maps to VolumeAttachment. On Pike we don't have the connector field on the VolumeAttachment ORM instance so we use the connection_info to store everything. We'll have a dictionary: {'conn': connection info 'connector': connector dictionary 'device': result of connect_volume} """ OVO_CLASS = cinder_objs.VolumeAttachment SIMPLE_JSON_IGNORE = ('volume',)
[docs] @classmethod def connect(cls, volume, connector, **kwargs): conn_info = volume.backend.driver.initialize_connection( volume._ovo, connector) conn = cls(volume.backend, connector=connector, volume=volume, status='attached', connection_info={'conn': conn_info}, **kwargs) conn.connector_info = connector conn.save() return conn
@staticmethod def _is_multipathed_conn(kwargs): # Priority: # - kwargs['use_multipath'] # - Multipath in connector_dict in kwargs or _ovo # - Detect from connection_info data from OVO in kwargs if 'use_multipath' in kwargs: return kwargs['use_multipath'] connector = kwargs.get('connector') or {} conn_info = kwargs.get('connection_info') or {} if '__ovo' in kwargs: ovo = kwargs['__ovo'] conn_info = conn_info or ovo.connection_info or {} connector = connector or ovo.connection_info.get('connector') or {} if 'multipath' in connector: return connector['multipath'] # If multipathed not defined autodetect based on connection info conn_info = conn_info['conn'].get('data', {}) iscsi_mp = 'target_iqns' in conn_info and 'target_portals' in conn_info fc_mp = not isinstance(conn_info.get('target_wwn', ''), six.string_types) return iscsi_mp or fc_mp def __init__(self, *args, **kwargs): self.use_multipath = self._is_multipathed_conn(kwargs) scan_attempts = brick_initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT self.scan_attempts = kwargs.pop('device_scan_attempts', scan_attempts) volume = kwargs.pop('volume', None) self._connector = None super(Connection, self).__init__(*args, **kwargs) LazyVolumeAttr.__init__(self, volume) # Attributes could be coming from __ovo, so we need to do this after # all the initialization. data = self.conn_info.get('data', {}) if not (self._ovo.obj_attr_is_set('attach_mode') and self.attach_mode): self._ovo.attach_mode = data.get('access_mode', 'rw') if data: data['access_mode'] = self.attach_mode @property def conn_info(self): conn_info = self._ovo.connection_info if conn_info: return conn_info.get('conn') return {} @conn_info.setter def conn_info(self, value): if not value: self._ovo.connection_info = None return if self._ovo.connection_info is None: self._ovo.connection_info = {} # access_mode in the connection_info is set on __init__, here we ensure # it's also set whenever we change the connection_info out of __init__. if 'data' in value: mode = value['data'].setdefault('access_mode', self.attach_mode) # Keep attach_mode in sync. self._ovo.attach_mode = mode self._ovo.connection_info['conn'] = value @property def protocol(self): return self.conn_info.get('driver_volume_type') @property def connector_info(self): if self.connection_info: return self.connection_info.get('connector') return None @connector_info.setter def connector_info(self, value): if self._ovo.connection_info is None: self._ovo.connection_info = {} self.connection_info['connector'] = value # Since we are changing the dictionary the OVO won't detect the change self._changed_fields.add('connection_info') @property def device(self): if self.connection_info: return self.connection_info.get('device') return None @device.setter def device(self, value): if value: self.connection_info['device'] = value else: self.connection_info.pop('device', None) # Since we are changing the dictionary the OVO won't detect the change self._changed_fields.add('connection_info') @property def path(self): device = self.device if not device: return None return device['path'] @property def connector(self): if not self._connector: if not self.conn_info: return None self._connector = brick_connector.InitiatorConnector.factory( self.protocol, self.backend_class.root_helper, use_multipath=self.use_multipath, device_scan_attempts=self.scan_attempts, # NOTE(geguileo): afaik only remotefs uses the connection info conn=self.conn_info, do_local_attach=True) return self._connector @property def attached(self): return bool(self.device) @property def connected(self): return bool(self.conn_info) @classmethod def _load(cls, backend, ovo, volume=None, save=False): # We let the __init__ method set the _volume if exists conn = cls(backend, __ovo=ovo, volume=volume) if save: conn.save() # Restore circular reference only if we have all the elements if conn._volume: utils.add_by_id(conn, conn._volume._connections) connections = getattr(conn._volume._ovo, CONNECTIONS_OVO_FIELD).objects utils.add_by_id(conn._ovo, connections) return conn def _disconnect(self, force=False): self.backend.driver.terminate_connection(self.volume._ovo, self.connector_info, force=force) self.conn_info = None self._ovo.status = 'detached' self.persistence.delete_connection(self)
[docs] def disconnect(self, force=False): self._disconnect(force) self.volume._disconnect(self)
[docs] def device_attached(self, device): self.device = device self.save()
[docs] def attach(self): device = self.connector.connect_volume(self.conn_info['data']) self.device_attached(device) try: if self.connector.check_valid_device(self.path): error_msg = None else: error_msg = ('Unable to access the backend storage via path ' '%s.' % self.path) except Exception: error_msg = ('Could not validate device %s. There may be missing ' 'packages on your host.' % self.path) LOG.exception(error_msg) if error_msg: # Prepare exception while we still have the value of the path exc = cinder_exception.DeviceUnavailable( path=self.path, attach_info=self._ovo.connection_info, reason=error_msg) self.detach(force=True, ignore_errors=True) raise exc if self._volume: self.volume.local_attach = self
[docs] def detach(self, force=False, ignore_errors=False, exc=None): if not exc: exc = brick_exception.ExceptionChainer() with exc.context(force, 'Disconnect failed'): self.connector.disconnect_volume(self.conn_info['data'], self.device, force=force, ignore_errors=ignore_errors) if not exc or ignore_errors: if self._volume: self.volume.local_attach = None self.device = None self.save() self._connector = None if exc and not ignore_errors: raise exc
[docs] @classmethod def get_by_id(cls, connection_id): result = cls.persistence.get_connections(connection_id=connection_id) if not result: msg = 'id=%s' % connection_id raise exception.ConnectionNotFound(filter=msg) return result[0]
@property def backend(self): if self._backend is None and hasattr(self, '_volume'): self._backend = self.volume.backend return self._backend @backend.setter def backend(self, value): self._backend = value
[docs] def save(self): self.persistence.set_connection(self)
[docs] def extend(self): return self.connector.extend_volume(self.conn_info['data'])
[docs]class Snapshot(NamedObject, LazyVolumeAttr): OVO_CLASS = cinder_objs.Snapshot SIMPLE_JSON_IGNORE = ('volume',) DEFAULT_FIELDS_VALUES = { 'status': 'creating', 'metadata': {}, } def __init__(self, volume, **kwargs): param_backend = self._get_backend(kwargs.pop('backend', None)) if '__ovo' in kwargs: backend = kwargs['__ovo'][BACKEND_NAME_SNAPSHOT_FIELD] else: kwargs.setdefault('user_id', volume.user_id) kwargs.setdefault('project_id', volume.project_id) kwargs['volume_id'] = volume.id kwargs['volume_size'] = volume.size kwargs['volume_type_id'] = volume.volume_type_id kwargs['volume'] = volume._ovo if volume: backend = volume.backend.id kwargs[BACKEND_NAME_SNAPSHOT_FIELD] = backend else: backend = param_backend and param_backend.id if not (backend or param_backend): raise ValueError('Backend not provided') if backend and param_backend and param_backend.id != backend: raise ValueError("Multiple backends provided and they don't match") super(Snapshot, self).__init__(backend=param_backend or backend, **kwargs) LazyVolumeAttr.__init__(self, volume) @classmethod def _load(cls, backend, ovo, volume=None, save=False): # We let the __init__ method set the _volume if exists snap = cls(volume, backend=backend, __ovo=ovo) if save: snap.save() # Restore circular reference only if we have all the elements if snap._volume: utils.add_by_id(snap, snap._volume._snapshots) utils.add_by_id(snap._ovo, snap._volume._ovo.snapshots.objects) return snap
[docs] def create(self): try: model_update = self.backend.driver.create_snapshot(self._ovo) self._ovo.status = 'available' if model_update: self._ovo.update(model_update) except Exception: self._ovo.status = 'error' self._raise_with_resource() finally: self.save()
[docs] def delete(self): try: self.backend.driver.delete_snapshot(self._ovo) self.persistence.delete_snapshot(self) self._ovo.status = 'deleted' except Exception: self._ovo.status = 'error_deleting' self.save() self._raise_with_resource() if self._volume is not None: self._volume._snapshot_removed(self)
[docs] def create_volume(self, **new_vol_params): new_vol_params.setdefault('size', self.volume_size) new_vol_params['snapshot_id'] = self.id new_vol = Volume(self.volume, **new_vol_params) self.backend._start_creating_volume(new_vol) try: model_update = self.backend.driver.create_volume_from_snapshot( new_vol._ovo, self._ovo) new_vol._ovo.status = 'available' if model_update: new_vol._ovo.update(model_update) self.backend._volume_created(new_vol) except Exception: new_vol._ovo.status = 'error' new_vol._raise_with_resource() finally: new_vol.save() return new_vol
[docs] @classmethod def get_by_id(cls, snapshot_id): result = cls.persistence.get_snapshots(snapshot_id=snapshot_id) if not result: raise exception.SnapshotNotFound(snapshot_id=snapshot_id) return result[0]
[docs] @classmethod def get_by_name(cls, snapshot_name): return cls.persistence.get_snapshots(snapshot_name=snapshot_name)
[docs] def save(self): self.persistence.set_snapshot(self)
setup = Object.setup CONTEXT = Object.CONTEXT