Martin Sivák has uploaded a new change for review.

Change subject: Document the Agent FSM using docstrings and autogenerate the 
graphviz sources
......................................................................

Document the Agent FSM using docstrings and autogenerate the graphviz sources

This adds two special lines to state class docstrings:

:transition[ destination]: description
- describes transition from current class to destination
- it is treated as staying in the current state when
  destination is not provided

:transitions_from StateClass:
- used to support inheritance, copies all transitions
  from StateClass to the current class

This patch also adds a script that reads the state_machine.py and
states.py modules and generates the fill graphviz source for the
state machine.

make fsm then generates the new source and png file with the state
machine.

Change-Id: Ie6de8b20a8908aa0906404283829775b5487a850
Signed-off-by: Martin Sivak <[email protected]>
(cherry picked from commit 21a18395a2b9d8724a3712910e20efaed78ae3ac)
---
M Makefile.am
A build/build-aux/gen_gv.py
M doc/agent-fsm.gv
M ovirt_hosted_engine_ha/agent/state_machine.py
M ovirt_hosted_engine_ha/agent/states.py
5 files changed, 258 insertions(+), 31 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-hosted-engine-ha 
refs/changes/57/24857/1

diff --git a/Makefile.am b/Makefile.am
index 697fb61..72c2aed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -89,3 +89,8 @@
        sed '/^$$/d' "$(srcdir)/m4/.gitignore" | while read f; do \
                rm -f $(srcdir)/m4/$$f; \
        done
+
+fsm:
+       PYTHONPATH=. python build/build-aux/gen_gv.py > doc/agent-fsm.gv
+       dot doc/agent-fsm.gv -Tpng -o doc/agent-fsm.png
+
diff --git a/build/build-aux/gen_gv.py b/build/build-aux/gen_gv.py
new file mode 100644
index 0000000..a72cd6e
--- /dev/null
+++ b/build/build-aux/gen_gv.py
@@ -0,0 +1,75 @@
+__author__ = 'msivak'
+import ovirt_hosted_engine_ha.agent.states
+import ovirt_hosted_engine_ha.agent.state_machine
+import re
+import inspect
+
+TRANSITION_RULE = re.compile(r":transition( (?P<dest>[^ -]*))?:(?P<doc>.*)")
+
+def process_cls(cls, rules, copy_rules):
+    cls_name = cls.__name__
+    cls_rules = {}
+    if cls.__doc__ is None:
+        return
+
+    for raw in cls.__doc__.split("\n"):
+        line = raw.strip()
+        if not line.startswith(":transition"):
+            continue
+        elif line.startswith(":transitions_from "):
+            copy_from = line.split(" ")[1][:-1]
+            copy_rules.append((copy_from, cls_name))
+            print "// copy rule %s -> %s" % (copy_from, cls_name)
+        else:
+            m = TRANSITION_RULE.match(line)
+            if m is None:
+                print "// no match", line
+                continue
+            dest = m.group("dest")
+            if dest is None:
+                dest = cls_name
+            cls_rules[dest] = m.group("doc").strip()
+
+    if cls_rules:
+        rules[cls_name] = cls_rules
+
+def get_rules(modules):
+    rules = {}
+    copy_rules = []
+    for module in modules:
+        print "// ", module
+        clss = inspect.getmembers(module, inspect.isclass)
+        for _name, cls in clss:
+            print "// ", cls
+            if not hasattr(cls, "consume"):
+                print "// skip"
+                continue
+            process_cls(cls, rules, copy_rules)
+    for src, dest in copy_rules:
+        rules[dest].update(rules[src])
+    return rules
+
+if __name__ == "__main__":
+    rules = get_rules([ovirt_hosted_engine_ha.agent.state_machine,
+                       ovirt_hosted_engine_ha.agent.states])
+    print """/**
+ * This file contains the source for a graphviz FSM diagram of the HA agent
+ * state machine.  To create an image, fsm.png in this case, run the following:
+ *
+ *   dot agent-fsm.gv -Tpng -o fsm.png
+ *
+ * A copy of the latest diagram should be available at:
+ *
+ *   http://www.ovirt.org/Features/Self_Hosted_Engine#Agent_State_Diagram
+ */
+
+digraph finite_state_machine {
+ranksep = 0.5;
+node [shape = doublecircle]; StartState;
+node [shape = circle];""", " ".join(s for s in rules.iterkeys() if s != 
"StartState"), ";"
+
+    for src, v in rules.iteritems():
+        for dest, doc in v.iteritems():
+            print '%s -> %s [ label = "%s" ];' % (src, dest, doc)
+
+    print "}"
diff --git a/doc/agent-fsm.gv b/doc/agent-fsm.gv
index 8100c0d..0182885 100644
--- a/doc/agent-fsm.gv
+++ b/doc/agent-fsm.gv
@@ -1,3 +1,34 @@
+//  <module 'ovirt_hosted_engine_ha.agent.state_machine' from 
'/home/msivak/Work/ovirt-hosted-engine-ha/ovirt_hosted_engine_ha/agent/state_machine.pyc'>
+//  <class 'ovirt_hosted_engine_ha.lib.fsm.machine.BaseFSM'>
+// skip
+//  <class 'ovirt_hosted_engine_ha.lib.fsm.machine.BaseState'>
+//  <class 'ovirt_hosted_engine_ha.agent.state_machine.EngineStateMachine'>
+// skip
+//  <class 'ovirt_hosted_engine_ha.agent.state_data.HostedEngineData'>
+// skip
+//  <class 'ovirt_hosted_engine_ha.agent.states.ReinitializeFSM'>
+//  <class 'ovirt_hosted_engine_ha.agent.state_machine.StartState'>
+//  <class 'ovirt_hosted_engine_ha.agent.state_data.StatsData'>
+// skip
+//  <module 'ovirt_hosted_engine_ha.agent.states' from 
'/home/msivak/Work/ovirt-hosted-engine-ha/ovirt_hosted_engine_ha/agent/states.pyc'>
+//  <class 'ovirt_hosted_engine_ha.lib.fsm.machine.BaseFSM'>
+// skip
+//  <class 'ovirt_hosted_engine_ha.lib.fsm.machine.BaseState'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineDown'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineForceStop'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineMigratingAway'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineStart'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineState'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineStop'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineUnexpectedlyDown'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineUp'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.EngineUpBadHealth'>
+// copy rule EngineUp -> EngineUpBadHealth
+//  <class 'ovirt_hosted_engine_ha.agent.states.GlobalMaintenance'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.LocalMaintenance'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.LocalMaintenanceMigrateVm'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.ReinitializeFSM'>
+//  <class 'ovirt_hosted_engine_ha.agent.states.UnknownLocalVmState'>
 /**
  * This file contains the source for a graphviz FSM diagram of the HA agent
  * state machine.  To create an image, fsm.png in this case, run the following:
@@ -10,35 +41,74 @@
  */
 
 digraph finite_state_machine {
-    ranksep = 0.3;
-    node [shape = doublecircle]; ENTRY;
-    node [shape = circle]; OFF START ON STOP MIGRATE;
-
-    ENTRY -> OFF [ label = "VM down locally" ];
-    ENTRY -> ON [ label = "VM up  \nlocally  " ];
-
-    OFF -> OFF [ label = "VM down locally" ];
-    OFF -> ON [ label = "VM unexpectedly  \nrunning locally  " ];
-    OFF -> START [ label = "VM down  \nglobally, host has  \nhighest score  " 
];
-    OFF -> MAINTENANCE [ label = "Maintenance\nmode enabled" ];
-
-    START -> OFF [ label = "VM startup  \nfailed  " ];
-    START -> ON [ label = "VM powered on" ];
-
-    ON -> ON [ label = "VM up locally" ];
-    ON -> OFF [ label = "VM died  \nunexpectedly  " ];
-    ON -> MIGRATE [ label = "VM up locally,  \nother host has  \nmuch better 
score  " ];
-    ON -> STOP [ label = "VM timed out  \nwith bad health  \nstatus  " ];
-    ON -> MAINTENANCE [ label = "Maintenance\nmode enabled" ];
-
-    STOP -> STOP [ label = "VM shutdown\nin progress" ];
-    STOP -> OFF [ label = "VM shutdown\nsuccessful" ];
-    STOP -> ENTRY [ label = "VM failed to stop,\nunknown state" ];
-
-    MIGRATE -> MIGRATE [ label = "VM migration\nin progress" ];
-    MIGRATE -> OFF [ label = "VM migration\nsuccessful" ];
-    MIGRATE -> STOP [ label = "VM migration\nfailed" ];
-
-    MAINTENANCE -> MAINTENANCE [ label = "Maintenance\nmode enabled" ];
-    MAINTENANCE -> ENTRY [ label = "Maintenance\nmode disabled" ];
+ranksep = 0.5;
+node [shape = doublecircle]; StartState;
+node [shape = circle]; EngineMigratingAway LocalMaintenanceMigrateVm 
EngineStop EngineStart ReinitializeFSM GlobalMaintenance UnknownLocalVmState 
EngineUp EngineDown LocalMaintenance EngineUpBadHealth EngineUnexpectedlyDown 
EngineForceStop ;
+EngineMigratingAway -> EngineMigratingAway [ label = "" ];
+EngineMigratingAway -> EngineDown [ label = "" ];
+EngineMigratingAway -> GlobalMaintenance [ label = "" ];
+EngineMigratingAway -> UnknownLocalVmState [ label = "" ];
+EngineMigratingAway -> ReinitializeFSM [ label = "" ];
+LocalMaintenanceMigrateVm -> EngineStop [ label = "" ];
+LocalMaintenanceMigrateVm -> GlobalMaintenance [ label = "" ];
+LocalMaintenanceMigrateVm -> UnknownLocalVmState [ label = "" ];
+LocalMaintenanceMigrateVm -> EngineMigratingAway [ label = "" ];
+EngineStop -> EngineStop [ label = "" ];
+EngineStop -> ReinitializeFSM [ label = "" ];
+EngineStop -> GlobalMaintenance [ label = "" ];
+EngineStop -> UnknownLocalVmState [ label = "" ];
+EngineStop -> LocalMaintenance [ label = "" ];
+EngineStop -> EngineForceStop [ label = "" ];
+EngineStart -> EngineDown [ label = "" ];
+EngineStart -> GlobalMaintenance [ label = "" ];
+EngineStart -> UnknownLocalVmState [ label = "" ];
+EngineStart -> EngineUp [ label = "" ];
+EngineStart -> LocalMaintenance [ label = "" ];
+ReinitializeFSM -> EngineDown [ label = "" ];
+ReinitializeFSM -> GlobalMaintenance [ label = "" ];
+ReinitializeFSM -> UnknownLocalVmState [ label = "" ];
+ReinitializeFSM -> EngineUp [ label = "" ];
+ReinitializeFSM -> LocalMaintenance [ label = "" ];
+GlobalMaintenance -> ReinitializeFSM [ label = "" ];
+GlobalMaintenance -> GlobalMaintenance [ label = "" ];
+GlobalMaintenance -> LocalMaintenance [ label = "" ];
+UnknownLocalVmState -> GlobalMaintenance [ label = "" ];
+UnknownLocalVmState -> UnknownLocalVmState [ label = "" ];
+UnknownLocalVmState -> LocalMaintenance [ label = "" ];
+EngineUp -> EngineMigratingAway [ label = "" ];
+EngineUp -> GlobalMaintenance [ label = "" ];
+EngineUp -> EngineUnexpectedlyDown [ label = "" ];
+EngineUp -> LocalMaintenanceMigrateVm [ label = "" ];
+EngineUp -> UnknownLocalVmState [ label = "" ];
+EngineUp -> EngineUp [ label = "" ];
+EngineUp -> EngineUpBadHealth [ label = "" ];
+EngineUp -> EngineStop [ label = "" ];
+StartState -> ReinitializeFSM [ label = "" ];
+EngineDown -> EngineStart [ label = "" ];
+EngineDown -> GlobalMaintenance [ label = "" ];
+EngineDown -> UnknownLocalVmState [ label = "" ];
+EngineDown -> EngineUp [ label = "" ];
+EngineDown -> EngineDown [ label = "" ];
+EngineDown -> LocalMaintenance [ label = "" ];
+LocalMaintenance -> ReinitializeFSM [ label = "" ];
+LocalMaintenance -> LocalMaintenance [ label = "" ];
+EngineUpBadHealth -> EngineMigratingAway [ label = "" ];
+EngineUpBadHealth -> LocalMaintenanceMigrateVm [ label = "" ];
+EngineUpBadHealth -> EngineStop [ label = "" ];
+EngineUpBadHealth -> GlobalMaintenance [ label = "" ];
+EngineUpBadHealth -> UnknownLocalVmState [ label = "" ];
+EngineUpBadHealth -> EngineUp [ label = "" ];
+EngineUpBadHealth -> EngineUpBadHealth [ label = "" ];
+EngineUpBadHealth -> EngineUnexpectedlyDown [ label = "" ];
+EngineUnexpectedlyDown -> EngineUnexpectedlyDown [ label = "" ];
+EngineUnexpectedlyDown -> GlobalMaintenance [ label = "" ];
+EngineUnexpectedlyDown -> UnknownLocalVmState [ label = "" ];
+EngineUnexpectedlyDown -> EngineUp [ label = "" ];
+EngineUnexpectedlyDown -> EngineDown [ label = "" ];
+EngineUnexpectedlyDown -> LocalMaintenance [ label = "" ];
+EngineForceStop -> EngineDown [ label = "" ];
+EngineForceStop -> ReinitializeFSM [ label = "" ];
+EngineForceStop -> GlobalMaintenance [ label = "" ];
+EngineForceStop -> UnknownLocalVmState [ label = "" ];
+EngineForceStop -> LocalMaintenance [ label = "" ];
 }
diff --git a/ovirt_hosted_engine_ha/agent/state_machine.py 
b/ovirt_hosted_engine_ha/agent/state_machine.py
index debb7fb..f3e3567 100644
--- a/ovirt_hosted_engine_ha/agent/state_machine.py
+++ b/ovirt_hosted_engine_ha/agent/state_machine.py
@@ -10,6 +10,9 @@
 
 
 class StartState(BaseState):
+    """
+    :transition ReinitializeFSM:
+    """
     def consume(self, fsm, new_data, logger):
         return ReinitializeFSM(new_data)
 
diff --git a/ovirt_hosted_engine_ha/agent/states.py 
b/ovirt_hosted_engine_ha/agent/states.py
index 08723a6..65a78fb 100644
--- a/ovirt_hosted_engine_ha/agent/states.py
+++ b/ovirt_hosted_engine_ha/agent/states.py
@@ -197,6 +197,9 @@
     This state is entered any time the host gets to local maintenance state.
     It monitors the environment and once the maintenance is completed,
     the FSM is reinitialized.
+
+    :transition:
+    :transition ReinitializeFSM:
     """
     def score(self, logger):
         logger.info('Score is 0 due to local maintenance mode',
@@ -223,6 +226,10 @@
     """
     This is an idler state that does not do anything while the global
     maintenance mode is enabled.
+
+    :transition:
+    :transition LocalMaintenance:
+    :transition ReinitializeFSM:
     """
     @check_global_maintenance(None)
     @check_local_maintenance(LocalMaintenance)
@@ -239,6 +246,11 @@
     """
     Error state that is used when we are not able to determine the
     status of the local engine VM.
+
+    :transition:
+    :transition GlobalMaintenance:
+    :transition LocalMaintenance:
+    :transition ReinitializeFSM:
     """
     @check_global_maintenance(GlobalMaintenance)
     @check_local_vm_unknown(None)
@@ -256,6 +268,12 @@
     """
     Determine the best state to start with based on the current
     information about the environment.
+
+    :transition GlobalMaintenance:
+    :transition LocalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition EngineUp:
+    :transition EngineDown:
     """
     def score(self, logger):
         return 0
@@ -288,6 +306,11 @@
     when the engine runs locally..
     It tries to migrate it to the best remote host and
     then moves to local maintenance state.
+
+    :transition GlobalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition EngineStop:
+    :transition EngineMigratingAway:
     """
     @check_global_maintenance(GlobalMaintenance)
     @check_local_vm_unknown(UnknownLocalVmState)
@@ -315,6 +338,15 @@
     """
     When the engine is up and running locally, this state is used
     to monitor it.
+
+    :transition GlobalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition LocalMaintenanceMigrateVm:
+    :transition EngineUnexpectedlyDown:
+    :transition EngineMigratingAway:
+    :transition EngineStop:
+    :transition EngineUpBadHealth:
+    :transition:
     """
     def _penalize_memory(self, vm_mem, lm, logger, score, score_cfg):
         # if the vm is up, do not check memory usage
@@ -363,6 +395,13 @@
     This state is used when the engine is running elsewhere and the local
     host has nothing to do except wait for the engine host to become
     bad.
+
+    :transition GlobalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition LocalMaintenance:
+    :transition EngineUp:
+    :transition:
+    :transition EngineStart:
     """
     @check_global_maintenance(GlobalMaintenance)
     @check_local_vm_unknown(UnknownLocalVmState)
@@ -420,6 +459,12 @@
     """
     This state is used to force-stop the local VM. Used only
     if the regular stop procedure did not finish on time.
+
+    :transition GlobalMaintenance:
+    :transition LocalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition EngineDown:
+    :transition ReinitializeFSM:
     """
     @check_global_maintenance(GlobalMaintenance)
     @check_local_maintenance(LocalMaintenance)
@@ -441,6 +486,13 @@
     This state is responsible for stopping the local VM in preparation
     of starting it elsewhere. If the stop action takes too long, it falls
     back to EngineForceStop.
+
+    :transition GlobalMaintenance:
+    :transition LocalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition EngineForceStop:
+    :transition:
+    :transition ReinitializeFSM:
     """
     @check_global_maintenance(GlobalMaintenance)
     @check_local_maintenance(LocalMaintenance)
@@ -485,6 +537,9 @@
     the VM is UP and the engine does not report healthy state.
     If the engine stays in this state too long, the VM is stopped and
     started somewhere else.
+
+    :transition EngineStop:
+    :transitions_from EngineUp:
     """
     @check_timeout(EngineStop, constants.ENGINE_BAD_HEALTH_TIMEOUT_SECS)
     def consume(self, fsm, new_data, logger):
@@ -520,6 +575,13 @@
     score to effectively move it to another host. This also serves as a
     shortcut for the user to start host maintenance mode, though it still
     should be set manually lest the score recover after a timeout.
+
+    :transition GlobalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition LocalMaintenance:
+    :transition EngineDown:
+    :transition EngineUp:
+    :transition:
     """
 
     @check_global_maintenance(GlobalMaintenance)
@@ -577,6 +639,12 @@
 class EngineStart(EngineState):
     """
     This state is responsible for starting the VM on the local machine.
+
+    :transition GlobalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition LocalMaintenance:
+    :transition EngineUp:
+    :transition EngineDown:
     """
     @check_global_maintenance(GlobalMaintenance)
     @check_local_vm_unknown(UnknownLocalVmState)
@@ -605,6 +673,12 @@
     """
     This state is responsible for monitoring a migration of the engine
     VM to some other machine.
+
+    :transition GlobalMaintenance:
+    :transition UnknownLocalVmState:
+    :transition:
+    :transition EngineDown:
+    :transition ReinitializeFSM:
     """
     def collect(self, fsm, new_data, logger):
         """


-- 
To view, visit http://gerrit.ovirt.org/24857
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ie6de8b20a8908aa0906404283829775b5487a850
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-hosted-engine-ha
Gerrit-Branch: ovirt-hosted-engine-ha-1.1
Gerrit-Owner: Martin Sivák <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to