changeset 13:21aa17f97522

Initial code for build master and slave... these don't do a lot yet.
author cmlenz
date Thu, 16 Jun 2005 14:49:51 +0000
parents fd802a55be55
children 1733c601d2f8
files bitten/master.py bitten/slave.py bitten/util/beep.py bitten/util/xmlio.py scripts/echoclient.py
diffstat 5 files changed, 209 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/bitten/master.py
@@ -0,0 +1,74 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
+#
+# Bitten is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# Trac is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Author: Christopher Lenz <cmlenz@gmx.de>
+
+import asyncore
+import getopt
+import os.path
+import sys
+
+from bitten.util import beep
+from bitten.util.xmlio import Element, parse_xml
+
+
+class BittenProfile(beep.Profile):
+    URI = 'http://bitten.cmlenz.net/beep-profile/'
+
+    def __init__(self):
+        beep.Profile.__init__(self)
+
+    def handle_msg(self, msgno, msg):
+        assert msg.get_content_type() == beep.BEEP_XML
+        elem = parse_xml(msg.get_payload())
+
+        if elem.tagname == 'register':
+            platform, os, os_family, os_version = None, None, None, None
+            for child in elem['*']:
+                if child.tagname == 'platform':
+                    platform = child.gettext()
+                elif child.tagname == 'os':
+                    os = child.gettext()
+                    os_family = child.family
+                    os_version = child.version
+            rpy = beep.MIMEMessage(Element('ok'), beep.BEEP_XML)
+            self.channel.send_rpy(msgno, rpy)
+            print 'Connected to %s (%s running %s %s [%s])' \
+                  % (elem.name, platform, os, os_version, os_family)
+
+
+if __name__ == '__main__':
+    options, args = getopt.getopt(sys.argv[1:], 'p:', ['port='])
+    if len(args) < 1:
+        print>>sys.stderr, 'usage: %s [options] ENV_PATH' % os.path.basename(sys.argv[0])
+        print>>sys.stderr
+        print>>sys.stderr, 'Valid options:'
+        print>>sys.stderr, '  -p [--port] arg\tport number to use (default: 7633)'
+        sys.exit(2)
+
+    if len(args) > 1:
+        port = int(args[1])
+    else:
+        port = 7633
+
+    listener = beep.Listener('localhost', port)
+    listener.profiles[BittenProfile.URI] = BittenProfile()
+    try:
+        asyncore.loop()
+    except KeyboardInterrupt:
+        pass
new file mode 100644
--- /dev/null
+++ b/bitten/slave.py
@@ -0,0 +1,115 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
+#
+# Bitten is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# Trac is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Author: Christopher Lenz <cmlenz@gmx.de>
+
+import asyncore
+import getopt
+import os
+import sys
+import time
+
+from bitten.util import beep
+from bitten.util.xmlio import Element, parse_xml
+
+
+class Slave(beep.Initiator):
+
+    channelno = None # The channel number used by the bitten profile
+
+    def channel_started(self, channelno, profile_uri):
+        if profile_uri == BittenProfileHandler.URI:
+            self.channelno = channelno
+
+    def greeting_received(self, profiles):
+        if BittenProfileHandler.URI in profiles:
+            self.channels[0].profile.send_start([BittenProfileHandler],
+                                                handle_ok=self.channel_started)
+
+class BittenProfileHandler(beep.Profile):
+    """Handles communication on the Bitten profile from the client perspective.
+    """
+    URI = 'http://bitten.cmlenz.net/beep-profile/'
+
+    def handle_connect(self):
+        """Register with the build master."""
+        sysname, nodename, release, version, machine = os.uname()
+        print 'Registering with build master as %s' % nodename
+        register = Element('register', name=nodename)[
+            Element('platform')[machine],
+            Element('os', family=os.name, version=release)[sysname]
+        ]
+        def handle_reply(cmd, msgno, msg):
+            if cmd == 'RPY':
+                print 'Registration successful'
+            else:
+                if msg.get_content_type() == beep.BEEP_XML:
+                    elem = parse_xml(msg.get_payload())
+                    if elem.tagname == 'error':
+                        raise beep.TerminateSession, \
+                              '%s (%s)' % (elem.gettext(), elem.code)
+                raise beep.TerminateSession, 'Registration failed!'
+        self.channel.send_msg(beep.MIMEMessage(register, beep.BEEP_XML),
+                              handle_reply)
+
+    def handle_msg(self, msgno, msg):
+        # TODO: Handle build initiation requests
+        pass
+
+
+if __name__ == '__main__':
+    options, args = getopt.getopt(sys.argv[1:], 'vq', ['verbose', 'qiuet'])
+    if len(args) < 1:
+        print>>sys.stderr, 'Usage: %s [options] host [port]' % sys.argv[0]
+        print>>sys.stderr
+        print>>sys.stderr, 'Valid options:'
+        print>>sys.stderr, '  -q [--quiet]\tprint as little as possible'
+        print>>sys.stderr, '  -v [--verbose]\tprint as much as possible'
+        sys.exit(2)
+
+    host = args[0]
+    if len(args) > 1:
+        port = int(args[1])
+    else:
+        port = 7633
+
+    verbose = False
+    quiet = False
+    for opt, arg in options:
+        if opt in ('-v', '--verbose'):
+            verbose = True
+        elif opt in ('-q', '--quiet'):
+            quiet = True
+
+    slave = Slave(host, port)
+    try:
+        try:
+            asyncore.loop()
+        except KeyboardInterrupt, beep.TerminateSession:
+            def handle_ok():
+                raise asyncore.ExitNow, 'Session terminated'
+            def handle_error(code, message):
+                print>>sys.stderr, \
+                    'Build master refused to terminate session (%d): %s' \
+                    % (code, message)
+            slave.channels[0].profile.send_close(slave.channelno)
+            slave.channels[0].profile.send_close(handle_ok=handle_ok,
+                                                 handle_error=handle_error)
+            time.sleep(.25)
+    except beep.TerminateSession, e:
+        print e
--- a/bitten/util/beep.py
+++ b/bitten/util/beep.py
@@ -45,6 +45,10 @@
     """Generic root class for BEEP exceptions."""
 
 
+class TerminateSession(Exception):
+    """Signal termination of a session."""
+
+
 class Listener(asyncore.dispatcher):
     """BEEP peer in the listener role.
     
@@ -98,13 +102,16 @@
         pass
 
     def handle_error(self):
+        """Called by asyncore when an exception is raised."""
         t, v = sys.exc_info()[:2]
-        if t is SystemExit:
+        if t is TerminateSession:
             raise t, v
-        else:
-            asynchat.async_chat.handle_error(self)
+        asynchat.async_chat.handle_error(self)
 
     def collect_incoming_data(self, data):
+        """Called by async_chat when data is received.
+        
+        Buffer the data and wait until a terminator is found."""
         self.inbuf.append(data)
 
     def found_terminator(self):
@@ -373,7 +380,7 @@
         assert message.get_content_type() == BEEP_XML
         elem = parse_xml(message.get_payload())
 
-        if elem.name == 'start':
+        if elem.tagname == 'start':
             for profile in elem['profile']:
                 if profile.uri in self.session.profiles.keys():
                     print 'Start channel %s for profile <%s>' % (elem.number,
@@ -388,7 +395,7 @@
             self.send_error(msgno, 550,
                             'All requested profiles are unsupported')
 
-        elif elem.name == 'close':
+        elif elem.tagname == 'close':
             channelno = int(elem.number)
             if channelno == 0:
                 if len(self.session.channels) > 1:
@@ -408,7 +415,7 @@
         assert message.get_content_type() == BEEP_XML
         elem = parse_xml(message.get_payload())
 
-        if elem.name == 'greeting':
+        if elem.tagname == 'greeting':
             if isinstance(self.session, Initiator):
                 profiles = [profile.uri for profile in elem['profile']]
                 self.session.greeting_received(profiles)
@@ -422,7 +429,7 @@
         # TODO: Terminate the session, I guess
         assert message.get_content_type() == BEEP_XML
         elem = parse_xml(message.get_payload())
-        assert elem.name == 'error'
+        assert elem.tagname == 'error'
         print elem.code
 
     def send_close(self, channelno=0, code=200, handle_ok=None,
--- a/bitten/util/xmlio.py
+++ b/bitten/util/xmlio.py
@@ -67,14 +67,14 @@
     >>> print Element('foo')['Hello ', Element('b')['world']]
     <foo>Hello <b>world</b></foo>
     """
-    __slots__ = ['name', 'attrs', 'children']
+    __slots__ = ['tagname', 'attrs', 'children']
 
-    def __init__(self, name, **attrs):
+    def __init__(self, tagname, **attrs):
         """Create an XML element using the specified tag name.
         
         All keyword arguments are handled as attributes of the element.
         """
-        self.name = name
+        self.tagname = tagname
         self.attrs = attrs
         self.children = []
 
@@ -89,7 +89,7 @@
         """Return a string representation of the XML element."""
         buf = StringIO()
         buf.write('<')
-        buf.write(self.name)
+        buf.write(self.tagname)
         for name, value in self.attrs.items():
             buf.write(' ')
             buf.write(name)
@@ -104,7 +104,7 @@
                 else:
                     buf.write(self._escape_text(child))
             buf.write('</')
-            buf.write(self.name)
+            buf.write(self.tagname)
             buf.write('>')
         else:
             buf.write('/>')
@@ -133,7 +133,7 @@
     def __init__(self, node):
         self.node = node
 
-    name = property(fget=lambda self: self.node.tagName)
+    tagname = property(fget=lambda self: self.node.tagName)
 
     def __getattr__(self, name):
         return self.node.getAttribute(name)
deleted file mode 100644
--- a/scripts/echoclient.py
+++ /dev/null
@@ -1,78 +0,0 @@
-import asyncore
-import sys
-import time
-
-from bitten.util.beep import Initiator, MIMEMessage, Profile
-from bitten.util.xmlio import Element, parse_xml
-
-
-class StdinChannel(asyncore.file_dispatcher):
-
-    def __init__(self, handle_read):
-        asyncore.file_dispatcher.__init__(self, sys.stdin.fileno())
-        self.read_handler = handle_read
-
-    def readable(self):
-        return True
-
-    def handle_read(self):
-        data = self.recv(8192)
-        self.read_handler(data)
-
-    def writable(self):
-        return False
-
-
-class EchoProfile(Profile):
-    URI = 'http://beepcore.org/beep/ECHO'
-
-    def handle_rpy(self, msgno, message):
-        print '\x1b[31m' + message.get_payload().rstrip() + '\x1b[0m'
-
-
-class EchoClient(Initiator):
-
-    channel = None
-
-    def greeting_received(self, profiles):
-        def handle_ok(channelno, uri):
-            print 'Channel %d started for profile %s...' % (channelno, uri)
-            self.channel = channelno
-        def handle_error(code, message):
-            print>>sys.stderr, 'Error %d: %s' % (code, message)
-        if EchoProfile.URI in profiles:
-            self.channels[0].profile.send_start([EchoProfile],
-                                                handle_ok=handle_ok,
-                                                handle_error=handle_error)
-
-
-if __name__ == '__main__':
-    host = 'localhost'
-    port = 8000
-    if len(sys.argv) > 1:
-        host = sys.argv[1]
-        if len(sys.argv) > 2:
-            port = int(sys.argv[2])
-
-    client = EchoClient(host, port)
-    def handle_input(data):
-        message = MIMEMessage(data, 'text/plain')
-        client.channels[client.channel].send_msg(message)
-    stdin = StdinChannel(handle_input)
-    try:
-        while client:
-            try:
-                asyncore.loop()
-            except KeyboardInterrupt:
-                mgmt = client.channels[0].profile
-                def handle_ok():
-                    raise asyncore.ExitNow, 'Session terminated'
-                def handle_error(code, message):
-                    print>>sys.stderr, \
-                        'Peer refused to terminate session (%d): %s' \
-                        % (code, message)
-                mgmt.send_close(client.channel)
-                mgmt.send_close(handle_ok=handle_ok, handle_error=handle_error)
-                time.sleep(.25)
-    except asyncore.ExitNow, e:
-        print e
Copyright (C) 2012-2017 Edgewall Software