Kenneth Giusti 623b1af3ab checkpoint
2013-12-23 10:35:17 -05:00

409 lines
13 KiB
Python

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import proton
from link import SenderLink, ReceiverLink
#
# An implementation of an AMQP 1.0 Connection
#
class ConnectionEventHandler(object):
"""
"""
def connection_active(self, connection):
"""connection handshake completed"""
pass
def connection_closed(self, connection, reason):
pass
def sender_requested(self, connection, link_handle,
requested_source, properties={}):
# call accept_sender to accept new link,
# reject_sender to reject it.
pass
def receiver_requested(self, connection, link_handle,
requested_target, properties={}):
# call accept_sender to accept new link,
# reject_sender to reject it.
pass
# @todo cleaner sasl support, esp. server side
def sasl_done(self, connection, result):
pass
class Connection(object):
"""
"""
EOS = -1 # indicates 'I/O stream closed'
def __init__(self, container, name, eventHandler=None, properties={}):
"""
"""
self._name = name
self._container = container
self._pn_connection = proton.Connection()
self._pn_connection.container = container.name
if 'hostname' in properties:
self._pn_connection.hostname = properties['hostname']
self._pn_transport = proton.Transport()
self._pn_transport.bind(self._pn_connection)
# @todo - logging??
self._pn_transport.trace(proton.Transport.TRACE_FRM)
self._handler = eventHandler
self._sender_links = {}
self._receiver_links = {}
self._pending_links = {}
self._pending_link_id = 0
self._read_done = False
self._write_done = False
self._next_tick = 0
self._user_context = None
self._active = False
# @todo sasl configuration and handling
self._sasl = None
self._sasl_done = False
self._pn_connection.open()
self._pn_session = self._pn_connection.session()
self._pn_session.open()
@property
def container(self):
return self._container
@property
# @todo - hopefully remove
def transport(self):
return self._pn_transport
@property
# @todo - hopefully remove
def connection(self):
return self._pn_connection
@property
def name(self):
return self._name
@property
# @todo - think about server side use of sasl!
def sasl(self):
if not self._sasl:
self._sasl = self._pn_transport.sasl()
return self._sasl
def _get_user_context(self):
return self._user_context
def _set_user_context(self, ctxt):
self._user_context = ctxt
user_context = property(_get_user_context, _set_user_context,
doc="""
Associate an arbitrary user object with this Connection.
""")
_NEED_INIT = proton.Endpoint.LOCAL_UNINIT
_NEED_CLOSE = (proton.Endpoint.LOCAL_ACTIVE|proton.Endpoint.REMOTE_CLOSED)
_ACTIVE = (proton.Endpoint.LOCAL_ACTIVE|proton.Endpoint.REMOTE_ACTIVE)
def process(self, now):
"""
"""
self._next_tick = self._pn_transport.tick(now)
# wait until SASL has authenticated
# @todo Server-side SASL
if self._sasl:
if self._sasl.state not in (proton.SASL.STATE_PASS,
proton.SASL.STATE_FAIL):
print("SASL wait.")
return
self._handler.sasl_done(self, self.sasl.outcome)
self._sasl = None
if self._pn_connection.state & self._NEED_INIT:
assert False, "Connection always opened() on create"
if not self._active:
if self._pn_connection.state == self._ACTIVE:
self._active = True
self._handler.connection_active(self)
ssn = self._pn_connection.session_head(self._NEED_INIT)
while ssn:
print "Opening remotely initiated session"
ssn.open()
ssn = ssn.next(self._NEED_INIT)
link = self._pn_connection.link_head(self._NEED_INIT)
while link:
print "Remotely initiated Link needs init"
index = self._pending_link_id
self._pending_link_id += 1
assert index not in self._pending_links
self._pending_links[index] = link
if link.is_sender:
req_source = ""
if link.remote_source.is_dynamic:
req_source = None
elif link.remote_source.address:
req_source = link.remote_source.address
self._handler.sender_pending(self, index, req_source,
{"target-address":
link.remote_target.address})
else:
req_target = ""
if link.remote_target.is_dynamic:
req_target = None
elif link.remote_target.address:
req_target = link.remote_target.address
self._handler.receiver_pending(self, index, req_target,
{"source-address":
link.remote_source.address})
link = link.next(self._NEED_INIT)
# ?any need for ACTIVE callback?
# process the work queue
delivery = self._pn_connection.work_head
while delivery:
print "Delivery updated!"
if delivery.link.is_sender:
sender_link = delivery.link.context
sender_link._delivery_updated(delivery)
else:
receiver_link = delivery.link.context
receiver_link._delivery_updated(delivery)
delivery = delivery.work_next
# close all endpoints closed by remotes
link = self._pn_connection.link_head(self._NEED_CLOSE)
while link:
print "Link needs close"
# @todo - find link, invoke callback
link = link.next(self._NEED_CLOSE)
ssn = self._pn_connection.session_head(self._NEED_CLOSE)
while ssn:
print "Session closed remotely"
ssn.close()
ssn = ssn.next(self._NEED_CLOSE)
if self._pn_connection.state == (self._NEED_CLOSE):
print "Connection remotely closed"
# @todo - think about handling this wrt links!
cond = self._pn_connection.remote_condition()
self._pn_connection.close()
self._handler.connection_closed(self, cond)
return self._next_tick
@property
def next_tick(self):
"""Timestamp for next call to tick()
"""
return self._next_tick
@property
def needs_input(self):
"""
"""
if self._read_done:
return self.EOS
capacity = self._pn_transport.capacity()
if capacity >= 0:
return capacity
self._read_done = True
return self.EOS
def process_input(self, in_data):
"""
"""
c = self.needs_input
if c <= 0:
return c
rc = self._pn_transport.push(in_data[:c])
if rc: # error
self._read_done = True
return self.EOS
return c
def close_input(self, reason=None):
if not self._read_done:
self._pn_transport.close_tail()
self._read_done = True
@property
def has_output(self):
"""
"""
if self._write_done:
return self.EOS
pending = self._pn_transport.pending()
if pending >= 0:
return pending
self._write_done = True
return self.EOS
def output_data(self):
"""
"""
c = self.has_output
if c <= 0:
return None
buf = self._pn_transport.peek(c)
return buf
def output_written(self, count):
self._pn_transport.pop(count)
def close_output(self, reason=None):
if not self._write_done:
self._pn_transport.close_head()
self._write_done = True
@property
def closed(self):
return self._write_done and self._read_done
def create_sender(self, source_address, target_address=None,
eventHandler=None, name=None, properties={}):
"""Factory for Sender links"""
ident = name or str(source_address)
if ident in self._sender_links:
raise KeyError("Sender %s already exists!" % ident)
pn_link = self._pn_session.sender(ident)
if pn_link:
s = SenderLink(self, pn_link, ident,
source_address, target_address,
eventHandler, properties)
self._sender_links[ident] = s
return s
return None
def accept_sender(self, link_handle, source_override=None,
event_handler=None, name=None, properties={}):
pn_link = self._pending_links.pop(link_handle)
if not pn_link:
raise Exception("Invalid link_handle: %s" % link_handle)
if pn_link.remote_source.is_dynamic and not source_override:
raise Exception("A source address must be supplied!")
source_addr = source_override or pn_link.remote_source.address
name = name or source_addr
if name in self._sender_links:
raise KeyError("Sender %s already exists!" % name)
self._sender_links[name] = SenderLink(self, pn_link, name,
source_addr,
pn_link.remote_target.address,
event_handler, properties)
return self._sender_links[name]
def reject_sender(self, link_handle, reason):
pn_link = self._pending_links.pop(link_handle)
if pn_link:
# @todo support reason for close
pn_link.close()
def create_receiver(self, target_address, source_address,
eventHandler, name=None, properties={}):
"""Factory for Receive links"""
ident = name or str(target_address)
if ident in self._receiver_links:
raise KeyError("Receiver %s already exists!" % ident)
pn_link = self._pn_session.receiver(ident)
if pn_link:
r = ReceiverLink(self, pn_link, ident, target_address,
source_address, eventHandler, properties)
if r:
self._receiver_links[ident] = r
return r
return None
def accept_receiver(self, link_handle, target_override=None,
event_handler=None, name=None, properties={}):
pn_link = self._pending_links.pop(link_handle)
if not pn_link:
raise Exception("Invalid link_handle: %s" % link_handle)
if pn_link.remote_target.is_dynamic and not target_override:
raise Exception("A target address must be supplied!")
target_addr = target_override or pn_link.remote_target.address
name = name or target_addr
if name in self._receiver_links:
raise KeyError("Receiver %s already exists!" % name)
self._receiver_links[name] = ReceiverLink(self, pn_link, name,
target_addr,
pn_link.remote_source.address,
event_handler, properties)
return self._receiver_links[name]
pass
def reject_receiver(self, link_handle, reason):
pn_link = self._pending_links.pop(link_handle)
if pn_link:
# @todo support reason for close
pn_link.close()
def destroy(self, error=None):
"""
"""
for l in self._sender_links.itervalues():
l.destroy(error)
self._sender_links = {}
for l in self._receiver_links.itervalues():
l.destroy(error)
self._receiver_links = {}
self._pn_session.close()
self._pn_session = None
self._pn_connection.close()
self._pn_connection = None
self._pn_transport = None
self._user_context = None
def _remove_sender(self, name):
if name in self._sender_links:
del self._sender_links[name]
def _remove_receiver(self, name):
if name in self._receiver_links:
del self._receiver_links[name]
__all__ = [
"ConnectionEventHandler",
"Connection"
]