2014-01-20 14:12:37 -05:00
|
|
|
# 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.
|
2013-12-17 15:11:04 -05:00
|
|
|
#
|
2014-01-20 14:12:37 -05:00
|
|
|
# Licensed 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
|
2013-12-17 15:11:04 -05:00
|
|
|
#
|
2014-01-20 14:12:37 -05:00
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
2013-12-17 15:11:04 -05:00
|
|
|
#
|
2014-01-20 14:12:37 -05:00
|
|
|
# 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.
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-01-13 15:39:04 -05:00
|
|
|
__all__ = [
|
|
|
|
"SenderEventHandler",
|
|
|
|
"SenderLink",
|
|
|
|
"ReceiverEventHandler",
|
|
|
|
"ReceiverLink"
|
2014-01-20 14:12:37 -05:00
|
|
|
]
|
2014-01-13 15:39:04 -05:00
|
|
|
|
2014-01-20 14:12:37 -05:00
|
|
|
import collections
|
|
|
|
import logging
|
2013-12-17 15:11:04 -05:00
|
|
|
import proton
|
|
|
|
|
2014-01-01 12:41:17 -05:00
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
# link lifecycle state:
|
|
|
|
_STATE_UNINIT = 0 # initial state
|
|
|
|
_STATE_PENDING = 1 # waiting for remote to open
|
|
|
|
_STATE_REQUESTED = 2 # waiting for local to open
|
|
|
|
_STATE_ACTIVE = 3
|
|
|
|
_STATE_NEED_CLOSE = 4 # remote initiated close
|
|
|
|
_STATE_CLOSING = 5 # locally closed, pending remote
|
|
|
|
_STATE_CLOSED = 6 # terminal state
|
|
|
|
|
|
|
|
# proton endpoint states:
|
|
|
|
_LOCAL_UNINIT = proton.Endpoint.LOCAL_UNINIT
|
|
|
|
_LOCAL_ACTIVE = proton.Endpoint.LOCAL_ACTIVE
|
|
|
|
_LOCAL_CLOSED = proton.Endpoint.LOCAL_CLOSED
|
|
|
|
_REMOTE_UNINIT = proton.Endpoint.REMOTE_UNINIT
|
|
|
|
_REMOTE_ACTIVE = proton.Endpoint.REMOTE_ACTIVE
|
|
|
|
_REMOTE_CLOSED = proton.Endpoint.REMOTE_CLOSED
|
|
|
|
|
|
|
|
def _do_pending(link):
|
|
|
|
"""The application has opened a new link."""
|
|
|
|
# nothing to do but wait for the remote:
|
|
|
|
return _STATE_PENDING
|
|
|
|
|
|
|
|
def _do_requested(link):
|
|
|
|
"""The peer has requested a new link be created."""
|
|
|
|
LOG.debug("Remote has initiated a link")
|
|
|
|
pn_link = link._pn_link
|
|
|
|
if isinstance(link, SenderLink):
|
|
|
|
# has the remote requested a source address?
|
|
|
|
req_source = ""
|
|
|
|
if pn_link.remote_source.dynamic:
|
|
|
|
req_source = None
|
|
|
|
elif pn_link.remote_source.address:
|
|
|
|
req_source = pn_link.remote_source.address
|
|
|
|
handler = link._connection._handler
|
|
|
|
if handler:
|
|
|
|
handler.sender_requested(link._connection,
|
|
|
|
pn_link.name, # handle
|
|
|
|
pn_link.name,
|
|
|
|
req_source,
|
|
|
|
{"target-address":
|
|
|
|
pn_link.remote_target.address})
|
|
|
|
else:
|
|
|
|
# has the remote requested a target address?
|
|
|
|
req_target = ""
|
|
|
|
if pn_link.remote_target.dynamic:
|
|
|
|
req_target = None
|
|
|
|
elif pn_link.remote_target.address:
|
|
|
|
req_target = pn_link.remote_target.address
|
|
|
|
handler = link._connection._handler
|
|
|
|
if handler:
|
|
|
|
handler.receiver_requested(link._connection,
|
|
|
|
pn_link.name, # handle
|
|
|
|
pn_link.name,
|
|
|
|
req_target,
|
|
|
|
{"source-address":
|
|
|
|
pn_link.remote_source.address})
|
|
|
|
return _STATE_REQUESTED
|
|
|
|
|
|
|
|
def _do_active(link):
|
|
|
|
"""Both ends of the link have become active."""
|
|
|
|
LOG.debug("Link is up")
|
|
|
|
if link._handler:
|
|
|
|
if isinstance(link, SenderLink):
|
|
|
|
link._handler.sender_active(link)
|
|
|
|
else:
|
|
|
|
link._handler.receiver_active(link)
|
|
|
|
return _STATE_ACTIVE
|
|
|
|
|
|
|
|
def _do_need_close(link):
|
|
|
|
"""The remote has closed its end of the link."""
|
|
|
|
# TODO(kgiusti) error reporting
|
|
|
|
LOG.debug("Link remote closed")
|
|
|
|
if link._handler:
|
|
|
|
if isinstance(link, SenderLink):
|
|
|
|
link._handler.sender_remote_closed(link, None)
|
|
|
|
else:
|
|
|
|
link._handler.receiver_remote_closed(link, None)
|
|
|
|
return _STATE_NEED_CLOSE
|
|
|
|
|
|
|
|
def _do_closing(link):
|
|
|
|
"""Locally closed, remote end still active."""
|
|
|
|
# nothing to do but wait for the remote
|
|
|
|
return _STATE_CLOSING
|
|
|
|
|
|
|
|
def _do_closed(link):
|
|
|
|
"""Both ends of the link have closed."""
|
|
|
|
LOG.debug("Link close completed")
|
|
|
|
if link._handler:
|
|
|
|
if isinstance(link, SenderLink):
|
|
|
|
link._handler.sender_closed(link)
|
|
|
|
else:
|
|
|
|
link._handler.receiver_closed(link)
|
|
|
|
return _STATE_CLOSED
|
|
|
|
|
|
|
|
# Given the current endpoint state for the link, move to the next state do any
|
|
|
|
# state-specific processing:
|
|
|
|
_EP_STATE_MACHINE = [
|
|
|
|
# _STATE_UNINIT:
|
|
|
|
{(_LOCAL_ACTIVE | _REMOTE_UNINIT): _do_pending,
|
|
|
|
(_LOCAL_UNINIT | _REMOTE_ACTIVE): _do_requested},
|
|
|
|
# _STATE_PENDING:
|
|
|
|
{(_LOCAL_ACTIVE | _REMOTE_ACTIVE): _do_active,
|
|
|
|
(_LOCAL_ACTIVE | _REMOTE_CLOSED): _do_need_close,
|
|
|
|
(_LOCAL_CLOSED | _REMOTE_UNINIT): _do_closed,
|
|
|
|
(_LOCAL_CLOSED | _REMOTE_CLOSED): _do_closed},
|
|
|
|
# _STATE_REQESTED:
|
|
|
|
{(_LOCAL_ACTIVE | _REMOTE_ACTIVE): _do_active,
|
|
|
|
(_LOCAL_CLOSED | _REMOTE_ACTIVE): _do_closing,
|
|
|
|
(_LOCAL_ACTIVE | _REMOTE_CLOSED): _do_need_close},
|
|
|
|
# _STATE_ACTIVE:
|
|
|
|
{(_LOCAL_ACTIVE | _REMOTE_CLOSED): _do_need_close,
|
|
|
|
(_LOCAL_CLOSED | _REMOTE_ACTIVE): _do_closing,
|
|
|
|
(_LOCAL_CLOSED | _REMOTE_CLOSED): _do_closed},
|
|
|
|
# _STATE_NEED_CLOSE:
|
|
|
|
{(_LOCAL_CLOSED | _REMOTE_CLOSED): _do_closed},
|
|
|
|
# _STATE_CLOSING:
|
|
|
|
{(_LOCAL_CLOSED | _REMOTE_CLOSED): _do_closed}]
|
2014-01-20 14:12:37 -05:00
|
|
|
|
2013-12-17 15:11:04 -05:00
|
|
|
class _Link(object):
|
2014-01-20 14:12:37 -05:00
|
|
|
"""A generic Link base class."""
|
2014-03-07 15:27:13 -05:00
|
|
|
|
|
|
|
def __init__(self, connection, pn_link):
|
|
|
|
self._state = _STATE_UNINIT
|
|
|
|
# last known endpoint state:
|
|
|
|
self._ep_state = _LOCAL_UNINIT | _REMOTE_UNINIT
|
2013-12-17 15:11:04 -05:00
|
|
|
self._connection = connection
|
2014-01-09 15:55:34 -05:00
|
|
|
self._name = pn_link.name
|
2014-03-07 15:27:13 -05:00
|
|
|
self._handler = None
|
|
|
|
self._properties = None
|
2014-01-09 15:55:34 -05:00
|
|
|
self._user_context = None
|
2014-03-04 15:38:08 -05:00
|
|
|
# TODO(kgiusti): raise jira to add 'context' attr to api
|
2013-12-17 15:11:04 -05:00
|
|
|
self._pn_link = pn_link
|
|
|
|
pn_link.context = self
|
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def configure(self, target_address, source_address, handler, properties):
|
|
|
|
"""Assign addresses, properties, etc."""
|
|
|
|
self._handler = handler
|
|
|
|
self._properties = properties
|
|
|
|
|
2013-12-23 10:35:17 -05:00
|
|
|
if target_address is None:
|
2014-03-07 15:27:13 -05:00
|
|
|
if not self._pn_link.is_sender:
|
|
|
|
raise Exception("Dynamic target not allowed")
|
2013-12-17 15:11:04 -05:00
|
|
|
self._pn_link.target.dynamic = True
|
2013-12-23 10:35:17 -05:00
|
|
|
elif target_address:
|
|
|
|
self._pn_link.target.address = target_address
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2013-12-23 10:35:17 -05:00
|
|
|
if source_address is None:
|
2014-03-07 15:27:13 -05:00
|
|
|
if not self._pn_link.is_receiver:
|
|
|
|
raise Exception("Dynamic source not allowed")
|
2013-12-17 15:11:04 -05:00
|
|
|
self._pn_link.source.dynamic = True
|
2013-12-23 10:35:17 -05:00
|
|
|
elif source_address:
|
|
|
|
self._pn_link.source.address = source_address
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-03-04 15:38:08 -05:00
|
|
|
if properties:
|
|
|
|
desired_mode = properties.get("distribution-mode")
|
|
|
|
if desired_mode:
|
|
|
|
if desired_mode == "copy":
|
|
|
|
mode = proton.Terminus.DIST_MODE_COPY
|
|
|
|
elif desired_mode == "move":
|
|
|
|
mode = proton.Terminus.DIST_MODE_MOVE
|
|
|
|
else:
|
|
|
|
raise Exception("Unknown distribution mode: %s" %
|
|
|
|
str(desired_mode))
|
|
|
|
self._pn_link.source.distribution_mode = mode
|
2014-01-09 15:55:34 -05:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return self._name
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-01-08 15:12:51 -05:00
|
|
|
def open(self):
|
2014-03-07 15:27:13 -05:00
|
|
|
LOG.debug("Opening the link.")
|
2014-01-08 15:12:51 -05:00
|
|
|
self._pn_link.open()
|
2014-03-07 15:27:13 -05:00
|
|
|
self._connection._add_work(self)
|
2014-01-08 15:12:51 -05:00
|
|
|
|
2013-12-18 16:31:01 -05:00
|
|
|
def _get_user_context(self):
|
|
|
|
return self._user_context
|
|
|
|
|
|
|
|
def _set_user_context(self, ctxt):
|
|
|
|
self._user_context = ctxt
|
|
|
|
|
2014-03-04 15:38:08 -05:00
|
|
|
_uc_docstr = """Arbitrary application object associated with this link."""
|
2013-12-18 16:31:01 -05:00
|
|
|
user_context = property(_get_user_context, _set_user_context,
|
2014-03-04 15:38:08 -05:00
|
|
|
doc=_uc_docstr)
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2013-12-23 10:35:17 -05:00
|
|
|
@property
|
|
|
|
def source_address(self):
|
2014-03-04 15:38:08 -05:00
|
|
|
"""Return the authorative source of the link."""
|
|
|
|
# If link is a sender, source is determined by the local
|
|
|
|
# value, else use the remote.
|
2013-12-23 10:35:17 -05:00
|
|
|
if self._pn_link.is_sender:
|
|
|
|
return self._pn_link.source.address
|
|
|
|
else:
|
|
|
|
return self._pn_link.remote_source.address
|
|
|
|
|
|
|
|
@property
|
|
|
|
def target_address(self):
|
2014-03-04 15:38:08 -05:00
|
|
|
"""Return the authorative target of the link."""
|
|
|
|
# If link is a receiver, target is determined by the local
|
|
|
|
# value, else use the remote.
|
2013-12-23 10:35:17 -05:00
|
|
|
if self._pn_link.is_receiver:
|
|
|
|
return self._pn_link.target.address
|
|
|
|
else:
|
|
|
|
return self._pn_link.remote_target.address
|
|
|
|
|
2013-12-23 16:04:04 -05:00
|
|
|
def close(self, error=None):
|
2014-03-07 15:27:13 -05:00
|
|
|
LOG.debug("Closing the link.")
|
2013-12-23 16:04:04 -05:00
|
|
|
self._pn_link.close()
|
2014-03-07 15:27:13 -05:00
|
|
|
self._connection._add_work(self)
|
2013-12-23 16:04:04 -05:00
|
|
|
|
|
|
|
@property
|
|
|
|
def closed(self):
|
|
|
|
state = self._pn_link.state
|
|
|
|
return state == (proton.Endpoint.LOCAL_CLOSED
|
|
|
|
| proton.Endpoint.REMOTE_CLOSED)
|
|
|
|
|
|
|
|
def destroy(self):
|
2014-03-04 15:38:08 -05:00
|
|
|
LOG.debug("link destroyed %s", str(self._pn_link))
|
2013-12-18 16:31:01 -05:00
|
|
|
self._user_context = None
|
2014-03-04 15:38:08 -05:00
|
|
|
if self._pn_link:
|
|
|
|
self._pn_link.context = None
|
|
|
|
self._pn_link.free()
|
|
|
|
self._pn_link = None
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def _process(self):
|
|
|
|
"""Link state machine processing."""
|
|
|
|
# check for transitions in Endpoint state:
|
|
|
|
pn_link = self._pn_link
|
|
|
|
if self._state != _STATE_CLOSED:
|
|
|
|
ep_state = pn_link.state
|
|
|
|
if ep_state != self._ep_state:
|
|
|
|
LOG.debug("link state: %s old ep: %s new ep: %s",
|
|
|
|
self._state, hex(self._ep_state), hex(ep_state))
|
|
|
|
self._ep_state = ep_state
|
|
|
|
self._state = _EP_STATE_MACHINE[self._state][ep_state](self)
|
|
|
|
|
|
|
|
def _process_delivery(self, pn_delivery):
|
|
|
|
pass
|
2014-01-20 14:12:37 -05:00
|
|
|
|
2013-12-17 15:11:04 -05:00
|
|
|
class SenderEventHandler(object):
|
|
|
|
def sender_active(self, sender_link):
|
2014-01-01 12:41:17 -05:00
|
|
|
LOG.debug("sender_active (ignored)")
|
|
|
|
|
2014-01-09 15:55:34 -05:00
|
|
|
def sender_remote_closed(self, sender_link, error=None):
|
|
|
|
LOG.debug("sender_remote_closed (ignored)")
|
|
|
|
|
|
|
|
def sender_closed(self, sender_link):
|
2014-01-01 12:41:17 -05:00
|
|
|
LOG.debug("sender_closed (ignored)")
|
2013-12-17 15:11:04 -05:00
|
|
|
|
|
|
|
|
|
|
|
class SenderLink(_Link):
|
|
|
|
|
|
|
|
# Status for message send callback
|
|
|
|
#
|
|
|
|
ABORTED = -2
|
|
|
|
TIMED_OUT = -1
|
|
|
|
UNKNOWN = 0
|
|
|
|
ACCEPTED = 1
|
|
|
|
REJECTED = 2
|
|
|
|
RELEASED = 3
|
|
|
|
MODIFIED = 4
|
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def __init__(self, connection, pn_link):
|
|
|
|
super(SenderLink, self).__init__(connection, pn_link)
|
2013-12-17 15:11:04 -05:00
|
|
|
self._pending_sends = collections.deque()
|
|
|
|
self._pending_acks = {}
|
|
|
|
self._next_deadline = 0
|
|
|
|
self._next_tag = 0
|
|
|
|
|
2014-03-04 15:38:08 -05:00
|
|
|
# TODO(kgiusti) - think about send-settle-mode configuration
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-01-20 14:12:37 -05:00
|
|
|
def send(self, message, delivery_callback=None,
|
|
|
|
handle=None, deadline=None):
|
|
|
|
self._pending_sends.append((message, delivery_callback, handle,
|
|
|
|
deadline))
|
2014-03-04 15:38:08 -05:00
|
|
|
# TODO(kgiusti) deadline not supported yet
|
2014-03-07 15:27:13 -05:00
|
|
|
if deadline:
|
|
|
|
raise NotImplementedError("send timeout not supported yet!")
|
2013-12-17 15:11:04 -05:00
|
|
|
if deadline and (self._next_deadline == 0 or
|
|
|
|
self._next_deadline > deadline):
|
|
|
|
self._next_deadline = deadline
|
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
pn_delivery = self._pn_link.delivery("tag-%x" % self._next_tag)
|
2013-12-17 15:11:04 -05:00
|
|
|
self._next_tag += 1
|
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
if pn_delivery.writable:
|
2013-12-17 15:11:04 -05:00
|
|
|
send_req = self._pending_sends.popleft()
|
2014-03-07 15:27:13 -05:00
|
|
|
self._write_msg(pn_delivery, send_req)
|
2013-12-17 15:11:04 -05:00
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def pending(self):
|
|
|
|
return len(self._pending_sends) + len(self._pending_acks)
|
|
|
|
|
|
|
|
def credit(self):
|
|
|
|
return self._pn_link.credit()
|
|
|
|
|
2013-12-23 16:04:04 -05:00
|
|
|
def close(self, error=None):
|
2013-12-17 15:11:04 -05:00
|
|
|
while self._pending_sends:
|
|
|
|
i = self._pending_sends.popleft()
|
|
|
|
cb = i[1]
|
|
|
|
if cb:
|
|
|
|
handle = i[2]
|
|
|
|
cb(self, handle, self.ABORTED, error)
|
|
|
|
for i in self._pending_acks.itervalues():
|
|
|
|
cb = i[1]
|
|
|
|
handle = i[2]
|
|
|
|
cb(self, handle, self.ABORTED, error)
|
2013-12-23 16:04:04 -05:00
|
|
|
self._pending_acks.clear()
|
|
|
|
super(SenderLink, self).close()
|
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def reject(self, reason):
|
|
|
|
"""See Link Reject, AMQP1.0 spec."""
|
|
|
|
# TODO(kgiusti) support reason for close
|
|
|
|
self._pn_link.source.type = proton.Terminus.UNSPECIFIED
|
|
|
|
self._pn_link.open()
|
|
|
|
self._pn_link.close()
|
|
|
|
|
2013-12-23 16:04:04 -05:00
|
|
|
def destroy(self):
|
2013-12-17 15:11:04 -05:00
|
|
|
self._connection._remove_sender(self._name)
|
|
|
|
self._connection = None
|
2013-12-23 16:04:04 -05:00
|
|
|
super(SenderLink, self).destroy()
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def _process_delivery(self, pn_delivery):
|
|
|
|
"""Check if the delivery can be processed."""
|
2013-12-17 15:11:04 -05:00
|
|
|
_disposition_state_map = {
|
|
|
|
proton.Disposition.ACCEPTED: SenderLink.ACCEPTED,
|
|
|
|
proton.Disposition.REJECTED: SenderLink.REJECTED,
|
|
|
|
proton.Disposition.RELEASED: SenderLink.RELEASED,
|
|
|
|
proton.Disposition.MODIFIED: SenderLink.MODIFIED,
|
2014-01-20 14:12:37 -05:00
|
|
|
}
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
if pn_delivery.tag in self._pending_acks:
|
|
|
|
if pn_delivery.settled: # remote has finished
|
|
|
|
LOG.debug("Remote has settled a sent msg")
|
|
|
|
send_req = self._pending_acks.pop(pn_delivery.tag)
|
|
|
|
state = _disposition_state_map.get(pn_delivery.remote_state,
|
2013-12-17 15:11:04 -05:00
|
|
|
self.UNKNOWN)
|
|
|
|
cb = send_req[1]
|
|
|
|
handle = send_req[2]
|
|
|
|
|
|
|
|
cb(self, handle, state, None)
|
2014-03-07 15:27:13 -05:00
|
|
|
pn_delivery.settle()
|
2013-12-17 15:11:04 -05:00
|
|
|
else:
|
|
|
|
# not for a sent msg, use it to send the next
|
2014-03-07 15:27:13 -05:00
|
|
|
if pn_delivery.writable and self._pending_sends:
|
2013-12-17 15:11:04 -05:00
|
|
|
send_req = self._pending_sends.popleft()
|
2014-03-07 15:27:13 -05:00
|
|
|
self._write_msg(pn_delivery, send_req)
|
2013-12-17 15:11:04 -05:00
|
|
|
else:
|
|
|
|
# what else is there???
|
2014-03-07 15:27:13 -05:00
|
|
|
pn_delivery.settle()
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def _write_msg(self, pn_delivery, send_req):
|
2013-12-17 15:11:04 -05:00
|
|
|
# given a writable delivery, send a message
|
|
|
|
# send_req = (msg, cb, handle, deadline)
|
2014-03-07 15:27:13 -05:00
|
|
|
LOG.debug("Sending a pending message")
|
2013-12-17 15:11:04 -05:00
|
|
|
msg = send_req[0]
|
|
|
|
cb = send_req[1]
|
2014-01-20 14:12:37 -05:00
|
|
|
self._pn_link.send(msg.encode())
|
2013-12-17 15:11:04 -05:00
|
|
|
self._pn_link.advance()
|
|
|
|
if cb: # delivery callback given
|
2014-03-07 15:27:13 -05:00
|
|
|
if pn_delivery.tag in self._pending_acks:
|
|
|
|
raise Exception("Duplicate delivery tag?")
|
|
|
|
self._pending_acks[pn_delivery.tag] = send_req
|
2013-12-17 15:11:04 -05:00
|
|
|
else:
|
|
|
|
# no status required, so settle it now.
|
2014-03-07 15:27:13 -05:00
|
|
|
pn_delivery.settle()
|
2013-12-17 15:11:04 -05:00
|
|
|
|
|
|
|
|
|
|
|
class ReceiverEventHandler(object):
|
|
|
|
|
|
|
|
def receiver_active(self, receiver_link):
|
2014-01-01 12:41:17 -05:00
|
|
|
LOG.debug("receiver_active (ignored)")
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-01-09 15:55:34 -05:00
|
|
|
def receiver_remote_closed(self, receiver_link, error=None):
|
|
|
|
LOG.debug("receiver_remote_closed (ignored)")
|
|
|
|
|
|
|
|
def receiver_closed(self, receiver_link):
|
2014-01-01 12:41:17 -05:00
|
|
|
LOG.debug("receiver_closed (ignored)")
|
2013-12-17 15:11:04 -05:00
|
|
|
|
|
|
|
def message_received(self, receiver_link, message, handle):
|
2014-01-01 12:41:17 -05:00
|
|
|
LOG.debug("message_received (ignored)")
|
2013-12-17 15:11:04 -05:00
|
|
|
|
|
|
|
|
|
|
|
class ReceiverLink(_Link):
|
2014-03-07 15:27:13 -05:00
|
|
|
def __init__(self, connection, pn_link):
|
|
|
|
super(ReceiverLink, self).__init__(connection, pn_link)
|
2013-12-17 15:11:04 -05:00
|
|
|
self._next_handle = 0
|
2014-01-20 14:12:37 -05:00
|
|
|
self._unsettled_deliveries = {} # indexed by handle
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-03-04 15:38:08 -05:00
|
|
|
# TODO(kgiusti) - think about receiver-settle-mode configuration
|
2013-12-17 15:11:04 -05:00
|
|
|
|
|
|
|
def capacity(self):
|
|
|
|
return self._pn_link.credit()
|
|
|
|
|
|
|
|
def add_capacity(self, amount):
|
|
|
|
self._pn_link.flow(amount)
|
|
|
|
|
|
|
|
def message_accepted(self, handle):
|
|
|
|
self._settle_delivery(handle, proton.Delivery.ACCEPTED)
|
|
|
|
|
|
|
|
def message_rejected(self, handle, reason=None):
|
2014-03-04 15:38:08 -05:00
|
|
|
# TODO(kgiusti): how to deal with 'reason'
|
2013-12-17 15:11:04 -05:00
|
|
|
self._settle_delivery(handle, proton.Delivery.REJECTED)
|
|
|
|
|
|
|
|
def message_released(self, handle):
|
|
|
|
self._settle_delivery(handle, proton.Delivery.RELEASED)
|
|
|
|
|
|
|
|
def message_modified(self, handle):
|
|
|
|
self._settle_delivery(handle, proton.Delivery.MODIFIED)
|
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def reject(self, reason):
|
|
|
|
"""See Link Reject, AMQP1.0 spec."""
|
|
|
|
# TODO(kgiusti) support reason for close
|
|
|
|
self._pn_link.target.type = proton.Terminus.UNSPECIFIED
|
|
|
|
self._pn_link.open()
|
|
|
|
self._pn_link.close()
|
|
|
|
|
2014-03-04 15:38:08 -05:00
|
|
|
def destroy(self):
|
2013-12-17 15:11:04 -05:00
|
|
|
self._connection._remove_receiver(self._name)
|
|
|
|
self._connection = None
|
2013-12-23 16:04:04 -05:00
|
|
|
super(ReceiverLink, self).destroy()
|
2013-12-17 15:11:04 -05:00
|
|
|
|
2014-03-07 15:27:13 -05:00
|
|
|
def _process_delivery(self, pn_delivery):
|
|
|
|
"""Check if the delivery can be processed."""
|
2014-03-04 15:38:08 -05:00
|
|
|
# TODO(kgiusti): multi-frame message transfer
|
2014-03-07 15:27:13 -05:00
|
|
|
if pn_delivery.readable:
|
|
|
|
LOG.debug("Receive delivery readable")
|
|
|
|
data = self._pn_link.recv(pn_delivery.pending)
|
2013-12-17 15:11:04 -05:00
|
|
|
msg = proton.Message()
|
|
|
|
msg.decode(data)
|
|
|
|
self._pn_link.advance()
|
|
|
|
|
|
|
|
if self._handler:
|
|
|
|
handle = "rmsg-%s:%x" % (self._name, self._next_handle)
|
|
|
|
self._next_handle += 1
|
2014-03-07 15:27:13 -05:00
|
|
|
self._unsettled_deliveries[handle] = pn_delivery
|
2013-12-17 15:11:04 -05:00
|
|
|
self._handler.message_received(self, msg, handle)
|
|
|
|
else:
|
2014-03-04 15:38:08 -05:00
|
|
|
# TODO(kgiusti): is it ok to assume Delivery.REJECTED?
|
2014-03-07 15:27:13 -05:00
|
|
|
pn_delivery.settle()
|
2013-12-17 15:11:04 -05:00
|
|
|
|
|
|
|
def _settle_delivery(self, handle, result):
|
|
|
|
# settle delivery associated with a handle
|
|
|
|
if handle not in self._unsettled_deliveries:
|
|
|
|
raise Exception("Invalid message handle: %s" % str(handle))
|
2014-03-07 15:27:13 -05:00
|
|
|
pn_delivery = self._unsettled_deliveries.pop(handle)
|
|
|
|
pn_delivery.update(result)
|
|
|
|
pn_delivery.settle()
|