Source code for glance.domain

# Copyright 2012 OpenStack Foundation
# Copyright 2013 IBM Corp.
# 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 collections
import datetime
import uuid

from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import importutils
import six

from glance.common import exception
from glance.common import timeutils
from glance.i18n import _, _LE, _LI, _LW

LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.import_opt('task_executor', 'glance.common.config', group='task')


_delayed_delete_imported = False


def _import_delayed_delete():
    # glance_store (indirectly) imports glance.domain therefore we can't put
    # the CONF.import_opt outside - we have to do it in a convoluted/indirect
    # way!
    global _delayed_delete_imported
    if not _delayed_delete_imported:
        CONF.import_opt('delayed_delete', 'glance_store')
        _delayed_delete_imported = True


[docs]class ImageFactory(object): _readonly_properties = ['created_at', 'updated_at', 'status', 'checksum', 'size', 'virtual_size'] _reserved_properties = ['owner', 'locations', 'deleted', 'deleted_at', 'direct_url', 'self', 'file', 'schema'] def _check_readonly(self, kwargs): for key in self._readonly_properties: if key in kwargs: raise exception.ReadonlyProperty(property=key) def _check_unexpected(self, kwargs): if kwargs: msg = _('new_image() got unexpected keywords %s') raise TypeError(msg % kwargs.keys()) def _check_reserved(self, properties): if properties is not None: for key in self._reserved_properties: if key in properties: raise exception.ReservedProperty(property=key)
[docs] def new_image(self, image_id=None, name=None, visibility='private', min_disk=0, min_ram=0, protected=False, owner=None, disk_format=None, container_format=None, extra_properties=None, tags=None, **other_args): extra_properties = extra_properties or {} self._check_readonly(other_args) self._check_unexpected(other_args) self._check_reserved(extra_properties) if image_id is None: image_id = str(uuid.uuid4()) created_at = timeutils.utcnow() updated_at = created_at status = 'queued' return Image(image_id=image_id, name=name, status=status, created_at=created_at, updated_at=updated_at, visibility=visibility, min_disk=min_disk, min_ram=min_ram, protected=protected, owner=owner, disk_format=disk_format, container_format=container_format, extra_properties=extra_properties, tags=tags or [])
[docs]class Image(object): valid_state_targets = { # Each key denotes a "current" state for the image. Corresponding # values list the valid states to which we can jump from that "current" # state. # NOTE(flwang): In v2, we are deprecating the 'killed' status, so it's # allowed to restore image from 'saving' to 'queued' so that upload # can be retried. 'queued': ('saving', 'active', 'deleted'), 'saving': ('active', 'killed', 'deleted', 'queued'), 'active': ('pending_delete', 'deleted', 'deactivated'), 'killed': ('deleted',), 'pending_delete': ('deleted',), 'deleted': (), 'deactivated': ('active', 'deleted'), } def __init__(self, image_id, status, created_at, updated_at, **kwargs): self.image_id = image_id self.status = status self.created_at = created_at self.updated_at = updated_at self.name = kwargs.pop('name', None) self.visibility = kwargs.pop('visibility', 'private') self.min_disk = kwargs.pop('min_disk', 0) self.min_ram = kwargs.pop('min_ram', 0) self.protected = kwargs.pop('protected', False) self.locations = kwargs.pop('locations', []) self.checksum = kwargs.pop('checksum', None) self.owner = kwargs.pop('owner', None) self._disk_format = kwargs.pop('disk_format', None) self._container_format = kwargs.pop('container_format', None) self.size = kwargs.pop('size', None) self.virtual_size = kwargs.pop('virtual_size', None) extra_properties = kwargs.pop('extra_properties', {}) self.extra_properties = ExtraProperties(extra_properties) self.tags = kwargs.pop('tags', []) if kwargs: message = _("__init__() got unexpected keyword argument '%s'") raise TypeError(message % list(kwargs.keys())[0]) @property def status(self): return self._status @status.setter
[docs] def status(self, status): has_status = hasattr(self, '_status') if has_status: if status not in self.valid_state_targets[self._status]: kw = {'cur_status': self._status, 'new_status': status} e = exception.InvalidImageStatusTransition(**kw) LOG.debug(e) raise e if self._status == 'queued' and status in ('saving', 'active'): missing = [k for k in ['disk_format', 'container_format'] if not getattr(self, k)] if len(missing) > 0: if len(missing) == 1: msg = _('Property %s must be set prior to ' 'saving data.') else: msg = _('Properties %s must be set prior to ' 'saving data.') raise ValueError(msg % ', '.join(missing)) # NOTE(flwang): Image size should be cleared as long as the image # status is updated to 'queued' if status == 'queued': self.size = None self.virtual_size = None self._status = status
@property def visibility(self): return self._visibility @visibility.setter
[docs] def visibility(self, visibility): if visibility not in ('public', 'private'): raise ValueError(_('Visibility must be either "public" ' 'or "private"')) self._visibility = visibility
@property def tags(self): return self._tags @tags.setter
[docs] def tags(self, value): self._tags = set(value)
@property def container_format(self): return self._container_format @container_format.setter
[docs] def container_format(self, value): if hasattr(self, '_container_format') and self.status != 'queued': msg = _("Attribute container_format can be only replaced " "for a queued image.") raise exception.Forbidden(message=msg) self._container_format = value
@property def disk_format(self): return self._disk_format @disk_format.setter
[docs] def disk_format(self, value): if hasattr(self, '_disk_format') and self.status != 'queued': msg = _("Attribute disk_format can be only replaced " "for a queued image.") raise exception.Forbidden(message=msg) self._disk_format = value
@property def min_disk(self): return self._min_disk @min_disk.setter
[docs] def min_disk(self, value): if value and value < 0: extra_msg = _('Cannot be a negative value') raise exception.InvalidParameterValue(value=value, param='min_disk', extra_msg=extra_msg) self._min_disk = value
@property def min_ram(self): return self._min_ram @min_ram.setter
[docs] def min_ram(self, value): if value and value < 0: extra_msg = _('Cannot be a negative value') raise exception.InvalidParameterValue(value=value, param='min_ram', extra_msg=extra_msg) self._min_ram = value
[docs] def delete(self): if self.protected: raise exception.ProtectedImageDelete(image_id=self.image_id) if CONF.delayed_delete and self.locations: self.status = 'pending_delete' else: self.status = 'deleted'
[docs] def deactivate(self): if self.status == 'active': self.status = 'deactivated' elif self.status == 'deactivated': # Noop if already deactive pass else: LOG.debug("Not allowed to deactivate image in status '%s'", self.status) msg = (_("Not allowed to deactivate image in status '%s'") % self.status) raise exception.Forbidden(message=msg)
[docs] def reactivate(self): if self.status == 'deactivated': self.status = 'active' elif self.status == 'active': # Noop if already active pass else: LOG.debug("Not allowed to reactivate image in status '%s'", self.status) msg = (_("Not allowed to reactivate image in status '%s'") % self.status) raise exception.Forbidden(message=msg)
[docs] def get_data(self, *args, **kwargs): raise NotImplementedError()
[docs] def set_data(self, data, size=None): raise NotImplementedError()
[docs]class ExtraProperties(collections.MutableMapping, dict): def __getitem__(self, key): return dict.__getitem__(self, key) def __setitem__(self, key, value): return dict.__setitem__(self, key, value) def __delitem__(self, key): return dict.__delitem__(self, key) def __eq__(self, other): if isinstance(other, ExtraProperties): return dict(self).__eq__(dict(other)) elif isinstance(other, dict): return dict(self).__eq__(other) else: return False def __ne__(self, other): return not self.__eq__(other) def __len__(self): return dict(self).__len__()
[docs] def keys(self): return dict(self).keys()
[docs]class ImageMembership(object): def __init__(self, image_id, member_id, created_at, updated_at, id=None, status=None): self.id = id self.image_id = image_id self.member_id = member_id self.created_at = created_at self.updated_at = updated_at self.status = status @property def status(self): return self._status @status.setter
[docs] def status(self, status): if status not in ('pending', 'accepted', 'rejected'): msg = _('Status must be "pending", "accepted" or "rejected".') raise ValueError(msg) self._status = status
[docs]class ImageMemberFactory(object):
[docs] def new_image_member(self, image, member_id): created_at = timeutils.utcnow() updated_at = created_at return ImageMembership(image_id=image.image_id, member_id=member_id, created_at=created_at, updated_at=updated_at, status='pending')
[docs]class Task(object): _supported_task_type = ('import',) _supported_task_status = ('pending', 'processing', 'success', 'failure') def __init__(self, task_id, task_type, status, owner, expires_at, created_at, updated_at, task_input, result, message): if task_type not in self._supported_task_type: raise exception.InvalidTaskType(task_type) if status not in self._supported_task_status: raise exception.InvalidTaskStatus(status) self.task_id = task_id self._status = status self.type = task_type self.owner = owner self.expires_at = expires_at # NOTE(nikhil): We use '_time_to_live' to determine how long a # task should live from the time it succeeds or fails. task_time_to_live = CONF.task.task_time_to_live self._time_to_live = datetime.timedelta(hours=task_time_to_live) self.created_at = created_at self.updated_at = updated_at self.task_input = task_input self.result = result self.message = message @property
[docs] def status(self): return self._status
@property def message(self): return self._message @message.setter
[docs] def message(self, message): if message: self._message = six.text_type(message) else: self._message = six.text_type('')
def _validate_task_status_transition(self, cur_status, new_status): valid_transitions = { 'pending': ['processing', 'failure'], 'processing': ['success', 'failure'], 'success': [], 'failure': [], } if new_status in valid_transitions[cur_status]: return True else: return False def _set_task_status(self, new_status): if self._validate_task_status_transition(self.status, new_status): self._status = new_status LOG.info(_LI("Task [%(task_id)s] status changing from " "%(cur_status)s to %(new_status)s"), {'task_id': self.task_id, 'cur_status': self.status, 'new_status': new_status}) self._status = new_status else: LOG.error(_LE("Task [%(task_id)s] status failed to change from " "%(cur_status)s to %(new_status)s"), {'task_id': self.task_id, 'cur_status': self.status, 'new_status': new_status}) raise exception.InvalidTaskStatusTransition( cur_status=self.status, new_status=new_status )
[docs] def begin_processing(self): new_status = 'processing' self._set_task_status(new_status)
[docs] def succeed(self, result): new_status = 'success' self.result = result self._set_task_status(new_status) self.expires_at = timeutils.utcnow() + self._time_to_live
[docs] def fail(self, message): new_status = 'failure' self.message = message self._set_task_status(new_status) self.expires_at = timeutils.utcnow() + self._time_to_live
[docs] def run(self, executor): executor.begin_processing(self.task_id)
[docs]class TaskStub(object): def __init__(self, task_id, task_type, status, owner, expires_at, created_at, updated_at): self.task_id = task_id self._status = status self.type = task_type self.owner = owner self.expires_at = expires_at self.created_at = created_at self.updated_at = updated_at @property
[docs] def status(self): return self._status
[docs]class TaskFactory(object):
[docs] def new_task(self, task_type, owner, task_input=None, **kwargs): task_id = str(uuid.uuid4()) status = 'pending' # Note(nikhil): expires_at would be set on the task, only when it # succeeds or fails. expires_at = None created_at = timeutils.utcnow() updated_at = created_at return Task( task_id, task_type, status, owner, expires_at, created_at, updated_at, task_input, kwargs.get('result'), kwargs.get('message') )
[docs]class TaskExecutorFactory(object): eventlet_deprecation_warned = False def __init__(self, task_repo, image_repo, image_factory): self.task_repo = task_repo self.image_repo = image_repo self.image_factory = image_factory
[docs] def new_task_executor(self, context): try: # NOTE(flaper87): Backwards compatibility layer. # It'll allow us to provide a deprecation path to # users that are currently consuming the `eventlet` # executor. task_executor = CONF.task.task_executor if task_executor == 'eventlet': # NOTE(jokke): Making sure we do not log the deprecation # warning 1000 times or anything crazy like that. if not TaskExecutorFactory.eventlet_deprecation_warned: msg = _LW("The `eventlet` executor has been deprecated. " "Use `taskflow` instead.") LOG.warn(msg) TaskExecutorFactory.eventlet_deprecation_warned = True task_executor = 'taskflow' executor_cls = ('glance.async.%s_executor.' 'TaskExecutor' % task_executor) LOG.debug("Loading %s executor", task_executor) executor = importutils.import_class(executor_cls) return executor(context, self.task_repo, self.image_repo, self.image_factory) except ImportError: with excutils.save_and_reraise_exception(): LOG.exception(_LE("Failed to load the %s executor provided " "in the config.") % CONF.task.task_executor)
[docs]class MetadefNamespace(object): def __init__(self, namespace_id, namespace, display_name, description, owner, visibility, protected, created_at, updated_at): self.namespace_id = namespace_id self.namespace = namespace self.display_name = display_name self.description = description self.owner = owner self.visibility = visibility or "private" self.protected = protected or False self.created_at = created_at self.updated_at = updated_at
[docs] def delete(self): if self.protected: raise exception.ProtectedMetadefNamespaceDelete( namespace=self.namespace)
[docs]class MetadefNamespaceFactory(object):
[docs] def new_namespace(self, namespace, owner, **kwargs): namespace_id = str(uuid.uuid4()) created_at = timeutils.utcnow() updated_at = created_at return MetadefNamespace( namespace_id, namespace, kwargs.get('display_name'), kwargs.get('description'), owner, kwargs.get('visibility'), kwargs.get('protected'), created_at, updated_at )
[docs]class MetadefObject(object): def __init__(self, namespace, object_id, name, created_at, updated_at, required, description, properties): self.namespace = namespace self.object_id = object_id self.name = name self.created_at = created_at self.updated_at = updated_at self.required = required self.description = description self.properties = properties
[docs] def delete(self): if self.namespace.protected: raise exception.ProtectedMetadefObjectDelete(object_name=self.name)
[docs]class MetadefObjectFactory(object):
[docs] def new_object(self, namespace, name, **kwargs): object_id = str(uuid.uuid4()) created_at = timeutils.utcnow() updated_at = created_at return MetadefObject( namespace, object_id, name, created_at, updated_at, kwargs.get('required'), kwargs.get('description'), kwargs.get('properties') )
[docs]class MetadefResourceType(object): def __init__(self, namespace, name, prefix, properties_target, created_at, updated_at): self.namespace = namespace self.name = name self.prefix = prefix self.properties_target = properties_target self.created_at = created_at self.updated_at = updated_at
[docs] def delete(self): if self.namespace.protected: raise exception.ProtectedMetadefResourceTypeAssociationDelete( resource_type=self.name)
[docs]class MetadefResourceTypeFactory(object):
[docs] def new_resource_type(self, namespace, name, **kwargs): created_at = timeutils.utcnow() updated_at = created_at return MetadefResourceType( namespace, name, kwargs.get('prefix'), kwargs.get('properties_target'), created_at, updated_at )
[docs]class MetadefProperty(object): def __init__(self, namespace, property_id, name, schema): self.namespace = namespace self.property_id = property_id self.name = name self.schema = schema
[docs] def delete(self): if self.namespace.protected: raise exception.ProtectedMetadefNamespacePropDelete( property_name=self.name)
[docs]class MetadefPropertyFactory(object):
[docs] def new_namespace_property(self, namespace, name, schema, **kwargs): property_id = str(uuid.uuid4()) return MetadefProperty( namespace, property_id, name, schema )
[docs]class MetadefTag(object): def __init__(self, namespace, tag_id, name, created_at, updated_at): self.namespace = namespace self.tag_id = tag_id self.name = name self.created_at = created_at self.updated_at = updated_at
[docs] def delete(self): if self.namespace.protected: raise exception.ProtectedMetadefTagDelete(tag_name=self.name)
[docs]class MetadefTagFactory(object):
[docs] def new_tag(self, namespace, name, **kwargs): tag_id = str(uuid.uuid4()) created_at = timeutils.utcnow() updated_at = created_at return MetadefTag( namespace, tag_id, name, created_at, updated_at )

Project Source