"Fossies" - the Fresh Open Source Software Archive

Member "glance-19.0.0/glance/domain/__init__.py" (16 Oct 2019, 23589 Bytes) of package /linux/misc/openstack/glance-19.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "__init__.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 18.0.0_vs_19.0.0.

    1 # Copyright 2012 OpenStack Foundation
    2 # Copyright 2013 IBM Corp.
    3 # All Rights Reserved.
    4 #
    5 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    6 #    not use this file except in compliance with the License. You may obtain
    7 #    a copy of the License at
    8 #
    9 #         http://www.apache.org/licenses/LICENSE-2.0
   10 #
   11 #    Unless required by applicable law or agreed to in writing, software
   12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   14 #    License for the specific language governing permissions and limitations
   15 #    under the License.
   16 
   17 # TODO(smcginnis) update this once six has support for collections.abc
   18 # (https://github.com/benjaminp/six/pull/241) or clean up once we drop py2.7.
   19 try:
   20     from collections.abc import MutableMapping
   21 except ImportError:
   22     from collections import MutableMapping
   23 
   24 import datetime
   25 import uuid
   26 
   27 from oslo_config import cfg
   28 from oslo_log import log as logging
   29 from oslo_utils import excutils
   30 from oslo_utils import importutils
   31 import six
   32 
   33 from glance.common import exception
   34 from glance.common import timeutils
   35 from glance.i18n import _, _LE, _LI, _LW
   36 
   37 LOG = logging.getLogger(__name__)
   38 CONF = cfg.CONF
   39 CONF.import_opt('task_executor', 'glance.common.config', group='task')
   40 
   41 
   42 _delayed_delete_imported = False
   43 
   44 
   45 def _import_delayed_delete():
   46     # glance_store (indirectly) imports glance.domain therefore we can't put
   47     # the CONF.import_opt outside - we have to do it in a convoluted/indirect
   48     # way!
   49     global _delayed_delete_imported
   50     if not _delayed_delete_imported:
   51         CONF.import_opt('delayed_delete', 'glance_store')
   52         _delayed_delete_imported = True
   53 
   54 
   55 class ImageFactory(object):
   56     _readonly_properties = ['created_at', 'updated_at', 'status', 'checksum',
   57                             'os_hash_algo', 'os_hash_value', 'size',
   58                             'virtual_size']
   59     _reserved_properties = ['owner', 'locations', 'deleted', 'deleted_at',
   60                             'direct_url', 'self', 'file', 'schema']
   61 
   62     def _check_readonly(self, kwargs):
   63         for key in self._readonly_properties:
   64             if key in kwargs:
   65                 raise exception.ReadonlyProperty(property=key)
   66 
   67     def _check_unexpected(self, kwargs):
   68         if kwargs:
   69             msg = _('new_image() got unexpected keywords %s')
   70             raise TypeError(msg % kwargs.keys())
   71 
   72     def _check_reserved(self, properties):
   73         if properties is not None:
   74             for key in self._reserved_properties:
   75                 if key in properties:
   76                     raise exception.ReservedProperty(property=key)
   77 
   78     def new_image(self, image_id=None, name=None, visibility='shared',
   79                   min_disk=0, min_ram=0, protected=False, owner=None,
   80                   disk_format=None, container_format=None,
   81                   extra_properties=None, tags=None, os_hidden=False,
   82                   **other_args):
   83         extra_properties = extra_properties or {}
   84         self._check_readonly(other_args)
   85         self._check_unexpected(other_args)
   86         self._check_reserved(extra_properties)
   87 
   88         if image_id is None:
   89             image_id = str(uuid.uuid4())
   90         created_at = timeutils.utcnow()
   91         updated_at = created_at
   92         status = 'queued'
   93 
   94         return Image(image_id=image_id, name=name, status=status,
   95                      created_at=created_at, updated_at=updated_at,
   96                      visibility=visibility, min_disk=min_disk,
   97                      min_ram=min_ram, protected=protected,
   98                      owner=owner, disk_format=disk_format,
   99                      container_format=container_format,
  100                      os_hidden=os_hidden,
  101                      extra_properties=extra_properties, tags=tags or [])
  102 
  103 
  104 class Image(object):
  105 
  106     valid_state_targets = {
  107         # Each key denotes a "current" state for the image. Corresponding
  108         # values list the valid states to which we can jump from that "current"
  109         # state.
  110         # NOTE(flwang): In v2, we are deprecating the 'killed' status, so it's
  111         # allowed to restore image from 'saving' to 'queued' so that upload
  112         # can be retried.
  113         'queued': ('saving', 'uploading', 'importing', 'active', 'deleted'),
  114         'saving': ('active', 'killed', 'deleted', 'queued'),
  115         'uploading': ('importing', 'queued', 'deleted'),
  116         'importing': ('active', 'deleted', 'queued'),
  117         'active': ('pending_delete', 'deleted', 'deactivated'),
  118         'killed': ('deleted',),
  119         'pending_delete': ('deleted', 'active'),
  120         'deleted': (),
  121         'deactivated': ('active', 'deleted'),
  122     }
  123 
  124     def __init__(self, image_id, status, created_at, updated_at, **kwargs):
  125         self.image_id = image_id
  126         self.status = status
  127         self.created_at = created_at
  128         self.updated_at = updated_at
  129         self.name = kwargs.pop('name', None)
  130         self.visibility = kwargs.pop('visibility', 'shared')
  131         self.os_hidden = kwargs.pop('os_hidden', False)
  132         self.min_disk = kwargs.pop('min_disk', 0)
  133         self.min_ram = kwargs.pop('min_ram', 0)
  134         self.protected = kwargs.pop('protected', False)
  135         self.locations = kwargs.pop('locations', [])
  136         self.checksum = kwargs.pop('checksum', None)
  137         self.os_hash_algo = kwargs.pop('os_hash_algo', None)
  138         self.os_hash_value = kwargs.pop('os_hash_value', None)
  139         self.owner = kwargs.pop('owner', None)
  140         self._disk_format = kwargs.pop('disk_format', None)
  141         self._container_format = kwargs.pop('container_format', None)
  142         self.size = kwargs.pop('size', None)
  143         self.virtual_size = kwargs.pop('virtual_size', None)
  144         extra_properties = kwargs.pop('extra_properties', {})
  145         self.extra_properties = ExtraProperties(extra_properties)
  146         self.tags = kwargs.pop('tags', [])
  147         if kwargs:
  148             message = _("__init__() got unexpected keyword argument '%s'")
  149             raise TypeError(message % list(kwargs.keys())[0])
  150 
  151     @property
  152     def status(self):
  153         return self._status
  154 
  155     @status.setter
  156     def status(self, status):
  157         has_status = hasattr(self, '_status')
  158         if has_status:
  159             if status not in self.valid_state_targets[self._status]:
  160                 kw = {'cur_status': self._status, 'new_status': status}
  161                 e = exception.InvalidImageStatusTransition(**kw)
  162                 LOG.debug(e)
  163                 raise e
  164 
  165             if self._status in ('queued', 'uploading') and status in (
  166                     'saving', 'active', 'importing'):
  167                 missing = [k for k in ['disk_format', 'container_format']
  168                            if not getattr(self, k)]
  169                 if len(missing) > 0:
  170                     if len(missing) == 1:
  171                         msg = _('Property %s must be set prior to '
  172                                 'saving data.')
  173                     else:
  174                         msg = _('Properties %s must be set prior to '
  175                                 'saving data.')
  176                     raise ValueError(msg % ', '.join(missing))
  177         # NOTE(flwang): Image size should be cleared as long as the image
  178         # status is updated to 'queued'
  179         if status == 'queued':
  180             self.size = None
  181             self.virtual_size = None
  182         self._status = status
  183 
  184     @property
  185     def visibility(self):
  186         return self._visibility
  187 
  188     @visibility.setter
  189     def visibility(self, visibility):
  190         if visibility not in ('community', 'public', 'private', 'shared'):
  191             raise ValueError(_('Visibility must be one of "community", '
  192                                '"public", "private", or "shared"'))
  193         self._visibility = visibility
  194 
  195     @property
  196     def tags(self):
  197         return self._tags
  198 
  199     @tags.setter
  200     def tags(self, value):
  201         self._tags = set(value)
  202 
  203     @property
  204     def container_format(self):
  205         return self._container_format
  206 
  207     @container_format.setter
  208     def container_format(self, value):
  209         if (hasattr(self, '_container_format') and
  210                 self.status not in ('queued', 'importing')):
  211             msg = _("Attribute container_format can be only replaced "
  212                     "for a queued image.")
  213             raise exception.Forbidden(message=msg)
  214         self._container_format = value
  215 
  216     @property
  217     def disk_format(self):
  218         return self._disk_format
  219 
  220     @disk_format.setter
  221     def disk_format(self, value):
  222         if (hasattr(self, '_disk_format') and
  223                 self.status not in ('queued', 'importing')):
  224             msg = _("Attribute disk_format can be only replaced "
  225                     "for a queued image.")
  226             raise exception.Forbidden(message=msg)
  227         self._disk_format = value
  228 
  229     @property
  230     def min_disk(self):
  231         return self._min_disk
  232 
  233     @min_disk.setter
  234     def min_disk(self, value):
  235         if value and value < 0:
  236             extra_msg = _('Cannot be a negative value')
  237             raise exception.InvalidParameterValue(value=value,
  238                                                   param='min_disk',
  239                                                   extra_msg=extra_msg)
  240         self._min_disk = value
  241 
  242     @property
  243     def min_ram(self):
  244         return self._min_ram
  245 
  246     @min_ram.setter
  247     def min_ram(self, value):
  248         if value and value < 0:
  249             extra_msg = _('Cannot be a negative value')
  250             raise exception.InvalidParameterValue(value=value,
  251                                                   param='min_ram',
  252                                                   extra_msg=extra_msg)
  253         self._min_ram = value
  254 
  255     def delete(self):
  256         if self.protected:
  257             raise exception.ProtectedImageDelete(image_id=self.image_id)
  258         if CONF.delayed_delete and self.locations:
  259             self.status = 'pending_delete'
  260         else:
  261             self.status = 'deleted'
  262 
  263     def deactivate(self):
  264         if self.status == 'active':
  265             self.status = 'deactivated'
  266         elif self.status == 'deactivated':
  267             # Noop if already deactive
  268             pass
  269         else:
  270             LOG.debug("Not allowed to deactivate image in status '%s'",
  271                       self.status)
  272             msg = (_("Not allowed to deactivate image in status '%s'")
  273                    % self.status)
  274             raise exception.Forbidden(message=msg)
  275 
  276     def reactivate(self):
  277         if self.status == 'deactivated':
  278             self.status = 'active'
  279         elif self.status == 'active':
  280             # Noop if already active
  281             pass
  282         else:
  283             LOG.debug("Not allowed to reactivate image in status '%s'",
  284                       self.status)
  285             msg = (_("Not allowed to reactivate image in status '%s'")
  286                    % self.status)
  287             raise exception.Forbidden(message=msg)
  288 
  289     def get_data(self, *args, **kwargs):
  290         raise NotImplementedError()
  291 
  292     def set_data(self, data, size=None, backend=None):
  293         raise NotImplementedError()
  294 
  295 
  296 class ExtraProperties(MutableMapping, dict):
  297 
  298     def __getitem__(self, key):
  299         return dict.__getitem__(self, key)
  300 
  301     def __setitem__(self, key, value):
  302         return dict.__setitem__(self, key, value)
  303 
  304     def __delitem__(self, key):
  305         return dict.__delitem__(self, key)
  306 
  307     def __eq__(self, other):
  308         if isinstance(other, ExtraProperties):
  309             return dict.__eq__(self, dict(other))
  310         elif isinstance(other, dict):
  311             return dict.__eq__(self, other)
  312         else:
  313             return False
  314 
  315     def __ne__(self, other):
  316         return not self.__eq__(other)
  317 
  318     def __len__(self):
  319         return dict.__len__(self)
  320 
  321     def keys(self):
  322         return dict.keys(self)
  323 
  324 
  325 class ImageMembership(object):
  326 
  327     def __init__(self, image_id, member_id, created_at, updated_at,
  328                  id=None, status=None):
  329         self.id = id
  330         self.image_id = image_id
  331         self.member_id = member_id
  332         self.created_at = created_at
  333         self.updated_at = updated_at
  334         self.status = status
  335 
  336     @property
  337     def status(self):
  338         return self._status
  339 
  340     @status.setter
  341     def status(self, status):
  342         if status not in ('pending', 'accepted', 'rejected'):
  343             msg = _('Status must be "pending", "accepted" or "rejected".')
  344             raise ValueError(msg)
  345         self._status = status
  346 
  347 
  348 class ImageMemberFactory(object):
  349 
  350     def new_image_member(self, image, member_id):
  351         created_at = timeutils.utcnow()
  352         updated_at = created_at
  353 
  354         return ImageMembership(image_id=image.image_id, member_id=member_id,
  355                                created_at=created_at, updated_at=updated_at,
  356                                status='pending')
  357 
  358 
  359 class Task(object):
  360     _supported_task_type = ('import', 'api_image_import')
  361 
  362     _supported_task_status = ('pending', 'processing', 'success', 'failure')
  363 
  364     def __init__(self, task_id, task_type, status, owner,
  365                  expires_at, created_at, updated_at,
  366                  task_input, result, message):
  367 
  368         if task_type not in self._supported_task_type:
  369             raise exception.InvalidTaskType(type=task_type)
  370 
  371         if status not in self._supported_task_status:
  372             raise exception.InvalidTaskStatus(status=status)
  373 
  374         self.task_id = task_id
  375         self._status = status
  376         self.type = task_type
  377         self.owner = owner
  378         self.expires_at = expires_at
  379         # NOTE(nikhil): We use '_time_to_live' to determine how long a
  380         # task should live from the time it succeeds or fails.
  381         task_time_to_live = CONF.task.task_time_to_live
  382         self._time_to_live = datetime.timedelta(hours=task_time_to_live)
  383         self.created_at = created_at
  384         self.updated_at = updated_at
  385         self.task_input = task_input
  386         self.result = result
  387         self.message = message
  388 
  389     @property
  390     def status(self):
  391         return self._status
  392 
  393     @property
  394     def message(self):
  395         return self._message
  396 
  397     @message.setter
  398     def message(self, message):
  399         if message:
  400             self._message = six.text_type(message)
  401         else:
  402             self._message = six.text_type('')
  403 
  404     def _validate_task_status_transition(self, cur_status, new_status):
  405         valid_transitions = {
  406             'pending': ['processing', 'failure'],
  407             'processing': ['success', 'failure'],
  408             'success': [],
  409             'failure': [],
  410         }
  411 
  412         if new_status in valid_transitions[cur_status]:
  413             return True
  414         else:
  415             return False
  416 
  417     def _set_task_status(self, new_status):
  418         if self._validate_task_status_transition(self.status, new_status):
  419             old_status = self.status
  420             self._status = new_status
  421             LOG.info(_LI("Task [%(task_id)s] status changing from "
  422                          "%(cur_status)s to %(new_status)s"),
  423                      {'task_id': self.task_id, 'cur_status': old_status,
  424                       'new_status': new_status})
  425         else:
  426             LOG.error(_LE("Task [%(task_id)s] status failed to change from "
  427                           "%(cur_status)s to %(new_status)s"),
  428                       {'task_id': self.task_id, 'cur_status': self.status,
  429                        'new_status': new_status})
  430             raise exception.InvalidTaskStatusTransition(
  431                 cur_status=self.status,
  432                 new_status=new_status
  433             )
  434 
  435     def begin_processing(self):
  436         new_status = 'processing'
  437         self._set_task_status(new_status)
  438 
  439     def succeed(self, result):
  440         new_status = 'success'
  441         self.result = result
  442         self._set_task_status(new_status)
  443         self.expires_at = timeutils.utcnow() + self._time_to_live
  444 
  445     def fail(self, message):
  446         new_status = 'failure'
  447         self.message = message
  448         self._set_task_status(new_status)
  449         self.expires_at = timeutils.utcnow() + self._time_to_live
  450 
  451     def run(self, executor):
  452         executor.begin_processing(self.task_id)
  453 
  454 
  455 class TaskStub(object):
  456 
  457     def __init__(self, task_id, task_type, status, owner,
  458                  expires_at, created_at, updated_at):
  459         self.task_id = task_id
  460         self._status = status
  461         self.type = task_type
  462         self.owner = owner
  463         self.expires_at = expires_at
  464         self.created_at = created_at
  465         self.updated_at = updated_at
  466 
  467     @property
  468     def status(self):
  469         return self._status
  470 
  471 
  472 class TaskFactory(object):
  473 
  474     def new_task(self, task_type, owner,
  475                  task_input=None, **kwargs):
  476         task_id = str(uuid.uuid4())
  477         status = 'pending'
  478         # Note(nikhil): expires_at would be set on the task, only when it
  479         # succeeds or fails.
  480         expires_at = None
  481         created_at = timeutils.utcnow()
  482         updated_at = created_at
  483         return Task(
  484             task_id,
  485             task_type,
  486             status,
  487             owner,
  488             expires_at,
  489             created_at,
  490             updated_at,
  491             task_input,
  492             kwargs.get('result'),
  493             kwargs.get('message')
  494         )
  495 
  496 
  497 class TaskExecutorFactory(object):
  498     eventlet_deprecation_warned = False
  499 
  500     def __init__(self, task_repo, image_repo, image_factory):
  501         self.task_repo = task_repo
  502         self.image_repo = image_repo
  503         self.image_factory = image_factory
  504 
  505     def new_task_executor(self, context):
  506         try:
  507             # NOTE(flaper87): Backwards compatibility layer.
  508             # It'll allow us to provide a deprecation path to
  509             # users that are currently consuming the `eventlet`
  510             # executor.
  511             task_executor = CONF.task.task_executor
  512             if task_executor == 'eventlet':
  513                 # NOTE(jokke): Making sure we do not log the deprecation
  514                 # warning 1000 times or anything crazy like that.
  515                 if not TaskExecutorFactory.eventlet_deprecation_warned:
  516                     msg = _LW("The `eventlet` executor has been deprecated. "
  517                               "Use `taskflow` instead.")
  518                     LOG.warn(msg)
  519                     TaskExecutorFactory.eventlet_deprecation_warned = True
  520                 task_executor = 'taskflow'
  521 
  522             executor_cls = ('glance.async_.%s_executor.'
  523                             'TaskExecutor' % task_executor)
  524             LOG.debug("Loading %s executor", task_executor)
  525             executor = importutils.import_class(executor_cls)
  526             return executor(context,
  527                             self.task_repo,
  528                             self.image_repo,
  529                             self.image_factory)
  530         except ImportError:
  531             with excutils.save_and_reraise_exception():
  532                 LOG.exception(_LE("Failed to load the %s executor provided "
  533                                   "in the config.") % CONF.task.task_executor)
  534 
  535 
  536 class MetadefNamespace(object):
  537 
  538     def __init__(self, namespace_id, namespace, display_name, description,
  539                  owner, visibility, protected, created_at, updated_at):
  540         self.namespace_id = namespace_id
  541         self.namespace = namespace
  542         self.display_name = display_name
  543         self.description = description
  544         self.owner = owner
  545         self.visibility = visibility or "private"
  546         self.protected = protected or False
  547         self.created_at = created_at
  548         self.updated_at = updated_at
  549 
  550     def delete(self):
  551         if self.protected:
  552             raise exception.ProtectedMetadefNamespaceDelete(
  553                 namespace=self.namespace)
  554 
  555 
  556 class MetadefNamespaceFactory(object):
  557 
  558     def new_namespace(self, namespace, owner, **kwargs):
  559         namespace_id = str(uuid.uuid4())
  560         created_at = timeutils.utcnow()
  561         updated_at = created_at
  562         return MetadefNamespace(
  563             namespace_id,
  564             namespace,
  565             kwargs.get('display_name'),
  566             kwargs.get('description'),
  567             owner,
  568             kwargs.get('visibility'),
  569             kwargs.get('protected'),
  570             created_at,
  571             updated_at
  572         )
  573 
  574 
  575 class MetadefObject(object):
  576 
  577     def __init__(self, namespace, object_id, name, created_at, updated_at,
  578                  required, description, properties):
  579         self.namespace = namespace
  580         self.object_id = object_id
  581         self.name = name
  582         self.created_at = created_at
  583         self.updated_at = updated_at
  584         self.required = required
  585         self.description = description
  586         self.properties = properties
  587 
  588     def delete(self):
  589         if self.namespace.protected:
  590             raise exception.ProtectedMetadefObjectDelete(object_name=self.name)
  591 
  592 
  593 class MetadefObjectFactory(object):
  594 
  595     def new_object(self, namespace, name, **kwargs):
  596         object_id = str(uuid.uuid4())
  597         created_at = timeutils.utcnow()
  598         updated_at = created_at
  599         return MetadefObject(
  600             namespace,
  601             object_id,
  602             name,
  603             created_at,
  604             updated_at,
  605             kwargs.get('required'),
  606             kwargs.get('description'),
  607             kwargs.get('properties')
  608         )
  609 
  610 
  611 class MetadefResourceType(object):
  612 
  613     def __init__(self, namespace, name, prefix, properties_target,
  614                  created_at, updated_at):
  615         self.namespace = namespace
  616         self.name = name
  617         self.prefix = prefix
  618         self.properties_target = properties_target
  619         self.created_at = created_at
  620         self.updated_at = updated_at
  621 
  622     def delete(self):
  623         if self.namespace.protected:
  624             raise exception.ProtectedMetadefResourceTypeAssociationDelete(
  625                 resource_type=self.name)
  626 
  627 
  628 class MetadefResourceTypeFactory(object):
  629 
  630     def new_resource_type(self, namespace, name, **kwargs):
  631         created_at = timeutils.utcnow()
  632         updated_at = created_at
  633         return MetadefResourceType(
  634             namespace,
  635             name,
  636             kwargs.get('prefix'),
  637             kwargs.get('properties_target'),
  638             created_at,
  639             updated_at
  640         )
  641 
  642 
  643 class MetadefProperty(object):
  644 
  645     def __init__(self, namespace, property_id, name, schema):
  646         self.namespace = namespace
  647         self.property_id = property_id
  648         self.name = name
  649         self.schema = schema
  650 
  651     def delete(self):
  652         if self.namespace.protected:
  653             raise exception.ProtectedMetadefNamespacePropDelete(
  654                 property_name=self.name)
  655 
  656 
  657 class MetadefPropertyFactory(object):
  658 
  659     def new_namespace_property(self, namespace, name, schema, **kwargs):
  660         property_id = str(uuid.uuid4())
  661         return MetadefProperty(
  662             namespace,
  663             property_id,
  664             name,
  665             schema
  666         )
  667 
  668 
  669 class MetadefTag(object):
  670 
  671     def __init__(self, namespace, tag_id, name, created_at, updated_at):
  672         self.namespace = namespace
  673         self.tag_id = tag_id
  674         self.name = name
  675         self.created_at = created_at
  676         self.updated_at = updated_at
  677 
  678     def delete(self):
  679         if self.namespace.protected:
  680             raise exception.ProtectedMetadefTagDelete(tag_name=self.name)
  681 
  682 
  683 class MetadefTagFactory(object):
  684 
  685     def new_tag(self, namespace, name, **kwargs):
  686         tag_id = str(uuid.uuid4())
  687         created_at = timeutils.utcnow()
  688         updated_at = created_at
  689         return MetadefTag(
  690             namespace,
  691             tag_id,
  692             name,
  693             created_at,
  694             updated_at
  695         )