← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~julian-edwards/maas/twisted-provisioning into lp:maas

 

Julian Edwards has proposed merging lp:~julian-edwards/maas/twisted-provisioning into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~julian-edwards/maas/twisted-provisioning/+merge/90092

Add the basic TAP framework for the provisioning server. A lot of code was cargo-culted from txlongpoll.
-- 
https://code.launchpad.net/~julian-edwards/maas/twisted-provisioning/+merge/90092
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~julian-edwards/maas/twisted-provisioning into lp:maas.
=== added directory 'src/provisioningserver'
=== added file 'src/provisioningserver/__init__.py'
=== added file 'src/provisioningserver/amqpclient.py'
--- src/provisioningserver/amqpclient.py	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/amqpclient.py	2012-01-25 12:26:24 +0000
@@ -0,0 +1,124 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# Shamelessly cargo-culted from the txlongpoll source.
+
+"""
+Asynchronous client for AMQP using txAMQP.
+"""
+
+from __future__ import (
+    print_function,
+    unicode_literals,
+    )
+
+import os.path
+
+from twisted.internet.defer import maybeDeferred
+from twisted.internet.protocol import ReconnectingClientFactory
+from txamqp.client import TwistedDelegate
+from txamqp.protocol import AMQClient
+from txamqp.queue import Closed
+from txamqp.spec import load as load_spec
+
+__metaclass__ = type
+__all__ = [
+    "AMQFactory",
+    ]
+
+
+class AMQClientWithCallback(AMQClient):
+    """
+    An C{AMQClient} that notifies connections with a callback.
+
+    @ivar connected_callback: callback called when C{connectionMade} is
+        called. It takes one argument, the protocol instance itself.
+    """
+
+    def __init__(self, connected_callback, *args, **kwargs):
+        AMQClient.__init__(self, *args, **kwargs)
+        self.connected_callback = connected_callback
+
+    def connectionMade(self):
+        AMQClient.connectionMade(self)
+        self.connected_callback(self)
+
+
+_base_dir = os.path.dirname(os.path.abspath(__file__))
+AMQP0_8_SPEC = load_spec(os.path.join(_base_dir, "specs", "amqp0-8.xml"))
+del _base_dir
+
+
+class AMQFactory(ReconnectingClientFactory):
+    """
+    A C{ClientFactory} for C{AMQClient} protocol with reconnecting facilities.
+
+    @ivar user: the user name to use to connect to the AMQP server.
+    @ivar password: the corresponding password of the user.
+    @ivar vhost: the AMQP vhost to create connections against.
+    @ivar connected_callback: callback called when a successful connection
+        happened. It takes one argument, the channel opened for the connection.
+    @ivar disconnected_callback: callback called when a previously connected
+        connection was lost. It takes no argument.
+    """
+    protocol = AMQClientWithCallback
+    initialDelay = 0.01
+
+    def __init__(self, user, password, vhost, connected_callback,
+                 disconnected_callback, failed_callback, spec=None):
+        self.user = user
+        self.password = password
+        self.vhost = vhost
+        self.delegate = TwistedDelegate()
+        if spec is None:
+            spec = AMQP0_8_SPEC
+        self.spec = spec
+        self.connected_callback = connected_callback
+        self.disconnected_callback = disconnected_callback
+        self.failed_callback = failed_callback
+
+    def buildProtocol(self, addr):
+        """
+        Create the protocol instance and returns it for letting Twisted
+        connect it to the transport.
+
+        @param addr: the attributed address, unused for now.
+        """
+        protocol = self.protocol(self.clientConnectionMade, self.delegate,
+                                 self.vhost, spec=self.spec)
+        protocol.factory = self
+        return protocol
+
+    def clientConnectionMade(self, client):
+        """
+        Called when a connection succeeds: login to the server, and open a
+        channel against it.
+        """
+        self.resetDelay()
+
+        def started(ignored):
+            # We don't care about authenticate result as long as it succeeds
+            return client.channel(1).addCallback(got_channel)
+
+        def got_channel(channel):
+            return channel.channel_open().addCallback(opened, channel)
+
+        def opened(ignored, channel):
+            deferred = maybeDeferred(
+                self.connected_callback, (client, channel))
+            deferred.addErrback(catch_closed)
+
+        def catch_closed(failure):
+            failure.trap(Closed)
+
+        deferred = client.authenticate(self.user, self.password)
+        return deferred.addCallback(started)
+
+    def clientConnectionLost(self, connector, reason):
+        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
+        self.disconnected_callback()
+
+    def clientConnectionFailed(self, connector, reason):
+        ReconnectingClientFactory.clientConnectionFailed(
+            self, connector, reason)
+        self.failed_callback((connector, reason))

=== added file 'src/provisioningserver/plugin.py'
--- src/provisioningserver/plugin.py	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/plugin.py	2012-01-25 12:26:24 +0000
@@ -0,0 +1,150 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+from __future__ import (
+    print_function,
+    unicode_literals,
+    )
+
+"""Twisted Application Plugin code for the MaaS provisioning server"""
+
+import setproctitle
+import signal
+import sys
+
+import oops
+from oops_datedir_repo import DateDirRepo
+from oops_twisted import (
+    Config as oops_config,
+    defer_publisher,
+    OOPSObserver,
+    )
+from twisted.application.internet import TCPClient
+from twisted.application.service import (
+    IServiceMaker,
+    MultiService,
+    )
+from twisted.internet import reactor
+from twisted.plugin import IPlugin
+from twisted.python import (
+    log,
+    usage,
+    )
+from twisted.python.logfile import LogFile
+from twisted.python.log import (
+    addObserver,
+    FileLogObserver,
+    )
+from zope.interface import implements
+
+from amqpclient import AMQFactory
+
+__metaclass__ = type
+__all__ = []
+
+
+def getRotatableLogFileObserver(filename):
+    """Setup a L{LogFile} for the given application."""
+    if filename != '-':
+        logfile = LogFile.fromFullPath(
+            filename, rotateLength=None, defaultMode=0644)
+        def signal_handler(sig, frame):
+            reactor.callFromThread(logfile.reopen)
+        signal.signal(signal.SIGUSR1, signal_handler)
+    else:
+        logfile = sys.stdout
+    return FileLogObserver(logfile)
+
+
+def setUpOOPSHandler(options, logfile):
+    """Add OOPS handling based on the passed command line options."""
+    config = oops_config()
+
+    # Add the oops publisher that writes files in the configured place
+    # if the command line option was set.
+
+    if options["oops-dir"]:
+        repo = DateDirRepo(options["oops-dir"])
+        config.publishers.append(
+            defer_publisher(oops.publish_new_only(repo.publish)))
+
+    if options["oops-reporter"]:
+        config.template['reporter'] = options["oops-reporter"]
+
+    observer = OOPSObserver(config, logfile.emit)
+    addObserver(observer.emit)
+    return observer
+
+
+class Options(usage.Options):
+    """Command line options for the provisioning server."""
+
+    optParameters = [
+        ["logfile", "l", "provisioningserver.log", "Logfile name."],
+        ["brokerport", "p", 5672, "Broker port"],
+        ["brokerhost", "h", '127.0.0.1', "Broker host"],
+        ["brokeruser", "u", None, "Broker user"],
+        ["brokerpassword", "a", None, "Broker password"],
+        ["brokervhost", "v", '/', "Broker vhost"],
+        ["oops-dir", "r", None, "Where to write OOPS reports"],
+        ["oops-reporter", "o", "MAAS-PS", "String identifying this service."],
+        ]
+
+    def postOptions(self):
+        for arg in ('brokeruser', 'brokerpassword'):
+            if not self[arg]:
+                raise usage.UsageError("--%s must be specified." % arg)
+        for int_arg in ('brokerport'):
+            try:
+                self[int_arg] = int(self[int_arg])
+            except (TypeError, ValueError):
+                raise usage.UsageError("--%s must be an integer." % int_arg)
+        if not self["oops-reporter"] and self["oops-dir"]:
+            raise usage.UsageError(
+                "A reporter must be supplied to identify reports "
+                "from this service from other OOPS reports.")
+
+class ProvisioningServiceMaker(object):
+    """Create a service for the Twisted plugin."""
+
+    implements(IServiceMaker, IPlugin)
+
+    def __init__(self, name, description):
+        self.tapname = name
+        self.description = description
+
+    def makeService(self, options):
+        """Construct a service."""
+        # Required to hide the command line options that include a password.
+        # There is a small window where it can be seen though, between
+        # invocation and when this code runs.
+        setproctitle.setproctitle("maas provisioning service")
+
+        logfile = getRotatableLogFileObserver(options["logfile"])
+        setUpOOPSHandler(options, logfile)
+
+        broker_port = options["brokerport"]
+        broker_host = options["brokerhost"]
+        broker_user = options["brokeruser"]
+        broker_password = options["brokerpassword"]
+        broker_vhost = options["brokervhost"]
+
+        # TODO: define callbacks for Rabbit connectivity.
+        # e.g. construct a manager object.
+        client_factory = AMQFactory(
+            broker_user, broker_password, broker_vhost,
+            CONNECTED_CALLBACK, DISCONNECTED_CALLBACK,
+            lambda (connector, reason): log.err(reason, "Connection failed"))
+
+        # TODO: Create services here, e.g.
+        # service1 = thing
+        # service2 = thing2
+        # services = MultiService()
+        # services.addService(service1)
+        # services.addService(service2)
+        # return services
+
+        client_service = TCPClient(broker_host, broker_port, client_factory)
+        services = MultiService()
+        services.addService(client_service)
+        return services

=== added directory 'src/provisioningserver/specs'
=== added file 'src/provisioningserver/specs/amqp0-8.xml'
--- src/provisioningserver/specs/amqp0-8.xml	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/specs/amqp0-8.xml	2012-01-25 12:26:24 +0000
@@ -0,0 +1,771 @@
+<?xml version="1.0"?>
+<!--
+Copyright (c) 2009 AMQP Working Group.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<amqp major="8" minor="0" port="5672">
+  <constant name="frame method" value="1"/>
+  <constant name="frame header" value="2"/>
+  <constant name="frame body" value="3"/>
+  <constant name="frame oob method" value="4"/>
+  <constant name="frame oob header" value="5"/>
+  <constant name="frame oob body" value="6"/>
+  <constant name="frame trace" value="7"/>
+  <constant name="frame heartbeat" value="8"/>
+  <constant name="frame min size" value="4096"/>
+  <constant name="frame end" value="206"/>
+  <constant name="reply success" value="200"/>
+  <constant name="not delivered" value="310" class="soft error"/>
+  <constant name="content too large" value="311" class="soft error"/>
+  <constant name="connection forced" value="320" class="hard error"/>
+  <constant name="invalid path" value="402" class="hard error"/>
+  <constant name="access refused" value="403" class="soft error"/>
+  <constant name="not found" value="404" class="soft error"/>
+  <constant name="resource locked" value="405" class="soft error"/>
+  <constant name="frame error" value="501" class="hard error"/>
+  <constant name="syntax error" value="502" class="hard error"/>
+  <constant name="command invalid" value="503" class="hard error"/>
+  <constant name="channel error" value="504" class="hard error"/>
+  <constant name="resource error" value="506" class="hard error"/>
+  <constant name="not allowed" value="530" class="hard error"/>
+  <constant name="not implemented" value="540" class="hard error"/>
+  <constant name="internal error" value="541" class="hard error"/>
+  <domain name="access ticket" type="short">
+    <assert check="ne" value="0"/>
+  </domain>
+  <domain name="class id" type="short"/>
+  <domain name="consumer tag" type="shortstr"/>
+  <domain name="delivery tag" type="longlong"/>
+  <domain name="exchange name" type="shortstr">
+    <assert check="length" value="127"/>
+  </domain>
+  <domain name="known hosts" type="shortstr"/>
+  <domain name="method id" type="short"/>
+  <domain name="no ack" type="bit"/>
+  <domain name="no local" type="bit"/>
+  <domain name="path" type="shortstr">
+    <assert check="notnull"/>
+    <assert check="syntax" rule="path"/>
+    <assert check="length" value="127"/>
+  </domain>
+  <domain name="peer properties" type="table"/>
+  <domain name="queue name" type="shortstr">
+    <assert check="length" value="127"/>
+  </domain>
+  <domain name="redelivered" type="bit"/>
+  <domain name="reply code" type="short">
+    <assert check="notnull"/>
+  </domain>
+  <domain name="reply text" type="shortstr">
+    <assert check="notnull"/>
+  </domain>
+  <class name="connection" handler="connection" index="10">
+    <chassis name="server" implement="MUST"/>
+    <chassis name="client" implement="MUST"/>
+    <method name="start" synchronous="1" index="10">
+      <chassis name="client" implement="MUST"/>
+      <response name="start-ok"/>
+      <field name="version major" type="octet"/>
+      <field name="version minor" type="octet"/>
+      <field name="server properties" domain="peer properties"/>
+      <field name="mechanisms" type="longstr">
+        <see name="security mechanisms"/>
+        <assert check="notnull"/>
+      </field>
+      <field name="locales" type="longstr">
+        <assert check="notnull"/>
+      </field>
+    </method>
+    <method name="start-ok" synchronous="1" index="11">
+      <chassis name="server" implement="MUST"/>
+      <field name="client properties" domain="peer properties"/>
+      <field name="mechanism" type="shortstr">
+        <assert check="notnull"/>
+      </field>
+      <field name="response" type="longstr">
+        <assert check="notnull"/>
+      </field>
+      <field name="locale" type="shortstr">
+        <assert check="notnull"/>
+      </field>
+    </method>
+    <method name="secure" synchronous="1" index="20">
+      <chassis name="client" implement="MUST"/>
+      <response name="secure-ok"/>
+      <field name="challenge" type="longstr">
+        <see name="security mechanisms"/>
+      </field>
+    </method>
+    <method name="secure-ok" synchronous="1" index="21">
+      <chassis name="server" implement="MUST"/>
+      <field name="response" type="longstr">
+        <assert check="notnull"/>
+      </field>
+    </method>
+    <method name="tune" synchronous="1" index="30">
+      <chassis name="client" implement="MUST"/>
+      <response name="tune-ok"/>
+      <field name="channel max" type="short"/>
+      <field name="frame max" type="long"/>
+      <field name="heartbeat" type="short"/>
+    </method>
+    <method name="tune-ok" synchronous="1" index="31">
+      <chassis name="server" implement="MUST"/>
+      <field name="channel max" type="short">
+        <assert check="notnull"/>
+        <assert check="le" method="tune" field="channel max"/>
+      </field>
+      <field name="frame max" type="long"/>
+      <field name="heartbeat" type="short"/>
+    </method>
+    <method name="open" synchronous="1" index="40">
+      <chassis name="server" implement="MUST"/>
+      <response name="open-ok"/>
+      <response name="redirect"/>
+      <field name="virtual host" domain="path">
+        <assert check="regexp" value="^[a-zA-Z0-9/-_]+$"/>
+      </field>
+      <field name="capabilities" type="shortstr"/>
+      <field name="insist" type="bit"/>
+    </method>
+    <method name="open-ok" synchronous="1" index="41">
+      <chassis name="client" implement="MUST"/>
+      <field name="known hosts" domain="known hosts"/>
+    </method>
+    <method name="redirect" synchronous="1" index="50">
+      <chassis name="client" implement="MAY"/>
+      <field name="host" type="shortstr">
+        <assert check="notnull"/>
+      </field>
+      <field name="known hosts" domain="known hosts"/>
+    </method>
+    <method name="close" synchronous="1" index="60">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <response name="close-ok"/>
+      <field name="reply code" domain="reply code"/>
+      <field name="reply text" domain="reply text"/>
+      <field name="class id" domain="class id"/>
+      <field name="method id" domain="class id"/>
+    </method>
+    <method name="close-ok" synchronous="1" index="61">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+    </method>
+  </class>
+  <class name="channel" handler="channel" index="20">
+    <chassis name="server" implement="MUST"/>
+    <chassis name="client" implement="MUST"/>
+    <method name="open" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="open-ok"/>
+      <field name="out of band" type="shortstr">
+        <assert check="null"/>
+      </field>
+    </method>
+    <method name="open-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="flow" synchronous="1" index="20">
+      <chassis name="server" implement="MUST"/>
+      <chassis name="client" implement="MUST"/>
+      <response name="flow-ok"/>
+      <field name="active" type="bit"/>
+    </method>
+    <method name="flow-ok" index="21">
+      <chassis name="server" implement="MUST"/>
+      <chassis name="client" implement="MUST"/>
+      <field name="active" type="bit"/>
+    </method>
+    <method name="alert" index="30">
+      <chassis name="client" implement="MUST"/>
+      <field name="reply code" domain="reply code"/>
+      <field name="reply text" domain="reply text"/>
+      <field name="details" type="table"/>
+    </method>
+    <method name="close" synchronous="1" index="40">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <response name="close-ok"/>
+      <field name="reply code" domain="reply code"/>
+      <field name="reply text" domain="reply text"/>
+      <field name="class id" domain="class id"/>
+      <field name="method id" domain="method id"/>
+    </method>
+    <method name="close-ok" synchronous="1" index="41">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+    </method>
+  </class>
+  <class name="access" handler="connection" index="30">
+    <chassis name="server" implement="MUST"/>
+    <chassis name="client" implement="MUST"/>
+    <method name="request" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="request-ok"/>
+      <field name="realm" domain="path"/>
+      <field name="exclusive" type="bit"/>
+      <field name="passive" type="bit"/>
+      <field name="active" type="bit"/>
+      <field name="write" type="bit"/>
+      <field name="read" type="bit"/>
+    </method>
+    <method name="request-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+      <field name="ticket" domain="access ticket"/>
+    </method>
+  </class>
+  <class name="exchange" handler="channel" index="40">
+    <chassis name="server" implement="MUST"/>
+    <chassis name="client" implement="MUST"/>
+    <method name="declare" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="declare-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="exchange" domain="exchange name">
+        <assert check="regexp" value="^[a-zA-Z0-9-_.:]+$"/>
+      </field>
+      <field name="type" type="shortstr">
+        <assert check="regexp" value="^[a-zA-Z0-9-_.:]+$"/>
+      </field>
+      <field name="passive" type="bit"/>
+      <field name="durable" type="bit"/>
+      <field name="auto delete" type="bit"/>
+      <field name="internal" type="bit"/>
+      <field name="nowait" type="bit"/>
+      <field name="arguments" type="table"/>
+    </method>
+    <method name="declare-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="delete" synchronous="1" index="20">
+      <chassis name="server" implement="MUST"/>
+      <response name="delete-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="exchange" domain="exchange name">
+        <assert check="notnull"/>
+      </field>
+      <field name="if unused" type="bit"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="delete-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+    </method>
+  </class>
+  <class name="queue" handler="channel" index="50">
+    <chassis name="server" implement="MUST"/>
+    <chassis name="client" implement="MUST"/>
+    <method name="declare" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="declare-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name">
+        <assert check="regexp" value="^[a-zA-Z0-9-_.:]*$"/>
+      </field>
+      <field name="passive" type="bit"/>
+      <field name="durable" type="bit"/>
+      <field name="exclusive" type="bit"/>
+      <field name="auto delete" type="bit"/>
+      <field name="nowait" type="bit"/>
+      <field name="arguments" type="table"/>
+    </method>
+    <method name="declare-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+      <field name="queue" domain="queue name">
+        <assert check="notnull"/>
+      </field>
+      <field name="message count" type="long"/>
+      <field name="consumer count" type="long"/>
+    </method>
+    <method name="bind" synchronous="1" index="20">
+      <chassis name="server" implement="MUST"/>
+      <response name="bind-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+      <field name="nowait" type="bit"/>
+      <field name="arguments" type="table"/>
+    </method>
+    <method name="bind-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="unbind" synchronous="1" index="50">
+      <chassis name="server" implement="MUST"/>
+      <response name="unbind-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" domain="shortstr"/>
+      <field name="arguments" domain="table"/>
+    </method>
+    <method name="unbind-ok" synchronous="1" index="51">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="purge" synchronous="1" index="30">
+      <chassis name="server" implement="MUST"/>
+      <response name="purge-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="purge-ok" synchronous="1" index="31">
+      <chassis name="client" implement="MUST"/>
+      <field name="message count" type="long"/>
+    </method>
+    <method name="delete" synchronous="1" index="40">
+      <chassis name="server" implement="MUST"/>
+      <response name="delete-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="if unused" type="bit"/>
+      <field name="if empty" type="bit">
+        <test/>
+      </field>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="delete-ok" synchronous="1" index="41">
+      <chassis name="client" implement="MUST"/>
+      <field name="message count" type="long"/>
+    </method>
+  </class>
+  <class name="basic" handler="channel" index="60">
+    <chassis name="server" implement="MUST"/>
+    <chassis name="client" implement="MAY"/>
+    <field name="content type" type="shortstr"/>
+    <field name="content encoding" type="shortstr"/>
+    <field name="headers" type="table"/>
+    <field name="delivery mode" type="octet"/>
+    <field name="priority" type="octet"/>
+    <field name="correlation id" type="shortstr"/>
+    <field name="reply to" type="shortstr"/>
+    <field name="expiration" type="shortstr"/>
+    <field name="message id" type="shortstr"/>
+    <field name="timestamp" type="timestamp"/>
+    <field name="type" type="shortstr"/>
+    <field name="user id" type="shortstr"/>
+    <field name="app id" type="shortstr"/>
+    <field name="cluster id" type="shortstr"/>
+    <method name="qos" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="qos-ok"/>
+      <field name="prefetch size" type="long"/>
+      <field name="prefetch count" type="short"/>
+      <field name="global" type="bit"/>
+    </method>
+    <method name="qos-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="consume" synchronous="1" index="20">
+      <chassis name="server" implement="MUST"/>
+      <response name="consume-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="no local" domain="no local"/>
+      <field name="no ack" domain="no ack"/>
+      <field name="exclusive" type="bit"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="consume-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+    </method>
+    <method name="cancel" synchronous="1" index="30">
+      <chassis name="server" implement="MUST"/>
+      <response name="cancel-ok"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="cancel-ok" synchronous="1" index="31">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+    </method>
+    <method name="publish" content="1" index="40">
+      <chassis name="server" implement="MUST"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+      <field name="mandatory" type="bit"/>
+      <field name="immediate" type="bit"/>
+    </method>
+    <method name="return" content="1" index="50">
+      <chassis name="client" implement="MUST"/>
+      <field name="reply code" domain="reply code"/>
+      <field name="reply text" domain="reply text"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+    </method>
+    <method name="deliver" content="1" index="60">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="redelivered" domain="redelivered"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+    </method>
+    <method name="get" synchronous="1" index="70">
+      <response name="get-ok"/>
+      <response name="get-empty"/>
+      <chassis name="server" implement="MUST"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="no ack" domain="no ack"/>
+    </method>
+    <method name="get-ok" synchronous="1" content="1" index="71">
+      <chassis name="client" implement="MAY"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="redelivered" domain="redelivered"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+      <field name="message count" type="long"/>
+    </method>
+    <method name="get-empty" synchronous="1" index="72">
+      <chassis name="client" implement="MAY"/>
+      <field name="cluster id" type="shortstr"/>
+    </method>
+    <method name="ack" index="80">
+      <chassis name="server" implement="MUST"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="multiple" type="bit"/>
+    </method>
+    <method name="reject" index="90">
+      <chassis name="server" implement="MUST"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="requeue" type="bit"/>
+    </method>
+    <method name="recover" index="100">
+      <chassis name="server" implement="MUST"/>
+      <field name="requeue" type="bit"/>
+    </method>
+  </class>
+  <class name="file" handler="channel" index="70">
+    <chassis name="server" implement="MAY"/>
+    <chassis name="client" implement="MAY"/>
+    <field name="content type" type="shortstr"/>
+    <field name="content encoding" type="shortstr"/>
+    <field name="headers" type="table"/>
+    <field name="priority" type="octet"/>
+    <field name="reply to" type="shortstr"/>
+    <field name="message id" type="shortstr"/>
+    <field name="filename" type="shortstr"/>
+    <field name="timestamp" type="timestamp"/>
+    <field name="cluster id" type="shortstr"/>
+    <method name="qos" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="qos-ok"/>
+      <field name="prefetch size" type="long"/>
+      <field name="prefetch count" type="short"/>
+      <field name="global" type="bit"/>
+    </method>
+    <method name="qos-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="consume" synchronous="1" index="20">
+      <chassis name="server" implement="MUST"/>
+      <response name="consume-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="no local" domain="no local"/>
+      <field name="no ack" domain="no ack"/>
+      <field name="exclusive" type="bit"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="consume-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+    </method>
+    <method name="cancel" synchronous="1" index="30">
+      <chassis name="server" implement="MUST"/>
+      <response name="cancel-ok"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="cancel-ok" synchronous="1" index="31">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+    </method>
+    <method name="open" synchronous="1" index="40">
+      <response name="open-ok"/>
+      <chassis name="server" implement="MUST"/>
+      <chassis name="client" implement="MUST"/>
+      <field name="identifier" type="shortstr"/>
+      <field name="content size" type="longlong"/>
+    </method>
+    <method name="open-ok" synchronous="1" index="41">
+      <response name="stage"/>
+      <chassis name="server" implement="MUST"/>
+      <chassis name="client" implement="MUST"/>
+      <field name="staged size" type="longlong"/>
+    </method>
+    <method name="stage" content="1" index="50">
+      <chassis name="server" implement="MUST"/>
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="publish" index="60">
+      <chassis name="server" implement="MUST"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+      <field name="mandatory" type="bit"/>
+      <field name="immediate" type="bit"/>
+      <field name="identifier" type="shortstr"/>
+    </method>
+    <method name="return" content="1" index="70">
+      <chassis name="client" implement="MUST"/>
+      <field name="reply code" domain="reply code"/>
+      <field name="reply text" domain="reply text"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+    </method>
+    <method name="deliver" index="80">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="redelivered" domain="redelivered"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+      <field name="identifier" type="shortstr"/>
+    </method>
+    <method name="ack" index="90">
+      <chassis name="server" implement="MUST"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="multiple" type="bit"/>
+    </method>
+    <method name="reject" index="100">
+      <chassis name="server" implement="MUST"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="requeue" type="bit"/>
+    </method>
+  </class>
+  <class name="stream" handler="channel" index="80">
+    <chassis name="server" implement="MAY"/>
+    <chassis name="client" implement="MAY"/>
+    <field name="content type" type="shortstr"/>
+    <field name="content encoding" type="shortstr"/>
+    <field name="headers" type="table"/>
+    <field name="priority" type="octet"/>
+    <field name="timestamp" type="timestamp"/>
+    <method name="qos" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="qos-ok"/>
+      <field name="prefetch size" type="long"/>
+      <field name="prefetch count" type="short"/>
+      <field name="consume rate" type="long"/>
+      <field name="global" type="bit"/>
+    </method>
+    <method name="qos-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="consume" synchronous="1" index="20">
+      <chassis name="server" implement="MUST"/>
+      <response name="consume-ok"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="queue" domain="queue name"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="no local" domain="no local"/>
+      <field name="exclusive" type="bit"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="consume-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+    </method>
+    <method name="cancel" synchronous="1" index="30">
+      <chassis name="server" implement="MUST"/>
+      <response name="cancel-ok"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="nowait" type="bit"/>
+    </method>
+    <method name="cancel-ok" synchronous="1" index="31">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+    </method>
+    <method name="publish" content="1" index="40">
+      <chassis name="server" implement="MUST"/>
+      <field name="ticket" domain="access ticket"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+      <field name="mandatory" type="bit"/>
+      <field name="immediate" type="bit"/>
+    </method>
+    <method name="return" content="1" index="50">
+      <chassis name="client" implement="MUST"/>
+      <field name="reply code" domain="reply code"/>
+      <field name="reply text" domain="reply text"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="routing key" type="shortstr"/>
+    </method>
+    <method name="deliver" content="1" index="60">
+      <chassis name="client" implement="MUST"/>
+      <field name="consumer tag" domain="consumer tag"/>
+      <field name="delivery tag" domain="delivery tag"/>
+      <field name="exchange" domain="exchange name"/>
+      <field name="queue" domain="queue name">
+        <assert check="notnull"/>
+      </field>
+    </method>
+  </class>
+  <class name="tx" handler="channel" index="90">
+    <chassis name="server" implement="SHOULD"/>
+    <chassis name="client" implement="MAY"/>
+    <method name="select" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="select-ok"/>
+    </method>
+    <method name="select-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="commit" synchronous="1" index="20">
+      <chassis name="server" implement="MUST"/>
+      <response name="commit-ok"/>
+    </method>
+    <method name="commit-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="rollback" synchronous="1" index="30">
+      <chassis name="server" implement="MUST"/>
+      <response name="rollback-ok"/>
+    </method>
+    <method name="rollback-ok" synchronous="1" index="31">
+      <chassis name="client" implement="MUST"/>
+    </method>
+  </class>
+  <class name="dtx" handler="channel" index="100">
+    <chassis name="server" implement="MAY"/>
+    <chassis name="client" implement="MAY"/>
+    <method name="select" synchronous="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <response name="select-ok"/>
+    </method>
+    <method name="select-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+    </method>
+    <method name="start" synchronous="1" index="20">
+      <chassis name="server" implement="MAY"/>
+      <response name="start-ok"/>
+      <field name="dtx identifier" type="shortstr">
+        <assert check="notnull"/>
+      </field>
+    </method>
+    <method name="start-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+    </method>
+  </class>
+  <class name="tunnel" handler="tunnel" index="110">
+    <chassis name="server" implement="MAY"/>
+    <chassis name="client" implement="MAY"/>
+    <field name="headers" type="table"/>
+    <field name="proxy name" type="shortstr"/>
+    <field name="data name" type="shortstr"/>
+    <field name="durable" type="octet"/>
+    <field name="broadcast" type="octet"/>
+    <method name="request" content="1" index="10">
+      <chassis name="server" implement="MUST"/>
+      <field name="meta data" type="table"/>
+    </method>
+  </class>
+  <class name="test" handler="channel" index="120">
+    <chassis name="server" implement="MUST"/>
+    <chassis name="client" implement="SHOULD"/>
+    <method name="integer" synchronous="1" index="10">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <response name="integer-ok"/>
+      <field name="integer 1" type="octet"/>
+      <field name="integer 2" type="short"/>
+      <field name="integer 3" type="long"/>
+      <field name="integer 4" type="longlong"/>
+      <field name="operation" type="octet">
+        <assert check="enum">
+          <value name="add"/>
+          <value name="min"/>
+          <value name="max"/>
+        </assert>
+      </field>
+    </method>
+    <method name="integer-ok" synchronous="1" index="11">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <field name="result" type="longlong"/>
+    </method>
+    <method name="string" synchronous="1" index="20">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <response name="string-ok"/>
+      <field name="string 1" type="shortstr"/>
+      <field name="string 2" type="longstr"/>
+      <field name="operation" type="octet">
+        <assert check="enum">
+          <value name="add"/>
+          <value name="min"/>
+          <value name="max"/>
+        </assert>
+      </field>
+    </method>
+    <method name="string-ok" synchronous="1" index="21">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <field name="result" type="longstr"/>
+    </method>
+    <method name="table" synchronous="1" index="30">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <response name="table-ok"/>
+      <field name="table" type="table"/>
+      <field name="integer op" type="octet">
+        <assert check="enum">
+          <value name="add"/>
+          <value name="min"/>
+          <value name="max"/>
+        </assert>
+      </field>
+      <field name="string op" type="octet">
+        <assert check="enum">
+          <value name="add"/>
+          <value name="min"/>
+          <value name="max"/>
+        </assert>
+      </field>
+    </method>
+    <method name="table-ok" synchronous="1" index="31">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <field name="integer result" type="longlong"/>
+      <field name="string result" type="longstr"/>
+    </method>
+    <method name="content" synchronous="1" content="1" index="40">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <response name="content-ok"/>
+    </method>
+    <method name="content-ok" synchronous="1" content="1" index="41">
+      <chassis name="client" implement="MUST"/>
+      <chassis name="server" implement="MUST"/>
+      <field name="content checksum" type="long"/>
+    </method>
+  </class>
+</amqp>

=== added directory 'twisted'
=== added directory 'twisted/plugins'
=== added file 'twisted/plugins/maasps.py'
--- twisted/plugins/maasps.py	1970-01-01 00:00:00 +0000
+++ twisted/plugins/maasps.py	2012-01-25 12:26:24 +0000
@@ -0,0 +1,22 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+from __future__ import (
+    absolute_import,
+    print_function,
+    unicode_literals,
+    )
+
+"""Twisted Application Plugin for the Maas provisioning server."""
+
+__metaclass__ = type
+__all__ = []
+
+
+from provisioningserver.plugin import ProvisioningServiceMaker
+
+# Construct objects which *provide* the relevant interfaces. The name of
+# these variables is irrelevant, as long as there are *some* names bound
+# to # providers of IPlugin and IServiceMaker.
+
+service = ProvisioningServiceMaker(...) # TODO: finish


Follow ups