]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/orch: Add OrchestratorEvent class
authorSebastian Wagner <sebastian.wagner@suse.com>
Sat, 6 Jun 2020 22:49:43 +0000 (00:49 +0200)
committerSebastian Wagner <sebastian.wagner@suse.com>
Thu, 16 Jul 2020 08:09:08 +0000 (10:09 +0200)
Signed-off-by: Sebastian Wagner <sebastian.wagner@suse.com>
src/pybind/mgr/orchestrator/__init__.py
src/pybind/mgr/orchestrator/_interface.py

index fa32bc4850d878259e547df22afbdcb783489048..fcb6a1313f2df15b9eddf3e3dc71e143bd10db9c 100644 (file)
@@ -14,5 +14,6 @@ from ._interface import \
     OrchestratorValidationError, OrchestratorError, NoOrchestrator, \
     ServiceDescription, InventoryFilter, HostSpec, \
     DaemonDescription, \
+    OrchestratorEvent, \
     InventoryHost, DeviceLightLoc, \
     UpgradeStatusSpec
index 7ca1438310b0ac47f946a78de02354d1645d51e7..0f0040907307d4f9a8cc63f3eb774434392e4171 100644 (file)
@@ -1222,7 +1222,9 @@ class DaemonDescription(object):
                  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
 
@@ -1262,6 +1264,8 @@ class DaemonDescription(object):
         # 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)
 
@@ -1339,6 +1343,9 @@ class DaemonDescription(object):
             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]
@@ -1348,11 +1355,13 @@ class DaemonDescription(object):
     @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:
@@ -1387,7 +1396,8 @@ class ServiceDescription(object):
                  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)
@@ -1414,6 +1424,8 @@ class ServiceDescription(object):
 
         self.spec: ServiceSpec = spec
 
+        self.events: List[OrchestratorEvent] = events or []
+
     def service_type(self):
         return self.spec.service_type
 
@@ -1430,13 +1442,15 @@ class ServiceDescription(object):
             '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
@@ -1444,13 +1458,15 @@ class ServiceDescription(object):
     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'):
@@ -1558,6 +1574,60 @@ class DeviceLightLoc(namedtuple('DeviceLightLoc', ['host', 'dev', 'path'])):
     __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