# 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. """Oslo Versioned Objects helper file. These methods help with the serialization of Cinderlib objects that uses the OVO serialization mechanism, so we remove circular references when doing the JSON serialization of objects (for example in a Volume OVO it has a 'snapshot' field which is a Snapshot OVO that has a 'volume' back reference), piggy back on the OVO's serialization mechanism to add/get additional data we want. """ import functools import json as json_lib import six from cinder.objects import base as cinder_base_ovo from oslo_versionedobjects import base as base_ovo from oslo_versionedobjects import fields as ovo_fields from cinderlib import objects # Variable used to avoid circular references BACKEND_CLASS = None def setup(backend_class): global BACKEND_CLASS BACKEND_CLASS = backend_class # Use custom dehydration methods that prevent maximum recursion errors # due to circular references: # ie: snapshot -> volume -> snapshots -> snapshot base_ovo.VersionedObject.obj_to_primitive = obj_to_primitive cinder_base_ovo.CinderObject.obj_from_primitive = classmethod( obj_from_primitive) fields = base_ovo.obj_fields fields.Object.to_primitive = staticmethod(field_ovo_to_primitive) fields.Field.to_primitive = field_to_primitive fields.List.to_primitive = iterable_to_primitive fields.Set.to_primitive = iterable_to_primitive fields.Dict.to_primitive = dict_to_primitive wrap_to_primitive(fields.FieldType) wrap_to_primitive(fields.DateTime) wrap_to_primitive(fields.IPAddress) def wrap_to_primitive(cls): method = getattr(cls, 'to_primitive') @functools.wraps(method) def to_primitive(obj, attr, value, visited=None): return method(obj, attr, value) setattr(cls, 'to_primitive', staticmethod(to_primitive)) def _set_visited(element, visited): # visited keeps track of elements visited to prevent loops if visited is None: visited = set() # We only care about complex object that can have loops, others are ignored # to prevent us from not serializing simple objects, such as booleans, that # can have the same instance used for multiple fields. if isinstance(element, (ovo_fields.ObjectField, cinder_base_ovo.CinderObject)): visited.add(id(element)) return visited def obj_to_primitive(self, target_version=None, version_manifest=None, visited=None): # No target_version, version_manifest, or changes support visited = _set_visited(self, visited) primitive = {} for name, field in self.fields.items(): if self.obj_attr_is_set(name): value = getattr(self, name) # Skip cycles if id(value) in visited: continue primitive[name] = field.to_primitive(self, name, value, visited) obj_name = self.obj_name() obj = { self._obj_primitive_key('name'): obj_name, self._obj_primitive_key('namespace'): self.OBJ_PROJECT_NAMESPACE, self._obj_primitive_key('version'): self.VERSION, self._obj_primitive_key('data'): primitive } # Piggyback to store our own data cl_obj = getattr(self, '_cl_obj', None) clib_data = cl_obj and cl_obj._to_primitive() if clib_data: obj['cinderlib.data'] = clib_data return obj def obj_from_primitive( cls, primitive, context=None, original_method=cinder_base_ovo.CinderObject.obj_from_primitive): result = original_method(primitive, context) result.cinderlib_data = primitive.get('cinderlib.data') return result def field_ovo_to_primitive(obj, attr, value, visited=None): return value.obj_to_primitive(visited=visited) def field_to_primitive(self, obj, attr, value, visited=None): if value is None: return None return self._type.to_primitive(obj, attr, value, visited) def iterable_to_primitive(self, obj, attr, value, visited=None): visited = _set_visited(self, visited) result = [] for elem in value: if id(elem) in visited: continue _set_visited(elem, visited) r = self._element_type.to_primitive(obj, attr, elem, visited) result.append(r) return result def dict_to_primitive(self, obj, attr, value, visited=None): visited = _set_visited(self, visited) primitive = {} for key, elem in value.items(): if id(elem) in visited: continue _set_visited(elem, visited) primitive[key] = self._element_type.to_primitive( obj, '%s["%s"]' % (attr, key), elem, visited) return primitive def load(json_src, save=False): """Load any json serialized cinderlib object.""" if isinstance(json_src, six.string_types): json_src = json_lib.loads(json_src) if isinstance(json_src, list): return [getattr(objects, obj['class']).load(obj, save) for obj in json_src] return getattr(objects, json_src['class']).load(json_src, save) def json(): """Convert to Json everything we have in this system.""" return [backend.json for backend in BACKEND_CLASS.backends.values()] def jsons(): """Convert to a Json string everything we have in this system.""" return json_lib.dumps(json(), separators=(',', ':')) def dump(): """Convert to Json everything we have in this system.""" return [backend.dump for backend in BACKEND_CLASS.backends.values()] def dumps(): """Convert to a Json string everything we have in this system.""" return json_lib.dumps(dump(), separators=(',', ':'))