Browse Source

First BoB tests created

Respect verbose flag when starting BoB
Make message processing robust
Refactor to make testable
Convert from poll() to select() 

Update TODO list



git-svn-id: svn://bind10.isc.org/svn/bind10/branches/parkinglot@520 e5f2f494-b856-4b98-b285-d166d9295462
Shane Kerr 15 years ago
parent
commit
22ec353059
3 changed files with 120 additions and 67 deletions
  1. 3 1
      src/bin/bind10/TODO
  2. 95 65
      src/bin/bind10/bind10.py
  3. 22 1
      src/bin/bind10/bind10_test.py

+ 3 - 1
src/bin/bind10/TODO

@@ -11,5 +11,7 @@
 - Back-off mechanism for restarting failed processes
 - Back-off mechanism for restarting failed processes
 - Start statistics daemon
 - Start statistics daemon
 - Statistics interaction (?)
 - Statistics interaction (?)
-- Stop using poll(), as primitive operating systems (OS X) don't support it
 - Use .spec file to define comands
 - Use .spec file to define comands
+- Rename "c-channel" stuff to msgq for clarity
+- Use logger
+- Reply to shutdown message?

+ 95 - 65
src/bin/bind10/bind10.py

@@ -81,7 +81,7 @@ class BoB:
         process listens on. If verbose is True, then the boss reports
         process listens on. If verbose is True, then the boss reports
         what it is doing.
         what it is doing.
         """
         """
-        self.verbose = True
+        self.verbose = verbose
         self.c_channel_port = c_channel_port
         self.c_channel_port = c_channel_port
         self.cc_session = None
         self.cc_session = None
         self.processes = {}
         self.processes = {}
@@ -153,7 +153,7 @@ class BoB:
         if self.verbose:
         if self.verbose:
             sys.stdout.write("Starting cmd-ctrld on port 8080\n")
             sys.stdout.write("Starting cmd-ctrld on port 8080\n")
         try:
         try:
-            cmd_ctrld = ProcessInfo("cmd-ctrld", 'cmd-ctrld')
+            cmd_ctrld = ProcessInfo("cmd-ctrld", ['cmd-ctrld'])
         except Exception as e:
         except Exception as e:
             c_channel.process.kill()
             c_channel.process.kill()
             bind_cfgd.process.kill()
             bind_cfgd.process.kill()
@@ -247,32 +247,59 @@ class BoB:
     def recv_and_process_cc_msg(self):
     def recv_and_process_cc_msg(self):
         """Receive and process the next message on the c-channel,
         """Receive and process the next message on the c-channel,
         if any."""
         if any."""
-        # XXX: this needs to be made more robust for handling
-        #      badly formatted messages
-        msg, data = self.cc_session.group_recvmsg(False)
+        msg, envelope = self.cc_session.group_recvmsg(False)
+        print(msg)
         if msg is None:
         if msg is None:
             return
             return
-        msg_from = data.get('from', '')
-
-        if (type(msg) is dict) and (type(data) is dict):
-            if "command" in msg:
-                cmd = msg['command']
-                if cmd[0] == "shutdown":
-                    if self.verbose:
-                        sys.stdout.write("Shutdown command received\n")
-                    self.runnable = False
-                else:
-                    if self.verbose:
-                        sys.stdout.write("Unknown command %s\n" % str(cmd))
-            else:
-                if self.verbose:
-                    del data['msg']
-                    sys.stdout.write("Unknown message received\n")
-                    sys.stdout.write(pprint.pformat(data) + "\n")
-                    sys.stdout.write(pprint.pformat(msg) + "\n")
-        else:
+        if not ((type(msg) is dict) and (type(envelope) is dict)):
             if self.verbose:
             if self.verbose:
                 sys.stdout.write("Non-dictionary message\n")
                 sys.stdout.write("Non-dictionary message\n")
+            return
+        if not "command" in msg:
+            if self.verbose:
+                if "msg" in envelope:
+                    del envelope['msg']
+                sys.stdout.write("Unknown message received\n")
+                sys.stdout.write(pprint.pformat(envelope) + "\n")
+                sys.stdout.write(pprint.pformat(msg) + "\n")
+            return
+        cmd = msg['command']
+        if not (type(cmd) is list):
+            if self.verbose:
+                sys.stdout.write("Non-list command\n")
+            return
+        if len(cmd) < 2:
+            if self.verbose:
+                sys.stdout.write("Command too short\n")
+            return
+        if cmd[0] != "boss":
+            return
+
+        # done checking and extracting... time to execute the command
+        if cmd[1] == "shutdown":
+            if self.verbose:
+                sys.stdout.write("shutdown command received\n")
+            self.runnable = False
+            # XXX: reply here?
+        elif cmd[1] == "getProcessList":
+            if self.verbose:
+                sys.stdout.write("getProcessList command received\n")
+            live_processes = [ ]
+            for proc_info in processes:
+                live_processes.append({ "name": proc_info.name, 
+                                        "args": proc_info.args, 
+                                        "pid": proc_info.pid, })
+            dead_processes = [ ]
+            for proc_info in dead_processes:
+                dead_processes.append({ "name": proc_info.name, 
+                                        "args": proc_info.args, })
+            cc.group_reply(envelope, { "response": cmd,
+                                       "sent": msg["sent"],
+                                       "live_processes": live_processes,
+                                       "dead_processes": dead_processes, })
+        else:
+            if self.verbose:
+                sys.stdout.write("Unknown command %s\n" % str(cmd))
 
 
     def restart_processes(self):
     def restart_processes(self):
         """Restart any dead processes."""
         """Restart any dead processes."""
@@ -297,38 +324,41 @@ class BoB:
         # remember any processes that refuse to be resurrected
         # remember any processes that refuse to be resurrected
         self.dead_processes = still_dead
         self.dead_processes = still_dead
 
 
-if __name__ == "__main__":
-    def reaper(signal_number, stack_frame):
-        """A child process has died (SIGCHLD received)."""
-        # don't do anything... 
-        # the Python signal handler has been set up to write
-        # down a pipe, waking up our select() bit
-        pass
+def reaper(signal_number, stack_frame):
+    """A child process has died (SIGCHLD received)."""
+    # don't do anything... 
+    # the Python signal handler has been set up to write
+    # down a pipe, waking up our select() bit
+    pass
                    
                    
-    def get_signame(signal_number):
-        """Return the symbolic name for a signal."""
-        for sig in dir(signal):
-            if sig.startswith("SIG") and sig[3].isalnum():
-                if getattr(signal, sig) == signal_number:
-                    return sig
-        return "Unknown signal %d" % signal_number
-
-    # XXX: perhaps register atexit() function and invoke that instead
-    def fatal_signal(signal_number, stack_frame):
-        """We need to exit (SIGINT or SIGTERM received)."""
-        global options
-        if options.verbose:
-            sys.stdout.write("Received %s.\n" % get_signame(signal_number))
-        signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-        boss_of_bind.runnable = False
-
-    def check_port(option, opt_str, value, parser):
-        """Function to insure that the port we are passed is actually 
-        a valid port number. Used by OptionParser() on startup."""
-        if not re.match('^(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$', value):
-            raise OptionValueError("%s requires a port number (0-65535)" % opt_str)
-        parser.values.msgq_port = value
-
+def get_signame(signal_number):
+    """Return the symbolic name for a signal."""
+    for sig in dir(signal):
+        if sig.startswith("SIG") and sig[3].isalnum():
+            if getattr(signal, sig) == signal_number:
+                return sig
+    return "Unknown signal %d" % signal_number
+
+# XXX: perhaps register atexit() function and invoke that instead
+def fatal_signal(signal_number, stack_frame):
+    """We need to exit (SIGINT or SIGTERM received)."""
+    global options
+    global boss_of_bind
+    if options.verbose:
+        sys.stdout.write("Received %s.\n" % get_signame(signal_number))
+    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+    boss_of_bind.runnable = False
+
+def check_port(option, opt_str, value, parser):
+    """Function to insure that the port we are passed is actually 
+    a valid port number. Used by OptionParser() on startup."""
+    if not re.match('^(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$', value):
+        raise OptionValueError("%s requires a port number (0-65535)" % opt_str)
+    parser.values.msgq_port = value
+  
+def main():
+    global options
+    global boss_of_bind
     # Parse any command-line options.
     # Parse any command-line options.
     parser = OptionParser(version=__version__)
     parser = OptionParser(version=__version__)
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
@@ -366,27 +396,24 @@ if __name__ == "__main__":
 
 
     # In our main loop, we check for dead processes or messages 
     # In our main loop, we check for dead processes or messages 
     # on the c-channel.
     # on the c-channel.
-    event_poller = select.poll()
     wakeup_fd = wakeup_pipe[0]
     wakeup_fd = wakeup_pipe[0]
-    event_poller.register(wakeup_fd, select.POLLIN)
     cc_fd = boss_of_bind.cc_session._socket.fileno()
     cc_fd = boss_of_bind.cc_session._socket.fileno()
-    event_poller.register(cc_fd, select.POLLIN)
     while boss_of_bind.runnable:
     while boss_of_bind.runnable:
-        # XXX: get time for next restart for poll
+        # XXX: get time for next restart for timeout
 
 
-        # poll() can raise EINTR when a signal arrives, 
+        # select() can raise EINTR when a signal arrives, 
         # even if they are resumable, so we have to catch
         # even if they are resumable, so we have to catch
         # the exception
         # the exception
         try:
         try:
-            events = event_poller.poll()
+            (rlist, wlist, xlist) = select.select([wakeup_fd, cc_fd], [], [])
         except select.error as err:
         except select.error as err:
             if err.args[0] == errno.EINTR:
             if err.args[0] == errno.EINTR:
-                events = []
+                (rlist, wlist, xlist) = ([], [], [])
             else:
             else:
-                sys.stderr.write("Error with poll(); %s\n" % err)
+                sys.stderr.write("Error with select(); %s\n" % err)
                 break
                 break
 
 
-        for (fd, event) in events:
+        for fd in rlist + xlist:
             if fd == cc_fd:
             if fd == cc_fd:
                 boss_of_bind.recv_and_process_cc_msg()
                 boss_of_bind.recv_and_process_cc_msg()
             elif fd == wakeup_fd:
             elif fd == wakeup_fd:
@@ -402,9 +429,12 @@ if __name__ == "__main__":
                 raise
                 raise
             if pid == 0: break
             if pid == 0: break
             boss_of_bind.reap(pid, exit_status)
             boss_of_bind.reap(pid, exit_status)
-        
+
         boss_of_bind.restart_processes()
         boss_of_bind.restart_processes()
 
 
     # shutdown
     # shutdown
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
     boss_of_bind.shutdown()
     boss_of_bind.shutdown()
+
+if __name__ == "__main__":
+    main()

+ 22 - 1
src/bin/bind10/bind10_test.py

@@ -1,4 +1,4 @@
-from bind10 import ProcessInfo
+from bind10 import ProcessInfo, BoB
 
 
 import unittest
 import unittest
 import sys
 import sys
@@ -68,5 +68,26 @@ class TestProcessInfo(unittest.TestCase):
         self.assertTrue(type(pi.pid) is int)
         self.assertTrue(type(pi.pid) is int)
         self.assertNotEqual(pi.pid, old_pid)
         self.assertNotEqual(pi.pid, old_pid)
 
 
+class TestBoB(unittest.TestCase):
+    def test_init(self):
+        bob = BoB()
+        self.assertEqual(bob.verbose, False)
+        self.assertEqual(bob.c_channel_port, 9912)
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.processes, {})
+        self.assertEqual(bob.dead_processes, {})
+        self.assertEqual(bob.runnable, False)
+
+    def test_init_alternate_port(self):
+        bob = BoB(2199)
+        self.assertEqual(bob.verbose, False)
+        self.assertEqual(bob.c_channel_port, 2199)
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.processes, {})
+        self.assertEqual(bob.dead_processes, {})
+        self.assertEqual(bob.runnable, False)
+
+    # verbose testing...
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()