Your IP : 172.28.240.42


Current Path : /usr/lib/python2.7/dist-packages/landscape/broker/
Upload File :
Current File : //usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyc


Tc@sdZddlZddlZddlmZddlmZmZddlm	Z	ddl
mZmZddl
mZmZddlmZdd	lmZmZmZd
efdYZdZdS(
sb6Manage outgoing and incoming messages when communicating with the server.

The protocol to communicate between the client and the server has been designed
to be very robust so that messages are not lost. In addition it is (vaguely)
symmetric, as the client and server need to send messages both ways.

Client->Server Payload
======================

All message payloads are bpickled with L{landscape.lib.bpickle.dumps}. Client
to server payloads are C{dict}s of the form::

  {'server-api': SERVER_API_VERSION,
   'client-api': CLIENT_API_VERSION,
   'sequence': SEQUENCE_NUMBER,
   'accepted-types': SERVER_ACCEPTED_TYPES_DIGEST,
   'messages': MESSAGES,
   'total-messages': TOTAL_COUNT_OF_PENDING_MESSAGES,
   'next-expected-sequence': EXPECTED_SEQUENCE_NUMBER,
   'client-accepted-types': CLIENT_ACCEPTED_TYPES (optional)}

The values have the following semantics:

  - C{SERVER_API_VERSION}: The API version that is required on the server
    in order to process the messages in this payload (the schema and semantics
    of message types are usually different for different API versions).

  - C{CLIENT_API_VERSION}: The API version of the client, hinting the server
    about the schema and semantics of the messages types accepted by the client
    (see below).

  - C{SEQUENCE_NUMBER}: A monotonically increasing nonnegative integer. The
    meaning of this is described below.

  - C{SERVER_ACCEPTED_TYPES_DIGEST}: A hash of the message types that the
    client thinks are currently accepted by the server. The server can use it
    to know whether to send the client a new up-to-date list of accepted
    message types.

  - C{MESSAGES}: A python list of messages, described below.

  - C{TOTAL_COUNT_OF_PENDING_MESSAGES}: The total number of messages in the
    client outgoing queue. This is includes the number of messages being sent
    in this payload, plus any other messages still pending and not included
    here.

  - C{EXPECTED_SEQUENCE_NUMBER}: The sequence number which the client expects
    the next message sent from the server to have.

  - C{CLIENT_ACCEPTED_TYPES}: Optionally, a list of message types that the
    client accepts. The server is supposed to send the client only messages of
    this type. It will be inlcuded in the payload only if the hash that the
    server sends us is out-of-date. This behavior is simmetric with respect to
    the C{SERVER_ACCEPTED_TYPES_DIGEST} field described above.

Server->Client Payload
======================

The payloads that the server sends to not-yet-registered clients (i.e. clients
that don't provide a secure ID associated with a computer) are C{dict}s of the
form::

  {'server-uuid': SERVER_UUID,
   'server-api': SERVER_API,
   'messages': MESSAGES}

where:

  - C{SERVER_UUID}: A string identifying the particular Landscape server the
    client is talking to.

  - C{SERVER_API}: The version number of the highest server API that this
    particular server is able to handle. It can be used by the client to
    implement backward compatibility with old servers, knowing what message
    schemas the server expects (since schemas can change from version to
    version).

  - C{MESSAGES}: A python list of messages, described below.

Additionally, payloads to registered clients will include these fields::

  {'next-expected-sequence': EXPECTED_SEQUENCE_NUMBER,
   'next-expected-token': EXPECTED_EXCHANGE_TOKEN,
   'client-accepted-types-hash': CLIENT_ACCEPTED_TYPES_DIGEST,

where:

  - C{EXPECTED_SEQUENCE_NUMBER}: The sequence number which the server expects
    the next message sent from the client to have.

  - C{EXPECTED_EXCHANGE_TOKEN}: The token (UUID string) that the server expects
    to receive back the next time the client performs an exchange. Since the
    client receives a new token at each exchange, this can be used by the
    server to detect cloned clients (either the orignal client or the cloned
    client will eventually send an expired token). The token is sent by the
    client as a special HTTP header (see L{landscape.broker.transport}).

  - C{CLIENT_ACCEPTED_TYPES_DIGEST}: A hash of the message types that the
    server thinks are currently accepted by the client. The client can use it
    to know whether to send to the server an up-to-date list the message types
    it now accepts (see CLIENT_ACCEPTED_TYPES in the client->server payload).

Individual Messages
===================

A message is a C{dict} with required and optional keys. Messages are packed
into Python lists and set as the value of the 'messages' key in the payload.

The C{dict} of a single message is of the form::

  {'type': MESSAGE_TYPE,
   ...}

where:

  - C{MESSAGE_TYPE}: A simple string, which lets the server decide what handler
    to dispatch the message to, also considering the SERVER_API_VERSION value.

  - C{...}: Other entries specific to the type of message.

This format is the same for messages sent by the server to the client and for
messages sent by the client to the server. In addition, messages sent by the
client to the server will contain also the following extra fields::

  {...
   'api': SERVER_API,
   'timestamp': TIMESTAMP,
   ...}

where:

 - C{SERVER_API}: The server API that the client was targeting when it
   generated the message. In single exchange the client will only include
   messages targeted to the same server API.

 - C{TIMESTAMP}: A timestamp indicating when the message was generated.

Message Sequencing
==================

A message numbering system is built in to the protocol to ensure robustness of
client/server communication. The way this works is not totally symmetrical, as
the client must connect to the server via HTTP, but the ordering that things
happen in over the course of many connections remains the same (see also
L{landscape.broker.store} for more concrete examples):

  - Receiver tells Sender which sequence number it expects the next batch of
    messages to start with.

  - Sender gives some messages to Receiver, specifying the sequence number of
    the first message. If the expected and actual sequence numbers are out of
    synch, Sender resynchronizes in a certain way.

The client and server must play the part of *both* of these roles on every
interaction, but it simplifies things to talk about them in terms of a single
role at a time.

When the client connects to the server, it does the following things acting
in the role of Sender (which is by far its more burdened role):

  - Send a payload containing messages and a sequence number. The sequence
    number should be the same number that the server gave as
    next-expected-sequence in the prior connection, or 0 if there was no
    previous connection.

  - Get back a next-expected-sequence from the server. If that value is is not
    len(messages) + previous-next-expected, then resynchronize.

It does the following when acting as Receiver:

  - Send a payload containing a next-expected-sequence, which should be the
    sequence number of the first message that the server responds with. This
    value should be previous-next-expected + len(previous_messages).

  - Receive some messages from the server, and process them immediately.

When the server is acting as Sender, it does the following:

  - Wait for a payload with next-expected-sequence from the client.

  - Perhaps resynchronize if next-expected-sequence is unexpected.

  - Respond with a payload of messages to the client. No sequence identifier
    is given for this payload of messages, because it would be redundant with
    data that has already passed over the wire (received from the client)
    during the very same TCP connection.

When the server is acting as a Receiver, it does the following:

  - Wait for a payload with a sequence identifier and a load of messages.
  - Respond with a next-expected-sequence.

There are two interesting exceptional cases which must be handled with
resynchronization:

  1. Messages received with sequence numbers less than the next expected
     sequence number should be discarded, and further messages starting at
     the expected sequence numbers should be processed.

  2. If the sequence number is higher than what the receiver expected, then
     no messages are processed and the receiver responds with the same
     {'next-expected-sequence': N}, so that the sender can resynchronize
     itself.

This implies that the receiver must record the sequence number of the last
successfully processed message, in order for it to respond to the sender
with that number. In addition, the sender must save outbound messages even
after they have been delivered over the transport, until the sender receives
a next-expected-sequence higher than the outbound message. The details of
this logic are described in L{landscape.broker.store}.

Exchange Sequence
=================

Diagram::

  1. BrokerService    -->  MessageExchange               : Start

  2. MessageExchange  -->  MessageExchange               : Schedule exchange

  3. [event]          <--  MessageExchange               : Fire "pre-exchange"

  4. [optional]                                          : Do registration
     (See L{landscape.broker.registration})              : sequence

  5. MessageExchange  -->  MessageStore                  : Request pending
                                                         : messages

  6. MessageExchange  <--  MessageStore                  : return( Messages )

  7. MessageExchange  -->  HTTPTransport                 : Exchange

  8. HTTPTransport    -->  {Server}LandscapeMessageSystem
                                                         : HTTP POST

  9. [Scope: Server]
   |
   |   9.1 LandscapeMessageSystem --> ComputerMessageAPI : run
   |
   |   9.2 ComputerMessageAPI     --> FunctionHandler    : handle
   |
   |   9.3 FunctionHandler        --> Callable           : call
   |       ( See also server code at:
   |             - C{canonical.landscape.message.handlers}
   |             - C{canonical.message.handler.FunctionHandler} )
   |
   |
   |   9.4 [If: the callable raises ConsistencyError]
   |     |
   |     | 9.4.1 ComputerMessageAPI --> Computer         : request
   |     |                                               : Resynchronize
   |     |
   |     | 9.4.2 Computer           --> Computer         : Create
   |     |                                               : ResynchronizeRequest
   |     |                                               : activity
   |     |
   |     --[End If]
   |
   |  9.5 ComputerMessageAPI     --> Computer            : get deliverable
   |                                                     : activities
   |
   |  9.6 ComputerMessageAPI     <-- Computer            : return activities
   |
   |  9.7 [Loop over activities]
   |    |
   |    | 9.7.1 ComputerMessageAPI  --> Activity         : deliver
   |    |
   |    | 9.7.2 Activity            --> MessageStore     : add activity message
   |    |
   |    --[End Loop]
   |
   |  9.8 ComputerMessageAPI     --> MessageStore        : get pending messages
   |
   |  9.9 ComputerMessageAPI     <-- MessageStore        : return messages
   |
   | 9.10 LandscapeMessageSystem <-- ComputerMessageAPI  : return payload
   |                                                     : (See below)
   |
   -- [End Scope]

  10. HTTPTransport    <--  {Server}LandscapeMessageSystem
                                                         : HTTP response
                                                         : with payload

  11. MessageExchange  <--  HTTPTransport                : response

  12. [If: server says it expects a very old message]
   |
   |  12.1 [event]              <-- MessageExchange      : event
   |       (See L{landscape.broker.server})              : "resynchronize-
   |                                                     : clients"
   |
   -- [End if]

  13. [Loop: over messages in payload]
   |
   |  13.1 [event]             <-- MessageExchange       : event
   |                                                     : message (message)
   |
   |  13.2 [Switch: on message type]
   |     |
   |     |- 13.2.1 [Case: message type is "accepted-types"]
   |     |       |
   |     |       | 13.2.1.1 MessageExchange -> MessageStore
   |     |       |                                       : set accepted types
   |     |       |
   |     |       | 13.2.1.2 MessageExchange -> MessageExchange
   |     |       |                                       : schedule urgent
   |     |       |                                       : exchange
   |     |       --[End Case]
   |     |
   |     |- 13.2.2 [Case: message type is "resynchronize"]
   |     |       |
   |     |       | 13.2.2.1 [event]         <- MessageExchange
   |     |       |        (See L{landscape.broker.server})
   |     |       |                                      : event
   |     |       |                                      : "resynchronize-
   |     |       |                                      : clients"
   |     |       |
   |     |       | 13.2.2.2 MessageExchange -> MessageStore
   |     |       |                                      : add "resynchronize"
   |     |       |                                      : message
   |     |       |
   |     |       | 13.2.2.3 MessageExchange -> MessageExchange
   |     |       |                                      : schedule urgent
   |     |       |                                      : exchange
   |     |       |
   |     |       --[End Case]
   |     |
   |     |- 13.2.3 [Case: message type is "set-intervals"]
   |     |       |
   |     |       | 13.2.3.1 MessageExchange -> BrokerConfiguration
   |     |       |                                      : set exchange
   |     |       |                                      : interval
   |     |       |
   |     |       --[End Case]
   |     |
   |     -- [End Switch]
   |
   -- [End Loop]

  14. Schedule exchange

iN(tmd5(tDeferredtsucceed(t
HTTPCodeError(tgot_next_expectedtANCIENT(tis_version_highert
sort_versions(tformat_delta(tDEFAULT_SERVER_APIt
SERVER_APIt
CLIENT_APItMessageExchangecBseZdZeZddZdZedZdZ	dZ
dZdZdd	Zd
ZdZdZeed
ZdZdZdZdZdZdZdZdZdZRS(svSchedule and handle message exchanges with the server.

    The L{MessageExchange} is the place where messages are sent to go out
    to the Landscape server. It accumulates messages in its L{MessageStore}
    and periodically delivers them to the server.

    It is also the place where messages coming from the server are handled. For
    each message type the L{MessageExchange} supports setting an handler that
    will be invoked when a message of the that type is received.

    An exchange is performed with an HTTP POST request, whose body contains
    outgoing messages and whose response contains incoming messages.
    idcCs||_||_||_||_||_|j|_|j|_||_	d|_d|_t
|_t
|_t|_d|_i|_||_t
|_|jd|j|jd|j|jd|j|jd|jdS(s
        @param reactor: The L{LandscapeReactor} used to fire events in response
            to messages received by the server.
        @param store: The L{MessageStore} used to queue outgoing messages.
        @param transport: The L{HTTPTransport} used to deliver messages.
        @param registration_info: The L{Identity} storing our secure ID.
        @param config: The L{BrokerConfiguration} with the `exchange_interval`
            and `urgent_exchange_interval` parameters, respectively holding
            the time interval between subsequent exchanges of non-urgent
            messages, and the time interval between subsequent exchanges
            of urgent messages.
        saccepted-typest
resynchronizes
set-intervalssresynchronize-clientsN(t_reactort_message_storet
_transportt_registration_infot_configtexchange_intervalt_exchange_intervalturgent_exchange_intervalt_urgent_exchange_intervalt
_max_messagestNonet_notification_idt_exchange_idtFalset_exchangingt_urgent_exchangetsett_client_accepted_typest_client_accepted_types_hasht_message_handlerst_exchange_storet_stoppedtregister_messaget_handle_accepted_typest_handle_resynchronizet_handle_set_intervalstcall_ont_resynchronize(tselftreactortstoret	transporttregistration_infotexchange_storetconfigtmax_messages((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyt__init__ys*														cCspd|krtS|d}|jj|}|dkrMtjd|tS|jj|jk}|j|S(sReturns C{True} if message is obsolete.

        A message is considered obsolete if the secure ID changed since it was
        received.
        soperation-ids4No message context for message with operation-id: %sN(	RR"tget_message_contextRtloggingtwarningRt	secure_idtremove(R*tmessagetoperation_idtcontexttresult((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyt_message_is_obsoletes

cCs|j|r-tjd|jddSd|krUt|jj|d<n|jj	|}|r|j
dtn|S(sInclude a message to be sent in an exchange.

        If urgent is True, an exchange with the server will be
        scheduled urgently.

        @param message: Same as in L{MessageStore.add}.
        snResponse message with operation-id %s was discarded because the client's secure ID has changed in the meantimesoperation-idt	timestampturgentN(R<R4tinfotgetRtintRttimeRtaddtschedule_exchangetTrue(R*R8R>t
message_id((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pytsendscCs|jdtdS(s9Start scheduling exchanges. The first one will be urgent.R>N(RDRE(R*((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pytstartscCsi|jdk	r.|jj|jd|_n|jdk	r\|jj|jd|_nt|_dS(sStop scheduling exchanges.N(RRRtcancel_callRRER#(R*((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pytstopscCst|jj}t|d}t||}|jj|tjd||jjdry|jdt	nx(||D]}|j
jd|tqWx(||D]}|j
jd|t	qWdS(s
        When the server updates us about the types of message it
        accepts, update our message store.

        If this makes existing held messages available for sending,
        urgently exchange messages.

        If new types are made available or old types are dropped a
        C{("message-type-acceptance-changed", type, bool)} reactor
        event will be fired.
        ttypessAccepted types changed: %siR>smessage-type-acceptance-changedN(
RRtget_accepted_typestget_accepted_types_difftset_accepted_typesR4R?tget_pending_messagesRDRERtfireR(R*R8t	old_typest	new_typestdiffttype((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR%scCsN|d}|jd}|jidd6|d6|jjdd|dS(Nsoperation-idtscopesR
RTsresynchronize-clients(R@RGRRP(R*R8topidRU((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR&s
cCs$|jj||jdtdS(NR>(Rtdrop_session_idsRDRE(R*RU((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR)scCs}d|kr6|d|j_tjd|jjnd|krl|d|j_tjd|jjn|jjdS(Ntexchanges$Exchange interval set to %d seconds.surgent-exchanges+Urgent exchange interval set to %d seconds.(RRR4R?Rtwrite(R*R8((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR's		c	s
jrtdSt_jjdjtjjrjt	j
djjnt	j
djjt
fdfd}fd}jj||jjjjjjdS(	s|Send pending messages to the server and process responses.

        A C{pre-exchange} reactor event will be emitted just before the
        actual exchange takes place.

        An C{exchange-done} or C{exchange-failed} reactor event will be
        emitted after a successful or failed exchange.

        @return: A L{Deferred} that is fired when exchange has completed.
        spre-exchanges)Starting urgent message exchange with %s.s"Starting message exchange with %s.csQjdtjjdtjdttjjddS(Ntforces
exchange-dones!Message exchange completed in %s.(
RDRERRPR4R?RRBtcallbackR((t
start_timetdeferredR*(s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pytexchange_completed*s
	cst_|rcjr1tjdt_nj|jjtj	j
nj	jdtjddS(Ns"Switching to normal exchange mode.sexchange-failedsMessage exchange failed.(RRRR4R?t_handle_resultRtrecord_successRARRBRP(R;(R*R^tpayload(s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyt
handle_result1s		
"
cst_t|tr]|jdkr]jjtkr]jjtj	dSnj
jdjjt
j
jtjddS(Nisexchange-failedsMessage exchange failed.(RRt
isinstanceRt	http_codeRtget_server_apiR	tset_server_apiRXRRPtrecord_failureRARBR4R?(terror_classterrort	traceback(R^R*(s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pythandle_failure>s	

s
server-apiN(RRRRERRPt
_make_payloadRBRR4R?Rtget_urlRtcall_in_threadRXRR6t_get_exchange_tokenR@(R*RbRk((R\R^RaR]R*s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyRXs*	
					
		cCs|jS(sMReturn a bool showing whether there is an urgent exchange scheduled.
        (R(R*((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyt	is_urgentVscCs|jr
dS|jr|s<|jdks<|r|jr|rNt|_n|jrm|jj|jn|jr|jj	}n|jj
}|jdk	r|jj|jn|d}|jj||j
|_|jj||j|_ndS(s_Schedule an exchange to happen.

        The exchange will occur after some time based on whether C{urgent} is
        True. An C{impending-exchange} reactor event will be emitted
        approximately 10 seconds before the exchange is started.

        @param urgent: If true, ensure an exchange happens within the
            urgent interval.  This will reschedule the exchange if necessary.
            If another urgent exchange is already scheduled, nothing happens.
        @param force: If true, an exchange will necessarily be scheduled,
            even if it was already scheduled before.
        Ni
(R#RRRRRERRIRRRRt
call_latert_notify_impending_exchangeRX(R*R>RZtintervaltnotification_interval((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyRD[s&
	
		
		cCs0|jj}|jjd|jj|S(sGet the token given us by the server at the last exchange.

        It will be C{None} if we are not fully registered yet or if something
        bad happened during the last exchange and we could not get the token
        that the server had given us.
        N(Rtget_exchange_tokentset_exchange_tokenRtcommit(R*texchange_token((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyRos
cCs|jjddS(Nsimpending-exchange(RRP(R*((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyRrscCs4|j}|j|j}|j|j}|j}|r|djd}x9t|D]%\}}|jd|krbPqbqbWd}|dk	r||3qn|j	}i|d6t
d6|jd6|d6|d6|d6|jd	6}|j
}	|j|	}
|
|jkr0|	|d
<n|S(sReturn a dict representing the complete exchange payload.

        The payload will contain all pending messages eligible for
        delivery, up to a maximum of C{max_messages} as passed to
        the L{__init__} method.
        itapis
server-apis
client-apitsequencesaccepted-typestmessagesstotal-messagessnext-expected-sequencesclient-accepted-typesN(Rt_hash_typesRLRORtcount_pending_messagesR@t	enumerateRReRtget_sequencetget_server_sequencet!get_client_accepted_message_typesR (R*R,taccepted_types_digestR{ttotal_messagest
server_apitiR8Rataccepted_client_typestaccepted_client_types_hash((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyRls2	



cCsdj|}t|jS(Nt;(tjoinRtdigest(R*RKtaccepted_types_str((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR|sc
Cs!|j}|jd|_|jd}|j}|dkre|j}|t|d7}nt||}|tkrtj	d|j
idd6|jjdn|j
|jd|j}|jd	}||kr.tj	d
||f|jjd|||j|n|jdd
}	t|	|jr~t|	|jgd}
|j|
n|j|j}xH|jddD]4}|j||d7}|j||jqW|jdrtj	d||kr|jdtqndS(sHandle a response from the server.

        Called by L{exchange} after a batch of messages has been
        successfully delivered to the server.

        If the C{server_uuid} changed, a C{"server-uuid-changed"} event
        will be fired.

        Call L{handle_message} for each message in C{result}.

        @param payload: The payload that was sent to the server.
        @param result: The response got in reply to the C{payload}.
        sclient-accepted-types-hashsnext-expected-sequenceR{sIServer asked for ancient data: resynchronizing all state with the server.R
RTsresynchronize-clientssnext-exchange-tokensserver-uuids%Server UUID changed (old=%s, new=%s).sserver-uuid-changeds
server-apis3.2iis0Pending messages remain after the last exchange.R>N((RR@R RRtlenRRR4R?RGRRPRvtget_server_uuidtset_server_uuidRReRt_apiRfRwRthandle_messagetset_server_sequenceRORDRE(
R*RaR;t
message_storet
next_expectedtold_sequencetmessage_store_statetold_uuidtnew_uuidRtlowest_server_apiRzR8((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR_sH	
	




cCs0|jj|gj||jj|dS(sRegister a handler for the given message type.

        The C{handler} callable will to be executed when a message of
        type C{type} has been received from the server.

        Multiple handlers for the same type will be called in the
        order they were registered.
        N(R!t
setdefaulttappendRRC(R*RTthandler((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR$s	cCsd|kr3|jj|d|jj|dn|jjd||d|jkrx&|j|dD]}||qkWndS(s
        Handle a message received from the server.

        Any message handlers registered with L{register_message} will
        be called.
        soperation-idRTR8N(R"tadd_message_contextRR6RRPR!(R*R8R((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR$s	cCs|jjt|dS(N(RRCtstr(R*RT((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyt%register_client_accepted_message_type8scCs
t|jS(N(tsortedR(R*((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyR=sN(t__name__t
__module__t__doc__R
RR2R<RRGRHRJR%R&RR)R'RXRpRDRoRrRlR|R_R$RRR(((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyRgs.
$							J	(			(		R			cCst|}t|}||}||@}||}g}|jg|D]}d|^qI|jg|D]}d|^qm|jg|D]}d|^qdj|S(Ns+%ss%ss-%st (RtextendR(RQRRtadded_typeststable_typest
removed_typesRSRT((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyRMAs


$$$(RRBR4tlandscape.lib.hashlibRttwisted.internet.deferRRtlandscape.lib.fetchRtlandscape.lib.messageRRtlandscape.lib.versioningRRt
landscape.logRt	landscapeR	R
RtobjectRRM(((s=/usr/lib/python2.7/dist-packages/landscape/broker/exchange.pyt<module>Xs