# Twisted as an Erlang node

## Prequesites

In this document, we'll assume that you have a basic knowledge of Twisted (how to create a client, how to create a server, what is the protocol/factory mechanism, what is a deferred), and of Erlang (what is a node, what is the EPMD, what are the basic types). Please refer to the respective documentation if it's not the case.

## Invoking methods on an Erlang node

To create connection to an erlang node, you'll need to get the cookie and create a node name. The utility functions `readCookie` and `buildNodeName` are here to help you out: the first read the cookie at the usual place ($HOME/.erlang.cookie), the second append the name of the host to the name you have chosen.

Then, instantiate the class `OneShotPortMapperFactory` with the infos, and call connectionToNode on it. This method will:

- create a connection with the EPMD
- ask for the port info of the node
- connect to the node
- return the connected node client protocol

Once you get back the protocol instance, you can use the callRemote method on its factory to make calls to the node.

If you have to make several calls to a node, don't bother keep a reference to it: OneShotPortMapperFactory will keep a cache of connected instances for you.

    from twisted.internet import reactor
    from twotp import OneShotPortMapperFactory, readCookie, buildNodeName

    def gotConnection(inst):
        return inst.factory.callRemote(inst, "file", "get_cwd"
            ).addCallback(gotResult)

    def gotResult(resp):
        print "Got response", resp
        reactor.stop()

    def eb(error):
        print "Got error", error
        reactor.stop()

    cookie = readCookie()
    nodeName = buildNodeName('nodename')
    epmd = OneShotPortMapperFactory(nodeName, cookie)
    epmd.connectToNode("foo").addCallback(gotConnection).addErrback(eb)
    reactor.run()

## Instantiating a node

The process for creating a node is relatively straightforward. First you create a node server factory, and make it listen on an arbitrary port. Then you create client connection to the EPMD, giving the server port as argument. This process is simplified by the method publish of the EPMD factory. The EPMD should be started outside Twisted. It is started automatically when you start an erlang application.

    from twisted.internet import reactor
    from twotp import PersistentPortMapperFactory, readCookie, buildNodeName

    cookie = readCookie()
    nodeName = buildNodeName('nodename')
    epmd = PersistentPortMapperFactory(nodeName, cookie)
    epmd.publish()
    reactor.run()

Once you have the node name and the cookie (see above to understand how), you create a `PersistentPortMapperFactory` instance, and call publish on it: it will create the node, make it listen on an arbitrary port, and connect to the EPMD to publish the information.

Of course, for now it does nothing useful, so we now see how to get data from an erlang nodes.

## Receiving method calls from an Erlang node

The key method here is the method publish you've seen above: this method takes a hash table of name/object. The key for this table is the module used when making a call from Erlang. The value is an instance of a class with methods prefixed by `remote_`.

    class Proxy(object):
        def remote_bar(self, arg):
            return arg * 2

    from twisted.internet import reactor
    from twotp import PersistentPortMapperFactory, readCookie, buildNodeName

    cookie = readCookie()
    nodeName = buildNodeName('nodename')
    epmd = PersistentPortMapperFactory(nodeName, cookie)
    epmd.publish(foo=Proxy())
    reactor.run()

The script above define a `Proxy` with a `remote_bar` method. We can publish with the keyword foo, which creates a module named foo with the method bar. We can now call this method in an Erlang console:

    (erlang@node)1> rpc:call('nodename@node', foo, bar, [3]).
    {ok,6}

## An example: using a Mnesia database

To try the communication as client, we'll set up a mnesia database to be used within a Twisted application. Please refer to the mnesia documentation for further information. Note that there is nothing new here: it's just a concrete example to put the things together.

First create a file named twisted.hrl with following record:

    -record(user, {id, name}).

Then create a file named twisted.erl

    -module(twisted).
    -export([init/0, insert_user/2, get_user/1]).
    -include("twisted.hrl").

    init() ->
        mnesia:create_table(user,
                            [{attributes, record_info(fields, user)}]).

    insert_user(UserName, UserId) ->
        User = #user{id=UserId, name=UserName},
        Fun = fun() ->
                mnesia:write(User)
            end,
        mnesia:transaction(Fun).

    get_user(UserId) ->
        Fun = fun() ->
                [User] = mnesia:read(user, UserId, read),
                User
            end,
        case mnesia:transaction(Fun) of
            {atomic, User} ->
                User
        end.

Now fire an erlang interpreter. We'll compile the twisted file, start the mnesia database, create the table, and insert a test user.

    $ erl -sname twisted_mnesia

    (twisted_mnesia@localhost)1> c(twisted).
    {ok,twisted}
    (twisted_mnesia@localhost)2> mnesia:start().
    ok
    (twisted_mnesia@localhost)3> twisted:init().
    {atomic,ok}
    (twisted_mnesia@localhost)4> twisted:insert_user(test1, 1).
    {atomic,ok}
    (twisted_mnesia@localhost)5> twisted:get_user(1).
    {user,1,test1}

The node is ready to accept our python calls.

    from twisted.internet import reactor
    from twotp import OneShotPortMapperFactory, readCookie, buildNodeName

    def gotConnection(inst):
        return inst.factory.callRemote(inst, "twisted", "get_user", 1
            ).addCallback(gotResult)

    def gotResult(resp):
        print "Got response", resp
        reactor.stop()

    def eb(error):
        print "Got error", error
        reactor.stop()

    cookie = readCookie()
    nodeName = buildNodeName('nodename')
    epmd = OneShotPortMapperFactory(nodeName, cookie)
    epmd.connectToNode("twisted_mnesia").addCallback(gotConnection).addErrback(eb)
    reactor.run()

If everything is fine, you should get the following response:

    Got response (<Atom at -0x48806e54, text 'user'>, 1, <Atom at -0x48806e74, text 'test1'>)