[Zope-CVS] CVS: Packages/Spread - README:1.1 setup.py:1.1 spreadmodule.c:1.1 testspread.py:1.1

Jeremy Hylton jeremy@zope.com
Tue, 25 Sep 2001 15:20:44 -0400


Update of /cvs-repository/Packages/Spread
In directory cvs.zope.org:/tmp/cvs-serv3529

Added Files:
	README setup.py spreadmodule.c testspread.py 
Log Message:
Initial checkin of Spread library



=== Added File Packages/Spread/README ===
This package contains a simple wrapper for the Spread toolkit.

Spread (www.spread.org) is a group communications package.  You'll
need to download and install it separately.  The Python API has been
built and tested against Spread 3.16, including a patch available from
the Web page.

The Spread makefiles do an odd install, putting things in
/var/tmp/testinstall by default.  It does *not* install header files,
so you'll need to copy those into place by hand.  I copied sp.h,
sp_events.h, and sp_funcs.h into /var/tmp/testinstall/include.  I'm
told the next release will install headers.

The distutils setup.py script needs to be told where the spread code
is installed.  I still don't know the preferred way to do that, so
you'll need to edit the SPREAD_DIR constant.  The setup script assumes
you've installed the header files manually.

The man pages installed by Spread (and available at www.spread.org)
are fairly helpful, although obscure at times.  There is no
documentation for the Python wrapper yet.

To run the testspread.py unit tests, you'll need to manually set your
Python path to the build directory.  Again, I don't know if distutils
provides a good way to do this.  (I wish it did.)  The following works
for me:

    PYTHONPATH=build/lib.linux-i686-2.1 python testspread.py


=== Added File Packages/Spread/setup.py ===
#! /usr/bin/env python

from distutils.core import setup, Extension

SPREAD_DIR = "/usr/local/spread"

ext = Extension('spread', ['spreadmodule.c'],
                include_dirs = [SPREAD_DIR + "/include"],
                library_dirs = [SPREAD_DIR + "/lib"],
                libraries = ['tsp'],
                )

setup(name = "Spread API for Python",
      ext_modules = [ext],
      )


=== Added File Packages/Spread/spreadmodule.c === (748/848 lines abridged)
/* Wrapper for the Spread toolkit: http://www.spread.org/
 */

#include "Python.h"
#include "structmember.h"
#include "sp.h"

/* XXX This ought to be in sp.h */
static  sp_time         Zero_timeout = { 0, 0 };

static PyObject *SpreadError;
static PyObject *spread_error(int);
static void spread_too_short_error(int, int, short, int);

#define MAX_GROUPS 10
#define MSG_SIZE 8 * 1024

typedef struct {
    PyObject_HEAD
    mailbox mbox;
    PyObject *private_group;
    int max_groups;
    int msg_size;
} MailboxObject;

typedef struct {
    PyObject_HEAD
    PyObject *sender;
    PyObject *groups;
    short msg_type; /* sp.h: #define int16 short*/
    int endian;
    PyObject *message;
    int truncated;
} RegularMsg;

typedef struct {
    group_id gid;
    int num_members;
    char members[1][MAX_GROUP_NAME];
} extra_member_info;

typedef struct {
    PyObject_HEAD
    int reason;
    PyObject *group;
    PyObject *group_id;
    PyObject *members;
    PyObject *extra; /* that are still members */
} MembershipMsg;


[-=- -=- -=- 748 lines omitted -=- -=- -=-]

    PyDict_SetItemString(d, "error", SpreadError);

    /* programmatically generated from sp.h */
    PyModule_AddIntConstant(m, "LOW_PRIORITY", LOW_PRIORITY);
    PyModule_AddIntConstant(m, "MEDIUM_PRIORITY", MEDIUM_PRIORITY);
    PyModule_AddIntConstant(m, "HIGH_PRIORITY", HIGH_PRIORITY);
    PyModule_AddIntConstant(m, "DEFAULT_SPREAD_PORT", DEFAULT_SPREAD_PORT);
    PyModule_AddIntConstant(m, "SPREAD_VERSION", SPREAD_VERSION);
    PyModule_AddIntConstant(m, "MAX_GROUP_NAME", MAX_GROUP_NAME);
    PyModule_AddIntConstant(m, "MAX_PRIVATE_NAME", MAX_PRIVATE_NAME);
    PyModule_AddIntConstant(m, "MAX_PROC_NAME", MAX_PROC_NAME);
    PyModule_AddIntConstant(m, "UNRELIABLE_MESS", UNRELIABLE_MESS);
    PyModule_AddIntConstant(m, "RELIABLE_MESS", RELIABLE_MESS);
    PyModule_AddIntConstant(m, "FIFO_MESS", FIFO_MESS);
    PyModule_AddIntConstant(m, "CAUSAL_MESS", CAUSAL_MESS);
    PyModule_AddIntConstant(m, "AGREED_MESS", AGREED_MESS);
    PyModule_AddIntConstant(m, "SAFE_MESS", SAFE_MESS);
    PyModule_AddIntConstant(m, "REGULAR_MESS", REGULAR_MESS);
    PyModule_AddIntConstant(m, "SELF_DISCARD", SELF_DISCARD);
    PyModule_AddIntConstant(m, "DROP_RECV", DROP_RECV);
    PyModule_AddIntConstant(m, "REG_MEMB_MESS", REG_MEMB_MESS);
    PyModule_AddIntConstant(m, "TRANSITION_MESS", TRANSITION_MESS);
    PyModule_AddIntConstant(m, "CAUSED_BY_JOIN", CAUSED_BY_JOIN);
    PyModule_AddIntConstant(m, "CAUSED_BY_LEAVE", CAUSED_BY_LEAVE);
    PyModule_AddIntConstant(m, "CAUSED_BY_DISCONNECT",
			    CAUSED_BY_DISCONNECT); 
    PyModule_AddIntConstant(m, "CAUSED_BY_NETWORK", CAUSED_BY_NETWORK);
    PyModule_AddIntConstant(m, "MEMBERSHIP_MESS", MEMBERSHIP_MESS);
    PyModule_AddIntConstant(m, "ENDIAN_RESERVED", ENDIAN_RESERVED);
    PyModule_AddIntConstant(m, "RESERVED", RESERVED);
    PyModule_AddIntConstant(m, "REJECT_MESS", REJECT_MESS);
    PyModule_AddIntConstant(m, "ACCEPT_SESSION", ACCEPT_SESSION);
    PyModule_AddIntConstant(m, "ILLEGAL_SPREAD", ILLEGAL_SPREAD);
    PyModule_AddIntConstant(m, "COULD_NOT_CONNECT", COULD_NOT_CONNECT);
    PyModule_AddIntConstant(m, "REJECT_QUOTA", REJECT_QUOTA);
    PyModule_AddIntConstant(m, "REJECT_NO_NAME", REJECT_NO_NAME);
    PyModule_AddIntConstant(m, "REJECT_ILLEGAL_NAME", REJECT_ILLEGAL_NAME);
    PyModule_AddIntConstant(m, "REJECT_NOT_UNIQUE", REJECT_NOT_UNIQUE);
    PyModule_AddIntConstant(m, "REJECT_VERSION", REJECT_VERSION);
    PyModule_AddIntConstant(m, "CONNECTION_CLOSED", CONNECTION_CLOSED);
    PyModule_AddIntConstant(m, "REJECT_AUTH", REJECT_AUTH);
    PyModule_AddIntConstant(m, "ILLEGAL_SESSION", ILLEGAL_SESSION);
    PyModule_AddIntConstant(m, "ILLEGAL_SERVICE", ILLEGAL_SERVICE);
    PyModule_AddIntConstant(m, "ILLEGAL_MESSAGE", ILLEGAL_MESSAGE);
    PyModule_AddIntConstant(m, "ILLEGAL_GROUP", ILLEGAL_GROUP);
    PyModule_AddIntConstant(m, "BUFFER_TOO_SHORT", BUFFER_TOO_SHORT);
    PyModule_AddIntConstant(m, "GROUPS_TOO_SHORT", GROUPS_TOO_SHORT);
    PyModule_AddIntConstant(m, "MESSAGE_TOO_LONG", MESSAGE_TOO_LONG);
}



=== Added File Packages/Spread/testspread.py ===
import spread
import time
import unittest

class SpreadTest(unittest.TestCase):

    spread_name = "4803@localhost"

    conn_counter = 0

    def _connect(self, membership=1):
        """Return a new connection"""
        c = SpreadTest.conn_counter
        SpreadTest.conn_counter += 1
        mb = spread.connect(self.spread_name, "conn%d" % c, 0, membership)
        return mb

    group_counter = 0

    def _group(self):
        """Return a new group name"""
        c = SpreadTest.group_counter
        SpreadTest.group_counter += 1
        return "group%d" % c

    def _connect_group(self, size):
        """Return a group and a list of size connections in that group.

        All the connections should have consumed all their membership
        messages.
        """
        group = self._group()
        l = [self._connect() for i in range(size)]
        for i in range(len(l)):
            c = l[i]
            if c.max_groups < size:
                c.max_groups = size
            c.join(group)
        # read membership messages until all receive notification of
        # the appropriate group size
        ready = []
        while l:
            for c in l[:]:
                if c.poll():
                    msg = c.receive()
                    self.assertEqual(type(msg), spread.MembershipMsgType)
                    if len(msg.members) == size:
                        ready.append(c)
                        l.remove(c)
        return group, ready

    def testSingleConnect(self):
        mbox = self._connect()
        group = self._group()
        mbox.join(group)
        while mbox.poll() == 0:
            time.sleep(0.1)
        msg = mbox.receive()

        # Should get a message indicating that we've joined the group
        self.assertEqual(msg.group, group,
                         "expected message group to match joined group")
        self.assertEqual(msg.reason, spread.CAUSED_BY_JOIN,
                         "expected message to be caused by join")
        self.assertEqual(len(msg.members), 1,
                         "expected group to have one member")
        self.assertEqual(msg.members[0], mbox.private_group,
                         "expected this mbox to be in group")
        self.assertEqual(len(msg.extra), 1,
                         "expected one mbox to cause the join")
        self.assertEqual(msg.extra[0], mbox.private_group,
                         "expected this mbox to cause the join")

        mbox.leave(group)
        # should get a self-leave message
        msg = mbox.receive()
        self.assertEqual(msg.group, group)
        self.assertEqual(msg.reason, spread.CAUSED_BY_LEAVE)
        self.assertEqual(len(msg.members), 0)
        self.assertEqual(len(msg.extra), 1)
        self.assertEqual(msg.extra[0], mbox.private_group)

        mbox.disconnect()

    def testTwoConnect(self):
        group = self._group()

        mbox1 = self._connect()
        mbox1.join(group)

        mbox2 = self._connect()
        mbox2.join(group)

        msg1_1 = mbox1.receive()
        msg1_2 = mbox1.receive()
        msg2_1 = mbox2.receive()

        self.assertEqual(msg1_1.reason, spread.CAUSED_BY_JOIN)
        self.assertEqual(msg1_2.reason, spread.CAUSED_BY_JOIN)
        self.assertEqual(msg2_1.reason, spread.CAUSED_BY_JOIN)
        self.assertEqual(len(msg1_2.members), 2)
        self.assertEqual(msg1_2.members, msg2_1.members)

        mbox1.leave(group)
        mbox2.leave(group)
        mbox1.disconnect()
        mbox2.disconnect()

    def testTwoGroups(self):
        mbox = self._connect()
        group1 = self._group()
        group2 = self._group()

        mbox.join(group1)
        mbox.join(group2)
        mbox.leave(group1)
        mbox.leave(group2)

        msg1 = mbox.receive()
        msg2 = mbox.receive()
        msg3 = mbox.receive()
        msg4 = mbox.receive()

        self.assertEqual(msg1.reason, spread.CAUSED_BY_JOIN)
        self.assertEqual(msg2.reason, spread.CAUSED_BY_JOIN)
        self.assertEqual(len(msg1.members), 1)
        self.assertEqual(len(msg2.members), 1)
        self.assertEqual(msg1.members[0], mbox.private_group)
        self.assertEqual(msg3.reason, spread.CAUSED_BY_LEAVE)
        self.assertEqual(msg4.reason, spread.CAUSED_BY_LEAVE)
        self.assertEqual(len(msg3.members), 0)
        self.assertEqual(len(msg4.members), 0)

        mbox.disconnect()

    def testMultipleRecipients(self):
        group, members = self._connect_group(12)

        wr = members[0]
        wr.multicast(spread.FIFO_MESS, group, "1")

        for rd in members:
            msg = rd.receive()
            if not hasattr(msg, 'message'):
                print msg
                print msg.reason
                print msg.svc_type
            self.assertEqual(msg.message, "1")
            self.assertEqual(msg.sender, wr.private_group)
            self.assertEqual(len(msg.groups), 1)
            self.assertEqual(msg.groups[0], group)
        
        wr = members[0]
        wr.multicast(spread.FIFO_MESS, group, "2")

        for rd in members:
            msg = rd.receive()
            self.assertEqual(msg.message, "2")
            self.assertEqual(msg.sender, wr.private_group)
            self.assertEqual(len(msg.groups), 1)
            self.assertEqual(msg.groups[0], group)

    def testBigMessage(self):
        group = self._group()
        mbox = self._connect()

        mbox.join(group)
        self.assertEqual(type(mbox.receive()), spread.MembershipMsgType)

        size = mbox.msg_size * 2
        try:
            mbox.multicast(spread.SAFE_MESS, group, "X" * size)
        except spread.error, err:
            print err
            print size
            raise
        try:
            mbox.receive()
        except spread.error, err:
            self.assertEqual(err[0], spread.BUFFER_TOO_SHORT)
            self.assertEqual(err[4], size)

        msg = mbox.receive(size)
        self.assertEqual(len(msg.message), size)

    def testBigGroup(self):
        group, members = self._connect_group(12)
        m1 = members[1]
        m2 = members[2]

        m1.max_groups = 6
        m2.leave(group)

        try:
            m1.receive()
        except spread.error, err:
            self.assertEqual(err[0], spread.GROUPS_TOO_SHORT)
        else:
            self.fail()

        m1.max_groups = err[4]
        m1.receive()

if __name__ == "__main__":
    unittest.main()