Improve string representation of DB models
This replaces the default "<XYClass object at 0xdeadbeeff00>" repr() for DB objects, which is pretty useless. This change makes it easier to identify DB objects in debuggers (and logs, if objects get logged). __repr__() and __str__() have slightly different purposes. The purpose of __repr__() is to return a string that uniquely identifies a single object. __str__(), on the other hand, should produce a human readable string. Another thing that is worth to note is that str() will call __repr__() if __str__() is not implemented, repr() will always call __repr__(). More details can be found under [1]. By implementing a generic __repr__() in the base class there should be no need to override it in the models classes. However, one might not be interested in seeing all the object's attributes in a string representation (which is what the generic __repr__() would do) or certain sensitive information should be hidden from users. In those cases it makes sense to implement __str__() for certain classes specifically. Release notes were taken from a follow-up change [2]. [1]: https://stackoverflow.com/a/2626364/6265131 [2]: I9b9eb3158b099cf04d788692fc9e5efcdfebf93e Change-Id: I4c218793948b0ed7ce0c143d6c794516fdeb224f (cherry picked from commit ff6d0c5280fbdbe8f5361469fb88448fa650f365)
This commit is contained in:
parent
a237182303
commit
2cb52fe60a
@ -132,6 +132,13 @@ class OctaviaBase(models.ModelBase):
|
||||
query = query.join(getattr(model, k)).filter_by(**v)
|
||||
return query
|
||||
|
||||
def __repr__(self):
|
||||
params = sorted(
|
||||
(k, getattr(self, k)) for k in self.__mapper__.columns.keys()
|
||||
)
|
||||
params = ", ".join(f"{k}={v!r}" for k, v in params)
|
||||
return f"{self.__class__.__name__}({params})"
|
||||
|
||||
|
||||
class LookupTableMixin(object):
|
||||
"""Mixin to add to classes that are lookup tables."""
|
||||
|
@ -237,6 +237,15 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
||||
primaryjoin='and_(foreign(Tags.resource_id)==Member.id)'
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return (f"Member(id={self.id!r}, name={self.name!r}, "
|
||||
f"project_id={self.project_id!r}, "
|
||||
f"provisioning_status={self.provisioning_status!r}, "
|
||||
f"ip_address={self.ip_address!r}, "
|
||||
f"protocol_port={self.protocol_port!r}, "
|
||||
f"operating_status={self.operating_status!r}, "
|
||||
f"weight={self.weight!r})")
|
||||
|
||||
|
||||
class HealthMonitor(base_models.BASE, base_models.IdMixin,
|
||||
base_models.ProjectMixin, models.TimestampMixin,
|
||||
@ -293,6 +302,11 @@ class HealthMonitor(base_models.BASE, base_models.IdMixin,
|
||||
http_version = sa.Column(sa.Float, nullable=True)
|
||||
domain_name = sa.Column(sa.String(255), nullable=True)
|
||||
|
||||
def __str__(self):
|
||||
return (f"HealthMonitor(id={self.id!r}, name={self.name!r}, "
|
||||
f"project_id={self.project_id!r}, type={self.type!r}, "
|
||||
f"enabled={self.enabled!r})")
|
||||
|
||||
|
||||
class Pool(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
||||
models.TimestampMixin, base_models.NameMixin, base_models.TagMixin):
|
||||
@ -372,6 +386,14 @@ class Pool(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
||||
_l_ids.append(li.id)
|
||||
return _listeners
|
||||
|
||||
def __str__(self):
|
||||
return (f"Pool(id={self.id!r}, name={self.name!r}, "
|
||||
f"project_id={self.project_id!r}, "
|
||||
f"provisioning_status={self.provisioning_status!r}, "
|
||||
f"protocol={self.protocol!r}, "
|
||||
f"lb_algorithm={self.lb_algorithm!r}, "
|
||||
f"enabled={self.enabled!r})")
|
||||
|
||||
|
||||
class LoadBalancer(base_models.BASE, base_models.IdMixin,
|
||||
base_models.ProjectMixin, models.TimestampMixin,
|
||||
@ -425,6 +447,13 @@ class LoadBalancer(base_models.BASE, base_models.IdMixin,
|
||||
name="fk_load_balancer_availability_zone_name"),
|
||||
nullable=True)
|
||||
|
||||
def __str__(self):
|
||||
return (f"LoadBalancer(id={self.id!r}, name={self.name!r}, "
|
||||
f"project_id={self.project_id!r}, vip={self.vip!r}, "
|
||||
f"provisioning_status={self.provisioning_status!r}, "
|
||||
f"operating_status={self.operating_status!r}, "
|
||||
f"provider={self.provider!r})")
|
||||
|
||||
|
||||
class VRRPGroup(base_models.BASE):
|
||||
|
||||
@ -574,6 +603,13 @@ class Listener(base_models.BASE, base_models.IdMixin,
|
||||
'ListenerCidr', cascade='all,delete-orphan',
|
||||
uselist=True, backref=orm.backref('listener', uselist=False))
|
||||
|
||||
def __str__(self):
|
||||
return (f"Listener(id={self.id!r}, "
|
||||
f"default_pool={self.default_pool!r}, name={self.name!r}, "
|
||||
f"project_id={self.project_id!r}, protocol={self.protocol!r}, "
|
||||
f"protocol_port={self.protocol_port!r}, "
|
||||
f"enabled={self.enabled!r})")
|
||||
|
||||
|
||||
class SNI(base_models.BASE):
|
||||
|
||||
@ -630,6 +666,12 @@ class Amphora(base_models.BASE, base_models.IdMixin, models.TimestampMixin):
|
||||
back_populates='amphorae')
|
||||
compute_flavor = sa.Column(sa.String(255), nullable=True)
|
||||
|
||||
def __str__(self):
|
||||
return (f"Amphora(id={self.id!r}, load_balancer_id="
|
||||
f"{self.load_balancer_id!r}, status={self.status!r}, "
|
||||
f"role={self.role!r}, lb_network_ip={self.lb_network_ip!r}, "
|
||||
f"vrrp_ip={self.vrrp_ip!r})")
|
||||
|
||||
|
||||
class AmphoraHealth(base_models.BASE):
|
||||
__data_model__ = data_models.AmphoraHealth
|
||||
@ -692,6 +734,12 @@ class L7Rule(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
||||
primaryjoin='and_(foreign(Tags.resource_id)==L7Rule.id)'
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return (f"L7Rule(id={self.id!r}, project_id={self.project_id!r}, "
|
||||
f"provisioning_status={self.provisioning_status!r}, "
|
||||
f"type={self.type!r}, key={self.key!r}, value={self.value!r}, "
|
||||
f"invert={self.invert!r}, enabled={self.enabled!r})")
|
||||
|
||||
|
||||
class L7Policy(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
||||
models.TimestampMixin, base_models.NameMixin,
|
||||
@ -751,6 +799,13 @@ class L7Policy(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
||||
primaryjoin='and_(foreign(Tags.resource_id)==L7Policy.id)'
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return (f"L7Policy(id={self.id!r}, name={self.name!r}, "
|
||||
f"project_id={self.project_id!r}, "
|
||||
f"provisioning_status={self.provisioning_status!r}, "
|
||||
f"action={self.action!r}, position={self.position!r}, "
|
||||
f"enabled={self.enabled!r})")
|
||||
|
||||
|
||||
class Quotas(base_models.BASE):
|
||||
|
||||
@ -776,6 +831,14 @@ class Quotas(base_models.BASE):
|
||||
in_use_l7policy = sa.Column(sa.Integer(), nullable=True)
|
||||
in_use_l7rule = sa.Column(sa.Integer(), nullable=True)
|
||||
|
||||
def __str__(self):
|
||||
return (f"Quotas(project_id={self.project_id!r}, "
|
||||
f"load_balancer={self.load_balancer!r}, "
|
||||
f"listener={self.listener!r}, pool={self.pool!r}, "
|
||||
f"health_monitor={self.health_monitor!r}, "
|
||||
f"member={self.member!r}, l7policy={self.l7policy!r}, "
|
||||
f"l7rule={self.l7rule!r})")
|
||||
|
||||
|
||||
class FlavorProfile(base_models.BASE, base_models.IdMixin,
|
||||
base_models.NameMixin):
|
||||
|
@ -197,11 +197,21 @@ class ModelTestMixin(object):
|
||||
kwargs = {'listener_id': listener_id, 'cidr': cidr}
|
||||
return self._insert(session, models.ListenerCidr, kwargs)
|
||||
|
||||
def create_quotas(self, session, **overrides):
|
||||
kwargs = {"project_id": self.FAKE_UUID_1}
|
||||
kwargs.update(overrides)
|
||||
return self._insert(session, models.Quotas, kwargs)
|
||||
|
||||
|
||||
class PoolModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
pool = self.create_pool(self.session)
|
||||
self.assertEqual(f"Pool(id={pool.id!r}, name=None, "
|
||||
f"project_id={pool.project_id!r}, "
|
||||
f"provisioning_status='ACTIVE', protocol='HTTP', "
|
||||
f"lb_algorithm='LEAST_CONNECTIONS', enabled=True)",
|
||||
str(pool))
|
||||
|
||||
self.assertIsNotNone(pool.created_at)
|
||||
self.assertIsNone(pool.updated_at)
|
||||
@ -276,6 +286,12 @@ class MemberModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
member = self.create_member(self.session, self.pool.id)
|
||||
self.assertEqual(f"Member(id={member.id!r}, name=None, "
|
||||
f"project_id={member.project_id!r}, "
|
||||
f"provisioning_status='ACTIVE', "
|
||||
f"ip_address='10.0.0.1', protocol_port=80, "
|
||||
f"operating_status='ONLINE', weight=None)",
|
||||
str(member))
|
||||
|
||||
self.assertIsNotNone(member.created_at)
|
||||
self.assertIsNone(member.updated_at)
|
||||
@ -321,7 +337,11 @@ class SessionPersistenceModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
self.pool = self.create_pool(self.session)
|
||||
|
||||
def test_create(self):
|
||||
self.create_session_persistence(self.session, self.pool.id)
|
||||
obj = self.create_session_persistence(self.session, self.pool.id)
|
||||
self.assertEqual(f"SessionPersistence(cookie_name='cookie_name', "
|
||||
f"persistence_granularity=None, "
|
||||
f"persistence_timeout=None, pool_id={obj.pool_id!r}, "
|
||||
f"type='HTTP_COOKIE')", str(obj))
|
||||
|
||||
def test_update(self):
|
||||
session_persistence = self.create_session_persistence(self.session,
|
||||
@ -353,6 +373,10 @@ class ListenerModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
listener = self.create_listener(self.session)
|
||||
self.assertEqual(f"Listener(id={listener.id!r}, default_pool=None, "
|
||||
f"name=None, project_id={listener.project_id!r}, "
|
||||
f"protocol='HTTP', protocol_port=80, enabled=True)",
|
||||
str(listener))
|
||||
|
||||
self.assertIsNotNone(listener.created_at)
|
||||
self.assertIsNone(listener.updated_at)
|
||||
@ -454,8 +478,12 @@ class ListenerStatisticsModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
self.amphora = self.create_amphora(self.session)
|
||||
|
||||
def test_create(self):
|
||||
self.create_listener_statistics(self.session, self.listener.id,
|
||||
self.amphora.id)
|
||||
obj = self.create_listener_statistics(self.session, self.listener.id,
|
||||
self.amphora.id)
|
||||
self.assertEqual(f"ListenerStatistics(active_connections=0, "
|
||||
f"amphora_id={obj.amphora_id!r}, bytes_in=0, "
|
||||
f"bytes_out=0, listener_id={obj.listener_id!r}, "
|
||||
f"request_errors=0, total_connections=0)", str(obj))
|
||||
|
||||
def test_create_with_negative_int(self):
|
||||
overrides = {'bytes_in': -1}
|
||||
@ -490,7 +518,10 @@ class HealthMonitorModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
self.pool = self.create_pool(self.session)
|
||||
|
||||
def test_create(self):
|
||||
self.create_health_monitor(self.session, self.pool.id)
|
||||
obj = self.create_health_monitor(self.session, self.pool.id)
|
||||
self.assertEqual(f"HealthMonitor(id={obj.id!r}, name=None, "
|
||||
f"project_id={obj.project_id!r}, type='HTTP', "
|
||||
f"enabled=True)", str(obj))
|
||||
|
||||
def test_update(self):
|
||||
health_monitor = self.create_health_monitor(self.session, self.pool.id)
|
||||
@ -524,6 +555,11 @@ class LoadBalancerModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
load_balancer = self.create_load_balancer(self.session)
|
||||
self.assertEqual(f"LoadBalancer(id={load_balancer.id!r}, name=None, "
|
||||
f"project_id={load_balancer.project_id!r}, "
|
||||
f"vip=None, provisioning_status='ACTIVE', "
|
||||
f"operating_status='ONLINE', provider=None)",
|
||||
str(load_balancer))
|
||||
|
||||
self.assertIsNotNone(load_balancer.created_at)
|
||||
self.assertIsNone(load_balancer.updated_at)
|
||||
@ -582,7 +618,11 @@ class VipModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
self.load_balancer = self.create_load_balancer(self.session)
|
||||
|
||||
def test_create(self):
|
||||
self.create_vip(self.session, self.load_balancer.id)
|
||||
obj = self.create_vip(self.session, self.load_balancer.id)
|
||||
self.assertEqual(f"Vip(ip_address=None, "
|
||||
f"load_balancer_id={obj.load_balancer_id!r}, "
|
||||
f"network_id=None, octavia_owned=None, port_id=None, "
|
||||
f"qos_policy_id=None, subnet_id=None)", str(obj))
|
||||
|
||||
def test_update(self):
|
||||
vip = self.create_vip(self.session, self.load_balancer.id)
|
||||
@ -616,7 +656,11 @@ class SNIModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
self.listener = self.create_listener(self.session)
|
||||
|
||||
def test_create(self):
|
||||
self.create_sni(self.session, listener_id=self.listener.id)
|
||||
obj = self.create_sni(self.session, listener_id=self.listener.id)
|
||||
self.assertEqual(f"SNI(listener_id={obj.listener_id!r}, "
|
||||
f"position=None, "
|
||||
f"tls_container_id={obj.tls_container_id!r})",
|
||||
str(obj))
|
||||
|
||||
def test_update(self):
|
||||
sni = self.create_sni(self.session, listener_id=self.listener.id)
|
||||
@ -649,7 +693,11 @@ class AmphoraModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
self.load_balancer = self.create_load_balancer(self.session)
|
||||
|
||||
def test_create(self):
|
||||
self.create_amphora(self.session)
|
||||
obj = self.create_amphora(self.session)
|
||||
self.assertEqual(f"Amphora(id={obj.id!r}, load_balancer_id=None, "
|
||||
f"status='ACTIVE', role=None, "
|
||||
f"lb_network_ip='10.0.0.1', vrrp_ip='10.0.0.1')",
|
||||
str(obj))
|
||||
|
||||
def test_update(self):
|
||||
amphora = self.create_amphora(
|
||||
@ -684,7 +732,10 @@ class AmphoraHealthModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
self.amphora = self.create_amphora(self.session)
|
||||
|
||||
def test_create(self):
|
||||
self.create_amphora_health(self.session)
|
||||
obj = self.create_amphora_health(self.session)
|
||||
self.assertEqual(f"AmphoraHealth(amphora_id={obj.amphora_id!r}, "
|
||||
f"busy=True, last_update={obj.last_update!r})",
|
||||
str(obj))
|
||||
|
||||
def test_update(self):
|
||||
amphora_health = self.create_amphora_health(self.session)
|
||||
@ -715,6 +766,10 @@ class L7PolicyModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
l7policy = self.create_l7policy(self.session, self.listener.id)
|
||||
self.assertEqual(f"L7Policy(id={l7policy.id!r}, name=None, "
|
||||
f"project_id=None, provisioning_status='ACTIVE', "
|
||||
f"action='REJECT', position=1, enabled=True)",
|
||||
str(l7policy))
|
||||
self.assertIsInstance(l7policy, models.L7Policy)
|
||||
|
||||
def test_update(self):
|
||||
@ -836,6 +891,10 @@ class L7RuleModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
l7rule = self.create_l7rule(self.session, self.l7policy.id)
|
||||
self.assertEqual(f"L7Rule(id={l7rule.id!r}, project_id=None, "
|
||||
f"provisioning_status='ACTIVE', type='PATH', "
|
||||
f"key=None, value='/api', invert=False, "
|
||||
f"enabled=True)", str(l7rule))
|
||||
self.assertIsInstance(l7rule, models.L7Rule)
|
||||
|
||||
def test_update(self):
|
||||
@ -1757,6 +1816,9 @@ class FlavorModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
flavor = self.create_flavor(self.session, self.profile.id)
|
||||
self.assertEqual(f"Flavor(description='fake flavor', enabled=True, "
|
||||
f"flavor_profile_id={flavor.flavor_profile_id!r}, "
|
||||
f"id={flavor.id!r}, name='fake_flavor')", str(flavor))
|
||||
self.assertIsNotNone(flavor.id)
|
||||
|
||||
def test_delete(self):
|
||||
@ -1776,6 +1838,9 @@ class FlavorProfileModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
fp = self.create_flavor_profile(self.session)
|
||||
self.assertEqual(f"FlavorProfile(flavor_data={fp.flavor_data!r}, "
|
||||
f"id={fp.id!r}, name='fake_profile', "
|
||||
f"provider_name='fake_provider')", str(fp))
|
||||
self.assertIsNotNone(fp.id)
|
||||
|
||||
def test_delete(self):
|
||||
@ -1789,3 +1854,15 @@ class FlavorProfileModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
new_fp = self.session.query(
|
||||
models.FlavorProfile).filter_by(id=id).first()
|
||||
self.assertIsNone(new_fp)
|
||||
|
||||
|
||||
class QuotasModelTest(base.OctaviaDBTestBase, ModelTestMixin):
|
||||
|
||||
def test_create(self):
|
||||
obj = self.create_quotas(self.session, load_balancer=1, listener=2,
|
||||
pool=3, health_monitor=4, member=5,
|
||||
l7policy=6, l7rule=8)
|
||||
self.assertEqual(f"Quotas(project_id={obj.project_id!r}, "
|
||||
f"load_balancer=1, listener=2, pool=3, "
|
||||
f"health_monitor=4, member=5, l7policy=6, l7rule=8)",
|
||||
str(obj))
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
other:
|
||||
- |
|
||||
The string representation of data base model objects has been improved.
|
||||
Calling str() on them will return a certain subset of fields and calling
|
||||
repr() on them will return all fields. This is helpful for debugging, but
|
||||
it may also change some of the log messages that Octavia emits.
|
Loading…
x
Reference in New Issue
Block a user