Suche
Close this search box.

A first shot at refactoring the OGP login in Python (pyogp) (technical)

Last weekend and yesterday I spent some time to refactor some code for the Python OGP library and tried to come up with a good structure for it. Now maybe I should explain first what I am talking about.

The Open Grid Protocol Login

The Open Grid Protocol as you might know is supposed to be the result of the Architecture Working Group’s (AWG) efforts on creating a standardized and open protocol for a decentralized metaverse based on what today is Second Life. It’s aim is to allow you to hook up your own servers, connect them to existing grids (such as Seond Life) or create complete new grids. As the work goes along Linden Lab tries to implement it on their side and they already started with that (more on it later). Additionally the AWG is working on a python library for this protocol which then can be used to create a test harness for testing components if they comply to the standard.

To understand the rest you need to know that in the future there is a distinction between an Agent Domain and a Region Domain. The first will handle login and everything around agents such as profile, groups, Instant Messaging, inventory. So it basically is some sort of the social network part of a grid.

The Region Domain handles the actual 3D simulation, knows about the land, where objects are, where avatars are etc. and will broadcast all that information to the connected clients. A Region Domain will consist of regions just as Second Life does today.

The Login process

The OGP Login Process

What’s defined so far in the OGP spec (draft 1) is the login process. This starts at the agent domain. It basically works as follows:

  1. The clients connects to the Agent Domain’s login webservice and sends it’s credentials
  2. The Agent Domain checks them and issues the client a so-called seed capability. This is an URL under which you can ask for access to various functionality of the Agent Domain.
  3. The client will ask the seed capability to issue a new capability for placing an avatar on a region (this one is called „place_avatar“).
  4. The Agent Domain again checks this request and issues a new capability (URL) for placing an avatar.
  5. The client send the URI of the region it wants to be placed on (as an avatar which is the 3d representation of an agent) to this place_avatar capability.
  6. The Agent Domain contacts the region identified by this URI and negotiates if this is ok or not. We assume it is.
  7. The Agent Domain replies with some information about the region to connect to such as the IP, port etc.

From here on the old protocol will be used for now which consists of lots of UDP packets and is not really well documented (but this will change with OGP).

Linden Lab so far implemented these steps on the server side and provided a script which runs down those steps in a quite plain python program.

My goal now was to refactor this to be more extensible and more component based. I did this by using the Zope Component Architecture (for an intro see here), buildout and eggs (for an egg introduction see here, buildout needs to be written). The result can be found in a base library package on Google code.

The status so far

I started with a doctest which shows how to use the library from a high level view. It looks like this:


Login
=====

>>> from pyogp.lib.base.credentials import PlainPasswordCredential
>>> from pyogp.lib.base.agentdomain import AgentDomain
>>> from pyogp.lib.base.regiondomain import Region

First we create some credentials:
>>> credentials = PlainPasswordCredential('Firstname', 'Lastname', 'password')

Then we need some agent domain to connect to. This might automatically retrieve some XRDS file to get the actual login endpoint:
>>> agentdomain = AgentDomain('http://agent.domain')

Now we can use both to get an agent object (which transparently handles capabilities etc.):
>>> agent = AgentDomain.login(credentials)

The next step is to use this agent to actually place the avatar somewhere. We therefor need a region:
>>> region = Region('http://region.uri')

Note that we can also first retrieve a RegionDomain object and ask this for possible regions and a map etc.


So let's place the agent in form of an avatar there (or try it at least):
>>> avatar = region.place_avatar(agent)

Now we should establish a presence there:

avatar.establish_presence()

As this is an infinite loop the question is how this could be handled. Maybe in a different thread?

So what’s happening here?

First of all I thought of how to start. And my idea was not to start with an agent because it mainly would be an empty hull but instead to start with credentials which then get converted to an agent on logging in to an agent domain. Hence PlainPasswordCredentials was created. You can imagine other credential classes which might take an OpenID, some MD5 password or whatever the agent domain supports.

My idea for the agent domain would also be that the main URL of it will have an XRDS file attached which can serve as service catalogue and define which login methods are supported and under which URL they can be found (as well as other servicetype/URL pairs). But that’s up for discussion, right now you simply give the login URI.

Next is to create an AgentDomain instance identified by that login URI: AgentDomain(uri). The AgentDomain class just has one method right now: login(). This will take the credential object and send it to the Agent Domain identified by it. It hopefully gets back a Seed Capability (or an error and error handling is something which also needs to be discussed and defined).

The interesting bit here is how the PlainPasswordCredential is serialized. Right now we send LLSD (an XML format invented by Linden Lab to serialize such data) but in the future it might also be JSON (because it’s more known to most web developers). To be flexible I implemented the serialization method not as method of the credential class but as an adapter (see the ZCA intro and this file for details).

So what you see in there is an adapter which serializes it to LLSD but it’s easy now to replace this to serialize in JSON format (the same needs to be done on the reading/deserialization side of course). This can even be replaced without touching the actual library code.

Here is the code which uses this serializer:

        serializer = ICredentialSerializer(credentials) # convert to string via adapter
        payload = serializer.serialize()
        headers = serializer.headers

(found in agentdomain.py)

Note that LLSD is never mentioned here, the configuration will use the LLSD serializer by default.

Using the agent

What’s returned be the login process is an Agent object which can be used to further place an avatar on a region.

For this we first instantiate a region object and give it the URI of the region we want to be connected to:

region = Region('http://region.uri')

Then we can use this region object (which right now is more or less an empty class) for the place_avatar function:

        place = IPlaceAvatarAdapter(agent)
        avatar = place(region)

So what’s happening here is again the usage of an adapter. It basically says: „Take the agent object and convert it to an object which implements IPlaceAvatarAdapter“. IPlaceAvatarAdapter is defined in interfaces.py and looks as follows:


class IPlaceAvatarAdapter(Interface):
    """adapts an agents to a method which can place an avatar on a region"""

    def __call__(region):
        """takes a region objects and tries to place the agent there as an avatar

        return an IAvatar"""

So it basically has one method: __call__ which takes the region and returns an avatar (in case everything went fine).

Now this method has to do two things:

  1. Retrieve a capability for the place_avatar function
  2. Call this function

You can find the implementation of this adapter in agent.py.

The context of this adapter is the agent (as this is passed to it) so it knows it implements IAgent. IAgent now has an attribute agentdomain which this adapter uses to retrieve the seed capability (the AgentDomain class right now is missing an interface definition but this will be added later and will include the seed_cap attribute).
The seed_cap contains an instance of the SeedCapability class which has the special get() method to retrieve further capabilities (which are simply Capability objects). A Capability object is rather easy, it has a name and a private URL associated with that capability which you can then send some payload to. You can find it’s implemenation in caps.py.

We stop in this step as I just got a 400 error back from the agent domain when trying to place the avatar. But maybe it helps already to understand what I have in mind and how it can be structured.

What’s left to do?

Well, mostly everything ;-)

But I think it’s important to come up with common patterns such as

  • how we handle seed caps,
  • how we can define serializers and deserialzers (they could also simply be utilities btw),
  • how we handle errors,
  • how we factor out the networking part to eventually be able to use eventlet or any other library (twisted?)

and so on. This is just a start and some sort of brainstorming.

Teile diesen Beitrag