Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Status info model #490

Merged
merged 13 commits into from
Jul 25, 2024
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Brewtils Changelog
------
TBD

- Formalized Status Info model and added helper features to track the history of the status changes.
- Added support models for tracking primary replication
- New Models for User, UserToken, Role, and AliasUserMap
- Must upgrade to a minimum version of Beer Garden 3.27.0 to support new authentication models. If authentication is not enabled, upgrade
Expand Down
55 changes: 52 additions & 3 deletions brewtils/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-

import copy
from datetime import datetime
from enum import Enum

import pytz # noqa # not in requirements file
Expand Down Expand Up @@ -256,7 +258,7 @@ def __init__(
self.description = description
self.id = id
self.status = status.upper() if status else None
self.status_info = status_info or {}
self.status_info = status_info if status_info else StatusInfo()
self.queue_type = queue_type
self.queue_info = queue_info or {}
self.icon_name = icon_name
Expand Down Expand Up @@ -433,6 +435,53 @@ def is_different(self, other):
return False


class StatusHistory(BaseModel):
schema = "StatusHistorySchema"

def __init__(self, status=None, heartbeat=None):
self.status = status
self.heartbeat = heartbeat

def __str__(self):
return "%s:%s" % (
self.status,
self.heartbeat,
)

def __repr__(self):
return "<StatusHistory: status=%s, heartbeat=%s>" % (
self.status,
self.heartbeat,
)


class StatusInfo(BaseModel):
schema = "StatusInfoSchema"

def __init__(self, heartbeat=None, history=None):
self.heartbeat = heartbeat
self.history = history or []

def set_status_heartbeat(self, status, max_history=None):

self.heartbeat = datetime.utcnow()
self.history.append(
StatusHistory(status=copy.deepcopy(status), heartbeat=self.heartbeat)
)

if max_history and max_history > 0 and len(self.history) > max_history:
self.history = self.history[(max_history * -1) :]

def __str__(self):
return self.heartbeat

def __repr__(self):
return "<StatusInfo: heartbeat=%s, history=%s>" % (
self.heartbeat,
self.history,
)


class RequestFile(BaseModel):
schema = "RequestFileSchema"

Expand Down Expand Up @@ -1438,7 +1487,7 @@ def __init__(
self.id = id
self.name = name
self.status = status.upper() if status else None
self.status_info = status_info or {}
self.status_info = status_info if status_info else StatusInfo()
self.namespaces = namespaces or []
self.systems = systems or []

Expand Down Expand Up @@ -1498,7 +1547,7 @@ def __init__(
):
self.api = api
self.status = status
self.status_info = status_info or {}
self.status_info = status_info if status_info else StatusInfo()
self.config = config or {}

def __str__(self):
Expand Down
76 changes: 76 additions & 0 deletions brewtils/schema_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class SchemaParser(object):
"AliasUserMapSchema": brewtils.models.AliasUserMap,
"SubscriberSchema": brewtils.models.Subscriber,
"TopicSchema": brewtils.models.Topic,
"StatusInfoSchema": brewtils.models.StatusInfo,
"StatusHistorySchema": brewtils.models.StatusHistory,
"ReplicationSchema": brewtils.models.Replication,
}

Expand Down Expand Up @@ -469,6 +471,40 @@ def parse_topic(cls, topic, from_string=False, **kwargs):
)

@classmethod
def parse_status_info(cls, status_info, from_string=False, **kwargs):
"""Convert raw JSON string or dictionary to a status info model object

Args:
status_info: The raw input
from_string: True if input is a JSON string, False if a dictionary
**kwargs: Additional parameters to be passed to the Schema (e.g. many=True)

Returns:
A StatusInfo object
"""
return cls.parse(
status_info, brewtils.models.StatusInfo, from_string=from_string, **kwargs
)

@classmethod
def parse_status_history(cls, status_history, from_string=False, **kwargs):
"""Convert raw JSON string or dictionary to a status history model object

Args:
status_history: The raw input
from_string: True if input is a JSON string, False if a dictionary
**kwargs: Additional parameters to be passed to the Schema (e.g. many=True)

Returns:
A StatusHistory object
"""
return cls.parse(
status_history,
brewtils.models.StatusHistory,
from_string=from_string,
**kwargs
)
@classmethod
def parse_replication(cls, replication, from_string=False, **kwargs):
"""Convert raw JSON string or dictionary to a replication model object

Expand Down Expand Up @@ -1024,6 +1060,46 @@ def serialize_topic(cls, topic, to_string=True, **kwargs):
**kwargs
)

@classmethod
def serialize_status_info(cls, status_info, to_string=True, **kwargs):
"""Convert a status info model into serialized form

Args:
status_info: The status info object(s) to be serialized
to_string: True to generate a JSON-formatted string, False to generate a
dictionary
**kwargs: Additional parameters to be passed to the Schema (e.g. many=True)

Returns:
Serialized representation of status_info
"""
return cls.serialize(
status_info,
to_string=to_string,
schema_name=brewtils.models.StatusInfo.schema,
**kwargs
)

@classmethod
def serialize_status_history(cls, status_history, to_string=True, **kwargs):
"""Convert a status history model into serialized form

Args:
status_history: The status history object(s) to be serialized
to_string: True to generate a JSON-formatted string, False to generate a
dictionary
**kwargs: Additional parameters to be passed to the Schema (e.g. many=True)

Returns:
Serialized representation of status_history
"""
return cls.serialize(
status_history,
to_string=to_string,
schema_name=brewtils.models.StatusHistory.schema,
**kwargs
)

@classmethod
def serialize_replication(cls, replication, to_string=True, **kwargs):
"""Convert a replication model into serialized form
Expand Down
10 changes: 10 additions & 0 deletions brewtils/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"AliasUserMapSchema",
"SubscriberSchema",
"TopicSchema",
"StatusInfoSchema",
"StatusHistorySchema",
"ReplicationSchema",
]

Expand Down Expand Up @@ -330,8 +332,14 @@ class RequestSchema(RequestTemplateSchema):
target_garden = fields.String(allow_none=True)


class StatusHistorySchema(BaseSchema):
heartbeat = DateTime(allow_none=True, format="epoch", example="1500065932000")
status = fields.Str(allow_none=True)


class StatusInfoSchema(BaseSchema):
heartbeat = DateTime(allow_none=True, format="epoch", example="1500065932000")
history = fields.Nested("StatusHistorySchema", many=True, allow_none=True)


class PatchSchema(BaseSchema):
Expand Down Expand Up @@ -656,6 +664,8 @@ class UserSchema(BaseSchema):
"AliasUserMap": AliasUserMapSchema,
"Subscriber": SubscriberSchema,
"Topic": TopicSchema,
"StatusInfo": StatusInfoSchema,
"StatusHistory": StatusHistorySchema,
"Replication": ReplicationSchema,
# Compatibility for the Job trigger types
"interval": IntervalTriggerSchema,
Expand Down
44 changes: 42 additions & 2 deletions brewtils/test/comparable.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
Resolvable,
Role,
Runner,
StatusHistory,
StatusInfo,
Subscriber,
System,
Topic,
Expand Down Expand Up @@ -68,6 +70,8 @@
"assert_runner_equal",
"assert_subscriber_equal",
"assert_topic_equal",
"assert_status_info_equal",
"assert_status_history_equal",
"assert_replication_equal",
]

Expand Down Expand Up @@ -191,7 +195,6 @@ def _assert_wrapper(obj1, obj2, expected_type=None, do_raise=False, **kwargs):


# These are the 'simple' models - they don't have any nested models as fields
assert_instance_equal = partial(_assert_wrapper, expected_type=Instance)
assert_choices_equal = partial(_assert_wrapper, expected_type=Choices)
assert_patch_equal = partial(_assert_wrapper, expected_type=PatchOperation)
assert_logging_config_equal = partial(_assert_wrapper, expected_type=LoggingConfig)
Expand All @@ -203,12 +206,48 @@ def _assert_wrapper(obj1, obj2, expected_type=None, do_raise=False, **kwargs):
assert_request_file_equal = partial(_assert_wrapper, expected_type=RequestFile)
assert_runner_equal = partial(_assert_wrapper, expected_type=Runner)
assert_resolvable_equal = partial(_assert_wrapper, expected_type=Resolvable)
assert_connection_equal = partial(_assert_wrapper, expected_type=Connection)
assert_subscriber_equal = partial(_assert_wrapper, expected_type=Subscriber)
assert_status_history_equal = partial(_assert_wrapper, expected_type=StatusHistory)


def assert_status_info_equal(obj1, obj2, do_raise=False):
return _assert_wrapper(
obj1,
obj2,
expected_type=StatusInfo,
deep_fields={
"history": partial(assert_status_history_equal, do_raise=True),
},
do_raise=do_raise,
)


def assert_instance_equal(obj1, obj2, do_raise=False):
return _assert_wrapper(
obj1,
obj2,
expected_type=Instance,
deep_fields={"status_info": partial(assert_status_info_equal, do_raise=True)},
do_raise=do_raise,
)


def assert_connection_equal(obj1, obj2, do_raise=False):
return _assert_wrapper(
obj1,
obj2,
expected_type=Connection,
deep_fields={"status_info": partial(assert_status_info_equal, do_raise=True)},
do_raise=do_raise,
)


assert_alias_user_map_equal = partial(_assert_wrapper, expected_type=AliasUserMap)
assert_subscriber_equal = partial(_assert_wrapper, expected_type=Subscriber)
assert_replication_equal = partial(_assert_wrapper, expected_type=Replication)



def assert_command_equal(obj1, obj2, do_raise=False):
return _assert_wrapper(
obj1,
Expand Down Expand Up @@ -424,6 +463,7 @@ def assert_garden_equal(obj1, obj2, do_raise=False):
"systems": partial(assert_system_equal, do_raise=True),
"receiving_connections": partial(assert_connection_equal, do_raise=True),
"publishing_connections": partial(assert_connection_equal, do_raise=True),
"status_info": partial(assert_status_info_equal, do_raise=True),
},
do_raise=do_raise,
)
Expand Down
Loading
Loading