started=None,
last_configured=None,
osdspec_affinity=None,
- last_deployed=None):
+ last_deployed=None,
+ events: Optional[List['OrchestratorEvent']]=None):
+
# Host is at the same granularity as InventoryHost
self.hostname: str = hostname
# Affinity to a certain OSDSpec
self.osdspec_affinity = osdspec_affinity # type: Optional[str]
+ self.events: List[OrchestratorEvent] = events or []
+
def name(self):
return '%s.%s' % (self.daemon_type, self.daemon_id)
if getattr(self, k):
out[k] = getattr(self, k).strftime(DATEFMT)
+ if self.events:
+ out['events'] = [e.to_json() for e in self.events]
+
empty = [k for k, v in out.items() if v is None]
for e in empty:
del out[e]
@handle_type_error
def from_json(cls, data):
c = data.copy()
+ event_strs = c.pop('events', [])
for k in ['last_refresh', 'created', 'started', 'last_deployed',
'last_configured']:
if k in c:
c[k] = datetime.datetime.strptime(c[k], DATEFMT)
- return cls(**c)
+ events = [OrchestratorEvent.from_json(e) for e in event_strs]
+ return cls(events=events, **c)
def __copy__(self):
# feel free to change this:
last_refresh=None,
created=None,
size=0,
- running=0):
+ running=0,
+ events: Optional[List['OrchestratorEvent']]=None):
# Not everyone runs in containers, but enough people do to
# justify having the container_image_id (image hash) and container_image
# (image name)
self.spec: ServiceSpec = spec
+ self.events: List[OrchestratorEvent] = events or []
+
def service_type(self):
return self.spec.service_type
'size': self.size,
'running': self.running,
'last_refresh': self.last_refresh,
- 'created': self.created
+ 'created': self.created,
}
for k in ['last_refresh', 'created']:
if getattr(self, k):
status[k] = getattr(self, k).strftime(DATEFMT)
status = {k: v for (k, v) in status.items() if v is not None}
out['status'] = status
+ if self.events:
+ out['events'] = [e.to_json() for e in self.events]
return out
@classmethod
def from_json(cls, data: dict):
c = data.copy()
status = c.pop('status', {})
+ event_strs = c.pop('events', [])
spec = ServiceSpec.from_json(c)
c_status = status.copy()
for k in ['last_refresh', 'created']:
if k in c_status:
c_status[k] = datetime.datetime.strptime(c_status[k], DATEFMT)
- return cls(spec=spec, **c_status)
+ events = [OrchestratorEvent.from_json(e) for e in event_strs]
+ return cls(spec=spec, events=events, **c_status)
@staticmethod
def yaml_representer(dumper: 'yaml.SafeDumper', data: 'DaemonDescription'):
__slots__ = ()
+class OrchestratorEvent:
+ """
+ Similar to K8s Events.
+
+ Some form of "important" log message attached to something.
+ """
+ INFO = 'INFO'
+ ERROR = 'ERROR'
+ regex_v1 = re.compile(r'^([^ ]+) ([^:]+):([^ ]+) \[([^\]]+)\] "(.*)"$')
+
+ def __init__(self, created: Union[str, datetime.datetime], kind, subject, level, message):
+ if isinstance(created, str):
+ created = datetime.datetime.strptime(created, DATEFMT)
+ self.created: datetime.datetime = created
+
+ assert kind in "service daemon".split()
+ self.kind: str = kind
+
+ # service name, or daemon danem or something
+ self.subject: str = subject
+
+ # Events are not meant for debugging. debugs should end in the log.
+ assert level in "INFO ERROR".split()
+ self.level = level
+
+ self.message: str = message
+
+ __slots__ = ('created', 'kind', 'subject', 'level', 'message')
+
+ def kind_subject(self) -> str:
+ return f'{self.kind}:{self.subject}'
+
+ def to_json(self) -> str:
+ # Make a long list of events readable.
+ created = self.created.strftime(DATEFMT)
+ return f'{created} {self.kind_subject()} [{self.level}] "{self.message}"'
+
+
+ @classmethod
+ @handle_type_error
+ def from_json(cls, data) -> "OrchestratorEvent":
+ """
+ >>> OrchestratorEvent.from_json('''2020-06-10T10:20:25.691255 daemon:crash.ubuntu [INFO] "Deployed crash.ubuntu on host 'ubuntu'"''').to_json()
+ '2020-06-10T10:20:25.691255 daemon:crash.ubuntu [INFO] "Deployed crash.ubuntu on host \\'ubuntu\\'"'
+
+ :param data:
+ :return:
+ """
+ match = cls.regex_v1.match(data)
+ if match:
+ return cls(*match.groups())
+ raise ValueError(f'Unable to match: "{data}"')
+
+
def _mk_orch_methods(cls):
# Needs to be defined outside of for.
# Otherwise meth is always bound to last key