yakumo.base

Package yakumo :: Module base |
[hide private]
[frames] | no frames]

# Source Code for Module yakumo.base

      1  # Copyright 2014-2017 by Akira Yoshiyama <akirayoshiyama@gmail.com>.
      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
     16  """
     17  Abstract classes for resource management
     18  """
     19
     20  import copy
     21  import inspect
     22  import time
     23
     24  from . import constant
     25  from . import exception
     26  from . import mapper
     27  from . import utils
     28
     29
     30  WRAPPER_METHODS = []
     31  BAD_ATTRS = ['self']
     32
     33
     34 -class Resource(object):
     35      """Base class for resources."""
     36
     37      _id = None
     38      _attrs = []
     39      _loaded = True
     40      _sub_manager_list = {}
     41      _state_attr = 'status'
     42      _stable_state = []
     43
     44 -    def __init__(self, manager, *args, **kwargs):
     45          """
     46          Create a resource object
     47
     48          Don't call this method directly; use Manager methods instead.
     49
     50          @param manager: Manager object
     51          @type manager: yakumo.base.Manager
     52          @return: Resource object
     53          @rtype: yakumo.base.Resource
     54          """
     55          self._manager = manager
     56          self._attr2json = manager._attr2json
     57          self._client = manager._client
     58          self._has_extra_attr = manager._has_extra_attr
     59          self._http = manager._http
     60          self._id_attr = manager._id_attr
     61          self._json2attr = manager._json2attr
     62          self._json_resource_key = manager._json_resource_key
     63          self._no_such_api = manager._no_such_api
     64          self._update_method = manager._update_method
     65          self._url_resource_path = manager._url_resource_path
     66          self._verbose = manager._verbose
     67
     68          id = kwargs.get(self._id_attr)
     69          if isinstance(id, Resource):
     70              self._id = id._id
     71          else:
     72              self._id = id
     73          self._attrs = [attr
     74                         for attr, json_attr, _mapper
     75                         in manager._attr_mapping]
     76          self._set_attrs(kwargs)
     77          if len(kwargs) == 1 and self._id_attr in kwargs:
     78              self._loaded = False
     79          if len(kwargs) >= 1 and self._id_attr in kwargs:
     80              for attr, sub_manager in self._sub_manager_list.items():
     81                  setattr(self, attr,
     82                          sub_manager(self, *args, **kwargs))
     83
     84 -    def __eq__(self, other):
     85          if not isinstance(other, Resource):
     86              return False
     87          return self._id == other._id
     88
     89 -    def __ne__(self, other):
     90          if not isinstance(other, Resource):
     91              return True
     92          return self._id != other._id
     93
     94 -    def __repr__(self):
     95          if 'name' in self._attrs and self._id_attr != 'name' and self._loaded:
     96              return '<%s.%s (%s="%s", name="%s")>' % (
     97                  self.__module__, self.__class__.__name__, self._id_attr,
     98                  self._id, self.name)
     99          empty = ''
    100          if not self._loaded:
    101              empty = ' empty'
    102          return '<%s.%s (%s="%s"%s)>' % (
    103              self.__module__, self.__class__.__name__, self._id_attr,
    104              self._id, empty)
    105
    106 -    def __str__(self):
    107          if self._verbose:
    108              attrs = self.get_attrs()
    109              return '<%s.%s (%s)>' % (
    110                  self.__module__, self.__class__.__name__, attrs)
    111          else:
    112              return self.__repr__()
    113
    114 -    def __getattr__(self, name):
    115          if not self._has_extra_attr and name not in self._attrs:
    116              raise AttributeError(name)
    117          if name == self._id_attr:
    118              return self._id
    119          if not self._loaded:
    120              self.reload()
    121              return self.__dict__.get(name)
    122          return None
    123
    124 -    def _set_attrs(self, kwargs):
    125          _kwargs = copy.copy(kwargs)
    126          for attr in self._attrs:
    127              if attr in _kwargs:
    128                  setattr(self, attr, _kwargs.pop(attr))
    129          if not self._has_extra_attr:
    130              return
    131          for key, value in _kwargs.items():
    132              if not key.startswith('_') and key not in BAD_ATTRS:
    133                  setattr(self, key, value)
    134
    135 -    def _clear_attrs(self):
    136          """
    137          Clear attributes
    138
    139          @rtype: None
    140          """
    141          ret = {}
    142          if not self._loaded:
    143              return
    144          for key in dir(self):
    145              if key.startswith('_'):
    146                  continue
    147              value = getattr(self, key)
    148              if inspect.ismethod(value) or inspect.isfunction(value):
    149                  continue
    150              delattr(self, key)
    151
    152 -    def get_id(self):
    153          """
    154          Query ID of a resource
    155
    156          @return: ID
    157          @rtype: str
    158          """
    159          return self._id
    160
    161 -    def get_attrs(self):
    162          """
    163          Aquire attributes as a dictionary
    164
    165          @return: attributes
    166          @rtype: dict
    167          """
    168          ret = {}
    169          if not self._loaded:
    170              self.reload()
    171              self._loaded = True
    172          for key in dir(self):
    173              if key.startswith('_'):
    174                  continue
    175              value = getattr(self, key)
    176              if inspect.ismethod(value) or inspect.isfunction(value):
    177                  continue
    178              ret[key] = value
    179          return ret
    180
    181 -    def reload(self):
    182          """
    183          (Re)load attributes of a resource
    184
    185          @return: Whether attributes are updated
    186          @rtype: bool
    187          """
    188          x = self._manager.get(getattr(self, self._id_attr))
    189          if x:
    190              self._clear_attrs()
    191              self._set_attrs(x.__dict__)
    192              self._loaded = True
    193              return True
    194          return False
    195
    196 -    def update(self, **kwargs):
    197          """
    198          Update a resource and reload it.
    199          kwargs: attributes and their values to update
    200
    201          @rtype: None
    202          """
    203          json_params = self._attr2json(kwargs)
    204          method = getattr(self._http, self._update_method)
    205          method(utils.join_path(self._url_resource_path, self._id),
    206                 data={self._json_resource_key: json_params})
    207          self.reload()
    208
    209 -    def delete(self):
    210          """
    211          Delete a resource
    212
    213          @rtype: None
    214          """
    215          self._http.delete(utils.join_path(self._url_resource_path, self._id))
    216
    217 -    def wait_for_finished(self, count=10, interval=10):
    218          """
    219          Wait for task finished
    220
    221          @keyword count: Maximum polling time
    222          @type count: int
    223          @keyword interval: Polling interval in seconds
    224          @type interval: int
    225          @rtype: None
    226          """
    227          for i in range(count):
    228              time.sleep(interval)
    229              try:
    230                  self.reload()
    231              except exception.NotFound:
    232                  return
    233              if getattr(self, self._state_attr, None) in self._stable_state:
    234                  return
    235
    236
    237 -class Manager(object):
    238      """Base class for resource managers."""
    239
    240      resource_class = None
    241      service_type = ''
    242      _attr_mapping = []
    243      _has_detail = True
    244      _has_extra_attr = False
    245      _hidden_methods = None
    246      _json_resource_key = ''
    247      _json_resources_key = ''
    248      _id_attr = 'id'
    249      _update_method = 'put'
    250      _url_resource_path = ''
    251      _url_resource_list_path = ''
    252
    253 -    def __init__(self, client, verbose=False, **kwargs):
    254          """
    255          Create a Manager object
    256
    257          kwargs: options
    258
    259          @param client: client object
    260          @type client: yakumo.Client
    261          @keyword verbose: Whether str(Resource) displays all attributes
    262          @type verbose: bool
    263          @return: Manager object
    264          @rtype: yakumo.base.Manager
    265          """
    266          self._client = client
    267          self._session = client._session
    268          self._http = self._session.get_proxy(self.service_type)
    269          self._to_json_mapping = {}
    270          self._to_attr_mapping = {}
    271          self._verbose = verbose
    272          if not self._url_resource_list_path:
    273              self._url_resource_list_path = self._url_resource_path
    274          if self.resource_class is None:
    275              return
    276          mapper.make_mappings(self._attr_mapping,
    277                               self._to_json_mapping,
    278                               self._to_attr_mapping)
    279          if self._hidden_methods is not None:
    280              for method in self._hidden_methods:
    281                  setattr(self, method, self._no_such_api)
    282
    283 -    def _no_such_api(self, *args):
    284          raise exception.NoSuchAPI()
    285
    286 -    def _json2attr(self, json_params):
    287          result = {}
    288          for key, value in json_params.items():
    289              _map = self._to_attr_mapping.get(key)
    290              if _map is not None:
    291                  result[_map['attr']] = _map['mapper'].to_attr(self, value)
    292              elif self._has_extra_attr and \
    293                      not key.startswith('_') and key not in BAD_ATTRS:
    294                  result[key] = value
    295          return result
    296
    297 -    def _attr2json(self, attrs):
    298          result = {}
    299          for key, value in attrs.items():
    300              if value is constant.UNDEF:
    301                  continue
    302              _map = self._to_json_mapping.get(key)
    303              if _map is not None:
    304                  result[_map['json_attr']] = \
    305                      _map['mapper'].to_json(self, value)
    306              elif self._has_extra_attr \
    307                      and not key.startswith('_') and key not in BAD_ATTRS:
    308                  result[key] = value
    309          return result
    310
    311 -    def get_empty(self, id):
    312          """
    313          Create a resource object without attributes
    314
    315          @return: Resource object (empty)
    316          @rtype: yakumo.base.Resource
    317          """
    318          if id is None:
    319              return None
    320          kwargs = {self._id_attr: id}
    321          return self.resource_class(self, **kwargs)
    322
    323 -    def create(self, **kwargs):
    324          """
    325          Create a new resource
    326
    327          kwargs: attributes of the resource
    328
    329          @return: Resource object (empty)
    330          @rtype: yakumo.base.Resource
    331          """
    332          json_params = self._attr2json(kwargs)
    333          ret = self._http.post(self._url_resource_path,
    334                                data={self._json_resource_key: json_params})
    335          attrs = self._json2attr(ret[self._json_resource_key])
    336          return self.get_empty(attrs[self._id_attr])
    337
    338 -    def get(self, id):
    339          """
    340          Aquire an existing resource object
    341
    342          @param id: ID
    343          @type id: str
    344          @return: Resource object
    345          @rtype: yakumo.base.Resource
    346          """
    347          try:
    348              ret = self._http.get(utils.join_path(self._url_resource_path, id))
    349              json_params = ret.get(self._json_resource_key)
    350              attrs = self._json2attr(json_params)
    351              return self.resource_class(self, **attrs)
    352          except exception.NotFound:
    353              raise
    354          except:
    355              return None
    356
    357 -    def _find_gen(self, **kwargs):
    358          if self._has_detail:
    359              ret = self._http.get(self._url_resource_list_path)
    360              for x in ret[self._json_resources_key]:
    361                  attrs = self._json2attr(x)
    362                  for k, v in kwargs.items():
    363                      if attrs.get(k) != v:
    364                          break
    365                  else:
    366                      yield self.resource_class(self, **attrs)
    367          else:
    368              try:
    369                  ret = self._http.get(self._url_resource_list_path)
    370              except:
    371                  return
    372              for x in ret[self._json_resources_key]:
    373                  ret = self.get(x['id'])
    374                  for k, v in kwargs.items():
    375                      if getattr(ret, k, None) != v:
    376                          break
    377                  else:
    378                      yield ret
    379
    380 -    def find(self, **kwargs):
    381          """
    382          Query existing resource object matched the conditions
    383
    384          kwargs is key=value style query conditions.
    385          Returns empty list if no matched resource.
    386
    387          @return: List of Resource object
    388          @rtype: yakumo.base.Resource
    389          """
    390          return list(self._find_gen(**kwargs))
    391
    392 -    def find_one(self, **kwargs):
    393          """
    394          Aquire an existing resource object matched the conditions
    395
    396          kwargs is key=value style query conditions.
    397          Returns None if no matched resource.
    398
    399          @return: Resource object
    400          @rtype: yakumo.base.Resource
    401          """
    402          try:
    403              return self._find_gen(**kwargs).next()
    404          except StopIteration:
    405              return None
    406
    407 -    def list(self):
    408          """
    409          Aquire an existing resource object
    410
    411          @return: List of Resource objects
    412          @rtype: [yakumo.base.Resource]
    413          """
    414          return self.find()
    415
    416
    417 -class SubManager(Manager):
    418      """Base class for sub resource managers."""
    419
    420 -    def __init__(self, parent_resource, *args, **kwargs):
    421          self.parent_resource = parent_resource
    422          try:
    423              self._url_resource_path = \
    424                  self._url_resource_path % self.parent_resource._id
    425          except TypeError:
    426              pass
    427          super(SubManager, self).__init__(
    428              self.parent_resource._manager._client, *args, **kwargs)
    429
    430
    431 -class GlanceV2Resource(Resource):
    432      """Base class for resource managers which don't use _json_resource_key."""
    433
    434 -    def update(self, **kwargs):
    435          """
    436          Update a resource and reload it.
    437          kwargs: attributes and their values to update
    438
    439          @rtype: None
    440          """
    441          json_params = self._attr2json(kwargs)
    442          self._http.put(utils.join_path(self._url_resource_path, self._id),
    443                         data=json_params)
    444
    445
    446 -class GlanceV2Manager(Manager):
    447      """Base class for resource managers which don't use _json_resource_key."""
    448
    449      _has_extra_attr = True
    450
    451 -    def create(self, **kwargs):
    452          """
    453          Create a new resource
    454
    455          kwargs: attributes of the resource
    456
    457          @return: Resource object (empty)
    458          @rtype: yakumo.base.Resource
    459          """
    460          json_params = self._attr2json(kwargs)
    461          ret = self._http.post(self._url_resource_path, data=json_params)
    462          attrs = self._json2attr(ret)
    463          return self.get_empty(attrs[self._id_attr])
    464
    465 -    def get(self, id):
    466          """
    467          Aquire an existing resource object
    468
    469          @param id: ID
    470          @type id: str
    471          @return: Resource object
    472          @rtype: yakumo.base.Resource
    473          """
    474          try:
    475              json_params = self._http.get(
    476                  utils.join_path(self._url_resource_path, id))
    477              attrs = self._json2attr(json_params)
    478              return self.resource_class(self, **attrs)
    479          except exception.NotFound:
    480              raise
    481          except:
    482              return None
    483
    484
    485 -class GlanceV2SubManager(SubManager, GlanceV2Manager):
    486      """Base class for sub resource managers for GlanceV2Manager."""
    487      pass
    488