Fix serialization of lists in data_models.to_dict
data_models.to_dict didn't convert values inside lists. When serializing TLSContainer, the intermediates list that contains bytes types wasn't converted properly. to_dict now converts the items of a list recursively. Add a test that checks if TLSContainer.to_dict is serializable. Story 2009310 Task 43699 Change-Id: I3859c7fcefc89e91fdaecd139143e6a74d8b3c1b
This commit is contained in:
@@ -26,6 +26,55 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class BaseDataModel(object):
|
class BaseDataModel(object):
|
||||||
|
def _to_dict(self, value, calling_classes=None, recurse=False):
|
||||||
|
calling_classes = calling_classes or []
|
||||||
|
# We need to have json convertible data for storing it in
|
||||||
|
# persistence jobboard backend.
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
ret = value.isoformat()
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
ret = value.decode()
|
||||||
|
elif recurse:
|
||||||
|
if isinstance(value, list):
|
||||||
|
ret = []
|
||||||
|
for item in value:
|
||||||
|
if isinstance(item, BaseDataModel):
|
||||||
|
if type(self) not in calling_classes:
|
||||||
|
ret.append(
|
||||||
|
item.to_dict(calling_classes=(
|
||||||
|
calling_classes + [type(self)]),
|
||||||
|
recurse=recurse))
|
||||||
|
else:
|
||||||
|
# TODO(rm_work): Is the idea that if this list
|
||||||
|
# contains ANY BaseDataModel, that all of them
|
||||||
|
# are data models, and we may as well quit?
|
||||||
|
# Or, were we supposed to append a `None` for
|
||||||
|
# each one? I assume the former?
|
||||||
|
ret = None
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
ret.append(
|
||||||
|
self._to_dict(item,
|
||||||
|
calling_classes=calling_classes,
|
||||||
|
recurse=recurse))
|
||||||
|
elif isinstance(value, BaseDataModel):
|
||||||
|
if type(self) not in calling_classes:
|
||||||
|
ret = value.to_dict(
|
||||||
|
calling_classes=calling_classes + [type(self)],
|
||||||
|
recurse=recurse)
|
||||||
|
else:
|
||||||
|
ret = None
|
||||||
|
else:
|
||||||
|
ret = value
|
||||||
|
else:
|
||||||
|
if isinstance(value, BaseDataModel):
|
||||||
|
ret = None
|
||||||
|
elif isinstance(value, list):
|
||||||
|
ret = []
|
||||||
|
else:
|
||||||
|
ret = value
|
||||||
|
return ret
|
||||||
|
|
||||||
def to_dict(self, calling_classes=None, recurse=False, **kwargs):
|
def to_dict(self, calling_classes=None, recurse=False, **kwargs):
|
||||||
"""Converts a data model to a dictionary."""
|
"""Converts a data model to a dictionary."""
|
||||||
calling_classes = calling_classes or []
|
calling_classes = calling_classes or []
|
||||||
@@ -37,50 +86,8 @@ class BaseDataModel(object):
|
|||||||
# tags is a list, it doesn't need recurse
|
# tags is a list, it doesn't need recurse
|
||||||
ret[attr] = value
|
ret[attr] = value
|
||||||
continue
|
continue
|
||||||
# We need to have json convertible data for storing it in
|
ret[attr] = self._to_dict(value, calling_classes=calling_classes,
|
||||||
# persistence jobboard backend.
|
recurse=recurse)
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
ret[attr] = value.isoformat()
|
|
||||||
continue
|
|
||||||
if isinstance(value, bytes):
|
|
||||||
ret[attr] = value.decode()
|
|
||||||
continue
|
|
||||||
if recurse:
|
|
||||||
if isinstance(getattr(self, attr), list):
|
|
||||||
ret[attr] = []
|
|
||||||
for item in value:
|
|
||||||
if isinstance(item, BaseDataModel):
|
|
||||||
if type(self) not in calling_classes:
|
|
||||||
ret[attr].append(
|
|
||||||
item.to_dict(calling_classes=(
|
|
||||||
calling_classes + [type(self)]),
|
|
||||||
recurse=recurse))
|
|
||||||
else:
|
|
||||||
# TODO(rm_work): Is the idea that if this list
|
|
||||||
# contains ANY BaseDataModel, that all of them
|
|
||||||
# are data models, and we may as well quit?
|
|
||||||
# Or, were we supposed to append a `None` for
|
|
||||||
# each one? I assume the former?
|
|
||||||
ret[attr] = None
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
ret[attr].append(item)
|
|
||||||
elif isinstance(getattr(self, attr), BaseDataModel):
|
|
||||||
if type(self) not in calling_classes:
|
|
||||||
ret[attr] = value.to_dict(
|
|
||||||
calling_classes=calling_classes + [type(self)],
|
|
||||||
recurse=recurse)
|
|
||||||
else:
|
|
||||||
ret[attr] = None
|
|
||||||
else:
|
|
||||||
ret[attr] = value
|
|
||||||
else:
|
|
||||||
if isinstance(getattr(self, attr), BaseDataModel):
|
|
||||||
ret[attr] = None
|
|
||||||
elif isinstance(getattr(self, attr), list):
|
|
||||||
ret[attr] = []
|
|
||||||
else:
|
|
||||||
ret[attr] = value
|
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
@@ -43,6 +44,7 @@ class TestDataModels(base.TestCase):
|
|||||||
self.COMPUTE_ID = uuidutils.generate_uuid()
|
self.COMPUTE_ID = uuidutils.generate_uuid()
|
||||||
self.IMAGE_ID = uuidutils.generate_uuid()
|
self.IMAGE_ID = uuidutils.generate_uuid()
|
||||||
self.COMPUTE_FLAVOR = uuidutils.generate_uuid()
|
self.COMPUTE_FLAVOR = uuidutils.generate_uuid()
|
||||||
|
self.TLS_CONTAINER_ID = uuidutils.generate_uuid()
|
||||||
|
|
||||||
self.LB_obj = data_models.LoadBalancer(
|
self.LB_obj = data_models.LoadBalancer(
|
||||||
id=self.LB_ID,
|
id=self.LB_ID,
|
||||||
@@ -533,3 +535,21 @@ class TestDataModels(base.TestCase):
|
|||||||
|
|
||||||
# test incrementing an incompatible object
|
# test incrementing an incompatible object
|
||||||
self.assertRaises(TypeError, stats_1.__iadd__, "boom")
|
self.assertRaises(TypeError, stats_1.__iadd__, "boom")
|
||||||
|
|
||||||
|
def test_TLSContainer_serialization(self):
|
||||||
|
tls_container = data_models.TLSContainer(
|
||||||
|
id=self.TLS_CONTAINER_ID,
|
||||||
|
primary_cn='fake_cn',
|
||||||
|
certificate=b'certificate_buffer1',
|
||||||
|
private_key=b'private_key1',
|
||||||
|
passphrase=b'passphrase1',
|
||||||
|
intermediates=[
|
||||||
|
b'intermediate_buffer1',
|
||||||
|
b'intermediate_buffer2',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tls_container_dict = tls_container.to_dict(recurse=True)
|
||||||
|
json_buffer = json.dumps(tls_container_dict)
|
||||||
|
json_doc = json.loads(json_buffer)
|
||||||
|
|
||||||
|
self.assertEqual(tls_container_dict, json_doc)
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fix a serialization issue when using TLSContainer with amphorav2 driver
|
||||||
|
with persistence, a list of bytes type in the data model was not correctly
|
||||||
|
converted to serializable data.
|
Reference in New Issue
Block a user