diff --git a/sdk/servicebus/azure-mgmt-servicebus/dev_requirements.txt b/sdk/servicebus/azure-mgmt-servicebus/dev_requirements.txt index c7c93cfae649..1a1c8d8fc379 100644 --- a/sdk/servicebus/azure-mgmt-servicebus/dev_requirements.txt +++ b/sdk/servicebus/azure-mgmt-servicebus/dev_requirements.txt @@ -1,2 +1,2 @@ -e ../../../tools/azure-sdk-tools --e ../../../tools/azure-devtools +-e ../../../tools/azure-devtools \ No newline at end of file diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index e28ce2ec1403..57cefcdf6b01 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -1,8 +1,58 @@ # Release History -## 0.50.2 (2019-12-9) +## 7.0.0b1 (2020-04-03) -**Features** +Version 7.0.0b1 is a preview of our efforts to create a client library that is user friendly and idiomatic to the Python ecosystem. The reasons for most of the changes in this update can be found in the Azure SDK Design Guidelines for Python. For more information, please visit https://aka.ms/azure-sdk-preview1-python. +* Note: Not all historical functionality exists in this version at this point. Topics, Subscriptions, scheduling, dead_letter management and more will be added incrementally over upcoming preview releases. + +**New Features** + +* Added new configuration parameters when creating `ServiceBusClient`. + * `credential`: The credential object used for authentication which implements `TokenCredential` interface of getting tokens. + * `http_proxy`: A dictionary populated with proxy settings. + * For detailed information about configuration parameters, please see docstring in `ServiceBusClient` and/or the reference documentation for more information. +* Added support for authentication using Azure Identity credentials. +* Added support for retry policy. +* Added support for http proxy. +* Manually calling `reconnect` should no longer be necessary, it is now performed implicitly. +* Manually calling `open` should no longer be necessary, it is now performed implicitly. + * Note: `close()`-ing is still required if a context manager is not used, to avoid leaking connections. +* Added support for sending a batch of messages destined for heterogenous sessions. + +**Breaking changes** + +* Simplified API and set of clients + * `get_queue` no longer exists, utilize `get_queue_sender/receiver` instead. + * `peek` and other `queue_client` functions have moved to their respective sender/receiver. + * Renamed `fetch_next` to `receive`. + * `reconnect` no longer exists, and is performed implicitly if needed. + * `open` no longer exists, and is performed implicitly if needed. +* Normalized top level client parameters with idiomatic and consistent naming. + * Renamed `debug` in `ServiceBusClient` initializer to `logging_enable`. + * Renamed `service_namespace` in `ServiceBusClient` initializer to `fully_qualified_namespace`. +* New error hierarchy, with more specific semantics + * `azure.servicebus.exceptions.ServiceBusError` + * `azure.servicebus.exceptions.ServiceBusConnectionError` + * `azure.servicebus.exceptions.ServiceBusResourceNotFound` + * `azure.servicebus.exceptions.ServiceBusAuthorizationError` + * `azure.servicebus.exceptions.NoActiveSession` + * `azure.servicebus.exceptions.OperationTimeoutError` + * `azure.servicebus.exceptions.InvalidHandlerState` + * `azure.servicebus.exceptions.AutoLockRenewTimeout` + * `azure.servicebus.exceptions.AutoLockRenewFailed` + * `azure.servicebus.exceptions.EventDataSendError` + * `azure.servicebus.exceptions.MessageSendFailed` + * `azure.servicebus.exceptions.MessageLockExpired` + * `azure.servicebus.exceptions.MessageSettleFailed` + * `azure.servicebus.exceptions.MessageAlreadySettled` + * `azure.servicebus.exceptions.SessionLockExpired` +* Session is now set on the message itself, via `session_id` parameter or property, as opposed to on `Send` or `get_sender` via `session`. This is to allow sending a batch of messages destined to varied sessions. +* Session management is now encapsulated within a property of a receiver, e.g. `receiver.session`, to better compartmentalize functionality specific to sessions. + * To use `AutoLockRenew` against sessions, one would simply pass the inner session object, instead of the receiver itself. + +## 0.50.2 (2019-12-09) + +**New Features** * Added support for delivery tag lock tokens @@ -29,7 +79,7 @@ Within the new namespace, the original HTTP-based API from version 0.21.1 remains unchanged (i.e. no additional features or bugfixes) so for those intending to only use HTTP operations - there is no additional benefit in updating at this time. -**Features** +**New Features** * New API supports message send and receive via AMQP with improved performance and stability. * New asynchronous APIs (using `asyncio`) for send, receive and message handling. @@ -43,7 +93,7 @@ This wheel package is now built with the azure wheel extension ## 0.21.0 (2017-01-13) -**Features** +**New Features** * `str` messages are now accepted in Python 3 and will be encoded in 'utf-8' (will not raise TypeError anymore) * `broker_properties` can now be defined as a dict, and not only a JSON `str`. datetime, int, float and boolean are converted. diff --git a/sdk/servicebus/azure-servicebus/README.md b/sdk/servicebus/azure-servicebus/README.md index cd4e0f53c593..d7d25bc3f939 100644 --- a/sdk/servicebus/azure-servicebus/README.md +++ b/sdk/servicebus/azure-servicebus/README.md @@ -1,78 +1,197 @@ -# Microsoft Azure Service Bus SDK for Python +# Azure Service Bus client library for Python -This is the Microsoft Azure Service Bus Client Library. -This package has been tested with Python 2.7, 3.4, 3.5, 3.6 and 3.7. +Azure Service Bus is a high performance cloud-managed messaging service for providing real-time and fault-tolerant communication between distributed senders and receivers. -Microsoft Azure Service Bus supports a set of cloud-based, message-oriented middleware technologies including reliable message queuing and durable publish/subscribe messaging. +Service Bus provides multiple mechanisms for asynchronous highly reliable communication, such as structured first-in-first-out messaging, +publish/subscribe capabilities, and the ability to easily scale as your needs grow. -* [SDK source code](https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/servicebus/azure-servicebus) -* [SDK reference documentation](https://docs.microsoft.com/python/api/overview/azure/servicebus/client?view=azure-python) -* [Service Bus documentation](https://docs.microsoft.com/azure/service-bus-messaging/) +Use the Service Bus client library for Python to communicate between applications and services and implement asynchronous messaging patterns. +* Create Service Bus namespaces, queues, topics, and subscriptions, and modify their settings +* Send and receive messages within your Service Bus channels. +* Utilize message locks, sessions, and dead letter functionality to implement complex messaging patterns. -## What's new in v0.50.2? +[Source code](./) | [Package (PyPi)][pypi] | [API reference documentation][api_docs] | [Product documentation][product_docs] | [Samples](./samples) | [Changelog](./CHANGELOG.md) -As of version 0.50.2 a new AMQP-based API is available for sending and receiving messages. This update involves **breaking changes**. -Please read [Migration from 0.21.1 to 0.50.2](#migration-from-0211-to-0502) to determine if upgrading is -right for you at this time. +## Getting started -The new AMQP-based API offers improved message passing reliability, performance and expanded feature support going forward. -The new API also offers support for asynchronous operations (based on asyncio) for sending, receiving and handling messages. +### Install the package -For documentation on the legacy HTTP-based operations please see [Using HTTP-based operations of the legacy API](https://docs.microsoft.com/python/api/overview/azure/servicebus?view=azure-python#using-http-based-operations-of-the-legacy-api). +Install the Azure Service Bus client library for Python with [pip][pip]: +```Bash +pip install azure-servicebus --pre +``` + +### Prerequisites: +To use this package, you must have: +* Azure subscription - [Create a free account][azure_sub] +* Azure Service Bus - [Namespace and management credentials][service_bus_namespace] +* Python 2.7, 3.5, 3.6, 3.7 or 3.8 - [Install Python][python] + + +If you need an Azure service bus namespace, you can create it via the [Azure Portal][azure_namespace_creation]. +If you do not wish to use the graphical portal UI, you can use the Azure CLI via [Cloud Shell][cloud_shell_bash], or Azure CLI run locally, to create one with this Azure CLI command: + +```Bash +az servicebus namespace create --resource-group --name --location +``` + +### Authenticate the client + +Interaction with Service Bus starts with an instance of the `ServiceBusClient` class. You either need a **connection string with SAS key**, or a **namespace** and one of its **account keys** to instantiate the client object. + +#### Get credentials + +Use the [Azure CLI][azure_cli] snippet below to populate an environment variable with the service bus connection string (you can also find these values in the [Azure portal][azure_portal]. The snippet is formatted for the Bash shell. + +```Bash +RES_GROUP= +NAMESPACE_NAME= + +export SERVICE_BUS_CONN_STR=$(az servicebus namespace authorization-rule keys list --resource-group $RES_GROUP --namespace-name $NAMESPACE_NAME --query RootManageSharedAccessKey --output tsv) +``` + +#### Create client + +Once you've populated the `SERVICE_BUS_CONN_STR` environment variable, you can create the `ServiceBusClient`. + +```Python +from azure.servicebus import ServiceBusClient + +import os +connstr = os.environ['SERVICE_BUS_CONN_STR'] + +with ServiceBusClient.from_connection_string(connstr) as client: + ... +``` -## Prerequisites +Note: client can be initialized without a context manager, but must be manually closed via client.close() to not leak resources. -* Azure subscription - [Create a free account](https://azure.microsoft.com/free/) -* Azure Service Bus [namespace and management credentials](https://docs.microsoft.com/azure/service-bus-messaging/service-bus-create-namespace-portal) +## Key concepts +Once you've initialized a `ServiceBusClient`, you can interact with the primary resource types within a Service Bus Namespace, of which multiple can exist and on which actual message transmission takes place, the namespace often serving as an application container: -## Installation +* [Queue][queue_concept]: Allows for Sending and Receiving of messages, ordered first-in-first-out. Often used for point-to-point communication. -```shell -pip install azure-servicebus +* [Topic][topic_concept]: As opposed to Queues, Topics are better suited to publish/subscribe scenarios. A topic can be sent to, but requires a subscription, of which there can be multiple in parallel, to consume from. + +* [Subscription][subscription_concept]: The mechanism to consume from a Topic. Each subscription is independent, and receaves a copy of each message sent to the topic. Rules and Filters can be used to tailor which messages are received by a specific subscription. + +For more information about these resources, see [What is Azure Service Bus?][service_bus_overview]. + +## Examples + +The following sections provide several code snippets covering some of the most common Service Bus tasks, including: + +* [Send a message to a queue](#send-to-a-queue) +* [Receive a message from a queue](#receive-from-a-queue) +* [Defer a message on receipt](#defer-a-message) + + +### Send to a queue + +This example sends a message to a queue that is assumed to already exist, created via the Azure portal or az commands. + +```Python +from azure.servicebus import ServiceBusClient + +import os +connstr = os.environ['SERVICE_BUS_CONN_STR'] + +with ServiceBusClient.from_connection_string(connstr) as client: + with client.get_queue_sender(queue_name): + + message = Message("Single message") + queue_sender.send(message) ``` -## Migration from 0.21.1 to 0.50.2 +### Receive from a queue -Major breaking changes were introduced in version 0.50.2. -The original HTTP-based API is still available in v0.50.2 - however it now exists under a new namesapce: `azure.servicebus.control_client`. +To receive from a queue, you can either perform a one-off receive via "receiver.receive()" or receive persistently as follows: -### Should I upgrade? +```Python +from azure.servicebus import ServiceBusClient -The new package (v0.50.2) offers no improvements in HTTP-based operations over v0.21.1. The HTTP-based API is identical except that it now -exists under a new namespace. For this reason if you only wish to use HTTP-based operations (`create_queue`, `delete_queue` etc) - there will be -no additional benefit in upgrading at this time. +import os +connstr = os.environ['SERVICE_BUS_CONN_STR'] +with ServiceBusClient.from_connection_string(connstr) as client: + with client.get_queue_receiver(queue_name) as receiver: + for msg in receiver: + print(str(msg)) + msg.complete() +``` -### How do I migrate my code to the new version? +### Defer a message -Code written against v0.21.0 can be ported to version 0.50.2 by simply changing the import namespace: +When receiving from a queue, you have multiple actions you can take on the messages you receive. Where the prior example completes a message, +permanently removing it from the queue and marking as complete, this example demonstrates how to defer the message, sending it back to the queue +such that it must now be received via sequence number: -```python -# from azure.servicebus import ServiceBusService <- This will now raise an ImportError -from azure.servicebus.control_client import ServiceBusService +```Python +from azure.servicebus import ServiceBusClient -key_name = 'RootManageSharedAccessKey' # SharedAccessKeyName from Azure portal -key_value = '' # SharedAccessKey from Azure portal -sbs = ServiceBusService(service_namespace, - shared_access_key_name=key_name, - shared_access_key_value=key_value) +import os +connstr = os.environ['SERVICE_BUS_CONN_STR'] + +with ServiceBusClient.from_connection_string(connstr) as client: + with client.get_queue_receiver(queue_name) as receiver: + for msg in receiver: + print(str(msg)) + msg.defer() ``` +## Troubleshooting + +### Logging + +- Enable `azure.servicebus` logger to collect traces from the library. +- Enable `uamqp` logger to collect traces from the underlying uAMQP library. +- Enable AMQP frame level trace by setting `logging_enable=True` when creating the client. + +## Next steps + +### More sample code + +Please find further examples in the [samples](./samples) directory demonstrating common Service Bus scenarios such as sending, receiving, and message handling. -# Usage +### Additional documentation -For reference documentation and code snippets see [Service Bus](https://docs.microsoft.com/python/api/overview/azure/servicebus) -on docs.microsoft.com. +For more extensive documentation on the Service Bus service, see the [Service Bus DB documentation][service_bus_docs] on docs.microsoft.com. +## Contributing -# Provide Feedback +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. -If you encounter any bugs or have suggestions, please file an issue in the -[Issues](https://github.com/Azure/azure-sdk-for-python/issues) -section of the project. +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. -![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-python%2Fazure-servicebus%2FREADME.png) + +[azure_cli]: https://docs.microsoft.com/cli/azure +[api_docs]: https://docs.microsoft.com/python/api/overview/azure/servicebus/client?view=azure-python +[product_docs]: https://docs.microsoft.com/azure/service-bus-messaging/ +[azure_portal]: https://portal.azure.com +[azure_sub]: https://azure.microsoft.com/free/ +[cloud_shell]: https://docs.microsoft.com/azure/cloud-shell/overview +[cloud_shell_bash]: https://shell.azure.com/bash +[pip]: https://pypi.org/project/pip/ +[pypi]: https://pypi.org/project/azure-servicebus/ +[python]: https://www.python.org/downloads/ +[venv]: https://docs.python.org/3/library/venv.html +[virtualenv]: https://virtualenv.pypa.io +[service_bus_namespace]: https://docs.microsoft.com/azure/service-bus-messaging/service-bus-create-namespace-portal +[service_bus_overview]: https://docs.microsoft.com/azure/service-bus-messaging/service-bus-messaging-overview +[queue_status_codes]: https://docs.microsoft.com/rest/api/servicebus/create-queue#response-codes +[service_bus_docs]: https://docs.microsoft.com/azure/service-bus/ +[queue_concept]: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview#queues +[topic_concept]: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview#topics +[subscription_concept]: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-queues-topics-subscriptions#topics-and-subscriptions +[azure_namespace_creation]: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-create-namespace-portal diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/__init__.py b/sdk/servicebus/azure-servicebus/azure/servicebus/__init__.py index 6d30e484e593..0e1fa7279ca6 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/__init__.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/__init__.py @@ -3,16 +3,19 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # ------------------------------------------------------------------------- +from uamqp import constants from ._version import VERSION __version__ = VERSION - -from azure.servicebus.common.message import Message, BatchMessage, PeekMessage, DeferredMessage -from azure.servicebus.servicebus_client import ServiceBusClient, QueueClient, TopicClient, SubscriptionClient -from azure.servicebus.common.constants import ReceiveSettleMode, NEXT_AVAILABLE -from azure.servicebus.common.utils import AutoLockRenew -from azure.servicebus.common.errors import ( +from ._servicebus_client import ServiceBusClient +from ._servicebus_sender import ServiceBusSender +from ._servicebus_receiver import ServiceBusReceiver, ServiceBusSession +from ._base_handler import ServiceBusSharedKeyCredential +from ._common.message import Message, BatchMessage, PeekMessage, ReceivedMessage +from ._common.constants import ReceiveSettleMode, NEXT_AVAILABLE +from ._common.utils import AutoLockRenew +from .exceptions import ( ServiceBusError, ServiceBusResourceNotFound, ServiceBusConnectionError, @@ -25,21 +28,16 @@ MessageLockExpired, SessionLockExpired, AutoLockRenewFailed, - AutoLockRenewTimeout) + AutoLockRenewTimeout,) -from uamqp.constants import TransportType +TransportType = constants.TransportType __all__ = [ 'Message', 'BatchMessage', 'PeekMessage', - 'AutoLockRenew', - 'DeferredMessage', - 'ServiceBusClient', - 'QueueClient', - 'TopicClient', - 'SubscriptionClient', + 'ReceivedMessage', 'ReceiveSettleMode', 'NEXT_AVAILABLE', 'ServiceBusError', @@ -55,4 +53,11 @@ 'SessionLockExpired', 'AutoLockRenewFailed', 'AutoLockRenewTimeout', - 'TransportType'] + 'ServiceBusClient', + 'ServiceBusReceiver', + 'ServiceBusSender', + 'ServiceBusSharedKeyCredential', + 'TransportType', + 'AutoLockRenew', + 'ServiceBusSession' +] diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py new file mode 100644 index 000000000000..e18d30cbd5ad --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -0,0 +1,287 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import collections +import logging +import uuid +import time +from datetime import timedelta +from typing import cast, Optional, Tuple, TYPE_CHECKING, Dict, Any + +try: + from urllib import quote_plus # type: ignore +except ImportError: + from urllib.parse import quote_plus + +import uamqp +from uamqp import utils +from uamqp.message import MessageProperties + +from ._common._configuration import Configuration +from .exceptions import ( + InvalidHandlerState, + ServiceBusError, + ServiceBusAuthorizationError, + _create_servicebus_exception +) +from ._common.utils import create_properties +from ._common.constants import ( + CONTAINER_PREFIX, + MANAGEMENT_PATH_SUFFIX, + TOKEN_TYPE_SASTOKEN, + MGMT_REQUEST_OP_TYPE_ENTITY_MGMT +) + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + +_AccessToken = collections.namedtuple("AccessToken", "token expires_on") +_LOGGER = logging.getLogger(__name__) + + +def _parse_conn_str(conn_str): + # type: (str) -> Tuple[str, str, str, str] + endpoint = None + shared_access_key_name = None + shared_access_key = None + entity_path = None # type: Optional[str] + for element in conn_str.split(";"): + key, _, value = element.partition("=") + if key.lower() == "endpoint": + endpoint = value.rstrip("/") + elif key.lower() == "hostname": + endpoint = value.rstrip("/") + elif key.lower() == "sharedaccesskeyname": + shared_access_key_name = value + elif key.lower() == "sharedaccesskey": + shared_access_key = value + elif key.lower() == "entitypath": + entity_path = value + if not all([endpoint, shared_access_key_name, shared_access_key]): + raise ValueError( + "Invalid connection string. Should be in the format: " + "Endpoint=sb:///;SharedAccessKeyName=;SharedAccessKey=" + ) + entity = cast(str, entity_path) + left_slash_pos = cast(str, endpoint).find("//") + if left_slash_pos != -1: + host = cast(str, endpoint)[left_slash_pos + 2:] + else: + host = str(endpoint) + return host, str(shared_access_key_name), str(shared_access_key), entity + + +def _generate_sas_token(uri, policy, key, expiry=None): + # type: (str, str, str, Optional[timedelta]) -> _AccessToken + """Create a shared access signiture token as a string literal. + :returns: SAS token as string literal. + :rtype: str + """ + if not expiry: + expiry = timedelta(hours=1) # Default to 1 hour. + + abs_expiry = int(time.time()) + expiry.seconds + encoded_uri = quote_plus(uri).encode("utf-8") # pylint: disable=no-member + encoded_policy = quote_plus(policy).encode("utf-8") # pylint: disable=no-member + encoded_key = key.encode("utf-8") + + token = utils.create_sas_token(encoded_policy, encoded_key, encoded_uri, expiry) + return _AccessToken(token=token, expires_on=abs_expiry) + + +class ServiceBusSharedKeyCredential(object): + """The shared access key credential used for authentication. + + :param str policy: The name of the shared access policy. + :param str key: The shared access key. + """ + + def __init__(self, policy, key): + # type: (str, str) -> None + self.policy = policy + self.key = key + self.token_type = TOKEN_TYPE_SASTOKEN + + def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument + # type: (str, Any) -> _AccessToken + if not scopes: + raise ValueError("No token scope provided.") + return _generate_sas_token(scopes[0], self.policy, self.key) + + +class BaseHandler(object): # pylint:disable=too-many-instance-attributes + def __init__( + self, + fully_qualified_namespace, + entity_name, + credential, + **kwargs + ): + self.fully_qualified_namespace = fully_qualified_namespace + self._entity_name = entity_name + self._mgmt_target = self._entity_name + MANAGEMENT_PATH_SUFFIX + self._credential = credential + self._container_id = CONTAINER_PREFIX + str(uuid.uuid4())[:8] + self._config = Configuration(**kwargs) + self._running = False + self._handler = None + self._auth_uri = None + self._properties = create_properties() + + def __enter__(self): + self._open_with_retry() + return self + + def __exit__(self, *args): + self.close() + + def _handle_exception(self, exception): + error, error_need_close_handler, error_need_raise = _create_servicebus_exception(_LOGGER, exception, self) + if error_need_close_handler: + self._close_handler() + if error_need_raise: + raise error + + return error + + @staticmethod + def _from_connection_string(conn_str, **kwargs): + # type: (str, Any) -> Dict[str, Any] + host, policy, key, entity_in_conn_str = _parse_conn_str(conn_str) + queue_name = kwargs.get("queue_name") + topic_name = kwargs.get("topic_name") + if not (queue_name or topic_name or entity_in_conn_str): + raise ValueError("Entity name is missing. Please specify `queue_name` or `topic_name`" + " or use a connection string including the entity information.") + + if queue_name and topic_name: + raise ValueError("`queue_name` and `topic_name` can not be specified simultaneously.") + + entity_in_kwargs = queue_name or topic_name + if entity_in_conn_str and entity_in_kwargs and (entity_in_conn_str != entity_in_kwargs): + raise ServiceBusAuthorizationError( + "Entity names do not match, the entity name in connection string is {};" + " the entity name in parameter is {}.".format(entity_in_conn_str, entity_in_kwargs) + ) + + kwargs["fully_qualified_namespace"] = host + kwargs["entity_name"] = entity_in_conn_str or entity_in_kwargs + kwargs["credential"] = ServiceBusSharedKeyCredential(policy, key) + return kwargs + + def _backoff( + self, + retried_times, + last_exception, + timeout=None, + entity_name=None + ): + entity_name = entity_name or self._container_id + backoff = self._config.retry_backoff_factor * 2 ** retried_times + if backoff <= self._config.retry_backoff_max and ( + timeout is None or backoff <= timeout + ): # pylint:disable=no-else-return + time.sleep(backoff) + _LOGGER.info( + "%r has an exception (%r). Retrying...", + format(entity_name), + last_exception, + ) + else: + _LOGGER.info( + "%r operation has timed out. Last exception before timeout is (%r)", + entity_name, + last_exception, + ) + raise last_exception + + def _do_retryable_operation(self, operation, timeout=None, **kwargs): + require_last_exception = kwargs.pop("require_last_exception", False) + require_timeout = kwargs.pop("require_timeout", False) + retried_times = 0 + last_exception = None + max_retries = self._config.retry_total + + while retried_times <= max_retries: + try: + if require_last_exception: + kwargs["last_exception"] = last_exception + if require_timeout: + kwargs["timeout"] = timeout + return operation(**kwargs) + except StopIteration: + raise + except Exception as exception: # pylint: disable=broad-except + last_exception = self._handle_exception(exception) + retried_times += 1 + if retried_times > max_retries: + break + self._backoff( + retried_times=retried_times, + last_exception=last_exception, + timeout=timeout + ) + + _LOGGER.info( + "%r operation has exhausted retry. Last exception: %r.", + self._container_id, + last_exception, + ) + raise last_exception + + def _mgmt_request_response(self, mgmt_operation, message, callback, **kwargs): + self._open() + if not self._running: + raise InvalidHandlerState("Client connection is closed.") + + mgmt_msg = uamqp.Message( + body=message, + properties=MessageProperties( + reply_to=self._mgmt_target, + encoding=self._config.encoding, + **kwargs + ) + ) + try: + return self._handler.mgmt_request( + mgmt_msg, + mgmt_operation, + op_type=MGMT_REQUEST_OP_TYPE_ENTITY_MGMT, + node=self._mgmt_target.encode(self._config.encoding), + timeout=5000, + callback=callback + ) + except Exception as exp: # pylint: disable=broad-except + raise ServiceBusError("Management request failed: {}".format(exp), exp) + + def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, **kwargs): + return self._do_retryable_operation( + self._mgmt_request_response, + mgmt_operation=mgmt_operation, + message=message, + callback=callback, + **kwargs + ) + + def _open(self): # pylint: disable=no-self-use + raise ValueError("Subclass should override the method.") + + def _open_with_retry(self): + return self._do_retryable_operation(self._open) + + def _close_handler(self): + if self._handler: + self._handler.close() + self._handler = None + self._running = False + + def close(self): + # type: () -> None + """Close down the handler links (and connection if the handler uses a separate connection). + + If the handler has already closed, this operation will do nothing. + + :rtype: None + """ + self._close_handler() diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/common/__init__.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/__init__.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/common/__init__.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_common/__init__.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py new file mode 100644 index 000000000000..2c4b46918971 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py @@ -0,0 +1,30 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Optional, Dict, Any + +from uamqp.constants import TransportType + + +class Configuration(object): # pylint:disable=too-many-instance-attributes + def __init__(self, **kwargs): + self.user_agent = kwargs.get("user_agent") # type: Optional[str] + self.retry_total = kwargs.get("retry_total", 3) # type: int + self.retry_backoff_factor = kwargs.get("retry_backoff_factor", 0.8) # type: float + self.retry_backoff_max = kwargs.get("retry_backoff_max", 120) # type: int + self.logging_enable = kwargs.get("logging_enable", False) # type: bool + self.http_proxy = kwargs.get("http_proxy") # type: Optional[Dict[str, Any]] + self.transport_type = ( + TransportType.AmqpOverWebsocket + if self.http_proxy + else kwargs.get("transport_type", TransportType.Amqp) + ) + self.auth_timeout = kwargs.get("auth_timeout", 60) # type: int + self.encoding = kwargs.get("encoding", "UTF-8") + self.auto_reconnect = kwargs.get("auto_reconnect", True) + self.idle_timeout = kwargs.get("idle_timeout", None) + prefetch = kwargs.get("prefetch", 0) + if int(prefetch) < 0 or int(prefetch) > 50000: + raise ValueError("Prefetch must be an integer between 0 and 50000 inclusive.") + self.prefetch = prefetch + 1 diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/common/constants.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/constants.py similarity index 53% rename from sdk/servicebus/azure-servicebus/azure/servicebus/common/constants.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_common/constants.py index fdf95ecaf522..d16964ecff16 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/common/constants.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/constants.py @@ -42,6 +42,60 @@ REQUEST_RESPONSE_REMOVE_RULE_OPERATION = VENDOR + b":remove-rule" REQUEST_RESPONSE_GET_RULES_OPERATION = VENDOR + b":enumerate-rules" +SETTLEMENT_COMPLETE = "completed" +SETTLEMENT_ABANDON = "abandoned" +SETTLEMENT_DEFER = "defered" +SETTLEMENT_DEADLETTER = "suspended" + +CONTAINER_PREFIX = "servicebus.pysdk-" +JWT_TOKEN_SCOPE = "https://servicebus.azure.net//.default" +USER_AGENT_PREFIX = "azsdk-python-servicebus" + +MANAGEMENT_PATH_SUFFIX = "/$management" + +MGMT_RESPONSE_SESSION_STATE = b'session-state' +MGMT_RESPONSE_MESSAGE_EXPIRATION = b'expirations' +MGMT_RESPONSE_RECEIVER_EXPIRATION = b'expiration' +MGMT_REQUEST_SESSION_ID = 'session-id' +MGMT_REQUEST_SESSION_STATE = 'session-state' +MGMT_REQUEST_DISPOSITION_STATUS = 'disposition-status' +MGMT_REQUEST_LOCK_TOKENS = 'lock-tokens' +MGMT_REQUEST_SEQUENCE_NUMBERS = 'sequence-numbers' +MGMT_REQUEST_RECEIVER_SETTLE_MODE = 'receiver-settle-mode' +MGMT_REQUEST_FROM_SEQUENCE_NUMBER = 'from-sequence-number' +MGMT_REQUEST_MESSAGE_COUNT = 'message-count' +MGMT_REQUEST_MESSAGE = 'message' +MGMT_REQUEST_MESSAGES = 'messages' +MGMT_REQUEST_MESSAGE_ID = 'message-id' +MGMT_REQUEST_PARTITION_KEY = 'partition-key' +MGMT_REQUEST_VIA_PARTITION_KEY = 'via-partition-key' +MGMT_REQUEST_DEAD_LETTER_REASON = 'deadletter-reason' +MGMT_REQUEST_DEAD_LETTER_DESCRIPTION = 'deadletter-description' +MGMT_REQUEST_OP_TYPE_ENTITY_MGMT = b"entity-mgmt" + +MESSAGE_COMPLETE = 'complete' +MESSAGE_DEAD_LETTER = 'dead-letter' +MESSAGE_ABANDON = 'abandon' +MESSAGE_DEFER = 'defer' +MESSAGE_RENEW_LOCK = 'renew' + +TOKEN_TYPE_JWT = b"jwt" +TOKEN_TYPE_SASTOKEN = b"servicebus.windows.net:sastoken" + +# message.encoded_size < 255, batch encode overhead is 5, >=256, overhead is 8 each +_BATCH_MESSAGE_OVERHEAD_COST = [5, 8] + +# Message annotation keys +_X_OPT_ENQUEUED_TIME = b'x-opt-enqueued-time' +_X_OPT_SEQUENCE_NUMBER = b'x-opt-sequence-number' +_X_OPT_ENQUEUE_SEQUENCE_NUMBER = b'x-opt-enqueue-sequence-number' +_X_OPT_PARTITION_ID = b'x-opt-partition-id' +_X_OPT_PARTITION_KEY = b'x-opt-partition-key' +_X_OPT_VIA_PARTITION_KEY = b'x-opt-via-partition-key' +_X_OPT_LOCKED_UNTIL = b'x-opt-locked-until' +_X_OPT_LOCK_TOKEN = b'x-opt-lock-token' +_X_OPT_SCHEDULED_ENQUEUE_TIME = b'x-opt-scheduled-enqueue-time' + class ReceiveSettleMode(Enum): PeekLock = constants.ReceiverSettleMode.PeekLock diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py new file mode 100644 index 000000000000..b973abffa6e4 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py @@ -0,0 +1,633 @@ +# ------------------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ------------------------------------------------------------------------- + +import datetime +import uuid +from typing import Optional, List, Union, Generator + +import uamqp +from uamqp import types + +from .constants import ( + _BATCH_MESSAGE_OVERHEAD_COST, + SETTLEMENT_ABANDON, + SETTLEMENT_COMPLETE, + SETTLEMENT_DEFER, + SETTLEMENT_DEADLETTER, + ReceiveSettleMode, + _X_OPT_ENQUEUED_TIME, + _X_OPT_SEQUENCE_NUMBER, + _X_OPT_ENQUEUE_SEQUENCE_NUMBER, + _X_OPT_PARTITION_ID, + _X_OPT_PARTITION_KEY, + _X_OPT_VIA_PARTITION_KEY, + _X_OPT_LOCKED_UNTIL, + _X_OPT_LOCK_TOKEN, + _X_OPT_SCHEDULED_ENQUEUE_TIME, + MGMT_RESPONSE_MESSAGE_EXPIRATION, + MGMT_REQUEST_DEAD_LETTER_REASON, + MGMT_REQUEST_DEAD_LETTER_DESCRIPTION, + MESSAGE_COMPLETE, + MESSAGE_DEAD_LETTER, + MESSAGE_ABANDON, + MESSAGE_DEFER, + MESSAGE_RENEW_LOCK +) +from ..exceptions import ( + MessageAlreadySettled, + MessageLockExpired, + SessionLockExpired, + MessageSettleFailed +) +from .utils import utc_from_timestamp, utc_now + + +class Message(object): # pylint: disable=too-many-public-methods,too-many-instance-attributes + """A Service Bus Message. + + :ivar properties: Properties of the internal AMQP message object. + :vartype properties: ~uamqp.message.MessageProperties + :ivar header: Header of the internal AMQP message object. + :vartype header: ~uamqp.message.MessageHeader + :ivar message: Internal AMQP message object. + :vartype message: ~uamqp.message.Message + + :param body: The data to send in a single message. + :type body: str or bytes + :keyword str encoding: The encoding for string data. Default is UTF-8. + :keyword str session_id: An optional session ID for the message to be sent. + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START send_complex_message] + :end-before: [END send_complex_message] + :language: python + :dedent: 4 + :caption: Sending a message with additional properties + + """ + + def __init__(self, body, **kwargs): + subject = kwargs.pop('subject', None) + # Although we might normally thread through **kwargs this causes + # problems as MessageProperties won't absorb spurious args. + self._encoding = kwargs.pop("encoding", 'UTF-8') + self.properties = uamqp.message.MessageProperties(encoding=self._encoding, subject=subject) + self.header = uamqp.message.MessageHeader() + self._annotations = {} + self._app_properties = {} + + self._expiry = None + self._receiver = None + self.session_id = kwargs.get("session_id", None) + if 'message' in kwargs: + self.message = kwargs['message'] + self._annotations = self.message.annotations + self._app_properties = self.message.application_properties + self.properties = self.message.properties + self.header = self.message.header + else: + self._build_message(body) + + def __str__(self): + return str(self.message) + + def _build_message(self, body): + if isinstance(body, list) and body: # TODO: This only works for a list of bytes/strings + self.message = uamqp.Message(body[0], properties=self.properties, header=self.header) + for more in body[1:]: + self.message._body.append(more) # pylint: disable=protected-access + elif body is None: + raise ValueError("Message body cannot be None.") + else: + self.message = uamqp.Message(body, properties=self.properties, header=self.header) + + @property + def session_id(self): + # type: () -> str + """The session id of the message + + :rtype: str + """ + try: + return self.properties.group_id.decode('UTF-8') + except (AttributeError, UnicodeDecodeError): + return self.properties.group_id + + @session_id.setter + def session_id(self, value): + """Set the session id on the message. + + :param value: The session id for the message. + :type value: str + """ + self.properties.group_id = value + + @property + def annotations(self): + # type: () -> dict + """The annotations of the message. + + :rtype: dict + """ + return self.message.annotations + + @annotations.setter + def annotations(self, value): + """Set the annotations on the message. + + :param value: The annotations for the Message. + :type value: dict + """ + self.message.annotations = value + + @property + def user_properties(self): + # type: () -> dict + """User defined properties on the message. + + :rtype: dict + """ + return self.message.application_properties + + @user_properties.setter + def user_properties(self, value): + """User defined properties on the message. + + :param value: The application properties for the Message. + :type value: dict + """ + self.message.application_properties = value + + @property + def enqueue_sequence_number(self): + # type: () -> Optional[int] + """ + + :rtype: int + """ + if self.message.annotations: + return self.message.annotations.get(_X_OPT_ENQUEUE_SEQUENCE_NUMBER) + return None + + @enqueue_sequence_number.setter + def enqueue_sequence_number(self, value): + if not self.message.annotations: + self.message.annotations = {} + self.message.annotations[types.AMQPSymbol(_X_OPT_ENQUEUE_SEQUENCE_NUMBER)] = value + + @property + def partition_key(self): + # type: () -> Optional[str] + """ + + :rtype: str + """ + if self.message.annotations: + return self.message.annotations.get(_X_OPT_PARTITION_KEY) + return None + + @partition_key.setter + def partition_key(self, value): + if not self.message.annotations: + self.message.annotations = {} + self.message.annotations[types.AMQPSymbol(_X_OPT_PARTITION_KEY)] = value + + @property + def via_partition_key(self): + # type: () -> Optional[str] + """ + + :rtype: str + """ + if self.message.annotations: + return self.message.annotations.get(_X_OPT_VIA_PARTITION_KEY) + return None + + @via_partition_key.setter + def via_partition_key(self, value): + if not self.message.annotations: + self.message.annotations = {} + self.message.annotations[types.AMQPSymbol(_X_OPT_VIA_PARTITION_KEY)] = value + + @property + def time_to_live(self): + # type: () -> Optional[datetime.timedelta] + """ + + :rtype: ~datetime.timedelta + """ + if self.header and self.header.time_to_live: + return datetime.timedelta(milliseconds=self.header.time_to_live) + return None + + @time_to_live.setter + def time_to_live(self, value): + if not self.header: + self.header = uamqp.message.MessageHeader() + if isinstance(value, datetime.timedelta): + self.header.time_to_live = value.seconds * 1000 + else: + self.header.time_to_live = int(value) * 1000 + + @property + def body(self): + # type: () -> Union[bytes, Generator[bytes]] + """The body of the Message. + + :rtype: bytes or generator[bytes] + """ + return self.message.get_data() + + def schedule(self, schedule_time_utc): + # type: (datetime.datetime) -> None + """Add a specific utc enqueue time to the message. + + :param schedule_time_utc: The scheduled utc time to enqueue the message. + :type schedule_time_utc: ~datetime.datetime + :rtype: None + """ + if not self.properties.message_id: + self.properties.message_id = str(uuid.uuid4()) + if not self.message.annotations: + self.message.annotations = {} + self.message.annotations[types.AMQPSymbol(_X_OPT_SCHEDULED_ENQUEUE_TIME)] = schedule_time_utc + + +class BatchMessage(object): + """A batch of messages. + + Sending messages in a batch is more performant than sending individual message. + BatchMessage helps you create the maximum allowed size batch of `Message` to improve sending performance. + + Use the `add` method to add messages until the maximum batch size limit in bytes has been reached - + at which point a `ValueError` will be raised. + + **Please use the create_batch method of ServiceBusSender + to create a BatchMessage object instead of instantiating a BatchMessage object directly.** + + :ivar max_size_in_bytes: The maximum size of bytes data that a BatchMessage object can hold. + :vartype max_size_in_bytes: int + :ivar message: Internal AMQP BatchMessage object. + :vartype message: ~uamqp.BatchMessage + + :param int max_size_in_bytes: The maximum size of bytes data that a BatchMessage object can hold. + + """ + def __init__(self, max_size_in_bytes=None): + # type: (Optional[int]) -> None + self.max_size_in_bytes = max_size_in_bytes or uamqp.constants.MAX_MESSAGE_LENGTH_BYTES + self.message = uamqp.BatchMessage(data=[], multi_messages=False, properties=None) + self._size = self.message.gather()[0].get_message_encoded_size() + self._count = 0 + self._messages = [] # type: List[Message] + + def __repr__(self): + # type: () -> str + batch_repr = "max_size_in_bytes={}, message_count={}".format( + self.max_size_in_bytes, self._count + ) + return "BatchMessage({})".format(batch_repr) + + def __len__(self): + return self._count + + @property + def size_in_bytes(self): + # type: () -> int + """The combined size of the events in the batch, in bytes. + + :rtype: int + """ + return self._size + + def add(self, message): + # type: (Message) -> None + """Try to add a single Message to the batch. + + The total size of an added message is the sum of its body, properties, etc. + If this added size results in the batch exceeding the maximum batch size, a `ValueError` will + be raised. + + :param message: The Message to be added to the batch. + :type message: ~azure.servicebus.Message + :rtype: None + :raises: :class:`ValueError`, when exceeding the size limit. + """ + message_size = message.message.get_message_encoded_size() + + # For a BatchMessage, if the encoded_message_size of event_data is < 256, then the overhead cost to encode that + # message into the BatchMessage would be 5 bytes, if >= 256, it would be 8 bytes. + size_after_add = ( + self._size + + message_size + + _BATCH_MESSAGE_OVERHEAD_COST[0 if (message_size < 256) else 1] + ) + + if size_after_add > self.max_size_in_bytes: + raise ValueError( + "EventDataBatch has reached its size limit: {}".format( + self.max_size_in_bytes + ) + ) + + self.message._body_gen.append(message) # pylint: disable=protected-access + self._size = size_after_add + self._count += 1 + self._messages.append(message) + + +class PeekMessage(Message): + """A preview message. + + This message is still on the queue, and unlocked. + A peeked message cannot be completed, abandoned, dead-lettered or deferred. + It has no lock token or expiry. + + :ivar received_timestamp_utc: The utc timestamp of when the message is received. + :vartype received_timestamp_utc: datetime.datetime + + """ + + def __init__(self, message): + super(PeekMessage, self).__init__(None, message=message) + self.received_timestamp_utc = utc_now() + + @property + def settled(self): + # type: () -> bool + """Whether the message has been settled. + + This will aways be `True` for a message received using ReceiveAndDelete mode, + otherwise it will be `False` until the message is completed or otherwise settled. + + :rtype: bool + """ + return self.message.settled + + @property + def partition_id(self): + # type: () -> Optional[str] + """ + + :rtype: int + """ + if self.message.annotations: + return self.message.annotations.get(_X_OPT_PARTITION_ID) + return None + + @property + def enqueued_time_utc(self): + # type: () -> Optional[datetime.datetime] + """ + + :rtype: ~datetime.datetime + """ + if self.message.annotations: + timestamp = self.message.annotations.get(_X_OPT_ENQUEUED_TIME) + if timestamp: + in_seconds = timestamp/1000.0 + return utc_from_timestamp(in_seconds) + return None + + @property + def scheduled_enqueue_time_utc(self): + # type: () -> Optional[datetime.datetime] + """ + + :rtype: ~datetime.datetime + """ + if self.message.annotations: + timestamp = self.message.annotations.get(_X_OPT_SCHEDULED_ENQUEUE_TIME) + if timestamp: + in_seconds = timestamp/1000.0 + return utc_from_timestamp(in_seconds) + return None + + @property + def sequence_number(self): + # type: () -> Optional[int] + """ + + :rtype: int + """ + if self.message.annotations: + return self.message.annotations.get(_X_OPT_SEQUENCE_NUMBER) + return None + + +class ReceivedMessage(PeekMessage): + """ + A Service Bus Message received from service side. + + :ivar auto_renew_error: Error when AutoLockRenew is used and it fails to renew the message lock. + :vartype auto_renew_error: ~azure.servicebus.AutoLockRenewTimeout or ~azure.servicebus.AutoLockRenewFailed + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START receive_complex_message] + :end-before: [END receive_complex_message] + :language: python + :dedent: 4 + :caption: Checking the properties on a received message. + """ + def __init__(self, message, mode=ReceiveSettleMode.PeekLock): + super(ReceivedMessage, self).__init__(message=message) + self._settled = (mode == ReceiveSettleMode.ReceiveAndDelete) + self.auto_renew_error = None + + def _is_live(self, action): + # pylint: disable=no-member + if not self._receiver or not self._receiver._running: # pylint: disable=protected-access + raise MessageSettleFailed(action, "Orphan message had no open connection.") + if self.settled: + raise MessageAlreadySettled(action) + try: + if self.expired: + raise MessageLockExpired(inner_exception=self.auto_renew_error) + except TypeError: + pass + try: + if self._receiver.session and self._receiver.session.expired: + raise SessionLockExpired(inner_exception=self._receiver.session.auto_renew_error) + except TypeError: + pass + + @property + def settled(self): + # type: () -> bool + """Whether the message has been settled. + + This will aways be `True` for a message received using ReceiveAndDelete mode, + otherwise it will be `False` until the message is completed or otherwise settled. + + :rtype: bool + """ + return self._settled + + @property + def expired(self): + # type: () -> bool + """ + + :rtype: bool + """ + if self._receiver._session_id: # pylint: disable=protected-access + raise TypeError("Session messages do not expire. Please use the Session expiry instead.") + if self.locked_until_utc and self.locked_until_utc <= utc_now(): + return True + return False + + @property + def locked_until_utc(self): + # type: () -> Optional[datetime.datetime] + """ + + :rtype: datetime.datetime + """ + if self._receiver._session_id or self.settled: # pylint: disable=protected-access + return None + if self._expiry: + return self._expiry + if self.message.annotations and _X_OPT_LOCKED_UNTIL in self.message.annotations: + expiry_in_seconds = self.message.annotations[_X_OPT_LOCKED_UNTIL]/1000 + self._expiry = utc_from_timestamp(expiry_in_seconds) + return self._expiry + + @property + def lock_token(self): + # type: () -> Optional[Union[uuid.UUID, str]] + """ + + :rtype: ~uuid.UUID or str + """ + if self.settled: + return None + + if self.message.delivery_tag: + return uuid.UUID(bytes_le=self.message.delivery_tag) + + delivery_annotations = self.message.delivery_annotations + if delivery_annotations: + return delivery_annotations.get(_X_OPT_LOCK_TOKEN) + return None + + def complete(self): + # type: () -> None + """Complete the message. + + This removes the message from the queue. + + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + self._is_live(MESSAGE_COMPLETE) + try: + self._receiver._settle_message(SETTLEMENT_COMPLETE, [self.lock_token]) # pylint: disable=protected-access + except Exception as e: + raise MessageSettleFailed(MESSAGE_COMPLETE, e) + self._settled = True + + def dead_letter(self, reason=None, description=None): + # type: (Optional[str], Optional[str]) -> None + """Move the message to the Dead Letter queue. + + The Dead Letter queue is a sub-queue that can be + used to store messages that failed to process correctly, or otherwise require further inspection + or processing. The queue can also be configured to send expired messages to the Dead Letter queue. + + :param str reason: The reason for dead-lettering the message. + :param str description: The detailed description for dead-lettering the message. + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + # pylint: disable=protected-access + self._is_live(MESSAGE_DEAD_LETTER) + details = { + MGMT_REQUEST_DEAD_LETTER_REASON: str(reason) if reason else "", + MGMT_REQUEST_DEAD_LETTER_DESCRIPTION: str(description) if description else ""} + try: + self._receiver._settle_message( + SETTLEMENT_DEADLETTER, + [self.lock_token], + dead_letter_details=details + ) + except Exception as e: + raise MessageSettleFailed(MESSAGE_DEAD_LETTER, e) + self._settled = True + + def abandon(self): + # type: () -> None + """Abandon the message. + + This message will be returned to the queue to be reprocessed. + + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + self._is_live(MESSAGE_ABANDON) + try: + self._receiver._settle_message(SETTLEMENT_ABANDON, [self.lock_token]) # pylint: disable=protected-access + except Exception as e: + raise MessageSettleFailed(MESSAGE_ABANDON, e) + self._settled = True + + def defer(self): + # type: () -> None + """Defer the message. + + This message will remain in the queue but must be received + specifically by its sequence number in order to be processed. + + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + self._is_live(MESSAGE_DEFER) + try: + self._receiver._settle_message(SETTLEMENT_DEFER, [self.lock_token]) # pylint: disable=protected-access + except Exception as e: + raise MessageSettleFailed(MESSAGE_DEFER, e) + self._settled = True + + def renew_lock(self): + # type: () -> None + """Renew the message lock. + + This will maintain the lock on the message to ensure + it is not returned to the queue to be reprocessed. In order to complete (or otherwise settle) + the message, the lock must be maintained. Messages received via ReceiveAndDelete mode are not + locked, and therefore cannot be renewed. This operation can also be performed as a threaded + background task by registering the message with an `azure.servicebus.AutoLockRenew` instance. + This operation is only available for non-sessionful messages. + + :rtype: None + :raises: TypeError if the message is sessionful. + :raises: ~azure.servicebus.common.errors.MessageLockExpired is message lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled is message has already been settled. + """ + if self._receiver._session_id: # pylint: disable=protected-access + raise TypeError("Session messages cannot be renewed. Please renew the Session lock instead.") + self._is_live(MESSAGE_RENEW_LOCK) + token = self.lock_token + if not token: + raise ValueError("Unable to renew lock - no lock token found.") + + expiry = self._receiver._renew_locks(token) # pylint: disable=protected-access + self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/common/mgmt_handlers.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/mgmt_handlers.py similarity index 89% rename from sdk/servicebus/azure-servicebus/azure/servicebus/common/mgmt_handlers.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_common/mgmt_handlers.py index dbc2843aedfb..b42d2cd955cb 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/common/mgmt_handlers.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/mgmt_handlers.py @@ -6,8 +6,9 @@ import uamqp -from azure.servicebus.common.message import PeekMessage, DeferredMessage -from azure.servicebus.common.errors import ServiceBusError, MessageLockExpired +from .message import PeekMessage, ReceivedMessage +from ..exceptions import ServiceBusError, MessageLockExpired +from .constants import ReceiveSettleMode def default(status_code, message, description): @@ -57,7 +58,13 @@ def list_sessions_op(status_code, message, description): raise ServiceBusError(error) -def deferred_message_op(status_code, message, description, mode=1, message_type=DeferredMessage): +def deferred_message_op( + status_code, + message, + description, + mode=ReceiveSettleMode.PeekLock, + message_type=ReceivedMessage +): if status_code == 200: parsed = [] for m in message.get_data()[b'messages']: diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/common/mixins.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/mixins.py similarity index 82% rename from sdk/servicebus/azure-servicebus/azure/servicebus/common/mixins.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_common/mixins.py index 25fba37a85e8..8bc0de00b643 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/common/mixins.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/mixins.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # ------------------------------------------------------------------------- - +# pylint: skip-file import datetime import uuid import requests @@ -15,21 +15,20 @@ from urllib.parse import unquote_plus from uamqp import Source -from uamqp.constants import TransportType import azure.common import azure.servicebus -from azure.servicebus.common.constants import ( +from .constants import ( NEXT_AVAILABLE, SESSION_LOCKED_UNTIL, DATETIMEOFFSET_EPOCH, SESSION_FILTER) -from azure.servicebus.common.utils import parse_conn_str, build_uri -from azure.servicebus.common.errors import ( +from .utils import parse_conn_str, build_uri +from ..exceptions import ( ServiceBusConnectionError, ServiceBusResourceNotFound) -from azure.servicebus.control_client import ServiceBusService -from azure.servicebus.control_client.models import AzureServiceBusResourceNotFound, Queue, Subscription, Topic +from .._control_client import ServiceBusService +from .._control_client.models import AzureServiceBusResourceNotFound, Queue, Subscription, Topic class ServiceBusMixin(object): @@ -289,8 +288,6 @@ def __init__(self, address, name, shared_access_key_name=None, 'key_name': shared_access_key_name, 'shared_access_key': shared_access_key_value} - self.auth_config['transport_type'] = kwargs.get('transport_type') or TransportType.Amqp - self.mgmt_client = kwargs.get('mgmt_client') or ServiceBusService( service_namespace=namespace, shared_access_key_name=shared_access_key_name, @@ -315,11 +312,11 @@ def from_connection_string(cls, conn_str, name=None, **kwargs): :param name: The name of the entity, if the 'EntityName' property is not included in the connection string. """ - address, policy, key, entity, transport_type = parse_conn_str(conn_str) + address, policy, key, entity = parse_conn_str(conn_str) entity = name or entity address = build_uri(address, entity) name = address.split('/')[-1] - return cls(address, name, shared_access_key_name=policy, shared_access_key_value=key, transport_type=transport_type, **kwargs) + return cls(address, name, shared_access_key_name=policy, shared_access_key_value=key, **kwargs) def _get_entity(self): raise NotImplementedError("Must be implemented by child class.") @@ -347,77 +344,3 @@ def get_properties(self): self.requires_session = False except requests.exceptions.ConnectionError as e: raise ServiceBusConnectionError("Namespace not found", e) - - -class SessionMixin(object): # pylint: disable=too-few-public-methods - - def _get_source(self): - source = Source(self.endpoint) - session_filter = None if self.session_filter == NEXT_AVAILABLE else self.session_filter - source.set_filter(session_filter, name=SESSION_FILTER, descriptor=None) - return source - - def _on_attach(self, source, target, properties, error): # pylint: disable=unused-argument - if str(source) == self.endpoint: - self.session_start = datetime.datetime.now() - expiry_in_seconds = properties.get(SESSION_LOCKED_UNTIL) - if expiry_in_seconds: - expiry_in_seconds = (expiry_in_seconds - DATETIMEOFFSET_EPOCH)/10000000 - self.locked_until = datetime.datetime.fromtimestamp(expiry_in_seconds) - session_filter = source.get_filter(name=SESSION_FILTER) - self.session_id = session_filter.decode(self.encoding) - - @property - def expired(self): - """Whether the receivers lock on a particular session has expired. - - :rtype: bool - """ - if self.locked_until and self.locked_until <= datetime.datetime.now(): - return True - return False - - -class SenderMixin(object): # pylint: disable=too-few-public-methods - - def _build_schedule_request(self, schedule_time, *messages): - request_body = {'messages': []} - for message in messages: - message.schedule(schedule_time) - if self.session_id and not message.properties.group_id: - message.properties.group_id = self.session_id - message_data = {} - message_data['message-id'] = message.properties.message_id - if message.properties.group_id: - message_data['session-id'] = message.properties.group_id - if message.partition_key: - message_data['partition-key'] = message.partition_key - if message.via_partition_key: - message_data['via-partition-key'] = message.via_partition_key - message_data['message'] = bytearray(message.message.encode_message()) - request_body['messages'].append(message_data) - return request_body - - def queue_message(self, message): - """Queue a message to be sent later. - - This operation should be followed up with send_pending_messages. - - :param message: The message to be sent. - :type message: ~azure.servicebus.common.message.Message - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START queue_and_send_messages] - :end-before: [END queue_and_send_messages] - :language: python - :dedent: 4 - :caption: Send the queued messages - :name: sender_queue - - """ - if not self.running: - self.open() - if self.session_id and not message.properties.group_id: - message.properties.group_id = self.session_id - self._handler.queue_message(message.message) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/common/utils.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/utils.py similarity index 65% rename from sdk/servicebus/azure-servicebus/azure/servicebus/common/utils.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_common/utils.py index 29dc67ea29ac..549e14a91e8c 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/common/utils.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/utils.py @@ -9,20 +9,58 @@ import logging import threading import time +import functools try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse from concurrent.futures import ThreadPoolExecutor -from uamqp.constants import TransportType +from uamqp import authentication -from azure.servicebus.common.errors import AutoLockRenewFailed, AutoLockRenewTimeout -from azure.servicebus import __version__ as sdk_version +from ..exceptions import AutoLockRenewFailed, AutoLockRenewTimeout +from .._version import VERSION as sdk_version +from .constants import ( + JWT_TOKEN_SCOPE, + TOKEN_TYPE_JWT, + TOKEN_TYPE_SASTOKEN +) _log = logging.getLogger(__name__) +class UTC(datetime.tzinfo): + """Time Zone info for handling UTC""" + + def utcoffset(self, dt): + """UTF offset for UTC is 0.""" + return datetime.timedelta(0) + + def tzname(self, dt): + """Timestamp representation.""" + return "Z" + + def dst(self, dt): + """No daylight saving for UTC.""" + return datetime.timedelta(hours=1) + + +try: + from datetime import timezone # pylint: disable=ungrouped-imports + + TZ_UTC = timezone.utc # type: ignore +except ImportError: + TZ_UTC = UTC() # type: ignore + + +def utc_from_timestamp(timestamp): + return datetime.datetime.fromtimestamp(timestamp, tz=TZ_UTC) + + +def utc_now(): + return datetime.datetime.now(tz=TZ_UTC) + + def get_running_loop(): try: import asyncio # pylint: disable=import-error @@ -48,7 +86,6 @@ def parse_conn_str(conn_str): shared_access_key_name = None shared_access_key = None entity_path = None - transport_type = TransportType.Amqp for element in conn_str.split(';'): key, _, value = element.partition('=') if key.lower() == 'endpoint': @@ -59,16 +96,9 @@ def parse_conn_str(conn_str): shared_access_key = value elif key.lower() == 'entitypath': entity_path = value - elif key.lower() == 'transporttype': - if value.lower() == "amqpoverwebsocket": - transport_type = TransportType.AmqpOverWebsocket - elif value.lower() == "amqp": - transport_type = TransportType.Amqp - else: - raise ValueError("Invalid value for TransportType in connection string") if not all([endpoint, shared_access_key_name, shared_access_key]): raise ValueError("Invalid connection string") - return endpoint, shared_access_key_name, shared_access_key, entity_path, transport_type + return endpoint, shared_access_key_name, shared_access_key, entity_path def build_uri(address, entity): @@ -92,37 +122,68 @@ def create_properties(): def renewable_start_time(renewable): try: - return renewable.received_timestamp + return renewable.received_timestamp_utc except AttributeError: pass try: - return renewable.session_start + return renewable._session_start # pylint: disable=protected-access except AttributeError: raise TypeError("Registered object is not renewable.") +def create_authentication(client): + # pylint: disable=protected-access + try: + # ignore mypy's warning because token_type is Optional + token_type = client._credential.token_type # type: ignore + except AttributeError: + token_type = TOKEN_TYPE_JWT + if token_type == TOKEN_TYPE_SASTOKEN: + auth = authentication.JWTTokenAuth( + client._auth_uri, + client._auth_uri, + functools.partial(client._credential.get_token, client._auth_uri), + token_type=token_type, + timeout=client._config.auth_timeout, + http_proxy=client._config.http_proxy, + transport_type=client._config.transport_type, + ) + auth.update_token() + return auth + return authentication.JWTTokenAuth( + client._auth_uri, + client._auth_uri, + functools.partial(client._credential.get_token, JWT_TOKEN_SCOPE), + token_type=token_type, + timeout=client._config.auth_timeout, + http_proxy=client._config.http_proxy, + transport_type=client._config.transport_type, + ) + + class AutoLockRenew(object): """Auto renew locks for messages and sessions using a background thread pool. :param executor: A user-specified thread pool. This cannot be combined with setting `max_workers`. :type executor: ~concurrent.futures.ThreadPoolExecutor - :param max_workers: Specifiy the maximum workers in the thread pool. If not + :param max_workers: Specify the maximum workers in the thread pool. If not specified the number used will be derived from the core count of the environment. This cannot be combined with `executor`. :type max_workers: int .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START auto_lock_renew_message] - :end-before: [END auto_lock_renew_message] + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START auto_lock_renew_message_sync] + :end-before: [END auto_lock_renew_message_sync] :language: python :dedent: 4 :caption: Automatically renew a message lock - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START auto_lock_renew_session] - :end-before: [END auto_lock_renew_session] + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START auto_lock_renew_session_sync] + :end-before: [END auto_lock_renew_session_sync] :language: python :dedent: 4 :caption: Automatically renew a session lock @@ -154,10 +215,10 @@ def _auto_lock_renew(self, renewable, starttime, timeout): _log.debug("Running lock auto-renew thread for %r seconds", timeout) try: while self._renewable(renewable): - if (datetime.datetime.now() - starttime) >= datetime.timedelta(seconds=timeout): + if (utc_now() - starttime) >= datetime.timedelta(seconds=timeout): _log.debug("Reached auto lock renew timeout - letting lock expire.") raise AutoLockRenewTimeout("Auto-renew period ({} seconds) elapsed.".format(timeout)) - if (renewable.locked_until - datetime.datetime.now()) <= datetime.timedelta(seconds=self.renew_period): + if (renewable.locked_until_utc - utc_now()) <= datetime.timedelta(seconds=self.renew_period): _log.debug("%r seconds or less until lock expires - auto renewing.", self.renew_period) renewable.renew_lock() time.sleep(self.sleep_time) @@ -174,11 +235,10 @@ def register(self, renewable, timeout=300): """Register a renewable entity for automatic lock renewal. :param renewable: A locked entity that needs to be renewed. - :type renewable: ~azure.servicebus.common.message.Message or - ~azure.servicebus.receive_handler.SessionReceiver - :param timeout: A time in seconds that the lock should be maintained for. + :type renewable: ~azure.servicebus.ReceivedMessage or + ~azure.servicebus.Session + :param float timeout: A time in seconds that the lock should be maintained for. Default value is 300 (5 minutes). - :type timeout: int """ starttime = renewable_start_time(renewable) self.executor.submit(self._auto_lock_renew, renewable, starttime, timeout) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/__init__.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/__init__.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/__init__.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/__init__.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_conversion.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_conversion.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_conversion.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_conversion.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_error.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_error.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_error.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_error.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_models.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_models.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_models.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_models.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_serialization.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_serialization.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_common_serialization.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_common_serialization.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/__init__.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/__init__.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/__init__.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/__init__.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/httpclient.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/httpclient.py similarity index 99% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/httpclient.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/httpclient.py index 1b63321c6809..ed4a14cb796b 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/httpclient.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/httpclient.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # ------------------------------------------------------------------------- - +# pylint: skip-file import base64 try: from httplib import ( diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/requestsclient.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/requestsclient.py similarity index 99% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/requestsclient.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/requestsclient.py index ee0cd4d0ea2d..c311ec7fdfc1 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_http/requestsclient.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_http/requestsclient.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # ------------------------------------------------------------------------- +# pylint: skip-file class _Response(object): diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_serialization.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_serialization.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/_serialization.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/_serialization.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/constants.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/constants.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/constants.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/constants.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/models.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/models.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/models.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/models.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/control_client/servicebusservice.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/servicebusservice.py similarity index 100% rename from sdk/servicebus/azure-servicebus/azure/servicebus/control_client/servicebusservice.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/_control_client/servicebusservice.py diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py new file mode 100644 index 000000000000..052cb5558e27 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py @@ -0,0 +1,220 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any, TYPE_CHECKING + +import uamqp + +from ._base_handler import _parse_conn_str, ServiceBusSharedKeyCredential +from ._servicebus_sender import ServiceBusSender +from ._servicebus_receiver import ServiceBusReceiver +from ._common._configuration import Configuration +from ._common.utils import create_authentication + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + + +class ServiceBusClient(object): + """The ServiceBusClient class defines a high level interface for + getting ServiceBusSender and ServiceBusReceiver. + + :ivar fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :vartype fully_qualified_namespace: str + + :param str fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :param ~azure.core.credentials.TokenCredential credential: The credential object used for authentication which + implements a particular interface for getting tokens. It accepts + :class:`ServiceBusSharedKeyCredential`, or credential objects + generated by the azure-identity library and objects that implement the `get_token(self, *scopes)` method. + :keyword str entity_name: Optional entity name, this can be the name of Queue or Topic. + It must be specified if the credential is for specific Queue or Topic. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_sb_client_sync] + :end-before: [END create_sb_client_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusClient. + + """ + def __init__( + self, + fully_qualified_namespace, + credential, + **kwargs + ): + # type: (str, TokenCredential, Any) -> None + self.fully_qualified_namespace = fully_qualified_namespace + self._credential = credential + self._config = Configuration(**kwargs) + self._connection = None + self._entity_name = kwargs.get("entity_name") + self._auth_uri = "sb://{}".format(self.fully_qualified_namespace) + if self._entity_name: + self._auth_uri = "{}/{}".format(self._auth_uri, self._entity_name) + # Internal flag for switching whether to apply connection sharing, pending fix in uamqp library + self._connection_sharing = False + + def __enter__(self): + if self._connection_sharing: + self._create_uamqp_connection() + return self + + def __exit__(self, *args): + self.close() + + def _create_uamqp_connection(self): + auth = create_authentication(self) + self._connection = uamqp.Connection( + hostname=self.fully_qualified_namespace, + sasl=auth, + debug=self._config.logging_enable + ) + + def close(self): + # type: () -> None + """ + Close down the ServiceBus client and the underlying connection. + + :return: None + """ + if self._connection_sharing and self._connection: + self._connection.destroy() + + @classmethod + def from_connection_string( + cls, + conn_str, + **kwargs + ): + # type: (str, Any) -> ServiceBusClient + """ + Create a ServiceBusClient from a connection string. + + :param str conn_str: The connection string of a Service Bus. + :keyword str entity_name: Optional entity name, this can be the name of Queue or Topic. + It must be specified if the credential is for specific Queue or Topic. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + :rtype: ~azure.servicebus.ServiceBusClient + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_sb_client_from_conn_str_sync] + :end-before: [END create_sb_client_from_conn_str_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusClient from connection string. + + """ + host, policy, key, entity_in_conn_str = _parse_conn_str(conn_str) + return cls( + fully_qualified_namespace=host, + entity_name=entity_in_conn_str or kwargs.pop("entity_name", None), + credential=ServiceBusSharedKeyCredential(policy, key), + **kwargs + ) + + def get_queue_sender(self, queue_name, **kwargs): + # type: (str, Any) -> ServiceBusSender + """Get ServiceBusSender for the specific queue. + + :param str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :rtype: ~azure.servicebus.ServiceBusSender + :raises: :class:`ServiceBusConnectionError` + :class:`ServiceBusAuthorizationError` + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_servicebus_sender_from_sb_client_sync] + :end-before: [END create_servicebus_sender_from_sb_client_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusSender from ServiceBusClient. + + """ + # pylint: disable=protected-access + sender = ServiceBusSender( + fully_qualified_namespace=self.fully_qualified_namespace, + queue_name=queue_name, + credential=self._credential, + logging_enable=self._config.logging_enable, + connection=self._connection, + **kwargs + ) + + return sender + + def get_queue_receiver(self, queue_name, **kwargs): + # type: (str, Any) -> ServiceBusReceiver + """Get ServiceBusReceiver for the specific queue. + + :param str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword mode: The mode with which messages will be retrieved from the entity. The two options + are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given + lock period before they will be removed from the queue. Messages received with ReceiveAndDelete + will be immediately removed from the queue, and cannot be subsequently rejected or re-received if + the client fails to process the message. The default mode is PeekLock. + :paramtype mode: ~azure.servicebus.ReceiveSettleMode + :keyword session_id: A specific session from which to receive. This must be specified for a + sessionful entity, otherwise it must be None. In order to receive messages from the next available + session, set this to NEXT_AVAILABLE. + :paramtype session_id: str or ~azure.servicebus.NEXT_AVAILABLE + :keyword int prefetch: The maximum number of messages to cache with each request to the service. + The default value is 0, meaning messages will be received from the service and processed + one at a time. Increasing this value will improve message throughput performance but increase + the change that messages will expire while they are cached if they're not processed fast enough. + :keyword float idle_timeout: The timeout in seconds between received messages after which the receiver will + automatically shutdown. The default value is 0, meaning no timeout. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :param int idle_timeout: The timeout in seconds between received messages after which the receiver will + automatically shutdown. The default value is 0, meaning no timeout. + :rtype: ~azure.servicebus.ServiceBusReceiver + :raises: :class:`ServiceBusConnectionError` + :class:`ServiceBusAuthorizationError` + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_servicebus_receiver_from_sb_client_sync] + :end-before: [END create_servicebus_receiver_from_sb_client_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusReceiver from ServiceBusClient. + + + """ + # pylint: disable=protected-access + receiver = ServiceBusReceiver( + fully_qualified_namespace=self.fully_qualified_namespace, + queue_name=queue_name, + credential=self._credential, + logging_enable=self._config.logging_enable, + connection=self._connection, + **kwargs + ) + + return receiver diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py new file mode 100644 index 000000000000..3c5af0df5a68 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py @@ -0,0 +1,640 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import time +import logging +import functools +import uuid +from typing import Any, List, TYPE_CHECKING, Optional, Union +import six + +from uamqp import ReceiveClient, Source, types +from uamqp.constants import SenderSettleMode + +from ._base_handler import BaseHandler +from ._common.utils import create_authentication, utc_from_timestamp, utc_now +from ._common.message import PeekMessage, ReceivedMessage +from ._common.constants import ( + REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, + REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, + REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, + REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, + REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, + REQUEST_RESPONSE_RENEWLOCK_OPERATION, + REQUEST_RESPONSE_PEEK_OPERATION, + ReceiveSettleMode, + NEXT_AVAILABLE, + SESSION_LOCKED_UNTIL, + DATETIMEOFFSET_EPOCH, + SESSION_FILTER, + MGMT_RESPONSE_SESSION_STATE, + MGMT_RESPONSE_RECEIVER_EXPIRATION, + MGMT_REQUEST_SESSION_ID, + MGMT_REQUEST_SESSION_STATE, + MGMT_REQUEST_DISPOSITION_STATUS, + MGMT_REQUEST_LOCK_TOKENS, + MGMT_REQUEST_SEQUENCE_NUMBERS, + MGMT_REQUEST_RECEIVER_SETTLE_MODE, + MGMT_REQUEST_FROM_SEQUENCE_NUMBER, + MGMT_REQUEST_MESSAGE_COUNT +) +from .exceptions import ( + _ServiceBusErrorPolicy, + SessionLockExpired +) +from ._common import mgmt_handlers + +if TYPE_CHECKING: + import datetime + from azure.core.credentials import TokenCredential + +_LOGGER = logging.getLogger(__name__) + + +class ServiceBusSession(object): + """ + The ServiceBusSession is used for manage session states and lock renewal. + + **Please use the instance variable `session` on the ServiceBusReceiver to get the corresponding ServiceBusSession + object linked with the receiver instead of instantiating a ServiceBusSession object directly.** + + :ivar auto_renew_error: Error when AutoLockRenew is used and it fails to renew the session lock. + :vartype auto_renew_error: ~azure.servicebus.AutoLockRenewTimeout or ~azure.servicebus.AutoLockRenewFailed + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START get_session_sync] + :end-before: [END get_session_sync] + :language: python + :dedent: 4 + :caption: Get session from a receiver + """ + def __init__(self, session_id, receiver, encoding="UTF-8"): + self._session_id = session_id + self._receiver = receiver + self._encoding = encoding + self._session_start = None + self._locked_until_utc = None + self.auto_renew_error = None + + def _can_run(self): + if self.expired: + raise SessionLockExpired(inner_exception=self.auto_renew_error) + + def get_session_state(self): + # type: () -> str + """Get the session state. + + Returns None if no state has been set. + + :rtype: str + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START get_session_state_sync] + :end-before: [END get_session_state_sync] + :language: python + :dedent: 4 + :caption: Get the session state + """ + self._can_run() + response = self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, + {MGMT_REQUEST_SESSION_ID: self.session_id}, + mgmt_handlers.default + ) + session_state = response.get(MGMT_RESPONSE_SESSION_STATE) + if isinstance(session_state, six.binary_type): + session_state = session_state.decode('UTF-8') + return session_state + + def set_session_state(self, state): + # type: (Union[str, bytes, bytearray]) -> None + """Set the session state. + + :param state: The state value. + :type state: str, bytes or bytearray + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START set_session_state_sync] + :end-before: [END set_session_state_sync] + :language: python + :dedent: 4 + :caption: Set the session state + """ + self._can_run() + state = state.encode(self._encoding) if isinstance(state, six.text_type) else state + return self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, + {MGMT_REQUEST_SESSION_ID: self.session_id, MGMT_REQUEST_SESSION_STATE: bytearray(state)}, + mgmt_handlers.default + ) + + def renew_lock(self): + # type: () -> None + """Renew the session lock. + + This operation must be performed periodically in order to retain a lock on the + session to continue message processing. + Once the lock is lost the connection will be closed. This operation can + also be performed as a threaded background task by registering the session + with an `azure.servicebus.AutoLockRenew` instance. + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START session_renew_lock_sync] + :end-before: [END session_renew_lock_sync] + :language: python + :dedent: 4 + :caption: Renew the session lock before it expires + """ + self._can_run() + expiry = self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, + {MGMT_REQUEST_SESSION_ID: self.session_id}, + mgmt_handlers.default + ) + self._locked_until_utc = utc_from_timestamp(expiry[MGMT_RESPONSE_RECEIVER_EXPIRATION]/1000.0) + + @property + def session_id(self): + # type: () -> str + """ + Session id of the current session. + + :rtype: str + """ + return self._session_id + + @property + def expired(self): + # type: () -> bool + """Whether the receivers lock on a particular session has expired. + + :rtype: bool + """ + return bool(self._locked_until_utc and self._locked_until_utc <= utc_now()) + + @property + def locked_until_utc(self): + # type: () -> datetime.datetime + """The time at which this session's lock will expire. + + :rtype: datetime.datetime + """ + return self._locked_until_utc + + +class ReceiverMixin(object): # pylint: disable=too-many-instance-attributes + def _create_attribute(self, **kwargs): + if kwargs.get("subscription_name"): + self._subscription_name = kwargs.get("subscription_name") + self._is_subscription = True + self.entity_path = self._entity_name + "/Subscriptions/" + self._subscription_name + else: + self.entity_path = self._entity_name + + self._session_id = kwargs.get("session_id") + self._auth_uri = "sb://{}/{}".format(self.fully_qualified_namespace, self.entity_path) + self._entity_uri = "amqps://{}/{}".format(self.fully_qualified_namespace, self.entity_path) + self._mode = kwargs.get("mode", ReceiveSettleMode.PeekLock) + self._error_policy = _ServiceBusErrorPolicy( + max_retries=self._config.retry_total, + is_session=bool(self._session_id) + ) + self._name = "SBReceiver-{}".format(uuid.uuid4()) + self._last_received_sequenced_number = None + + def _build_message(self, received, message_type=ReceivedMessage): + message = message_type(message=received, mode=self._mode) + message._receiver = self # pylint: disable=protected-access + self._last_received_sequenced_number = message.sequence_number + return message + + def _get_source_for_session_entity(self): + source = Source(self._entity_uri) + session_filter = None if self._session_id == NEXT_AVAILABLE else self._session_id + source.set_filter(session_filter, name=SESSION_FILTER, descriptor=None) + return source + + def _on_attach_for_session_entity(self, source, target, properties, error): # pylint: disable=unused-argument + # pylint: disable=protected-access + if str(source) == self._entity_uri: + # This has to live on the session object so that autorenew has access to it. + self._session._session_start = utc_now() + expiry_in_seconds = properties.get(SESSION_LOCKED_UNTIL) + if expiry_in_seconds: + expiry_in_seconds = (expiry_in_seconds - DATETIMEOFFSET_EPOCH)/10000000 + self._session._locked_until_utc = utc_from_timestamp(expiry_in_seconds) + session_filter = source.get_filter(name=SESSION_FILTER) + self._session_id = session_filter.decode(self._config.encoding) + self._session._session_id = self._session_id + + def _can_run(self): + if self._session and self._session.expired: + raise SessionLockExpired(inner_exception=self._session.auto_renew_error) + + +class ServiceBusReceiver(BaseHandler, ReceiverMixin): # pylint: disable=too-many-instance-attributes + """The ServiceBusReceiver class defines a high level interface for + receiving messages from the Azure Service Bus Queue or Topic Subscription. + + :ivar fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :vartype fully_qualified_namespace: str + :ivar entity_path: The path of the entity that the client connects to. + :vartype entity_path: str + + :param str fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :param ~azure.core.credentials.TokenCredential credential: The credential object used for authentication which + implements a particular interface for getting tokens. It accepts + :class:`ServiceBusSharedKeyCredential`, or credential objects + generated by the azure-identity library and objects that implement the `get_token(self, *scopes)` method. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword str topic_name: The path of specific Service Bus Topic which contains the Subscription + the client connects to. + :keyword str subscription_name: The path of specific Service Bus Subscription under the + specified Topic the client connects to. + :keyword int prefetch: The maximum number of messages to cache with each request to the service. + The default value is 0, meaning messages will be received from the service and processed + one at a time. Increasing this value will improve message throughput performance but increase + the change that messages will expire while they are cached if they're not processed fast enough. + :keyword float idle_timeout: The timeout in seconds between received messages after which the receiver will + automatically shutdown. The default value is 0, meaning no timeout. + :keyword mode: The mode with which messages will be retrieved from the entity. The two options + are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given + lock period before they will be removed from the queue. Messages received with ReceiveAndDelete + will be immediately removed from the queue, and cannot be subsequently rejected or re-received if + the client fails to process the message. The default mode is PeekLock. + :paramtype mode: ~azure.servicebus.ReceiveSettleMode + :keyword session_id: A specific session from which to receive. This must be specified for a + sessionful entity, otherwise it must be None. In order to receive messages from the next available + session, set this to NEXT_AVAILABLE. + :paramtype session_id: str or ~azure.servicebus.NEXT_AVAILABLE + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_servicebus_receiver_sync] + :end-before: [END create_servicebus_receiver_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusReceiver. + + """ + def __init__( + self, + fully_qualified_namespace, + credential, + **kwargs + ): + # type: (str, TokenCredential, Any) -> None + if kwargs.get("entity_name"): + super(ServiceBusReceiver, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + **kwargs + ) + else: + queue_name = kwargs.get("queue_name") + topic_name = kwargs.get("topic_name") + subscription_name = kwargs.get("subscription_name") + if queue_name and topic_name: + raise ValueError("Queue/Topic name can not be specified simultaneously.") + if not (queue_name or topic_name): + raise ValueError("Queue/Topic name is missing. Please specify queue_name/topic_name.") + if topic_name and not subscription_name: + raise ValueError("Subscription name is missing for the topic. Please specify subscription_name.") + + entity_name = queue_name or topic_name + + super(ServiceBusReceiver, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + entity_name=entity_name, + **kwargs + ) + self._message_iter = None + self._create_attribute(**kwargs) + self._connection = kwargs.get("connection") + self._session = ServiceBusSession(self._session_id, self, self._config.encoding) if self._session_id else None + self._prefetch = kwargs.get("prefetch") + + def __iter__(self): + return self + + def __next__(self): + self._can_run() + while True: + try: + return self._do_retryable_operation(self._iter_next) + except StopIteration: + self.close() + raise + + next = __next__ # for python2.7 + + def _iter_next(self): + self._open() + uamqp_message = next(self._message_iter) + message = self._build_message(uamqp_message) + return message + + def _create_handler(self, auth): + self._handler = ReceiveClient( + self._get_source_for_session_entity() if self._session_id else self._entity_uri, + auth=auth, + debug=self._config.logging_enable, + properties=self._properties, + error_policy=self._error_policy, + client_name=self._name, + on_attach=self._on_attach_for_session_entity if self._session_id else None, + auto_complete=False, + encoding=self._config.encoding, + receive_settle_mode=self._mode.value, + send_settle_mode=SenderSettleMode.Settled if self._mode == ReceiveSettleMode.ReceiveAndDelete else None, + timeout=self._config.idle_timeout * 1000 if self._config.idle_timeout else 0, + prefetch=self._config.prefetch + ) + + def _open(self): + if self._running: + return + if self._handler: + self._handler.close() + + auth = None if self._connection else create_authentication(self) + self._create_handler(auth) + self._handler.open(connection=self._connection) + self._message_iter = self._handler.receive_messages_iter() + while not self._handler.client_ready(): + time.sleep(0.05) + self._running = True + + def _receive(self, max_batch_size=None, timeout=None): + self._open() + max_batch_size = max_batch_size or self._handler._prefetch # pylint: disable=protected-access + + timeout_ms = 1000 * (timeout or self._config.idle_timeout) if (timeout or self._config.idle_timeout) else 0 + batch = self._handler.receive_message_batch( + max_batch_size=max_batch_size, + timeout=timeout_ms + ) + + return [self._build_message(message) for message in batch] + + def _settle_message(self, settlement, lock_tokens, dead_letter_details=None): + message = { + MGMT_REQUEST_DISPOSITION_STATUS: settlement, + MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens) + } + + if self._session_id: + message[MGMT_REQUEST_SESSION_ID] = self._session_id + if dead_letter_details: + message.update(dead_letter_details) + + return self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, + message, + mgmt_handlers.default + ) + + def _renew_locks(self, *lock_tokens): + message = {MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} + return self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_RENEWLOCK_OPERATION, + message, + mgmt_handlers.lock_renew_op + ) + + @property + def session(self): + # type: ()->ServiceBusSession + """ + Get the ServiceBusSession object linked with the receiver. Session is only available to session-enabled + entities. + + :rtype: ~azure.servicebus.ServiceBusSession + :raises: :class:`TypeError` + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START get_session_sync] + :end-before: [END get_session_sync] + :language: python + :dedent: 4 + :caption: Get session from a receiver + """ + if not self._session_id: + raise TypeError("Session is only available to session-enabled entities.") + return self._session # type: ignore + + @classmethod + def from_connection_string( + cls, + conn_str, + **kwargs + ): + # type: (str, Any) -> ServiceBusReceiver + """Create a ServiceBusReceiver from a connection string. + + :param conn_str: The connection string of a Service Bus. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword str topic_name: The path of specific Service Bus Topic which contains the Subscription + the client connects to. + :keyword str subscription_name: The path of specific Service Bus Subscription under the + specified Topic the client connects to. + :keyword mode: The mode with which messages will be retrieved from the entity. The two options + are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given + lock period before they will be removed from the queue. Messages received with ReceiveAndDelete + will be immediately removed from the queue, and cannot be subsequently rejected or re-received if + the client fails to process the message. The default mode is PeekLock. + :paramtype mode: ~azure.servicebus.ReceiveSettleMode + :keyword session_id: A specific session from which to receive. This must be specified for a + sessionful entity, otherwise it must be None. In order to receive messages from the next available + session, set this to NEXT_AVAILABLE. + :paramtype session_id: str or ~azure.servicebus.NEXT_AVAILABLE + :keyword int prefetch: The maximum number of messages to cache with each request to the service. + The default value is 0, meaning messages will be received from the service and processed + one at a time. Increasing this value will improve message throughput performance but increase + the change that messages will expire while they are cached if they're not processed fast enough. + :keyword float idle_timeout: The timeout in seconds between received messages after which the receiver will + automatically shutdown. The default value is 0, meaning no timeout. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + :rtype: ~azure.servicebus.ServiceBusReceiverClient + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_servicebus_receiver_from_conn_str_sync] + :end-before: [END create_servicebus_receiver_from_conn_str_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusReceiver from connection string. + + """ + constructor_args = cls._from_connection_string( + conn_str, + **kwargs + ) + if kwargs.get("queue_name") and kwargs.get("subscription_name"): + raise ValueError("Queue entity does not have subscription.") + + if kwargs.get("topic_name") and not kwargs.get("subscription_name"): + raise ValueError("Subscription name is missing for the topic. Please specify subscription_name.") + return cls(**constructor_args) + + def receive(self, max_batch_size=None, max_wait_time=None): + # type: (int, float) -> List[ReceivedMessage] + """Receive a batch of messages at once. + + This approach it optimal if you wish to process multiple messages simultaneously. Note that the + number of messages retrieved in a single batch will be dependent on + whether `prefetch` was set for the receiver. This call will prioritize returning + quickly over meeting a specified batch size, and so will return as soon as at least + one message is received and there is a gap in incoming messages regardless + of the specified batch size. + + :param int max_batch_size: Maximum number of messages in the batch. Actual number + returned will depend on prefetch size and incoming stream rate. + :param float max_wait_time: Maximum time to wait in seconds for the first message to arrive. + If no messages arrive, and no timeout is specified, this call will not return + until the connection is closed. If specified, an no messages arrive within the + timeout period, an empty list will be returned. + :rtype: list[~azure.servicebus.Message] + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START receive_sync] + :end-before: [END receive_sync] + :language: python + :dedent: 4 + :caption: Receive messages from ServiceBus. + + """ + self._can_run() + return self._do_retryable_operation( + self._receive, + max_batch_size=max_batch_size, + timeout=max_wait_time, + require_timeout=True + ) + + def receive_deferred_messages(self, sequence_numbers): + # type: (List[int]) -> List[ReceivedMessage] + """Receive messages that have previously been deferred. + + When receiving deferred messages from a partitioned entity, all of the supplied + sequence numbers must be messages from the same partition. + + :param list[int] sequence_numbers: A list of the sequence numbers of messages that have been + deferred. + :rtype: list[~azure.servicebus.ReceivedMessage] + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START receive_defer_sync] + :end-before: [END receive_defer_sync] + :language: python + :dedent: 4 + :caption: Receive deferred messages from ServiceBus. + + """ + self._can_run() + if not sequence_numbers: + raise ValueError("At least one sequence number must be specified.") + self._open() + try: + receive_mode = self._mode.value.value + except AttributeError: + receive_mode = int(self._mode) + message = { + MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), + MGMT_REQUEST_RECEIVER_SETTLE_MODE: types.AMQPuInt(receive_mode) + } + + if self._session_id: + message[MGMT_REQUEST_SESSION_ID] = self._session_id + + handler = functools.partial(mgmt_handlers.deferred_message_op, mode=self._mode) + messages = self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, + message, + handler + ) + for m in messages: + m._receiver = self # pylint: disable=protected-access + return messages + + def peek(self, message_count=1, sequence_number=None): + # type: (int, Optional[int]) -> List[PeekMessage] + """Browse messages currently pending in the queue. + + Peeked messages are not removed from queue, nor are they locked. They cannot be completed, + deferred or dead-lettered. + + :param int message_count: The maximum number of messages to try and peek. The default + value is 1. + :param int sequence_number: A message sequence number from which to start browsing messages. + :rtype: list[~azure.servicebus.PeekMessage] + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START peek_messages_sync] + :end-before: [END peek_messages_sync] + :language: python + :dedent: 4 + :caption: Look at pending messages in the queue. + + """ + self._can_run() + if not sequence_number: + sequence_number = self._last_received_sequenced_number or 1 + if int(message_count) < 1: + raise ValueError("count must be 1 or greater.") + if int(sequence_number) < 1: + raise ValueError("start_from must be 1 or greater.") + + self._open() + message = { + MGMT_REQUEST_FROM_SEQUENCE_NUMBER: types.AMQPLong(sequence_number), + MGMT_REQUEST_MESSAGE_COUNT: message_count + } + + if self._session_id: + message[MGMT_REQUEST_SESSION_ID] = self._session_id + + return self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_PEEK_OPERATION, + message, + mgmt_handlers.peek_op + ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py new file mode 100644 index 000000000000..5eaf4a502bcc --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -0,0 +1,347 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import logging +import time +import uuid +from typing import Any, TYPE_CHECKING, Union, List + +import uamqp +from uamqp import SendClient, types + +from ._base_handler import BaseHandler +from ._common import mgmt_handlers +from ._common.message import Message, BatchMessage +from .exceptions import ( + MessageSendFailed, + OperationTimeoutError, + _ServiceBusErrorPolicy +) +from ._common.utils import create_authentication +from ._common.constants import ( + REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, + REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, + MGMT_REQUEST_SEQUENCE_NUMBERS, + MGMT_REQUEST_SESSION_ID, + MGMT_REQUEST_MESSAGE, + MGMT_REQUEST_MESSAGES, + MGMT_REQUEST_MESSAGE_ID, + MGMT_REQUEST_PARTITION_KEY, + MGMT_REQUEST_VIA_PARTITION_KEY +) + +if TYPE_CHECKING: + import datetime + from azure.core.credentials import TokenCredential + +_LOGGER = logging.getLogger(__name__) + + +class SenderMixin(object): + def _create_attribute(self): + self._auth_uri = "sb://{}/{}".format(self.fully_qualified_namespace, self._entity_name) + self._entity_uri = "amqps://{}/{}".format(self.fully_qualified_namespace, self._entity_name) + self._error_policy = _ServiceBusErrorPolicy(max_retries=self._config.retry_total) + self._name = "SBSender-{}".format(uuid.uuid4()) + self._max_message_size_on_link = 0 + self.entity_name = self._entity_name + + def _set_msg_timeout(self, timeout=None, last_exception=None): + if not timeout: + return + timeout_time = time.time() + timeout + remaining_time = timeout_time - time.time() + if remaining_time <= 0.0: + if last_exception: + error = last_exception + else: + error = OperationTimeoutError("Send operation timed out") + _LOGGER.info("%r send operation timed out. (%r)", self._name, error) + raise error + self._handler._msg_timeout = remaining_time * 1000 # type: ignore # pylint: disable=protected-access + + @classmethod + def _build_schedule_request(cls, schedule_time_utc, *messages): + request_body = {MGMT_REQUEST_MESSAGES: []} + for message in messages: + message.schedule(schedule_time_utc) + message_data = {} + message_data[MGMT_REQUEST_MESSAGE_ID] = message.properties.message_id + if message.properties.group_id: + message_data[MGMT_REQUEST_SESSION_ID] = message.properties.group_id + if message.partition_key: + message_data[MGMT_REQUEST_PARTITION_KEY] = message.partition_key + if message.via_partition_key: + message_data[MGMT_REQUEST_VIA_PARTITION_KEY] = message.via_partition_key + message_data[MGMT_REQUEST_MESSAGE] = bytearray(message.message.encode_message()) + request_body[MGMT_REQUEST_MESSAGES].append(message_data) + return request_body + + +class ServiceBusSender(BaseHandler, SenderMixin): + """The ServiceBusSender class defines a high level interface for + sending messages to the Azure Service Bus Queue or Topic. + + :ivar fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :vartype fully_qualified_namespace: str + :ivar entity_name: The name of the entity that the client connects to. + :vartype entity_name: str + + :param str fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :param ~azure.core.credentials.TokenCredential credential: The credential object used for authentication which + implements a particular interface for getting tokens. It accepts + :class:`ServiceBusSharedKeyCredential`, or credential objects + generated by the azure-identity library and objects that implement the `get_token(self, *scopes)` method. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword str topic_name: The path of specific Service Bus Topic the client connects to. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_servicebus_sender_sync] + :end-before: [END create_servicebus_sender_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusSender. + + """ + def __init__( + self, + fully_qualified_namespace, + credential, + **kwargs + ): + # type: (str, TokenCredential, Any) -> None + if kwargs.get("entity_name"): + super(ServiceBusSender, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + **kwargs + ) + else: + queue_name = kwargs.get("queue_name") + topic_name = kwargs.get("topic_name") + if queue_name and topic_name: + raise ValueError("Queue/Topic name can not be specified simultaneously.") + if not (queue_name or topic_name): + raise ValueError("Queue/Topic name is missing. Please specify queue_name/topic_name.") + entity_name = queue_name or topic_name + super(ServiceBusSender, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + entity_name=entity_name, + **kwargs + ) + + self._max_message_size_on_link = 0 + self._create_attribute() + self._connection = kwargs.get("connection") + + def _create_handler(self, auth): + self._handler = SendClient( + self._entity_uri, + auth=auth, + debug=self._config.logging_enable, + properties=self._properties, + error_policy=self._error_policy, + client_name=self._name, + encoding=self._config.encoding + ) + + def _open(self): + # pylint: disable=protected-access + if self._running: + return + if self._handler: + self._handler.close() + + auth = None if self._connection else create_authentication(self) + self._create_handler(auth) + self._handler.open(connection=self._connection) + while not self._handler.client_ready(): + time.sleep(0.05) + self._running = True + self._max_message_size_on_link = self._handler.message_handler._link.peer_max_message_size \ + or uamqp.constants.MAX_MESSAGE_LENGTH_BYTES + + def _send(self, message, timeout=None, last_exception=None): + self._open() + self._set_msg_timeout(timeout, last_exception) + try: + self._handler.send_message(message.message) + except Exception as e: + raise MessageSendFailed(e) + + def _schedule(self, message, schedule_time_utc): + # type: (Union[Message, BatchMessage], datetime.datetime) -> List[int] + """Send Message or BatchMessage to be enqueued at a specific time. + Returns a list of the sequence numbers of the enqueued messages. + :param message: The messages to schedule. + :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage + :param schedule_time_utc: The utc date and time to enqueue the messages. + :type schedule_time_utc: ~datetime.datetime + :rtype: List[int] + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START scheduling_messages] + :end-before: [END scheduling_messages] + :language: python + :dedent: 4 + :caption: Schedule a message to be sent in future + """ + # pylint: disable=protected-access + self._open() + if isinstance(message, BatchMessage): + request_body = self._build_schedule_request(schedule_time_utc, *message._messages) + else: + request_body = self._build_schedule_request(schedule_time_utc, message) + return self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, + request_body, + mgmt_handlers.schedule_op + ) + + def _cancel_scheduled_messages(self, sequence_numbers): + # type: (Union[int, List[int]]) -> None + """ + Cancel one or more messages that have previously been scheduled and are still pending. + + :param sequence_numbers: The sequence numbers of the scheduled messages. + :type sequence_numbers: int or list[int] + :rtype: None + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START cancel_scheduled_messages] + :end-before: [END cancel_scheduled_messages] + :language: python + :dedent: 4 + :caption: Cancelling messages scheduled to be sent in future + """ + self._open() + if isinstance(sequence_numbers, int): + numbers = [types.AMQPLong(sequence_numbers)] + else: + numbers = [types.AMQPLong(s) for s in sequence_numbers] + request_body = {MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray(numbers)} + return self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, + request_body, + mgmt_handlers.default + ) + + @classmethod + def from_connection_string( + cls, + conn_str, + **kwargs + ): + # type: (str, Any) -> ServiceBusSender + """Create a ServiceBusSender from a connection string. + + :param conn_str: The connection string of a Service Bus. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. Only one of queue_name or topic_name can be provided. + :keyword str topic_name: The path of specific Service Bus Topic the client connects to. Only one of queue_name or topic_name can be provided. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + :rtype: ~azure.servicebus.ServiceBusSenderClient + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_servicebus_sender_from_conn_str_sync] + :end-before: [END create_servicebus_sender_from_conn_str_sync] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusSender from connection string. + + """ + constructor_args = cls._from_connection_string( + conn_str, + **kwargs + ) + return cls(**constructor_args) + + def send(self, message, timeout=None): + # type: (Union[Message, BatchMessage], float) -> None + """Sends message and blocks until acknowledgement is received or operation times out. + + :param message: The ServiceBus message to be sent. + :type message: ~azure.servicebus.Message + :param float timeout: The maximum wait time to send the event data. + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageSendFailed if the message fails to + send or ~azure.servicebus.common.errors.OperationTimeoutError if sending times out. + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START send_sync] + :end-before: [END send_sync] + :language: python + :dedent: 4 + :caption: Send message. + + """ + self._do_retryable_operation( + self._send, + message=message, + timeout=timeout, + require_timeout=True, + require_last_exception=True + ) + + def create_batch(self, max_size_in_bytes=None): + # type: (int) -> BatchMessage + """Create a BatchMessage object with the max size of all content being constrained by max_size_in_bytes. + The max_size should be no greater than the max allowed message size defined by the service. + + :param int max_size_in_bytes: The maximum size of bytes data that a BatchMessage object can hold. By + default, the value is determined by your Service Bus tier. + :rtype: ~azure.servicebus.BatchMessage + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_code_servicebus.py + :start-after: [START create_batch_sync] + :end-before: [END create_batch_sync] + :language: python + :dedent: 4 + :caption: Create BatchMessage object within limited size + + """ + if not self._max_message_size_on_link: + self._open_with_retry() + + if max_size_in_bytes and max_size_in_bytes > self._max_message_size_on_link: + raise ValueError( + "Max message size: {} is too large, acceptable max batch size is: {} bytes.".format( + max_size_in_bytes, self._max_message_size_on_link + ) + ) + + return BatchMessage( + max_size_in_bytes=(max_size_in_bytes or self._max_message_size_on_link) + ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_version.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_version.py index 8bbaebb97523..23331fc02017 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_version.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_version.py @@ -3,4 +3,4 @@ # Licensed under the MIT License. # ------------------------------------ -VERSION = '0.50.2' \ No newline at end of file +VERSION = '7.0.0b1' diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py index c2125d35a417..1413312f62d3 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/__init__.py @@ -3,50 +3,19 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # ------------------------------------------------------------------------- - -from azure.servicebus.common.errors import ( - ServiceBusError, - ServiceBusResourceNotFound, - ServiceBusConnectionError, - ServiceBusAuthorizationError, - InvalidHandlerState, - NoActiveSession, - MessageAlreadySettled, - MessageSettleFailed, - MessageSendFailed, - MessageLockExpired, - SessionLockExpired, - AutoLockRenewFailed, - AutoLockRenewTimeout) -from azure.servicebus.common.constants import ReceiveSettleMode, NEXT_AVAILABLE -from azure.servicebus.common.message import BatchMessage, PeekMessage -from .async_message import Message, DeferredMessage -from .async_client import ServiceBusClient, QueueClient, TopicClient, SubscriptionClient -from .async_utils import AutoLockRenew - +from ._async_message import ReceivedMessage +from ._base_handler_async import ServiceBusSharedKeyCredential +from ._servicebus_sender_async import ServiceBusSender +from ._servicebus_receiver_async import ServiceBusReceiver, ServiceBusSession +from ._servicebus_client_async import ServiceBusClient +from ._async_utils import AutoLockRenew __all__ = [ - 'Message', - 'AutoLockRenew', - 'BatchMessage', - 'PeekMessage', - 'DeferredMessage', + 'ReceivedMessage', 'ServiceBusClient', - 'QueueClient', - 'TopicClient', - 'SubscriptionClient', - 'ReceiveSettleMode', - 'NEXT_AVAILABLE', - 'ServiceBusError', - 'ServiceBusResourceNotFound', - 'ServiceBusConnectionError', - 'ServiceBusAuthorizationError', - 'InvalidHandlerState', - 'NoActiveSession', - 'MessageAlreadySettled', - 'MessageSettleFailed', - 'MessageSendFailed', - 'MessageLockExpired', - 'SessionLockExpired', - 'AutoLockRenewFailed', - 'AutoLockRenewTimeout'] + 'ServiceBusSender', + 'ServiceBusReceiver', + 'ServiceBusSharedKeyCredential', + 'AutoLockRenew', + 'ServiceBusSession' +] diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py new file mode 100644 index 000000000000..fbbeb290421c --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py @@ -0,0 +1,146 @@ +# ------------------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +from typing import Optional + +from .._common import message as sync_message +from .._common.constants import ( + SETTLEMENT_ABANDON, + SETTLEMENT_COMPLETE, + SETTLEMENT_DEFER, + SETTLEMENT_DEADLETTER, + ReceiveSettleMode, + MGMT_RESPONSE_MESSAGE_EXPIRATION, + MGMT_REQUEST_DEAD_LETTER_REASON, + MGMT_REQUEST_DEAD_LETTER_DESCRIPTION, + MESSAGE_COMPLETE, + MESSAGE_DEAD_LETTER, + MESSAGE_ABANDON, + MESSAGE_DEFER, + MESSAGE_RENEW_LOCK +) +from .._common.utils import get_running_loop, utc_from_timestamp +from ..exceptions import MessageSettleFailed + + +class ReceivedMessage(sync_message.ReceivedMessage): + """A Service Bus Message received from service side. + + """ + + def __init__(self, message, mode=ReceiveSettleMode.PeekLock, loop=None): + self._loop = loop or get_running_loop() + super(ReceivedMessage, self).__init__(message=message, mode=mode) + + async def complete(self): + # type: () -> None + """Complete the message. + + This removes the message from the queue. + + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + # pylint: disable=protected-access + self._is_live(MESSAGE_COMPLETE) + try: + await self._receiver._settle_message(SETTLEMENT_COMPLETE, [self.lock_token]) + except Exception as e: + raise MessageSettleFailed(MESSAGE_COMPLETE, e) + self._settled = True + + async def dead_letter(self, reason=None, description=None): + # type: (Optional[str], Optional[str]) -> None + """Move the message to the Dead Letter queue. + + The Dead Letter queue is a sub-queue that can be + used to store messages that failed to process correctly, or otherwise require further inspection + or processing. The queue can also be configured to send expired messages to the Dead Letter queue. + + :param str reason: The reason for dead-lettering the message. + :param str description: The detailed description for dead-lettering the message. + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + # pylint: disable=protected-access + self._is_live(MESSAGE_DEAD_LETTER) + details = { + MGMT_REQUEST_DEAD_LETTER_REASON: str(reason) if reason else "", + MGMT_REQUEST_DEAD_LETTER_DESCRIPTION: str(description) if description else ""} + try: + await self._receiver._settle_message( + SETTLEMENT_DEADLETTER, + [self.lock_token], + dead_letter_details=details + ) + except Exception as e: + raise MessageSettleFailed(MESSAGE_DEAD_LETTER, e) + self._settled = True + + async def abandon(self): + # type: () -> None + """Abandon the message. This message will be returned to the queue to be reprocessed. + + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + # pylint: disable=protected-access + self._is_live(MESSAGE_ABANDON) + try: + await self._receiver._settle_message(SETTLEMENT_ABANDON, [self.lock_token]) + except Exception as e: + raise MessageSettleFailed(MESSAGE_ABANDON, e) + self._settled = True + + async def defer(self): + # type: () -> None + """Abandon the message. This message will be returned to the queue to be reprocessed. + + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. + :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. + """ + # pylint: disable=protected-access + self._is_live(MESSAGE_DEFER) + try: + await self._receiver._settle_message(SETTLEMENT_DEFER, [self.lock_token]) + except Exception as e: + raise MessageSettleFailed(MESSAGE_DEFER, e) + self._settled = True + + async def renew_lock(self): + # type: () -> None + """Renew the message lock. + + This will maintain the lock on the message to ensure + it is not returned to the queue to be reprocessed. In order to complete (or otherwise settle) + the message, the lock must be maintained. Messages received via ReceiveAndDelete mode are not + locked, and therefore cannot be renewed. This operation can also be performed as an asynchronous + background task by registering the message with an `azure.servicebus.aio.AutoLockRenew` instance. + This operation is only available for non-sessionful messages. + + :rtype: None + :raises: TypeError if the message is sessionful. + :raises: ~azure.servicebus.common.errors.MessageLockExpired is message lock has already expired. + :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. + :raises: ~azure.servicebus.common.errors.MessageAlreadySettled is message has already been settled. + """ + if self._receiver._session_id: # pylint: disable=protected-access + raise TypeError("Session messages cannot be renewed. Please renew the Session lock instead.") + self._is_live(MESSAGE_RENEW_LOCK) + token = self.lock_token + if not token: + raise ValueError("Unable to renew lock - no lock token found.") + + expiry = await self._receiver._renew_locks(token) # pylint: disable=protected-access + self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_utils.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_utils.py similarity index 58% rename from sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_utils.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_utils.py index 0d751ed80e3c..59ca15e83014 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_utils.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_utils.py @@ -7,14 +7,52 @@ import asyncio import logging import datetime +import functools -from azure.servicebus.common.utils import renewable_start_time, get_running_loop -from azure.servicebus.common.errors import AutoLockRenewTimeout, AutoLockRenewFailed +from uamqp import authentication + +from .._common.utils import renewable_start_time, get_running_loop, utc_now +from ..exceptions import AutoLockRenewTimeout, AutoLockRenewFailed +from .._common.constants import ( + JWT_TOKEN_SCOPE, + TOKEN_TYPE_JWT, + TOKEN_TYPE_SASTOKEN +) _log = logging.getLogger(__name__) +async def create_authentication(client): + # pylint: disable=protected-access + try: + # ignore mypy's warning because token_type is Optional + token_type = client._credential.token_type # type: ignore + except AttributeError: + token_type = TOKEN_TYPE_JWT + if token_type == TOKEN_TYPE_SASTOKEN: + auth = authentication.JWTTokenAsync( + client._auth_uri, + client._auth_uri, + functools.partial(client._credential.get_token, client._auth_uri), + token_type=token_type, + timeout=client._config.auth_timeout, + http_proxy=client._config.http_proxy, + transport_type=client._config.transport_type, + ) + await auth.update_token() + return auth + return authentication.JWTTokenAsync( + client._auth_uri, + client._auth_uri, + functools.partial(client._credential.get_token, JWT_TOKEN_SCOPE), + token_type=token_type, + timeout=client._config.auth_timeout, + http_proxy=client._config.http_proxy, + transport_type=client._config.transport_type, + ) + + class AutoLockRenew: """Auto lock renew. @@ -25,16 +63,17 @@ class AutoLockRenew: :type loop: ~asyncio.EventLoop .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START auto_lock_renew_async_message] - :end-before: [END auto_lock_renew_async_message] + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START auto_lock_renew_message_async] + :end-before: [END auto_lock_renew_message_async] :language: python :dedent: 4 :caption: Automatically renew a message lock - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START auto_lock_renew_async_session] - :end-before: [END auto_lock_renew_async_session] + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START auto_lock_renew_session_async] + :end-before: [END auto_lock_renew_session_async] :language: python :dedent: 4 :caption: Automatically renew a session lock @@ -67,10 +106,10 @@ async def _auto_lock_renew(self, renewable, starttime, timeout): _log.debug("Running async lock auto-renew for %r seconds", timeout) try: while self._renewable(renewable): - if (datetime.datetime.now() - starttime) >= datetime.timedelta(seconds=timeout): + if (utc_now() - starttime) >= datetime.timedelta(seconds=timeout): _log.debug("Reached auto lock renew timeout - letting lock expire.") raise AutoLockRenewTimeout("Auto-renew period ({} seconds) elapsed.".format(timeout)) - if (renewable.locked_until - datetime.datetime.now()) <= datetime.timedelta(seconds=self.renew_period): + if (renewable.locked_until_utc - utc_now()) <= datetime.timedelta(seconds=self.renew_period): _log.debug("%r seconds or less until lock expires - auto renewing.", self.renew_period) await renewable.renew_lock() await asyncio.sleep(self.sleep_time) @@ -87,11 +126,10 @@ def register(self, renewable, timeout=300): """Register a renewable entity for automatic lock renewal. :param renewable: A locked entity that needs to be renewed. - :type renewable: ~azure.servicebus.aio.async_message.Message or - ~azure.servicebus.aio.async_receive_handler.SessionReceiver - :param timeout: A time in seconds that the lock should be maintained for. + :type renewable: ~azure.servicebus.aio.ReceivedMessage or + ~azure.servicebus.aio.Session + :param float timeout: A time in seconds that the lock should be maintained for. Default value is 300 (5 minutes). - :type timeout: int """ starttime = renewable_start_time(renewable) renew_future = asyncio.ensure_future(self._auto_lock_renew(renewable, starttime, timeout), loop=self.loop) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py new file mode 100644 index 000000000000..fd0b06c21228 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py @@ -0,0 +1,197 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import logging +import asyncio +from typing import TYPE_CHECKING, Any + +import uamqp +from uamqp.message import MessageProperties + +from .._base_handler import BaseHandler, _generate_sas_token +from .._common.constants import ( + TOKEN_TYPE_SASTOKEN, + MGMT_REQUEST_OP_TYPE_ENTITY_MGMT +) +from ..exceptions import ( + InvalidHandlerState, + ServiceBusError, + _create_servicebus_exception +) + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + +_LOGGER = logging.getLogger(__name__) + + +class ServiceBusSharedKeyCredential(object): + """The shared access key credential used for authentication. + + :param str policy: The name of the shared access policy. + :param str key: The shared access key. + """ + + def __init__(self, policy: str, key: str): + self.policy = policy + self.key = key + self.token_type = TOKEN_TYPE_SASTOKEN + + async def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument + if not scopes: + raise ValueError("No token scope provided.") + return _generate_sas_token(scopes[0], self.policy, self.key) + + +class BaseHandlerAsync(BaseHandler): + def __init__( + self, + fully_qualified_namespace: str, + entity_name: str, + credential: "TokenCredential", + **kwargs: Any + ) -> None: + self._loop = kwargs.pop("loop", None) + super(BaseHandlerAsync, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + entity_name=entity_name, + credential=credential, + **kwargs + ) + + async def __aenter__(self): + await self._open_with_retry() + return self + + async def __aexit__(self, *args): + await self.close() + + async def _handle_exception(self, exception): + error, error_need_close_handler, error_need_raise = _create_servicebus_exception(_LOGGER, exception, self) + if error_need_close_handler: + await self._close_handler() + if error_need_raise: + raise error + + return error + + async def _backoff( + self, + retried_times, + last_exception, + timeout=None, + entity_name=None + ): + entity_name = entity_name or self._container_id + backoff = self._config.retry_backoff_factor * 2 ** retried_times + if backoff <= self._config.retry_backoff_max and ( + timeout is None or backoff <= timeout + ): + await asyncio.sleep(backoff) + _LOGGER.info( + "%r has an exception (%r). Retrying...", + entity_name, + last_exception, + ) + else: + _LOGGER.info( + "%r operation has timed out. Last exception before timeout is (%r)", + entity_name, + last_exception, + ) + raise last_exception + + async def _do_retryable_operation(self, operation, timeout=None, **kwargs): + require_last_exception = kwargs.pop("require_last_exception", False) + require_timeout = kwargs.pop("require_timeout", False) + retried_times = 0 + last_exception = None + max_retries = self._config.retry_total + + while retried_times <= max_retries: + try: + if require_last_exception: + kwargs["last_exception"] = last_exception + if require_timeout: + kwargs["timeout"] = timeout + return await operation(**kwargs) + except StopAsyncIteration: + raise + except Exception as exception: # pylint: disable=broad-except + last_exception = await self._handle_exception(exception) + retried_times += 1 + if retried_times > max_retries: + break + await self._backoff( + retried_times=retried_times, + last_exception=last_exception, + timeout=timeout + ) + + _LOGGER.info( + "%r operation has exhausted retry. Last exception: %r.", + self._container_id, + last_exception, + ) + raise last_exception + + async def _mgmt_request_response(self, mgmt_operation, message, callback, **kwargs): + await self._open() + if not self._running: + raise InvalidHandlerState("Client connection is closed.") + + mgmt_msg = uamqp.Message( + body=message, + properties=MessageProperties( + reply_to=self._mgmt_target, + encoding=self._config.encoding, + **kwargs)) + try: + return await self._handler.mgmt_request_async( + mgmt_msg, + mgmt_operation, + op_type=MGMT_REQUEST_OP_TYPE_ENTITY_MGMT, + node=self._mgmt_target.encode(self._config.encoding), + timeout=5000, + callback=callback) + except Exception as exp: # pylint: disable=broad-except + raise ServiceBusError("Management request failed: {}".format(exp), exp) + + async def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, **kwargs): + return await self._do_retryable_operation( + self._mgmt_request_response, + mgmt_operation=mgmt_operation, + message=message, + callback=callback, + **kwargs + ) + + @staticmethod + def _from_connection_string(conn_str, **kwargs): + kwargs = BaseHandler._from_connection_string(conn_str, **kwargs) + kwargs["credential"] = ServiceBusSharedKeyCredential(kwargs["credential"].policy, kwargs["credential"].key) + return kwargs + + async def _open(self): # pylint: disable=no-self-use + raise ValueError("Subclass should override the method.") + + async def _open_with_retry(self): + return await self._do_retryable_operation(self._open) + + async def _close_handler(self): + if self._handler: + await self._handler.close_async() + self._handler = None + self._running = False + + async def close(self): + # type: () -> None + """Close down the handler connection. + + If the handler has already closed, this operation will do nothing. An optional exception can be passed in to + indicate that the handler was shutdown due to error. + + :rtype: None + """ + await self._close_handler() diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py new file mode 100644 index 000000000000..446832493e8e --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py @@ -0,0 +1,218 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any, TYPE_CHECKING + +import uamqp + +from .._base_handler import _parse_conn_str +from ._base_handler_async import ServiceBusSharedKeyCredential +from ._servicebus_sender_async import ServiceBusSender +from ._servicebus_receiver_async import ServiceBusReceiver +from .._common._configuration import Configuration +from ._async_utils import create_authentication + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + + +class ServiceBusClient(object): + """The ServiceBusClient class defines a high level interface for + getting ServiceBusSender and ServiceBusReceiver. + + :ivar fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :vartype fully_qualified_namespace: str + + :param str fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :param ~azure.core.credentials.TokenCredential credential: The credential object used for authentication which + implements a particular interface for getting tokens. It accepts + :class:`ServiceBusSharedKeyCredential`, or credential objects + generated by the azure-identity library and objects that implement the `get_token(self, *scopes)` method. + :keyword str entity_name: Optional entity name, this can be the name of Queue or Topic. + It must be specified if the credential is for specific Queue or Topic. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_sb_client_async] + :end-before: [END create_sb_client_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusClient. + + """ + def __init__( + self, + fully_qualified_namespace, + credential, + **kwargs + ): + # type: (str, TokenCredential, Any) -> None + self.fully_qualified_namespace = fully_qualified_namespace + self._credential = credential + self._config = Configuration(**kwargs) + self._connection = None + self._entity_name = kwargs.get("entity_name") + self._auth_uri = "sb://{}".format(self.fully_qualified_namespace) + if self._entity_name: + self._auth_uri = "{}/{}".format(self._auth_uri, self._entity_name) + # Internal flag for switching whether to apply connection sharing, pending fix in uamqp library + self._connection_sharing = False + + async def __aenter__(self): + if self._connection_sharing: + await self._create_uamqp_connection() + return self + + async def __aexit__(self, *args): + await self.close() + + async def _create_uamqp_connection(self): + auth = await create_authentication(self) + self._connection = uamqp.ConnectionAsync( + hostname=self.fully_qualified_namespace, + sasl=auth, + debug=self._config.logging_enable + ) + + @classmethod + def from_connection_string( + cls, + conn_str, + **kwargs + ): + # type: (str, Any) -> ServiceBusClient + """ + Create a ServiceBusClient from a connection string. + + :param conn_str: The connection string of a Service Bus. + :keyword str entity_name: Optional entity name, this can be the name of Queue or Topic. + It must be specified if the credential is for specific Queue or Topic. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + :rtype: ~azure.servicebus.ServiceBusClient + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_sb_client_from_conn_str_async] + :end-before: [END create_sb_client_from_conn_str_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusClient from connection string. + + """ + host, policy, key, entity_in_conn_str = _parse_conn_str(conn_str) + return cls( + fully_qualified_namespace=host, + entity_name=entity_in_conn_str or kwargs.pop("entity_name", None), + credential=ServiceBusSharedKeyCredential(policy, key), + **kwargs + ) + + async def close(self): + # type: () -> None + """ + Close down the ServiceBus client. + + :return: None + """ + if self._connection_sharing and self._connection: + await self._connection.destroy_async() + + def get_queue_sender(self, queue_name, **kwargs): + # type: (str, Any) -> ServiceBusSender + """Get ServiceBusSender for the specific queue. + + :param str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :rtype: ~azure.servicebus.aio.ServiceBusSender + :raises: :class:`ServiceBusConnectionError` + :class:`ServiceBusAuthorizationError` + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_sb_client_from_conn_str_async] + :end-before: [END create_sb_client_from_conn_str_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusClient from connection string. + + """ + # pylint: disable=protected-access + sender = ServiceBusSender( + fully_qualified_namespace=self.fully_qualified_namespace, + queue_name=queue_name, + credential=self._credential, + logging_enable=self._config.logging_enable, + connection=self._connection, + **kwargs + ) + + return sender + + def get_queue_receiver(self, queue_name, **kwargs): + # type: (str, Any) -> ServiceBusReceiver + """Get ServiceBusReceiver for the specific queue. + + :param str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword mode: The mode with which messages will be retrieved from the entity. The two options + are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given + lock period before they will be removed from the queue. Messages received with ReceiveAndDelete + will be immediately removed from the queue, and cannot be subsequently rejected or re-received if + the client fails to process the message. The default mode is PeekLock. + :paramtype mode: ~azure.servicebus.ReceiveSettleMode + :keyword session_id: A specific session from which to receive. This must be specified for a + sessionful entity, otherwise it must be None. In order to receive messages from the next available + session, set this to NEXT_AVAILABLE. + :paramtype session_id: str or ~azure.servicebus.NEXT_AVAILABLE + :keyword int prefetch: The maximum number of messages to cache with each request to the service. + The default value is 0, meaning messages will be received from the service and processed + one at a time. Increasing this value will improve message throughput performance but increase + the change that messages will expire while they are cached if they're not processed fast enough. + :keyword float idle_timeout: The timeout in seconds between received messages after which the receiver will + automatically shutdown. The default value is 0, meaning no timeout. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :rtype: ~azure.servicebus.aio.ServiceBusReceiver + :raises: :class:`ServiceBusConnectionError` + :class:`ServiceBusAuthorizationError` + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_servicebus_sender_from_sb_client_async] + :end-before: [END create_servicebus_sender_from_sb_client_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusSender from ServiceBusClient. + + """ + # pylint: disable=protected-access + receiver = ServiceBusReceiver( + fully_qualified_namespace=self.fully_qualified_namespace, + queue_name=queue_name, + credential=self._credential, + logging_enable=self._config.logging_enable, + connection=self._connection, + **kwargs + ) + + return receiver diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py new file mode 100644 index 000000000000..021ee24331ed --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py @@ -0,0 +1,528 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import asyncio +import collections +import functools +import logging +from typing import Any, TYPE_CHECKING, List, Union +import six + +from uamqp import ReceiveClientAsync, types +from uamqp.constants import SenderSettleMode + +from ._base_handler_async import BaseHandlerAsync +from ._async_message import ReceivedMessage +from .._servicebus_receiver import ReceiverMixin, ServiceBusSession as BaseSession +from .._common.constants import ( + REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, + REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, + REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, + REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, + REQUEST_RESPONSE_PEEK_OPERATION, + REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, + REQUEST_RESPONSE_RENEWLOCK_OPERATION, + ReceiveSettleMode, + MGMT_RESPONSE_SESSION_STATE, + MGMT_RESPONSE_RECEIVER_EXPIRATION, + MGMT_REQUEST_SESSION_ID, + MGMT_REQUEST_SESSION_STATE, + MGMT_REQUEST_DISPOSITION_STATUS, + MGMT_REQUEST_LOCK_TOKENS, + MGMT_REQUEST_SEQUENCE_NUMBERS, + MGMT_REQUEST_RECEIVER_SETTLE_MODE, + MGMT_REQUEST_FROM_SEQUENCE_NUMBER, + MGMT_REQUEST_MESSAGE_COUNT +) +from .._common import mgmt_handlers +from .._common.utils import utc_from_timestamp +from ._async_utils import create_authentication + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + +_LOGGER = logging.getLogger(__name__) + + +class ServiceBusSession(BaseSession): + """ + The ServiceBusSession is used for manage session states and lock renewal. + + **Please use the instance variable `session` on the ServiceBusReceiver to get the corresponding ServiceBusSession + object linked with the receiver instead of instantiating a ServiceBusSession object directly.** + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START get_session_async] + :end-before: [END get_session_async] + :language: python + :dedent: 4 + :caption: Get session from a receiver + """ + + async def get_session_state(self): + # type: () -> str + """Get the session state. + + Returns None if no state has been set. + + :rtype: str + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START get_session_state_async] + :end-before: [END get_session_state_async] + :language: python + :dedent: 4 + :caption: Get the session state + """ + self._can_run() + response = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, + {MGMT_REQUEST_SESSION_ID: self.session_id}, + mgmt_handlers.default + ) + session_state = response.get(MGMT_RESPONSE_SESSION_STATE) + if isinstance(session_state, six.binary_type): + session_state = session_state.decode('UTF-8') + return session_state + + async def set_session_state(self, state): + # type: (Union[str, bytes, bytearray]) -> None + """Set the session state. + + :param state: The state value. + :type state: str, bytes or bytearray + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START set_session_state_async] + :end-before: [END set_session_state_async] + :language: python + :dedent: 4 + :caption: Set the session state + """ + self._can_run() + state = state.encode(self._encoding) if isinstance(state, six.text_type) else state + return await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, + {MGMT_REQUEST_SESSION_ID: self.session_id, MGMT_REQUEST_SESSION_STATE: bytearray(state)}, + mgmt_handlers.default + ) + + async def renew_lock(self): + # type: () -> None + """Renew the session lock. + + This operation must be performed periodically in order to retain a lock on the + session to continue message processing. + Once the lock is lost the connection will be closed. This operation can + also be performed as a threaded background task by registering the session + with an `azure.servicebus.aio.AutoLockRenew` instance. + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START session_renew_lock_async] + :end-before: [END session_renew_lock_async] + :language: python + :dedent: 4 + :caption: Renew the session lock before it expires + """ + self._can_run() + expiry = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, + {MGMT_REQUEST_SESSION_ID: self.session_id}, + mgmt_handlers.default + ) + self._locked_until_utc = utc_from_timestamp(expiry[MGMT_RESPONSE_RECEIVER_EXPIRATION]/1000.0) + + +class ServiceBusReceiver(collections.abc.AsyncIterator, BaseHandlerAsync, ReceiverMixin): + """The ServiceBusReceiver class defines a high level interface for + receiving messages from the Azure Service Bus Queue or Topic Subscription. + + :ivar fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :vartype fully_qualified_namespace: str + :ivar entity_path: The path of the entity that the client connects to. + :vartype entity_path: str + + :param str fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :param ~azure.core.credentials.TokenCredential credential: The credential object used for authentication which + implements a particular interface for getting tokens. It accepts + :class:`ServiceBusSharedKeyCredential`, or credential objects + generated by the azure-identity library and objects that implement the `get_token(self, *scopes)` method. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword str topic_name: The path of specific Service Bus Topic which contains the Subscription + the client connects to. + :keyword str subscription_name: The path of specific Service Bus Subscription under the + specified Topic the client connects to. + :keyword mode: The mode with which messages will be retrieved from the entity. The two options + are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given + lock period before they will be removed from the queue. Messages received with ReceiveAndDelete + will be immediately removed from the queue, and cannot be subsequently rejected or re-received if + the client fails to process the message. The default mode is PeekLock. + :paramtype mode: ~azure.servicebus.ReceiveSettleMode + :keyword session_id: A specific session from which to receive. This must be specified for a + sessionful entity, otherwise it must be None. In order to receive messages from the next available + session, set this to NEXT_AVAILABLE. + :paramtype session_id: str or ~azure.servicebus.NEXT_AVAILABLE + :keyword int prefetch: The maximum number of messages to cache with each request to the service. + The default value is 0 meaning messages will be received from the service and processed + one at a time. Increasing this value will improve message throughput performance but increase + the change that messages will expire while they are cached if they're not processed fast enough. + :keyword float idle_timeout: The timeout in seconds between received messages after which the receiver will + automatically shutdown. The default value is 0, meaning no timeout. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_servicebus_receiver_async] + :end-before: [END create_servicebus_receiver_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusReceiver. + + """ + def __init__( + self, + fully_qualified_namespace: str, + credential: "TokenCredential", + **kwargs: Any + ): + if kwargs.get("entity_name"): + super(ServiceBusReceiver, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + **kwargs + ) + else: + queue_name = kwargs.get("queue_name") + topic_name = kwargs.get("topic_name") + subscription_name = kwargs.get("subscription_name") + if queue_name and topic_name: + raise ValueError("Queue/Topic name can not be specified simultaneously.") + if not (queue_name or topic_name): + raise ValueError("Queue/Topic name is missing. Please specify queue_name/topic_name.") + if topic_name and not subscription_name: + raise ValueError("Subscription name is missing for the topic. Please specify subscription_name.") + + entity_name = queue_name or topic_name + + super(ServiceBusReceiver, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + entity_name=str(entity_name), + **kwargs + ) + self._message_iter = None + self._session_id = None + self._create_attribute(**kwargs) + self._connection = kwargs.get("connection") + self._session = ServiceBusSession(self._session_id, self, self._config.encoding) if self._session_id else None + + async def __anext__(self): + self._can_run() + while True: + try: + return await self._do_retryable_operation(self._iter_next) + except StopAsyncIteration: + await self.close() + raise + + async def _iter_next(self): + await self._open() + uamqp_message = await self._message_iter.__anext__() + message = self._build_message(uamqp_message, ReceivedMessage) + return message + + def _create_handler(self, auth): + self._handler = ReceiveClientAsync( + self._get_source_for_session_entity() if self._session_id else self._entity_uri, + auth=auth, + debug=self._config.logging_enable, + properties=self._properties, + error_policy=self._error_policy, + client_name=self._name, + on_attach=self._on_attach_for_session_entity if self._session_id else None, + auto_complete=False, + encoding=self._config.encoding, + receive_settle_mode=self._mode.value, + send_settle_mode=SenderSettleMode.Settled if self._mode == ReceiveSettleMode.ReceiveAndDelete else None, + timeout=self._config.idle_timeout * 1000 if self._config.idle_timeout else 0, + prefetch=self._config.prefetch + ) + + async def _open(self): + if self._running: + return + if self._handler: + await self._handler.close_async() + auth = None if self._connection else (await create_authentication(self)) + self._create_handler(auth) + await self._handler.open_async(connection=self._connection) + self._message_iter = self._handler.receive_messages_iter_async() + while not await self._handler.client_ready_async(): + await asyncio.sleep(0.05) + self._running = True + + async def _receive(self, max_batch_size=None, timeout=None): + await self._open() + max_batch_size = max_batch_size or self._handler._prefetch # pylint: disable=protected-access + + timeout_ms = 1000 * (timeout or self._config.idle_timeout) if (timeout or self._config.idle_timeout) else 0 + batch = await self._handler.receive_message_batch_async( + max_batch_size=max_batch_size, + timeout=timeout_ms + ) + + return [self._build_message(message, ReceivedMessage) for message in batch] + + async def _settle_message(self, settlement, lock_tokens, dead_letter_details=None): + message = { + MGMT_REQUEST_DISPOSITION_STATUS: settlement, + MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} + + if self._session_id: + message[MGMT_REQUEST_SESSION_ID] = self._session_id + if dead_letter_details: + message.update(dead_letter_details) + + return await self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, + message, + mgmt_handlers.default + ) + + async def _renew_locks(self, *lock_tokens): + message = {MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} + return await self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_RENEWLOCK_OPERATION, + message, + mgmt_handlers.lock_renew_op + ) + + @property + def session(self): + # type: ()->ServiceBusSession + """ + Get the ServiceBusSession object linked with the receiver. Session is only available to session-enabled + entities. + + :rtype: ~azure.servicebus.aio.ServiceBusSession + :raises: :class:`TypeError` + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START get_session_async] + :end-before: [END get_session_async] + :language: python + :dedent: 4 + :caption: Get session from a receiver + """ + if not self._session_id: + raise TypeError("Session is only available to session-enabled entities.") + return self._session # type: ignore + + @classmethod + def from_connection_string( + cls, + conn_str: str, + **kwargs: Any + ) -> "ServiceBusReceiver": + """Create a ServiceBusReceiver from a connection string. + + :param conn_str: The connection string of a Service Bus. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword str topic_name: The path of specific Service Bus Topic which contains the Subscription + the client connects to. + :keyword str subscription_name: The path of specific Service Bus Subscription under the + specified Topic the client connects to. + :keyword mode: The mode with which messages will be retrieved from the entity. The two options + are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given + lock period before they will be removed from the queue. Messages received with ReceiveAndDelete + will be immediately removed from the queue, and cannot be subsequently rejected or re-received if + the client fails to process the message. The default mode is PeekLock. + :paramtype mode: ~azure.servicebus.ReceiveSettleMode + :keyword session_id: A specific session from which to receive. This must be specified for a + sessionful entity, otherwise it must be None. In order to receive messages from the next available + session, set this to NEXT_AVAILABLE. + :paramtype session_id: str or ~azure.servicebus.NEXT_AVAILABLE + :keyword int prefetch: The maximum number of messages to cache with each request to the service. + The default value is 0, meaning messages will be received from the service and processed + one at a time. Increasing this value will improve message throughput performance but increase + the change that messages will expire while they are cached if they're not processed fast enough. + :keyword float idle_timeout: The timeout in seconds between received messages after which the receiver will + automatically shutdown. The default value is 0, meaning no timeout. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + :rtype: ~azure.servicebus.aio.ServiceBusReceiver + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_servicebus_receiver_from_conn_str_async] + :end-before: [END create_servicebus_receiver_from_conn_str_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusReceiver from connection string. + + """ + constructor_args = cls._from_connection_string( + conn_str, + **kwargs + ) + if kwargs.get("queue_name") and kwargs.get("subscription_name"): + raise ValueError("Queue entity does not have subscription.") + + if kwargs.get("topic_name") and not kwargs.get("subscription_name"): + raise ValueError("Subscription name is missing for the topic. Please specify subscription_name.") + return cls(**constructor_args) + + async def receive(self, max_batch_size=None, max_wait_time=None): + # type: (int, float) -> List[ReceivedMessage] + """Receive a batch of messages at once. + + This approach it optimal if you wish to process multiple messages simultaneously. Note that the + number of messages retrieved in a single batch will be dependent on + whether `prefetch` was set for the receiver. This call will prioritize returning + quickly over meeting a specified batch size, and so will return as soon as at least + one message is received and there is a gap in incoming messages regardless + of the specified batch size. + + :param int max_batch_size: Maximum number of messages in the batch. Actual number + returned will depend on prefetch size and incoming stream rate. + :param float max_wait_time: Maximum time to wait in seconds for the first message to arrive. + If no messages arrive, and no timeout is specified, this call will not return + until the connection is closed. If specified, an no messages arrive within the + timeout period, an empty list will be returned. + :rtype: list[~azure.servicebus.aio.ReceivedMessage] + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START receive_async] + :end-before: [END receive_async] + :language: python + :dedent: 4 + :caption: Receive messages from ServiceBus. + + """ + self._can_run() + return await self._do_retryable_operation( + self._receive, + max_batch_size=max_batch_size, + timeout=max_wait_time, + require_timeout=True + ) + + async def receive_deferred_messages(self, sequence_numbers): + # type: (List[int]) -> List[ReceivedMessage] + """Receive messages that have previously been deferred. + + When receiving deferred messages from a partitioned entity, all of the supplied + sequence numbers must be messages from the same partition. + + :param list[int] sequence_numbers: A list of the sequence numbers of messages that have been + deferred. + :rtype: list[~azure.servicebus.aio.ReceivedMessage] + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START receive_defer_async] + :end-before: [END receive_defer_async] + :language: python + :dedent: 4 + :caption: Receive deferred messages from ServiceBus. + + """ + self._can_run() + if not sequence_numbers: + raise ValueError("At least one sequence number must be specified.") + await self._open() + try: + receive_mode = self._mode.value.value + except AttributeError: + receive_mode = int(self._mode) + message = { + MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), + MGMT_REQUEST_RECEIVER_SETTLE_MODE: types.AMQPuInt(receive_mode) + } + + if self._session_id: + message[MGMT_REQUEST_SESSION_ID] = self._session_id + + handler = functools.partial(mgmt_handlers.deferred_message_op, mode=self._mode, message_type=ReceivedMessage) + messages = await self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, + message, + handler + ) + for m in messages: + m._receiver = self # pylint: disable=protected-access + return messages + + async def peek(self, message_count=1, sequence_number=0): + """Browse messages currently pending in the queue. + + Peeked messages are not removed from queue, nor are they locked. They cannot be completed, + deferred or dead-lettered. + + :param int message_count: The maximum number of messages to try and peek. The default + value is 1. + :param int sequence_number: A message sequence number from which to start browsing messages. + :rtype: list[~azure.servicebus.PeekMessage] + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START peek_messages_async] + :end-before: [END peek_messages_async] + :language: python + :dedent: 4 + :caption: Peek messages in the queue. + """ + self._can_run() + if not sequence_number: + sequence_number = self._last_received_sequenced_number or 1 + if int(message_count) < 1: + raise ValueError("count must be 1 or greater.") + if int(sequence_number) < 1: + raise ValueError("start_from must be 1 or greater.") + + await self._open() + + message = { + MGMT_REQUEST_FROM_SEQUENCE_NUMBER: types.AMQPLong(sequence_number), + MGMT_REQUEST_MESSAGE_COUNT: message_count + } + + return await self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_PEEK_OPERATION, + message, + mgmt_handlers.peek_op + ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py new file mode 100644 index 000000000000..1bab7a6bc5b1 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py @@ -0,0 +1,295 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import logging +import asyncio +from typing import Any, TYPE_CHECKING, Union, List + +import uamqp +from uamqp import SendClientAsync, types + +from .._common.message import Message, BatchMessage +from .._servicebus_sender import SenderMixin +from ._base_handler_async import BaseHandlerAsync +from ..exceptions import ( + MessageSendFailed +) +from .._common.constants import ( + REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, + REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, + MGMT_REQUEST_SEQUENCE_NUMBERS +) +from .._common import mgmt_handlers +from ._async_utils import create_authentication + +if TYPE_CHECKING: + import datetime + from azure.core.credentials import TokenCredential + +_LOGGER = logging.getLogger(__name__) + + +class ServiceBusSender(BaseHandlerAsync, SenderMixin): + """The ServiceBusSender class defines a high level interface for + sending messages to the Azure Service Bus Queue or Topic. + + :ivar fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :vartype fully_qualified_namespace: str + :ivar entity_name: The name of the entity that the client connects to. + :vartype entity_name: str + + :param str fully_qualified_namespace: The fully qualified host name for the Service Bus namespace. + The namespace format is: `.servicebus.windows.net`. + :param ~azure.core.credentials.TokenCredential credential: The credential object used for authentication which + implements a particular interface for getting tokens. It accepts + :class:`ServiceBusSharedKeyCredential`, or credential objects + generated by the azure-identity library and objects that implement the `get_token(self, *scopes)` method. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. Only one of queue_name or topic_name can be provided. + :keyword str topic_name: The path of specific Service Bus Topic the client connects to. Only one of queue_name or topic_name can be provided. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_servicebus_sender_async] + :end-before: [END create_servicebus_sender_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusSender. + + """ + def __init__( + self, + fully_qualified_namespace: str, + credential: "TokenCredential", + **kwargs: Any + ): + if kwargs.get("entity_name"): + super(ServiceBusSender, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + **kwargs + ) + else: + queue_name = kwargs.get("queue_name") + topic_name = kwargs.get("topic_name") + if queue_name and topic_name: + raise ValueError("Queue/Topic name can not be specified simultaneously.") + if not (queue_name or topic_name): + raise ValueError("Queue/Topic name is missing. Please specify queue_name/topic_name.") + entity_name = queue_name or topic_name + super(ServiceBusSender, self).__init__( + fully_qualified_namespace=fully_qualified_namespace, + credential=credential, + entity_name=str(entity_name), + **kwargs + ) + + self._max_message_size_on_link = 0 + self._create_attribute() + self._connection = kwargs.get("connection") + + def _create_handler(self, auth): + self._handler = SendClientAsync( + self._entity_uri, + auth=auth, + debug=self._config.logging_enable, + properties=self._properties, + error_policy=self._error_policy, + client_name=self._name, + encoding=self._config.encoding + ) + + async def _open(self): + # pylint: disable=protected-access + if self._running: + return + if self._handler: + await self._handler.close_async() + auth = None if self._connection else (await create_authentication(self)) + self._create_handler(auth) + await self._handler.open_async(connection=self._connection) + while not await self._handler.client_ready_async(): + await asyncio.sleep(0.05) + self._running = True + self._max_message_size_on_link = self._handler.message_handler._link.peer_max_message_size \ + or uamqp.constants.MAX_MESSAGE_LENGTH_BYTES + + async def _send(self, message, timeout=None, last_exception=None): + await self._open() + self._set_msg_timeout(timeout, last_exception) + try: + await self._handler.send_message_async(message.message) + except Exception as e: + raise MessageSendFailed(e) + + async def _schedule(self, message, schedule_time_utc): + # type: (Union[Message, BatchMessage], datetime.datetime) -> List[int] + """Send Message or BatchMessage to be enqueued at a specific time. + Returns a list of the sequence numbers of the enqueued messages. + :param message: The messages to schedule. + :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage + :param schedule_time_utc: The utc date and time to enqueue the messages. + :type schedule_time_utc: ~datetime.datetime + :rtype: List[int] + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START scheduling_messages_async] + :end-before: [END scheduling_messages_async] + :language: python + :dedent: 4 + :caption: Schedule a message to be sent in future + """ + # pylint: disable=protected-access + await self._open() + if isinstance(message, BatchMessage): + request_body = self._build_schedule_request(schedule_time_utc, *message._messages) + else: + request_body = self._build_schedule_request(schedule_time_utc, message) + return await self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, + request_body, + mgmt_handlers.schedule_op + ) + + async def _cancel_scheduled_messages(self, sequence_numbers): + # type: (Union[int, List[int]]) -> None + """ + Cancel one or more messages that have previously been scheduled and are still pending. + + :param sequence_numbers: he sequence numbers of the scheduled messages. + :type sequence_numbers: int or list[int] + :rtype: None + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START cancel_scheduled_messages_async] + :end-before: [END cancel_scheduled_messages_async] + :language: python + :dedent: 4 + :caption: Cancelling messages scheduled to be sent in future + """ + await self._open() + if isinstance(sequence_numbers, int): + numbers = [types.AMQPLong(sequence_numbers)] + else: + numbers = [types.AMQPLong(s) for s in sequence_numbers] + request_body = {MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray(numbers)} + return await self._mgmt_request_response_with_retry( + REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, + request_body, + mgmt_handlers.default + ) + + @classmethod + def from_connection_string( + cls, + conn_str: str, + **kwargs: Any + ) -> "ServiceBusSender": + """Create a ServiceBusSender from a connection string. + + :param conn_str: The connection string of a Service Bus. + :keyword str queue_name: The path of specific Service Bus Queue the client connects to. + :keyword str topic_name: The path of specific Service Bus Topic the client connects to. + :keyword bool logging_enable: Whether to output network trace logs to the logger. Default is `False`. + :keyword int retry_total: The total number of attempts to redo a failed operation when an error occurs. + Default value is 3. + :keyword transport_type: The type of transport protocol that will be used for communicating with + the Service Bus service. Default is `TransportType.Amqp`. + :paramtype transport_type: ~azure.servicebus.TransportType + :keyword dict http_proxy: HTTP proxy settings. This must be a dictionary with the following + keys: `'proxy_hostname'` (str value) and `'proxy_port'` (int value). + Additionally the following keys may also be present: `'username', 'password'`. + :rtype: ~azure.servicebus.aio.ServiceBusSender + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_servicebus_sender_from_conn_str_async] + :end-before: [END create_servicebus_sender_from_conn_str_async] + :language: python + :dedent: 4 + :caption: Create a new instance of the ServiceBusSender from connection string. + + """ + constructor_args = cls._from_connection_string( + conn_str, + **kwargs + ) + return cls(**constructor_args) + + async def send(self, message, timeout=None): + # type: (Message, float) -> None + """Sends message and blocks until acknowledgement is received or operation times out. + + :param message: The ServiceBus message to be sent. + :type message: ~azure.servicebus.Message + :param float timeout: The maximum wait time to send the event data. + :rtype: None + :raises: ~azure.servicebus.common.errors.MessageSendFailed if the message fails to + send or ~azure.servicebus.common.errors.OperationTimeoutError if sending times out. + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START send_async] + :end-before: [END send_async] + :language: python + :dedent: 4 + :caption: Send message. + + """ + await self._do_retryable_operation( + self._send, + message=message, + timeout=timeout, + require_timeout=True, + require_last_exception=True + ) + + async def create_batch(self, max_size_in_bytes=None): + # type: (int) -> BatchMessage + """Create a BatchMessage object with the max size of all content being constrained by max_size_in_bytes. + The max_size should be no greater than the max allowed message size defined by the service. + + :param int max_size_in_bytes: The maximum size of bytes data that a BatchMessage object can hold. By + default, the value is determined by your Service Bus tier. + :rtype: ~azure.servicebus.BatchMessage + + .. admonition:: Example: + + .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py + :start-after: [START create_batch_async] + :end-before: [END create_batch_async] + :language: python + :dedent: 4 + :caption: Create BatchMessage object within limited size + + """ + if not self._max_message_size_on_link: + await self._open_with_retry() + + if max_size_in_bytes and max_size_in_bytes > self._max_message_size_on_link: + raise ValueError( + "Max message size: {} is too large, acceptable max batch size is: {} bytes.".format( + max_size_in_bytes, self._max_message_size_on_link + ) + ) + + return BatchMessage( + max_size_in_bytes=(max_size_in_bytes or self._max_message_size_on_link) + ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_base_handler.py deleted file mode 100644 index 9b81f61998a1..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_base_handler.py +++ /dev/null @@ -1,214 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import asyncio -import logging -from urllib.parse import urlparse - -from uamqp import AMQPClientAsync -from uamqp.message import Message, MessageProperties -from uamqp import authentication -from uamqp import constants, errors - -from azure.servicebus.common.constants import ASSOCIATEDLINKPROPERTYNAME -from azure.servicebus.common.utils import create_properties, get_running_loop -from azure.servicebus.common.errors import ( - _ServiceBusErrorPolicy, - ServiceBusError, - ServiceBusConnectionError, - InvalidHandlerState, - ServiceBusAuthorizationError) - - -_log = logging.getLogger(__name__) - - -class BaseHandler: # pylint: disable=too-many-instance-attributes - - def __init__(self, endpoint, auth_config, *, loop=None, connection=None, encoding='UTF-8', debug=False, **kwargs): - self.loop = loop or get_running_loop() - self.running = False - self.error = None - self.endpoint = endpoint - self.entity = urlparse(endpoint).path.strip('/') - self.mgmt_target = self.entity + "/$management" - self.debug = debug - self.encoding = encoding - self.auth_config = auth_config - self.connection = connection - self.auto_reconnect = kwargs.pop('auto_reconnect', True) - self.properties = create_properties() - self.error_policy = kwargs.pop('error_policy', None) - self.handler_kwargs = kwargs - if not self.error_policy: - max_retries = kwargs.pop('max_message_retries', 3) - is_session = hasattr(self, 'session_id') - self.error_policy = _ServiceBusErrorPolicy(max_retries=max_retries, is_session=is_session) - self._handler = None - self._build_handler() - - async def __aenter__(self): - """Open the handler in a context manager.""" - await self.open() - return self - - async def __aexit__(self, *args): - """Close the handler when exiting a context manager.""" - await self.close() - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAsync.from_shared_access_key(**self.auth_config) - self._handler = AMQPClientAsync( - self.endpoint, - loop=self.loop, - auth=auth, - debug=self.debug, - properties=self.properties, - error_policy=self.error_policy, - encoding=self.encoding, - **self.handler_kwargs) - - async def _mgmt_request_response(self, operation, message, callback, keep_alive_associated_link=True, **kwargs): - if not self.running: - raise InvalidHandlerState("Client connection is closed.") - - application_properties = {} - if keep_alive_associated_link: - try: - application_properties = {ASSOCIATEDLINKPROPERTYNAME:self._handler.message_handler.name} - except AttributeError: - pass - - mgmt_msg = Message( - body=message, - properties=MessageProperties( - reply_to=self.mgmt_target, - encoding=self.encoding, - **kwargs), - application_properties=application_properties) - try: - return await self._handler.mgmt_request_async( - mgmt_msg, - operation, - op_type=b"entity-mgmt", - node=self.mgmt_target.encode(self.encoding), - timeout=5000, - callback=callback) - except Exception as exp: # pylint: disable=broad-except - raise ServiceBusError("Management request failed: {}".format(exp), exp) - - async def _handle_exception(self, exception): - if isinstance(exception, (errors.LinkDetach, errors.ConnectionClose)): - if exception.action and exception.action.retry and self.auto_reconnect: - _log.info("Async handler detached. Attempting reconnect.") - await self.reconnect() - elif exception.condition == constants.ErrorCodes.UnauthorizedAccess: - _log.info("Async handler detached. Shutting down.") - error = ServiceBusAuthorizationError(str(exception), exception) - await self.close(exception=error) - raise error - else: - _log.info("Async handler detached. Shutting down.") - error = ServiceBusConnectionError(str(exception), exception) - await self.close(exception=error) - raise error - elif isinstance(exception, errors.MessageHandlerError): - if self.auto_reconnect: - _log.info("Async handler error. Attempting reconnect.") - await self.reconnect() - else: - _log.info("Async handler error. Shutting down.") - error = ServiceBusConnectionError(str(exception), exception) - await self.close(exception=error) - raise error - elif isinstance(exception, errors.AMQPConnectionError): - message = "Failed to open handler: {}".format(exception) - raise ServiceBusConnectionError(message, exception) - else: - _log.info("Unexpected error occurred (%r). Shutting down.", exception) - error = ServiceBusError("Handler failed: {}".format(exception), exception) - await self.close(exception=error) - raise error - - async def reconnect(self): - """Reconnect the handler. - - If the handler was disconnected from the service with - a retryable error - attempt to reconnect. - This method will be called automatically for most retryable errors. - """ - await self._handler.close_async() - self.running = False - self._build_handler() - await self.open() - - async def open(self): - """Open handler connection and authenticate session. - - If the handler is already open, this operation will do nothing. - A handler opened with this method must be explicitly closed. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - - .. note:: This operation is not thread-safe. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_sender_directly] - :end-before: [END open_close_sender_directly] - :language: python - :dedent: 4 - :caption: Explicitly open and close a Sender. - - """ - if self.running: - return - self.running = True - try: - await self._handler.open_async(connection=self.connection) - while not await self._handler.client_ready_async(): - await asyncio.sleep(0.05) - except Exception as e: # pylint: disable=broad-except - try: - await self._handle_exception(e) - except: - self.running = False - raise - - async def close(self, exception=None): - """Close down the handler connection. - - If the handler has already closed, - this operation will do nothing. An optional exception can be passed in to - indicate that the handler was shutdown due to error. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - - .. note:: This operation is not thread-safe. - - :param exception: An optional exception if the handler is closing - due to an error. - :type exception: Exception - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_sender_directly] - :end-before: [END open_close_sender_directly] - :language: python - :dedent: 4 - :caption: Explicitly open and close a Sender. - - """ - self.running = False - if self.error: - return - if isinstance(exception, ServiceBusError): - self.error = exception - elif exception: - self.error = ServiceBusError(str(exception)) - else: - self.error = ServiceBusError("This message handler is now closed.") - await self._handler.close_async() diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_client.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_client.py deleted file mode 100644 index 4b411ed8788f..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_client.py +++ /dev/null @@ -1,828 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import uuid -import datetime -import functools -from urllib.parse import urlparse - -import requests -from uamqp import types -from uamqp.constants import TransportType - -from azure.servicebus.common import mgmt_handlers, mixins -from azure.servicebus.aio.async_base_handler import BaseHandler -from azure.servicebus.aio.async_send_handler import Sender, SessionSender -from azure.servicebus.aio.async_receive_handler import Receiver, SessionReceiver -from azure.servicebus.aio.async_message import Message, DeferredMessage -from azure.servicebus.control_client import ServiceBusService, SERVICE_BUS_HOST_BASE, DEFAULT_HTTP_TIMEOUT -from azure.servicebus.control_client.models import AzureServiceBusResourceNotFound -from azure.servicebus.common.utils import parse_conn_str, build_uri, get_running_loop -from azure.servicebus.common.errors import ServiceBusConnectionError, ServiceBusResourceNotFound -from azure.servicebus.common.constants import ( - REQUEST_RESPONSE_PEEK_OPERATION, - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - ReceiveSettleMode) - - -class ServiceBusClient(mixins.ServiceBusMixin): - """A Service Bus client for a namespace with the specified SAS authentication settings. - - :param str service_namespace: Service Bus namespace, required for all operations. - :param str host_base: Optional. Live host base URL. Defaults to Azure URL. - :param str shared_access_key_name: SAS authentication key name. - :param str shared_access_key_value: SAS authentication key value. - :param transport_type: Optional. Underlying transport protocol type (Amqp or AmqpOverWebsocket) - Default value is ~azure.servicebus.TransportType.Amqp - :type transport_type: ~azure.servicebus.TransportType - :param loop: An async event loop. - :param int http_request_timeout: Optional. Timeout for the HTTP request, in seconds. - :param http_request_session: Optional. Session object to use for HTTP requests. - :param bool debug: Whether to output AMQP network trace to the logger. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START create_async_servicebus_client] - :end-before: [END create_async_servicebus_client] - :language: python - :dedent: 4 - :caption: Create a ServiceBusClient. - - """ - - def __init__(self, *, service_namespace=None, host_base=SERVICE_BUS_HOST_BASE, - shared_access_key_name=None, shared_access_key_value=None, loop=None, - transport_type=TransportType.Amqp, - http_request_timeout=DEFAULT_HTTP_TIMEOUT, http_request_session=None, debug=False): - - self.loop = loop or get_running_loop() - self.service_namespace = service_namespace - self.host_base = host_base - self.shared_access_key_name = shared_access_key_name - self.shared_access_key_value = shared_access_key_value - self.transport_type = transport_type - self.debug = debug - self.mgmt_client = ServiceBusService( - service_namespace=service_namespace, - host_base=host_base, - shared_access_key_name=shared_access_key_name, - shared_access_key_value=shared_access_key_value, - timeout=http_request_timeout, - request_session=http_request_session) - - @classmethod - def from_connection_string(cls, conn_str, *, loop=None, **kwargs): - """Create a Service Bus client from a connection string. - - :param conn_str: The connection string. - :type conn_str: str - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START create_async_servicebus_client_connstr] - :end-before: [END create_async_servicebus_client_connstr] - :language: python - :dedent: 4 - :caption: Create a ServiceBusClient via a connection string. - - """ - address, policy, key, _, transport_type = parse_conn_str(conn_str) - parsed_namespace = urlparse(address) - namespace, _, base = parsed_namespace.hostname.partition('.') - return cls( - service_namespace=namespace, - shared_access_key_name=policy, - shared_access_key_value=key, - host_base='.' + base, - loop=loop, - transport_type=transport_type, - **kwargs) - - def get_queue(self, queue_name): - """Get an async client for a queue entity. - - :param queue_name: The name of the queue. - :type queue_name: str - :rtype: ~azure.servicebus.aio.async_client.QueueClient - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the queue is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START get_async_queue_client] - :end-before: [END get_async_queue_client] - :language: python - :dedent: 4 - :caption: Get a QueueClient for the specified queue. - - """ - try: - queue = self.mgmt_client.get_queue(queue_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed queue does not exist.") - return QueueClient.from_entity( - self._get_host(), queue, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - mgmt_client=self.mgmt_client, - loop=self.loop, - debug=self.debug) - - def list_queues(self): - """Get async clients for all queue entities in the namespace. - - :rtype: list[~azure.servicebus.aio.async_client.QueueClient] - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - """ - try: - queues = self.mgmt_client.list_queues() - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - queue_clients = [] - for queue in queues: - queue_clients.append(QueueClient.from_entity( - self._get_host(), queue, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - mgmt_client=self.mgmt_client, - loop=self.loop, - debug=self.debug)) - return queue_clients - - def get_topic(self, topic_name): - """Get an async client for a topic entity. - - :param topic_name: The name of the topic. - :type topic_name: str - :rtype: ~azure.servicebus.aio.async_client.TopicClient - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the topic is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START get_async_topic_client] - :end-before: [END get_async_topic_client] - :language: python - :dedent: 4 - :caption: Get a TopicClient for the specified topic. - - """ - try: - topic = self.mgmt_client.get_topic(topic_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed topic does not exist.") - return TopicClient.from_entity( - self._get_host(), topic, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - loop=self.loop, - debug=self.debug) - - def list_topics(self): - """Get an async client for all topic entities in the namespace. - - :rtype: list[~azure.servicebus.aio.async_client.TopicClient] - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - """ - try: - topics = self.mgmt_client.list_topics() - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - topic_clients = [] - for topic in topics: - topic_clients.append(TopicClient.from_entity( - self._get_host(), topic, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - loop=self.loop, - debug=self.debug)) - return topic_clients - - def get_subscription(self, topic_name, subscription_name): - """Get an async client for a subscription entity. - - :param topic_name: The name of the topic. - :type topic_name: str - :param subscription_name: The name of the subscription. - :type subscription_name: str - :rtype: ~azure.servicebus.aio.async_client.SubscriptionClient - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the subscription is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START get_async_subscription_client] - :end-before: [END get_async_subscription_client] - :language: python - :dedent: 4 - :caption: Get a TopicClient for the specified topic. - - """ - try: - subscription = self.mgmt_client.get_subscription(topic_name, subscription_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed subscription does not exist.") - return SubscriptionClient.from_entity( - self._get_host(), topic_name, subscription, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - loop=self.loop, - debug=self.debug) - - def list_subscriptions(self, topic_name): - """Get an async client for all subscription entities in the topic. - - :param topic_name: The topic to list subscriptions for. - :type topic_name: str - :rtype: list[~azure.servicebus.aio.async_client.SubscriptionClient] - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the topic is not found. - """ - try: - subs = self.mgmt_client.list_subscriptions(topic_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed topic does not exist.") - sub_clients = [] - for sub in subs: - sub_clients.append(SubscriptionClient.from_entity( - self._get_host(), topic_name, sub, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - loop=self.loop, - debug=self.debug)) - return sub_clients - - -class SendClientMixin: - - async def send(self, messages, message_timeout=0, session=None, **kwargs): - """Send one or more messages to the current entity. - - This operation will open a single-use connection, send the supplied messages, and close - connection. If the entity requires sessions, a session ID must be either - provided here, or set on each outgoing message. - - :param messages: One or more messages to be sent. - :type messages: ~azure.servicebus.aio.async_message.Message or - list[~azure.servicebus.aio.async_message.Message] - :param message_timeout: The period in seconds during which the Message must be - sent. If the send is not completed in this time it will return a failure result. - :type message_timeout: int - :param session: An optional session ID. If supplied this session ID will be - applied to every outgoing message sent with this Sender. - If an individual message already has a session ID, that will be - used instead. If no session ID is supplied here, nor set on an outgoing - message, a ValueError will be raised if the entity is sessionful. - :type session: str or ~uuid.Guid - :raises: ~azure.servicebus.common.errors.MessageSendFailed - :returns: A list of the send results of all the messages. Each - send result is a tuple with two values. The first is a boolean, indicating `True` - if the message sent, or `False` if it failed. The second is an error if the message - failed, otherwise it will be `None`. - :rtype: list[tuple[bool, ~azure.servicebus.common.errors.MessageSendFailed]] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START queue_client_send] - :end-before: [END queue_client_send] - :language: python - :dedent: 4 - :caption: Send a single message. - - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START queue_client_send_multiple] - :end-before: [END queue_client_send_multiple] - :language: python - :dedent: 4 - :caption: Send multiple messages. - - """ - async with self.get_sender(message_timeout=message_timeout, session=session, **kwargs) as sender: - if isinstance(messages, Message): - sender.queue_message(messages) - else: - try: - messages = list(messages) - except TypeError: - raise TypeError( - "Value of messages must be a 'Message' object or a synchronous iterable of 'Message' objects.") - - for m in messages: - if not isinstance(m, Message): - raise TypeError("Item in iterator is not of type 'Message'.") - sender.queue_message(m) - - return await sender.send_pending_messages() - - def get_sender(self, message_timeout=0, session=None, **kwargs): - """Get a Sender for the Service Bus endpoint. - - A Sender represents a single open connection within which multiple send operations can be made. - - :param message_timeout: The period in seconds during which messages sent with - this Sender must be sent. If the send is not completed in this time it will fail. - :type message_timeout: int - :param session: An optional session ID. If supplied this session ID will be - applied to every outgoing message sent with this Sender. - If an individual message already has a session ID, that will be - used instead. If no session ID is supplied here, nor set on an outgoing - message, a ValueError will be raised if the entity is sessionful. - :type session: str or ~uuid.Guid - :returns: A Sender instance with an unopened connection. - :rtype: ~azure.servicebus.aio.async_send_handler.Sender - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_sender_context] - :end-before: [END open_close_sender_context] - :language: python - :dedent: 4 - :caption: Send multiple messages with a Sender. - - """ - handler_id = str(uuid.uuid4()) - if self.entity and self.requires_session: - return SessionSender( - handler_id, - self.entity_uri, - self.auth_config, - session=session, - loop=self.loop, - debug=self.debug, - msg_timeout=message_timeout, - **kwargs) - return Sender( - handler_id, - self.entity_uri, - self.auth_config, - session=session, - loop=self.loop, - debug=self.debug, - msg_timeout=message_timeout, - **kwargs) - - -class ReceiveClientMixin: - - async def peek(self, count=1, start_from=0, session=None, **kwargs): - """Browse messages currently pending in the queue. - - Peeked messages are not removed from queue, nor are they locked. They cannot be completed, - deferred or dead-lettered. - - :param count: The maximum number of messages to try and peek. The default - value is 1. - :type count: int - :param start_from: A message sequence number from which to start browsing messages. - :type start_from: int - :param session: If the entity requires sessions, a session ID must be supplied - in order that only messages from that session will be browsed. If the entity - does not require sessions this value will be ignored. - :type session: str - :rtype: list[~azure.servicebus.common.message.PeekMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START client_peek_messages] - :end-before: [END client_peek_messages] - :language: python - :dedent: 4 - :caption: Peek messages in the queue. - - """ - message = { - 'from-sequence-number': types.AMQPLong(start_from), - 'message-count': int(count)} - if self.entity and self.requires_session: - if not session: - raise ValueError("Sessions are required, please set session.") - message['session-id'] = session - - async with BaseHandler( - self.entity_uri, self.auth_config, loop=self.loop, debug=self.debug, **kwargs) as handler: - return await handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_PEEK_OPERATION, - message, - mgmt_handlers.peek_op) - - async def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock, **kwargs): - """Receive messages that have previously been deferred. - - When receiving deferred messages from a partitioned entity, all of the supplied - sequence numbers must be messages from the same partition. - - :param sequence_numbers: A list of the sequence numbers of messages that have been - deferred. - :type sequence_numbers: list[int] - :param mode: The mode with which messages will be retrieved from the entity. The two options - are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given - lock period before they will be removed from the queue. Messages received with ReceiveAndDelete - will be immediately removed from the queue, and cannot be subsequently rejected or re-received if - the client fails to process the message. The default mode is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :rtype: list[~azure.servicebus.aio.async_message.DeferredMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START client_defer_messages] - :end-before: [END client_defer_messages] - :language: python - :dedent: 8 - :caption: Defer messages, then retrieve them by sequence number. - - """ - if (self.entity and self.requires_session) or kwargs.get('session'): - raise ValueError("Sessionful deferred messages can only be received within a locked receive session.") - if not sequence_numbers: - raise ValueError("At least one sequence number must be specified.") - try: - receive_mode = mode.value.value - except AttributeError: - receive_mode = int(mode) - message = { - 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), - 'receiver-settle-mode': types.AMQPuInt(receive_mode)} - - mgmt_handler = functools.partial( - mgmt_handlers.deferred_message_op, mode=receive_mode, message_type=DeferredMessage) - async with BaseHandler( - self.entity_uri, self.auth_config, loop=self.loop, debug=self.debug, **kwargs) as handler: - return await handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - message, - mgmt_handler) - - async def settle_deferred_messages(self, settlement, messages, **kwargs): - """Settle messages that have been previously deferred. - - :param settlement: How the messages are to be settled. This must be a string - of one of the following values: 'completed', 'suspended', 'abandoned'. - :type settlement: str - :param messages: A list of deferred messages to be settled. - :type messages: list[~azure.servicebus.aio.async_message.DeferredMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START client_settle_deferred_messages] - :end-before: [END client_settle_deferred_messages] - :language: python - :dedent: 4 - :caption: Settle deferred messages. - - """ - if (self.entity and self.requires_session) or kwargs.get('session'): - raise ValueError("Sessionful deferred messages can only be settled within a locked receive session.") - if settlement.lower() not in ['completed', 'suspended', 'abandoned']: - raise ValueError("Settlement must be one of: 'completed', 'suspended', 'abandoned'") - if not messages: - raise ValueError("At least one message must be specified.") - message = { - 'disposition-status': settlement.lower(), - 'lock-tokens': types.AMQPArray([m.lock_token for m in messages])} - - async with BaseHandler( - self.entity_uri, self.auth_config, loop=self.loop, debug=self.debug, **kwargs) as handler: - return await handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - message, - mgmt_handlers.default) - - async def list_sessions(self, updated_since=None, max_results=100, skip=0, **kwargs): - """List session IDs. - - List the IDs of sessions in the queue with pending messages and where the state of the session - has been updated since the timestamp provided. If no timestamp is provided, all will be returned. - If the state of a session has never been set, it will not be returned regardless of whether - there are messages pending. - - :param updated_since: The UTC datetime from which to return updated pending session IDs. - :type updated_since: ~datetime.datetime - :param max_results: The maximum number of session IDs to return. Default value is 100. - :type max_results: int - :param skip: The page value to jump to. Default value is 0. - :type skip: int - :rtype: list[str] - """ - if self.entity and not self.requires_session: - raise ValueError("This is not a sessionful entity.") - message = { - 'last-updated-time': updated_since or datetime.datetime.utcfromtimestamp(0), - 'skip': types.AMQPInt(skip), - 'top': types.AMQPInt(max_results), - } - async with BaseHandler( - self.entity_uri, self.auth_config, loop=self.loop, debug=self.debug, **kwargs) as handler: - return await handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, - message, - mgmt_handlers.list_sessions_op) - - def get_receiver(self, session=None, prefetch=0, mode=ReceiveSettleMode.PeekLock, idle_timeout=0, **kwargs): - """Get a Receiver for the Service Bus endpoint. - - A Receiver represents a single open connection with which multiple receive operations can be made. - - :param session: A specific session from which to receive. This must be specified for a - sessionful entity, otherwise it must be None. In order to receive the next available - session, set this to NEXT_AVAILABLE. - :type session: str or ~azure.servicebus.common.constants.NEXT_AVAILABLE - :param prefetch: The maximum number of messages to cache with each request to the service. - The default value is 0, meaning messages will be received from the service and processed - one at a time. Increasing this value will improve message throughput performance but increase - the chance that messages will expire while they are cached if they're not processed fast enough. - :type prefetch: int - :param mode: The mode with which messages will be retrieved from the entity. The two options - are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given - lock period before they will be removed from the queue. Messages received with ReceiveAndDelete - will be immediately removed from the queue, and cannot be subsequently rejected or re-received if - the client fails to process the message. The default mode is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param idle_timeout: The timeout in seconds between received messages after which the receiver will - automatically shutdown. The default value is 0, meaning no timeout. - :type idle_timeout: int - :returns: A Receiver instance with an unopened connection. - :rtype: ~azure.servicebus.aio.async_receive_handler.Receiver - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_receiver_context] - :end-before: [END open_close_receiver_context] - :language: python - :dedent: 4 - :caption: Receive messages with a Receiver. - - """ - if self.entity and not self.requires_session and session: - raise ValueError("A session cannot be used with a non-sessionful entitiy.") - if self.entity and self.requires_session and not session: - raise ValueError("This entity requires a session.") - if int(prefetch) < 0 or int(prefetch) > 50000: - raise ValueError("Prefetch must be an integer between 0 and 50000 inclusive.") - - prefetch += 1 - handler_id = str(uuid.uuid4()) - if session: - return SessionReceiver( - handler_id, - self.entity_uri, - self.auth_config, - session=session, - loop=self.loop, - debug=self.debug, - timeout=int(idle_timeout * 1000), - prefetch=prefetch, - mode=mode, - **kwargs) - return Receiver( - handler_id, - self.entity_uri, - self.auth_config, - loop=self.loop, - debug=self.debug, - timeout=int(idle_timeout * 1000), - prefetch=prefetch, - mode=mode, - **kwargs) - - def get_deadletter_receiver( - self, transfer_deadletter=False, prefetch=0, - mode=ReceiveSettleMode.PeekLock, idle_timeout=0, **kwargs): - """Get a Receiver for the deadletter endpoint of the entity. - - A Receiver represents a single open connection with which multiple receive operations can be made. - - :param transfer_deadletter: Whether to connect to the transfer deadletter queue, or the standard - deadletter queue. Default is False, using the standard deadletter endpoint. - :type transfer_deadletter: bool - :param prefetch: The maximum number of messages to cache with each request to the service. - The default value is 0, meaning messages will be received from the service and processed - one at a time. Increasing this value will improve message throughput performance but increase - the change that messages will expire while they are cached if they're not processed fast enough. - :type prefetch: int - :param mode: The mode with which messages will be retrieved from the entity. The two options - are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given - lock period before they will be removed from the queue. Messages received with ReceiveAndDelete - will be immediately removed from the queue, and cannot be subsequently rejected or re-received if - the client fails to process the message. The default mode is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param idle_timeout: The timeout in seconds between received messages after which the receiver will - automatically shutdown. The default value is 0, meaning no timeout. - :type idle_timeout: int - :returns: A Receiver instance with an unopened Connection. - :rtype: ~azure.servicebus.aio.async_receive_handler.Receiver - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START receiver_deadletter_messages] - :end-before: [END receiver_deadletter_messages] - :language: python - :dedent: 4 - :caption: Receive dead-lettered messages. - - """ - if int(prefetch) < 0 or int(prefetch) > 50000: - raise ValueError("Prefetch must be an integer between 0 and 50000 inclusive.") - - prefetch += 1 - handler_id = str(uuid.uuid4()) - if transfer_deadletter: - entity_uri = self.mgmt_client.format_transfer_dead_letter_queue_name(self.entity_uri) - else: - entity_uri = self.mgmt_client.format_dead_letter_queue_name(self.entity_uri) - return Receiver( - handler_id, - entity_uri, - self.auth_config, - loop=self.loop, - debug=self.debug, - timeout=int(idle_timeout * 1000), - prefetch=prefetch, - mode=mode, - **kwargs) - - -class BaseClient(mixins.BaseClient): - - def __init__(self, address, name, *, shared_access_key_name=None, - shared_access_key_value=None, loop=None, debug=False, **kwargs): - - self.loop = loop or get_running_loop() - super(BaseClient, self).__init__( - address, name, shared_access_key_name=shared_access_key_name, - shared_access_key_value=shared_access_key_value, debug=debug, **kwargs) - - def _get_entity(self): - raise NotImplementedError("Must be implemented by child class.") - - -class QueueClient(SendClientMixin, ReceiveClientMixin, BaseClient): - """A queue client. - - The QueueClient class defines a high level interface for sending - messages to and receiving messages from an Azure Service Bus queue. - If you do not wish to perform management operations, a QueueClient can be - instantiated directly to perform send and receive operations to a Queue. - However if a QueueClient is created directly, a `get_properties` operation will - need to be completed in order to retrieve the properties of this queue (for example, - whether it is sessionful). - - :param address: The full URI of the Service Bus namespace. This can optionally - include URL-encoded access name and key. - :type address: str - :param name: The name of the queue to which the Client will connect. - :type name: str - :param shared_access_key_name: The name of the shared access policy. This must be supplied - if not encoded into the address. - :type shared_access_key_name: str - :param shared_access_key_value: The shared access key. This must be supplied if not encoded - into the address. - :type shared_access_key_value: str - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param debug: Whether to output network trace logs to the logger. Default is `False`. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START create_queue_client] - :end-before: [END create_queue_client] - :language: python - :dedent: 8 - :caption: Create a QueueClient. - - """ - - def _get_entity(self): - return self.mgmt_client.get_queue(self.name) - - -class TopicClient(SendClientMixin, BaseClient): - """A topic client. - - The TopicClient class defines a high level interface for sending - messages to an Azure Service Bus Topic. - If you do not wish to perform management operations, a TopicClient can be - instantiated directly to perform send operations to a Topic. - - :param address: The full URI of the Service Bus namespace. This can optionally - include URL-encoded access name and key. - :type address: str - :param name: The name of the topic to which the Client will connect. - :type name: str - :param shared_access_key_name: The name of the shared access policy. This must be supplied - if not encoded into the address. - :type shared_access_key_name: str - :param shared_access_key_value: The shared access key. This must be supplied if not encoded - into the address. - :type shared_access_key_value: str - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param debug: Whether to output network trace logs to the logger. Default is `False`. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START create_topic_client] - :end-before: [END create_topic_client] - :language: python - :dedent: 8 - :caption: Create a TopicClient. - - """ - - def _get_entity(self): - return self.mgmt_client.get_topic(self.name) - - -class SubscriptionClient(ReceiveClientMixin, BaseClient): - """A subscription client. - - The SubscriptionClient class defines a high level interface for receiving - messages to an Azure Service Bus Subscription. - If you do not wish to perform management operations, a SubscriptionClient can be - instantiated directly to perform receive operations from a Subscription. - - :param address: The full URI of the Service Bus namespace. This can optionally - include URL-encoded access name and key. - :type address: str - :param name: The name of the topic to which the Client will connect. - :type name: str - :param shared_access_key_name: The name of the shared access policy. This must be supplied - if not encoded into the address. - :type shared_access_key_name: str - :param shared_access_key_value: The shared access key. This must be supplied if not encoded - into the address. - :type shared_access_key_value: str - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param debug: Whether to output network trace logs to the logger. Default is `False`. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START create_sub_client] - :end-before: [END create_sub_client] - :language: python - :dedent: 8 - :caption: Create a SubscriptionClient. - - """ - - def __init__(self, address, name, *, shared_access_key_name=None, - shared_access_key_value=None, loop=None, debug=False, **kwargs): - - super(SubscriptionClient, self).__init__( - address, name, - shared_access_key_name=shared_access_key_name, - shared_access_key_value=shared_access_key_value, - loop=loop, debug=debug, **kwargs) - self.topic_name = self.address.path.split("/")[1] - - @classmethod - def from_connection_string(cls, conn_str, name, topic=None, **kwargs): # pylint: disable=arguments-differ - """Create a SubscriptionClient from a connection string. - - :param conn_str: The connection string. - :type conn_str: str - :param name: The name of the Subscription. - :type name: str - :param topic: The name of the Topic, if the EntityName is - not included in the connection string. - :type topic: str - """ - address, policy, key, entity, transport_type = parse_conn_str(conn_str) - entity = topic or entity - address = build_uri(address, entity) - address += "/Subscriptions/" + name - return cls(address, name, shared_access_key_name=policy, shared_access_key_value=key, transport_type=transport_type, **kwargs) - - @classmethod - def from_entity(cls, address, topic, entity, **kwargs): # pylint: disable=arguments-differ - client = cls( - address + "/" + topic + "/Subscriptions/" + entity.name, - entity.name, - validated_entity=entity, - **kwargs) - return client - - def _get_entity(self): - return self.mgmt_client.get_subscription(self.topic_name, self.name) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_message.py deleted file mode 100644 index 8d63e534f693..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_message.py +++ /dev/null @@ -1,223 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import datetime -import functools - -from azure.servicebus.common import message -from azure.servicebus.common.utils import get_running_loop -from azure.servicebus.common.errors import MessageSettleFailed -from azure.servicebus.common.constants import DEADLETTERNAME - - -class Message(message.Message): - """A Service Bus Message. - - :param body: The data to send in a single message. The maximum size per message is 256 kB. - :type body: str or bytes - :param encoding: The encoding for string data. Default is UTF-8. - :type encoding: str - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START send_complex_message] - :end-before: [END send_complex_message] - :language: python - :dedent: 4 - :caption: Sending a message with additional properties - - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START receive_complex_message] - :end-before: [END receive_complex_message] - :language: python - :dedent: 4 - :caption: Checking the properties on a received message - """ - - def __init__(self, body, *, encoding='UTF-8', loop=None, **kwargs): - self._loop = loop or get_running_loop() - super(Message, self).__init__(body, encoding=encoding, **kwargs) - - async def renew_lock(self): - """Renew the message lock. - - This will maintain the lock on the message to ensure - it is not returned to the queue to be reprocessed. In order to complete (or otherwise settle) - the message, the lock must be maintained. Messages received via ReceiveAndDelete mode are not - locked, and therefore cannot be renewed. This operation can also be performed as an asynchronous - background task by registering the message with an `azure.servicebus.aio.AutoLockRenew` instance. - This operation is only available for non-sessionful messages. - - :raises: TypeError if the message is sessionful. - :raises: ~azure.servicebus.common.errors.MessageLockExpired is message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled is message has already been settled. - """ - if hasattr(self._receiver, 'locked_until'): - raise TypeError("Session messages cannot be renewed. Please renew the Session lock instead.") - self._is_live('renew') - token = self.lock_token - if not token: - raise ValueError("Unable to renew lock - no lock token found.") - - expiry = await self._receiver._renew_locks(token) # pylint: disable=protected-access - self._expiry = datetime.datetime.fromtimestamp(expiry[b'expirations'][0]/1000.0) - - async def complete(self): - """Complete the message. This removes the message from the queue. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('accept') - try: - await self._loop.run_in_executor(None, self.message.accept) - except Exception as e: - raise MessageSettleFailed("accept", e) - - async def dead_letter(self, description=None): - """Move the message to the Dead Letter queue. - - The Dead Letter queue is a sub-queue that can be - used to store messages that failed to process correctly, or otherwise require further inspection - or processing. The queue can also be configured to send expired messages to the Dead Letter queue. - To receive dead-lettered messages, use `QueueClient.get_deadletter_receiver()` or - `SubscriptionClient.get_deadletter_receiver()`. - - :param description: The reason for dead-lettering the message. - :type description: str - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('reject') - try: - reject = functools.partial(self.message.reject, condition=DEADLETTERNAME, description=description) - await self._loop.run_in_executor(None, reject) - except Exception as e: - raise MessageSettleFailed("reject", e) - - async def abandon(self): - """Abandon the message. - - This message will be returned to the queue to be reprocessed. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('abandon') - try: - modify = functools.partial(self.message.modify, True, False) - await self._loop.run_in_executor(None, modify) - except Exception as e: - raise MessageSettleFailed("abandon", e) - - async def defer(self): - """Defer the message. - - This message will remain in the queue but must be received - specifically by its sequence number in order to be processed. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('defer') - try: - modify = functools.partial(self.message.modify, True, True) - await self._loop.run_in_executor(None, modify) - except Exception as e: - raise MessageSettleFailed("defer", e) - - -class DeferredMessage(Message): - """A message that has been deferred. - - A deferred message can be completed, - abandoned, or dead-lettered, however it cannot be deferred again. - """ - - def __init__(self, deferred, mode): - self._settled = mode == 0 - super(DeferredMessage, self).__init__(None, message=deferred) - - def _is_live(self, action): - if not self._receiver: - raise ValueError("Orphan message had no open connection.") - super(DeferredMessage, self)._is_live(action) - - @property - def lock_token(self): - if self.settled: - return None - if hasattr(self.message, 'delivery_tag') and self.message.delivery_tag: - return uuid.UUID(bytes_le=self.message.delivery_tag) - delivery_annotations = self.message.delivery_annotations - if delivery_annotations: - return delivery_annotations.get(self._x_OPT_LOCK_TOKEN) - return None - - @property - def settled(self): - return self._settled - - async def complete(self): - """Complete the message. - - This removes the message from the queue. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('complete') - await self._receiver._settle_deferred('completed', [self.lock_token]) # pylint: disable=protected-access - self._settled = True - - async def dead_letter(self, description=None): - """Move the message to the Dead Letter queue. - - The Dead Letter queue is a sub-queue that can be - used to store messages that failed to process correctly, or otherwise require further inspection - or processing. The queue can also be configured to send expired messages to the Dead Letter queue. - To receive dead-lettered messages, use `QueueClient.get_deadletter_receiver()` or - `SubscriptionClient.get_deadletter_receiver()`. - - :param description: The reason for dead-lettering the message. - :type description: str - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('dead-letter') - details = { - 'deadletter-reason': str(description) if description else "", - 'deadletter-description': str(description) if description else ""} - await self._receiver._settle_deferred( # pylint: disable=protected-access - 'suspended', [self.lock_token], dead_letter_details=details) - self._settled = True - - async def abandon(self): - """Abandon the message. This message will be returned to the queue to be reprocessed. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('abandon') - await self._receiver._settle_deferred('abandoned', [self.lock_token]) # pylint: disable=protected-access - self._settled = True - - async def defer(self): - """A DeferredMessage cannot be deferred. Raises `ValueError`.""" - raise ValueError("Message is already deferred.") diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_receive_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_receive_handler.py deleted file mode 100644 index 69c6d7ff02a2..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_receive_handler.py +++ /dev/null @@ -1,698 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import asyncio -import datetime -import functools -import uuid -import collections - -import six - -from uamqp import ReceiveClientAsync -from uamqp import authentication -from uamqp import constants, types, errors - -from azure.servicebus.aio import Message, DeferredMessage -from azure.servicebus.aio.async_base_handler import BaseHandler -from azure.servicebus.common import mgmt_handlers, mixins -from azure.servicebus.common.errors import ( - InvalidHandlerState, - NoActiveSession, - SessionLockExpired) -from azure.servicebus.common.constants import ( - SESSION_LOCK_LOST, - SESSION_LOCK_TIMEOUT, - REQUEST_RESPONSE_RENEWLOCK_OPERATION, - REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, - REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, - REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, - REQUEST_RESPONSE_PEEK_OPERATION, - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - ReceiveSettleMode) - - -class Receiver(collections.abc.AsyncIterator, BaseHandler): # pylint: disable=too-many-instance-attributes - """A message receiver. - - This receive handler acts as an iterable message stream for retrieving - messages for a Service Bus entity. It operates a single connection that must be opened and - closed on completion. The service connection will remain open for the entirety of the iterator. - If you find yourself only partially iterating the message stream, you should run the receiver - in a `with` statement to ensure the connection is closed. - The Receiver should not be instantiated directly, and should be accessed from a `QueueClient` or - `SubscriptionClient` using the `get_receiver()` method. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Receiver. - :type handler_id: str - :param source: The endpoint from which to receive messages. - :type source: ~uamqp.Source - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param mode: The receive connection mode. Value must be either PeekLock or ReceiveAndDelete. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_receiver_context] - :end-before: [END open_close_receiver_context] - :language: python - :dedent: 4 - :caption: Running a queue receiver within a context manager. - - """ - - def __init__( - self, handler_id, source, auth_config, *, loop=None, connection=None, - mode=ReceiveSettleMode.PeekLock, encoding='UTF-8', debug=False, **kwargs): - self._used = asyncio.Event() - self.name = "SBReceiver-{}".format(handler_id) - self.last_received = None - self.mode = mode - self.message_iter = None - super(Receiver, self).__init__( - source, auth_config, loop=loop, connection=connection, encoding=encoding, debug=debug, **kwargs) - - async def __anext__(self): - await self._can_run() - while True: - if self.receiver_shutdown: - await self.close() - raise StopAsyncIteration - try: - received = await self.message_iter.__anext__() - wrapped = self._build_message(received) - return wrapped - except StopAsyncIteration: - await self.close() - raise - except Exception as e: # pylint: disable=broad-except - await self._handle_exception(e) - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAsync.from_shared_access_key(**self.auth_config) - self._handler = ReceiveClientAsync( - self.endpoint, - auth=auth, - debug=self.debug, - properties=self.properties, - error_policy=self.error_policy, - client_name=self.name, - auto_complete=False, - encoding=self.encoding, - loop=self.loop, - **self.handler_kwargs) - - async def _build_receiver(self): - """This is a temporary patch pending a fix in uAMQP.""" - # pylint: disable=protected-access - self._handler.message_handler = self._handler.receiver_type( - self._handler._session, - self._handler._remote_address, - self._handler._name, - on_message_received=self._handler._message_received, - name='receiver-link-{}'.format(uuid.uuid4()), - debug=self._handler._debug_trace, - prefetch=self._handler._prefetch, - max_message_size=self._handler._max_message_size, - properties=self._handler._link_properties, - error_policy=self._handler._error_policy, - encoding=self._handler._encoding, - loop=self._handler.loop) - if self.mode != ReceiveSettleMode.PeekLock: - self._handler.message_handler.send_settle_mode = constants.SenderSettleMode.Settled - self._handler.message_handler.receive_settle_mode = constants.ReceiverSettleMode.ReceiveAndDelete - self._handler.message_handler._settle_mode = constants.ReceiverSettleMode.ReceiveAndDelete - await self._handler.message_handler.open_async() - - def _build_message(self, received): - message = Message(None, message=received) - message._receiver = self # pylint: disable=protected-access - self.last_received = message.sequence_number - return message - - async def _can_run(self): - if self._used.is_set(): - raise InvalidHandlerState("Receiver has already closed.") - if self.receiver_shutdown: - await self.close() - raise InvalidHandlerState("Receiver has already closed.") - if not self.running: - await self.open() - - async def _renew_locks(self, *lock_tokens): - message = {'lock-tokens': types.AMQPArray(lock_tokens)} - return await self._mgmt_request_response( - REQUEST_RESPONSE_RENEWLOCK_OPERATION, - message, - mgmt_handlers.lock_renew_op) - - async def _settle_deferred(self, settlement, lock_tokens, dead_letter_details=None): - message = { - 'disposition-status': settlement, - 'lock-tokens': types.AMQPArray(lock_tokens)} - if dead_letter_details: - message.update(dead_letter_details) - return await self._mgmt_request_response( - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - message, - mgmt_handlers.default) - - @property - def receiver_shutdown(self): - """Whether the receiver connection has been marked for shutdown. - - If this value is `True` - it does not indicate that the connection - has yet been closed. - This property is used internally and should not be relied upon to asses - the status of the connection. - - :rtype: bool - """ - if self._handler: - return self._handler._shutdown # pylint: disable=protected-access - return True - - @receiver_shutdown.setter - def receiver_shutdown(self, value): - """Mark the connection as ready for shutdown. - - This property is used internally and should not be set in normal usage. - - :param bool value: Whether to shutdown the connection. - """ - if self._handler: - self._handler._shutdown = value # pylint: disable=protected-access - else: - raise ValueError("Receiver has no AMQP handler") - - @property - def queue_size(self): - """The current size of the unprocessed message queue. - - :rtype: int - """ - # pylint: disable=protected-access - if self._handler._received_messages: - return self._handler._received_messages.qsize() - return 0 - - async def open(self): - """Open receiver connection and authenticate session. - - If the receiver is already open, this operation will do nothing. - This method will be called automatically when one starts to iterate - messages in the receiver, so there should be no need to call it directly. - A receiver opened with this method must be explicitly closed. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - - .. note:: This operation is not thread-safe. - - """ - if self.running: - return - self.running = True - try: - await self._handler.open_async(connection=self.connection) - self.message_iter = self._handler.receive_messages_iter_async() - while not await self._handler.auth_complete_async(): - await asyncio.sleep(0.05) - await self._build_receiver() - while not await self._handler.client_ready_async(): - await asyncio.sleep(0.05) - except Exception as e: # pylint: disable=broad-except - try: - await self._handle_exception(e) - except: - self.running = False - raise - - async def close(self, exception=None): - """Close down the receiver connection. - - If the receiver has already closed, this operation will do nothing. An optional - exception can be passed in to indicate that the handler was shutdown due to error. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - The receiver will be implicitly closed on completion of the message iterator, - however this method will need to be called explicitly if the message iterator is not run - to completion. - - .. note:: This operation is not thread-safe. - - :param exception: An optional exception if the handler is closing - due to an error. - :type exception: Exception - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_receiver_directly] - :end-before: [END open_close_receiver_directly] - :language: python - :dedent: 4 - :caption: Iterate then explicitly close a Receiver. - - """ - if not self.running: - return - self.running = False - self.receiver_shutdown = True - self._used.set() - await super(Receiver, self).close(exception=exception) - - async def peek(self, count=1, start_from=0): - """Browse messages currently pending in the queue. - - Peeked messages are not removed from queue, nor are they locked. They cannot be completed, - deferred or dead-lettered. - - :param count: The maximum number of messages to try and peek. The default - value is 1. - :type count: int - :param start_from: A message sequence number from which to start browsing messages. - :type start_from: int - :rtype: list[~azure.servicebus.common.message.PeekMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START receiver_peek_messages] - :end-before: [END receiver_peek_messages] - :language: python - :dedent: 4 - :caption: Peek messages in the queue. - - """ - await self._can_run() - if not start_from: - start_from = self.last_received or 1 - if int(count) < 1: - raise ValueError("count must be 1 or greater.") - if int(start_from) < 1: - raise ValueError("start_from must be 1 or greater.") - - message = { - 'from-sequence-number': types.AMQPLong(start_from), - 'message-count': count - } - return await self._mgmt_request_response( - REQUEST_RESPONSE_PEEK_OPERATION, - message, - mgmt_handlers.peek_op) - - async def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock): - """Receive messages that have previously been deferred. - - When receiving deferred messages from a partitioned entity, all of the supplied - sequence numbers must be messages from the same partition. - - :param sequence_numbers: A list of the sequence numbers of messages that have been - deferred. - :type sequence_numbers: list[int] - :param mode: The receive mode, default value is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :rtype: list[~azure.servicebus.aio.async_message.DeferredMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START receiver_defer_messages] - :end-before: [END receiver_defer_messages] - :language: python - :dedent: 8 - :caption: Defer messages, then retrieve them by sequence number. - - """ - if not sequence_numbers: - raise ValueError("At least one sequence number must be specified.") - await self._can_run() - try: - receive_mode = mode.value.value - except AttributeError: - receive_mode = int(mode) - message = { - 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), - 'receiver-settle-mode': types.AMQPuInt(receive_mode) - } - handler = functools.partial(mgmt_handlers.deferred_message_op, mode=receive_mode, message_type=DeferredMessage) - messages = await self._mgmt_request_response( - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - message, - handler) - for m in messages: - m._receiver = self # pylint: disable=protected-access - return messages - - async def fetch_next(self, max_batch_size=None, timeout=None): - """Receive a batch of messages at once. - - This approach it optimal if you wish to process multiple messages simultaneously. - Note that the number of messages retrieved in a single batch will be dependent on - whether `prefetch` was set for the receiver. This call will prioritize returning - quickly over meeting a specified batch size, and so will return as soon as at least - one message is received and there is a gap in incoming messages regardless - of the specified batch size. - - :param max_batch_size: Maximum number of messages in the batch. Actual number - returned will depend on prefetch size and incoming stream rate. - :type max_batch_size: int - :param timeout: The time to wait in seconds for the first message to arrive. - If no messages arrive, and no timeout is specified, this call will not return - until the connection is closed. If specified, an no messages arrive within the - timeout period, an empty list will be returned. - :rtype: list[~azure.servicebus.aio.async_message.Message] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START receiver_fetch_batch] - :end-before: [END receiver_fetch_batch] - :language: python - :dedent: 4 - :caption: Fetch a batch of messages. - - """ - await self._can_run() - wrapped_batch = [] - max_batch_size = max_batch_size or self._handler._prefetch # pylint: disable=protected-access - try: - timeout_ms = 1000 * timeout if timeout else 0 - batch = await self._handler.receive_message_batch_async( - max_batch_size=max_batch_size, - timeout=timeout_ms) - for received in batch: - message = self._build_message(received) - wrapped_batch.append(message) - except Exception as e: # pylint: disable=broad-except - await self._handle_exception(e) - return wrapped_batch - - -class SessionReceiver(Receiver, mixins.SessionMixin): - """A session message receiver. - - This receive handler acts as an iterable message stream for retrieving - messages for a sessionful Service Bus entity. It operates a single connection that must be opened and - closed on completion. The service connection will remain open for the entirety of the iterator. - If you find yourself only partially iterating the message stream, you should run the receiver - in a `with` statement to ensure the connection is closed. - The Receiver should not be instantiated directly, and should be accessed from a `QueueClient` or - `SubscriptionClient` using the `get_receiver()` method. - When receiving messages from a session, connection errors that would normally be automatically - retried will instead raise an error due to the loss of the lock on a particular session. - A specific session can be specified, or the receiver can retrieve any available session using - the `NEXT_AVAILABLE` constant. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Receiver. - :type handler_id: str - :param source: The endpoint from which to receive messages. - :type source: ~uamqp.Source - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param session: The ID of the session to receive from. - :type session: str or ~azure.servicebus.common.constants.NEXT_AVAILABLE - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param mode: The receive connection mode. Value must be either PeekLock or ReceiveAndDelete. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_receiver_session_context] - :end-before: [END open_close_receiver_session_context] - :language: python - :dedent: 4 - :caption: Running a session receiver within a context manager. - - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_receiver_session_nextavailable] - :end-before: [END open_close_receiver_session_nextavailable] - :language: python - :dedent: 4 - :caption: Running a session receiver for the next available session. - - """ - - def __init__( - self, handler_id, source, auth_config, *, session=None, loop=None, - connection=None, encoding='UTF-8', debug=False, **kwargs): - self.session_id = None - self.session_filter = session - self.locked_until = None - self.session_start = None - self.auto_reconnect = False - self.auto_renew_error = None - super(SessionReceiver, self).__init__( - handler_id, source, auth_config, loop=loop, - connection=connection, encoding=encoding, debug=debug, **kwargs) - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAsync.from_shared_access_key(**self.auth_config) - self._handler = ReceiveClientAsync( - self._get_source(), - auth=auth, - debug=self.debug, - properties=self.properties, - error_policy=self.error_policy, - client_name=self.name, - on_attach=self._on_attach, - auto_complete=False, - encoding=self.encoding, - loop=self.loop, - **self.handler_kwargs) - - async def _can_run(self): - await super(SessionReceiver, self)._can_run() - if self.expired: - raise SessionLockExpired(inner_exception=self.auto_renew_error) - - async def _handle_exception(self, exception): - if isinstance(exception, errors.LinkDetach) and exception.condition == SESSION_LOCK_LOST: - error = SessionLockExpired("Connection detached - lock on Session {} lost.".format(self.session_id)) - await self.close(exception=error) - raise error - elif isinstance(exception, errors.LinkDetach) and exception.condition == SESSION_LOCK_TIMEOUT: - error = NoActiveSession("Queue has no active session to receive from.") - await self.close(exception=error) - raise error - return await super(SessionReceiver, self)._handle_exception(exception) - - async def _settle_deferred(self, settlement, lock_tokens, dead_letter_details=None): - message = { - 'disposition-status': settlement, - 'lock-tokens': types.AMQPArray(lock_tokens), - 'session-id': self.session_id} - if dead_letter_details: - message.update(dead_letter_details) - return await self._mgmt_request_response( - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - message, - mgmt_handlers.default) - - async def get_session_state(self): - """Get the session state. - - Returns None if no state has been set. - - :rtype: str - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START set_session_state] - :end-before: [END set_session_state] - :language: python - :dedent: 4 - :caption: Getting and setting the state of a session. - - """ - await self._can_run() - response = await self._mgmt_request_response( - REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, - {'session-id': self.session_id}, - mgmt_handlers.default) - session_state = response.get(b'session-state') - if isinstance(session_state, six.binary_type): - session_state = session_state.decode('UTF-8') - return session_state - - async def set_session_state(self, state): - """Set the session state. - - :param state: The state value. - :type state: str or bytes or bytearray - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START set_session_state] - :end-before: [END set_session_state] - :language: python - :dedent: 4 - :caption: Getting and setting the state of a session. - - """ - await self._can_run() - state = state.encode(self.encoding) if isinstance(state, six.text_type) else state - return await self._mgmt_request_response( - REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, - {'session-id': self.session_id, 'session-state': bytearray(state)}, - mgmt_handlers.default) - - async def renew_lock(self): - """Renew the session lock. - - This operation must be performed periodically in order to retain a lock on the session - to continue message processing. Once the lock is lost the connection will be closed. - This operation can also be performed as an asynchronous background task by registering the session - with an `azure.servicebus.aio.AutoLockRenew` instance. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START receiver_renew_session_lock] - :end-before: [END receiver_renew_session_lock] - :language: python - :dedent: 4 - :caption: Renew the sesison lock. - - """ - await self._can_run() - expiry = await self._mgmt_request_response( - REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, - {'session-id': self.session_id}, - mgmt_handlers.default) - self.locked_until = datetime.datetime.fromtimestamp(expiry[b'expiration']/1000.0) - - async def peek(self, count=1, start_from=0): - """Browse messages currently pending in the queue. - - Peeked messages are not removed from queue, nor are they locked. - They cannot be completed, deferred or dead-lettered. - This operation will only peek pending messages in the current session. - - :param count: The maximum number of messages to try and peek. The default - value is 1. - :type count: int - :param start_from: A message sequence number from which to start browsing messages. - :type start_from: int - :rtype: list[~azure.servicebus.common.message.PeekMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START receiver_peek_session_messages] - :end-before: [END receiver_peek_session_messages] - :language: python - :dedent: 8 - :caption: Peek messages in the queue. - - """ - if not start_from: - start_from = self.last_received or 1 - if int(count) < 1: - raise ValueError("count must be 1 or greater.") - if int(start_from) < 1: - raise ValueError("start_from must be 1 or greater.") - - await self._can_run() - message = { - 'from-sequence-number': types.AMQPLong(start_from), - 'message-count': count, - 'session-id': self.session_id} - return await self._mgmt_request_response( - REQUEST_RESPONSE_PEEK_OPERATION, - message, - mgmt_handlers.peek_op) - - async def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock): - """Receive messages that have previously been deferred. - - This operation can only receive deferred messages from the current session. - When receiving deferred messages from a partitioned entity, all of the supplied - sequence numbers must be messages from the same partition. - - :param sequence_numbers: A list of the sequence numbers of messages that have been - deferred. - :type sequence_numbers: list[int] - :param mode: The receive mode, default value is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :rtype: list[~azure.servicebus.aio.async_message.DeferredMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START receiver_defer_session_messages] - :end-before: [END receiver_defer_session_messages] - :language: python - :dedent: 8 - :caption: Defer messages, then retrieve them by sequence number. - - """ - if not sequence_numbers: - raise ValueError("At least one sequence number must be specified.") - await self._can_run() - try: - receive_mode = mode.value.value - except AttributeError: - receive_mode = int(mode) - message = { - 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), - 'receiver-settle-mode': types.AMQPuInt(receive_mode), - 'session-id': self.session_id - } - handler = functools.partial(mgmt_handlers.deferred_message_op, mode=receive_mode, message_type=DeferredMessage) - messages = await self._mgmt_request_response( - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - message, - handler) - for m in messages: - m._receiver = self # pylint: disable=protected-access - return messages - - async def list_sessions(self, updated_since=None, max_results=100, skip=0): - """List session IDs. - - List the IDs of sessions in the queue with pending messages and where the state of the session - has been updated since the timestamp provided. If no timestamp is provided, all will be returned. - If the state of a session has never been set, it will not be returned regardless of whether - there are messages pending. - - :param updated_since: The UTC datetime from which to return updated pending session IDs. - :type updated_since: ~datetime.datetime - :param max_results: The maximum number of session IDs to return. Default value is 100. - :type max_results: int - :param skip: The page value to jump to. Default value is 0. - :type skip: int - :rtype: list[str] - """ - if int(max_results) < 1: - raise ValueError("max_results must be 1 or greater.") - - await self._can_run() - message = { - 'last-updated-time': updated_since or datetime.datetime.utcfromtimestamp(0), - 'skip': skip, - 'top': max_results, - } - return await self._mgmt_request_response( - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, - message, - mgmt_handlers.list_sessions_op, - keep_alive_associated_link=False) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_send_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_send_handler.py deleted file mode 100644 index 28fc2d95b255..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/async_send_handler.py +++ /dev/null @@ -1,304 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -from uamqp import SendClientAsync -from uamqp import authentication -from uamqp import constants, types - -from azure.servicebus.common.errors import MessageSendFailed -from azure.servicebus.common import mgmt_handlers, mixins -from azure.servicebus.common.message import Message -from azure.servicebus.common.constants import ( - REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, - REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION) -from azure.servicebus.aio.async_base_handler import BaseHandler - - -class Sender(BaseHandler, mixins.SenderMixin): - """This handler is for sending messages to a Service Bus entity. - - It operates a single connection that must be opened and closed on completion. - The Sender can be run within a context manager to ensure that the connection is closed on exit. - The Sender should not be instantiated directly, and should be accessed from a `QueueClient` or - `TopicClient` using the `get_sender()` method. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Sender. - :type handler_id: str - :param target: The endpoint to send messages to. - :type target: ~uamqp.Target - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param session: An optional session ID. If supplied, all outgoing messages will have this - session ID added (unless they already have one specified). - :type session: str - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - """ - - def __init__( - self, handler_id, target, auth_config, *, session=None, loop=None, - connection=None, encoding='UTF-8', debug=False, **kwargs): - self.name = "SBSender-{}".format(handler_id) - self.session_id = session - super(Sender, self).__init__( - target, auth_config, loop=loop, connection=connection, encoding=encoding, debug=debug, **kwargs) - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAsync.from_shared_access_key(**self.auth_config) - self._handler = SendClientAsync( - self.endpoint, - auth=auth, - debug=self.debug, - properties=self.properties, - error_policy=self.error_policy, - client_name=self.name, - encoding=self.encoding, - loop=self.loop, - **self.handler_kwargs) - - async def send(self, message): - """Send a message and blocks until acknowledgement is received or the operation fails. - - :param message: The message to be sent. - :type message: ~azure.servicebus.aio.async_message.Message - :raises: ~azure.servicebus.common.errors.MessageSendFailed if the message fails to - send. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_sender_context] - :end-before: [END open_close_sender_context] - :language: python - :dedent: 4 - :caption: Open a Sender and send messages. - - """ - if not isinstance(message, Message): - raise TypeError("Value of message must be of type 'Message'.") - if not self.running: - await self.open() - if self.session_id and not message.properties.group_id: - message.properties.group_id = self.session_id - try: - await self._handler.send_message_async(message.message) - except Exception as e: # pylint: disable=broad-except - raise MessageSendFailed(e) - - async def schedule(self, schedule_time, *messages): - """Send one or more messages to be enqueued at a specific time. - - Returns a list of the sequence numbers of the enqueued messages. - - :param schedule_time: The date and time to enqueue the messages. - :type schedule_time: ~datetime.datetime - :param messages: The messages to schedule. - :type messages: ~azure.servicebus.aio.async_message.Message - :rtype: list[int] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START schedule_messages] - :end-before: [END schedule_messages] - :language: python - :dedent: 4 - :caption: Schedule messages. - - """ - if not self.running: - await self.open() - request_body = self._build_schedule_request(schedule_time, *messages) - return await self._mgmt_request_response( - REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, - request_body, - mgmt_handlers.schedule_op) - - async def cancel_scheduled_messages(self, *sequence_numbers): - """Cancel one or more messages that have previsouly been scheduled and are still pending. - - :param sequence_numbers: The seqeuence numbers of the scheduled messages. - :type sequence_numbers: int - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START cancel_schedule_messages] - :end-before: [END cancel_schedule_messages] - :language: python - :dedent: 4 - :caption: Schedule messages. - - """ - if not self.running: - await self.open() - numbers = [types.AMQPLong(s) for s in sequence_numbers] - request_body = {'sequence-numbers': types.AMQPArray(numbers)} - return await self._mgmt_request_response( - REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, - request_body, - mgmt_handlers.default) - - async def send_pending_messages(self): - """Wait until all pending messages have been sent. - - :returns: A list of the send results of all the pending messages. Each - send result is a tuple with two values. The first is a boolean, indicating `True` - if the message sent, or `False` if it failed. The second is an error if the message - failed, otherwise it will be `None`. - :rtype: list[tuple[bool, ~azure.servicebus.common.errors.MessageSendFailed]] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START queue_sender_messages] - :end-before: [END queue_sender_messages] - :language: python - :dedent: 4 - :caption: Schedule messages. - - """ - if not self.running: - await self.open() - try: - pending = self._handler._pending_messages[:] # pylint: disable=protected-access - await self._handler.wait_async() - results = [] - for m in pending: - if m.state == constants.MessageState.SendFailed: - results.append((False, MessageSendFailed(m._response))) # pylint: disable=protected-access - else: - results.append((True, None)) - return results - except Exception as e: # pylint: disable=broad-except - raise MessageSendFailed(e) - - async def reconnect(self): - """Reconnect the handler. - - If the handler was disconnected from the service with - a retryable error - attempt to reconnect. - This method will be called automatically for most retryable errors. - Also attempts to re-queue any messages that were pending before the reconnect. - """ - unsent_events = self._handler.pending_messages - await super(Sender, self).reconnect() - try: - self._handler.queue_message(*unsent_events) - await self._handler.wait_async() - except Exception as e: # pylint: disable=broad-except - await self._handle_exception(e) - - -class SessionSender(Sender): - """This handler is for sending messages to a sessionful Service Bus entity. - - It operates a single connection that must be opened and closed on completion. - The Sender can be run within a context manager to ensure that the connection is closed on exit. - The Sender should not be instantiated directly, and should be accessed from a `QueueClient` or - `TopicClient` using the `get_sender()` method. - An attempt to send a message without a session ID specified either on the Sender or the message - will raise a `ValueError`. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Sender. - :type handler_id: str - :param target: The endpoint to send messages to. - :type target: ~uamqp.Target - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param session: An optional session ID. If supplied, all outgoing messages will have this - session ID added (unless they already have one specified). - :type session: str - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - """ - - async def send(self, message): - """Send a message and blocks until acknowledgement is received or the operation fails. - - If neither the Sender nor the message has a session ID, a `ValueError` will be raised. - - :param message: The message to be sent. - :type message: ~azure.servicebus.aio.async_message.Message - :raises: ~azure.servicebus.common.errors.MessageSendFailed if the message fails to - send. - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START open_close_session_sender_context] - :end-before: [END open_close_session_sender_context] - :language: python - :dedent: 4 - :caption: Open a sessionful Sender and send messages. - - """ - if not isinstance(message, Message): - raise TypeError("Value of message must be of type 'Message'.") - if not self.session_id and not message.properties.group_id: - raise ValueError("Message must have Session ID.") - return await super(SessionSender, self).send(message) - - def queue_message(self, message): - """Queue a message to be sent later. - - This operation should be followed up with send_pending_messages. If neither the - Sender nor the message has a session ID, a `ValueError` will be raised. - - :param message: The message to be sent. - :type message: ~azure.servicebus.aio.async_message.Message - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START queue_session_sender_messages] - :end-before: [END queue_session_sender_messages] - :language: python - :dedent: 4 - :caption: Schedule messages. - - """ - if not self.session_id and not message.properties.group_id: - raise ValueError("Message must have Session ID.") - super(SessionSender, self).queue_message(message) - - async def schedule_messages(self, schedule_time, *messages): - """Send one or more messages to be enqueued at a specific time. - - Returns a list of the sequence numbers of the enqueued messages. - If neither the Sender nor the message has a session ID, a `ValueError` will be raised. - - :param schedule_time: The date and time to enqueue the messages. - :type schedule_time: ~datetime.datetime - :param messages: The messages to schedule. - :type messages: ~azure.servicebus.aio.async_message.Message - :rtype: list[int] - - .. admonition:: Example: - .. literalinclude:: ../samples/async_samples/test_examples_async.py - :start-after: [START schedule_session_messages] - :end-before: [END schedule_session_messages] - :language: python - :dedent: 4 - :caption: Schedule messages. - - """ - for message in messages: - if not self.session_id and not message.properties.group_id: - raise ValueError("Message must have Session ID.") - return await super(SessionSender, self).schedule(schedule_time, *messages) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/base_handler.py deleted file mode 100644 index 31cdf72bf6ac..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/base_handler.py +++ /dev/null @@ -1,198 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import time -import logging -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - -from uamqp import AMQPClient -from uamqp import authentication -from uamqp import constants, errors -from uamqp.message import Message, MessageProperties - -from azure.servicebus.common.constants import ASSOCIATEDLINKPROPERTYNAME -from azure.servicebus.common.utils import create_properties -from azure.servicebus.common.errors import ( - _ServiceBusErrorPolicy, - InvalidHandlerState, - ServiceBusError, - ServiceBusConnectionError, - ServiceBusAuthorizationError) - - -_log = logging.getLogger(__name__) - - -class BaseHandler(object): # pylint: disable=too-many-instance-attributes - - def __init__(self, endpoint, auth_config, connection=None, encoding='UTF-8', debug=False, **kwargs): - self.running = False - self.error = None - self.endpoint = endpoint - self.entity = urlparse(endpoint).path.strip('/') - self.mgmt_target = self.entity + "/$management" - self.debug = debug - self.encoding = encoding - self.auth_config = auth_config - self.connection = connection - self.auto_reconnect = kwargs.pop('auto_reconnect', True) - self.properties = create_properties() - self.error_policy = kwargs.pop('error_policy', None) - self.handler_kwargs = kwargs - if not self.error_policy: - max_retries = kwargs.pop('max_message_retries', 3) - is_session = hasattr(self, 'session_id') - self.error_policy = _ServiceBusErrorPolicy(max_retries=max_retries, is_session=is_session) - self._handler = None - self._build_handler() - - def __enter__(self): - """Open the handler in a context manager.""" - self.open() - return self - - def __exit__(self, *args): - """Close the handler when exiting a context manager.""" - self.close() - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAuth.from_shared_access_key(**self.auth_config) - self._handler = AMQPClient( - self.endpoint, - auth=auth, - debug=self.debug, - properties=self.properties, - error_policy=self.error_policy, - encoding=self.encoding, - **self.handler_kwargs) - - def _mgmt_request_response(self, operation, message, callback, keep_alive_associated_link=True, **kwargs): - if not self.running: - raise InvalidHandlerState("Client connection is closed.") - - application_properties = {} - # Some mgmt calls do not support an associated link name. Most do, however, so on by default. - if keep_alive_associated_link: - try: - application_properties = {ASSOCIATEDLINKPROPERTYNAME:self._handler.message_handler.name} - except AttributeError: - pass - - mgmt_msg = Message( - body=message, - properties=MessageProperties( - reply_to=self.mgmt_target, - encoding=self.encoding, - **kwargs), - application_properties=application_properties) - try: - return self._handler.mgmt_request( - mgmt_msg, - operation, - op_type=b"entity-mgmt", - node=self.mgmt_target.encode(self.encoding), - timeout=5000, - callback=callback) - except Exception as exp: # pylint: disable=broad-except - raise ServiceBusError("Management request failed: {}".format(exp), exp) - - def _handle_exception(self, exception): - if isinstance(exception, (errors.LinkDetach, errors.ConnectionClose)): - if exception.action and exception.action.retry and self.auto_reconnect: - _log.info("Handler detached. Attempting reconnect.") - self.reconnect() - elif exception.condition == constants.ErrorCodes.UnauthorizedAccess: - _log.info("Handler detached. Shutting down.") - error = ServiceBusAuthorizationError(str(exception), exception) - self.close(exception=error) - raise error - else: - _log.info("Handler detached. Shutting down.") - error = ServiceBusConnectionError(str(exception), exception) - self.close(exception=error) - raise error - elif isinstance(exception, errors.MessageHandlerError): - if self.auto_reconnect: - _log.info("Handler error. Attempting reconnect.") - self.reconnect() - else: - _log.info("Handler error. Shutting down.") - error = ServiceBusConnectionError(str(exception), exception) - self.close(exception=error) - raise error - elif isinstance(exception, errors.AMQPConnectionError): - message = "Failed to open handler: {}".format(exception) - raise ServiceBusConnectionError(message, exception) - else: - _log.info("Unexpected error occurred (%r). Shutting down.", exception) - error = ServiceBusError("Handler failed: {}".format(exception)) - self.close(exception=error) - raise error - - def reconnect(self): - """Reconnect the handler. - - If the handler was disconnected from the service with - a retryable error - attempt to reconnect. - This method will be called automatically for most retryable errors. - """ - self._handler.close() - self.running = False - self._build_handler() - self.open() - - def open(self): - """Open handler connection and authenticate session. - - If the handler is already open, this operation will do nothing. - A handler opened with this method must be explicitly closed. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - - .. note:: This operation is not thread-safe. - - """ - if self.running: - return - self.running = True - try: - self._handler.open(connection=self.connection) - while not self._handler.client_ready(): - time.sleep(0.05) - except Exception as e: # pylint: disable=broad-except - try: - self._handle_exception(e) - except: - self.running = False - raise - - def close(self, exception=None): - """Close down the handler connection. - - If the handler has already closed, this operation will do nothing. An optional exception can be passed in to - indicate that the handler was shutdown due to error. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - - .. note:: This operation is not thread-safe. - - :param exception: An optional exception if the handler is closing - due to an error. - :type exception: Exception - """ - self.running = False - if self.error: - return - if isinstance(exception, ServiceBusError): - self.error = exception - elif exception: - self.error = ServiceBusError(str(exception)) - else: - self.error = ServiceBusError("This message handler is now closed.") - self._handler.close() diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/common/message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/common/message.py deleted file mode 100644 index df9410fb108f..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/common/message.py +++ /dev/null @@ -1,544 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import datetime -import uuid - -import uamqp -from uamqp import types - -from azure.servicebus.common.constants import DEADLETTERNAME -from azure.servicebus.common.errors import ( - MessageAlreadySettled, - MessageSettleFailed, - MessageLockExpired, - SessionLockExpired) - - -class Message(object): # pylint: disable=too-many-public-methods,too-many-instance-attributes - """A Service Bus Message. - - :param body: The data to send in a single message. The maximum size per message is 256 kB. - :type body: str or bytes - :param encoding: The encoding for string data. Default is UTF-8. - :type encoding: str - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START send_complex_message] - :end-before: [END send_complex_message] - :language: python - :dedent: 4 - :caption: Sending a message with additional properties - - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START receive_complex_message] - :end-before: [END receive_complex_message] - :language: python - :dedent: 4 - :caption: Checking the properties on a received message - - """ - - _X_OPT_ENQUEUED_TIME = b'x-opt-enqueued-time' - _X_OPT_SEQUENCE_NUMBER = b'x-opt-sequence-number' - _X_OPT_ENQUEUE_SEQUENCE_NUMBER = b'x-opt-enqueue-sequence-number' - _X_OPT_PARTITION_ID = b'x-opt-partition-id' - _X_OPT_PARTITION_KEY = b'x-opt-partition-key' - _X_OPT_VIA_PARTITION_KEY = b'x-opt-via-partition-key' - _X_OPT_LOCKED_UNTIL = b'x-opt-locked-until' - _x_OPT_LOCK_TOKEN = b'x-opt-lock-token' - _x_OPT_SCHEDULED_ENQUEUE_TIME = b'x-opt-scheduled-enqueue-time' - - def __init__(self, body, encoding='UTF-8', **kwargs): - subject = kwargs.pop('subject', None) - # Although we might normally thread through **kwargs this causes problems as MessageProperties won't absorb spurious args. - self.properties = uamqp.message.MessageProperties(encoding=encoding, subject=subject) - self.header = uamqp.message.MessageHeader() - self.received_timestamp = None - self.auto_renew_error = None - self._annotations = {} - self._app_properties = {} - self._encoding = encoding - self._expiry = None - self._receiver = None - if 'message' in kwargs: - self.message = kwargs['message'] - self._annotations = self.message.annotations - self._app_properties = self.message.application_properties - self.properties = self.message.properties - self.header = self.message.header - self.received_timestamp = datetime.datetime.now() - else: - self._build_message(body) - - def __str__(self): - return str(self.message) - - def _build_message(self, body): - if isinstance(body, list) and body: # TODO: This only works for a list of bytes/strings - self.message = uamqp.Message(body[0], properties=self.properties, header=self.header) - for more in body[1:]: - self.message._body.append(more) # pylint: disable=protected-access - elif body is None: - raise ValueError("Message body cannot be None.") - else: - self.message = uamqp.Message(body, properties=self.properties, header=self.header) - - def _is_live(self, action): - # pylint: disable=no-member - if self.settled: - raise MessageAlreadySettled(action) - try: - if self.expired: - raise MessageLockExpired(inner_exception=self.auto_renew_error) - except TypeError: - pass - if hasattr(self._receiver, 'expired') and self._receiver.expired: - raise SessionLockExpired(inner_exception=self._receiver.auto_renew_error) - - @property - def settled(self): - """Whether the message has been settled. - - This will aways be `True` for a message received using ReceiveAndDelete mode, - otherwise it will be `False` until the message is completed or otherwise settled. - - :rtype: bool - """ - return self.message.settled - - @property - def annotations(self): - """The annotations of the message. - - :rtype: dict - """ - return self.message.annotations - - @annotations.setter - def annotations(self, value): - """Set the annotations on the message. - - :param value: The annotations for the Message. - :type value: dict - """ - self.message.annotations = value - - @property - def user_properties(self): - """User defined properties on the message. - - :rtype: dict - """ - return self.message.application_properties - - @user_properties.setter - def user_properties(self, value): - """User defined properties on the message. - - :param value: The application properties for the Message. - :type value: dict - """ - self.message.application_properties = value - - @property - def enqueued_time(self): - if self.message.annotations: - timestamp = self.message.annotations.get(self._X_OPT_ENQUEUED_TIME) - if timestamp: - in_seconds = timestamp/1000.0 - return datetime.datetime.utcfromtimestamp(in_seconds) - return None - - @property - def scheduled_enqueue_time(self): - if self.message.annotations: - timestamp = self.message.annotations.get(self._x_OPT_SCHEDULED_ENQUEUE_TIME) - if timestamp: - in_seconds = timestamp/1000.0 - return datetime.datetime.utcfromtimestamp(in_seconds) - return None - - @property - def sequence_number(self): - if self.message.annotations: - return self.message.annotations.get(self._X_OPT_SEQUENCE_NUMBER) - return None - - @property - def enqueue_sequence_number(self): - if self.message.annotations: - return self.message.annotations.get(self._X_OPT_ENQUEUE_SEQUENCE_NUMBER) - return None - - @enqueue_sequence_number.setter - def enqueue_sequence_number(self, value): - if not self.message.annotations: - self.message.annotations = {} - self.message.annotations[types.AMQPSymbol(self._X_OPT_ENQUEUE_SEQUENCE_NUMBER)] = value - - @property - def partition_id(self): - if self.message.annotations: - return self.message.annotations.get(self._X_OPT_PARTITION_ID) - return None - - @property - def partition_key(self): - if self.message.annotations: - return self.message.annotations.get(self._X_OPT_PARTITION_KEY) - return None - - @partition_key.setter - def partition_key(self, value): - if not self.message.annotations: - self.message.annotations = {} - self.message.annotations[types.AMQPSymbol(self._X_OPT_PARTITION_KEY)] = value - - @property - def via_partition_key(self): - if self.message.annotations: - return self.message.annotations.get(self._X_OPT_VIA_PARTITION_KEY) - return None - - @via_partition_key.setter - def via_partition_key(self, value): - if not self.message.annotations: - self.message.annotations = {} - self.message.annotations[types.AMQPSymbol(self._X_OPT_VIA_PARTITION_KEY)] = value - - @property - def locked_until(self): - if hasattr(self._receiver, 'locked_until') or self.settled: - return None - if self._expiry: - return self._expiry - if self.message.annotations and self._X_OPT_LOCKED_UNTIL in self.message.annotations: - expiry_in_seconds = self.message.annotations[self._X_OPT_LOCKED_UNTIL]/1000 - self._expiry = datetime.datetime.fromtimestamp(expiry_in_seconds) - return self._expiry - - @property - def expired(self): - if hasattr(self._receiver, 'locked_until'): - raise TypeError("Session messages do not expire. Please use the Session expiry instead.") - if self.locked_until and self.locked_until <= datetime.datetime.now(): - return True - return False - - @property - def lock_token(self): - if hasattr(self._receiver, 'locked_until') or self.settled: - return None - if hasattr(self.message, 'delivery_tag') and self.message.delivery_tag: - return uuid.UUID(bytes_le=self.message.delivery_tag) - - delivery_annotations = self.message.delivery_annotations - if delivery_annotations: - return delivery_annotations.get(self._x_OPT_LOCK_TOKEN) - return None - - @property - def session_id(self): - try: - return self.properties.group_id.decode('UTF-8') - except (AttributeError, UnicodeDecodeError): - return self.properties.group_id - - @session_id.setter - def session_id(self, value): - self.properties.group_id = value - - @property - def time_to_live(self): - if self.header and self.header.time_to_live: - return datetime.timedelta(milliseconds=self.header.time_to_live) - return None - - @time_to_live.setter - def time_to_live(self, value): - if not self.header: - self.header = uamqp.message.MessageHeader() - if isinstance(value, datetime.timedelta): - self.header.time_to_live = value.seconds * 1000 - else: - self.header.time_to_live = int(value) * 1000 - - @property - def body(self): - """The body of the Message. - - :rtype: bytes or generator[bytes] - """ - return self.message.get_data() - - def schedule(self, schedule_time): - """Add a specific enqueue time to the message. - - :param schedule_time: The scheduled time to enqueue the message. - :type schedule_time: ~datetime.datetime - """ - if not self.properties.message_id: - self.properties.message_id = str(uuid.uuid4()) - if not self.message.annotations: - self.message.annotations = {} - self.message.annotations[types.AMQPSymbol(self._x_OPT_SCHEDULED_ENQUEUE_TIME)] = schedule_time - - def renew_lock(self): - """Renew the message lock. - - This will maintain the lock on the message to ensure - it is not returned to the queue to be reprocessed. In order to complete (or otherwise settle) - the message, the lock must be maintained. Messages received via ReceiveAndDelete mode are not - locked, and therefore cannot be renewed. This operation can also be performed as a threaded - background task by registering the message with an `azure.servicebus.AutoLockRenew` instance. - This operation is only available for non-sessionful messages. - - :raises: TypeError if the message is sessionful. - :raises: ~azure.servicebus.common.errors.MessageLockExpired is message lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled is message has already been settled. - """ - if hasattr(self._receiver, 'locked_until'): - raise TypeError("Session messages cannot be renewed. Please renew the Session lock instead.") - self._is_live('renew') - token = self.lock_token - if not token: - raise ValueError("Unable to renew lock - no lock token found.") - - expiry = self._receiver._renew_locks(token) # pylint: disable=protected-access - self._expiry = datetime.datetime.fromtimestamp(expiry[b'expirations'][0]/1000.0) - - def complete(self): - """Complete the message. - - This removes the message from the queue. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('complete') - try: - self.message.accept() - except Exception as e: - raise MessageSettleFailed("complete", e) - - def dead_letter(self, description=None): - """Move the message to the Dead Letter queue. - - The Dead Letter queue is a sub-queue that can be - used to store messages that failed to process correctly, or otherwise require further inspection - or processing. The queue can also be configured to send expired messages to the Dead Letter queue. - To receive dead-lettered messages, use `QueueClient.get_deadletter_receiver()` or - `SubscriptionClient.get_deadletter_receiver()`. - - :param description: The reason for dead-lettering the message. - :type description: str - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('reject') - try: - self.message.reject(condition=DEADLETTERNAME, description=description) - except Exception as e: - raise MessageSettleFailed("reject", e) - - def abandon(self): - """Abandon the message. - - This message will be returned to the queue to be reprocessed. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('abandon') - try: - self.message.modify(True, False) - except Exception as e: - raise MessageSettleFailed("abandon", e) - - def defer(self): - """Defer the message. - - This message will remain in the queue but must be received - specifically by its sequence number in order to be processed. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('defer') - try: - self.message.modify(True, True) - except Exception as e: - raise MessageSettleFailed("defer", e) - - -class BatchMessage(Message): - """A batch of messages combined into a single message body. - - The body of the messages in the batch should be supplied by an iterable, - such as a generator. - If the contents of the iterable exceeds the maximum size of a single message (256 kB), - the data will be broken up across multiple messages. - - :param body: The data to send in each message in the batch. The maximum size per message is 256 kB. - If data is supplied in excess of this limit, multiple messages will be sent. - :type body: Iterable - :param encoding: The encoding for string data. Default is UTF-8. - :type encoding: str - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START send_batch_message] - :end-before: [END send_batch_message] - :language: python - :dedent: 4 - :caption: Send a batched message. - - """ - - def _build_message(self, body): - if body is None: - raise ValueError("Message body cannot be None.") - else: - self.message = uamqp.BatchMessage( - data=body, multi_messages=True, properties=self.properties, header=self.header) - - -class PeekMessage(Message): - """A preview message. - - This message is still on the queue, and unlocked. - A peeked message cannot be completed, abandoned, dead-lettered or deferred. - It has no lock token or expiry. - - """ - - def __init__(self, message): - super(PeekMessage, self).__init__(None, message=message) - - @property - def locked_until(self): - raise TypeError("Peeked message is not locked.") - - @property - def lock_token(self): - raise TypeError("Peeked message is not locked.") - - def renew_lock(self): - """A PeekMessage cannot be renewed. Raises `TypeError`.""" - raise TypeError("Peeked message is not locked.") - - def complete(self): - """A PeekMessage cannot be completed Raises `TypeError`.""" - raise TypeError("Peeked message cannot be completed.") - - def dead_letter(self, description=None): - """A PeekMessage cannot be dead-lettered. Raises `TypeError`.""" - raise TypeError("Peeked message cannot be dead-lettered.") - - def abandon(self): - """A PeekMessage cannot be abandoned. Raises `TypeError`.""" - raise TypeError("Peeked message cannot be abandoned.") - - def defer(self): - """A PeekMessage cannot be deferred. Raises `TypeError`.""" - raise TypeError("Peeked message cannot be deferred.") - - -class DeferredMessage(Message): - """A message that has been deferred. - - A deferred message can be completed, - abandoned, or dead-lettered, however it cannot be deferred again. - - """ - - def __init__(self, message, mode): - self._settled = mode == 0 - super(DeferredMessage, self).__init__(None, message=message) - - def _is_live(self, action): - if not self._receiver: - raise ValueError("Orphan message had no open connection.") - super(DeferredMessage, self)._is_live(action) - - @property - def lock_token(self): - if self.settled: - return None - delivery_annotations = self.message.delivery_annotations - if delivery_annotations: - return delivery_annotations.get(self._x_OPT_LOCK_TOKEN) - return None - - @property - def settled(self): - return self._settled - - def complete(self): - """Complete the message. - - This removes the message from the queue. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('complete') - self._receiver._settle_deferred('completed', [self.lock_token]) # pylint: disable=protected-access - self._settled = True - - def dead_letter(self, description=None): - """Move the message to the Dead Letter queue. - - The Dead Letter queue is a sub-queue that can be - used to store messages that failed to process correctly, or otherwise require further inspection - or processing. The queue can also be configured to send expired messages to the Dead Letter queue. - To receive dead-lettered messages, use `QueueClient.get_deadletter_receiver()` or - `SubscriptionClient.get_deadletter_receiver()`. - - :param description: The reason for dead-lettering the message. - :type description: str - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('dead-letter') - details = { - 'deadletter-reason': str(description) if description else "", - 'deadletter-description': str(description) if description else ""} - self._receiver._settle_deferred( # pylint: disable=protected-access - 'suspended', [self.lock_token], dead_letter_details=details) - self._settled = True - - def abandon(self): - """Abandon the message. - - This message will be returned to the queue to be reprocessed. - - :raises: ~azure.servicebus.common.errors.MessageAlreadySettled if the message has been settled. - :raises: ~azure.servicebus.common.errors.MessageLockExpired if message lock has already expired. - :raises: ~azure.servicebus.common.errors.SessionLockExpired if session lock has already expired. - :raises: ~azure.servicebus.common.errors.MessageSettleFailed if message settle operation fails. - """ - self._is_live('abandon') - self._receiver._settle_deferred('abandoned', [self.lock_token]) # pylint: disable=protected-access - self._settled = True - - def defer(self): - """A DeferredMessage cannot be deferred. Raises `ValueError`.""" - raise ValueError("Message is already deferred.") diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/common/errors.py b/sdk/servicebus/azure-servicebus/azure/servicebus/exceptions.py similarity index 71% rename from sdk/servicebus/azure-servicebus/azure/servicebus/common/errors.py rename to sdk/servicebus/azure-servicebus/azure/servicebus/exceptions.py index c716a367001c..a13da02a9c29 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/common/errors.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/exceptions.py @@ -6,6 +6,8 @@ from uamqp import errors, constants +from ._common.constants import SESSION_LOCK_LOST, SESSION_LOCK_TIMEOUT + _NO_RETRY_ERRORS = ( constants.ErrorCodes.DecodeError, @@ -60,6 +62,56 @@ def _error_handler(error): return errors.ErrorAction(retry=True) +def _create_servicebus_exception(logger, exception, handler): + error_need_close_handler = True + error_need_raise = False + + if isinstance(exception, MessageSendFailed): + logger.info("Message send error (%r)", exception) + error, error_need_close_handler, error_need_raise = exception, False, True + elif isinstance(exception, errors.LinkDetach) and exception.condition == SESSION_LOCK_LOST: + try: + session_id = handler._session_id # pylint: disable=protected-access + except AttributeError: + session_id = None + error = SessionLockExpired("Connection detached - lock on Session {} lost.".format(session_id)) + error_need_raise = True + elif isinstance(exception, errors.LinkDetach) and exception.condition == SESSION_LOCK_TIMEOUT: + error = NoActiveSession("Queue has no active session to receive from.") + error_need_raise = True + elif isinstance(exception, errors.AuthenticationException): + logger.info("Authentication failed due to exception: (%r).", exception) + error = ServiceBusAuthorizationError(str(exception), exception) + elif isinstance(exception, (errors.LinkDetach, errors.ConnectionClose)): + logger.info("Handler detached due to exception: (%r).", exception) + if exception.condition == constants.ErrorCodes.UnauthorizedAccess: + error = ServiceBusAuthorizationError(str(exception), exception) + else: + error = ServiceBusConnectionError(str(exception), exception) + elif isinstance(exception, errors.MessageHandlerError): + logger.info("Handler error: (%r).", exception) + error = ServiceBusConnectionError(str(exception), exception) + elif isinstance(exception, errors.AMQPConnectionError): + logger.info("Failed to open handler: (%r).", exception) + message = "Failed to open handler: {}.".format(exception) + error = ServiceBusConnectionError(message, exception) + error_need_raise, error_need_close_handler = True, False + else: + logger.info("Unexpected error occurred (%r). Shutting down.", exception) + error = exception + if not isinstance(exception, ServiceBusError): + error = ServiceBusError("Handler failed: {}.".format(exception)) + + try: + err_condition = exception.condition + if err_condition in _NO_RETRY_ERRORS: + error_need_raise = True + except AttributeError: + pass + + return error, error_need_close_handler, error_need_raise + + class _ServiceBusErrorPolicy(errors.ErrorPolicy): def __init__(self, max_retries=3, is_session=False): @@ -115,6 +167,10 @@ class NoActiveSession(ServiceBusError): """No active Sessions are available to receive from.""" +class OperationTimeoutError(ServiceBusError): + """Operation timed out.""" + + class MessageAlreadySettled(ServiceBusError): """Failed to settle the message. diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/receive_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/receive_handler.py deleted file mode 100644 index c63e212b0498..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/receive_handler.py +++ /dev/null @@ -1,703 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import threading -import time -import datetime -import functools -import uuid - -import six - -from uamqp import ReceiveClient -from uamqp import authentication -from uamqp import constants, types, errors - -from azure.servicebus.common.message import Message -from azure.servicebus.common import mgmt_handlers, mixins -from azure.servicebus.base_handler import BaseHandler -from azure.servicebus.common.errors import ( - InvalidHandlerState, - NoActiveSession, - SessionLockExpired) -from azure.servicebus.common.constants import ( - SESSION_LOCK_LOST, - SESSION_LOCK_TIMEOUT, - REQUEST_RESPONSE_RENEWLOCK_OPERATION, - REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, - REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, - REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, - REQUEST_RESPONSE_PEEK_OPERATION, - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - ReceiveSettleMode) - - -class Receiver(BaseHandler): # pylint: disable=too-many-instance-attributes - """A message receiver. - - This receive handler acts as an iterable message stream for retrieving - messages for a Service Bus entity. It operates a single connection that must be opened and - closed on completion. The service connection will remain open for the entirety of the iterator. - If you find yourself only partially iterating the message stream, you should run the receiver - in a `with` statement to ensure the connection is closed. - The Receiver should not be instantiated directly, and should be accessed from a `QueueClient` or - `SubscriptionClient` using the `get_receiver()` method. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Receiver. - :type handler_id: str - :param source: The endpoint from which to receive messages. - :type source: ~uamqp.Source - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param mode: The receive connection mode. Value must be either PeekLock or ReceiveAndDelete. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_receiver] - :end-before: [END get_receiver] - :language: python - :dedent: 4 - :caption: Get the receiver client from Service Bus client - - """ - - def __init__( - self, handler_id, source, auth_config, connection=None, - mode=ReceiveSettleMode.PeekLock, encoding='UTF-8', debug=False, **kwargs): - self._used = threading.Event() - self.name = "SBReceiver-{}".format(handler_id) - self.last_received = None - self.mode = mode - self.message_iter = None - super(Receiver, self).__init__( - source, auth_config, connection=connection, encoding=encoding, debug=debug, **kwargs) - - def __iter__(self): - return self - - def __next__(self): - self._can_run() - while True: - if self.receiver_shutdown: - self.close() - raise StopIteration - try: - received = next(self.message_iter) - wrapped = self._build_message(received) - return wrapped - except StopIteration: - self.close() - raise - except Exception as e: # pylint: disable=broad-except - self._handle_exception(e) - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAuth.from_shared_access_key(**self.auth_config) - self._handler = ReceiveClient( - self.endpoint, - auth=auth, - debug=self.debug, - properties=self.properties, - error_policy=self.error_policy, - client_name=self.name, - auto_complete=False, - encoding=self.encoding, - **self.handler_kwargs) - - def _build_message(self, received): - message = Message(None, message=received) - message._receiver = self # pylint: disable=protected-access - self.last_received = message.sequence_number - return message - - def _can_run(self): - if self._used.is_set(): - raise InvalidHandlerState("Receiver has already closed.") - if self.receiver_shutdown: - self.close() - raise InvalidHandlerState("Receiver has already closed.") - if not self.running: - self.open() - - def _renew_locks(self, *lock_tokens): - message = {'lock-tokens': types.AMQPArray(lock_tokens)} - return self._mgmt_request_response( - REQUEST_RESPONSE_RENEWLOCK_OPERATION, - message, - mgmt_handlers.lock_renew_op) - - def _settle_deferred(self, settlement, lock_tokens, dead_letter_details=None): - message = { - 'disposition-status': settlement, - 'lock-tokens': types.AMQPArray(lock_tokens)} - if dead_letter_details: - message.update(dead_letter_details) - return self._mgmt_request_response( - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - message, - mgmt_handlers.default) - - def _build_receiver(self): - """This is a temporary patch pending a fix in uAMQP.""" - # pylint: disable=protected-access - self._handler.message_handler = self._handler.receiver_type( - self._handler._session, - self._handler._remote_address, - self._handler._name, - on_message_received=self._handler._message_received, - name='receiver-link-{}'.format(uuid.uuid4()), - debug=self._handler._debug_trace, - prefetch=self._handler._prefetch, - max_message_size=self._handler._max_message_size, - properties=self._handler._link_properties, - error_policy=self._handler._error_policy, - encoding=self._handler._encoding) - if self.mode != ReceiveSettleMode.PeekLock: - self._handler.message_handler.send_settle_mode = constants.SenderSettleMode.Settled - self._handler.message_handler.receive_settle_mode = constants.ReceiverSettleMode.ReceiveAndDelete - self._handler.message_handler._settle_mode = constants.ReceiverSettleMode.ReceiveAndDelete - self._handler.message_handler.open() - - def next(self): - return self.__next__() - - @property - def receiver_shutdown(self): - if self._handler: - return self._handler._shutdown # pylint: disable=protected-access - return True - - @receiver_shutdown.setter - def receiver_shutdown(self, value): - if self._handler: - self._handler._shutdown = value # pylint: disable=protected-access - else: - raise ValueError("Receiver has no AMQP handler") - - @property - def queue_size(self): - """The current size of the unprocessed message queue. - - :rtype: int - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START queue_size] - :end-before: [END queue_size] - :language: python - :dedent: 4 - :caption: Get the number of unprocessed messages in the queue - - """ - # pylint: disable=protected-access - if self._handler._received_messages: - return self._handler._received_messages.qsize() - return 0 - - def peek(self, count=1, start_from=None): - """Browse messages currently pending in the queue. - - Peeked messages are not removed from queue, nor are they locked. They cannot be completed, - deferred or dead-lettered. - - :param count: The maximum number of messages to try and peek. The default - value is 1. - :type count: int - :param start_from: A message sequence number from which to start browsing messages. - :type start_from: int - :rtype: list[~azure.servicebus.common.message.PeekMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START peek_messages] - :end-before: [END peek_messages] - :language: python - :dedent: 4 - :caption: Look at pending messages in the queue - - """ - if not start_from: - start_from = self.last_received or 1 - if int(count) < 1: - raise ValueError("count must be 1 or greater.") - if int(start_from) < 1: - raise ValueError("start_from must be 1 or greater.") - - self._can_run() - message = { - 'from-sequence-number': types.AMQPLong(start_from), - 'message-count': count - } - return self._mgmt_request_response( - REQUEST_RESPONSE_PEEK_OPERATION, - message, - mgmt_handlers.peek_op) - - def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock): - """Receive messages that have previously been deferred. - - When receiving deferred messages from a partitioned entity, all of the supplied - sequence numbers must be messages from the same partition. - - :param sequence_numbers: A list of the sequence numbers of messages that have been - deferred. - :type sequence_numbers: list[int] - :param mode: The receive mode, default value is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :rtype: list[~azure.servicebus.common.message.DeferredMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START receive_deferred_messages] - :end-before: [END receive_deferred_messages] - :language: python - :dedent: 8 - :caption: Get the messages which were previously deferred - - """ - if not sequence_numbers: - raise ValueError("At least one sequence number must be specified.") - self._can_run() - try: - receive_mode = mode.value.value - except AttributeError: - receive_mode = int(mode) - message = { - 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), - 'receiver-settle-mode': types.AMQPuInt(receive_mode) - } - handler = functools.partial(mgmt_handlers.deferred_message_op, mode=receive_mode) - messages = self._mgmt_request_response( - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - message, - handler) - for m in messages: - m._receiver = self # pylint: disable=protected-access - return messages - - def open(self): - """Open receiver connection and authenticate session. - - If the receiver is already open, this operation will do nothing. - This method will be called automatically when one starts to iterate - messages in the receiver, so there should be no need to call it directly. - A receiver opened with this method must be explicitly closed. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - - .. note:: This operation is not thread-safe. - - """ - if self.running: - return - self.running = True - try: - self._handler.open(connection=self.connection) - self.message_iter = self._handler.receive_messages_iter() - while not self._handler.auth_complete(): - time.sleep(0.05) - self._build_receiver() - while not self._handler.client_ready(): - time.sleep(0.05) - except Exception as e: # pylint: disable=broad-except - try: - self._handle_exception(e) - except: - self.running = False - raise - - def close(self, exception=None): - """Close down the receiver connection. - - If the receiver has already closed, this operation will do nothing. An optional exception can be passed in to - indicate that the handler was shutdown due to error. - It is recommended to open a handler within a context manager as - opposed to calling the method directly. - The receiver will be implicitly closed on completion of the message iterator, - however this method will need to be called explicitly if the message iterator is not run - to completion. - - .. note:: This operation is not thread-safe. - - :param exception: An optional exception if the handler is closing - due to an error. - :type exception: Exception - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START open_close_receiver_connection] - :end-before: [END open_close_receiver_connection] - :language: python - :dedent: 4 - :caption: Close the connection and shutdown the receiver - - """ - if not self.running: - return - self.running = False - self.receiver_shutdown = True - self._used.set() - super(Receiver, self).close(exception=exception) - - def fetch_next(self, max_batch_size=None, timeout=None): - """Receive a batch of messages at once. - - This approach it optimal if you wish to process multiple messages simultaneously. Note that the - number of messages retrieved in a single batch will be dependent on - whether `prefetch` was set for the receiver. This call will prioritize returning - quickly over meeting a specified batch size, and so will return as soon as at least - one message is received and there is a gap in incoming messages regardless - of the specified batch size. - - :param max_batch_size: Maximum number of messages in the batch. Actual number - returned will depend on prefetch size and incoming stream rate. - :type max_batch_size: int - :param timeout: The time to wait in seconds for the first message to arrive. - If no messages arrive, and no timeout is specified, this call will not return - until the connection is closed. If specified, an no messages arrive within the - timeout period, an empty list will be returned. - :rtype: list[~azure.servicebus.common.message.Message] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START fetch_next_messages] - :end-before: [END fetch_next_messages] - :language: python - :dedent: 4 - :caption: Get the messages in batch from the receiver - - """ - self._can_run() - wrapped_batch = [] - max_batch_size = max_batch_size or self._handler._prefetch # pylint: disable=protected-access - try: - timeout_ms = 1000 * timeout if timeout else 0 - batch = self._handler.receive_message_batch( - max_batch_size=max_batch_size, - timeout=timeout_ms) - for received in batch: - message = self._build_message(received) - wrapped_batch.append(message) - except Exception as e: # pylint: disable=broad-except - self._handle_exception(e) - return wrapped_batch - - -class SessionReceiver(Receiver, mixins.SessionMixin): - """A session message receiver. - - This receive handler acts as an iterable message stream for retrieving - messages for a sessionful Service Bus entity. It operates a single connection that must be opened and - closed on completion. The service connection will remain open for the entirety of the iterator. - If you find yourself only partially iterating the message stream, you should run the receiver - in a `with` statement to ensure the connection is closed. - The Receiver should not be instantiated directly, and should be accessed from a `QueueClient` or - `SubscriptionClient` using the `get_receiver()` method. - When receiving messages from a session, connection errors that would normally be automatically - retried will instead raise an error due to the loss of the lock on a particular session. - A specific session can be specified, or the receiver can retrieve any available session using - the `NEXT_AVAILABLE` constant. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Receiver. - :type handler_id: str - :param source: The endpoint from which to receive messages. - :type source: ~uamqp.Source - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param session: The ID of the session to receive from. - :type session: str or ~azure.servicebus.common.constants.NEXT_AVAILABLE - :param loop: An async event loop - :type loop: ~asyncio.EventLoop - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param mode: The receive connection mode. Value must be either PeekLock or ReceiveAndDelete. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_session_receiver_client] - :end-before: [END create_session_receiver_client] - :language: python - :dedent: 4 - :caption: Running a session receiver within a context manager. - - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_receiver_session_nextavailable] - :end-before: [END create_receiver_session_nextavailable] - :language: python - :dedent: 4 - :caption: Running a session receiver for the next available session. - - """ - - def __init__( - self, handler_id, source, auth_config, session=None, - connection=None, encoding='UTF-8', debug=False, **kwargs): - self.session_id = None - self.session_filter = session - self.locked_until = None - self.session_start = None - self.auto_reconnect = False - self.auto_renew_error = None - super(SessionReceiver, self).__init__( - handler_id, source, auth_config, - connection=connection, encoding=encoding, debug=debug, **kwargs) - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAuth.from_shared_access_key(**self.auth_config) - self._handler = ReceiveClient( - self._get_source(), - auth=auth, - debug=self.debug, - properties=self.properties, - error_policy=self.error_policy, - client_name=self.name, - on_attach=self._on_attach, - auto_complete=False, - encoding=self.encoding, - **self.handler_kwargs) - - def _can_run(self): - super(SessionReceiver, self)._can_run() - if self.expired: - raise SessionLockExpired(inner_exception=self.auto_renew_error) - - def _handle_exception(self, exception): - if isinstance(exception, errors.LinkDetach) and exception.condition == SESSION_LOCK_LOST: - error = SessionLockExpired("Connection detached - lock on Session {} lost.".format(self.session_id)) - self.close(exception=error) - raise error - elif isinstance(exception, errors.LinkDetach) and exception.condition == SESSION_LOCK_TIMEOUT: - error = NoActiveSession("Queue has no active session to receive from.") - self.close(exception=error) - raise error - return super(SessionReceiver, self)._handle_exception(exception) - - def _settle_deferred(self, settlement, lock_tokens, dead_letter_details=None): - message = { - 'disposition-status': settlement, - 'lock-tokens': types.AMQPArray(lock_tokens), - 'session-id': self.session_id} - if dead_letter_details: - message.update(dead_letter_details) - return self._mgmt_request_response( - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - message, - mgmt_handlers.default) - - def get_session_state(self): - """Get the session state. - - Returns None if no state has been set. - - :rtype: str - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_session_state] - :end-before: [END get_session_state] - :language: python - :dedent: 4 - :caption: Get the session state of the receiver - - """ - self._can_run() - response = self._mgmt_request_response( - REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, - {'session-id': self.session_id}, - mgmt_handlers.default) - session_state = response.get(b'session-state') - if isinstance(session_state, six.binary_type): - session_state = session_state.decode('UTF-8') - return session_state - - def set_session_state(self, state): - """Set the session state. - - :param state: The state value. - :type state: str, bytes or bytearray - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START set_session_state] - :end-before: [END set_session_state] - :language: python - :dedent: 4 - :caption: Set the session state of the receiver - - """ - self._can_run() - state = state.encode(self.encoding) if isinstance(state, six.text_type) else state - return self._mgmt_request_response( - REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, - {'session-id': self.session_id, 'session-state': bytearray(state)}, - mgmt_handlers.default) - - def renew_lock(self): - """Renew the session lock. - - This operation must be performed periodically in order to retain a lock on the - session to continue message processing. - Once the lock is lost the connection will be closed. This operation can - also be performed as a threaded background task by registering the session - with an `azure.servicebus.AutoLockRenew` instance. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START renew_lock] - :end-before: [END renew_lock] - :language: python - :dedent: 4 - :caption: Renew the session lock before it expires - - """ - self._can_run() - expiry = self._mgmt_request_response( - REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, - {'session-id': self.session_id}, - mgmt_handlers.default) - self.locked_until = datetime.datetime.fromtimestamp(expiry[b'expiration']/1000.0) - - def peek(self, count=1, start_from=None): - """Browse messages currently pending in the queue. - - Peeked messages are not removed from queue, nor are they locked. They cannot be completed, - deferred or dead-lettered. - This operation will only peek pending messages in the current session. - - :param count: The maximum number of messages to try and peek. The default - value is 1. - :type count: int - :param start_from: A message sequence number from which to start browsing messages. - :type start_from: int - :rtype: list[~azure.servicebus.common.message.PeekMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START peek_messages] - :end-before: [END peek_messages] - :language: python - :dedent: 4 - :caption: Look at pending messages in the queue - - """ - if not start_from: - start_from = self.last_received or 1 - if int(count) < 1: - raise ValueError("count must be 1 or greater.") - if int(start_from) < 1: - raise ValueError("start_from must be 1 or greater.") - - self._can_run() - message = { - 'from-sequence-number': types.AMQPLong(start_from), - 'message-count': count, - 'session-id': self.session_id} - return self._mgmt_request_response( - REQUEST_RESPONSE_PEEK_OPERATION, - message, - mgmt_handlers.peek_op) - - def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock): - """Receive messages that have previously been deferred. - - This operation can only receive deferred messages from the current session. - When receiving deferred messages from a partitioned entity, all of the supplied - sequence numbers must be messages from the same partition. - - :param sequence_numbers: A list of the sequence numbers of messages that have been - deferred. - :type sequence_numbers: list[int] - :param mode: The receive mode, default value is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :rtype: list[~azure.servicebus.common.message.DeferredMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START receive_deferred_messages] - :end-before: [END receive_deferred_messages] - :language: python - :dedent: 4 - :caption: Get the messages which were previously deferred in the session - - """ - if not sequence_numbers: - raise ValueError("At least one sequence number must be specified.") - self._can_run() - try: - receive_mode = mode.value.value - except AttributeError: - receive_mode = int(mode) - message = { - 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), - 'receiver-settle-mode': types.AMQPuInt(receive_mode), - 'session-id': self.session_id - } - handler = functools.partial(mgmt_handlers.deferred_message_op, mode=receive_mode) - messages = self._mgmt_request_response( - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - message, - handler) - for m in messages: - m._receiver = self # pylint: disable=protected-access - return messages - - def list_sessions(self, updated_since=None, max_results=100, skip=0): - """List session IDs. - - List the Session IDs with pending messages in the queue where the state of the session - has been updated since the timestamp provided. If no timestamp is provided, all will be returned. - If the state of a Session has never been set, it will not be returned regardless of whether - there are messages pending. - - :param updated_since: The UTC datetime from which to return updated pending Session IDs. - :type updated_since: datetime.datetime - :param max_results: The maximum number of Session IDs to return. Default value is 100. - :type max_results: int - :param skip: The page value to jump to. Default value is 0. - :type skip: int - :rtype: list[str] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START list_sessions] - :end-before: [END list_sessions] - :language: python - :dedent: 4 - :caption: List the ids of sessions with pending messages - - """ - if int(max_results) < 1: - raise ValueError("max_results must be 1 or greater.") - - self._can_run() - message = { - 'last-updated-time': updated_since or datetime.datetime.utcfromtimestamp(0), - 'skip': skip, - 'top': max_results, - } - return self._mgmt_request_response( - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, - message, - mgmt_handlers.list_sessions_op, - keep_alive_associated_link=False) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/send_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/send_handler.py deleted file mode 100644 index 9ac08ff41522..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/send_handler.py +++ /dev/null @@ -1,314 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import logging - -from uamqp import SendClient -from uamqp import authentication -from uamqp import constants, types - -from azure.servicebus.base_handler import BaseHandler -from azure.servicebus.common.errors import MessageSendFailed -from azure.servicebus.common import mgmt_handlers, mixins -from azure.servicebus.common.message import Message -from azure.servicebus.common.constants import ( - REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, - REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION) - - -_log = logging.getLogger(__name__) - - -class Sender(BaseHandler, mixins.SenderMixin): - """A message sender. - - This handler is for sending messages to a Service Bus entity. - It operates a single connection that must be opened and closed on completion. - The Sender can be run within a context manager to ensure that the connection is closed on exit. - The Sender should not be instantiated directly, and should be accessed from a `QueueClient` or - `TopicClient` using the `get_sender()` method. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Sender. - :type handler_id: str - :param target: The endpoint to send messages to. - :type target: ~uamqp.Target - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param session: An optional session ID. If supplied, all outgoing messages will have this - session ID added (unless they already have one specified). - :type session: str - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_sender_client] - :end-before: [END create_sender_client] - :language: python - :dedent: 4 - :caption: Create a new instance of the Sender - - """ - - def __init__( - self, handler_id, target, auth_config, session=None, - connection=None, encoding='UTF-8', debug=False, **kwargs): - self.name = "SBSender-{}".format(handler_id) - self.session_id = session - super(Sender, self).__init__( - target, auth_config, connection=connection, encoding=encoding, debug=debug, **kwargs) - - def _build_handler(self): - auth = None if self.connection else authentication.SASTokenAuth.from_shared_access_key(**self.auth_config) - self._handler = SendClient( - self.endpoint, - auth=auth, - debug=self.debug, - properties=self.properties, - client_name=self.name, - error_policy=self.error_policy, - encoding=self.encoding, - **self.handler_kwargs) - - def send(self, message): - """Send a message and blocks until acknowledgement is received or the operation fails. - - :param message: The message to be sent. - :type message: ~azure.servicebus.common.message.Message - :raises: ~azure.servicebus.common.errors.MessageSendFailed if the message fails to send. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START send_message] - :end-before: [END send_message] - :language: python - :dedent: 4 - :caption: Send a message and block - - """ - if not isinstance(message, Message): - raise TypeError("Value of message must be of type 'Message'.") - if not self.running: - self.open() - if self.session_id and not message.properties.group_id: - message.properties.group_id = self.session_id - try: - self._handler.send_message(message.message) - except Exception as e: - raise MessageSendFailed(e) - - def schedule(self, schedule_time, *messages): - """Send one or more messages to be enqueued at a specific time. - - Returns a list of the sequence numbers of the enqueued messages. - - :param schedule_time: The date and time to enqueue the messages. - :type schedule_time: ~datetime.datetime - :param messages: The messages to schedule. - :type messages: ~azure.servicebus.common.message.Message - :rtype: list[int] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START scheduling_messages] - :end-before: [END scheduling_messages] - :language: python - :dedent: 4 - :caption: Schedule a message to be sent in future - - """ - if not self.running: - self.open() - request_body = self._build_schedule_request(schedule_time, *messages) - return self._mgmt_request_response( - REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, - request_body, - mgmt_handlers.schedule_op) - - def cancel_scheduled_messages(self, *sequence_numbers): - """Cancel one or more messages that have previsouly been scheduled and are still pending. - - :param sequence_numbers: The seqeuence numbers of the scheduled messages. - :type sequence_numbers: int - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START cancel_scheduled_messages] - :end-before: [END cancel_scheduled_messages] - :language: python - :dedent: 4 - :caption: Cancelling messages scheduled to be sent in future - - """ - if not self.running: - self.open() - numbers = [types.AMQPLong(s) for s in sequence_numbers] - request_body = {'sequence-numbers': types.AMQPArray(numbers)} - return self._mgmt_request_response( - REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, - request_body, - mgmt_handlers.default) - - def send_pending_messages(self): - """Wait until all transferred events have been sent. - - :returns: A list of the send results of all the pending messages. Each - send result is a tuple with two values. The first is a boolean, indicating `True` - if the message sent, or `False` if it failed. The second is an error if the message - failed, otherwise it will be `None`. - :rtype: list[tuple[bool, ~azure.servicebus.common.errors.MessageSendFailed]] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START queue_and_send_messages] - :end-before: [END queue_and_send_messages] - :language: python - :dedent: 4 - :caption: Send the queued messages - - """ - if not self.running: - self.open() - try: - pending = self._handler._pending_messages[:] # pylint: disable=protected-access - _log.debug("Sending %r pending messages", len(pending)) - self._handler.wait() - results = [] - for m in pending: - if m.state == constants.MessageState.SendFailed: - results.append((False, MessageSendFailed(m._response))) # pylint: disable=protected-access - else: - results.append((True, None)) - return results - except Exception as e: - raise MessageSendFailed(e) - - def reconnect(self): - """Reconnect the handler. - - If the handler was disconnected from the service with - a retryable error - attempt to reconnect. - This method will be called automatically for most retryable errors. - Also attempts to re-queue any messages that were pending before the reconnect. - """ - unsent_events = self._handler.pending_messages - super(Sender, self).reconnect() - try: - self._handler.queue_message(*unsent_events) - self._handler.wait() - except Exception as e: # pylint: disable=broad-except - self._handle_exception(e) - - -class SessionSender(Sender): - """A session message sender. - - This handler is for sending messages to a sessionful Service Bus entity. - It operates a single connection that must be opened and closed on completion. - The Sender can be run within a context manager to ensure that the connection is closed on exit. - The Sender should not be instantiated directly, and should be accessed from a `QueueClient` or - `TopicClient` using the `get_sender()` method. - An attempt to send a message without a session ID specified either on the Sender or the message - will raise a `ValueError`. - - .. note:: This object is not thread-safe. - - :param handler_id: The ID used as the connection name for the Sender. - :type handler_id: str - :param target: The endpoint to send messages to. - :type target: ~uamqp.Target - :param auth_config: The SASL auth credentials. - :type auth_config: dict[str, str] - :param session: An optional session ID. If supplied, all outgoing messages will have this - session ID added (unless they already have one specified). - :type session: str - :param connection: A shared connection [not yet supported]. - :type connection: ~uamqp.Connection - :param encoding: The encoding used for string properties. Default is 'UTF-8'. - :type encoding: str - :param debug: Whether to enable network trace debug logs. - :type debug: bool - - """ - - def send(self, message): - """Send a message and blocks until acknowledgement is received or the operation fails. - - If neither the Sender nor the message has a session ID, a `ValueError` will be raised. - - :param message: The message to be sent. - :type message: ~azure.servicebus.common.message.Message - :raises: ~azure.servicebus.common.errors.MessageSendFailed if the message fails to - send. - :raises: ValueError if there is no session ID specified. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START send_message] - :end-before: [END send_message] - :language: python - :dedent: 4 - :caption: Send a message and block - - """ - if not isinstance(message, Message): - raise TypeError("Value of message must be of type 'Message'.") - if not self.session_id and not message.properties.group_id: - raise ValueError("Message must have Session ID.") - super(SessionSender, self).send(message) - - def queue_message(self, message): - """Queue a message to be sent later. - - This operation should be followed up with send_pending_messages. If neither the Sender nor the message - has a session ID, a `ValueError` will be raised. - - :param message: The message to be sent. - :type message: ~azure.servicebus.Message - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START queue_and_send_session_messages] - :end-before: [END queue_and_send_session_messages] - :language: python - :dedent: 4 - :caption: Put the message to be sent later in the queue - - """ - if not self.session_id and not message.properties.group_id: - raise ValueError("Message must have Session ID.") - super(SessionSender, self).queue_message(message) - - def schedule(self, schedule_time, *messages): - """Send one or more messages to be enqueued at a specific time. - - Returns a list of the sequence numbers of the enqueued messages. - - :param schedule_time: The date and time to enqueue the messages. - :type schedule_time: ~datetime.datetime - :param messages: The messages to schedule. - :type messages: ~azure.servicebus.common.message.Message - :rtype: list[int] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START scheduling_messages] - :end-before: [END scheduling_messages] - :language: python - :dedent: 4 - :caption: Schedule a message to be sent in future - - """ - for message in messages: - if not self.session_id and not message.properties.group_id: - raise ValueError("Message must have Session ID.") - return super(SessionSender, self).schedule(schedule_time, *messages) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/servicebus_client.py b/sdk/servicebus/azure-servicebus/azure/servicebus/servicebus_client.py deleted file mode 100644 index 8f42e4078270..000000000000 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/servicebus_client.py +++ /dev/null @@ -1,828 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import datetime -import uuid -import functools -import requests -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - -from uamqp import types -from uamqp.constants import TransportType - -from azure.servicebus.common import mgmt_handlers, mixins -from azure.servicebus.common.constants import ( - ReceiveSettleMode, - REQUEST_RESPONSE_PEEK_OPERATION, - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION) -from azure.servicebus.common.message import Message -from azure.servicebus.common.utils import parse_conn_str, build_uri -from azure.servicebus.common.errors import ServiceBusConnectionError, ServiceBusResourceNotFound -from azure.servicebus.control_client import ServiceBusService, SERVICE_BUS_HOST_BASE, DEFAULT_HTTP_TIMEOUT -from azure.servicebus.control_client.models import AzureServiceBusResourceNotFound -from azure.servicebus.send_handler import Sender, SessionSender -from azure.servicebus.receive_handler import Receiver, SessionReceiver -from azure.servicebus.base_handler import BaseHandler - - -class ServiceBusClient(mixins.ServiceBusMixin): - """A Service Bus client for a namespace with the specified SAS authentication settings. - - :param str service_namespace: Service Bus namespace, required for all operations. - :param str host_base: Optional. Live host base URL. Defaults to Public Azure. - :param str shared_access_key_name: SAS authentication key name. - :param str shared_access_key_value: SAS authentication key value. - :param transport_type: Optional. Underlying transport protocol type (Amqp or AmqpOverWebsocket) - Default value is ~azure.servicebus.TransportType.Amqp - :type transport_type: ~azure.servicebus.TransportType - :param int http_request_timeout: Optional. Timeout for the HTTP request, in seconds. - Default value is 65 seconds. - :param http_request_session: Optional. Session object to use for HTTP requests. - :type http_request_session: ~requests.Session - :param bool debug: Whether to output AMQP network trace to the logger. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_servicebus_client] - :end-before: [END create_servicebus_client] - :language: python - :dedent: 4 - :caption: Create a new instance of the Service Bus client - - """ - - def __init__(self, service_namespace=None, host_base=SERVICE_BUS_HOST_BASE, - shared_access_key_name=None, shared_access_key_value=None, - transport_type=TransportType.Amqp, - http_request_timeout=DEFAULT_HTTP_TIMEOUT, http_request_session=None, debug=False): - - self.service_namespace = service_namespace - self.host_base = host_base - self.shared_access_key_name = shared_access_key_name - self.shared_access_key_value = shared_access_key_value - self.transport_type = transport_type - self.debug = debug - self.mgmt_client = ServiceBusService( - service_namespace=service_namespace, - host_base=host_base, - shared_access_key_name=shared_access_key_name, - shared_access_key_value=shared_access_key_value, - timeout=http_request_timeout, - request_session=http_request_session) - - @classmethod - def from_connection_string(cls, conn_str, **kwargs): - """Create a Service Bus client from a connection string. - - :param conn_str: The connection string. - :type conn_str: str - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_servicebus_client_connstr] - :end-before: [END create_servicebus_client_connstr] - :language: python - :dedent: 4 - :caption: Create a ServiceBusClient via a connection string. - - """ - address, policy, key, _, transport_type = parse_conn_str(conn_str) - parsed_namespace = urlparse(address) - namespace, _, base = parsed_namespace.hostname.partition('.') - return cls( - namespace, - shared_access_key_name=policy, - shared_access_key_value=key, - transport_type=transport_type, - host_base='.' + base, - **kwargs) - - def _get_host(self): - return "sb://" + self.service_namespace + self.host_base - - def get_queue(self, queue_name): - """Get a client for a queue entity. - - :param queue_name: The name of the queue. - :type queue_name: str - :rtype: ~azure.servicebus.servicebus_client.QueueClient - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the queue is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_queue_client] - :end-before: [END get_queue_client] - :language: python - :dedent: 8 - :caption: Get the specific queue client from Service Bus client - - """ - try: - queue = self.mgmt_client.get_queue(queue_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed queue does not exist.") - return QueueClient.from_entity( - self._get_host(), queue, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - mgmt_client=self.mgmt_client, - debug=self.debug) - - def list_queues(self): - """Get clients for all queue entities in the namespace. - - :rtype: list[~azure.servicebus.servicebus_client.QueueClient] - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START list_queues] - :end-before: [END list_queues] - :language: python - :dedent: 4 - :caption: List the queues from Service Bus client - - """ - try: - queues = self.mgmt_client.list_queues() - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - queue_clients = [] - for queue in queues: - queue_clients.append(QueueClient.from_entity( - self._get_host(), queue, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - mgmt_client=self.mgmt_client, - debug=self.debug)) - return queue_clients - - def get_topic(self, topic_name): - """Get a client for a topic entity. - - :param topic_name: The name of the topic. - :type topic_name: str - :rtype: ~azure.servicebus.servicebus_client.TopicClient - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the topic is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_topic_client] - :end-before: [END get_topic_client] - :language: python - :dedent: 8 - :caption: Get the specific topic client from Service Bus client - - """ - try: - topic = self.mgmt_client.get_topic(topic_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed topic does not exist.") - return TopicClient.from_entity( - self._get_host(), topic, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - debug=self.debug) - - def list_topics(self): - """Get a client for all topic entities in the namespace. - - :rtype: list[~azure.servicebus.servicebus_client.TopicClient] - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START list_topics] - :end-before: [END list_topics] - :language: python - :dedent: 4 - :caption: List the topics from Service Bus client - - """ - try: - topics = self.mgmt_client.list_topics() - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - topic_clients = [] - for topic in topics: - topic_clients.append(TopicClient.from_entity( - self._get_host(), topic, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - debug=self.debug)) - return topic_clients - - def get_subscription(self, topic_name, subscription_name): - """Get a client for a subscription entity. - - :param topic_name: The name of the topic. - :type topic_name: str - :param subscription_name: The name of the subscription. - :type subscription_name: str - :rtype: ~azure.servicebus.servicebus_client.SubscriptionClient - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the subscription is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_subscription_client] - :end-before: [END get_subscription_client] - :language: python - :dedent: 8 - :caption: Get the specific subscription client from Service Bus client - - """ - try: - subscription = self.mgmt_client.get_subscription(topic_name, subscription_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed subscription does not exist.") - return SubscriptionClient.from_entity( - self._get_host(), topic_name, subscription, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - debug=self.debug) - - def list_subscriptions(self, topic_name): - """Get a client for all subscription entities in the topic. - - :param topic_name: The topic to list subscriptions for. - :type topic_name: str - :rtype: list[~azure.servicebus.servicebus_client.SubscriptionClient] - :raises: ~azure.servicebus.common.errors.ServiceBusConnectionError if the namespace is not found. - :raises: ~azure.servicebus.common.errors.ServiceBusResourceNotFound if the topic is not found. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START list_subscriptions] - :end-before: [END list_subscriptions] - :language: python - :dedent: 4 - :caption: List the subscriptions from Service Bus client - - """ - try: - subs = self.mgmt_client.list_subscriptions(topic_name) - except requests.exceptions.ConnectionError as e: - raise ServiceBusConnectionError("Namespace: {} not found".format(self.service_namespace), e) - except AzureServiceBusResourceNotFound: - raise ServiceBusResourceNotFound("Specificed topic does not exist.") - sub_clients = [] - for sub in subs: - sub_clients.append(SubscriptionClient.from_entity( - self._get_host(), topic_name, sub, - shared_access_key_name=self.shared_access_key_name, - shared_access_key_value=self.shared_access_key_value, - transport_type=self.transport_type, - debug=self.debug)) - return sub_clients - - -class SendClientMixin(object): - - def send(self, messages, message_timeout=0, session=None, **kwargs): - """Send one or more messages to the current entity. - - This operation will open a single-use connection, send the supplied messages, and close - connection. If the entity requires sessions, a session ID must be either - provided here, or set on each outgoing message. - - :param messages: One or more messages to be sent. - :type messages: ~azure.servicebus.common.message.Message or list[~azure.servicebus.common.message.Message] - :param message_timeout: The period in seconds during which the Message must be - sent. If the send is not completed in this time it will return a failure result. - :type message_timeout: int - :param session: An optional session ID. If supplied this session ID will be - applied to every outgoing message sent with this Sender. - If an individual message already has a session ID, that will be - used instead. If no session ID is supplied here, nor set on an outgoing - message, a ValueError will be raised if the entity is sessionful. - :type session: str or ~uuid.Guid - :raises: ~azure.servicebus.common.errors.MessageSendFailed - :returns: A list of the send results of all the messages. Each - send result is a tuple with two values. The first is a boolean, indicating `True` - if the message sent, or `False` if it failed. The second is an error if the message - failed, otherwise it will be `None`. - :rtype: list[tuple[bool, ~azure.servicebus.common.errors.MessageSendFailed]] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START send_message_service_bus] - :end-before: [END send_message_service_bus] - :language: python - :dedent: 4 - :caption: Send a message to current entity via a single use connection - - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START send_message_service_bus_multiple] - :end-before: [END send_message_service_bus_multiple] - :language: python - :dedent: 4 - :caption: Send messages to current entity via a single use connection - - """ - with self.get_sender(message_timeout=message_timeout, session=session, **kwargs) as sender: - if isinstance(messages, Message): - sender.queue_message(messages) - else: - try: - messages = list(messages) - except TypeError: - raise TypeError("Value of messages must be a 'Message' object or an iterable of 'Message' objects.") - - for m in messages: - if not isinstance(m, Message): - raise TypeError("Item {} in iterator is not of type 'Message'.".format(m)) - sender.queue_message(m) - - return sender.send_pending_messages() - - def get_sender(self, message_timeout=0, session=None, **kwargs): - """Get a Sender for the Service Bus endpoint. - - A Sender represents a single open Connection with which multiple send operations can be made. - - :param message_timeout: The period in seconds during which messages sent with - this Sender must be sent. If the send is not completed in this time it will fail. - :type message_timeout: int - :param session: An optional session ID. If supplied this session ID will be - applied to every outgoing message sent with this Sender. - If an individual message already has a session ID, that will be - used instead. If no session ID is supplied here, nor set on an outgoing - message, a ValueError will be raised if the entity is sessionful. - :type session: str or ~uuid.Guid - :returns: A Sender instance with an unopened connection. - :rtype: ~azure.servicebus.send_handler.Sender - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_sender] - :end-before: [END get_sender] - :language: python - :dedent: 4 - :caption: Get sender client from Service Bus client - - """ - handler_id = str(uuid.uuid4()) - if self.entity and self.requires_session: - return SessionSender( - handler_id, - self.entity_uri, - self.auth_config, - session=session, - debug=self.debug, - msg_timeout=message_timeout, - **kwargs) - return Sender( - handler_id, - self.entity_uri, - self.auth_config, - session=session, - debug=self.debug, - msg_timeout=message_timeout, - **kwargs) - - -class ReceiveClientMixin(object): - - def peek(self, count=1, start_from=0, session=None, **kwargs): - """Browse messages currently pending in the queue. - - Peeked messages are not removed from queue, nor are they locked. They cannot be completed, - deferred or dead-lettered. - - :param count: The maximum number of messages to try and peek. The default - value is 1. - :type count: int - :param start_from: A message sequence number from which to start browsing messages. - :type start_from: int - :param session: If the entity requires sessions, a session ID must be supplied - in order that only messages from that session will be browsed. If the entity - does not require sessions this value will be ignored. - :type session: str - :rtype: list[~azure.servicebus.common.message.PeekMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START peek_messages_service_bus] - :end-before: [END peek_messages_service_bus] - :language: python - :dedent: 4 - :caption: Look at specificied number of messages without removing them from queue - - """ - message = {'from-sequence-number': types.AMQPLong(start_from), 'message-count': int(count)} - if self.entity and self.requires_session: - if not session: - raise ValueError("Sessions are required, please set session.") - message['session-id'] = session - - with BaseHandler(self.entity_uri, self.auth_config, debug=self.debug, **kwargs) as handler: - return handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_PEEK_OPERATION, - message, - mgmt_handlers.peek_op) - - def list_sessions(self, updated_since=None, max_results=100, skip=0, **kwargs): - """List session IDs. - - List the Session IDs with pending messages in the queue where the state of the session - has been updated since the timestamp provided. If no timestamp is provided, all will be returned. - If the state of a session has never been set, it will not be returned regardless of whether - there are messages pending. - - :param updated_since: The UTC datetime from which to return updated pending Session IDs. - :type updated_since: datetime.datetime - :param max_results: The maximum number of Session IDs to return. Default value is 100. - :type max_results: int - :param skip: The page value to jump to. Default value is 0. - :type skip: int - :rtype: list[str] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START list_sessions_service_bus] - :end-before: [END list_sessions_service_bus] - :language: python - :dedent: 4 - :caption: Get the Ids of session which have messages pending in the queue - - """ - if self.entity and not self.requires_session: - raise ValueError("This is not a sessionful entity.") - message = { - 'last-updated-time': updated_since or datetime.datetime.utcfromtimestamp(0), - 'skip': types.AMQPInt(skip), - 'top': types.AMQPInt(max_results), - } - with BaseHandler(self.entity_uri, self.auth_config, debug=self.debug, **kwargs) as handler: - return handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, - message, - mgmt_handlers.list_sessions_op) - - def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock, **kwargs): - """Receive messages by sequence number that have been previously deferred. - - When receiving deferred messages from a partitioned entity, all of the supplied - sequence numbers must be messages from the same partition. - - :param sequence_numbers: A list of the sequence numbers of messages that have been - deferred. - :type sequence_numbers: list[int] - :param mode: The mode with which messages will be retrieved from the entity. The two options - are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given - lock period before they will be removed from the queue. Messages received with ReceiveAndDelete - will be immediately removed from the queue, and cannot be subsequently rejected or re-received if - the client fails to process the message. The default mode is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :rtype: list[~azure.servicebus.common.message.Message] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START receive_deferred_messages_service_bus] - :end-before: [END receive_deferred_messages_service_bus] - :language: python - :dedent: 8 - :caption: Get the messages which were deferred using their sequence numbers - - """ - if (self.entity and self.requires_session) or kwargs.get('session'): - raise ValueError("Sessionful deferred messages can only be received within a locked receive session.") - if not sequence_numbers: - raise ValueError("At least one sequence number must be specified.") - try: - receive_mode = mode.value.value - except AttributeError: - receive_mode = int(mode) - message = { - 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), - 'receiver-settle-mode': types.AMQPuInt(receive_mode)} - mgmt_handler = functools.partial(mgmt_handlers.deferred_message_op, mode=receive_mode) - with BaseHandler(self.entity_uri, self.auth_config, debug=self.debug, **kwargs) as handler: - return handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, - message, - mgmt_handler) - - def settle_deferred_messages(self, settlement, messages, **kwargs): - """Settle messages that have been previously deferred. - - :param settlement: How the messages are to be settled. This must be a string - of one of the following values: 'completed', 'suspended', 'abandoned'. - :type settlement: str - :param messages: A list of deferred messages to be settled. - :type messages: list[~azure.servicebus.common.message.DeferredMessage] - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START settle_deferred_messages_service_bus] - :end-before: [END settle_deferred_messages_service_bus] - :language: python - :dedent: 8 - :caption: Settle deferred messages. - - """ - if (self.entity and self.requires_session) or kwargs.get('session'): - raise ValueError("Sessionful deferred messages can only be settled within a locked receive session.") - if settlement.lower() not in ['completed', 'suspended', 'abandoned']: - raise ValueError("Settlement must be one of: 'completed', 'suspended', 'abandoned'") - if not messages: - raise ValueError("At least one message must be specified.") - message = { - 'disposition-status': settlement.lower(), - 'lock-tokens': types.AMQPArray([m.lock_token for m in messages])} - - with BaseHandler(self.entity_uri, self.auth_config, debug=self.debug, **kwargs) as handler: - return handler._mgmt_request_response( # pylint: disable=protected-access - REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, - message, - mgmt_handlers.default) - - def get_receiver(self, session=None, prefetch=0, mode=ReceiveSettleMode.PeekLock, idle_timeout=0, **kwargs): - """Get a Receiver for the Service Bus endpoint. - - A Receiver represents a single open Connection with which multiple receive operations can be made. - - :param session: A specific session from which to receive. This must be specified for a - sessionful entity, otherwise it must be None. In order to receive the next available - session, set this to NEXT_AVAILABLE. - :type session: str or ~azure.servicebus.common.constants.NEXT_AVAILABLE - :param prefetch: The maximum number of messages to cache with each request to the service. - The default value is 0, meaning messages will be received from the service and processed - one at a time. Increasing this value will improve message throughput performance but increase - the change that messages will expire while they are cached if they're not processed fast enough. - :type prefetch: int - :param mode: The mode with which messages will be retrieved from the entity. The two options - are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given - lock period before they will be removed from the queue. Messages received with ReceiveAndDelete - will be immediately removed from the queue, and cannot be subsequently rejected or re-received if - the client fails to process the message. The default mode is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param idle_timeout: The timeout in seconds between received messages after which the receiver will - automatically shutdown. The default value is 0, meaning no timeout. - :type idle_timeout: int - :returns: A Receiver instance with an unopened Connection. - :rtype: ~azure.servicebus.receive_handler.Receiver - :raises: If the current Service Bus entity requires sessions, a TypeError will - be raised. - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_receiver] - :end-before: [END get_receiver] - :language: python - :dedent: 4 - :caption: Get the receiver client from Service Bus client - - """ - if self.entity and not self.requires_session and session: - raise ValueError("A session cannot be used with a non-sessionful entitiy.") - if self.entity and self.requires_session and not session: - raise ValueError("This entity requires a session.") - if int(prefetch) < 0 or int(prefetch) > 50000: - raise ValueError("Prefetch must be an integer between 0 and 50000 inclusive.") - - prefetch += 1 - handler_id = str(uuid.uuid4()) - if session: - return SessionReceiver( - handler_id, - self.entity_uri, - self.auth_config, - session=session, - debug=self.debug, - timeout=int(idle_timeout * 1000), - prefetch=prefetch, - mode=mode, - **kwargs) - return Receiver( - handler_id, - self.entity_uri, - self.auth_config, - debug=self.debug, - timeout=int(idle_timeout * 1000), - prefetch=prefetch, - mode=mode, - **kwargs) - - def get_deadletter_receiver( - self, transfer_deadletter=False, prefetch=0, - mode=ReceiveSettleMode.PeekLock, idle_timeout=0, **kwargs): - """Get a Receiver for the deadletter endpoint of the queue. - - A Receiver represents a single open Connection with which multiple receive operations can be made. - - :param transfer_deadletter: Whether to connect to the transfer deadletter queue, or the standard - deadletter queue. Default is False, using the standard deadletter endpoint. - :type transfer_deadletter: bool - :param prefetch: The maximum number of messages to cache with each request to the service. - The default value is 0, meaning messages will be received from the service and processed - one at a time. Increasing this value will improve message throughput performance but increase - the change that messages will expire while they are cached if they're not processed fast enough. - :type prefetch: int - :param mode: The mode with which messages will be retrieved from the entity. The two options - are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given - lock period before they will be removed from the queue. Messages received with ReceiveAndDelete - will be immediately removed from the queue, and cannot be subsequently rejected or re-received if - the client fails to process the message. The default mode is PeekLock. - :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode - :param idle_timeout: The timeout in seconds between received messages after which the receiver will - automatically shutdown. The default value is 0, meaning no timeout. - :type idle_timeout: int - :returns: A Receiver instance with an unopened Connection. - :rtype: ~azure.servicebus.receive_handler.Receiver - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START get_dead_letter_receiver] - :end-before: [END get_dead_letter_receiver] - :language: python - :dedent: 4 - :caption: Get the dead lettered messages - - """ - if int(prefetch) < 0 or int(prefetch) > 50000: - raise ValueError("Prefetch must be an integer between 0 and 50000 inclusive.") - - prefetch += 1 - handler_id = str(uuid.uuid4()) - if transfer_deadletter: - entity_uri = self.mgmt_client.format_transfer_dead_letter_queue_name(self.entity_uri) - else: - entity_uri = self.mgmt_client.format_dead_letter_queue_name(self.entity_uri) - return Receiver( - handler_id, - entity_uri, - self.auth_config, - debug=self.debug, - timeout=int(idle_timeout * 1000), - prefetch=prefetch, - mode=mode, - **kwargs) - - -class QueueClient(SendClientMixin, ReceiveClientMixin, mixins.BaseClient): - """A queue client. - - The QueueClient class defines a high level interface for sending - messages to and receiving messages from an Azure Service Bus queue. - If you do not wish to perform management operations, a QueueClient can be - instantiated directly to perform send and receive operations to a Queue. - However if a QueueClient is created directly, a `get_properties` operation will - need to be completed in order to retrieve the properties of this queue (for example, - whether it is sessionful). - - :param address: The full URI of the Service Bus namespace. This can optionally - include URL-encoded access name and key. - :type address: str - :param name: The name of the queue to which the Client will connect. - :type name: str - :param shared_access_key_name: The name of the shared access policy. This must be supplied - if not encoded into the address. - :type shared_access_key_name: str - :param shared_access_key_value: The shared access key. This must be supplied if not encoded - into the address. - :type shared_access_key_value: str - :param debug: Whether to output network trace logs to the logger. Default is `False`. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_queue_client_directly] - :end-before: [END create_queue_client_directly] - :language: python - :dedent: 8 - :caption: Create a QueueClient. - - """ - - def _get_entity(self): - return self.mgmt_client.get_queue(self.name) - - -class TopicClient(SendClientMixin, mixins.BaseClient): - """A topic client. - - The TopicClient class defines a high level interface for sending - messages to an Azure Service Bus Topic. - If you do not wish to perform management operations, a TopicClient can be - instantiated directly to perform send operations to a Topic. - - :param address: The full URI of the Service Bus namespace. This can optionally - include URL-encoded access name and key. - :type address: str - :param name: The name of the topic to which the Client will connect. - :type name: str - :param shared_access_key_name: The name of the shared access policy. This must be supplied - if not encoded into the address. - :type shared_access_key_name: str - :param shared_access_key_value: The shared access key. This must be supplied if not encoded - into the address. - :type shared_access_key_value: str - :param debug: Whether to output network trace logs to the logger. Default is `False`. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_topic_client_directly] - :end-before: [END create_topic_client_directly] - :language: python - :dedent: 8 - :caption: Create a TopicClient. - - """ - - def _get_entity(self): - return self.mgmt_client.get_topic(self.name) - - -class SubscriptionClient(ReceiveClientMixin, mixins.BaseClient): - """A subscription client. - - The SubscriptionClient class defines a high level interface for receiving - messages to an Azure Service Bus Subscription. - If you do not wish to perform management operations, a SubscriptionClient can be - instantiated directly to perform receive operations from a Subscription. - - :param address: The full URI of the Service Bus namespace. This can optionally - include URL-encoded access name and key. - :type address: str - :param name: The name of the topic to which the Client will connect. - :type name: str - :param shared_access_key_name: The name of the shared access policy. This must be supplied - if not encoded into the address. - :type shared_access_key_name: str - :param shared_access_key_value: The shared access key. This must be supplied if not encoded - into the address. - :type shared_access_key_value: str - :param debug: Whether to output network trace logs to the logger. Default is `False`. - :type debug: bool - - .. admonition:: Example: - .. literalinclude:: ../samples/sync_samples/test_examples.py - :start-after: [START create_sub_client_directly] - :end-before: [END create_sub_client_directly] - :language: python - :dedent: 8 - :caption: Create a SubscriptionClient. - - """ - - def __init__(self, address, name, shared_access_key_name=None, - shared_access_key_value=None, debug=False, **kwargs): - - super(SubscriptionClient, self).__init__( - address, name, shared_access_key_name=shared_access_key_name, - shared_access_key_value=shared_access_key_value, debug=debug, **kwargs) - self.topic_name = self.address.path.split("/")[1] - - @classmethod - def from_connection_string(cls, conn_str, name, topic=None, **kwargs): # pylint: disable=arguments-differ - """Create a SubscriptionClient from a connection string. - - :param conn_str: The connection string. - :type conn_str: str - :param name: The name of the Subscription. - :type name: str - :param topic: The name of the Topic, if the EntityName is - not included in the connection string. - :type topic: str - """ - address, policy, key, entity, transport_type = parse_conn_str(conn_str) - entity = topic or entity - address = build_uri(address, entity) - address += "/Subscriptions/" + name - return cls(address, name, shared_access_key_name=policy, shared_access_key_value=key, transport_type=transport_type, **kwargs) - - @classmethod - def from_entity(cls, address, topic, entity, **kwargs): # pylint: disable=arguments-differ - client = cls( - address + "/" + topic + "/Subscriptions/" + entity.name, - entity.name, - validated_entity=entity, - **kwargs) - return client - - def _get_entity(self): - return self.mgmt_client.get_subscription(self.topic_name, self.name) diff --git a/sdk/servicebus/azure-servicebus/conftest.py b/sdk/servicebus/azure-servicebus/conftest.py index de813f082b9a..6d440a7f620e 100644 --- a/sdk/servicebus/azure-servicebus/conftest.py +++ b/sdk/servicebus/azure-servicebus/conftest.py @@ -16,298 +16,24 @@ collect_ignore.append("tests/async_tests") collect_ignore.append("samples/async_samples") -def get_live_servicebus_config(): - config = {} - config['hostname'] = os.environ['SERVICE_BUS_HOSTNAME'] - config['key_name'] = os.environ['SERVICE_BUS_SAS_POLICY'] - config['access_key'] = os.environ['SERVICE_BUS_SAS_KEY'] - config['conn_str'] = os.environ['SERVICE_BUS_CONNECTION_STR'] - return config - - -def create_standard_queue(servicebus_config, client=None): - from azure.servicebus.control_client import ServiceBusService, Queue - queue_name = str(uuid.uuid4()) - queue_value = Queue( - lock_duration='PT30S', - requires_duplicate_detection=False, - dead_lettering_on_message_expiration=True, - requires_session=False) - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - if client.create_queue(queue_name, queue=queue_value, fail_on_exist=True): - return queue_name - raise ValueError("Queue creation failed.") - - -# def create_partitioned_queue(servicebus_config, client=None): -# from azure.servicebus.control_client import ServiceBusService, Queue -# queue_name = str(uuid.uuid4()) -# queue_value = Queue( -# lock_duration='PT30S', -# requires_duplicate_detection=False, -# dead_lettering_on_message_expiration=True, -# requires_session=False) -# client = client or ServiceBusService( -# service_namespace=servicebus_config['hostname'], -# shared_access_key_name=servicebus_config['key_name'], -# shared_access_key_value=servicebus_config['access_key']) -# if client.create_queue(queue_name, queue=queue_value, fail_on_exist=True): -# return queue_name -# raise ValueError("Queue creation failed.") - - -def create_duplicate_queue(servicebus_config, client=None): - from azure.servicebus.control_client import ServiceBusService, Queue - queue_name = str(uuid.uuid4()) - queue_value = Queue( - lock_duration='PT30S', - requires_duplicate_detection=True, - dead_lettering_on_message_expiration=True, - requires_session=False) - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - if client.create_queue(queue_name, queue=queue_value, fail_on_exist=True): - return queue_name - raise ValueError("Queue creation failed.") - - -def create_deadletter_queue(servicebus_config, client=None): - from azure.servicebus.control_client import ServiceBusService, Queue - queue_name = str(uuid.uuid4()) - queue_value = Queue( - lock_duration='PT30S', - requires_duplicate_detection=False, - dead_lettering_on_message_expiration=True, - requires_session=False) - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - if client.create_queue(queue_name, queue=queue_value, fail_on_exist=True): - return queue_name - raise ValueError("Queue creation failed.") - - -def create_session_queue(servicebus_config, client=None): - from azure.servicebus.control_client import ServiceBusService, Queue - queue_name = str(uuid.uuid4()) - queue_value = Queue( - lock_duration='PT30S', - requires_duplicate_detection=False, - requires_session=True) - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - if client.create_queue(queue_name, queue=queue_value, fail_on_exist=True): - return queue_name - raise ValueError("Queue creation failed.") - - -# def create_partitioned_session_queue(servicebus_config, client=None): -# from azure.servicebus.control_client import ServiceBusService, Queue -# queue_name = str(uuid.uuid4()) -# queue_value = Queue( -# lock_duration='PT30S', -# requires_duplicate_detection=False, -# requires_session=True) -# client = client or ServiceBusService( -# service_namespace=servicebus_config['hostname'], -# shared_access_key_name=servicebus_config['key_name'], -# shared_access_key_value=servicebus_config['access_key']) -# if client.create_queue(queue_name, queue=queue_value, fail_on_exist=True): -# return queue_name -# raise ValueError("Queue creation failed.") - - -def create_standard_topic(servicebus_config, client=None): - from azure.servicebus.control_client import ServiceBusService, Topic - topic_name = str(uuid.uuid4()) - topic_value = Topic(requires_duplicate_detection=False) - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - if client.create_topic(topic_name, topic=topic_value, fail_on_exist=True): - return topic_name - raise ValueError("Queue creation failed.") - - -def create_standard_subscription(servicebus_config, topic_name, client=None): - from azure.servicebus.control_client import ServiceBusService, Subscription - subscription_name = str(uuid.uuid4()) - sub_value = Subscription(dead_lettering_on_message_expiration=True) - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - if client.create_subscription( - topic_name, subscription_name, - subscription=sub_value, fail_on_exist=True): - return (topic_name, subscription_name) - raise ValueError("Queue creation failed.") - - -def create_session_subscription(servicebus_config, topic_name, client=None): - from azure.servicebus.control_client import ServiceBusService, Subscription - subscription_name = str(uuid.uuid4()) - sub_value = Subscription( - dead_lettering_on_message_expiration=True, - requires_session=True) - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - if client.create_subscription( - topic_name, subscription_name, - subscription=sub_value, fail_on_exist=True): - return (topic_name, subscription_name) - raise ValueError("Queue creation failed.") - - -def cleanup_queue(servicebus_config, queue_name, client=None): - from azure.servicebus.control_client import ServiceBusService - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - client.delete_queue(queue_name) - - -def cleanup_topic(servicebus_config, topic_name, client=None): - from azure.servicebus.control_client import ServiceBusService - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - client.delete_topic(topic_name) - - -def cleanup_subscription(servicebus_config, topic_name, subscription, client=None): - from azure.servicebus.control_client import ServiceBusService - client = client or ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - client.delete_subscription(topic_name, subscription) - - -@pytest.fixture() -def live_servicebus_config(): - try: - config = get_live_servicebus_config() - except KeyError: - pytest.skip("Live ServiceBus configuration not found.") - else: - if not all(config.values()): - pytest.skip("Live ServiceBus configuration empty.") - return config - - -@pytest.fixture() -def servicebus_conn_str_readonly(): - try: - return os.environ['SERVICE_BUS_CONNECTION_STR_RO'] - except KeyError: - pytest.skip("Live ServiceBus configuration not found.") - - -@pytest.fixture() -def servicebus_conn_str_writeonly(): - try: - return os.environ['SERVICE_BUS_CONNECTION_STR_WO'] - except KeyError: - pytest.skip("Live ServiceBus configuration not found.") - - -@pytest.fixture() -def queue_servicebus_conn_str(): - try: - return os.environ['SERVICE_BUS_CONNECTION_STR_ENTITY'] - except KeyError: - pytest.skip("Live ServiceBus configuration not found.") - - -@pytest.fixture() -def standard_queue(live_servicebus_config): # pylint: disable=redefined-outer-name - from azure.servicebus.control_client import ServiceBusService - client = ServiceBusService( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key']) - try: - queue_name = create_standard_queue(live_servicebus_config, client=client) - yield queue_name - finally: - cleanup_queue(live_servicebus_config, queue_name, client=client) - - -@pytest.fixture() -def partitioned_queue(live_servicebus_config): # pylint: disable=redefined-outer-name,unused-argument - pytest.skip("Pending API version update") - - -@pytest.fixture() -def session_queue(live_servicebus_config): # pylint: disable=redefined-outer-name - from azure.servicebus.control_client import ServiceBusService - client = ServiceBusService( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key']) - try: - queue_name = create_session_queue(live_servicebus_config, client=client) - yield queue_name - finally: - cleanup_queue(live_servicebus_config, queue_name, client=client) - - -@pytest.fixture() -def partitioned_session_queue(live_servicebus_config): # pylint: disable=redefined-outer-name,unused-argument - pytest.skip("Pending API version update") - - -@pytest.fixture() -def duplicate_queue(live_servicebus_config): # pylint: disable=redefined-outer-name - from azure.servicebus.control_client import ServiceBusService - client = ServiceBusService( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key']) - try: - queue_name = create_duplicate_queue(live_servicebus_config, client=client) - yield queue_name - finally: - cleanup_queue(live_servicebus_config, queue_name, client=client) - - -@pytest.fixture() -def standard_topic(live_servicebus_config): # pylint: disable=redefined-outer-name - from azure.servicebus.control_client import ServiceBusService - client = ServiceBusService( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key']) - try: - topic_name = create_standard_topic(live_servicebus_config, client=client) - yield topic_name - finally: - cleanup_topic(live_servicebus_config, topic_name, client=client) - - -@pytest.fixture() -def standard_subscription(live_servicebus_config, standard_topic): # pylint: disable=redefined-outer-name - from azure.servicebus.control_client import ServiceBusService - client = ServiceBusService( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key']) - try: - topic, subscription = create_standard_subscription(live_servicebus_config, standard_topic, client=client) - yield (topic, subscription) - finally: - cleanup_subscription(live_servicebus_config, topic, subscription, client=client) +# Only run stress tests on request. +if not any([arg.startswith('test_stress') or arg.endswith('StressTest') for arg in sys.argv]): + collect_ignore.append("tests/stress_tests") + +# Allow us to pass stress_test_duration from the command line. +def pytest_addoption(parser): + parser.addoption('--stress_test_duration_seconds', action="store", default=None) + +# Note: This is duplicated between here and the basic conftest, so that it does not throw warnings if you're +# running locally to this SDK. (Everything works properly, pytest just makes a bit of noise.) +def pytest_configure(config): + # register an additional marker + config.addinivalue_line( + "markers", "liveTest: mark test to be a live test only" + ) + config.addinivalue_line( + "markers", "live_test_only: mark test to be a live test only" + ) + config.addinivalue_line( + "markers", "playback_test_only: mark test to be a playback test only" + ) diff --git a/sdk/servicebus/azure-servicebus/migration_guide.md b/sdk/servicebus/azure-servicebus/migration_guide.md new file mode 100644 index 000000000000..751e390df9f9 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/migration_guide.md @@ -0,0 +1,149 @@ +# Guide to migrate from azure-servicebus v0.50 to v7 + +This document is intended for users that are familiar with v0.50 of the Python SDK for Service Bus library (`azure-servicebus 0.50.x`) and wish +to migrate their application to v7 of the same library. + +For users new to the Python SDK for Service Bus, please see the [readme file for the azure-servicebus](./README.md). + +## General changes +Version 7 of the azure-servicebus package is the result of our efforts to create a client library that is user-friendly and idiomatic to the Python ecosystem. +Alongside an API redesign driven by the new [Azure SDK Design Guidelines for Python](https://azure.github.io/azure-sdk/python_introduction.html#design-principles), +the latest version improves on several areas from v0.50. + +Note: The large version gap is in order to normalize service bus SDK versions across our languages, as they consolidate on structure as well. + +### Specific clients for sending and receiving +In v7 we've simplified the API surface, making two distinct clients, rather than one for each of queue, topic, and subscription: +* `ServiceBusSender` for sending messages. [Sync API](https://azuresdkdocs.blob.core.windows.net/$web/python/azure-servicebus/7.0.0b1/azure.servicebus.html#azure.servicebus.ServiceBusSender) +and [Async API](https://azuresdkdocs.blob.core.windows.net/$web/python/azure-servicebus/7.0.0b1/azure.servicebus.aio.html#azure.servicebus.aio.ServiceBusSender) +* `ServiceBusReceiver` for receiving messages. [Sync API](https://azuresdkdocs.blob.core.windows.net/$web/python/azure-servicebus/7.0.0b1/azure.servicebus.html#azure.servicebus.ServiceBusReceiver) +and [Async API](https://azuresdkdocs.blob.core.windows.net/$web/python/azure-servicebus/7.0.0b1/azure.servicebus.aio.html#azure.servicebus.aio.ServiceBusReceiver) + +As a user this will be largely transparent to you, as initialization will still occur primarily via the top level ServiceBusClient, +the primary difference will be that rather than creating a queue_client, for instance, and then a sender off of that, you would simply +create a servicebus queue sender off of your ServiceBusClient instance via the "get_queue_sender" method. + +It should also be noted that many of the helper methods that previously existed on the intermediary client (e.g. QueueClient and Peek) now +exist on the receiver (in the case of peek) or sender itself. This is to better consolidate functionality and align messaging link lifetime +semantics with the sender or receiver lifetime. + +### Client constructors + +| In v7 | Equivalent in v5 | Sample | +|---|---|---| +| `ServiceBusClient.from_connection_string()` | `ServiceBusClient.from_connection_string()` | [using credential](./samples/sync_samples/sample_code_servicebus.py ) | +| `QueueClient.from_connection_string()` | `ServiceBusClient.from_connection_string().get_queue_()` | [client initialization](./samples/sync_samples/send_queue.py ) | + +### Receiving messages + +| In v0.50 | Equivalent in v7 | Sample | +|---|---|---| +| `QueueClient.from_connection_string().get_receiver().fetch_next() and ServiceBusClient.from_connection_string().get_queue().get_receiver().fetch_next()`| `ServiceBusClient.from_connection_string().get_queue_receiver().receive()`| [receive a single batch of messages](./samples/sync_samples/send_queue.py) | + +### Sending messages + +| In v0.50 | Equivalent in v7 | Sample | +|---|---|---| +| `QueueClient.from_connection_string().send() and ServiceBusClient.from_connection_string().get_queue().get_sender().send()`| `ServiceBusClient.from_connection_string().get_queue_receiver().receive()`| [receive a single batch of messages](./samples/sync_samples/receive_queue.py) | + +### Working with sessions + +| In v0.50 | Equivalent in v7 | Sample | +|---|---|---| +| `queue_client.send(message, session='foo') and queue_client.get_sender(session='foo').send(message)`| `sb_client.get_queue_sender().send(Message('body', session_id='foo'))`| [send a message to a session](./samples/sync_samples/session_send_receive.py) | +| `AutoLockRenew().register(queue_client.get_receiver(session_id='foo'))`| `AutoLockRenew().register(sb_client.get_queue_receiver(session_id='foo').session)`| [access a session and ensure its lock is auto-renewed](./samples/sync_samples/session_send_receive.py) | + + +## Migration samples + +* [Receiving messages](#migrating-code-from-queueclient-and-receiver-to-servicebusreceiver-for-receiving-messages) +* [Sending messages](#migrating-code-from-queueclient-and-sender-to-servicebussender-for-sending-messages) + +### Migrating code from `QueueClient` and `Receiver` to `ServiceBusReceiver` for receiving messages + +In v0.50, `QueueClient` would be created directly or from the `ServiceBusClient.get_queue` method, +after which user would call `get_receiver` to obtain a receiver, calling `fetch_next` to receive a single +batch of messages, or iterate over the receiver to receive continuously. + +In v7, users should initialize the client via `ServiceBusClient.get_queue_receiver`. Single-batch-receive +has been renamed to `receive`, iterating over the receiver for continual message consumption has not changed. + +For example, this code which keeps receiving from a partition in v0.50: + +```python +client = ServiceBusClient.from_connection_string(CONNECTION_STR) +queue_client = client.get_queue(queue) + +with queue_client.get_receiver(idle_timeout=1, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: + + # Receive list of messages as a batch + batch = receiver.fetch_next(max_batch_size=10) + for message in batch: + print("Message: {}".format(message)) + message.complete() + + # Receive messages as a continuous generator + for message in receiver: + print("Message: {}".format(message)) + message.complete() +``` + +Becomes this in v7: +```python +with ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) as client: + + with client.get_queue_receiver(queue_name=QUEUE_NAME) as receiver: + batch = receiver.receive(max_batch_size=10, max_wait_time=5) + for message in batch: + print("Message: {}".format(message)) + message.complete() + + for message in receiver: + print("Message: {}".format(message)) + message.complete() +``` + + +### Migrating code from `QueueClient` and `Sender` to `ServiceBusSender` for sending messages + +In v0.50, `QueueClient` would be created directly or from the `ServiceBusClient.get_queue` method, +after which user would call `get_sender` to obtain a sender, calling `send` to send a single or batch +of messages. Send could also be called directly off of the `QueueClient` + +In v7, users should initialize the client via `ServiceBusClient.get_queue_sender`. Sending itself has not +changed, but currently does not support sending a list of messages in one call. If this is desired, first +insert those messages into a batch. + +So in v0.50: +```python +client = ServiceBusClient.from_connection_string(CONNECTION_STR) + +queue_client = client.get_queue(queue) +with queue_client.get_sender() as sender: + # Send one at a time. + for i in range(100): + message = Message("Sample message no. {}".format(i)) + sender.send(message) + + # Send as a batch. + messages_to_batch = [Message("Batch message no. {}".format(i)) for i in range(10)] + batch = BatchMessage(messages_to_batch) + sender.send(batch) +``` + +In v7: +```python +with ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) as client: + + with client.get_queue_sender(queue_name=QUEUE_NAME) as sender: + # Sending one at a time. + for i in range(100): + message = Message("Sample message no. {}".format(i)) + sender.send(message) + + # Send as a batch + batch = new BatchMessage() + for i in range(10): + batch.add(Message("Batch message no. {}".format(i))) + sender.send(batch) +``` diff --git a/sdk/servicebus/azure-servicebus/samples/README.md b/sdk/servicebus/azure-servicebus/samples/README.md index ecbca90beec0..aaf82947a01e 100644 --- a/sdk/servicebus/azure-servicebus/samples/README.md +++ b/sdk/servicebus/azure-servicebus/samples/README.md @@ -4,7 +4,7 @@ languages: - python products: - azure - - azure-servicebus + - azure-service-bus urlFragment: servicebus-samples --- @@ -13,7 +13,7 @@ urlFragment: servicebus-samples These are code samples that show common scenario operations with the Azure Service Bus client library. Both [sync version](./sync_sampes) and [async version](./async_samples) of samples are provided, async samples require Python 3.5 or later. -- [topic_send.py](./sync_samples/topic_send.py) ([async version](./async_samples/topic_send_async.py)) - Examples to send messages on a service bus topic: +- [send_queue.py](./sync_samples/send_queue.py) ([async version](./async_samples/send_queue_async.py)) - Examples to send messages on a service bus queue: - From a connection string - Enabling Logging @@ -35,10 +35,7 @@ pip install azure-servicebus 1. Open a terminal window and `cd` to the directory that the samples are saved in. 2. Set the environment variables specified in the sample file you wish to run. -3. Follow the usage described in the file, e.g. `python topic_send.py`. - - Note: If the sample in question uses pytest (look for @livetest marks) please run via pytest specifying the test name, and have the servicebus credentials present in environment variables - as described in conftest.py. +3. Follow the usage described in the file, e.g. `python send_queue.py`. ## Next steps diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/example_queue_send_receive_batch_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/example_queue_send_receive_batch_async.py deleted file mode 100644 index 993fa41f1d37..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/async_samples/example_queue_send_receive_batch_async.py +++ /dev/null @@ -1,48 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import asyncio - -import conftest - -from azure.servicebus.aio import ServiceBusClient, Message -from azure.servicebus.common.constants import ReceiveSettleMode - - -async def sample_queue_send_receive_batch_async(sb_config, queue): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - async with queue_client.get_sender() as sender: - for i in range(100): - message = Message("Sample message no. {}".format(i)) - await sender.send(message) - await sender.send(Message("shutdown")) - - async with queue_client.get_receiver(idle_timeout=1, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - # Receive list of messages as a batch - batch = await receiver.fetch_next(max_batch_size=10) - await asyncio.gather(*[m.complete() for m in batch]) - - # Receive messages as a continuous generator - async for message in receiver: - print("Message: {}".format(message)) - print("Sequence number: {}".format(message.sequence_number)) - await message.complete() - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - loop = asyncio.get_event_loop() - try: - loop.run_until_complete(sample_queue_send_receive_batch_async(live_config, queue_name)) - finally: - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/example_session_send_receive_batch_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/example_session_send_receive_batch_async.py deleted file mode 100644 index 2e7300a11489..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/async_samples/example_session_send_receive_batch_async.py +++ /dev/null @@ -1,47 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import asyncio -import uuid - -import conftest - -from azure.servicebus.aio import ServiceBusClient, Message - - -async def sample_session_send_receive_batch_async(sb_config, queue): - - session_id = str(uuid.uuid4()) - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key']) - - queue_client = client.get_queue(queue) - - async with queue_client.get_sender(session=session_id) as sender: - for i in range(100): - message = Message("Sample message no. {}".format(i)) - await sender.send(message) - await sender.send(Message("shutdown")) - - async with queue_client.get_receiver(session=session_id) as session: - await session.set_session_state("START") - async for message in session: - await message.complete() - if str(message) == "shutdown": - await session.set_session_state("END") - break - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_session_queue(live_config) - loop = asyncio.get_event_loop() - try: - loop.run_until_complete(sample_session_send_receive_batch_async(live_config, queue_name)) - finally: - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/example_session_send_receive_with_pool_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/example_session_send_receive_with_pool_async.py deleted file mode 100644 index 902a01b35599..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/async_samples/example_session_send_receive_with_pool_async.py +++ /dev/null @@ -1,66 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import asyncio -import uuid - -import conftest - -from azure.servicebus.aio import ServiceBusClient, Message -from azure.servicebus.common.constants import NEXT_AVAILABLE -from azure.servicebus.common.errors import NoActiveSession - - -async def message_processing(queue_client): - while True: - try: - async with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=1) as session: - await session.set_session_state("OPEN") - async for message in session: - print("Message: {}".format(message)) - print("Time to live: {}".format(message.header.time_to_live)) - print("Sequence number: {}".format(message.sequence_number)) - print("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - print("Partition ID: {}".format(message.partition_id)) - print("Partition Key: {}".format(message.partition_key)) - print("Locked until: {}".format(message.locked_until)) - print("Lock Token: {}".format(message.lock_token)) - print("Enqueued time: {}".format(message.enqueued_time)) - await message.complete() - if str(message) == 'shutdown': - await session.set_session_state("CLOSED") - break - except NoActiveSession: - return - - -async def sample_session_send_receive_with_pool_async(sb_config, queue): - - concurrent_receivers = 5 - sessions = [str(uuid.uuid4()) for i in range(concurrent_receivers)] - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key']) - - queue_client = client.get_queue(queue) - for session_id in sessions: - async with queue_client.get_sender(session=session_id) as sender: - await asyncio.gather(*[sender.send(Message("Sample message no. {}".format(i))) for i in range(20)]) - await sender.send(Message("shutdown")) - - receive_sessions = [message_processing(queue_client) for _ in range(concurrent_receivers)] - await asyncio.gather(*receive_sessions) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_session_queue(live_config) - loop = asyncio.get_event_loop() - try: - loop.run_until_complete(sample_session_send_receive_with_pool_async(live_config, queue_name)) - finally: - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/receive_deferred_message_queue_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_deferred_message_queue_async.py new file mode 100644 index 000000000000..fdf8c9950709 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_deferred_message_queue_async.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show receiving deferred message from a Service Bus Queue asynchronously. +""" + +# pylint: disable=C0111 + +import os +import asyncio +from azure.servicebus.aio import ServiceBusClient +from azure.servicebus import ReceiveSettleMode + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + + +async def main(): + servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + + async with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME, prefetch=10) + + async with receiver: + received_msgs = await receiver.receive(max_batch_size=10, max_wait_time=5) + deferred_sequenced_numbers = [] + for msg in received_msgs: + print("Deferring msg: {}".format(str(msg))) + deferred_sequenced_numbers.append(msg.sequence_number) + await msg.defer() + + if deferred_sequenced_numbers: + received_deferred_msg = await receiver.receive_deferred_messages( + sequence_numbers=deferred_sequenced_numbers + ) + + for msg in received_deferred_msg: + print("Completing deferred msg: {}".format(str(msg))) + await msg.complete() + else: + print("No messages received.") + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) +print("Receive is done.") diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/receive_iterator_queue_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_iterator_queue_async.py new file mode 100644 index 000000000000..6794684bd1ae --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_iterator_queue_async.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show iterator receiving from a Service Bus Queue asynchronously. +""" + +# pylint: disable=C0111 + +import os +import asyncio +from azure.servicebus.aio import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + + +async def main(): + servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + + async with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME) + async with receiver: + async for msg in receiver: + print(str(msg)) + await msg.complete() + print("Receive is done.") + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/receive_peek_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_peek_async.py new file mode 100644 index 000000000000..675c12512077 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_peek_async.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +ample to show browsing messages currently pending in the queue asynchronously. +""" + +# pylint: disable=C0111 + +import os +import asyncio +from azure.servicebus.aio import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + + +async def main(): + servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + + async with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME) + async with receiver: + received_msgs = await receiver.peek(message_count=2) + for msg in received_msgs: + print(str(msg)) + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/receive_queue_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_queue_async.py new file mode 100644 index 000000000000..1cdee11336f4 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/async_samples/receive_queue_async.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show receiving batch messages from a Service Bus Queue asynchronously. +""" + +# pylint: disable=C0111 + +import os +import asyncio +from azure.servicebus.aio import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + + +async def main(): + servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + + async with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME, prefetch=10) + async with receiver: + received_msgs = await receiver.receive(max_batch_size=10, max_wait_time=5) + for msg in received_msgs: + print(str(msg)) + await msg.complete() + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/sample_code_servicebus_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/sample_code_servicebus_async.py new file mode 100644 index 000000000000..7ea3fe9e993e --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/async_samples/sample_code_servicebus_async.py @@ -0,0 +1,253 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +""" +Examples to show basic async use case of python azure-servicebus SDK, including: + - Create ServiceBusClient + - Create ServiceBusSender/ServiceBusReceiver + - Send single message + - Receive and settle messages + - Receive and settle deferred messages +""" +import os +import asyncio +from azure.servicebus.aio import ServiceBusClient +from azure.servicebus import Message + + +_RUN_ITERATOR = False + + +async def process_message(message): + print(message) + + +def example_create_servicebus_client_async(): + # [START create_sb_client_from_conn_str_async] + import os + from azure.servicebus.aio import ServiceBusClient + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + servicebus_client = ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) + # [END create_sb_client_from_conn_str_async] + + # [START create_sb_client_async] + import os + from azure.servicebus.aio import ServiceBusClient, ServiceBusSharedKeyCredential + fully_qualified_namespace = os.environ['SERVICE_BUS_CONNECTION_STR'] + shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] + shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] + servicebus_client = ServiceBusClient( + fully_qualified_namespace=fully_qualified_namespace, + credential=ServiceBusSharedKeyCredential( + shared_access_policy, + shared_access_key + ) + ) + # [END create_sb_client_async] + return servicebus_client + + +async def example_create_servicebus_sender_async(): + servicebus_client = example_create_servicebus_client_async() + # [START create_servicebus_sender_from_conn_str_async] + import os + from azure.servicebus.aio import ServiceBusSender + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_sender = ServiceBusSender.from_connection_string( + conn_str=servicebus_connection_str, + queue_name=queue_name + ) + # [END create_servicebus_sender_from_conn_str_async] + + # [START create_servicebus_sender_async] + import os + from azure.servicebus.aio import ServiceBusSender, ServiceBusSharedKeyCredential + fully_qualified_namespace = os.environ['SERVICE_BUS_CONNECTION_STR'] + shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] + shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_sender = ServiceBusSender( + fully_qualified_namespace=fully_qualified_namespace, + credential=ServiceBusSharedKeyCredential( + shared_access_policy, + shared_access_key + ), + queue_name=queue_name + ) + # [END create_servicebus_sender_async] + + # [START create_servicebus_sender_from_sb_client_async] + import os + from azure.servicebus.aio import ServiceBusClient + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + servicebus_client = ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) + async with servicebus_client: + queue_sender = servicebus_client.get_queue_sender(queue_name=queue_name) + # [END create_servicebus_sender_from_sb_client_async] + return queue_sender + + +async def example_create_servicebus_receiver_async(): + servicebus_client = example_create_servicebus_client_async() + + # [START create_servicebus_receiver_from_conn_str_async] + import os + from azure.servicebus.aio import ServiceBusReceiver + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_receiver = ServiceBusReceiver.from_connection_string( + conn_str=servicebus_connection_str, + queue_name=queue_name + ) + # [END create_servicebus_receiver_from_conn_str_async] + + # [START create_servicebus_receiver_async] + import os + from azure.servicebus.aio import ServiceBusReceiver, ServiceBusSharedKeyCredential + fully_qualified_namespace = os.environ['SERVICE_BUS_CONNECTION_STR'] + shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] + shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_receiver = ServiceBusReceiver( + fully_qualified_namespace=fully_qualified_namespace, + credential=ServiceBusSharedKeyCredential( + shared_access_policy, + shared_access_key + ), + queue_name=queue_name + ) + # [END create_servicebus_receiver_async] + + # [START create_servicebus_receiver_from_sb_client_async] + import os + from azure.servicebus.aio import ServiceBusClient + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + servicebus_client = ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) + async with servicebus_client: + queue_receiver = servicebus_client.get_queue_receiver(queue_name=queue_name) + # [END create_servicebus_receiver_from_sb_client_async] + + return queue_receiver + + +async def example_send_and_receive_async(): + servicebus_sender = await example_create_servicebus_sender_async() + servicebus_receiver = await example_create_servicebus_receiver_async() + + from azure.servicebus import Message + # [START send_async] + async with servicebus_sender: + message = Message("Hello World") + await servicebus_sender.send(message) + # [END send_async] + + # [START create_batch_async] + async with servicebus_sender: + batch_message = await servicebus_sender.create_batch() + batch_message.add(Message("Single message inside batch")) + # [END create_batch_async] + + # [START peek_messages_async] + async with servicebus_receiver: + messages = await servicebus_receiver.peek() + for message in messages: + print(message) + # [END peek_messages_async] + + # [START receive_async] + async with servicebus_receiver: + messages = await servicebus_receiver.receive(max_wait_time=5) + for message in messages: + print(message) + await message.complete() + # [END receive_async] + + # [START auto_lock_renew_message_async] + from azure.servicebus.aio import AutoLockRenew + + lock_renewal = AutoLockRenew() + async with servicebus_receiver: + async for message in servicebus_receiver: + lock_renewal.register(message, timeout=60) + await process_message(message) + await message.complete() + # [END auto_lock_renew_message_async] + + +async def example_receive_deferred_async(): + servicebus_sender = await example_create_servicebus_sender_async() + servicebus_receiver = await example_create_servicebus_receiver_async() + async with servicebus_sender: + await servicebus_sender.send(Message("Hello World")) + # [START receive_defer_async] + async with servicebus_receiver: + deferred_sequenced_numbers = [] + messages = await servicebus_receiver.receive(max_wait_time=5) + for message in messages: + deferred_sequenced_numbers.append(message.sequence_number) + print(message) + await message.defer() + + received_deferred_msg = await servicebus_receiver.receive_deferred_messages( + sequence_numbers=deferred_sequenced_numbers + ) + + for msg in received_deferred_msg: + await msg.complete() + # [END receive_defer_async] + + +async def example_session_ops_async(): + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + session_id = "" + + async with ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) as servicebus_client: + # [START get_session_async] + async with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + # [END get_session_async] + + # [START get_session_state_async] + async with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + session_state = await session.get_session_state() + # [END get_session_state_async] + + # [START set_session_state_async] + async with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + session_state = await session.set_session_state("START") + # [END set_session_state_async] + + # [START session_renew_lock_async] + async with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + session_state = await session.renew_lock() + # [END session_renew_lock_async] + + # [START auto_lock_renew_session_async] + from azure.servicebus.aio import AutoLockRenew + + lock_renewal = AutoLockRenew() + async with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + # Auto renew session lock for 2 minutes + lock_renewal.register(session, timeout=120) + async for message in receiver: + await process_message(message) + await message.complete() + # [END auto_lock_renew_session_async] + break + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(example_send_and_receive_async()) + loop.run_until_complete(example_receive_deferred_async()) + # loop.run_until_complete(example_session_ops_async()) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/send_queue_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/send_queue_async.py new file mode 100644 index 000000000000..0dfeaecc86e1 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/async_samples/send_queue_async.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show sending message(s) to a Service Bus Queue asynchronously. +""" + +# pylint: disable=C0111 + +import os +import asyncio +from azure.servicebus import Message +from azure.servicebus.aio import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + + +async def send_single_message(sender): + message = Message("DATA" * 64) + await sender.send(message) + + +async def send_batch_message(sender): + batch_message = await sender.create_batch() + while True: + try: + batch_message.add(Message("DATA" * 256)) + except ValueError: + # BatchMessage object reaches max_size. + # New BatchMessage object can be created here to send more data. + break + await sender.send(batch_message) + + +async def main(): + servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + + async with servicebus_client: + sender = servicebus_client.get_queue_sender(queue_name=QUEUE_NAME) + async with sender: + await send_single_message(sender) + await send_batch_message(sender) + + print("Send message is done.") + + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/session_send_receive_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/session_send_receive_async.py new file mode 100644 index 000000000000..c782c71dfd14 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/async_samples/session_send_receive_async.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show sending message(s) to and receiving messages from a Service Bus Queue with session enabled. asynchronously. +""" + +# pylint: disable=C0111 + +import os +import asyncio +from azure.servicebus import Message +from azure.servicebus.aio import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] +SESSION_ID = "" + + +async def send_single_message(sender): + message = Message("DATA" * 64) + message.session_id = SESSION_ID + await sender.send(message) + + +async def send_batch_message(sender): + batch_message = await sender.create_batch() + while True: + try: + message = Message("DATA" * 256) + message.session_id = SESSION_ID + batch_message.add(message) + except ValueError: + # BatchMessage object reaches max_size. + # New BatchMessage object can be created here to send more data. + break + await sender.send(batch_message) + + +async def receive_batch_messages(receiver): + session = receiver.session + await session.set_session_state("START") + print("Session state:", await session.get_session_state()) + received_msgs = await receiver.receive(max_batch_size=10, max_wait_time=5) + for msg in received_msgs: + print(str(msg)) + await msg.complete() + await session.renew_lock() + await session.set_session_state("END") + print("Session state:", await session.get_session_state()) + + +async def main(): + servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + + async with servicebus_client: + sender = servicebus_client.get_queue_sender(queue_name=QUEUE_NAME) + async with sender: + await send_single_message(sender) + await send_batch_message(sender) + + print("Send message is done.") + + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME, session_id=SESSION_ID, prefetch=10) + async with receiver: + await receive_batch_messages(receiver) + + print("Receive is done.") + + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/test_examples_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/test_examples_async.py deleted file mode 100644 index 760010dc6f98..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/async_samples/test_examples_async.py +++ /dev/null @@ -1,426 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import pytest -import asyncio -import logging -import sys -import os -import pytest -import time -from datetime import datetime, timedelta - -from azure.servicebus.aio import ( - ServiceBusClient, - QueueClient, - Message, - BatchMessage, - DeferredMessage, - AutoLockRenew) -from azure.servicebus.common.message import PeekMessage -from azure.servicebus.common.constants import ReceiveSettleMode -from azure.servicebus.common.errors import ( - ServiceBusResourceNotFound, - ServiceBusError, - MessageLockExpired, - InvalidHandlerState, - MessageAlreadySettled, - AutoLockRenewTimeout, - MessageSendFailed, - MessageSettleFailed) - - -async def process_message(message): - print(message) - -@pytest.mark.liveTest -@pytest.mark.asyncio -async def test_async_snippet_queues(live_servicebus_config, standard_queue): - # [START create_async_servicebus_client] - import os - from azure.servicebus.aio import ServiceBusClient, Message - - namespace = os.environ['SERVICE_BUS_HOSTNAME'] - shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] - shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] - - client = ServiceBusClient( - service_namespace=namespace, - shared_access_key_name=shared_access_policy, - shared_access_key_value=shared_access_key) - # [END create_async_servicebus_client] - - # [START create_async_servicebus_client_connstr] - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - - client = ServiceBusClient.from_connection_string(connection_str) - # [END create_async_servicebus_client_connstr] - - # [START get_async_queue_client] - from azure.servicebus import ServiceBusResourceNotFound - - try: - queue_client = client.get_queue("MyQueue") - except ServiceBusResourceNotFound: - pass - # [END get_async_queue_client] - try: - # [START create_queue_client] - import os - from azure.servicebus.aio import QueueClient - - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - queue_client = QueueClient.from_connection_string(connection_str, name="MyQueue") - queue_properties = queue_client.get_properties() - - # [END create_queue_client] - except ServiceBusResourceNotFound: - pass - - queue_client = client.get_queue(standard_queue) - - # [START client_peek_messages] - peeked_messages = await queue_client.peek(count=5) - # [END client_peek_messages] - - await queue_client.send(Message("a")) - try: - # [START client_defer_messages] - sequence_numbers = [] - async with queue_client.get_receiver() as receiver: - async for message in receiver: - sequence_numbers.append(message.sequence_number) - await message.defer() - break - - deferred = await queue_client.receive_deferred_messages(sequence_numbers) - # [END client_defer_messages] - except ValueError: - pass - - await queue_client.send(Message("a")) - try: - sequence_numbers = [] - async with queue_client.get_receiver(idle_timeout=2) as receiver: - async for message in receiver: - sequence_numbers.append(message.sequence_number) - await message.defer() - break - # [START client_settle_deferred_messages] - deferred = await queue_client.receive_deferred_messages(sequence_numbers) - - await queue_client.settle_deferred_messages('completed', deferred) - # [END client_settle_deferred_messages] - except ValueError: - pass - - # [START open_close_sender_directly] - from azure.servicebus.aio import Message - - sender = queue_client.get_sender() - try: - await sender.open() - await sender.send(Message("foobar")) - finally: - await sender.close() - # [END open_close_sender_directly] - - # [START queue_client_send] - from azure.servicebus.aio import Message - - message = Message("Hello World") - await queue_client.send(message) - # [END queue_client_send] - - # [START queue_client_send_multiple] - from azure.servicebus.aio import Message - - messages = [Message("First"), Message("Second")] - await queue_client.send(messages, message_timeout=30) - # [END queue_client_send_multiple] - - # [START open_close_receiver_directly] - receiver = queue_client.get_receiver() - async for message in receiver: - print(message) - break - await receiver.close() - # [END open_close_receiver_directly] - - await queue_client.send(Message("a")) - # [START open_close_receiver_context] - async with queue_client.get_receiver() as receiver: - async for message in receiver: - await process_message(message) - # [END open_close_receiver_context] - break - - # [START open_close_sender_context] - async with queue_client.get_sender() as sender: - - await sender.send(Message("First")) - await sender.send(Message("Second")) - # [END open_close_sender_context] - - # [START queue_sender_messages] - async with queue_client.get_sender() as sender: - - sender.queue_message(Message("First")) - sender.queue_message(Message("Second")) - await sender.send_pending_messages() - # [END queue_sender_messages] - - # [START schedule_messages] - async with queue_client.get_sender() as sender: - - enqueue_time = datetime.utcnow() + timedelta(minutes=10) - await sender.schedule(enqueue_time, Message("First"), Message("Second")) - # [END schedule_messages] - - # [START cancel_schedule_messages] - async with queue_client.get_sender() as sender: - - enqueue_time = datetime.utcnow() + timedelta(minutes=10) - sequence_numbers = await sender.schedule(enqueue_time, Message("First"), Message("Second")) - - await sender.cancel_scheduled_messages(*sequence_numbers) - # [END cancel_schedule_messages] - - # [START receiver_peek_messages] - async with queue_client.get_receiver() as receiver: - pending_messages = await receiver.peek(count=5) - # [END receiver_peek_messages] - - try: - await queue_client.send(Message("a")) - # [START receiver_defer_messages] - async with queue_client.get_receiver() as receiver: - async for message in receiver: - sequence_no = message.sequence_number - await message.defer() - break - - message = await receiver.receive_deferred_messages([sequence_no]) - # [END receiver_defer_messages] - except ServiceBusError: - pass - - await queue_client.send(Message("a")) - # [START receiver_deadletter_messages] - async with queue_client.get_receiver(idle_timeout=5) as receiver: - async for message in receiver: - await message.dead_letter() - - async with queue_client.get_deadletter_receiver() as receiver: - async for message in receiver: - await message.complete() - # [END receiver_deadletter_messages] - break - - # [START receiver_fetch_batch] - async with queue_client.get_receiver(idle_timeout=5, prefetch=100) as receiver: - messages = await receiver.fetch_next(timeout=5) - await asyncio.gather(*[m.complete() for m in messages]) - # [END receiver_fetch_batch] - - # [START auto_lock_renew_async_message] - from azure.servicebus.aio import AutoLockRenew - - lock_renewal = AutoLockRenew() - async with queue_client.get_receiver(idle_timeout=3) as queue_receiver: - async for message in queue_receiver: - lock_renewal.register(message, timeout=60) - await process_message(message) - - await message.complete() - # [END auto_lock_renew_async_message] - -@pytest.mark.liveTest -@pytest.mark.asyncio -async def test_async_snippet_sessions(live_servicebus_config, session_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=session_queue) - queue_client.get_properties() - - # [START open_close_session_sender_context] - from azure.servicebus.aio import Message - - async with queue_client.get_sender(session="MySessionID") as sender: - - await sender.send(Message("First")) - await sender.send(Message("Second")) - # [END open_close_session_sender_context] - - # [START queue_session_sender_messages] - async with queue_client.get_sender(session="MySessionID") as sender: - - sender.queue_message(Message("First")) - sender.queue_message(Message("Second")) - await sender.send_pending_messages() - # [END queue_session_sender_messages] - - # [START schedule_session_messages] - async with queue_client.get_sender(session="MySessionID") as sender: - - enqueue_time = datetime.utcnow() + timedelta(minutes=10) - await sender.schedule(enqueue_time, Message("First"), Message("Second")) - # [END schedule_session_messages] - - # [START open_close_receiver_session_context] - async with queue_client.get_receiver(session="MySessionID") as session: - async for message in session: - await process_message(message) - # [END open_close_receiver_session_context] - break - - # [START open_close_receiver_session_nextavailable] - from azure.servicebus import NEXT_AVAILABLE, NoActiveSession - - try: - async with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) as receiver: - async for message in receiver: - await process_message(message) - except NoActiveSession: - pass - # [END open_close_receiver_session_nextavailable] - - # [START set_session_state] - async with queue_client.get_receiver(session="MySessionID", idle_timeout=5) as session: - current_state = await session.get_session_state() - if not current_state: - await session.set_session_state("OPENED") - # [END set_session_state] - - try: - # [START receiver_peek_session_messages] - async with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) as receiver: - pending_messages = await receiver.peek(count=5) - # [END receiver_peek_session_messages] - except NoActiveSession: - pass - - await queue_client.send([Message("a"), Message("b"), Message("c"), Message("d"), Message("e"), Message("f")], session="MySessionID") - try: - # [START receiver_defer_session_messages] - async with queue_client.get_receiver(session="MySessionID", idle_timeout=5) as receiver: - sequence_numbers = [] - async for message in receiver: - sequence_numbers.append(message.sequence_number) - await message.defer() - break - - message = await receiver.receive_deferred_messages(sequence_numbers) - # [END receiver_defer_session_messages] - except ServiceBusError: - pass - - # [START receiver_renew_session_lock] - async with queue_client.get_receiver(session="MySessionID", idle_timeout=5) as session: - async for message in session: - await process_message(message) - await session.renew_lock() - # [END receiver_renew_session_lock] - break - - # [START auto_lock_renew_async_session] - from azure.servicebus.aio import AutoLockRenew - - lock_renewal = AutoLockRenew() - async with queue_client.get_receiver(session="MySessionID", idle_timeout=3) as session: - lock_renewal.register(session) - - async for message in session: - await process_message(message) - await message.complete() - # [END auto_lock_renew_async_session] - break - -@pytest.mark.liveTest -@pytest.mark.asyncio -async def test_async_snippet_topics(live_servicebus_config, standard_subscription): - topic_name, subscription_name = standard_subscription - - import os - from azure.servicebus.aio import ServiceBusClient - - namespace = os.environ['SERVICE_BUS_HOSTNAME'] - shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] - shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] - - client = ServiceBusClient( - service_namespace=namespace, - shared_access_key_name=shared_access_policy, - shared_access_key_value=shared_access_key) - - # [START get_async_topic_client] - from azure.servicebus import ServiceBusResourceNotFound - - try: - topic_client = client.get_topic("MyTopic") - except ServiceBusResourceNotFound: - pass - # [END get_async_topic_client] - - try: - # [START create_topic_client] - import os - from azure.servicebus.aio import TopicClient - - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - topic_client = TopicClient.from_connection_string(connection_str, name="MyTopic") - topic_properties = topic_client.get_properties() - # [END create_topic_client] - except ServiceBusResourceNotFound: - pass - - # [START get_async_subscription_client] - from azure.servicebus import ServiceBusResourceNotFound - - try: - subscription_client = client.get_subscription("MyTopic", "MySubscription") - except ServiceBusResourceNotFound: - pass - # [END get_async_subscription_client] - - try: - # [START create_sub_client] - import os - from azure.servicebus.aio import SubscriptionClient - - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - subscription_client = SubscriptionClient.from_connection_string(connection_str, name="MySubscription", topic="MyTopic") - properties = subscription_client.get_properties() - # [END create_sub_client] - except ServiceBusResourceNotFound: - pass - -@pytest.mark.liveTest -@pytest.mark.asyncio -async def test_async_sample_queue_send_receive_batch(live_servicebus_config, standard_queue): - try: - from samples.async_samples.example_queue_send_receive_batch_async import sample_queue_send_receive_batch_async - except ImportError: - pytest.skip("") - await sample_queue_send_receive_batch_async(live_servicebus_config, standard_queue) - -@pytest.mark.liveTest -@pytest.mark.asyncio -async def test_async_sample_session_send_receive_batch(live_servicebus_config, session_queue): - try: - from samples.async_samples.example_session_send_receive_batch_async import sample_session_send_receive_batch_async - except ImportError: - pytest.skip("") - await sample_session_send_receive_batch_async(live_servicebus_config, session_queue) - -@pytest.mark.liveTest -@pytest.mark.asyncio -async def test_async_sample_session_send_receive_with_pool(live_servicebus_config, session_queue): - try: - from samples.async_samples.example_session_send_receive_with_pool_async import sample_session_send_receive_with_pool_async - except ImportError: - pytest.skip("") - await sample_session_send_receive_with_pool_async(live_servicebus_config, session_queue) diff --git a/sdk/servicebus/azure-servicebus/samples/async_samples/topic_send_async.py b/sdk/servicebus/azure-servicebus/samples/async_samples/topic_send_async.py deleted file mode 100644 index 2356ce2e2f1a..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/async_samples/topic_send_async.py +++ /dev/null @@ -1,42 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import asyncio -import logging -import sys -import os - -from azure.servicebus.aio import TopicClient -from azure.servicebus.aio import Message - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger - - -logger = get_logger(logging.DEBUG) -connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - - -async def main(): - topic_client = TopicClient.from_connection_string(connection_str, name="pytopic", debug=False) - message = Message(b"sample topic message") - await topic_client.send(message) - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/example_queue_send_receive_batch.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/example_queue_send_receive_batch.py deleted file mode 100644 index e73c0e03e1de..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/sync_samples/example_queue_send_receive_batch.py +++ /dev/null @@ -1,57 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import conftest - -from azure.servicebus import ServiceBusClient -from azure.servicebus import Message -from azure.servicebus.common.constants import ReceiveSettleMode - - -def sample_queue_send_receive_batch(sb_config, queue): - - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_sender() as sender: - for i in range(100): - message = Message("Sample message no. {}".format(i)) - sender.send(message) - - with queue_client.get_receiver(idle_timeout=1, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - - total = 0 - # Receive list of messages as a batch - batch = receiver.fetch_next(max_batch_size=10) - for message in batch: - print("Message: {}".format(message)) - print("Sequence number: {}".format(message.sequence_number)) - message.complete() - total += 1 - - # Receive messages as a continuous generator - for message in receiver: - print("Message: {}".format(message)) - print("Sequence number: {}".format(message.sequence_number)) - message.complete() - total += 1 - - print("Received total {} messages".format(total)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - sample_queue_send_receive_batch(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/example_session_send_receive_batch.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/example_session_send_receive_batch.py deleted file mode 100644 index 193b27e938e9..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/sync_samples/example_session_send_receive_batch.py +++ /dev/null @@ -1,49 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import uuid - -import conftest - -from azure.servicebus import ServiceBusClient -from azure.servicebus import Message - - -def sample_session_send_receive_batch(sb_config, queue): - session_id = str(uuid.uuid4()) - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_sender(session=session_id) as sender: - for i in range(100): - message = Message("Sample message no. {}".format(i)) - sender.send(message) - sender.send(Message("shutdown")) - - - with queue_client.get_receiver(session=session_id) as session: - session.set_session_state("START") - for message in session: - print(message) - message.complete() - if str(message) == "shutdown": - session.set_session_state("END") - break - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_session_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - sample_session_send_receive_batch(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/example_session_send_receive_with_pool.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/example_session_send_receive_with_pool.py deleted file mode 100644 index 28071beeb31c..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/sync_samples/example_session_send_receive_with_pool.py +++ /dev/null @@ -1,75 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import uuid -import concurrent - -import conftest - -from azure.servicebus import ServiceBusClient, Message -from azure.servicebus.common.constants import NEXT_AVAILABLE -from azure.servicebus.common.errors import NoActiveSession - - -def message_processing(queue_client, messages): - while True: - try: - with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=1) as session: - session.set_session_state("OPEN") - for message in session: - messages.append(message) - print("Message: {}".format(message)) - print("Time to live: {}".format(message.header.time_to_live)) - print("Sequence number: {}".format(message.sequence_number)) - print("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - print("Partition ID: {}".format(message.partition_id)) - print("Partition Key: {}".format(message.partition_key)) - print("Locked until: {}".format(message.locked_until)) - print("Lock Token: {}".format(message.lock_token)) - print("Enqueued time: {}".format(message.enqueued_time)) - message.complete() - if str(message) == 'shutdown': - session.set_session_state("CLOSED") - except NoActiveSession: - return - - -def sample_session_send_receive_with_pool(sb_config, queue): - - concurrent_receivers = 5 - sessions = [str(uuid.uuid4()) for i in range(2 * concurrent_receivers)] - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - for session_id in sessions: - with queue_client.get_sender(session=session_id) as sender: - for i in range(20): - message = Message("Sample message no. {}".format(i)) - sender.send(message) - - all_messages = [] - futures = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_receivers) as thread_pool: - for _ in range(concurrent_receivers): - futures.append(thread_pool.submit(message_processing, queue_client, all_messages)) - concurrent.futures.wait(futures) - - print("Received total {} messages across {} sessions.".format(len(all_messages), 2*concurrent_receivers)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_session_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - sample_session_send_receive_with_pool(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_deferred_message_queue.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_deferred_message_queue.py new file mode 100644 index 000000000000..9fd2aae15eec --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_deferred_message_queue.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show receiving deferred message from a Service Bus Queue. +""" + +# pylint: disable=C0111 + +import os +from azure.servicebus import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + +servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + +with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME, prefetch=10) + with receiver: + received_msgs = receiver.receive(max_batch_size=10, max_wait_time=5) + deferred_sequenced_numbers = [] + for msg in received_msgs: + print("Deferring msg: {}".format(str(msg))) + deferred_sequenced_numbers.append(msg.sequence_number) + msg.defer() + + if deferred_sequenced_numbers: + received_deferred_msg = receiver.receive_deferred_messages( + sequence_numbers=deferred_sequenced_numbers + ) + + for msg in received_deferred_msg: + print("Completing deferred msg: {}".format(str(msg))) + msg.complete() + else: + print("No messages received.") + +print("Receive is done.") diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_iterator_queue.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_iterator_queue.py new file mode 100644 index 000000000000..0bd9e52cafb7 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_iterator_queue.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show iterator receiving from a Service Bus Queue. +""" + +# pylint: disable=C0111 + +import os +from azure.servicebus import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + +servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + +with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME) + with receiver: + for msg in receiver: + print(str(msg)) + msg.complete() + +print("Receive is done.") diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_peek.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_peek.py new file mode 100644 index 000000000000..301db5ab28ba --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_peek.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show browsing messages currently pending in the queue. +""" + +# pylint: disable=C0111 + +import os +from azure.servicebus import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + +servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + +with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME) + with receiver: + received_msgs = receiver.peek(message_count=2) + for msg in received_msgs: + print(str(msg)) + +print("Receive is done.") diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_queue.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_queue.py new file mode 100644 index 000000000000..580d725dbc1f --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/sync_samples/receive_queue.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show receiving batch messages from a Service Bus Queue. +""" + +# pylint: disable=C0111 + +import os +from azure.servicebus import ServiceBusClient + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + +servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR) + +with servicebus_client: + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME, prefetch=10) + with receiver: + received_msgs = receiver.receive(max_batch_size=10, max_wait_time=5) + for msg in received_msgs: + print(str(msg)) + msg.complete() + +print("Receive is done.") diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/sample_code_servicebus.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/sample_code_servicebus.py new file mode 100644 index 000000000000..9a00ca1f02eb --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/sync_samples/sample_code_servicebus.py @@ -0,0 +1,275 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +""" +Examples to show basic use case of python azure-servicebus SDK, including: + - Create ServiceBusClient + - Create ServiceBusSender/ServiceBusReceiver + - Send single message + - Receive and settle messages + - Receive and settle deferred messages +""" + +import os +import datetime +from azure.servicebus import ServiceBusClient, Message + + +def process_message(message): + print(message) + + +def example_create_servicebus_client_sync(): + # [START create_sb_client_from_conn_str_sync] + import os + from azure.servicebus import ServiceBusClient + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + servicebus_client = ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) + # [END create_sb_client_from_conn_str_sync] + + # [START create_sb_client_sync] + import os + from azure.servicebus import ServiceBusClient, ServiceBusSharedKeyCredential + fully_qualified_namespace = os.environ['SERVICE_BUS_CONNECTION_STR'] + shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] + shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] + servicebus_client = ServiceBusClient( + fully_qualified_namespace=fully_qualified_namespace, + credential=ServiceBusSharedKeyCredential( + shared_access_policy, + shared_access_key + ) + ) + # [END create_sb_client_sync] + return servicebus_client + + +def example_create_servicebus_sender_sync(): + servicebus_client = example_create_servicebus_client_sync() + # [START create_servicebus_sender_from_conn_str_sync] + import os + from azure.servicebus import ServiceBusSender + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_sender = ServiceBusSender.from_connection_string( + conn_str=servicebus_connection_str, + queue_name=queue_name + ) + # [END create_servicebus_sender_from_conn_str_sync] + + # [START create_servicebus_sender_sync] + import os + from azure.servicebus import ServiceBusSender, ServiceBusSharedKeyCredential + fully_qualified_namespace = os.environ['SERVICE_BUS_CONNECTION_STR'] + shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] + shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_sender = ServiceBusSender( + fully_qualified_namespace=fully_qualified_namespace, + credential=ServiceBusSharedKeyCredential( + shared_access_policy, + shared_access_key + ), + queue_name=queue_name + ) + # [END create_servicebus_sender_sync] + + # [START create_servicebus_sender_from_sb_client_sync] + import os + from azure.servicebus import ServiceBusClient + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + servicebus_client = ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) + with servicebus_client: + queue_sender = servicebus_client.get_queue_sender(queue_name=queue_name) + # [END create_servicebus_sender_from_sb_client_sync] + return queue_sender + + +def example_create_servicebus_receiver_sync(): + servicebus_client = example_create_servicebus_client_sync() + + # [START create_servicebus_receiver_from_conn_str_sync] + import os + from azure.servicebus import ServiceBusReceiver + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_receiver = ServiceBusReceiver.from_connection_string( + conn_str=servicebus_connection_str, + queue_name=queue_name + ) + # [END create_servicebus_receiver_from_conn_str_sync] + + # [START create_servicebus_receiver_sync] + import os + from azure.servicebus import ServiceBusReceiver, ServiceBusSharedKeyCredential + fully_qualified_namespace = os.environ['SERVICE_BUS_CONNECTION_STR'] + shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] + shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + queue_receiver = ServiceBusReceiver( + fully_qualified_namespace=fully_qualified_namespace, + credential=ServiceBusSharedKeyCredential( + shared_access_policy, + shared_access_key + ), + queue_name=queue_name + ) + # [END create_servicebus_receiver_sync] + + # [START create_servicebus_receiver_from_sb_client_sync] + import os + from azure.servicebus import ServiceBusClient + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + servicebus_client = ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) + with servicebus_client: + queue_receiver = servicebus_client.get_queue_receiver(queue_name=queue_name) + # [END create_servicebus_receiver_from_sb_client_sync] + + return queue_receiver + + +def example_send_and_receive_sync(): + servicebus_sender = example_create_servicebus_sender_sync() + servicebus_receiver = example_create_servicebus_receiver_sync() + + from azure.servicebus import Message + # [START send_sync] + with servicebus_sender: + message = Message("Hello World") + servicebus_sender.send(message) + # [END send_sync] + + # [START create_batch_sync] + with servicebus_sender: + batch_message = servicebus_sender.create_batch() + batch_message.add(Message("Single message inside batch")) + # [END create_batch_sync] + + # [START send_complex_message] + message = Message("Hello World!!") + message.session_id = "MySessionID" + message.partition_key = "UsingSpecificPartition" + message.user_properties = {'data': 'custom_data'} + message.time_to_live = datetime.timedelta(seconds=30) + # [END send_complex_message] + + # [START peek_messages_sync] + with servicebus_receiver: + messages = servicebus_receiver.peek() + for message in messages: + print(message) + # [END peek_messages_sync] + + # [START auto_lock_renew_message_sync] + from azure.servicebus import AutoLockRenew + lock_renewal = AutoLockRenew(max_workers=4) + with servicebus_receiver: + for message in servicebus_receiver: + # Auto renew message for 1 minute. + lock_renewal.register(message, timeout=60) + process_message(message) + message.complete() + # [END auto_lock_renew_message_sync] + break + + # [START receive_sync] + with servicebus_receiver: + messages = servicebus_receiver.receive(max_wait_time=5) + for message in messages: + print(message) + message.complete() + # [END receive_sync] + + # [START receive_complex_message] + messages = servicebus_receiver.receive(max_wait_time=5) + for message in messages: + print("Receiving: {}".format(message)) + print("Time to live: {}".format(message.time_to_live)) + print("Sequence number: {}".format(message.sequence_number)) + print("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) + print("Partition ID: {}".format(message.partition_id)) + print("Partition Key: {}".format(message.partition_key)) + print("User Properties: {}".format(message.user_properties)) + print("Annotations: {}".format(message.annotations)) + print("Delivery count: {}".format(message.header.delivery_count)) + print("Message ID: {}".format(message.properties.message_id)) + print("Locked until: {}".format(message.locked_until_utc)) + print("Lock Token: {}".format(message.lock_token)) + print("Enqueued time: {}".format(message.enqueued_time_utc)) + # [END receive_complex_message] + + +def example_receive_deferred_sync(): + servicebus_sender = example_create_servicebus_sender_sync() + servicebus_receiver = example_create_servicebus_receiver_sync() + with servicebus_sender: + servicebus_sender.send(Message("Hello World")) + # [START receive_defer_sync] + with servicebus_receiver: + deferred_sequenced_numbers = [] + messages = servicebus_receiver.receive(max_wait_time=5) + for message in messages: + deferred_sequenced_numbers.append(message.sequence_number) + print(message) + message.defer() + + received_deferred_msg = servicebus_receiver.receive_deferred_messages( + sequence_numbers=deferred_sequenced_numbers + ) + + for msg in received_deferred_msg: + msg.complete() + # [END receive_defer_sync] + + +def example_session_ops_sync(): + servicebus_connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] + queue_name = os.environ['SERVICE_BUS_QUEUE_NAME'] + session_id = "" + + with ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) as servicebus_client: + # [START get_session_sync] + with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + # [END get_session_sync] + + # [START get_session_state_sync] + with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + session_state = session.get_session_state() + # [END get_session_state_sync] + + # [START set_session_state_sync] + with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + session_state = session.set_session_state("START") + # [END set_session_state_sync] + + # [START session_renew_lock_sync] + with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + session_state = session.renew_lock() + # [END session_renew_lock_sync] + + # [START auto_lock_renew_session_sync] + from azure.servicebus import AutoLockRenew + + lock_renewal = AutoLockRenew(max_workers=4) + with servicebus_client.get_queue_receiver(queue_name=queue_name, session_id=session_id) as receiver: + session = receiver.session + # Auto renew session lock for 2 minutes + lock_renewal.register(session, timeout=120) + for message in receiver: + process_message(message) + message.complete() + # [END auto_lock_renew_session_sync] + break + + +example_send_and_receive_sync() +example_receive_deferred_sync() +# example_session_ops_sync() diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/send_queue.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/send_queue.py new file mode 100644 index 000000000000..b553b9212d89 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/sync_samples/send_queue.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show sending message(s) to a Service Bus Queue. +""" + +# pylint: disable=C0111 + +import os +from azure.servicebus import ServiceBusClient, Message + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] + + +def send_single_message(sender): + message = Message("DATA" * 64) + sender.send(message) + + +def send_batch_message(sender): + batch_message = sender.create_batch() + while True: + try: + batch_message.add(Message("DATA" * 256)) + except ValueError: + # BatchMessage object reaches max_size. + # New BatchMessage object can be created here to send more data. + break + sender.send(batch_message) + + +servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR, logging_enable=True) +with servicebus_client: + sender = servicebus_client.get_queue_sender(queue_name=QUEUE_NAME) + with sender: + send_single_message(sender) + send_batch_message(sender) + +print("Send message is done.") diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/session_send_receive.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/session_send_receive.py new file mode 100644 index 000000000000..cddc0a0df1df --- /dev/null +++ b/sdk/servicebus/azure-servicebus/samples/sync_samples/session_send_receive.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Example to show sending message(s) to and receiving messages from a Service Bus Queue with session enabled. +""" + +# pylint: disable=C0111 + +import os +from azure.servicebus import ServiceBusClient, Message + +CONNECTION_STR = os.environ['SERVICE_BUS_CONNECTION_STR'] +QUEUE_NAME = os.environ["SERVICE_BUS_QUEUE_NAME"] +SESSION_ID = "" + + +def send_single_message(sender): + message = Message("DATA" * 64, session_id=SESSION_ID) + sender.send(message) + + +def send_batch_message(sender): + batch_message = sender.create_batch() + while True: + try: + message = Message("DATA" * 256, session_id=SESSION_ID) + batch_message.add(message) + except ValueError: + # BatchMessage object reaches max_size. + # New BatchMessage object can be created here to send more data. + break + sender.send(batch_message) + + +def receive_batch_message(receiver): + session = receiver.session + session.set_session_state("START") + print("Session state:", session.get_session_state()) + received_msgs = receiver.receive(max_batch_size=10, max_wait_time=5) + for msg in received_msgs: + print(str(msg)) + msg.complete() + session.renew_lock() + session.set_session_state("END") + print("Session state:", session.get_session_state()) + + +if __name__ == '__main__': + servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR, logging_enable=True) + with servicebus_client: + sender = servicebus_client.get_queue_sender(queue_name=QUEUE_NAME) + with sender: + send_single_message(sender) + send_batch_message(sender) + + print("Send message is done.") + + receiver = servicebus_client.get_queue_receiver(queue_name=QUEUE_NAME, session_id=SESSION_ID, prefetch=10) + with receiver: + receive_batch_message(receiver) + + print("Receive is done.") + + diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/subscription_receive.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/subscription_receive.py deleted file mode 100644 index 1b17fc4b92aa..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/sync_samples/subscription_receive.py +++ /dev/null @@ -1,43 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import logging -import sys -import os - -from azure.servicebus import SubscriptionClient - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger - - -logger = get_logger(logging.DEBUG) -connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - -if __name__ == '__main__': - - sub_client = SubscriptionClient.from_connection_string( - connection_str, name="pytopic/Subscriptions/pysub", debug=False) - - with sub_client.get_receiver() as receiver: - batch = receiver.fetch_next(timeout=10) - while batch: - print("Received {} messages".format(len(batch))) - for message in batch: - message.complete() - batch = receiver.fetch_next(timeout=10) diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/test_examples.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/test_examples.py deleted file mode 100644 index 0ec83e09d1bc..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/sync_samples/test_examples.py +++ /dev/null @@ -1,507 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import pytest -import datetime -import os - -from azure.servicebus import ServiceBusResourceNotFound, ServiceBusError - -def create_servicebus_client(): - # [START create_servicebus_client] - import os - from azure.servicebus import ServiceBusClient - - namespace = os.environ['SERVICE_BUS_HOSTNAME'] - shared_access_policy = os.environ['SERVICE_BUS_SAS_POLICY'] - shared_access_key = os.environ['SERVICE_BUS_SAS_KEY'] - - # Create a new Service Bus client using SAS credentials - client = ServiceBusClient( - service_namespace=namespace, - shared_access_key_name=shared_access_policy, - shared_access_key_value=shared_access_key) - - # [END create_servicebus_client] - return client - - -def process_message(message): - print(message) - -# TODO: Prior to Track2 release, these should be converted to console-runnable. See EventHubs. -@pytest.mark.liveTest -def test_example_create_servicebus_client(live_servicebus_config): - - client = create_servicebus_client() - - # [START create_servicebus_client_connstr] - from azure.servicebus import ServiceBusClient - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - - client = ServiceBusClient.from_connection_string(connection_str) - # [END create_servicebus_client_connstr] - - try: - # [START get_queue_client] - # Queue Client can be used to send and receive messages - # from an Azure ServiceBus Service - queue_name = 'MyQueue' - queue_client = client.get_queue(queue_name) - # [END get_queue_client] - except ServiceBusResourceNotFound: - pass - - # [START list_queues] - queues = client.list_queues() - # Process the queues - for queue_client in queues: - print(queue_client.name) - # [END list_queues] - - try: - # [START get_topic_client] - # Topic Client can be used to send messages to an Azure ServiceBus Service - topic_name = 'MyTopic' - topic_client = client.get_topic(topic_name) - # [END get_topic_client] - except ServiceBusResourceNotFound: - pass - - # [START list_topics] - topics = client.list_topics() - # Process topics - for topic_client in topics: - print(topic_client.name) - # [END list_topics] - - try: - # [START get_subscription_client] - # Subscription client can receivce messages from Azure Service Bus subscription - subscription_name = 'MySubscription' - subscription_client = client.get_subscription(topic_name, subscription_name) - # [END get_subscription_client] - except ServiceBusResourceNotFound: - pass - - # [START list_subscriptions] - subscriptions = client.list_subscriptions(topic_name) - # Process subscriptions - for sub_client in subscriptions: - print(sub_client.name) - # [END list_subscriptions] - -@pytest.mark.liveTest -def test_example_send_receive_service_bus(live_servicebus_config, standard_queue, session_queue): - import os - import datetime - from azure.servicebus import ServiceBusClient, ServiceBusResourceNotFound - from azure.servicebus import Message - - client = create_servicebus_client() - - try: - # [START create_queue_client_directly] - import os - from azure.servicebus import QueueClient - - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - queue_client = QueueClient.from_connection_string(connection_str, name="MyQueue") - queue_properties = queue_client.get_properties() - - # [END create_queue_client_directly] - except ServiceBusResourceNotFound: - pass - - try: - # [START create_topic_client_directly] - import os - from azure.servicebus import TopicClient - - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - topic_client = TopicClient.from_connection_string(connection_str, name="MyTopic") - properties = topic_client.get_properties() - - # [END create_topic_client_directly] - except ServiceBusResourceNotFound: - pass - - try: - # [START create_sub_client_directly] - import os - from azure.servicebus import SubscriptionClient - - connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - subscription_client = SubscriptionClient.from_connection_string( - connection_str, name="MySub", topic="MyTopic") - properties = subscription_client.get_properties() - - # [END create_sub_client_directly] - except ServiceBusResourceNotFound: - pass - - queue_client = client.get_queue(standard_queue) - session_client = client.get_queue(session_queue) - - # [START get_sender] - with queue_client.get_sender() as queue_sender: - - queue_sender.send(Message("First")) - queue_sender.send(Message("Second")) - # [END get_sender] - - # [START send_message_service_bus_multiple] - from azure.servicebus import Message - - message1 = Message("Hello World!") - message2 = Message("How are you?") - queue_client.send([message1, message2]) - # [END send_message_service_bus_multiple] - - # [START send_complex_message] - message = Message("Hello World!") - message.session_id = "MySessionID" - message.partition_key = "UsingSpecificPartition" - message.user_properties = {'data': 'custom_data'} - message.time_to_live = datetime.timedelta(seconds=30) - - queue_client.send(message) - # [END send_complex_message] - - # [START send_batch_message] - from azure.servicebus import BatchMessage - - def batched_data(): - for i in range(100): - yield "Batched Message no. {}".format(i) - - message = BatchMessage(batched_data()) - results = queue_client.send(message) - # [END send_batch_message] - - # [START send_message_service_bus] - from azure.servicebus import Message - - message = Message("Hello World!") - queue_client.send(message) - # [END send_message_service_bus] - - # [START get_receiver] - with queue_client.get_receiver() as queue_receiver: - messages = queue_receiver.fetch_next(timeout=3) - # [END get_receiver] - - # [START peek_messages_service_bus] - # Specify the number of messages to peek at. - pending_messages = queue_client.peek(count=5) - # [END peek_messages_service_bus] - - # [START auto_lock_renew_message] - from azure.servicebus import AutoLockRenew - - lock_renewal = AutoLockRenew(max_workers=4) - with queue_client.get_receiver(idle_timeout=3) as queue_receiver: - for message in queue_receiver: - # Auto renew message for 1 minute. - lock_renewal.register(message, timeout=60) - process_message(message) - - message.complete() - # [END auto_lock_renew_message] - - # [START auto_lock_renew_session] - from azure.servicebus import AutoLockRenew - - lock_renewal = AutoLockRenew(max_workers=4) - with session_client.get_receiver(session="MySessionID", idle_timeout=3) as session: - # Auto renew session lock for 2 minutes - lock_renewal.register(session, timeout=120) - - for message in session: - process_message(message) - message.complete() - # [END auto_lock_renew_session] - - # [START list_sessions_service_bus] - session_ids = session_client.list_sessions() - - # List sessions updated after specific time - import datetime - yesterday = datetime.datetime.today() - datetime.timedelta(days=1) - session_ids = session_client.list_sessions(updated_since=yesterday) - # [END list_sessions_service_bus] - - try: - # [START receive_deferred_messages_service_bus] - seq_numbers = [] - with queue_client.get_receiver(idle_timeout=3) as queue_receiver: - for message in queue_receiver: - seq_numbers.append(message.sequence_number) - message.defer() - - # Receive deferred messages - provide sequence numbers of - # messages which were deferred. - deferred = queue_client.receive_deferred_messages(sequence_numbers=seq_numbers) - # [END receive_deferred_messages_service_bus] - except ValueError: - pass - deferred = [] - try: - # [START settle_deferred_messages_service_bus] - queue_client.settle_deferred_messages('completed', deferred) - # [END settle_deferred_messages_service_bus] - except ValueError: - pass - - # [START get_dead_letter_receiver] - # Get dead lettered messages - with queue_client.get_deadletter_receiver(idle_timeout=1) as dead_letter_receiver: - - # Receive dead lettered message continuously - for message in dead_letter_receiver: - print(message) - message.complete() - # [END get_dead_letter_receiver] - -@pytest.mark.liveTest -def test_example_receiver_client(live_servicebus_config, standard_queue, session_queue): - import os - import datetime - from azure.servicebus import ServiceBusClient - from azure.servicebus import Message - from azure.servicebus.receive_handler import Receiver, SessionReceiver - - sb_client = create_servicebus_client() - queue_client = sb_client.get_queue(standard_queue) - session_client = sb_client.get_queue(session_queue) - queue_client.send([Message("a"), Message("b"), Message("c"), Message("d"), Message("e"), Message("f")]) - session_client.send([Message("a"), Message("b"), Message("c"), Message("d"), Message("e"), Message("f")], session="MySessionID") - - # [START open_close_receiver_connection] - receiver = queue_client.get_receiver() - for message in receiver: - print(message) - break - receiver.close() - # [END open_close_receiver_connection] - - # [START create_receiver_client] - with queue_client.get_receiver() as receiver: - for message in receiver: - process_message(message) - # [END create_receiver_client] - break - - # [START queue_size] - # Get the number of unprocessed messages in queue - num_unprocessed_msgs = receiver.queue_size - # [END queue_size] - - # [START peek_messages] - # Peek at specific number of messages - with queue_client.get_receiver() as receiver: - receiver.peek(count=5) - # [END peek_messages] - - # [START receive_complex_message] - with queue_client.get_receiver(idle_timeout=3) as receiver: - for message in receiver: - print("Receiving: {}".format(message)) - print("Time to live: {}".format(message.time_to_live)) - print("Sequence number: {}".format(message.sequence_number)) - print("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - print("Partition ID: {}".format(message.partition_id)) - print("Partition Key: {}".format(message.partition_key)) - print("User Properties: {}".format(message.user_properties)) - print("Annotations: {}".format(message.annotations)) - print("Delivery count: {}".format(message.header.delivery_count)) - print("Message ID: {}".format(message.properties.message_id)) - print("Locked until: {}".format(message.locked_until)) - print("Lock Token: {}".format(message.lock_token)) - print("Enqueued time: {}".format(message.enqueued_time)) - # [END receive_complex_message] - - try: - # [START receive_deferred_messages] - seq_numbers = [] - with queue_client.get_receiver(idle_timeout=3) as queue_receiver: - for message in queue_receiver: - seq_numbers.append(message.sequence_number) - message.defer() - - # Receive deferred messages - provide sequence numbers of - # messages which were deferred. - with queue_client.get_receiver() as queue_receiver: - deferred = queue_receiver.receive_deferred_messages(sequence_numbers=seq_numbers) - # [END receive_deferred_messages] - except ValueError: - pass - - # [START fetch_next_messages] - with queue_client.get_receiver(prefetch=200) as queue_receiver: - # Receive messages in Batch (specify the amount ) - messages = queue_receiver.fetch_next(max_batch_size=15, timeout=1) - for m in messages: - print(m.message) - # [END fetch_next_messages] - - - # [START create_session_receiver_client] - with session_client.get_receiver(session="MySessionID") as session: - for message in session: - process_message(message) - # [END create_session_receiver_client] - break - - # [START create_receiver_session_nextavailable] - from azure.servicebus import NEXT_AVAILABLE, NoActiveSession - - try: - with session_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) as receiver: - for message in receiver: - process_message(message) - except NoActiveSession: - pass - # [END create_receiver_session_nextavailable] - - # [START set_session_state] - # Set the session state - with session_client.get_receiver(session="MySessionID") as receiver: - receiver.set_session_state('START') - # [END set_session_state] - - # [START get_session_state] - # Get the session state - with session_client.get_receiver(session="MySessionID") as receiver: - session_state = receiver.get_session_state() - # [END get_session_state] - - # [START renew_lock] - # Renew session lock before it expires - with session_client.get_receiver(session="MySessionID") as session: - messages = session.fetch_next(timeout=3) - session.renew_lock() - # [END renew_lock] - - # [START list_sessions] - # List sessions - with session_client.get_receiver(session="MySessionID") as receiver: - session_ids = receiver.list_sessions() - - # List sessions updated after specific time - today = datetime.datetime.today() - yesterday = today - datetime.timedelta(days=1) - session_ids = receiver.list_sessions(updated_since=yesterday) - # [END list_sessions] - -@pytest.mark.liveTest -def test_example_create_sender_send_message(live_servicebus_config, standard_queue, session_queue): - import os - from azure.servicebus import ServiceBusClient - from azure.servicebus import Message - from azure.servicebus.send_handler import Sender, SessionSender - - sb_client = create_servicebus_client() - queue_client = sb_client.get_queue(standard_queue) - session_client = sb_client.get_queue(session_queue) - - # [START create_sender_client] - from azure.servicebus import Message - - with queue_client.get_sender() as sender: - sender.send(Message("Hello World!")) - - # [END create_sender_client] - - # [START create_session_sender_client] - from azure.servicebus import Message - - with session_client.get_sender(session="MySessionID") as sender: - sender.send(Message("Hello World!")) - - with session_client.get_sender() as sender: - message = Message("Hello World!") - message.session_id = "MySessionID" - sender.send(message) - # [END create_session_sender_client] - - # [START send_message] - # Send the message via sender - with queue_client.get_sender() as sender: - message = Message("Hello World!") - sender.send(message) - # [END send_message] - - # [START scheduling_messages] - with queue_client.get_sender() as sender: - message = Message("Hello World!") - today = datetime.datetime.today() - - # Schedule the message 5 days from today - sequence_numbers = sender.schedule(today + datetime.timedelta(days=5), message) - # [END scheduling_messages] - - # [START cancel_scheduled_messages] - with queue_client.get_sender() as sender: - message = Message("Hello World!") - today = datetime.datetime.today() - - # Schedule the message 5 days from today - sequence_numbers = sender.schedule(today + datetime.timedelta(days=5), message) - - # Cancel scheduled messages - sender.cancel_scheduled_messages(*sequence_numbers) - # [END cancel_scheduled_messages] - - # [START queue_and_send_messages] - with queue_client.get_sender() as sender: - message1 = Message("Hello World!") - message2 = Message("How are you?") - sender.queue_message(message1) - sender.queue_message(message2) - - message_status = sender.send_pending_messages() - for status in message_status: - if not status[0]: - print("Message send failed: {}".format(status[1])) - # [END queue_and_send_messages] - - # [START queue_and_send_session_messages] - with queue_client.get_sender(session="MySessionID") as sender: - message1 = Message("Hello World!") - message2 = Message("How are you?") - sender.queue_message(message1) - sender.queue_message(message2) - - message_status = sender.send_pending_messages() - for status in message_status: - if not status[0]: - print("Message send failed: {}".format(status[1])) - # [END queue_and_send_session_messages] - -@pytest.mark.liveTest -def test_sample_queue_send_receive_batch(live_servicebus_config, standard_queue): - try: - from samples.sync_samples.example_queue_send_receive_batch import sample_queue_send_receive_batch - except ImportError: - pytest.skip("") - sample_queue_send_receive_batch(live_servicebus_config, standard_queue) - -@pytest.mark.liveTest -def test_sample_session_send_receive_batch(live_servicebus_config, session_queue): - try: - from samples.sync_samples.example_session_send_receive_batch import sample_session_send_receive_batch - except ImportError: - pytest.skip("") - sample_session_send_receive_batch(live_servicebus_config, session_queue) - -@pytest.mark.liveTest -def test_sample_session_send_receive_with_pool(live_servicebus_config, session_queue): - try: - from samples.sync_samples.example_session_send_receive_with_pool import sample_session_send_receive_with_pool - except ImportError: - pytest.skip("") - sample_session_send_receive_with_pool(live_servicebus_config, session_queue) diff --git a/sdk/servicebus/azure-servicebus/samples/sync_samples/topic_send.py b/sdk/servicebus/azure-servicebus/samples/sync_samples/topic_send.py deleted file mode 100644 index f3fc3df2f3bc..000000000000 --- a/sdk/servicebus/azure-servicebus/samples/sync_samples/topic_send.py +++ /dev/null @@ -1,39 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- - -import logging -import sys -import os - -from azure.servicebus import TopicClient -from azure.servicebus import Message - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger - - -logger = get_logger(logging.DEBUG) -connection_str = os.environ['SERVICE_BUS_CONNECTION_STR'] - - -if __name__ == '__main__': - - topic_client = TopicClient.from_connection_string(connection_str, name="pytopic", debug=False) - with topic_client.get_sender() as sender: - message = Message(b"sample topic message") - sender.send(message) diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index 6b3ea9795561..a6f20e698434 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -10,18 +10,18 @@ import os import pytest import time +import uuid from datetime import datetime, timedelta from azure.servicebus.aio import ( ServiceBusClient, - QueueClient, - Message, - BatchMessage, - DeferredMessage, + ReceivedMessage, AutoLockRenew) -from azure.servicebus.common.message import PeekMessage -from azure.servicebus.common.constants import ReceiveSettleMode -from azure.servicebus.common.errors import ( +from azure.servicebus._common.message import Message, BatchMessage, PeekMessage +from azure.servicebus._common.constants import ReceiveSettleMode +from azure.servicebus._common.utils import utc_now +from azure.servicebus.exceptions import ( + ServiceBusConnectionError, ServiceBusError, MessageLockExpired, InvalidHandlerState, @@ -31,44 +31,12 @@ MessageSettleFailed) from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer from servicebus_preparer import CachedServiceBusNamespacePreparer, CachedServiceBusQueuePreparer, ServiceBusQueuePreparer - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger +from utilities import get_logger, print_message _logger = get_logger(logging.DEBUG) -def print_message(message): - _logger.info("Receiving: {}".format(message)) - _logger.debug("Time to live: {}".format(message.time_to_live)) - _logger.debug("Sequence number: {}".format(message.sequence_number)) - _logger.debug("Enqueue Sequence number: {}".format(message.enqueue_sequence_number)) - _logger.debug("Partition ID: {}".format(message.partition_id)) - _logger.debug("Partition Key: {}".format(message.partition_key)) - _logger.debug("User Properties: {}".format(message.user_properties)) - _logger.debug("Annotations: {}".format(message.annotations)) - _logger.debug("Delivery count: {}".format(message.header.delivery_count)) - try: - _logger.debug("Locked until: {}".format(message.locked_until)) - _logger.debug("Lock Token: {}".format(message.lock_token)) - except TypeError: - pass - _logger.debug("Enqueued time: {}".format(message.enqueued_time)) - - -class ServiceBusQueueTests(AzureMgmtTestCase): +class ServiceBusQueueAsyncTests(AzureMgmtTestCase): @pytest.mark.liveTest @pytest.mark.live_test_only @@ -76,29 +44,26 @@ class ServiceBusQueueTests(AzureMgmtTestCase): @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - message.enqueue_sequence_number = i - await sender.send(message) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - with pytest.raises(ValueError): - queue_client.get_receiver(session="test", idle_timeout=5) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Handler message no. {}".format(i)) + message.enqueue_sequence_number = i + await sender.send(message) - receiver = queue_client.get_receiver(idle_timeout=5) - count = 0 - async for message in receiver: - print_message(message) - count += 1 - await message.complete() + with pytest.raises(ServiceBusConnectionError): + await (sb_client.get_queue_receiver(servicebus_queue.name, session_id="test", idle_timeout=5))._open_with_retry() - assert count == 10 + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) as receiver: + count = 0 + async for message in receiver: + print_message(_logger, message) + count += 1 + await message.complete() + + assert count == 10 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -106,21 +71,19 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) async def test_github_issue_7079_async(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - sb_client = ServiceBusClient.from_connection_string( - servicebus_namespace_connection_string, debug=False) - queue = sb_client.get_queue(servicebus_queue.name) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - async with queue.get_sender() as sender: - for i in range(5): - await sender.send(Message("Message {}".format(i))) - messages = queue.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - batch = await messages.fetch_next() - count = len(batch) - await messages.reconnect() - async for message in messages: - _logger.debug(message) - count += 1 - assert count == 5 + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + await sender.send(Message("Message {}".format(i))) + async with sb_client.get_queue_receiver(servicebus_queue.name, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) as messages: + batch = await messages.receive() + count = len(batch) + async for message in messages: + _logger.debug(message) + count += 1 + assert count == 5 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -128,21 +91,20 @@ async def test_github_issue_7079_async(self, servicebus_namespace_connection_str @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) async def test_github_issue_6178_async(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - sb_client = ServiceBusClient.from_connection_string( - servicebus_namespace_connection_string, debug=False) - queue = sb_client.get_queue(servicebus_queue.name) - - for i in range(3): - await queue.send(Message("Message {}".format(i))) - - messages = queue.get_receiver(idle_timeout=60) - async for message in messages: - _logger.debug(message) - _logger.debug(message.sequence_number) - _logger.debug(message.enqueued_time) - _logger.debug(message.expired) - await message.complete() - await asyncio.sleep(40) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + await sender.send(Message("Message {}".format(i))) + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=60) as messages: + async for message in messages: + _logger.debug(message) + _logger.debug(message.sequence_number) + _logger.debug(message.enqueued_time_utc) + _logger.debug(message.expired) + await message.complete() + await asyncio.sleep(40) @pytest.mark.liveTest @@ -151,34 +113,31 @@ async def test_github_issue_6178_async(self, servicebus_namespace_connection_str @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) async def test_async_queue_by_queue_client_conn_str_receive_handler_receiveanddelete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - message.enqueue_sequence_number = i - await sender.send(message) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - messages = [] - receiver = queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - async for message in receiver: - messages.append(message) - with pytest.raises(MessageAlreadySettled): - await message.complete() + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Handler message no. {}".format(i)) + message.enqueue_sequence_number = i + await sender.send(message) + + messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) as receiver: + async for message in receiver: + messages.append(message) + with pytest.raises(MessageAlreadySettled): + await message.complete() - assert not receiver.running - assert len(messages) == 10 - time.sleep(30) + assert not receiver._running + assert len(messages) == 10 + time.sleep(30) - messages = [] - receiver = queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - async for message in receiver: - messages.append(message) - assert len(messages) == 0 + messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) as receiver: + async for message in receiver: + messages.append(message) + assert len(messages) == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -186,71 +145,66 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_receiveandde @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) async def test_async_queue_by_queue_client_conn_str_receive_handler_with_stop(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Stop message no. {}".format(i)) - await sender.send(message) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - messages = [] - receiver = queue_client.get_receiver(idle_timeout=5) - async for message in receiver: - messages.append(message) - await message.complete() - if len(messages) >= 5: - break + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Stop message no. {}".format(i)) + await sender.send(message) - assert receiver.running - assert len(messages) == 5 + messages = [] + receiver = sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, prefetch=0) + async with receiver: + async for message in receiver: + messages.append(message) + await message.complete() + if len(messages) >= 5: + break + + assert receiver._running + assert len(messages) == 5 - async with receiver: - async for message in receiver: - messages.append(message) - await message.complete() - if len(messages) >= 5: - break + async with receiver: + async for message in receiver: + messages.append(message) + await message.complete() + if len(messages) >= 5: + break - assert not receiver.running - assert len(messages) == 6 + assert not receiver._running + assert len(messages) == 6 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_iter_messages_simple(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + async def test_async_queue_by_servicebus_client_iter_messages_simple(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = client.get_queue(servicebus_queue.name) - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Iter message no. {}".format(i)) - await sender.send(message) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Iter message no. {}".format(i)) + await sender.send(message) - count = 0 - async for message in receiver: - print_message(message) - await message.complete() - with pytest.raises(MessageAlreadySettled): + count = 0 + async for message in receiver: + print_message(_logger, message) await message.complete() - with pytest.raises(MessageAlreadySettled): - await message.renew_lock() - count += 1 + with pytest.raises(MessageAlreadySettled): + await message.complete() + with pytest.raises(MessageAlreadySettled): + await message.renew_lock() + count += 1 - with pytest.raises(InvalidHandlerState): - await receiver.__anext__() + with pytest.raises(StopAsyncIteration): + await receiver.__anext__() - assert count == 10 + assert count == 10 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -258,482 +212,431 @@ async def test_async_queue_by_servicebus_client_iter_messages_simple(self, servi @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) async def test_async_queue_by_servicebus_conn_str_client_iter_messages_with_abandon(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string, debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Abandoned message no. {}".format(i)) + await sender.send(message) + + count = 0 + async for message in receiver: + print_message(_logger, message) + if not message.header.delivery_count: + count += 1 + await message.abandon() + else: + assert message.header.delivery_count == 1 + await message.complete() - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Abandoned message no. {}".format(i)) - await sender.send(message) + assert count == 10 - count = 0 - async for message in receiver: - print_message(message) - if not message.header.delivery_count: - count += 1 - await message.abandon() - else: - assert message.header.delivery_count == 1 + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + async for message in receiver: + print_message(_logger, message) await message.complete() - - assert count == 10 - - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - async for message in receiver: - print_message(message) - await message.complete() - count += 1 - assert count == 0 + count += 1 + assert count == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_iter_messages_with_defer(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - await sender.send(message) + async def test_async_queue_by_servicebus_client_iter_messages_with_defer(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Deferred message no. {}".format(i)) + await sender.send(message) + + count = 0 + async for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() - count = 0 - async for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - async for message in receiver: - print_message(message) - await message.complete() - count += 1 - assert count == 0 + assert count == 10 + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + async for message in receiver: + print_message(_logger, message) + await message.complete() + count += 1 + assert count == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - await sender.send(message) + async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Deferred message no. {}".format(i)) + await sender.send(message) + + count = 0 + async for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() - count = 0 - async for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 - - deferred = await queue_client.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.PeekLock) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(ValueError): - await message.complete() - with pytest.raises(ValueError): - await queue_client.settle_deferred_messages('foo', deferred) - - await queue_client.settle_deferred_messages('completed', deferred) - with pytest.raises(ServiceBusError): - await queue_client.receive_deferred_messages(deferred_messages) + assert count == 10 + + deferred = await receiver.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + await message.complete() + + with pytest.raises(ServiceBusError): + await receiver.receive_deferred_messages(deferred_messages) @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = await queue_client.send(messages) - assert all(result[0] for result in results) - - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - async for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - assert count == 10 - - async with queue_client.get_receiver(idle_timeout=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - assert message.lock_token - assert message.locked_until - assert message._receiver - await message.renew_lock() - await message.complete() + async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for message in [Message("Deferred message no. {}".format(i)) for i in range(10)]: + results = await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + async for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() + assert count == 10 + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) as session: + deferred = await session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + assert message.lock_token + assert message.locked_until_utc + assert message._receiver + await message.renew_lock() + await message.complete() + @pytest.mark.skip(reason="requires deadletter receiver") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = await queue_client.send(messages) - assert all(result[0] for result in results) - - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for message in [Message("Deferred message no. {}".format(i)) for i in range(10)]: + results = await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + async for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() + + assert count == 10 + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) as session: + deferred = await session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + await message.dead_letter(reason="Testing reason", description="Testing description") + count = 0 - async for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 - - async with queue_client.get_receiver(idle_timeout=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - await message.dead_letter("something") - - count = 0 - async with queue_client.get_deadletter_receiver(idle_timeout=5) as receiver: - async for message in receiver: - count += 1 - print_message(message) - assert message.user_properties[b'DeadLetterReason'] == b'something' - assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' - await message.complete() - assert count == 10 + async with await sb_client.get_deadletter_receiver(idle_timeout=5) as receiver: + async for message in receiver: + count += 1 + print_message(_logger, message) + assert message.user_properties[b'DeadLetterReason'] == b'Testing reason' + assert message.user_properties[b'DeadLetterErrorDescription'] == b'Testing description' + await message.complete() + assert count == 10 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = await queue_client.send(messages) - assert all(result[0] for result in results) - - count = 0 - receiver = queue_client.get_receiver(idle_timeout=5) - async for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 - async with queue_client.get_receiver(idle_timeout=5) as receiver: - deferred = await receiver.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.ReceiveAndDelete) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(MessageAlreadySettled): - await message.complete() - with pytest.raises(ServiceBusError): + async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for message in [Message("Deferred message no. {}".format(i)) for i in range(10)]: + results = await sender.send(message) + + count = 0 + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) as receiver: + async for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() + + assert count == 10 + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.ReceiveAndDelete) as receiver: deferred = await receiver.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + with pytest.raises(MessageAlreadySettled): + await message.complete() + with pytest.raises(ServiceBusError): + deferred = await receiver.receive_deferred_messages(deferred_messages) @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_not_found(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - async with queue_client.get_sender() as sender: - for i in range(3): - message = Message("Deferred message no. {}".format(i)) - await sender.send(message) - - count = 0 - async for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() + async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_not_found(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + message = Message("Deferred message no. {}".format(i)) + await sender.send(message) + + count = 0 + async for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() - assert count == 3 + assert count == 3 - with pytest.raises(ServiceBusError): - deferred = await queue_client.receive_deferred_messages([3, 4], mode=ReceiveSettleMode.PeekLock) + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + with pytest.raises(ServiceBusError): + deferred = await receiver.receive_deferred_messages([3, 4]) - with pytest.raises(ServiceBusError): - deferred = await queue_client.receive_deferred_messages([5, 6, 7], mode=ReceiveSettleMode.PeekLock) + with pytest.raises(ServiceBusError): + deferred = await receiver.receive_deferred_messages([5, 6, 7]) @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_receive_batch_with_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - await sender.send(message) - - count = 0 - messages = await receiver.fetch_next() - while messages: - for message in messages: - print_message(message) + async def test_async_queue_by_servicebus_client_receive_batch_with_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Dead lettered message no. {}".format(i)) + await sender.send(message) + + count = 0 + messages = await receiver.receive() + while messages: + for message in messages: + print_message(_logger, message) + count += 1 + await message.dead_letter(reason="Testing reason", description="Testing description") + messages = await receiver.receive() + + assert count == 10 + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + async for message in receiver: + print_message(_logger, message) + await message.complete() count += 1 - await message.dead_letter(description="Testing") - messages = await receiver.fetch_next() - - assert count == 10 - - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - async for message in receiver: - print_message(message) - await message.complete() - count += 1 - assert count == 0 + assert count == 0 + @pytest.mark.skip(reason="requires deadletter receiver") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_receive_batch_with_retrieve_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - await sender.send(message) - - count = 0 - messages = await receiver.fetch_next() - while messages: - for message in messages: - print_message(message) - await message.dead_letter(description="Testing queue deadletter") + async def test_async_queue_by_servicebus_client_receive_batch_with_retrieve_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Dead lettered message no. {}".format(i)) + await sender.send(message) + + count = 0 + messages = await receiver.receive() + while messages: + for message in messages: + print_message(_logger, message) + await message.dead_letter(reason="Testing reason", description="Testing description") + count += 1 + messages = await receiver.receive() + + with pytest.raises(InvalidHandlerState): + await receiver.receive() + + assert count == 10 + + async with await sb_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + async for message in receiver: + print_message(_logger, message) + await message.complete() count += 1 - messages = await receiver.fetch_next() - - with pytest.raises(InvalidHandlerState): - await receiver.fetch_next() - - assert count == 10 - - async with queue_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - async for message in receiver: - print_message(message) - await message.complete() - count += 1 - assert count == 10 + assert count == 10 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_session_fail(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + async def test_async_queue_by_servicebus_client_session_fail(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = client.get_queue(servicebus_queue.name) - with pytest.raises(ValueError): - queue_client.get_receiver(session="test") + with pytest.raises(ServiceBusConnectionError): + await sb_client.get_queue_receiver(servicebus_queue.name, session_id="test")._open_with_retry() - async with queue_client.get_sender(session="test") as sender: - await sender.send(Message("test session sender")) + async with sb_client.get_queue_sender(servicebus_queue.name, session_id="test") as sender: + await sender.send(Message("test session sender")) @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_browse_messages_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - async with queue_client.get_sender() as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - await sender.send(message) + async def test_async_queue_by_servicebus_client_browse_messages_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i)) + await sender.send(message) - messages = await queue_client.peek(5) - assert len(messages) == 5 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.peek(5) + assert len(messages) == 5 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - async with queue_client.get_sender() as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - await sender.send(message) - - messages = await receiver.peek(5) - assert len(messages) > 0 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + async def test_async_queue_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i)) + await sender.send(message) + + messages = await receiver.peek(5) + assert len(messages) > 0 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_browse_empty_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - messages = await receiver.peek(10) - assert len(messages) == 0 + async def test_async_queue_by_servicebus_client_browse_empty_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: + messages = await receiver.peek(10) + assert len(messages) == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_renew_message_locks(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - messages = [] - locks = 3 - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - async with queue_client.get_sender() as sender: - for i in range(locks): - message = Message("Test message no. {}".format(i)) - await sender.send(message) + async def test_async_queue_by_servicebus_client_renew_message_locks(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + messages = [] + locks = 3 + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(locks): + message = Message("Test message no. {}".format(i)) + await sender.send(message) + + messages.extend(await receiver.receive()) + recv = True + while recv: + recv = await receiver.receive() + messages.extend(recv) - messages.extend(await receiver.fetch_next()) - recv = True - while recv: - recv = await receiver.fetch_next() - messages.extend(recv) - - try: - assert not message.expired - for m in messages: - time.sleep(5) - initial_expiry = m.locked_until - await m.renew_lock() - assert (m.locked_until - initial_expiry) >= timedelta(seconds=5) - finally: - await messages[0].complete() - await messages[1].complete() - time.sleep(30) - with pytest.raises(MessageLockExpired): - await messages[2].complete() + try: + with pytest.raises(AttributeError): + assert not message.expired + for m in messages: + time.sleep(5) + initial_expiry = m.locked_until_utc + await m.renew_lock() + assert (m.locked_until_utc - initial_expiry) >= timedelta(seconds=5) + finally: + await messages[0].complete() + await messages[1].complete() + time.sleep(30) + with pytest.raises(MessageLockExpired): + await messages[2].complete() @pytest.mark.liveTest @pytest.mark.live_test_only @@ -741,457 +644,392 @@ async def test_async_queue_by_servicebus_client_renew_message_locks(self, servic @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) async def test_async_queue_by_queue_client_conn_str_receive_handler_with_autolockrenew(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - async with queue_client.get_sender() as sender: - for i in range(10): - message = Message("{}".format(i)) - await sender.send(message) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - renewer = AutoLockRenew() - messages = [] - async with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - async for message in receiver: - if not messages: - messages.append(message) - assert not message.expired - renewer.register(message, timeout=60) - print("Registered lock renew thread", message.locked_until, datetime.now()) - await asyncio.sleep(50) - print("Finished first sleep", message.locked_until) - assert not message.expired - await asyncio.sleep(25) - print("Finished second sleep", message.locked_until, datetime.now()) - assert message.expired - try: - await message.complete() - raise AssertionError("Didn't raise MessageLockExpired") - except MessageLockExpired as e: - assert isinstance(e.inner_exception, AutoLockRenewTimeout) - else: - if message.expired: - print("Remaining messages", message.locked_until, datetime.now()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("{}".format(i)) + await sender.send(message) + + renewer = AutoLockRenew() + messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: + async for message in receiver: + if not messages: + messages.append(message) + assert not message.expired + renewer.register(message, timeout=60) + print("Registered lock renew thread", message.locked_until_utc, utc_now()) + await asyncio.sleep(50) + print("Finished first sleep", message.locked_until_utc) + assert not message.expired + await asyncio.sleep(25) + await asyncio.sleep(max(0,(message.locked_until_utc - utc_now()).total_seconds())) + print("Finished second sleep", message.locked_until_utc, utc_now()) assert message.expired - with pytest.raises(MessageLockExpired): + try: await message.complete() + raise AssertionError("Didn't raise MessageLockExpired") + except MessageLockExpired as e: + assert isinstance(e.inner_exception, AutoLockRenewTimeout) else: - assert message.header.delivery_count >= 1 - print("Remaining messages", message.locked_until, datetime.now()) - messages.append(message) - await message.complete() - await renewer.shutdown() - assert len(messages) == 11 + if message.expired: + print("Remaining messages", message.locked_until_utc, utc_now()) + assert message.expired + with pytest.raises(MessageLockExpired): + await message.complete() + else: + assert message.header.delivery_count >= 1 + print("Remaining messages", message.locked_until_utc, utc_now()) + messages.append(message) + await message.complete() + await renewer.shutdown() + assert len(messages) == 11 + @pytest.mark.skip(reason='requires queuing messages') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_fail_send_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - try: - queue_client = client.get_queue(servicebus_queue.name) - except MessageSendFailed: - pytest.skip("Open issue for uAMQP on OSX") - - too_large = "A" * 1024 * 512 - results = await queue_client.send(Message(too_large)) - assert len(results) == 1 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) - - async with queue_client.get_sender() as sender: - with pytest.raises(MessageSendFailed): - await sender.send(Message(too_large)) - - async with queue_client.get_sender() as sender: - sender.queue_message(Message(too_large)) - results = await sender.send_pending_messages() - assert len(results) == 1 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) + async def test_async_queue_by_servicebus_client_fail_send_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + too_large = "A" * 1024 * 512 + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(MessageSendFailed): + await sender.send(Message(too_large)) + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + sender.queue_message(Message(too_large)) + results = await sender.send_pending_messages() + assert len(results) == 1 + assert not results[0][0] + assert isinstance(results[0][1], MessageSendFailed) @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_by_servicebus_client_fail_send_batch_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + async def test_async_queue_by_servicebus_client_fail_send_batch_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): pytest.skip("TODO: Pending bugfix in uAMQP") def batch_data(): for i in range(3): yield str(i) * 1024 * 256 - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - results = await queue_client.send(BatchMessage(batch_data())) - assert len(results) == 4 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) - - async with queue_client.get_sender() as sender: - with pytest.raises(MessageSendFailed): - await sender.send(BatchMessage(batch_data())) - - async with queue_client.get_sender() as sender: - sender.queue_message(BatchMessage(batch_data())) - results = await sender.send_pending_messages() - assert len(results) == 4 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(MessageSendFailed): + await sender.send(BatchMessage(batch_data())) + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + sender.queue_message(BatchMessage(batch_data())) + results = await sender.send_pending_messages() + assert len(results) == 4 + assert not results[0][0] + assert isinstance(results[0][1], MessageSendFailed) + @pytest.mark.skip(reason="Requires dead letter receiver") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_message_time_to_live(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - queue_client = client.get_queue(servicebus_queue.name) - - async with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.time_to_live = timedelta(seconds=30) - await sender.send(message) + async def test_async_queue_message_time_to_live(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - time.sleep(30) - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=10) - assert not messages + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id = uuid.uuid4() + message = Message(content) + message.time_to_live = timedelta(seconds=30) + await sender.send(message) - async with queue_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - async for message in receiver: - print_message(message) - await message.complete() - count += 1 - assert count == 1 + time.sleep(30) + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert not messages + + async with await sb_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + async for message in receiver: + print_message(_logger, message) + await message.complete() + count += 1 + assert count == 1 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_duplicate_detection=True, dead_lettering_on_message_expiration=True) - async def test_async_queue_message_duplicate_detection(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - message_id = uuid.uuid4() - queue_client = client.get_queue(servicebus_queue.name) - - async with queue_client.get_sender() as sender: - for i in range(5): - message = Message(str(i)) - message.properties.message_id = message_id - await sender.send(message) + async def test_async_queue_message_duplicate_detection(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - async with queue_client.get_receiver(idle_timeout=5) as receiver: - count = 0 - async for message in receiver: - print_message(message) - assert message.properties.message_id == message_id - await message.complete() - count += 1 - assert count == 1 + message_id = uuid.uuid4() + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message(str(i)) + message.properties.message_id = message_id + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) as receiver: + count = 0 + async for message in receiver: + print_message(_logger, message) + assert message.properties.message_id == message_id + await message.complete() + count += 1 + assert count == 1 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_message_connection_closed(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - queue_client = client.get_queue(servicebus_queue.name) - - async with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - await sender.send(message) - - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=10) - assert len(messages) == 1 - - with pytest.raises(MessageSettleFailed): - await messages[0].complete() + async def test_async_queue_message_connection_closed(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message = Message(content) + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert len(messages) == 1 + + with pytest.raises(MessageSettleFailed): + await messages[0].complete() @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_message_expiry(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - queue_client = client.get_queue(servicebus_queue.name) - - async with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - await sender.send(message) - - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=10) - assert len(messages) == 1 - time.sleep(30) - assert messages[0].expired - with pytest.raises(MessageLockExpired): - await messages[0].complete() - with pytest.raises(MessageLockExpired): - await messages[0].renew_lock() + async def test_async_queue_message_expiry(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message = Message(content) + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert len(messages) == 1 + time.sleep(60) + assert messages[0].expired + with pytest.raises(MessageLockExpired): + await messages[0].complete() + with pytest.raises(MessageLockExpired): + await messages[0].renew_lock() - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=30) - assert len(messages) == 1 - print_message(messages[0]) - assert messages[0].header.delivery_count > 0 - await messages[0].complete() + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=30) + assert len(messages) == 1 + print_message(_logger, messages[0]) + assert messages[0].header.delivery_count > 0 + await messages[0].complete() @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_message_lock_renew(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - queue_client = client.get_queue(servicebus_queue.name) - - async with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - await sender.send(message) - - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=10) - assert len(messages) == 1 - time.sleep(15) - await messages[0].renew_lock() - time.sleep(15) - await messages[0].renew_lock() - time.sleep(15) - assert not messages[0].expired - await messages[0].complete() - - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=10) - assert len(messages) == 0 + async def test_async_queue_message_lock_renew(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message = Message(content) + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert len(messages) == 1 + time.sleep(15) + await messages[0].renew_lock() + time.sleep(15) + await messages[0].renew_lock() + time.sleep(15) + assert not messages[0].expired + await messages[0].complete() + + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert len(messages) == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_message_receive_and_delete(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - - async with queue_client.get_sender() as sender: - message = Message("Receive and delete test") - await sender.send(message) - - async with queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete) as receiver: - messages = await receiver.fetch_next(timeout=10) - assert len(messages) == 1 - received = messages[0] - print_message(received) - with pytest.raises(MessageAlreadySettled): - await received.complete() - with pytest.raises(MessageAlreadySettled): - await received.abandon() - with pytest.raises(MessageAlreadySettled): - await received.defer() - with pytest.raises(MessageAlreadySettled): - await received.dead_letter() - with pytest.raises(MessageAlreadySettled): - await received.renew_lock() - - time.sleep(30) - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=10) - for m in messages: - print_message(m) - assert len(messages) == 0 + async def test_async_queue_message_receive_and_delete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = Message("Receive and delete test") + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name, mode=ReceiveSettleMode.ReceiveAndDelete) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert len(messages) == 1 + received = messages[0] + print_message(_logger, received) + with pytest.raises(MessageAlreadySettled): + await received.complete() + with pytest.raises(MessageAlreadySettled): + await received.abandon() + with pytest.raises(MessageAlreadySettled): + await received.defer() + with pytest.raises(MessageAlreadySettled): + await received.dead_letter() + with pytest.raises(MessageAlreadySettled): + await received.renew_lock() + + time.sleep(30) + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=10) + for m in messages: + print_message(_logger, m) + assert len(messages) == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_message_batch(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - - def message_content(): - for i in range(5): - yield "Message no. {}".format(i) - - - async with queue_client.get_sender() as sender: - message = BatchMessage(message_content()) - await sender.send(message) - - async with queue_client.get_receiver() as receiver: - messages = await receiver.fetch_next(timeout=10) - recv = True - while recv: - recv = await receiver.fetch_next(timeout=10) - messages.extend(recv) + async def test_async_queue_message_batch(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = BatchMessage() + for i in range(5): + message.add(Message("Message no. {}".format(i))) + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = await receiver.receive(max_wait_time=10) + recv = True + while recv: + recv = await receiver.receive(max_wait_time=10) + messages.extend(recv) - assert len(messages) == 5 - for m in messages: - print_message(m) - await m.complete() + assert len(messages) == 5 + for m in messages: + print_message(_logger, m) + await m.complete() + @pytest.mark.skip(reason="requires scheduler") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_schedule_message(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - async with queue_client.get_receiver() as receiver: - async with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.properties.message_id = message_id - message.schedule(enqueue_time) - await sender.send(message) + async def test_async_queue_schedule_message(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id = uuid.uuid4() + message = Message(content) + message.properties.message_id = message_id + message.schedule(enqueue_time) + await sender.send(message) - messages = await receiver.fetch_next(timeout=120) - if messages: - try: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id == message_id - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 1 - finally: - for m in messages: - await m.complete() - else: - raise Exception("Failed to receive scheduled message.") + messages = await receiver.receive(max_wait_time=120) + if messages: + try: + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id == message_id + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 1 + finally: + for m in messages: + await m.complete() + else: + raise Exception("Failed to receive scheduled message.") + @pytest.mark.skip(reason="requires scheduler") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_schedule_multiple_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - messages = [] - async with queue_client.get_receiver(prefetch=20) as receiver: - async with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id_a = uuid.uuid4() - message_a = Message(content) - message_a.properties.message_id = message_id_a - message_id_b = uuid.uuid4() - message_b = Message(content) - message_b.properties.message_id = message_id_b - tokens = await sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - recv = await receiver.fetch_next(timeout=120) - messages.extend(recv) - recv = await receiver.fetch_next(timeout=5) - messages.extend(recv) - if messages: - try: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id in (message_id_a, message_id_b) - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 2 - finally: - for m in messages: - await m.complete() - else: - raise Exception("Failed to receive scheduled message.") + async def test_async_queue_schedule_multiple_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, prefetch=20) as receiver: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id_a = uuid.uuid4() + message_a = Message(content) + message_a.properties.message_id = message_id_a + message_id_b = uuid.uuid4() + message_b = Message(content) + message_b.properties.message_id = message_id_b + tokens = await sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + + recv = await receiver.receive(max_wait_time=120) + messages.extend(recv) + recv = await receiver.receive(max_wait_time=5) + messages.extend(recv) + if messages: + try: + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id in (message_id_a, message_id_b) + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 2 + finally: + for m in messages: + await m.complete() + else: + raise Exception("Failed to receive scheduled message.") + @pytest.mark.skip(reason="requires scheduler") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - async def test_async_queue_cancel_scheduled_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - async with queue_client.get_receiver() as receiver: - async with queue_client.get_sender() as sender: - message_a = Message("Test scheduled message") - message_b = Message("Test scheduled message") - tokens = await sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - await sender.cancel_scheduled_messages(*tokens) - - messages = await receiver.fetch_next(timeout=120) - assert len(messages) == 0 + async def test_async_queue_cancel_scheduled_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + async with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message_a = Message("Test scheduled message") + message_b = Message("Test scheduled message") + tokens = await sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + + await sender.cancel_scheduled_messages(*tokens) + + messages = await receiver.receive(max_wait_time=120) + assert len(messages) == 0 diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_sb_client_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_sb_client_async.py deleted file mode 100644 index 9352ebbeb8ac..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_sb_client_async.py +++ /dev/null @@ -1,28 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import pytest - -from azure.servicebus.aio import ( - ServiceBusClient) -from uamqp.constants import TransportType -from devtools_testutils import AzureMgmtTestCase, RandomNameResourceGroupPreparer -from servicebus_preparer import ( - ServiceBusNamespacePreparer -) - -class ServiceBusClientAsyncTests(AzureMgmtTestCase): - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - def test_servicebusclient_from_conn_str_amqpoverwebsocket_async(self, servicebus_namespace_connection_string, **kwargs): - sb_client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string) - assert sb_client.transport_type == TransportType.Amqp - - websocket_sb_client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string + ';TransportType=AmqpOverWebsocket') - assert websocket_sb_client.transport_type == TransportType.AmqpOverWebsocket diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py index c78bcf5569cd..e0179ce9ff48 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py @@ -13,10 +13,13 @@ import uuid from datetime import datetime, timedelta -from azure.servicebus.aio import ServiceBusClient, QueueClient, Message, DeferredMessage, AutoLockRenew -from azure.servicebus.common.message import PeekMessage -from azure.servicebus.common.constants import ReceiveSettleMode, NEXT_AVAILABLE -from azure.servicebus.common.errors import ( +from uamqp.errors import VendorLinkDetach +from azure.servicebus.aio import ServiceBusClient, ReceivedMessage, AutoLockRenew +from azure.servicebus._common.message import Message, PeekMessage +from azure.servicebus._common.constants import ReceiveSettleMode, NEXT_AVAILABLE +from azure.servicebus._common.utils import utc_now +from azure.servicebus.exceptions import ( + ServiceBusConnectionError, ServiceBusError, NoActiveSession, SessionLockExpired, @@ -25,45 +28,13 @@ MessageAlreadySettled, AutoLockRenewTimeout, MessageSettleFailed) -from devtools_testutils import AzureMgmtTestCase, RandomNameResourceGroupPreparer, CachedResourceGroupPreparer -from servicebus_preparer import ServiceBusNamespacePreparer, ServiceBusTopicPreparer, ServiceBusQueuePreparer, CachedServiceBusNamespacePreparer - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger +from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer +from servicebus_preparer import CachedServiceBusNamespacePreparer, ServiceBusTopicPreparer, ServiceBusQueuePreparer +from utilities import get_logger, print_message _logger = get_logger(logging.DEBUG) -def print_message(message): - _logger.info("Receiving: {}".format(message)) - _logger.debug("Time to live: {}".format(message.time_to_live)) - _logger.debug("Sequence number: {}".format(message.sequence_number)) - _logger.debug("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - _logger.debug("Partition ID: {}".format(message.partition_id)) - _logger.debug("Partition Key: {}".format(message.partition_key)) - _logger.debug("User Properties: {}".format(message.user_properties)) - _logger.debug("Annotations: {}".format(message.annotations)) - _logger.debug("Delivery count: {}".format(message.header.delivery_count)) - try: - _logger.debug("Locked until: {}".format(message.locked_until)) - _logger.debug("Lock Token: {}".format(message.lock_token)) - except TypeError: - pass - _logger.debug("Enqueued time: {}".format(message.enqueued_time)) - - class ServiceBusAsyncSessionTests(AzureMgmtTestCase): @pytest.mark.liveTest @@ -72,30 +43,27 @@ class ServiceBusAsyncSessionTests(AzureMgmtTestCase): @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) async def test_async_session_by_session_client_conn_str_receive_handler_peeklock(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() - - session_id = str(uuid.uuid4()) - async with queue_client.get_sender(session=session_id) as sender: - for i in range(3): - message = Message("Handler message no. {}".format(i)) - await sender.send(message) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - with pytest.raises(ValueError): - session = queue_client.get_receiver(idle_timeout=5) + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + message = Message("Handler message no. {}".format(i), session_id=session_id) + await sender.send(message) + + with pytest.raises(ServiceBusConnectionError): + await sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5)._open_with_retry() - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - count = 0 - async for message in session: - print_message(message) - assert message.session_id == session_id - count += 1 - await message.complete() + session = sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) + count = 0 + async for message in session: + print_message(_logger, message) + assert message.session_id == session_id + count += 1 + await message.complete() - assert count == 3 + assert count == 3 @pytest.mark.liveTest @@ -104,37 +72,33 @@ async def test_async_session_by_session_client_conn_str_receive_handler_peeklock @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) async def test_async_session_by_queue_client_conn_str_receive_handler_receiveanddelete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() - - session_id = str(uuid.uuid4()) - async with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - await sender.send(message) + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Handler message no. {}".format(i), session_id=session_id) + await sender.send(message) - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - async for message in session: - messages.append(message) - assert session_id == session.session_id - assert session_id == message.session_id - with pytest.raises(MessageAlreadySettled): - await message.complete() + messages = [] + session = sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) + async for message in session: + messages.append(message) + assert session_id == session.session.session_id + assert session_id == message.session_id + with pytest.raises(MessageAlreadySettled): + await message.complete() - assert not session.running - assert len(messages) == 10 - time.sleep(30) + assert not session._running + assert len(messages) == 10 + time.sleep(30) - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - async for message in session: - messages.append(message) - assert len(messages) == 0 + messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) as session: + async for message in session: + messages.append(message) + assert len(messages) == 0 @pytest.mark.liveTest @@ -143,42 +107,40 @@ async def test_async_session_by_queue_client_conn_str_receive_handler_receiveand @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) async def test_async_session_by_session_client_conn_str_receive_handler_with_stop(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Stop message no. {}".format(i), session_id=session_id) + await sender.send(message) - session_id = str(uuid.uuid4()) - async with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Stop message no. {}".format(i)) - await sender.send(message) + messages = [] + session = sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) + async with session: + async for message in session: + assert session_id == session.session.session_id + assert session_id == message.session_id + messages.append(message) + await message.complete() + if len(messages) >= 5: + break - messages = [] - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - async for message in session: - assert session_id == session.session_id - assert session_id == message.session_id - messages.append(message) - await message.complete() - if len(messages) >= 5: - break - - assert session.running - assert len(messages) == 5 - - async with session: - async for message in session: - assert session_id == session.session_id - assert session_id == message.session_id - messages.append(message) - await message.complete() - if len(messages) >= 5: - break + assert session._running + assert len(messages) == 5 - assert not session.running - assert len(messages) == 6 + async with session: + async for message in session: + assert session_id == session.session.session_id + assert session_id == message.session_id + messages.append(message) + await message.complete() + if len(messages) >= 5: + break + + assert not session._running + assert len(messages) == 6 @pytest.mark.liveTest @@ -187,15 +149,12 @@ async def test_async_session_by_session_client_conn_str_receive_handler_with_sto @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) async def test_async_session_by_session_client_conn_str_receive_handler_with_no_session(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - session = queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) - with pytest.raises(NoActiveSession): - await session.open() + session = sb_client.get_queue_receiver(servicebus_queue.name, session_id=NEXT_AVAILABLE, idle_timeout=5) + with pytest.raises(NoActiveSession): + await session._open_with_retry() @pytest.mark.liveTest @@ -204,20 +163,18 @@ async def test_async_session_by_session_client_conn_str_receive_handler_with_no_ @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) async def test_async_session_by_session_client_conn_str_receive_handler_with_inactive_session(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - async for message in session: - messages.append(message) + session_id = str(uuid.uuid4()) + messages = [] + session = sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) + async with session: + async for message in session: + messages.append(message) - assert not session.running - assert len(messages) == 0 + assert not session._running + assert len(messages) == 0 @pytest.mark.liveTest @@ -225,128 +182,80 @@ async def test_async_session_by_session_client_conn_str_receive_handler_with_ina @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = await queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - async for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 - - async with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - assert message.lock_token - assert not message.locked_until - assert message._receiver - with pytest.raises(TypeError): - await message.renew_lock() - await message.complete() + async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for message in [Message("Deferred message no. {}".format(i), session_id=session_id) for i in range(10)]: + await sender.send(message) - @pytest.mark.liveTest - @pytest.mark.live_test_only - @CachedResourceGroupPreparer(name_prefix='servicebustest') - @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = await queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - async for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 - - async with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - await message.dead_letter("something") - - count = 0 - async with queue_client.get_deadletter_receiver(idle_timeout=5) as receiver: - async for message in receiver: - count += 1 - print_message(message) - assert message.user_properties[b'DeadLetterReason'] == b'something' - assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' - await message.complete() - assert count == 10 + count = 0 + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + async for message in session: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() + assert count == 10 + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + deferred = await session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + assert message.lock_token + assert not message.locked_until_utc + assert message._receiver + with pytest.raises(TypeError): + await message.renew_lock() + await message.complete() + + @pytest.mark.skip(reason='requires dead letter receiver') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = await queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - async for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 - async with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.ReceiveAndDelete) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(MessageAlreadySettled): - await message.complete() - with pytest.raises(ServiceBusError): + async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for message in [Message("Deferred message no. {}".format(i), session_id=session_id) for i in range(10)]: + await sender.send(message) + + count = 0 + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + async for message in session: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() + + assert count == 10 + + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: deferred = await session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + await message.dead_letter(reason="Testing reason", description="Testing description") + + count = 0 + async with sb_client.get_deadletter_receiver(idle_timeout=5) as receiver: + async for message in receiver: + count += 1 + print_message(_logger, message) + assert message.user_properties[b'DeadLetterReason'] == b'Testing reason' + assert message.user_properties[b'DeadLetterErrorDescription'] == b'Testing description' + await message.complete() + assert count == 10 @pytest.mark.liveTest @@ -354,36 +263,34 @@ async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_de @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - async with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - await sender.send(message) - - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - count = 0 - async for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - await message.defer() - - assert count == 10 + async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for message in [Message("Deferred message no. {}".format(i), session_id=session_id) for i in range(10)]: + await sender.send(message) - with pytest.raises(ValueError): - deferred = await queue_client.receive_deferred_messages(deferred_messages, session=session_id) + count = 0 + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + async for message in session: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + await message.defer() - with pytest.raises(ValueError): - await queue_client.settle_deferred_messages("completed", [message], session=session_id) + assert count == 10 + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5, mode=ReceiveSettleMode.ReceiveAndDelete) as session: + deferred = await session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + with pytest.raises(MessageAlreadySettled): + await message.complete() + with pytest.raises(ServiceBusError): + deferred = await session.receive_deferred_messages(deferred_messages) @pytest.mark.liveTest @@ -391,74 +298,68 @@ async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_de @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_fetch_next_with_retrieve_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - async with queue_client.get_receiver(session=session_id, idle_timeout=5, prefetch=10) as receiver: + async def test_async_session_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - async with queue_client.get_sender(session=session_id) as sender: + deferred_messages = [] + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) + message = Message("Deferred message no. {}".format(i), session_id=session_id) await sender.send(message) - count = 0 - messages = await receiver.fetch_next() - while messages: - for message in messages: - print_message(message) - await message.dead_letter(description="Testing queue deadletter") - count += 1 - messages = await receiver.fetch_next() - assert count == 10 - - async with queue_client.get_deadletter_receiver(idle_timeout=5) as session: + session = sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) count = 0 async for message in session: - print_message(message) - #assert message.user_properties[b'DeadLetterReason'] == b'something' # TODO - #assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' # TODO - await message.complete() + deferred_messages.append(message.sequence_number) + print_message(_logger, message) count += 1 - assert count == 10 + await message.defer() + + assert count == 10 + + with pytest.raises(MessageSettleFailed): + await message.complete() + @pytest.mark.skip(reason='requires deadletter receiver') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_browse_messages_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - async with queue_client.get_sender(session=session_id) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - await sender.send(message) - - with pytest.raises(ValueError): - messages = await queue_client.peek(5) - - messages = await queue_client.peek(5, session=session_id) - assert len(messages) == 5 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + async def test_async_session_by_servicebus_client_fetch_next_with_retrieve_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5, prefetch=10) as receiver: + + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Dead lettered message no. {}".format(i), session_id=session_id) + await sender.send(message) + + count = 0 + messages = await receiver.receive() + while messages: + for message in messages: + print_message(_logger, message) + await message.dead_letter(reason="Testing reason", description="Testing description") + count += 1 + messages = await receiver.receive() + assert count == 10 + + async with sb_client.get_deadletter_receiver(idle_timeout=5) as session: + count = 0 + async for message in session: + print_message(_logger, message) + #assert message.user_properties[b'DeadLetterReason'] == b'Testing reason' # TODO + #assert message.user_properties[b'DeadLetterErrorDescription'] == b'Testing description' # TODO + await message.complete() + count += 1 + assert count == 10 @pytest.mark.liveTest @@ -466,29 +367,24 @@ async def test_async_session_by_servicebus_client_browse_messages_client(self, s @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - async with queue_client.get_receiver(idle_timeout=5, session=session_id) as receiver: - async with queue_client.get_sender(session=session_id) as sender: + async def test_async_session_by_servicebus_client_browse_messages_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: for i in range(5): - message = Message("Test message no. {}".format(i)) + message = Message("Test message no. {}".format(i), session_id=session_id) await sender.send(message) - messages = await receiver.peek(5) - assert len(messages) > 0 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = await receiver.peek(5) + assert len(messages) == 5 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() @pytest.mark.liveTest @@ -496,46 +392,66 @@ async def test_async_session_by_servicebus_client_browse_messages_with_receiver( @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_renew_client_locks(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + async def test_async_session_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, session_id=session_id) as receiver: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i), session_id=session_id) + await sender.send(message) + + messages = await receiver.peek(5) + assert len(messages) > 0 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - messages = [] - locks = 3 - async with queue_client.get_receiver(session=session_id, prefetch=10) as receiver: - async with queue_client.get_sender(session=session_id) as sender: - for i in range(locks): - message = Message("Test message no. {}".format(i)) - await sender.send(message) - messages.extend(await receiver.fetch_next()) - recv = True - while recv: - recv = await receiver.fetch_next(timeout=5) - messages.extend(recv) + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) + async def test_async_session_by_servicebus_client_renew_client_locks(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + messages = [] + locks = 3 + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, prefetch=10) as receiver: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(locks): + message = Message("Test message no. {}".format(i), session_id=session_id) + await sender.send(message) + + messages.extend(await receiver.receive()) + recv = True + while recv: + recv = await receiver.receive(max_wait_time=5) + messages.extend(recv) - try: - for m in messages: - with pytest.raises(TypeError): - expired = m.expired - assert m.locked_until is None - assert m.lock_token is None - time.sleep(5) - initial_expiry = receiver.locked_until - await receiver.renew_lock() - assert (receiver.locked_until - initial_expiry) >= timedelta(seconds=5) - finally: - await messages[0].complete() - await messages[1].complete() - time.sleep(40) - with pytest.raises(SessionLockExpired): - await messages[2].complete() + try: + for m in messages: + with pytest.raises(TypeError): + expired = m.expired + assert m.locked_until_utc is None + assert m.lock_token is not None + time.sleep(5) + initial_expiry = receiver.session.locked_until_utc + await receiver.session.renew_lock() + assert (receiver.session.locked_until_utc - initial_expiry) >= timedelta(seconds=5) + finally: + await messages[0].complete() + await messages[1].complete() + time.sleep(70) #TODO: BUG: Was 40 + with pytest.raises(SessionLockExpired): + await messages[2].complete() @pytest.mark.liveTest @@ -544,52 +460,49 @@ async def test_async_session_by_servicebus_client_renew_client_locks(self, servi @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) async def test_async_session_by_conn_str_receive_handler_with_autolockrenew(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + session_id = str(uuid.uuid4()) - session_id = str(uuid.uuid4()) - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - async with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("{}".format(i)) - await sender.send(message) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("{}".format(i), session_id=session_id) + await sender.send(message) - renewer = AutoLockRenew() - messages = [] - async with queue_client.get_receiver(session=session_id, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=20) as session: - renewer.register(session, timeout=60) - print("Registered lock renew thread", session.locked_until, datetime.now()) - with pytest.raises(SessionLockExpired): - async for message in session: - if not messages: - await asyncio.sleep(45) - print("First sleep {}".format(session.locked_until - datetime.now())) - assert not session.expired - with pytest.raises(TypeError): - message.expired - assert message.locked_until is None - with pytest.raises(TypeError): - await message.renew_lock() - assert message.lock_token is None - await message.complete() - messages.append(message) - - elif len(messages) == 1: - await asyncio.sleep(45) - print("Second sleep {}".format(session.locked_until - datetime.now())) - assert session.expired - assert isinstance(session.auto_renew_error, AutoLockRenewTimeout) - try: + renewer = AutoLockRenew() + messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=20) as session: + renewer.register(session.session, timeout=60) + print("Registered lock renew thread", session.session.locked_until_utc, utc_now()) + with pytest.raises(SessionLockExpired): + async for message in session: + if not messages: + await asyncio.sleep(45) + print("First sleep {}".format(session.session.locked_until_utc - utc_now())) + assert not session.session.expired + with pytest.raises(TypeError): + message.expired + assert message.locked_until_utc is None + with pytest.raises(TypeError): + await message.renew_lock() + assert message.lock_token is not None await message.complete() - raise AssertionError("Didn't raise SessionLockExpired") - except SessionLockExpired as e: - assert isinstance(e.inner_exception, AutoLockRenewTimeout) - messages.append(message) + messages.append(message) - await renewer.shutdown() - assert len(messages) == 2 + elif len(messages) == 1: + await asyncio.sleep(45) + print("Second sleep {}".format(session.session.locked_until_utc - utc_now())) + assert session.session.expired + assert isinstance(session.session.auto_renew_error, AutoLockRenewTimeout) + try: + await message.complete() + raise AssertionError("Didn't raise SessionLockExpired") + except SessionLockExpired as e: + assert isinstance(e.inner_exception, AutoLockRenewTimeout) + messages.append(message) + + await renewer.shutdown() + assert len(messages) == 2 @pytest.mark.liveTest @@ -597,28 +510,23 @@ async def test_async_session_by_conn_str_receive_handler_with_autolockrenew(self @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_message_connection_closed(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + async def test_async_session_message_connection_closed(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) + session_id = str(uuid.uuid4()) - async with queue_client.get_sender() as sender: - message = Message("test") - message.session_id = session_id - await sender.send(message) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = Message("test") + message.session_id = session_id + await sender.send(message) - async with queue_client.get_receiver(session=session_id) as receiver: - messages = await receiver.fetch_next(timeout=10) - assert len(messages) == 1 + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert len(messages) == 1 - with pytest.raises(MessageSettleFailed): - await messages[0].complete() + with pytest.raises(MessageSettleFailed): + await messages[0].complete() @pytest.mark.liveTest @@ -626,42 +534,38 @@ async def test_async_session_message_connection_closed(self, servicebus_namespac @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_message_expiry(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) - - async with queue_client.get_sender() as sender: - message = Message("Testing expired messages") - message.session_id = session_id - await sender.send(message) - - async with queue_client.get_receiver(session=session_id) as receiver: - messages = await receiver.fetch_next(timeout=10) - assert len(messages) == 1 - print_message(messages[0]) - await asyncio.sleep(30) - with pytest.raises(TypeError): - messages[0].expired - with pytest.raises(TypeError): - await messages[0].renew_lock() - assert receiver.expired - with pytest.raises(SessionLockExpired): - await messages[0].complete() - with pytest.raises(SessionLockExpired): - await receiver.renew_lock() + async def test_async_session_message_expiry(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) - async with queue_client.get_receiver(session=session_id) as receiver: - messages = await receiver.fetch_next(timeout=30) - assert len(messages) == 1 - print_message(messages[0]) - #assert messages[0].header.delivery_count # TODO confirm this with service - await messages[0].complete() + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = Message("Testing expired messages") + message.session_id = session_id + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = await receiver.receive(max_wait_time=10) + assert len(messages) == 1 + print_message(_logger, messages[0]) + await asyncio.sleep(60) #TODO: Was 30, but then lock isn't expired. + with pytest.raises(TypeError): + messages[0].expired + with pytest.raises(TypeError): + await messages[0].renew_lock() + assert receiver.session.expired + with pytest.raises(SessionLockExpired): + await messages[0].complete() + with pytest.raises(SessionLockExpired): + await receiver.session.renew_lock() + + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = await receiver.receive(max_wait_time=30) + assert len(messages) == 1 + print_message(_logger, messages[0]) + #assert messages[0].header.delivery_count # TODO confirm this with service + await messages[0].complete() @pytest.mark.liveTest @@ -669,125 +573,112 @@ async def test_async_session_message_expiry(self, servicebus_namespace, serviceb @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_schedule_message(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - async with queue_client.get_sender(session=session_id) as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.properties.message_id = message_id - message.schedule(enqueue_time) - await sender.send(message) - - messages = [] - renewer = AutoLockRenew() - async with queue_client.get_receiver(session=session_id) as receiver: - renewer.register(receiver, timeout=140) - messages.extend(await receiver.fetch_next(timeout=120)) - messages.extend(await receiver.fetch_next(timeout=5)) - if messages: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id == message_id - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 1 - else: - raise Exception("Failed to receive schdeduled message.") - await renewer.shutdown() - + async def test_async_session_schedule_message(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + import uuid + session_id = str(uuid.uuid4()) + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id = uuid.uuid4() + message = Message(content, session_id=session_id) + message.properties.message_id = message_id + message.schedule(enqueue_time) + await sender.send(message) + messages = [] + renewer = AutoLockRenew() + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + renewer.register(receiver.session, timeout=140) + messages.extend(await receiver.receive(max_wait_time=120)) + messages.extend(await receiver.receive(max_wait_time=5)) + if messages: + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id == message_id + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 1 + else: + raise Exception("Failed to receive schdeduled message.") + await renewer.shutdown() + + + @pytest.mark.skip(reason='requires scheduling functionality') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_schedule_multiple_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - messages = [] - async with queue_client.get_sender(session=session_id) as sender: - content = str(uuid.uuid4()) - message_id_a = uuid.uuid4() - message_a = Message(content) - message_a.properties.message_id = message_id_a - message_id_b = uuid.uuid4() - message_b = Message(content) - message_b.properties.message_id = message_id_b - tokens = await sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - renewer = AutoLockRenew() - async with queue_client.get_receiver(session=session_id, prefetch=20) as receiver: - renewer.register(receiver, timeout=140) - messages.extend(await receiver.fetch_next(timeout=120)) - messages.extend(await receiver.fetch_next(timeout=5)) - if messages: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id in (message_id_a, message_id_b) - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 2 - else: - raise Exception("Failed to receive schdeduled message.") - await renewer.shutdown() - - + async def test_async_session_schedule_multiple_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + import uuid + session_id = str(uuid.uuid4()) + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + messages = [] + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id_a = uuid.uuid4() + message_a = Message(content, session_id=session_id) + message_a.properties.message_id = message_id_a + message_id_b = uuid.uuid4() + message_b = Message(content, session_id=session_id) + message_b.properties.message_id = message_id_b + tokens = await sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + + renewer = AutoLockRenew() + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, prefetch=20) as receiver: + renewer.register(receiver.session, timeout=140) + messages.extend(await receiver.receive(max_wait_time=120)) + messages.extend(await receiver.receive(max_wait_time=5)) + if messages: + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id in (message_id_a, message_id_b) + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 2 + else: + raise Exception("Failed to receive schdeduled message.") + await renewer.shutdown() + + + @pytest.mark.skip(reasion="requires scheduling") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_cancel_scheduled_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - async with queue_client.get_sender(session=session_id) as sender: - message_a = Message("Test scheduled message") - message_b = Message("Test scheduled message") - tokens = await sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - await sender.cancel_scheduled_messages(*tokens) - - renewer = AutoLockRenew() - messages = [] - async with queue_client.get_receiver(session=session_id) as receiver: - renewer.register(receiver, timeout=140) - messages.extend(await receiver.fetch_next(timeout=120)) - messages.extend(await receiver.fetch_next(timeout=5)) - try: - assert len(messages) == 0 - except AssertionError: - for m in messages: - print(str(m)) - await m.complete() - raise - await renewer.shutdown() + async def test_async_session_cancel_scheduled_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message_a = Message("Test scheduled message", session_id=session_id) + message_b = Message("Test scheduled message", session_id=session_id) + tokens = await sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + await sender.cancel_scheduled_messages(*tokens) + + renewer = AutoLockRenew() + messages = [] + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + renewer.register(receiver.session, timeout=140) + messages.extend(await receiver.receive(max_wait_time=120)) + messages.extend(await receiver.receive(max_wait_time=5)) + try: + assert len(messages) == 0 + except AssertionError: + for m in messages: + print(str(m)) + await m.complete() + raise + await renewer.shutdown() @pytest.mark.liveTest @@ -796,96 +687,83 @@ async def test_async_session_cancel_scheduled_messages(self, servicebus_namespac @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) async def test_async_session_get_set_state_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - session_id = str(uuid.uuid4()) - queue_client.get_properties() - async with queue_client.get_sender(session=session_id) as sender: - for i in range(3): - message = Message("Handler message no. {}".format(i)) - await sender.send(message) + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - async with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - assert await session.get_session_state() == None - await session.set_session_state("first_state") - count = 0 - async for m in session: - assert m.properties.group_id == session_id.encode('utf-8') - count += 1 - with pytest.raises(InvalidHandlerState): - await session.get_session_state() - assert count == 3 + session_id = str(uuid.uuid4()) + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + message = Message("Handler message no. {}".format(i), session_id=session_id) + await sender.send(message) + + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + assert await session.session.get_session_state() == None + await session.session.set_session_state("first_state") + count = 0 + async for m in session: + assert m.properties.group_id == session_id.encode('utf-8') + count += 1 + await session.session.get_session_state() + assert count == 3 + @pytest.mark.skip(reason='Requires list sessions') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_list_sessions_with_receiver(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - sessions = [] - start_time = datetime.now() - for i in range(5): - sessions.append(str(uuid.uuid4())) - - for session in sessions: - async with queue_client.get_sender(session=session) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - await sender.send(message) - for session in sessions: - async with queue_client.get_receiver(session=session) as receiver: - await receiver.set_session_state("SESSION {}".format(session)) + async def test_async_session_by_servicebus_client_list_sessions_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - async with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - current_sessions = await receiver.list_sessions(updated_since=start_time) - assert len(current_sessions) == 5 - assert current_sessions == sessions + sessions = [] + start_time = utc_now() + for i in range(5): + sessions.append(str(uuid.uuid4())) + for session in sessions: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i), session_id=session) + await sender.send(message) + for session in sessions: + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session) as receiver: + await receiver.session.set_session_state("SESSION {}".format(session)) + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=NEXT_AVAILABLE, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + current_sessions = await receiver.list_sessions(updated_since=start_time) + assert len(current_sessions) == 5 + assert current_sessions == sessions + + + @pytest.mark.skip(reason="requires list_session") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_list_sessions_with_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - sessions = [] - start_time = datetime.now() - for i in range(5): - sessions.append(str(uuid.uuid4())) - - for session in sessions: - async with queue_client.get_sender(session=session) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - await sender.send(message) - for session in sessions: - async with queue_client.get_receiver(session=session) as receiver: - await receiver.set_session_state("SESSION {}".format(session)) + async def test_async_session_by_servicebus_client_list_sessions_with_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - current_sessions = await queue_client.list_sessions(updated_since=start_time) - assert len(current_sessions) == 5 - assert current_sessions == sessions + sessions = [] + start_time = utc_now() + for i in range(5): + sessions.append(str(uuid.uuid4())) + + for session in sessions: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i), session_id=session) + await sender.send(message) + for session in sessions: + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session) as receiver: + await receiver.session.set_session_state("SESSION {}".format(session)) + + current_sessions = await sb_client.list_sessions(updated_since=start_time) + assert len(current_sessions) == 5 + assert current_sessions == sessions @pytest.mark.liveTest @@ -893,14 +771,14 @@ async def test_async_session_by_servicebus_client_list_sessions_with_client(self @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - async def test_async_session_by_servicebus_client_session_pool(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + async def test_async_session_by_servicebus_client_session_pool(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): messages = [] errors = [] - async def message_processing(queue_client): + async def message_processing(sb_client): while True: try: - async with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) as session: + async with sb_client.get_queue_receiver(servicebus_queue.name, session_id=NEXT_AVAILABLE, idle_timeout=5) as session: async for message in session: print("Message: {}".format(message)) messages.append(message) @@ -913,19 +791,15 @@ async def message_processing(queue_client): concurrent_receivers = 5 sessions = [str(uuid.uuid4()) for i in range(concurrent_receivers)] - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - for session_id in sessions: - async with queue_client.get_sender(session=session_id) as sender: - await asyncio.gather(*[sender.send(Message("Sample message no. {}".format(i))) for i in range(20)]) - - receive_sessions = [message_processing(queue_client) for _ in range(concurrent_receivers)] - await asyncio.gather(*receive_sessions, return_exceptions=True) - - assert not errors - assert len(messages) == 100 \ No newline at end of file + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + for session_id in sessions: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + await asyncio.gather(*[sender.send(Message("Sample message no. {}".format(i), session_id=session_id)) for i in range(20)]) + + receive_sessions = [message_processing(sb_client) for _ in range(concurrent_receivers)] + await asyncio.gather(*receive_sessions, return_exceptions=True) + + assert not errors + assert len(messages) == 100 \ No newline at end of file diff --git a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicequeue.py b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicequeue.py index 9b7e9967ed21..e79958e00c3c 100644 --- a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicequeue.py +++ b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicequeue.py @@ -7,7 +7,7 @@ """ How To: Create a Queue ---------------------- ->>> from azure.servicebus.control_client import * +>>> from azure.servicebus._control_client import * >>> bus_service = ServiceBusService(shared_access_key_name=key_name, shared_access_key_value=key_value, 'owner') >>> bus_service.create_queue('taskqueue') True diff --git a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicetopic.py b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicetopic.py index fbe5bc841c99..2a3fb5d166ee 100644 --- a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicetopic.py +++ b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/doctest_servicebusservicetopic.py @@ -7,7 +7,7 @@ """ How to Create a Topic --------------------- ->>> from azure.servicebus.control_client import * +>>> from azure.servicebus._control_client import * >>> bus_service = ServiceBusService(shared_access_key_name=key_name, shared_access_key_value=key_value, 'owner') >>> bus_service.create_topic('mytopic') True diff --git a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_eventhub.py b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_eventhub.py index 243d726529a5..9544d03ee9bf 100644 --- a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_eventhub.py +++ b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_eventhub.py @@ -18,7 +18,7 @@ from azure.common import ( AzureMissingResourceHttpError, ) -from azure.servicebus.control_client import ( +from azure.servicebus._control_client import ( AuthorizationRule, EventHub, ServiceBusService, diff --git a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_servicebus.py b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_servicebus.py index ab7eb0ee792a..ef3feb36ced5 100644 --- a/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_servicebus.py +++ b/sdk/servicebus/azure-servicebus/tests/control_plane_tests/test_servicebus_servicebus.py @@ -19,8 +19,8 @@ AzureMissingResourceHttpError, AzureConflictHttpError, ) -from azure.servicebus.control_client._http import HTTPError -from azure.servicebus.control_client import ( +from azure.servicebus._control_client._http import HTTPError +from azure.servicebus._control_client import ( AZURE_SERVICEBUS_NAMESPACE, AZURE_SERVICEBUS_ACCESS_KEY, AZURE_SERVICEBUS_ISSUER, diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_base.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_base.py new file mode 100644 index 000000000000..554d1b380c8d --- /dev/null +++ b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_base.py @@ -0,0 +1,182 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +#-------------------------------------------------------------------------- + +import time +from datetime import datetime, timedelta +import concurrent +import sys +import uuid + +from azure.servicebus import ServiceBusClient, Message, BatchMessage +from azure.servicebus._common.constants import ReceiveSettleMode +from azure.servicebus.exceptions import MessageAlreadySettled + +class ReceiveType: + push="push" + pull="pull" + + +class StressTestResults(object): + def __init__(self): + self.total_sent=0 + self.total_received=0 + self.time_elapsed=None + self.state_by_sender={} + self.state_by_receiver={} + + def __repr__(self): + return str(vars(self)) + + +class StressTestRunnerState(object): + '''Per-runner state, e.g. if you spawn 3 senders each will have this as their state object, + which will be coalesced at completion into StressTestResults''' + def __init__(self): + self.total_sent=0 + self.total_received=0 + + def __repr__(self): + return str(vars(self)) + + +class StressTestRunner: + '''Framework for running a service bus stress test. + Duration can be overriden via the --stress_test_duration flag from the command line''' + + def __init__(self, + senders, + receivers, + duration = timedelta(minutes=15), + receive_type = ReceiveType.push, + send_batch_size = None, + message_size = 10, + idle_timeout = 10, + send_delay = .01, + receive_delay = 0): + self.senders = senders + self.receivers = receivers + self.duration=duration + self.receive_type = receive_type + self.message_size = message_size + self.send_batch_size = send_batch_size + self.idle_timeout = idle_timeout + self.send_delay = send_delay + self.receive_delay = receive_delay + + # Because of pickle we need to create a state object and not just pass around ourselves. + # If we ever require multiple runs of this one after another, just make Run() reset this. + self._state = StressTestRunnerState() + + self._duration_override = None + for arg in sys.argv: + if arg.startswith('--stress_test_duration_seconds='): + self._duration_override = timedelta(seconds=int(arg.split('=')[1])) + + + # Plugin functions the caller can override to further tailor the test. + @staticmethod + def OnSend(state, sent_message): + '''Called on every successful send''' + pass + + + @staticmethod + def OnReceive(state, received_message): + '''Called on every successful receive''' + pass + + + @staticmethod + def OnComplete(send_results=[], receive_results=[]): + '''Called on stress test run completion''' + pass + + + @staticmethod + def PreProcessMessage(message): + '''Allows user to transform the message before batching or sending it.''' + pass + + + @staticmethod + def PreProcessMessageBatch(message): + '''Allows user to transform the batch before sending it.''' + pass + + + @staticmethod + def PreProcessMessageBody(payload): + '''Allows user to transform message payload before sending it.''' + return payload + + + def _ConstructMessage(self): + if self.send_batch_size != None: + batch = BatchMessage() + for _ in range(self.send_batch_size): + message = Message(self.PreProcessMessageBody("a" * self.message_size)) + self.PreProcessMessage(message) + batch.add(message) + self.PreProcessMessageBatch(batch) + return batch + else: + message = Message(self.PreProcessMessageBody("a" * self.message_size)) + self.PreProcessMessage(message) + return message + + + def _Send(self, sender, end_time): + with sender: + while end_time > datetime.utcnow(): + message = self._ConstructMessage() + sender.send(message) + self.OnSend(self._state, message) + self._state.total_sent += 1 + time.sleep(self.send_delay) + return self._state + + + def _Receive(self, receiver, end_time): + receiver._config.idle_timeout = self.idle_timeout + with receiver: + while end_time > datetime.utcnow(): + if self.receive_type == ReceiveType.pull: + batch = receiver.receive() + elif self.receive_type == ReceiveType.push: + batch = receiver + + for message in batch: + self.OnReceive(self._state, message) + try: + message.complete() + except MessageAlreadySettled: # It may have been settled in the plugin callback. + pass + self._state.total_received += 1 + #TODO: Get EnqueuedTimeUtc out of broker properties and calculate latency. Should properties/app properties be mostly None? + if end_time <= datetime.utcnow(): + break + time.sleep(self.receive_delay) + return self._state + + + def Run(self): + start_time = datetime.utcnow() + end_time = start_time + (self._duration_override or self.duration) + sent_messages = 0 + received_messages = 0 + with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: + senders = [proc_pool.submit(self._Send, sender, end_time) for sender in self.senders] + receivers = [proc_pool.submit(self._Receive, receiver, end_time) for receiver in self.receivers] + + result = StressTestResults() + result.state_by_sender = {s:f.result() for s,f in zip(self.senders, concurrent.futures.as_completed(senders))} + result.state_by_receiver = {r:f.result() for r,f in zip(self.receivers, concurrent.futures.as_completed(receivers))} + result.total_sent = sum([r.total_sent for r in result.state_by_sender.values()]) + result.total_received = sum([r.total_received for r in result.state_by_receiver.values()]) + result.time_elapsed = end_time - start_time + print("Stress test completed. Results:\n", result) + return result + diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_peeklock_send_receive.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_peeklock_send_receive.py deleted file mode 100644 index cffcf53a6174..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_peeklock_send_receive.py +++ /dev/null @@ -1,119 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import os -import time -import uuid -from datetime import datetime, timedelta -import concurrent - -from azure.servicebus import ServiceBusClient -from azure.servicebus.common.message import BatchMessage -from azure.servicebus.common.constants import ReceiveSettleMode - - -def create_standard_queue(sb_config): - from azure.servicebus.control_client import ServiceBusService, Queue - queue_name = str(uuid.uuid4()) - queue_value = Queue( - lock_duration='PT30S', - requires_duplicate_detection=False, - dead_lettering_on_message_expiration=True, - requires_session=False) - client = ServiceBusService( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key']) - if client.create_queue(queue_name, queue=queue_value, fail_on_exist=True): - return queue_name - raise ValueError("Queue creation failed.") - - -def cleanup_queue(servicebus_config, queue_name): - from azure.servicebus.control_client import ServiceBusService - client = ServiceBusService( - service_namespace=servicebus_config['hostname'], - shared_access_key_name=servicebus_config['key_name'], - shared_access_key_value=servicebus_config['access_key']) - client.delete_queue(queue_name) - - -def message_send_process(sb_config, queue, endtime): - - def message_batch(): - for i in range(5): - yield "Stress Test message no. {}".format(i) - - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - total = 0 - queue_client = client.get_queue(queue) - with queue_client.get_sender() as sender: - while endtime > datetime.now(): - message = BatchMessage(message_batch()) - sender.send(message) - total += 5 - time.sleep(0.01) - if total % 50 == 0: - print("Sent {} messages".format(total)) - return total - - -def message_receive_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_receiver(idle_timeout=10, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - total = 0 - for message in receiver: - message.complete() - total += 1 - if total % 50 == 0: - print("Received {} messages".format(total)) - if endtime <= datetime.now(): - break - - return total - - -def stress_test_queue_peeklock_send_receive(sb_config, queue): - starttime = datetime.now() - endtime = starttime + timedelta(hours=24) - sent_messages = 0 - received_messages = 0 - - with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: - senders = [proc_pool.submit(message_send_process, sb_config, queue, endtime) for i in range(2)] - receivers = [proc_pool.submit(message_receive_process, sb_config, queue, endtime) for i in range(2)] - - for done in concurrent.futures.as_completed(senders + receivers): - if done in senders: - sent_messages += done.result() - else: - received_messages += done.result() - print("Sent {} messages and received {} messages.".format(sent_messages, received_messages)) - - -if __name__ == '__main__': - live_config = {} - live_config['hostname'] = os.environ['SERVICE_BUS_HOSTNAME'] - live_config['key_name'] = os.environ['SERVICE_BUS_SAS_POLICY'] - live_config['access_key'] = os.environ['SERVICE_BUS_SAS_KEY'] - try: - test_queue = create_standard_queue(live_config) - print("Created queue {}".format(test_queue)) - stress_test_queue_peeklock_send_receive(live_config, test_queue) - finally: - print("Cleaning up queue {}".format(test_queue)) - cleanup_queue(live_config, test_queue) diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_peeklock_send_receive_batch.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_peeklock_send_receive_batch.py deleted file mode 100644 index 634f4085b8df..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_peeklock_send_receive_batch.py +++ /dev/null @@ -1,89 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import time -from datetime import datetime, timedelta -import concurrent - -import conftest -from azure.servicebus import ServiceBusClient -from azure.servicebus.common.message import BatchMessage -from azure.servicebus.common.constants import ReceiveSettleMode - - -def message_send_process(sb_config, queue, endtime): - - def message_batch(): - for i in range(5): - yield "Stress Test message no. {}".format(i) - - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - total = 0 - queue_client = client.get_queue(queue) - with queue_client.get_sender() as sender: - while endtime > datetime.now(): - message = BatchMessage(message_batch()) - sender.send(message) - total += 5 - time.sleep(0.01) - return total - - -def message_receive_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_receiver(idle_timeout=10, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - total = 0 - batch = receiver.fetch_next() - while batch: - for message in batch: - message.complete() - total += 1 - if endtime <= datetime.now(): - break - batch = receiver.fetch_next() - - return total - - -def stress_test_queue_peeklock_send_receive_batch(sb_config, queue): - starttime = datetime.now() - endtime = starttime + timedelta(seconds=30) - sent_messages = 0 - received_messages = 0 - - with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: - senders = [proc_pool.submit(message_send_process, sb_config, queue, endtime) for i in range(2)] - receivers = [proc_pool.submit(message_receive_process, sb_config, queue, endtime) for i in range(2)] - - for done in concurrent.futures.as_completed(senders + receivers): - if done in senders: - sent_messages += done.result() - else: - received_messages += done.result() - - print("Sent {} messages and received {} messages.".format(sent_messages, received_messages)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - stress_test_queue_peeklock_send_receive_batch(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_receivedelete_send_receive.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_receivedelete_send_receive.py deleted file mode 100644 index ecca0a43cb83..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_receivedelete_send_receive.py +++ /dev/null @@ -1,85 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import time -from datetime import datetime, timedelta -import concurrent - -import conftest -from azure.servicebus import ServiceBusClient -from azure.servicebus.common.message import BatchMessage -from azure.servicebus.common.constants import ReceiveSettleMode - - -def message_send_process(sb_config, queue, endtime): - - def message_batch(): - for i in range(5): - yield "Stress Test message no. {}".format(i) - - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - total = 0 - queue_client = client.get_queue(queue) - with queue_client.get_sender() as sender: - while endtime > datetime.now(): - message = BatchMessage(message_batch()) - sender.send(message) - total += 5 - time.sleep(0.01) - return total - - -def message_receive_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_receiver(idle_timeout=10, mode=ReceiveSettleMode.ReceiveAndDelete, prefetch=10) as receiver: - total = 0 - for _ in receiver: - total += 1 - if endtime <= datetime.now(): - break - - return total - - -def stress_test_queue_receivedelete_send_receive(sb_config, queue): - starttime = datetime.now() - endtime = starttime + timedelta(seconds=130) - sent_messages = 0 - received_messages = 0 - - with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: - senders = [proc_pool.submit(message_send_process, sb_config, queue, endtime) for i in range(2)] - receivers = [proc_pool.submit(message_receive_process, sb_config, queue, endtime) for i in range(2)] - - for done in concurrent.futures.as_completed(senders + receivers): - if done in senders: - sent_messages += done.result() - else: - received_messages += done.result() - - print("Sent {} messages and received {} messages.".format(sent_messages, received_messages)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - stress_test_queue_receivedelete_send_receive(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_receivedelete_send_receive_batch.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_receivedelete_send_receive_batch.py deleted file mode 100644 index 87b52d771eb9..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_receivedelete_send_receive_batch.py +++ /dev/null @@ -1,88 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import time -from datetime import datetime, timedelta -import concurrent - -import conftest -from azure.servicebus import ServiceBusClient -from azure.servicebus.common.message import BatchMessage -from azure.servicebus.common.constants import ReceiveSettleMode - - -def message_send_process(sb_config, queue, endtime): - - def message_batch(): - for i in range(5): - yield "Stress Test message no. {}".format(i) - - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - total = 0 - queue_client = client.get_queue(queue) - with queue_client.get_sender() as sender: - while endtime > datetime.now(): - message = BatchMessage(message_batch()) - sender.send(message) - total += 5 - time.sleep(0.01) - return total - - -def message_receive_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_receiver(idle_timeout=10, mode=ReceiveSettleMode.ReceiveAndDelete, prefetch=10) as receiver: - total = 0 - batch = receiver.fetch_next() - while batch: - for _ in batch: - total += 1 - if endtime <= datetime.now(): - break - batch = receiver.fetch_next() - - return total - - -def stress_test_queue_receivedelete_send_receive_batch(sb_config, queue): - starttime = datetime.now() - endtime = starttime + timedelta(seconds=30) - sent_messages = 0 - received_messages = 0 - - with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: - senders = [proc_pool.submit(message_send_process, sb_config, queue, endtime) for i in range(2)] - receivers = [proc_pool.submit(message_receive_process, sb_config, queue, endtime) for i in range(2)] - - for done in concurrent.futures.as_completed(senders + receivers): - if done in senders: - sent_messages += done.result() - else: - received_messages += done.result() - - print("Sent {} messages and received {} messages.".format(sent_messages, received_messages)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - stress_test_queue_receivedelete_send_receive_batch(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_reconnect_send_receive.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_reconnect_send_receive.py deleted file mode 100644 index 154c47b53672..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_reconnect_send_receive.py +++ /dev/null @@ -1,77 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import time -from datetime import datetime, timedelta -import concurrent - -import conftest -from azure.servicebus import ServiceBusClient, Message - - -def message_send_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - total = 0 - queue_client = client.get_queue(queue) - while endtime > datetime.now(): - queue_client.send(Message("Slow stress test message")) - total += 1 - time.sleep(3) - return total - - -def message_receive_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - total = 0 - while endtime > datetime.now(): - with queue_client.get_receiver() as receiver: - batch = receiver.fetch_next() - for message in batch: - total += 1 - message.complete() - - return total - - -def stress_test_queue_slow_send_receive(sb_config, queue): - starttime = datetime.now() - endtime = starttime + timedelta(seconds=30) - sent_messages = 0 - received_messages = 0 - - with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: - senders = [proc_pool.submit(message_send_process, sb_config, queue, endtime) for i in range(1)] - receivers = [proc_pool.submit(message_receive_process, sb_config, queue, endtime) for i in range(1)] - - for done in concurrent.futures.as_completed(senders + receivers): - if done in senders: - sent_messages += done.result() - else: - received_messages += done.result() - - print("Sent {} messages and received {} messages.".format(sent_messages, received_messages)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - stress_test_queue_slow_send_receive(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_slow_send_receive.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_slow_send_receive.py deleted file mode 100644 index 1c13ae52d91f..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_slow_send_receive.py +++ /dev/null @@ -1,79 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import time -from datetime import datetime, timedelta -import concurrent - -import conftest -from azure.servicebus import ServiceBusClient, Message -from azure.servicebus.common.constants import ReceiveSettleMode - - -def message_send_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - total = 0 - queue_client = client.get_queue(queue) - with queue_client.get_sender() as sender: - while endtime > datetime.now(): - sender.send(Message("Slow stress test message")) - total += 1 - time.sleep(3600) - return total - - -def message_receive_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_receiver(mode=ReceiveSettleMode.PeekLock) as receiver: - total = 0 - for message in receiver: - message.complete() - total += 1 - if endtime <= datetime.now(): - break - - return total - - -def stress_test_queue_slow_send_receive(sb_config, queue): - starttime = datetime.now() - endtime = starttime + timedelta(hours=3) - sent_messages = 0 - received_messages = 0 - - with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: - senders = [proc_pool.submit(message_send_process, sb_config, queue, endtime) for i in range(1)] - receivers = [proc_pool.submit(message_receive_process, sb_config, queue, endtime) for i in range(1)] - - for done in concurrent.futures.as_completed(senders + receivers): - if done in senders: - sent_messages += done.result() - else: - received_messages += done.result() - - print("Sent {} messages and received {} messages.".format(sent_messages, received_messages)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - stress_test_queue_slow_send_receive(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_slow_send_receive_batch.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_slow_send_receive_batch.py deleted file mode 100644 index c92fd6ba93f6..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/stress_tests/stress_test_queue_slow_send_receive_batch.py +++ /dev/null @@ -1,82 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import time -from datetime import datetime, timedelta -import concurrent - -import conftest -from azure.servicebus import ServiceBusClient, Message -from azure.servicebus.common.constants import ReceiveSettleMode - - -def message_send_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - total = 0 - queue_client = client.get_queue(queue) - with queue_client.get_sender() as sender: - while endtime > datetime.now(): - sender.send(Message("Slow stress test message")) - total += 1 - time.sleep(3600) - return total - - -def message_receive_process(sb_config, queue, endtime): - client = ServiceBusClient( - service_namespace=sb_config['hostname'], - shared_access_key_name=sb_config['key_name'], - shared_access_key_value=sb_config['access_key'], - debug=False) - - queue_client = client.get_queue(queue) - with queue_client.get_receiver(mode=ReceiveSettleMode.PeekLock) as receiver: - total = 0 - batch = receiver.fetch_next() - while batch: - for message in batch: - message.complete() - total += 1 - if endtime <= datetime.now(): - break - batch = receiver.fetch_next() - - return total - - -def stress_test_queue_slow_send_receive(sb_config, queue): - starttime = datetime.now() - endtime = starttime + timedelta(hours=3) - sent_messages = 0 - received_messages = 0 - - with concurrent.futures.ProcessPoolExecutor(max_workers=4) as proc_pool: - senders = [proc_pool.submit(message_send_process, sb_config, queue, endtime) for i in range(1)] - receivers = [proc_pool.submit(message_receive_process, sb_config, queue, endtime) for i in range(2)] - - for done in concurrent.futures.as_completed(senders + receivers): - if done in senders: - sent_messages += done.result() - else: - received_messages += done.result() - - print("Sent {} messages and received {} messages.".format(sent_messages, received_messages)) - - -if __name__ == '__main__': - live_config = conftest.get_live_servicebus_config() - queue_name = conftest.create_standard_queue(live_config) - print("Created queue {}".format(queue_name)) - try: - stress_test_queue_slow_send_receive(live_config, queue_name) - finally: - print("Cleaning up queue {}".format(queue_name)) - conftest.cleanup_queue(live_config, queue_name) diff --git a/sdk/servicebus/azure-servicebus/tests/stress_tests/test_stress_queues.py b/sdk/servicebus/azure-servicebus/tests/stress_tests/test_stress_queues.py new file mode 100644 index 000000000000..befd1e7d89d8 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/tests/stress_tests/test_stress_queues.py @@ -0,0 +1,116 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +#-------------------------------------------------------------------------- + +from datetime import datetime, timedelta +import logging +import pytest +import sys +import time + +from azure.servicebus import ServiceBusClient +from azure.servicebus._common.constants import ReceiveSettleMode + +from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer + +from servicebus_preparer import ServiceBusNamespacePreparer, ServiceBusQueuePreparer +from stress_tests.stress_test_base import StressTestRunner, ReceiveType +from utilities import get_logger + +_logger = get_logger(logging.DEBUG) + +class ServiceBusQueueStressTests(AzureMgmtTestCase): + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @ServiceBusQueuePreparer(name_prefix='servicebustest') + def test_stress_queue_send_and_receive(self, servicebus_namespace_connection_string, servicebus_queue): + sb_client = ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, debug=False) + + stress_test = StressTestRunner(senders = [sb_client.get_queue_sender(servicebus_queue.name)], + receivers = [sb_client.get_queue_receiver(servicebus_queue.name)], + duration=timedelta(seconds=60)) + + result = stress_test.Run() + assert(result.total_sent > 0) + assert(result.total_received > 0) + + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @ServiceBusQueuePreparer(name_prefix='servicebustest') + def test_stress_queue_send_and_pull_receive(self, servicebus_namespace_connection_string, servicebus_queue): + sb_client = ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, debug=False) + + stress_test = StressTestRunner(senders = [sb_client.get_queue_sender(servicebus_queue.name)], + receivers = [sb_client.get_queue_receiver(servicebus_queue.name)], + receive_type=ReceiveType.pull, + duration=timedelta(seconds=60)) + + result = stress_test.Run() + assert(result.total_sent > 0) + assert(result.total_received > 0) + + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @ServiceBusQueuePreparer(name_prefix='servicebustest') + def test_stress_queue_batch_send_and_receive(self, servicebus_namespace_connection_string, servicebus_queue): + sb_client = ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, debug=False) + + stress_test = StressTestRunner(senders = [sb_client.get_queue_sender(servicebus_queue.name)], + receivers = [sb_client.get_queue_receiver(servicebus_queue.name)], + duration=timedelta(seconds=60), + send_batch_size=5) + + result = stress_test.Run() + assert(result.total_sent > 0) + assert(result.total_received > 0) + + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @ServiceBusQueuePreparer(name_prefix='servicebustest') + def test_stress_queue_slow_send_and_receive(self, servicebus_namespace_connection_string, servicebus_queue): + sb_client = ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, debug=False) + + stress_test = StressTestRunner(senders = [sb_client.get_queue_sender(servicebus_queue.name)], + receivers = [sb_client.get_queue_receiver(servicebus_queue.name)], + duration=timedelta(seconds=3501*3), + send_delay=3500) + + result = stress_test.Run() + assert(result.total_sent > 0) + assert(result.total_received > 0) + + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @ServiceBusQueuePreparer(name_prefix='servicebustest') + def test_stress_queue_receive_and_delete(self, servicebus_namespace_connection_string, servicebus_queue): + sb_client = ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, debug=False) + + stress_test = StressTestRunner(senders = [sb_client.get_queue_sender(servicebus_queue.name)], + receivers = [sb_client.get_queue_receiver(servicebus_queue.name, mode=ReceiveSettleMode.ReceiveAndDelete)], + duration=timedelta(seconds=60)) + + result = stress_test.Run() + assert(result.total_sent > 0) + assert(result.total_received > 0) \ No newline at end of file diff --git a/sdk/servicebus/azure-servicebus/tests/test_partitioned_queues.py b/sdk/servicebus/azure-servicebus/tests/test_partitioned_queues.py deleted file mode 100644 index 208245bd742a..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/test_partitioned_queues.py +++ /dev/null @@ -1,1011 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import logging -import sys -import os -import pytest -import time -from datetime import datetime, timedelta - -from azure.servicebus import ServiceBusClient, QueueClient, AutoLockRenew -from azure.servicebus.common.message import Message, PeekMessage, BatchMessage, DeferredMessage -from azure.servicebus.common.constants import ReceiveSettleMode -from azure.servicebus.common.errors import ( - ServiceBusError, - MessageLockExpired, - InvalidHandlerState, - MessageAlreadySettled, - AutoLockRenewTimeout, - MessageSendFailed, - MessageSettleFailed) - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger - -_logger = get_logger(logging.DEBUG) - - -def print_message(message): - _logger.info("Receiving: {}".format(message)) - _logger.debug("Time to live: {}".format(message.time_to_live)) - _logger.debug("Sequence number: {}".format(message.sequence_number)) - _logger.debug("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - _logger.debug("Partition ID: {}".format(message.partition_id)) - _logger.debug("Partition Key: {}".format(message.partition_key)) - _logger.debug("User Properties: {}".format(message.user_properties)) - _logger.debug("Annotations: {}".format(message.annotations)) - _logger.debug("Delivery count: {}".format(message.header.delivery_count)) - try: - _logger.debug("Locked until: {}".format(message.locked_until)) - _logger.debug("Lock Token: {}".format(message.lock_token)) - except TypeError: - pass - _logger.debug("Enqueued time: {}".format(message.enqueued_time)) - -@pytest.mark.liveTest -def test_pqueue_by_queue_client_conn_str_receive_handler_peeklock(live_servicebus_config, partitioned_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_queue, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - message.enqueue_sequence_number = i - sender.send(message) - - receiver = queue_client.get_receiver(idle_timeout=5) - count = 0 - for message in receiver: - print_message(message) - count += 1 - message.complete() - - assert count == 10 - -@pytest.mark.liveTest -def test_pqueue_by_queue_client_conn_str_receive_handler_receiveanddelete(live_servicebus_config, partitioned_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_queue, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - message.enqueue_sequence_number = i - sender.send(message) - - messages = [] - receiver = queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in receiver: - messages.append(message) - with pytest.raises(MessageAlreadySettled): - message.complete() - - assert not receiver.running - assert len(messages) == 10 - time.sleep(30) - - messages = [] - receiver = queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in receiver: - messages.append(message) - assert len(messages) == 0 - -@pytest.mark.liveTest -def test_pqueue_by_queue_client_conn_str_receive_handler_with_stop(live_servicebus_config, partitioned_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_queue, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Stop message no. {}".format(i)) - sender.send(message) - - messages = [] - receiver = queue_client.get_receiver(idle_timeout=5) - for message in receiver: - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert receiver.running - assert len(messages) == 5 - - with receiver: - for message in receiver: - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert not receiver.running - assert len(messages) == 6 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_iter_messages_simple(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Iter message no. {}".format(i)) - sender.send(message) - - count = 0 - for message in receiver: - print_message(message) - message.complete() - with pytest.raises(MessageAlreadySettled): - message.complete() - with pytest.raises(MessageAlreadySettled): - message.renew_lock() - count += 1 - - with pytest.raises(InvalidHandlerState): - next(receiver) - assert count == 10 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_conn_str_client_iter_messages_with_abandon(live_servicebus_config, partitioned_queue): - client = ServiceBusClient.from_connection_string(live_servicebus_config['conn_str'], debug=False) - queue_client = client.get_queue(partitioned_queue) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Abandoned message no. {}".format(i)) - sender.send(message) - - count = 0 - for message in receiver: - print_message(message) - if not message.header.delivery_count: - count += 1 - message.abandon() - else: - assert message.header.delivery_count == 1 - message.complete() - - assert count == 10 - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 0 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_iter_messages_with_defer(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - deferred_messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - sender.send(message) - - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 0 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_iter_messages_with_retrieve_deferred_client(live_servicebus_config, partitioned_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - deferred_messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - message.partition_key = "MyPartitionKey" - sender.send(message) - - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - deferred = queue_client.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.PeekLock) - assert len(deferred) == 10 - - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(ValueError): - message.complete() - - with pytest.raises(ValueError): - queue_client.settle_deferred_messages('foo', message) - queue_client.settle_deferred_messages('completed', message) - - with pytest.raises(ServiceBusError): - queue_client.receive_deferred_messages(deferred_messages) - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(live_servicebus_config, partitioned_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - for m in messages: - m.partition_key = "MyPartitionKey" - results = queue_client.send(messages, session="test_session") - assert all(result[0] for result in results) - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - deferred = receiver.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - assert message.lock_token - assert message.locked_until - assert message._receiver - message.renew_lock() - message.complete() - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - for m in messages: - m.partition_key = "MyPartitionKey" - results = queue_client.send(messages) - assert all(result[0] for result in results) - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with queue_client.get_receiver(idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - message.dead_letter("something") - - count = 0 - with queue_client.get_deadletter_receiver(idle_timeout=5) as receiver: - for message in receiver: - count += 1 - print_message(message) - assert message.user_properties[b'DeadLetterReason'] == b'something' - assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' - message.complete() - assert count == 10 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - for m in messages: - m.partition_key = "MyPartitionKey" - results = queue_client.send(messages) - assert all(result[0] for result in results) - - count = 0 - receiver = queue_client.get_receiver(idle_timeout=5) - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - with queue_client.get_receiver(idle_timeout=5) as receiver: - deferred = receiver.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.ReceiveAndDelete) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(MessageAlreadySettled): - message.complete() - with pytest.raises(ServiceBusError): - deferred = receiver.receive_deferred_messages(deferred_messages) - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_iter_messages_with_retrieve_deferred_not_found(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - deferred_messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(3): - message = Message("Deferred message no. {}".format(i)) - message.partition_key = "MyPartitionKey" - sender.send(message) - - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 3 - - with pytest.raises(ServiceBusError): - deferred = queue_client.receive_deferred_messages([3, 4], mode=ReceiveSettleMode.PeekLock) - - with pytest.raises(ServiceBusError): - deferred = queue_client.receive_deferred_messages([5, 6, 7], mode=ReceiveSettleMode.PeekLock) - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_receive_batch_with_deadletter(live_servicebus_config, partitioned_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - sender.send(message) - - count = 0 - messages = receiver.fetch_next() - while messages: - for message in messages: - print_message(message) - count += 1 - message.dead_letter(description="Testing") - messages = receiver.fetch_next() - - assert count == 10 - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 0 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_receive_batch_with_retrieve_deadletter(live_servicebus_config, partitioned_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - sender.send(message) - - count = 0 - messages = receiver.fetch_next() - while messages: - for message in messages: - print_message(message) - message.dead_letter(description="Testing queue deadletter") - count += 1 - messages = receiver.fetch_next() - - with pytest.raises(InvalidHandlerState): - receiver.fetch_next() - - assert count == 10 - - with queue_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 10 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_session_fail(live_servicebus_config, partitioned_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - with pytest.raises(ValueError): - queue_client.get_receiver(session="test") - - with queue_client.get_sender(session="test") as sender: - sender.send(Message("test session sender")) - - -def test_pqueue_by_servicebus_client_browse_messages_client(live_servicebus_config, partitioned_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - with queue_client.get_sender() as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - message.partition_key = "MyPartitionKey" - sender.send(message) - - messages = queue_client.peek(5) - assert len(messages) == 5 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_browse_messages_with_receiver(live_servicebus_config, partitioned_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - with queue_client.get_sender() as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - message.partition_key = "MyPartitionKey" - sender.send(message) - - messages = receiver.peek(5) - assert len(messages) > 0 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() - - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_browse_empty_messages(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - messages = receiver.peek(10) - assert len(messages) == 0 - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_fail_send_messages(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - too_large = "A" * 1024 * 512 - try: - results = queue_client.send(Message(too_large)) - except MessageSendFailed: - pytest.skip("Open issue for uAMQP on OSX") - - assert len(results) == 1 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) - - with queue_client.get_sender() as sender: - with pytest.raises(MessageSendFailed): - sender.send(Message(too_large)) - - with queue_client.get_sender() as sender: - sender.queue_message(Message(too_large)) - results = sender.send_pending_messages() - assert len(results) == 1 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_fail_send_batch_messages(live_servicebus_config, partitioned_queue): - pytest.skip("TODO: Pending bugfix in uAMQP") - def batch_data(): - for i in range(3): - yield str(i) * 1024 * 256 - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - results = queue_client.send(BatchMessage(batch_data())) - assert len(results) == 4 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) - - with queue_client.get_sender() as sender: - with pytest.raises(MessageSendFailed): - sender.send(BatchMessage(batch_data())) - - with queue_client.get_sender() as sender: - sender.queue_message(BatchMessage(batch_data())) - results = sender.send_pending_messages() - assert len(results) == 4 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) - - -@pytest.mark.liveTest -def test_pqueue_by_servicebus_client_renew_message_locks(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - messages = [] - locks = 3 - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - with queue_client.get_sender() as sender: - for i in range(locks): - message = Message("Test message no. {}".format(i)) - message.partition_key = "MyPartitionKey" - sender.send(message) - - messages.extend(receiver.fetch_next()) - recv = True - while recv: - recv = receiver.fetch_next() - messages.extend(recv) - - try: - assert not message.expired - for m in messages: - time.sleep(5) - initial_expiry = m.locked_until - m.renew_lock() - assert (m.locked_until - initial_expiry) >= timedelta(seconds=5) - finally: - messages[0].complete() - messages[1].complete() - time.sleep(30) - with pytest.raises(MessageLockExpired): - messages[2].complete() - -@pytest.mark.liveTest -def test_pqueue_by_queue_client_conn_str_receive_handler_with_autolockrenew(live_servicebus_config, partitioned_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_queue, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("{}".format(i)) - sender.send(message) - - renewer = AutoLockRenew() - messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - for message in receiver: - if not messages: - messages.append(message) - assert not message.expired - renewer.register(message, timeout=60) - print("Registered lock renew thread", message.locked_until, datetime.now()) - time.sleep(50) - print("Finished first sleep", message.locked_until) - assert not message.expired - time.sleep(25) - print("Finished second sleep", message.locked_until, datetime.now()) - assert message.expired - try: - message.complete() - raise AssertionError("Didn't raise MessageLockExpired") - except MessageLockExpired as e: - assert isinstance(e.inner_exception, AutoLockRenewTimeout) - else: - if message.expired: - print("Remaining messages", message.locked_until, datetime.now()) - assert message.expired - with pytest.raises(MessageLockExpired): - message.complete() - else: - assert message.header.delivery_count >= 1 - print("Remaining messages", message.locked_until, datetime.now()) - messages.append(message) - message.complete() - renewer.shutdown() - assert len(messages) == 11 - -@pytest.mark.liveTest -def test_pqueue_message_time_to_live(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - queue_client = client.get_queue(partitioned_queue) - - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.time_to_live = timedelta(seconds=30) - sender.send(message) - - time.sleep(30) - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert not messages - - with queue_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 1 - -@pytest.mark.liveTest -def test_pqueue_message_connection_closed(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - queue_client = client.get_queue(partitioned_queue) - - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - message.partition_key = "MyPartitionKey" - sender.send(message) - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - - with pytest.raises(MessageSettleFailed): - messages[0].complete() - -@pytest.mark.liveTest -def test_pqueue_message_expiry(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - queue_client = client.get_queue(partitioned_queue) - - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - sender.send(message) - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - time.sleep(30) - assert messages[0].expired - with pytest.raises(MessageLockExpired): - messages[0].complete() - with pytest.raises(MessageLockExpired): - messages[0].renew_lock() - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=30) - assert len(messages) == 1 - print_message(messages[0]) - assert messages[0].header.delivery_count > 0 - messages[0].complete() - -@pytest.mark.liveTest -def test_pqueue_message_lock_renew(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - queue_client = client.get_queue(partitioned_queue) - - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - sender.send(message) - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - time.sleep(15) - messages[0].renew_lock() - time.sleep(15) - messages[0].renew_lock() - time.sleep(15) - assert not messages[0].expired - messages[0].complete() - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 0 - -@pytest.mark.liveTest -def test_pqueue_message_receive_and_delete(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - queue_client = client.get_queue(partitioned_queue) - - with queue_client.get_sender() as sender: - message = Message("Receive and delete test") - sender.send(message) - - with queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete) as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - received = messages[0] - print_message(received) - with pytest.raises(MessageAlreadySettled): - received.complete() - with pytest.raises(MessageAlreadySettled): - received.abandon() - with pytest.raises(MessageAlreadySettled): - received.defer() - with pytest.raises(MessageAlreadySettled): - received.dead_letter() - with pytest.raises(MessageAlreadySettled): - received.renew_lock() - - time.sleep(30) - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - for m in messages: - print_message(m) - assert len(messages) == 0 - -@pytest.mark.liveTest -def test_pqueue_message_batch(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - queue_client = client.get_queue(partitioned_queue) - - def message_content(): - for i in range(5): - yield "Message no. {}".format(i) - - - with queue_client.get_sender() as sender: - message = BatchMessage(message_content()) - sender.send(message) - - with queue_client.get_receiver() as receiver: - messages =receiver.fetch_next(timeout=10) - recv = True - while recv: - recv = receiver.fetch_next(timeout=10) - messages.extend(recv) - - assert len(messages) == 5 - for m in messages: - print_message(m) - m.complete() - -@pytest.mark.liveTest -def test_pqueue_schedule_message(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - queue_client = client.get_queue(partitioned_queue) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver() as receiver: - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.properties.message_id = message_id - message.schedule(enqueue_time) - sender.send(message) - - messages = receiver.fetch_next(timeout=120) - if messages: - try: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id == message_id - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 1 - finally: - for m in messages: - m.complete() - else: - raise Exception("Failed to receive schdeduled message.") - -@pytest.mark.liveTest -def test_pqueue_schedule_multiple_messages(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - queue_client = client.get_queue(partitioned_queue) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver(prefetch=20) as receiver: - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id_a = uuid.uuid4() - message_a = Message(content) - message_a.properties.message_id = message_id_a - message_id_b = uuid.uuid4() - message_b = Message(content) - message_b.properties.message_id = message_id_b - tokens = sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - messages = receiver.fetch_next(timeout=120) - messages.extend(receiver.fetch_next(timeout=5)) - if messages: - try: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id in (message_id_a, message_id_b) - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 2 - finally: - for m in messages: - m.complete() - else: - raise Exception("Failed to receive schdeduled message.") - -@pytest.mark.liveTest -def test_pqueue_cancel_scheduled_messages(live_servicebus_config, partitioned_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_queue) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver() as receiver: - with queue_client.get_sender() as sender: - message_a = Message("Test scheduled message") - message_b = Message("Test scheduled message") - tokens = sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - sender.cancel_scheduled_messages(*tokens) - - messages = receiver.fetch_next(timeout=120) - try: - assert len(messages) == 0 - except AssertionError: - for m in messages: - print(str(m)) - m.complete() - raise diff --git a/sdk/servicebus/azure-servicebus/tests/test_partitioned_sessions.py b/sdk/servicebus/azure-servicebus/tests/test_partitioned_sessions.py deleted file mode 100644 index 152ac3dd1705..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/test_partitioned_sessions.py +++ /dev/null @@ -1,797 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import logging -import concurrent -import sys -import os -import pytest -import time -import uuid -from datetime import datetime, timedelta - -from azure.servicebus import ServiceBusClient, QueueClient, AutoLockRenew -from azure.servicebus.common.message import Message, PeekMessage, BatchMessage, DeferredMessage -from azure.servicebus.common.constants import ReceiveSettleMode, NEXT_AVAILABLE -from azure.servicebus.common.errors import ( - ServiceBusError, - NoActiveSession, - SessionLockExpired, - MessageLockExpired, - InvalidHandlerState, - MessageAlreadySettled, - AutoLockRenewTimeout, - MessageSettleFailed) - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger - -_logger = get_logger(logging.DEBUG) - - -def print_message(message): - _logger.info("Receiving: {}".format(message)) - _logger.debug("Time to live: {}".format(message.header.time_to_live)) - _logger.debug("Sequence number: {}".format(message.sequence_number)) - _logger.debug("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - _logger.debug("Partition ID: {}".format(message.partition_id)) - _logger.debug("Partition Key: {}".format(message.partition_key)) - _logger.debug("Enqueued time: {}".format(message.enqueued_time)) - -@pytest.mark.liveTest -def test_qsession_by_session_client_conn_str_receive_handler_peeklock(live_servicebus_config, partitioned_session_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_session_queue, - debug=False) - queue_client.get_properties() - - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(3): - message = Message("Handler message no. {}".format(i)) - sender.send(message) - - with pytest.raises(ValueError): - session = queue_client.get_receiver(idle_timeout=5) - - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - count = 0 - for message in session: - print_message(message) - assert message.session_id == session_id - count += 1 - message.complete() - - assert count == 3 - -@pytest.mark.liveTest -def test_qsession_by_queue_client_conn_str_receive_handler_receiveanddelete(live_servicebus_config, partitioned_session_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_session_queue, - debug=False) - queue_client.get_properties() - - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - sender.send(message) - - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in session: - messages.append(message) - assert session_id == session.session_id - assert session_id == message.session_id - with pytest.raises(MessageAlreadySettled): - message.complete() - - assert not session.running - assert len(messages) == 10 - time.sleep(30) - - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in session: - messages.append(message) - assert len(messages) == 0 - -@pytest.mark.liveTest -def test_qsession_by_session_client_conn_str_receive_handler_with_stop(live_servicebus_config, partitioned_session_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_session_queue, - debug=False) - - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Stop message no. {}".format(i)) - sender.send(message) - - messages = [] - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - assert session_id == session.session_id - assert session_id == message.session_id - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert session.running - assert len(messages) == 5 - - with session: - for message in session: - assert session_id == session.session_id - assert session_id == message.session_id - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert not session.running - assert len(messages) == 6 - -@pytest.mark.liveTest -def test_qsession_by_session_client_conn_str_receive_handler_with_no_session(live_servicebus_config, partitioned_session_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_session_queue, - debug=False) - - session = queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) - with pytest.raises(NoActiveSession): - session.open() - -@pytest.mark.liveTest -def test_qsession_by_session_client_conn_str_receive_handler_with_inactive_session(live_servicebus_config, partitioned_session_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_session_queue, - debug=False) - - session_id = str(uuid.uuid4()) - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in session: - messages.append(message) - - assert not session.running - assert len(messages) == 0 - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - for m in messages: - m.partition_key = "MyPartitionKey" - results = queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - assert message.lock_token - assert not message.locked_until - assert message._receiver - with pytest.raises(TypeError): - message.renew_lock() - message.complete() - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - for m in messages: - m.partition_key = "MyPartitionKey" - results = queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - message.dead_letter("something") - - count = 0 - with queue_client.get_deadletter_receiver(idle_timeout=5) as receiver: - for message in receiver: - count += 1 - print_message(message) - assert message.user_properties[b'DeadLetterReason'] == b'something' - assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' - message.complete() - assert count == 10 - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - for m in messages: - m.partition_key = "MyPartitionKey" - results = queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.ReceiveAndDelete) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(MessageAlreadySettled): - message.complete() - with pytest.raises(ServiceBusError): - deferred = session.receive_deferred_messages(deferred_messages) - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_iter_messages_with_retrieve_deferred_client(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - deferred_messages = [] - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - sender.send(message) - - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - count = 0 - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with pytest.raises(ValueError): - deferred = queue_client.receive_deferred_messages(deferred_messages, session=session_id) - - with pytest.raises(ValueError): - queue_client.settle_deferred_messages('completed', [message], session=session_id) - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_fetch_next_with_retrieve_deadletter(live_servicebus_config, partitioned_session_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - session_id = str(uuid.uuid4()) - with queue_client.get_receiver(session=session_id, idle_timeout=5, prefetch=10) as receiver: - - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - sender.send(message) - - count = 0 - messages = receiver.fetch_next() - while messages: - for message in messages: - print_message(message) - message.dead_letter(description="Testing queue deadletter") - count += 1 - messages = receiver.fetch_next() - assert count == 10 - - with queue_client.get_deadletter_receiver(idle_timeout=5) as session: - count = 0 - for message in session: - print_message(message) - message.complete() - #assert message.user_properties[b'DeadLetterReason'] == b'something' # TODO - #assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' # TODO - count += 1 - assert count == 10 - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_browse_messages_client(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - message.partition_key = "MyPartitionKey" - sender.send(message) - - with pytest.raises(ValueError): - messages = queue_client.peek(5) - - messages = queue_client.peek(5, session=session_id) - assert len(messages) == 5 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_browse_messages_with_receiver(live_servicebus_config, partitioned_session_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - session_id = str(uuid.uuid4()) - with queue_client.get_receiver(idle_timeout=5, session=session_id) as receiver: - with queue_client.get_sender(session=session_id) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - message.partition_key = "MyPartitionKey" - sender.send(message) - - messages = receiver.peek(5) - assert len(messages) > 0 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_renew_client_locks(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - session_id = str(uuid.uuid4()) - messages = [] - locks = 3 - with queue_client.get_receiver(session=session_id, prefetch=10) as receiver: - with queue_client.get_sender(session=session_id) as sender: - for i in range(locks): - message = Message("Test message no. {}".format(i)) - sender.send(message) - - messages.extend(receiver.fetch_next()) - recv = True - while recv: - recv = receiver.fetch_next(timeout=5) - messages.extend(recv) - - try: - for m in messages: - with pytest.raises(TypeError): - expired = m.expired - assert m.locked_until is None - assert m.lock_token is None - time.sleep(5) - initial_expiry = receiver.locked_until - receiver.renew_lock() - assert (receiver.locked_until - initial_expiry) >= timedelta(seconds=5) - finally: - messages[0].complete() - messages[1].complete() - time.sleep(30) - with pytest.raises(SessionLockExpired): - messages[2].complete() - -@pytest.mark.liveTest -def test_qsession_by_conn_str_receive_handler_with_autolockrenew(live_servicebus_config, partitioned_session_queue): - session_id = str(uuid.uuid4()) - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_session_queue, - debug=False) - - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("{}".format(i)) - sender.send(message) - - renewer = AutoLockRenew() - messages = [] - with queue_client.get_receiver(session=session_id, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as session: - renewer.register(session, timeout=60) - print("Registered lock renew thread", session.locked_until, datetime.now()) - with pytest.raises(SessionLockExpired): - for message in session: - if not messages: - print("Starting first sleep") - time.sleep(40) - print("First sleep {}".format(session.locked_until - datetime.now())) - assert not session.expired - with pytest.raises(TypeError): - message.expired - assert message.locked_until is None - with pytest.raises(TypeError): - message.renew_lock() - assert message.lock_token is None - message.complete() - messages.append(message) - - elif len(messages) == 1: - print("Starting second sleep") - time.sleep(40) - print("Second sleep {}".format(session.locked_until - datetime.now())) - assert session.expired - assert isinstance(session.auto_renew_error, AutoLockRenewTimeout) - try: - message.complete() - raise AssertionError("Didn't raise SessionLockExpired") - except SessionLockExpired as e: - assert isinstance(e.inner_exception, AutoLockRenewTimeout) - messages.append(message) - - renewer.shutdown() - assert len(messages) == 2 - -@pytest.mark.liveTest -def test_qsession_message_connection_closed(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(partitioned_session_queue) - - with queue_client.get_sender() as sender: - message = Message("test") - message.session_id = session_id - sender.send(message) - - with queue_client.get_receiver(session=session_id) as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - - with pytest.raises(MessageSettleFailed): - messages[0].complete() - -@pytest.mark.liveTest -def test_qsession_message_expiry(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(partitioned_session_queue) - - with queue_client.get_sender() as sender: - message = Message("Testing expired messages") - message.session_id = session_id - sender.send(message) - - with queue_client.get_receiver(session=session_id) as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - print_message(messages[0]) - time.sleep(30) - with pytest.raises(TypeError): - messages[0].expired - with pytest.raises(TypeError): - messages[0].renew_lock() - assert receiver.expired - with pytest.raises(SessionLockExpired): - messages[0].complete() - with pytest.raises(SessionLockExpired): - receiver.renew_lock() - - with queue_client.get_receiver(session=session_id) as receiver: - messages = receiver.fetch_next(timeout=30) - assert len(messages) == 1 - print_message(messages[0]) - #assert messages[0].header.delivery_count # TODO confirm this with service - messages[0].complete() - -@pytest.mark.liveTest -def test_qsession_schedule_message(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(partitioned_session_queue) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver(session=session_id) as receiver: - with queue_client.get_sender(session=session_id) as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.properties.message_id = message_id - message.schedule(enqueue_time) - sender.send(message) - - messages = [] - count = 0 - while not messages and count < 12: - messages = receiver.fetch_next(timeout=10) - receiver.renew_lock() - count += 1 - - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id == message_id - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 1 - -@pytest.mark.liveTest -def test_qsession_schedule_multiple_messages(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - import uuid - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(partitioned_session_queue) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - - with queue_client.get_receiver(session=session_id, prefetch=20) as receiver: - with queue_client.get_sender(session=session_id) as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message_a = Message(content) - message_a.properties.message_id = message_id - message_b = Message(content) - message_b.properties.message_id = message_id - sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - messages = [] - count = 0 - while len(messages) < 2 and count < 12: - messages = receiver.fetch_next(timeout=10) - receiver.renew_lock() - count += 1 - - data = str(messages[0]) - assert data == content - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 2 - -@pytest.mark.liveTest -def test_qsession_cancel_scheduled_messages(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(partitioned_session_queue) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - - with queue_client.get_sender(session=session_id) as sender: - message_id = uuid.uuid4() - message_a = Message("Test scheduled message") - message_a.properties.message_id = message_id - message_b = Message("Test scheduled message") - message_b.properties.message_id = message_id - tokens = sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - sender.cancel_scheduled_messages(*tokens) - - with queue_client.get_receiver(session=session_id) as receiver: - messages = [] - count = 0 - while not messages and count < 13: - messages = receiver.fetch_next(timeout=10) - receiver.renew_lock() - count += 1 - assert len(messages) == 0 - -@pytest.mark.liveTest -def test_qsession_get_set_state_with_receiver(live_servicebus_config, partitioned_session_queue): - queue_client = QueueClient.from_connection_string( - live_servicebus_config['conn_str'], - name=partitioned_session_queue, - debug=False) - queue_client.get_properties() - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(3): - message = Message("Handler message no. {}".format(i)) - sender.send(message) - - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - assert session.get_session_state() == None - session.set_session_state("first_state") - count = 0 - for m in session: - assert m.properties.group_id == session_id.encode('utf-8') - count += 1 - with pytest.raises(InvalidHandlerState): - session.get_session_state() - assert count == 3 - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_list_sessions_with_receiver(live_servicebus_config, partitioned_session_queue): - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - sessions = [] - start_time = datetime.now() - for i in range(5): - sessions.append(str(uuid.uuid4())) - - for session in sessions: - with queue_client.get_sender(session=session) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) - for session in sessions: - with queue_client.get_receiver(session=session) as receiver: - receiver.set_session_state("SESSION {}".format(session)) - - with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - current_sessions = receiver.list_sessions(updated_since=start_time) - assert len(current_sessions) == 5 - assert current_sessions == sessions - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_list_sessions_with_client(live_servicebus_config, partitioned_session_queue): - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - sessions = [] - start_time = datetime.now() - for i in range(5): - sessions.append(str(uuid.uuid4())) - - for session in sessions: - with queue_client.get_sender(session=session) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) - for session in sessions: - with queue_client.get_receiver(session=session) as receiver: - receiver.set_session_state("SESSION {}".format(session)) - - current_sessions = queue_client.list_sessions(updated_since=start_time) - assert len(current_sessions) == 5 - assert current_sessions == sessions - -@pytest.mark.liveTest -def test_qsession_by_servicebus_client_session_pool(live_servicebus_config, partitioned_session_queue): - messages = [] - errors = [] - concurrent_receivers = 5 - - def message_processing(queue_client): - while True: - try: - with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) as session: - for message in session: - print("Message: {}".format(message)) - messages.append(message) - message.complete() - except NoActiveSession: - return - except Exception as e: - errors.append(e) - raise - - client = ServiceBusClient( - service_namespace=live_servicebus_config['hostname'], - shared_access_key_name=live_servicebus_config['key_name'], - shared_access_key_value=live_servicebus_config['access_key'], - debug=False) - - queue_client = client.get_queue(partitioned_session_queue) - sessions = [str(uuid.uuid4()) for i in range(concurrent_receivers)] - - for session in sessions: - with queue_client.get_sender(session=session) as sender: - for i in range(20): - message = Message("Test message no. {}".format(i)) - sender.send(message) - - futures = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_receivers) as thread_pool: - for _ in range(concurrent_receivers): - futures.append(thread_pool.submit(message_processing, queue_client)) - concurrent.futures.wait(futures) - - assert not errors - assert len(messages) == 100 \ No newline at end of file diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index e41507c66d60..33e162ccb6c8 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -10,13 +10,14 @@ import pytest import time import uuid -import random from datetime import datetime, timedelta -from azure.servicebus import ServiceBusClient, QueueClient, AutoLockRenew -from azure.servicebus.common.message import Message, PeekMessage, BatchMessage, DeferredMessage -from azure.servicebus.common.constants import ReceiveSettleMode -from azure.servicebus.common.errors import ( +from azure.servicebus import ServiceBusClient, AutoLockRenew +from azure.servicebus._common.message import Message, PeekMessage, ReceivedMessage, BatchMessage +from azure.servicebus._common.constants import ReceiveSettleMode, _X_OPT_LOCK_TOKEN +from azure.servicebus._common.utils import utc_now +from azure.servicebus.exceptions import ( + ServiceBusConnectionError, ServiceBusError, MessageLockExpired, InvalidHandlerState, @@ -27,45 +28,11 @@ from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer from servicebus_preparer import CachedServiceBusNamespacePreparer, ServiceBusQueuePreparer, CachedServiceBusQueuePreparer - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger +from utilities import get_logger, print_message _logger = get_logger(logging.DEBUG) -def print_message(message): - _logger.info("Receiving: {}".format(message)) - _logger.debug("Time to live: {}".format(message.time_to_live)) - _logger.debug("Sequence number: {}".format(message.sequence_number)) - _logger.debug("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - _logger.debug("Partition ID: {}".format(message.partition_id)) - _logger.debug("Partition Key: {}".format(message.partition_key)) - _logger.debug("User Properties: {}".format(message.user_properties)) - _logger.debug("Annotations: {}".format(message.annotations)) - _logger.debug("Delivery count: {}".format(message.header.delivery_count)) - try: - _logger.debug("Locked until: {}".format(message.locked_until)) - _logger.debug("Lock Token: {}".format(message.lock_token)) - except TypeError: - pass - _logger.debug("Enqueued time: {}".format(message.enqueued_time)) - - -def sleep_until_expired(locked_entity): - time.sleep(max(0,(locked_entity.locked_until - datetime.now()).total_seconds() + 1)) - # A note regarding live_test_only. # Old servicebus tests were not written to work on both stubs and live entities. # This disables those tests for non-live scenarios, and should be removed as tests @@ -77,24 +44,25 @@ class ServiceBusQueueTests(AzureMgmtTestCase): @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_github_issue_7079(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - + def test_receive_and_delete_reconnect_interaction(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + # Note: This test was to guard against github issue 7079 sb_client = ServiceBusClient.from_connection_string( - servicebus_namespace_connection_string, debug=False) - queue = sb_client.get_queue(servicebus_queue.name) + servicebus_namespace_connection_string, logging_enable=False) - with queue.get_sender() as sender: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: for i in range(5): sender.send(Message("Message {}".format(i))) - messages = queue.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - batch = messages.fetch_next() - count = len(batch) - messages.reconnect() - for message in messages: - _logger.debug(message) - count += 1 - assert count == 5 + with sb_client.get_queue_receiver(servicebus_queue.name, + mode=ReceiveSettleMode.ReceiveAndDelete, + idle_timeout=10) as receiver: + batch = receiver.receive() + count = len(batch) + + for message in receiver: + _logger.debug(message) + count += 1 + assert count == 5 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -102,21 +70,22 @@ def test_github_issue_7079(self, servicebus_namespace_connection_string, service @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_github_issue_6178(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - sb_client = ServiceBusClient.from_connection_string( - servicebus_namespace_connection_string, debug=False) - queue = sb_client.get_queue(servicebus_queue.name) - - for i in range(3): - queue.send(Message("Message {}".format(i))) - - messages = queue.get_receiver(idle_timeout=60) - for message in messages: - _logger.debug(message) - _logger.debug(message.sequence_number) - _logger.debug(message.enqueued_time) - _logger.debug(message.expired) - message.complete() - time.sleep(40) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + sender.send(Message("Message {}".format(i))) + + with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=60) as receiver: + for message in receiver: + _logger.debug(message) + _logger.debug(message.sequence_number) + _logger.debug(message.enqueued_time_utc) + _logger.debug(message.expired) + message.complete() + time.sleep(40) + @pytest.mark.liveTest @pytest.mark.live_test_only @@ -124,29 +93,26 @@ def test_github_issue_6178(self, servicebus_namespace_connection_string, service @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_queue_by_queue_client_conn_str_receive_handler_peeklock(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - message.enqueue_sequence_number = i - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - receiver = queue_client.get_receiver(idle_timeout=5) - count = 0 - for message in receiver: - print_message(message) - assert message.message.delivery_tag is not None - assert message.lock_token == message.message.delivery_annotations.get(message._x_OPT_LOCK_TOKEN) - assert message.lock_token == uuid.UUID(bytes_le=message.message.delivery_tag) - count += 1 - message.complete() + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Handler message no. {}".format(i)) + message.enqueue_sequence_number = i + sender.send(message) + + receiver = sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) + count = 0 + for message in receiver: + print_message(_logger, message) + assert message.message.delivery_tag is not None + assert message.lock_token == message.message.delivery_annotations.get(_X_OPT_LOCK_TOKEN) + assert message.lock_token == uuid.UUID(bytes_le=message.message.delivery_tag) + count += 1 + message.complete() - assert count == 10 + assert count == 10 @pytest.mark.liveTest @@ -156,33 +122,35 @@ def test_queue_by_queue_client_conn_str_receive_handler_peeklock(self, servicebu @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_queue_by_queue_client_conn_str_receive_handler_receiveanddelete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - message.enqueue_sequence_number = i - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Handler message no. {}".format(i)) + message.enqueue_sequence_number = i + sender.send(message) - messages = [] - receiver = queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in receiver: - messages.append(message) - with pytest.raises(MessageAlreadySettled): - message.complete() + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, + mode=ReceiveSettleMode.ReceiveAndDelete, + idle_timeout=5) as receiver: + for message in receiver: + messages.append(message) + with pytest.raises(MessageAlreadySettled): + message.complete() - assert not receiver.running - assert len(messages) == 10 - time.sleep(30) + assert len(messages) == 10 + assert not receiver._running + time.sleep(30) - messages = [] - receiver = queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in receiver: - messages.append(message) - assert len(messages) == 0 + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, + mode=ReceiveSettleMode.ReceiveAndDelete, + idle_timeout=5) as receiver: + for message in receiver: + messages.append(message) + assert len(messages) == 0 @pytest.mark.liveTest @@ -192,36 +160,35 @@ def test_queue_by_queue_client_conn_str_receive_handler_receiveanddelete(self, s @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_queue_by_queue_client_conn_str_receive_handler_with_stop(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Stop message no. {}".format(i)) - sender.send(message) + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - messages = [] - receiver = queue_client.get_receiver(idle_timeout=5) - for message in receiver: - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert receiver.running - assert len(messages) == 5 + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Stop message no. {}".format(i)) + sender.send(message) - with receiver: - for message in receiver: - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert not receiver.running - assert len(messages) == 6 + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) as receiver: + for message in receiver: + messages.append(message) + message.complete() + if len(messages) >= 5: + break + + assert receiver._running + assert len(messages) == 5 + + with receiver: + for message in receiver: + messages.append(message) + message.complete() + if len(messages) >= 5: + break + + assert not receiver._running + assert len(messages) == 6 @pytest.mark.liveTest @@ -229,35 +196,33 @@ def test_queue_by_queue_client_conn_str_receive_handler_with_stop(self, serviceb @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_iter_messages_simple(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_by_servicebus_client_iter_messages_simple(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Iter message no. {}".format(i)) - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - count = 0 - for message in receiver: - print_message(message) - message.complete() - with pytest.raises(MessageAlreadySettled): + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Iter message no. {}".format(i)) + sender.send(message) + + count = 0 + for message in receiver: + print_message(_logger, message) message.complete() - with pytest.raises(MessageAlreadySettled): - message.renew_lock() - count += 1 - - with pytest.raises(InvalidHandlerState): - next(receiver) - assert count == 10 + with pytest.raises(MessageAlreadySettled): + message.complete() + with pytest.raises(MessageAlreadySettled): + message.renew_lock() + count += 1 + + with pytest.raises(StopIteration): + next(receiver) + assert count == 10 @pytest.mark.liveTest @@ -267,34 +232,35 @@ def test_queue_by_servicebus_client_iter_messages_simple(self, servicebus_namesp @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_queue_by_servicebus_conn_str_client_iter_messages_with_abandon(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string, debug=False) - queue_client = client.get_queue(servicebus_queue.name) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Abandoned message no. {}".format(i)) - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - count = 0 - for message in receiver: - print_message(message) - if not message.header.delivery_count: - count += 1 - message.abandon() - else: - assert message.header.delivery_count == 1 - message.complete() + with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Abandoned message no. {}".format(i)) + sender.send(message) + + count = 0 + for message in receiver: + print_message(_logger, message) + if not message.header.delivery_count: + count += 1 + message.abandon() + else: + assert message.header.delivery_count == 1 + message.complete() - assert count == 10 + assert count == 10 - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 0 + with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=20, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + for message in receiver: + print_message(_logger, message) + message.complete() + count += 1 + assert count == 0 @pytest.mark.liveTest @@ -302,38 +268,37 @@ def test_queue_by_servicebus_conn_str_client_iter_messages_with_abandon(self, se @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_iter_messages_with_defer(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + def test_queue_by_servicebus_client_iter_messages_with_defer(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - sender.send(message) - - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + with sb_client.get_queue_receiver( + servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Deferred message no. {}".format(i)) + sender.send(message) + + count = 0 + for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() - assert count == 10 - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 0 + assert count == 10 + with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + for message in receiver: + print_message(_logger, message) + message.complete() + count += 1 + assert count == 0 @pytest.mark.liveTest @@ -341,44 +306,37 @@ def test_queue_by_servicebus_client_iter_messages_with_defer(self, servicebus_na @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - sender.send(message) - - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() + def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - assert count == 10 + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Deferred message no. {}".format(i)) + sender.send(message) + + count = 0 + for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() - deferred = queue_client.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.PeekLock) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(ValueError): - message.complete() - with pytest.raises(ValueError): - queue_client.settle_deferred_messages('foo', deferred) - - queue_client.settle_deferred_messages('completed', deferred) - with pytest.raises(ServiceBusError): - queue_client.receive_deferred_messages(deferred_messages) + assert count == 10 + deferred = receiver.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + message.complete() + + with pytest.raises(ServiceBusError): + receiver.receive_deferred_messages(deferred_messages) @pytest.mark.liveTest @@ -386,86 +344,90 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_client( @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + deferred_messages = [] + for i in range(10): + message = Message("Deferred message no. {}".format(i), session_id="test_session") + sender.send(message) - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = queue_client.send(messages, session="test_session") - assert all(result[0] for result in results) + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - deferred = receiver.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - assert message.lock_token - assert message.locked_until - assert message._receiver - message.renew_lock() - message.complete() + assert count == 10 + + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + deferred = receiver.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + assert message.lock_token + assert message.locked_until_utc + assert message._receiver + message.renew_lock() + message.complete() + @pytest.mark.skip(reason="Pending dead letter receiver") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = queue_client.send(messages) - assert all(result[0] for result in results) - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + - assert count == 10 + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + deferred_messages = [] + for i in range(10): + message = Message("Deferred message no. {}".format(i)) + sender.send(message) - with queue_client.get_receiver(idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - message.dead_letter("something") + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() - count = 0 - with queue_client.get_deadletter_receiver(idle_timeout=5) as receiver: - for message in receiver: - count += 1 - print_message(message) - assert message.user_properties[b'DeadLetterReason'] == b'something' - assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' - message.complete() - assert count == 10 + assert count == 10 + + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5) as receiver: + deferred = receiver.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + message.dead_letter(reason="Testing reason", description="Testing description") + + count = 0 + with sb_client.get_deadletter_receiver(servicebus_queue.name, + idle_timeout=5) as receiver: + for message in receiver: + count += 1 + print_message(_logger, message) + assert message.user_properties[b'DeadLetterReason'] == b'Testing reason' + assert message.user_properties[b'DeadLetterErrorDescription'] == b'Testing description' + message.complete() + assert count == 10 @pytest.mark.liveTest @@ -473,37 +435,35 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receive @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = queue_client.send(messages) - assert all(result[0] for result in results) - - count = 0 - receiver = queue_client.get_receiver(idle_timeout=5) - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - with queue_client.get_receiver(idle_timeout=5) as receiver: - deferred = receiver.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.ReceiveAndDelete) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(MessageAlreadySettled): - message.complete() - with pytest.raises(ServiceBusError): + def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + sender.send(Message("Deferred message no. {}".format(i))) + + deferred_messages = [] + count = 0 + with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5) as receiver: + for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() + + assert count == 10 + with sb_client.get_queue_receiver(servicebus_queue.name, + mode=ReceiveSettleMode.ReceiveAndDelete, + idle_timeout=5) as receiver: deferred = receiver.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + with pytest.raises(MessageAlreadySettled): + message.complete() + with pytest.raises(ServiceBusError): + deferred = receiver.receive_deferred_messages(deferred_messages) @pytest.mark.liveTest @@ -511,36 +471,34 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receive @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_not_found(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - - with queue_client.get_sender() as sender: - for i in range(3): - message = Message("Deferred message no. {}".format(i)) - sender.send(message) - - count = 0 - for message in receiver: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() + def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_not_found(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + deferred_messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + message = Message("Deferred message no. {}".format(i)) + sender.send(message) + + count = 0 + for message in receiver: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() - assert count == 3 + assert count == 3 - with pytest.raises(ServiceBusError): - deferred = queue_client.receive_deferred_messages([3, 4], mode=ReceiveSettleMode.PeekLock) + with pytest.raises(ServiceBusError): + deferred = receiver.receive_deferred_messages([3, 4]) - with pytest.raises(ServiceBusError): - deferred = queue_client.receive_deferred_messages([5, 6, 7], mode=ReceiveSettleMode.PeekLock) + with pytest.raises(ServiceBusError): + deferred = receiver.receive_deferred_messages([5, 6, 7]) @pytest.mark.liveTest @@ -548,84 +506,84 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_not_fou @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_receive_batch_with_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - sender.send(message) + def test_queue_by_servicebus_client_receive_batch_with_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - count = 0 - messages = receiver.fetch_next() - while messages: - for message in messages: - print_message(message) + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock, + prefetch=10) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Dead lettered message no. {}".format(i)) + sender.send(message) + + count = 0 + messages = receiver.receive() + while messages: + for message in messages: + print_message(_logger, message) + count += 1 + message.dead_letter(reason="Testing reason", description="Testing description") + messages = receiver.receive() + + assert count == 10 + + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + for message in receiver: + print_message(_logger, message) + message.complete() count += 1 - message.dead_letter(description="Testing") - messages = receiver.fetch_next() - - assert count == 10 - - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 0 + assert count == 0 + @pytest.mark.skip(reason="Pending dead letter receiver") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_receive_batch_with_retrieve_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_by_servicebus_client_receive_batch_with_retrieve_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = client.get_queue(servicebus_queue.name) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - sender.send(message) - - count = 0 - messages = receiver.fetch_next() - while messages: - for message in messages: - print_message(message) - message.dead_letter(description="Testing queue deadletter") + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock, + prefetch=10) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Dead lettered message no. {}".format(i)) + sender.send(message) + + count = 0 + messages = receiver.receive() + while messages: + for message in messages: + print_message(_logger, message) + message.dead_letter(reason="Testing reason", description="Testing description") + count += 1 + messages = receiver.receive() + + receiver.receive(1,5) + + assert count == 10 + + with sb_client.get_deadletter_receiver(idle_timeout=5) as receiver: + count = 0 + for message in receiver: + print_message(_logger, message) + message.complete() count += 1 - messages = receiver.fetch_next() - - with pytest.raises(InvalidHandlerState): - receiver.fetch_next() - - assert count == 10 - - with queue_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 10 + assert count == 10 @pytest.mark.liveTest @@ -633,21 +591,16 @@ def test_queue_by_servicebus_client_receive_batch_with_retrieve_deadletter(self, @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_session_fail(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_by_servicebus_client_session_fail(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(random.randint(1000,9999)) #so we can used a cached queue and not collide. - queue_client = client.get_queue(servicebus_queue.name) - with pytest.raises(ValueError): - queue_client.get_receiver(session=session_id) + with pytest.raises(ServiceBusConnectionError): + sb_client.get_queue_receiver(servicebus_queue.name, session_id="test")._open_with_retry() - with queue_client.get_sender(session=session_id) as sender: - sender.send(Message("test session sender")) + with sb_client.get_queue_sender(servicebus_queue.name, session_id="test") as sender: + sender.send(Message("test session sender")) @pytest.mark.liveTest @@ -655,27 +608,24 @@ def test_queue_by_servicebus_client_session_fail(self, servicebus_namespace, ser @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_browse_messages_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_by_servicebus_client_browse_messages_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = client.get_queue(servicebus_queue.name) - with queue_client.get_sender() as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i)) + sender.send(message) - messages = queue_client.peek(5) - assert len(messages) == 5 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.peek(5) + assert len(messages) == 5 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() @pytest.mark.liveTest @@ -683,28 +633,26 @@ def test_queue_by_servicebus_client_browse_messages_client(self, servicebus_name @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - with queue_client.get_sender() as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) - - messages = receiver.peek(5) - assert len(messages) > 0 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + def test_queue_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i)) + sender.send(message) + + messages = receiver.peek(5) + assert len(messages) > 0 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() @pytest.mark.liveTest @@ -712,20 +660,20 @@ def test_queue_by_servicebus_client_browse_messages_with_receiver(self, serviceb @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_browse_empty_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_by_servicebus_client_browse_empty_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - messages = receiver.peek(10) - assert len(messages) == 0 + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock, + prefetch=10) as receiver: + messages = receiver.peek(10) + assert len(messages) == 0 + @pytest.mark.skip(reason="Pending queue message") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @@ -733,33 +681,26 @@ def test_queue_by_servicebus_client_browse_empty_messages(self, servicebus_names @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_queue_by_servicebus_client_fail_send_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - too_large = "A" * 1024 * 512 - try: - results = queue_client.send(Message(too_large)) - except MessageSendFailed: - pytest.skip("Open issue for uAMQP on OSX") - - assert len(results) == 1 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) - - with queue_client.get_sender() as sender: - with pytest.raises(MessageSendFailed): - sender.send(Message(too_large)) - - with queue_client.get_sender() as sender: - sender.queue_message(Message(too_large)) - results = sender.send_pending_messages() - assert len(results) == 1 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + too_large = "A" * 1024 * 512 + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + try: + results = sender.send(Message(too_large)) + except MessageSendFailed: + pytest.skip("Open issue for uAMQP on OSX") + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(MessageSendFailed): + sender.send(Message(too_large)) + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + sender.queue_message(Message(too_large)) + results = sender.send_pending_messages() + assert len(results) == 1 + assert not results[0][0] + assert isinstance(results[0][1], MessageSendFailed) @pytest.mark.liveTest @@ -770,79 +711,68 @@ def test_queue_by_servicebus_client_fail_send_messages(self, servicebus_namespac def test_queue_by_servicebus_client_fail_send_batch_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): pytest.skip("TODO: Pending bugfix in uAMQP") - def batch_data(): + def batch_data(batch): for i in range(3): - yield str(i) * 1024 * 256 - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + batch.add(Message(str(i) * 1024 * 256)) + return batch - queue_client = client.get_queue(servicebus_queue.name) - results = queue_client.send(BatchMessage(batch_data())) - assert len(results) == 4 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - with queue_client.get_sender() as sender: - with pytest.raises(MessageSendFailed): - sender.send(BatchMessage(batch_data())) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(MessageSendFailed): + batch = BatchMessage() + sender.send(batch_data(batch)) - with queue_client.get_sender() as sender: - sender.queue_message(BatchMessage(batch_data())) - results = sender.send_pending_messages() - assert len(results) == 4 - assert not results[0][0] - assert isinstance(results[0][1], MessageSendFailed) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + sender.queue_message(BatchMessage(batch_data())) + results = sender.send_pending_messages() + assert len(results) == 4 + assert not results[0][0] + assert isinstance(results[0][1], MessageSendFailed) @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') - @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_by_servicebus_client_renew_message_locks(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) + def test_queue_by_servicebus_client_renew_message_locks(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - messages = [] - locks = 3 - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - with queue_client.get_sender() as sender: - for i in range(locks): - message = Message("Test message no. {}".format(i)) - sender.send(message) - - messages.extend(receiver.fetch_next()) - recv = True - while recv: - recv = receiver.fetch_next() - messages.extend(recv) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + messages = [] + locks = 3 + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock, + prefetch=10) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(locks): + message = Message("Test message no. {}".format(i)) + sender.send(message) + + messages.extend(receiver.receive()) + recv = True + while recv: + recv = receiver.receive() + messages.extend(recv) - try: - assert not message.expired - for m in messages: - time.sleep(5) - initial_expiry = m.locked_until - m.renew_lock() - assert (m.locked_until - initial_expiry) >= timedelta(seconds=5) - finally: - messages[0].complete() - messages[1].complete() - # This magic number is because of a 30 second lock renewal window. Chose 31 seconds because at 30, you'll see "off by .05 seconds" flaky failures - # potentially as a side effect of network delays/sleeps/"typical distributed systems nonsense." In a perfect world we wouldn't have a magic number/network hop but this allows - # a slightly more robust test in absence of that. - assert (messages[2].locked_until - datetime.now()) <= timedelta(seconds=31) - sleep_until_expired(messages[2]) - with pytest.raises(MessageLockExpired): - messages[2].complete() + try: + for m in messages: + assert not m.expired + time.sleep(5) + initial_expiry = m.locked_until_utc + m.renew_lock() + assert (m.locked_until_utc - initial_expiry) >= timedelta(seconds=5) + finally: + messages[0].complete() + messages[1].complete() + assert (messages[2].locked_until_utc - utc_now()) <= timedelta(seconds=60) + time.sleep((messages[2].locked_until_utc - utc_now()).total_seconds()) + with pytest.raises(MessageLockExpired): + messages[2].complete() @pytest.mark.liveTest @pytest.mark.live_test_only @@ -851,84 +781,84 @@ def test_queue_by_servicebus_client_renew_message_locks(self, servicebus_namespa @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_queue_by_queue_client_conn_str_receive_handler_with_autolockrenew(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - with queue_client.get_sender() as sender: - for i in range(10): - message = Message("{}".format(i)) - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - renewer = AutoLockRenew() - messages = [] - with queue_client.get_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: - for message in receiver: - if not messages: - messages.append(message) - assert not message.expired - renewer.register(message, timeout=60) - time.sleep(50) - print("Finished first sleep", message.locked_until) - assert not message.expired - time.sleep(25) #Time out the auto-renewer - sleep_until_expired(message) # Now time out the message. - print("Finished second sleep", message.locked_until, datetime.now()) - assert message.expired - try: - message.complete() - raise AssertionError("Didn't raise MessageLockExpired") - except MessageLockExpired as e: - assert isinstance(e.inner_exception, AutoLockRenewTimeout) - else: - if message.expired: - print("Remaining messages", message.locked_until, datetime.now()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("{}".format(i)) + sender.send(message) + + renewer = AutoLockRenew() + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock, + prefetch=10) as receiver: + for message in receiver: + if not messages: + messages.append(message) + assert not message.expired + renewer.register(message, timeout=60) + print("Registered lock renew thread", message.locked_until_utc, utc_now()) + time.sleep(50) + print("Finished first sleep", message.locked_until_utc) + assert not message.expired + time.sleep((message.locked_until_utc - utc_now()).total_seconds()+1) + print("Finished second sleep", message.locked_until_utc, utc_now()) assert message.expired - with pytest.raises(MessageLockExpired): + try: message.complete() + raise AssertionError("Didn't raise MessageLockExpired") + except MessageLockExpired as e: + assert isinstance(e.inner_exception, AutoLockRenewTimeout) else: - assert message.header.delivery_count >= 1 - print("Remaining messages", message.locked_until, datetime.now()) - messages.append(message) - message.complete() - renewer.shutdown() - assert len(messages) == 11 + if message.expired: + print("Remaining messages", message.locked_until_utc, utc_now()) + assert message.expired + with pytest.raises(MessageLockExpired): + message.complete() + else: + assert message.header.delivery_count >= 1 + print("Remaining messages", message.locked_until_utc, utc_now()) + messages.append(message) + message.complete() + renewer.shutdown() + assert len(messages) == 11 + @pytest.mark.skip(reason="Pending dead letter queue receiver") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_message_time_to_live(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_message_time_to_live(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.time_to_live = timedelta(seconds=30) - sender.send(message) - - time.sleep(30) - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert not messages + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id = uuid.uuid4() + message = Message(content) + message.time_to_live = timedelta(seconds=30) + sender.send(message) - with queue_client.get_deadletter_receiver(idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - count = 0 - for message in receiver: - print_message(message) - message.complete() - count += 1 - assert count == 1 + time.sleep(30) + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(5, timeout=10) + assert not messages + + with sb_client.get_deadletter_receiver(servicebus_queue.name, + idle_timeout=5, + mode=ReceiveSettleMode.PeekLock) as receiver: + count = 0 + for message in receiver: + print_message(_logger, message) + message.complete() + count += 1 + assert count == 1 @pytest.mark.liveTest @@ -936,30 +866,28 @@ def test_queue_message_time_to_live(self, servicebus_namespace, servicebus_names @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_duplicate_detection=True, dead_lettering_on_message_expiration=True) - def test_queue_message_duplicate_detection(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_message_duplicate_detection(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - message_id = uuid.uuid4() - queue_client = client.get_queue(servicebus_queue.name) - - with queue_client.get_sender() as sender: - for i in range(5): - message = Message(str(i)) - message.properties.message_id = message_id - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - with queue_client.get_receiver(idle_timeout=5) as receiver: - count = 0 - for message in receiver: - print_message(message) - assert message.properties.message_id == message_id - message.complete() - count += 1 - assert count == 1 + message_id = uuid.uuid4() + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message(str(i)) + message.properties.message_id = message_id + sender.send(message) + + with sb_client.get_queue_receiver(servicebus_queue.name, + idle_timeout=5) as receiver: + count = 0 + for message in receiver: + print_message(_logger, message) + assert message.properties.message_id == message_id + message.complete() + count += 1 + assert count == 1 @pytest.mark.liveTest @@ -967,62 +895,55 @@ def test_queue_message_duplicate_detection(self, servicebus_namespace, servicebu @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_message_connection_closed(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_message_connection_closed(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message = Message(content) + sender.send(message) - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_wait_time=10) + assert len(messages) == 1 - with pytest.raises(MessageSettleFailed): - messages[0].complete() + with pytest.raises(MessageSettleFailed): + messages[0].complete() + @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_message_expiry(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) + def test_queue_message_expiry(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - sender.send(message) - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - time.sleep(30) - assert messages[0].expired - with pytest.raises(MessageLockExpired): - messages[0].complete() - with pytest.raises(MessageLockExpired): - messages[0].renew_lock() + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message = Message(content) + sender.send(message) + + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_wait_time=10) + assert len(messages) == 1 + time.sleep((messages[0].locked_until_utc - utc_now()).total_seconds()+1) + assert messages[0].expired + with pytest.raises(MessageLockExpired): + messages[0].complete() + with pytest.raises(MessageLockExpired): + messages[0].renew_lock() - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=30) - assert len(messages) == 1 - print_message(messages[0]) - assert messages[0].header.delivery_count > 0 - messages[0].complete() + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_wait_time=30) + assert len(messages) == 1 + print_message(_logger, messages[0]) + assert messages[0].header.delivery_count > 0 + messages[0].complete() @pytest.mark.liveTest @@ -1030,34 +951,30 @@ def test_queue_message_expiry(self, servicebus_namespace, servicebus_namespace_k @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_message_lock_renew(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) + def test_queue_message_lock_renew(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message = Message(content) - sender.send(message) - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - time.sleep(15) - messages[0].renew_lock() - time.sleep(15) - messages[0].renew_lock() - time.sleep(15) - assert not messages[0].expired - messages[0].complete() - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 0 + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message = Message(content) + sender.send(message) + + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_wait_time=10) + assert len(messages) == 1 + time.sleep(15) + messages[0].renew_lock() + time.sleep(15) + messages[0].renew_lock() + time.sleep(15) + assert not messages[0].expired + messages[0].complete() + + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_wait_time=10) + assert len(messages) == 0 @pytest.mark.liveTest @@ -1065,42 +982,39 @@ def test_queue_message_lock_renew(self, servicebus_namespace, servicebus_namespa @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_message_receive_and_delete(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) + def test_queue_message_receive_and_delete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - with queue_client.get_sender() as sender: - message = Message("Receive and delete test") - sender.send(message) - - with queue_client.get_receiver(mode=ReceiveSettleMode.ReceiveAndDelete) as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - received = messages[0] - print_message(received) - with pytest.raises(MessageAlreadySettled): - received.complete() - with pytest.raises(MessageAlreadySettled): - received.abandon() - with pytest.raises(MessageAlreadySettled): - received.defer() - with pytest.raises(MessageAlreadySettled): - received.dead_letter() - with pytest.raises(MessageAlreadySettled): - received.renew_lock() - - time.sleep(30) - - with queue_client.get_receiver() as receiver: - messages = receiver.fetch_next(timeout=10) - for m in messages: - print_message(m) - assert len(messages) == 0 + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = Message("Receive and delete test") + sender.send(message) + + with sb_client.get_queue_receiver(servicebus_queue.name, + mode=ReceiveSettleMode.ReceiveAndDelete) as receiver: + messages = receiver.receive(max_wait_time=10) + assert len(messages) == 1 + received = messages[0] + print_message(_logger, received) + with pytest.raises(MessageAlreadySettled): + received.complete() + with pytest.raises(MessageAlreadySettled): + received.abandon() + with pytest.raises(MessageAlreadySettled): + received.defer() + with pytest.raises(MessageAlreadySettled): + received.dead_letter() + with pytest.raises(MessageAlreadySettled): + received.renew_lock() + + time.sleep(30) + + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_wait_time=10) + for m in messages: + print_message(_logger, m) + assert len(messages) == 0 @pytest.mark.liveTest @@ -1108,35 +1022,33 @@ def test_queue_message_receive_and_delete(self, servicebus_namespace, servicebus @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_message_batch(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - - def message_content(): - for i in range(5): - yield "Message no. {}".format(i) + def test_queue_message_batch(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + def message_content(): + for i in range(5): + yield Message("Message no. {}".format(i)) - with queue_client.get_sender() as sender: - message = BatchMessage(message_content()) - sender.send(message) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = BatchMessage() + for each in message_content(): + message.add(each) + sender.send(message) - with queue_client.get_receiver() as receiver: - messages =receiver.fetch_next(timeout=10) - recv = True - while recv: - recv = receiver.fetch_next(timeout=10) - messages.extend(recv) + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + messages =receiver.receive(max_wait_time=10) + recv = True + while recv: + recv = receiver.receive(max_wait_time=10) + messages.extend(recv) - assert len(messages) == 5 - for m in messages: - print_message(m) - m.complete() + assert len(messages) == 5 + for m in messages: + print_message(_logger, m) + m.complete() @pytest.mark.liveTest @@ -1144,113 +1056,105 @@ def message_content(): @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_schedule_message(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver() as receiver: - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.properties.message_id = message_id - message.schedule(enqueue_time) - sender.send(message) + def test_queue_schedule_message(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id = uuid.uuid4() + message = Message(content) + message.properties.message_id = message_id + message.schedule(enqueue_time) + sender.send(message) - messages = receiver.fetch_next(timeout=120) - if messages: - try: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id == message_id - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 1 - finally: - for m in messages: - m.complete() - else: - raise Exception("Failed to receive schdeduled message.") + messages = receiver.receive(max_wait_time=120) + if messages: + try: + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id == message_id + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 1 + finally: + for m in messages: + m.complete() + else: + raise Exception("Failed to receive schdeduled message.") + @pytest.mark.skip("Pending message scheduling functionality") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_schedule_multiple_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver(prefetch=20) as receiver: - with queue_client.get_sender() as sender: - content = str(uuid.uuid4()) - message_id_a = uuid.uuid4() - message_a = Message(content) - message_a.properties.message_id = message_id_a - message_id_b = uuid.uuid4() - message_b = Message(content) - message_b.properties.message_id = message_id_b - tokens = sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - messages = receiver.fetch_next(timeout=120) - messages.extend(receiver.fetch_next(timeout=5)) - if messages: - try: - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id in (message_id_a, message_id_b) - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 2 - finally: - for m in messages: - m.complete() - else: - raise Exception("Failed to receive schdeduled message.") + def test_queue_schedule_multiple_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + with sb_client.get_queue_receiver(servicebus_queue.name, + prefetch=20) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id_a = uuid.uuid4() + message_a = Message(content) + message_a.properties.message_id = message_id_a + message_id_b = uuid.uuid4() + message_b = Message(content) + message_b.properties.message_id = message_id_b + tokens = sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + + messages = receiver.fetch_next(timeout=120) + messages.extend(receiver.fetch_next(timeout=5)) + if messages: + try: + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id in (message_id_a, message_id_b) + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 2 + finally: + for m in messages: + m.complete() + else: + raise Exception("Failed to receive schdeduled message.") + @pytest.mark.skip(reason="Pending message scheduling functionality") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_queue_cancel_scheduled_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_queue_cancel_scheduled_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - queue_client = client.get_queue(servicebus_queue.name) - - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver() as receiver: - with queue_client.get_sender() as sender: - message_a = Message("Test scheduled message") - message_b = Message("Test scheduled message") - tokens = sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - - sender.cancel_scheduled_messages(*tokens) - - messages = receiver.fetch_next(timeout=120) - try: - assert len(messages) == 0 - except AssertionError: - for m in messages: - print(str(m)) - m.complete() - raise - \ No newline at end of file + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message_a = Message("Test scheduled message") + message_b = Message("Test scheduled message") + tokens = sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + + sender.cancel_scheduled_messages(*tokens) + + messages = receiver.receive(max_wait_time=120) + try: + assert len(messages) == 0 + except AssertionError: + for m in messages: + print(str(m)) + m.complete() + raise diff --git a/sdk/servicebus/azure-servicebus/tests/test_sb_client.py b/sdk/servicebus/azure-servicebus/tests/test_sb_client.py index 94ffa71f0e4c..6d898579b304 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_sb_client.py +++ b/sdk/servicebus/azure-servicebus/tests/test_sb_client.py @@ -13,19 +13,18 @@ from azure.common import AzureHttpError, AzureConflictHttpError from azure.mgmt.servicebus.models import AccessRights -from azure.servicebus import ServiceBusClient, QueueClient -from azure.servicebus.common.message import Message, PeekMessage, BatchMessage -from azure.servicebus.common.constants import ReceiveSettleMode -from azure.servicebus.common.errors import ( +from azure.servicebus import ServiceBusClient, ServiceBusSharedKeyCredential +from azure.servicebus._common.message import Message, PeekMessage +from azure.servicebus._common.constants import ReceiveSettleMode +from azure.servicebus.exceptions import ( ServiceBusError, ServiceBusConnectionError, ServiceBusAuthorizationError, ServiceBusResourceNotFound ) -from uamqp.constants import TransportType -from devtools_testutils import AzureMgmtTestCase, RandomNameResourceGroupPreparer +from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer from servicebus_preparer import ( - ServiceBusNamespacePreparer, + CachedServiceBusNamespacePreparer, ServiceBusTopicPreparer, ServiceBusQueuePreparer, ServiceBusNamespaceAuthorizationRulePreparer, @@ -36,157 +35,94 @@ class ServiceBusClientTests(AzureMgmtTestCase): @pytest.mark.liveTest @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) def test_sb_client_bad_credentials(self, servicebus_namespace, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name="invalid", - shared_access_key_value="invalid", - debug=False) - with pytest.raises(AzureHttpError): - client.get_queue(servicebus_queue.name) + fully_qualified_namespace=servicebus_namespace.name + '.servicebus.windows.net', + credential=ServiceBusSharedKeyCredential('invalid', 'invalid'), + logging_enable=False) + with client: + with pytest.raises(ServiceBusError): + with client.get_queue_sender(servicebus_queue.name) as sender: + sender.send(Message("test")) @pytest.mark.liveTest @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') def test_sb_client_bad_namespace(self, **kwargs): client = ServiceBusClient( - service_namespace="invalid", - shared_access_key_name="invalid", - shared_access_key_value="invalid", - debug=False) - - with pytest.raises(ServiceBusConnectionError): - client.get_queue("testq") + fully_qualified_namespace='invalid.servicebus.windows.net', + credential=ServiceBusSharedKeyCredential('invalid', 'invalid'), + logging_enable=False) + with client: + with pytest.raises(ServiceBusError): + with client.get_queue_sender('invalidqueue') as sender: + sender.send(Message("test")) @pytest.mark.liveTest @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - def test_sb_client_bad_entity(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + def test_sb_client_bad_entity(self, servicebus_namespace_connection_string, **kwargs): - with pytest.raises(ServiceBusResourceNotFound): - client.get_queue("invalid") + client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string) - with pytest.raises(ServiceBusResourceNotFound): - client.get_topic("invalid") + with client: + with pytest.raises(ServiceBusConnectionError): + with client.get_queue_sender("invalid") as sender: + sender.send(Message("test")) @pytest.mark.liveTest @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_sb_client_entity_conflict(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - with pytest.raises(AzureConflictHttpError): - client.create_queue(servicebus_queue.name) - - with pytest.raises(AzureConflictHttpError): - client.create_queue(servicebus_queue.name, lock_duration=300) - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) - def test_sb_client_entity_delete(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - with pytest.raises(ServiceBusResourceNotFound): - client.delete_queue("invalid", fail_not_exist=True) - - client.delete_queue("invalid", fail_not_exist=False) - client.delete_queue(servicebus_queue.name) - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) @ServiceBusNamespaceAuthorizationRulePreparer(name_prefix='servicebustest', access_rights=[AccessRights.listen]) def test_sb_client_readonly_credentials(self, servicebus_authorization_rule_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient.from_connection_string(servicebus_authorization_rule_connection_string, debug=False) - with pytest.raises(AzureHttpError): - client.get_queue(servicebus_queue.name) + client = ServiceBusClient.from_connection_string(servicebus_authorization_rule_connection_string) - client = QueueClient.from_connection_string(servicebus_authorization_rule_connection_string, name=servicebus_queue.name) - with client.get_receiver(idle_timeout=5) as receiver: - messages = receiver.fetch_next() + with client: + with client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_batch_size=1, max_wait_time=1) - with pytest.raises(ServiceBusAuthorizationError): - client.send(Message("test")) + with pytest.raises(ServiceBusError): + with client.get_queue_sender(servicebus_queue.name) as sender: + sender.send(Message("test")) @pytest.mark.liveTest @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) @ServiceBusNamespaceAuthorizationRulePreparer(name_prefix='servicebustest', access_rights=[AccessRights.send]) def test_sb_client_writeonly_credentials(self, servicebus_authorization_rule_connection_string, servicebus_queue, **kwargs): client = ServiceBusClient.from_connection_string(servicebus_authorization_rule_connection_string) - with pytest.raises(AzureHttpError): - client.get_queue(servicebus_queue.name) - client = QueueClient.from_connection_string(servicebus_authorization_rule_connection_string, name=servicebus_queue.name, debug=False) - with pytest.raises(ServiceBusAuthorizationError): - with client.get_receiver(idle_timeout=5) as receiver: - messages = receiver.fetch_next() + with client: + with pytest.raises(ServiceBusError): + with client.get_queue_receiver(servicebus_queue.name) as receiver: + messages = receiver.receive(max_batch_size=1, max_wait_time=1) - client.send([Message("test1"), Message("test2")]) - with pytest.raises(TypeError): - client.send("cat") - with pytest.raises(TypeError): - client.send(1234) - with pytest.raises(TypeError): - client.send([1,2,3]) - with pytest.raises(TypeError): - client.send([Message("test1"), "test2"]) + with client.get_queue_sender(servicebus_queue.name) as sender: + sender.send(Message("test")) + with pytest.raises(ServiceBusError): + sender.send("cat") @pytest.mark.liveTest @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusNamespaceAuthorizationRulePreparer(name_prefix='servicebustest') - @ServiceBusQueuePreparer(name_prefix='servicebustest_q1', parameter_name='wrong_queue', dead_lettering_on_message_expiration=True) - @ServiceBusQueuePreparer(name_prefix='servicebustest_q2', dead_lettering_on_message_expiration=True) - @ServiceBusQueueAuthorizationRulePreparer(name_prefix='servicebustest_q2') + @ServiceBusQueuePreparer(name_prefix='servicebustest_qone', parameter_name='wrong_queue', dead_lettering_on_message_expiration=True) + @ServiceBusQueuePreparer(name_prefix='servicebustest_qtwo', dead_lettering_on_message_expiration=True) + @ServiceBusQueueAuthorizationRulePreparer(name_prefix='servicebustest_qtwo') def test_sb_client_incorrect_queue_conn_str(self, servicebus_queue_authorization_rule_connection_string, wrong_queue, **kwargs): client = ServiceBusClient.from_connection_string(servicebus_queue_authorization_rule_connection_string) - with pytest.raises(AzureHttpError): - client.get_queue(wrong_queue.name) - - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer(name_prefix='servicebustest') - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - def test_servicebusclient_from_conn_str_amqpoverwebsocket(self, servicebus_namespace_connection_string, **kwargs): - sb_client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string) - assert sb_client.transport_type == TransportType.Amqp - - websocket_sb_client = ServiceBusClient.from_connection_string(servicebus_namespace_connection_string + ';TransportType=AmqpOverWebsocket') - assert websocket_sb_client.transport_type == TransportType.AmqpOverWebsocket + with client: + with pytest.raises(ServiceBusError): + with client.get_queue_sender(wrong_queue.name) as sender: + sender.send(Message("test")) \ No newline at end of file diff --git a/sdk/servicebus/azure-servicebus/tests/test_sessions.py b/sdk/servicebus/azure-servicebus/tests/test_sessions.py index d6a17895d13c..439e1a0b71c3 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_sessions.py +++ b/sdk/servicebus/azure-servicebus/tests/test_sessions.py @@ -13,10 +13,12 @@ import uuid from datetime import datetime, timedelta -from azure.servicebus import ServiceBusClient, QueueClient, AutoLockRenew -from azure.servicebus.common.message import Message, PeekMessage, BatchMessage, DeferredMessage -from azure.servicebus.common.constants import ReceiveSettleMode, NEXT_AVAILABLE -from azure.servicebus.common.errors import ( +from azure.servicebus import ServiceBusClient, AutoLockRenew +from azure.servicebus._common.message import Message, PeekMessage, ReceivedMessage +from azure.servicebus._common.constants import ReceiveSettleMode, NEXT_AVAILABLE +from azure.servicebus._common.utils import utc_now +from azure.servicebus.exceptions import ( + ServiceBusConnectionError, ServiceBusError, NoActiveSession, SessionLockExpired, @@ -26,41 +28,13 @@ AutoLockRenewTimeout, MessageSettleFailed) -from devtools_testutils import AzureMgmtTestCase, RandomNameResourceGroupPreparer, CachedResourceGroupPreparer -from servicebus_preparer import ServiceBusNamespacePreparer, ServiceBusTopicPreparer, ServiceBusQueuePreparer, CachedServiceBusNamespacePreparer - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger +from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer +from servicebus_preparer import CachedServiceBusNamespacePreparer, ServiceBusTopicPreparer, ServiceBusQueuePreparer +from utilities import get_logger, print_message _logger = get_logger(logging.DEBUG) -def print_message(message): - _logger.info("Receiving: {}".format(message)) - _logger.debug("Time to live: {}".format(message.header.time_to_live)) - _logger.debug("Sequence number: {}".format(message.sequence_number)) - _logger.debug("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) - _logger.debug("Partition ID: {}".format(message.partition_id)) - _logger.debug("Partition Key: {}".format(message.partition_key)) - _logger.debug("Enqueued time: {}".format(message.enqueued_time)) - - -def sleep_until_expired(locked_entity): - time.sleep(max(0,(locked_entity.locked_until - datetime.now()).total_seconds() + 1)) - - class ServiceBusSessionTests(AzureMgmtTestCase): @pytest.mark.liveTest @pytest.mark.live_test_only @@ -68,30 +42,27 @@ class ServiceBusSessionTests(AzureMgmtTestCase): @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) def test_session_by_session_client_conn_str_receive_handler_peeklock(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(3): - message = Message("Handler message no. {}".format(i)) - sender.send(message) + session_id = str(uuid.uuid4()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + message = Message("Handler message no. {}".format(i), session_id=session_id) + sender.send(message) - with pytest.raises(ValueError): - session = queue_client.get_receiver(idle_timeout=5) + with pytest.raises(ServiceBusConnectionError): + session = sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5)._open_with_retry() - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - count = 0 - for message in session: - print_message(message) - assert message.session_id == session_id - count += 1 - message.complete() + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + count = 0 + for message in session: + print_message(_logger, message) + assert message.session_id == session_id + count += 1 + message.complete() - assert count == 3 + assert count == 3 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -99,36 +70,36 @@ def test_session_by_session_client_conn_str_receive_handler_peeklock(self, servi @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) def test_session_by_queue_client_conn_str_receive_handler_receiveanddelete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Handler message no. {}".format(i)) - sender.send(message) + session_id = str(uuid.uuid4()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Handler message no. {}".format(i), session_id=session_id) + sender.send(message) - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in session: - messages.append(message) - assert session_id == session.session_id - assert session_id == message.session_id - with pytest.raises(MessageAlreadySettled): - message.complete() - - assert not session.running - assert len(messages) == 10 - time.sleep(30) + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + mode=ReceiveSettleMode.ReceiveAndDelete, + idle_timeout=5) as session: + for message in session: + messages.append(message) + assert session_id == session._session_id + assert session_id == message.session_id + with pytest.raises(MessageAlreadySettled): + message.complete() - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in session: - messages.append(message) - assert len(messages) == 0 + assert not session._running + assert len(messages) == 10 + time.sleep(30) + + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) as session: + for message in session: + messages.append(message) + assert len(messages) == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -136,41 +107,39 @@ def test_session_by_queue_client_conn_str_receive_handler_receiveanddelete(self, @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) def test_session_by_session_client_conn_str_receive_handler_with_stop(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Stop message no. {}".format(i)) - sender.send(message) + session_id = str(uuid.uuid4()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Stop message no. {}".format(i), session_id=session_id) + sender.send(message) - messages = [] - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - assert session_id == session.session_id - assert session_id == message.session_id - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert session.running - assert len(messages) == 5 - - with session: - for message in session: - assert session_id == session.session_id - assert session_id == message.session_id - messages.append(message) - message.complete() - if len(messages) >= 5: - break - - assert not session.running - assert len(messages) == 6 + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + for message in session: + assert session_id == session._session_id + assert session_id == message.session_id + messages.append(message) + message.complete() + if len(messages) >= 5: + break + + assert session._running + assert len(messages) == 5 + + with session: + for message in session: + assert session_id == session._session_id + assert session_id == message.session_id + messages.append(message) + message.complete() + if len(messages) >= 5: + break + + assert not session._running + assert len(messages) == 6 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -178,14 +147,13 @@ def test_session_by_session_client_conn_str_receive_handler_with_stop(self, serv @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) def test_session_by_session_client_conn_str_receive_handler_with_no_session(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - session = queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) - with pytest.raises(NoActiveSession): - session.open() + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + with pytest.raises(NoActiveSession): + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=NEXT_AVAILABLE, + idle_timeout=5) as session: + session.open() @pytest.mark.liveTest @pytest.mark.live_test_only @@ -193,144 +161,148 @@ def test_session_by_session_client_conn_str_receive_handler_with_no_session(self @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) def test_session_by_session_client_conn_str_receive_handler_with_inactive_session(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - messages = [] - session = queue_client.get_receiver(session=session_id, mode=ReceiveSettleMode.ReceiveAndDelete, idle_timeout=5) - for message in session: - messages.append(message) + session_id = str(uuid.uuid4()) + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + mode=ReceiveSettleMode.ReceiveAndDelete, + idle_timeout=5) as session: + for message in session: + messages.append(message) - assert not session.running - assert len(messages) == 0 + assert not session._running + assert len(messages) == 0 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - assert message.lock_token - assert not message.locked_until - assert message._receiver - with pytest.raises(TypeError): - message.renew_lock() - message.complete() + def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_complete(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + deferred_messages = [] + session_id = str(uuid.uuid4()) + for i in range(10): + message = Message("Deferred message no. {}".format(i), session_id=session_id) + sender.send(message) + + count = 0 + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + idle_timeout=5) as session: + for message in session: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() + + assert count == 10 + + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + idle_timeout=5) as session: + deferred = session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + assert message.lock_token + assert not message.locked_until_utc + assert message._receiver + with pytest.raises(TypeError): + message.renew_lock() + message.complete() + @pytest.mark.skip(reason='Requires deadletter receiver') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - message.dead_letter("something") - - count = 0 - with queue_client.get_deadletter_receiver(idle_timeout=5) as receiver: - for message in receiver: - count += 1 - print_message(message) - assert message.user_properties[b'DeadLetterReason'] == b'something' - assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' - message.complete() - assert count == 10 + def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + deferred_messages = [] + session_id = str(uuid.uuid4()) + messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] + results = sender.send(messages, session_id=session_id) + assert all(result[0] for result in results) + + count = 0 + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + idle_timeout=5) as session: + for message in session: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() + + assert count == 10 + + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + idle_timeout=5) as session: + deferred = session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + message.dead_letter(reason="Testing reason", description="Testing description") + + count = 0 + with sb_client.get_deadletter_receiver(servicebus_queue.name, idle_timeout=5) as receiver: + for message in receiver: + count += 1 + print_message(_logger, message) + assert message.user_properties[b'DeadLetterReason'] == b'Testing reason' + assert message.user_properties[b'DeadLetterErrorDescription'] == b'Testing description' + message.complete() + assert count == 10 @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - messages = [Message("Deferred message no. {}".format(i)) for i in range(10)] - results = queue_client.send(messages, session=session_id) - assert all(result[0] for result in results) - - count = 0 - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() - - assert count == 10 - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - deferred = session.receive_deferred_messages(deferred_messages, mode=ReceiveSettleMode.ReceiveAndDelete) - assert len(deferred) == 10 - for message in deferred: - assert isinstance(message, DeferredMessage) - with pytest.raises(MessageAlreadySettled): - message.complete() - with pytest.raises(ServiceBusError): + def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_receiver_deletemode(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + deferred_messages = [] + session_id = str(uuid.uuid4()) + messages = [Message("Deferred message no. {}".format(i), session_id=session_id) for i in range(10)] + for message in messages: + sender.send(message) + + count = 0 + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + for message in session: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() + + assert count == 10 + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + idle_timeout=5, + mode=ReceiveSettleMode.ReceiveAndDelete) as session: deferred = session.receive_deferred_messages(deferred_messages) + assert len(deferred) == 10 + for message in deferred: + assert isinstance(message, ReceivedMessage) + with pytest.raises(MessageAlreadySettled): + message.complete() + with pytest.raises(ServiceBusError): + deferred = session.receive_deferred_messages(deferred_messages) @pytest.mark.liveTest @@ -338,79 +310,75 @@ def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_recei @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - deferred_messages = [] - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Deferred message no. {}".format(i)) - sender.send(message) + def test_session_by_servicebus_client_iter_messages_with_retrieve_deferred_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session = queue_client.get_receiver(session=session_id, idle_timeout=5) - count = 0 - for message in session: - deferred_messages.append(message.sequence_number) - print_message(message) - count += 1 - message.defer() + deferred_messages = [] + session_id = str(uuid.uuid4()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Deferred message no. {}".format(i), session_id=session_id) + sender.send(message) - assert count == 10 + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + count = 0 + for message in session: + deferred_messages.append(message.sequence_number) + print_message(_logger, message) + count += 1 + message.defer() - with pytest.raises(ValueError): - deferred = queue_client.receive_deferred_messages(deferred_messages, session=session_id) + assert count == 10 - with pytest.raises(ValueError): - queue_client.settle_deferred_messages('completed', [message], session=session_id) + deferred = session.receive_deferred_messages(deferred_messages) + + with pytest.raises(MessageAlreadySettled): + message.complete() + @pytest.mark.skip(reason='Requires deadletter receiver') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_fetch_next_with_retrieve_deadletter(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - with queue_client.get_receiver(session=session_id, idle_timeout=5, prefetch=10) as receiver: - - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("Dead lettered message no. {}".format(i)) - sender.send(message) - - count = 0 - messages = receiver.fetch_next() - while messages: - for message in messages: - print_message(message) - message.dead_letter(description="Testing queue deadletter") + def test_session_by_servicebus_client_receive_with_retrieve_deadletter(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + with sb_client.get_queue_receiver(servicebus_queue.name, + session_id=session_id, + idle_timeout=5, + prefetch=10) as receiver: + + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("Dead lettered message no. {}".format(i), session_id=session_id) + sender.send(message) + + count = 0 + messages = receiver.receive() + while messages: + for message in messages: + print_message(_logger, message) + message.dead_letter(reason="Testing reason", description="Testing description") + count += 1 + messages = receiver.receive() + assert count == 10 + + with sb_client.get_deadletter_receiver(servicebus_queue.name, + idle_timeout=5) as session: + count = 0 + for message in session: + print_message(_logger, message) + message.complete() + #assert message.user_properties[b'DeadLetterReason'] == b'something' # TODO + #assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' # TODO count += 1 - messages = receiver.fetch_next() - assert count == 10 - - with queue_client.get_deadletter_receiver(idle_timeout=5) as session: - count = 0 - for message in session: - print_message(message) - message.complete() - #assert message.user_properties[b'DeadLetterReason'] == b'something' # TODO - #assert message.user_properties[b'DeadLetterErrorDescription'] == b'something' # TODO - count += 1 - assert count == 10 + assert count == 10 @pytest.mark.liveTest @@ -418,30 +386,27 @@ def test_session_by_servicebus_client_fetch_next_with_retrieve_deadletter(self, @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_browse_messages_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) + def test_session_by_servicebus_client_browse_messages_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + session_id = str(uuid.uuid4()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i), session_id=session_id) + sender.send(message) - with pytest.raises(ValueError): - messages = queue_client.peek(5) + with pytest.raises(ServiceBusConnectionError): + with sb_client.get_queue_receiver(servicebus_queue.name): + messages = sb_client.peek(5) - messages = queue_client.peek(5, session=session_id) - assert len(messages) == 5 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = receiver.peek(5) + assert len(messages) == 5 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() @pytest.mark.liveTest @@ -449,29 +414,25 @@ def test_session_by_servicebus_client_browse_messages_client(self, servicebus_na @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_session_by_servicebus_client_browse_messages_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - with queue_client.get_receiver(idle_timeout=5, session=session_id) as receiver: - with queue_client.get_sender(session=session_id) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) + session_id = str(uuid.uuid4()) + with sb_client.get_queue_receiver(servicebus_queue.name, idle_timeout=5, session_id=session_id) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i), session_id=session_id) + sender.send(message) - messages = receiver.peek(5) - assert len(messages) > 0 - assert all(isinstance(m, PeekMessage) for m in messages) - for message in messages: - print_message(message) - with pytest.raises(TypeError): - message.complete() + messages = receiver.peek(5) + assert len(messages) > 0 + assert all(isinstance(m, PeekMessage) for m in messages) + for message in messages: + print_message(_logger, message) + with pytest.raises(AttributeError): + message.complete() @pytest.mark.liveTest @@ -479,51 +440,46 @@ def test_session_by_servicebus_client_browse_messages_with_receiver(self, servic @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_renew_client_locks(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_session_by_servicebus_client_renew_client_locks(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - session_id = str(uuid.uuid4()) - messages = [] - locks = 3 - with queue_client.get_receiver(session=session_id, prefetch=10) as receiver: - with queue_client.get_sender(session=session_id) as sender: - for i in range(locks): - message = Message("Test message no. {}".format(i)) - sender.send(message) - - messages.extend(receiver.fetch_next()) - recv = True - while recv: - recv = receiver.fetch_next(timeout=5) - messages.extend(recv) - - try: - for m in messages: - with pytest.raises(TypeError): - expired = m.expired - assert m.locked_until is None - assert m.lock_token is None - time.sleep(5) - initial_expiry = receiver.locked_until - receiver.renew_lock() - assert (receiver.locked_until - initial_expiry) >= timedelta(seconds=5) - finally: - messages[0].complete() - messages[1].complete() + session_id = str(uuid.uuid4()) + messages = [] + locks = 3 + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, prefetch=10) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(locks): + message = Message("Test message no. {}".format(i), session_id=session_id) + sender.send(message) + + messages.extend(receiver.receive()) + recv = True + while recv: + recv = receiver.receive(max_wait_time=5) + messages.extend(recv) - # This magic number is because of a 30 second lock renewal window. Chose 31 seconds because at 30, you'll see "off by .05 seconds" flaky failures - # potentially as a side effect of network delays/sleeps/"typical distributed systems nonsense." In a perfect world we wouldn't have a magic number/network hop but this allows - # a slightly more robust test in absence of that. - assert (receiver.locked_until - datetime.now()) <= timedelta(seconds=31) - sleep_until_expired(receiver) - with pytest.raises(SessionLockExpired): - messages[2].complete() + try: + for m in messages: + with pytest.raises(TypeError): + expired = m.expired + assert m.locked_until_utc is None + assert m.lock_token is not None + time.sleep(5) + initial_expiry = receiver.session._locked_until_utc + receiver.session.renew_lock() + assert (receiver.session._locked_until_utc - initial_expiry) >= timedelta(seconds=5) + finally: + messages[0].complete() + messages[1].complete() + + # This magic number is because of a 30 second lock renewal window. Chose 31 seconds because at 30, you'll see "off by .05 seconds" flaky failures + # potentially as a side effect of network delays/sleeps/"typical distributed systems nonsense." In a perfect world we wouldn't have a magic number/network hop but this allows + # a slightly more robust test in absence of that. + assert (receiver.session._locked_until_utc - utc_now()) <= timedelta(seconds=60) + time.sleep((receiver.session._locked_until_utc - utc_now()).total_seconds()) + with pytest.raises(SessionLockExpired): + messages[2].complete() @pytest.mark.liveTest @@ -534,53 +490,50 @@ def test_session_by_servicebus_client_renew_client_locks(self, servicebus_namesp def test_session_by_conn_str_receive_handler_with_autolockrenew(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): session_id = str(uuid.uuid4()) - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - - with queue_client.get_sender(session=session_id) as sender: - for i in range(10): - message = Message("{}".format(i)) - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - renewer = AutoLockRenew() - messages = [] - with queue_client.get_receiver(session=session_id, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as session: - renewer.register(session, timeout=60) - print("Registered lock renew thread", session.locked_until, datetime.now()) - with pytest.raises(SessionLockExpired): - for message in session: - if not messages: - print("Starting first sleep") - time.sleep(40) - print("First sleep {}".format(session.locked_until - datetime.now())) - assert not session.expired - with pytest.raises(TypeError): - message.expired - assert message.locked_until is None - with pytest.raises(TypeError): - message.renew_lock() - assert message.lock_token is None - message.complete() - messages.append(message) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(10): + message = Message("{}".format(i), session_id=session_id) + sender.send(message) - elif len(messages) == 1: - print("Starting second sleep") - time.sleep(25) # to ensure that we run out the autolockrenew timeout - sleep_until_expired(session) - print("Second sleep {}".format(session.locked_until - datetime.now())) - assert session.expired - assert isinstance(session.auto_renew_error, AutoLockRenewTimeout) - try: + renewer = AutoLockRenew() + messages = [] + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5, mode=ReceiveSettleMode.PeekLock, prefetch=10) as receiver: + renewer.register(receiver.session, timeout=60) + print("Registered lock renew thread", receiver.session._locked_until_utc, utc_now()) + with pytest.raises(SessionLockExpired): + for message in receiver: + if not messages: + print("Starting first sleep") + time.sleep(40) + print("First sleep {}".format(receiver.session._locked_until_utc - utc_now())) + assert not receiver.session.expired + with pytest.raises(TypeError): + message.expired + assert message.locked_until_utc is None + with pytest.raises(TypeError): + message.renew_lock() + assert message.lock_token is not None message.complete() - raise AssertionError("Didn't raise SessionLockExpired") - except SessionLockExpired as e: - assert isinstance(e.inner_exception, AutoLockRenewTimeout) - messages.append(message) + messages.append(message) - renewer.shutdown() - assert len(messages) == 2 + elif len(messages) == 1: + print("Starting second sleep") + time.sleep(40) + print("Second sleep {}".format(receiver.session._locked_until_utc - utc_now())) + assert receiver.session.expired + assert isinstance(receiver.session.auto_renew_error, AutoLockRenewTimeout) + try: + message.complete() + raise AssertionError("Didn't raise SessionLockExpired") + except SessionLockExpired as e: + assert isinstance(e.inner_exception, AutoLockRenewTimeout) + messages.append(message) + + renewer.shutdown() + assert len(messages) == 2 @pytest.mark.liveTest @@ -588,28 +541,24 @@ def test_session_by_conn_str_receive_handler_with_autolockrenew(self, servicebus @CachedResourceGroupPreparer() @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_message_connection_closed(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_session_message_connection_closed(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) + session_id = str(uuid.uuid4()) - with queue_client.get_sender() as sender: - message = Message("test") - message.session_id = session_id - sender.send(message) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = Message("test") + message.session_id = session_id + sender.send(message) - with queue_client.get_receiver(session=session_id) as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = receiver.receive(max_wait_time=10) + assert len(messages) == 1 - with pytest.raises(MessageSettleFailed): - messages[0].complete() + with pytest.raises(MessageSettleFailed): + messages[0].complete() @pytest.mark.liveTest @@ -617,161 +566,149 @@ def test_session_message_connection_closed(self, servicebus_namespace, servicebu @CachedResourceGroupPreparer() @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_message_expiry(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): + def test_session_message_expiry(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + session_id = str(uuid.uuid4()) - with queue_client.get_sender() as sender: - message = Message("Testing expired messages") - message.session_id = session_id - sender.send(message) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message = Message("Testing expired messages") + message.session_id = session_id + sender.send(message) - with queue_client.get_receiver(session=session_id) as receiver: - messages = receiver.fetch_next(timeout=10) - assert len(messages) == 1 - print_message(messages[0]) - time.sleep(30) - with pytest.raises(TypeError): - messages[0].expired - with pytest.raises(TypeError): - messages[0].renew_lock() - assert receiver.expired - with pytest.raises(SessionLockExpired): - messages[0].complete() - with pytest.raises(SessionLockExpired): - receiver.renew_lock() + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = receiver.receive(max_wait_time=10) + assert len(messages) == 1 + print_message(_logger, messages[0]) + time.sleep(60) + with pytest.raises(TypeError): + messages[0].expired + with pytest.raises(TypeError): + messages[0].renew_lock() + #TODO: Bug: Why was this 30s sleep before? compare with T1. + assert receiver.session.expired + with pytest.raises(SessionLockExpired): + messages[0].complete() + with pytest.raises(SessionLockExpired): + receiver.session.renew_lock() - with queue_client.get_receiver(session=session_id) as receiver: - messages = receiver.fetch_next(timeout=30) - assert len(messages) == 1 - print_message(messages[0]) - #assert messages[0].header.delivery_count # TODO confirm this with service - messages[0].complete() + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = receiver.receive(max_wait_time=30) + assert len(messages) == 1 + print_message(_logger, messages[0]) + #assert messages[0].header.delivery_count # TODO confirm this with service + messages[0].complete() + @pytest.mark.skip(reason='Requires schedule functionality') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_schedule_message(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver(session=session_id) as receiver: - with queue_client.get_sender(session=session_id) as sender: - content = str(uuid.uuid4()) - message_id = uuid.uuid4() - message = Message(content) - message.properties.message_id = message_id - message.schedule(enqueue_time) - sender.send(message) + def test_session_schedule_message(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id = uuid.uuid4() + message = Message(content, session_id=session_id) + message.properties.message_id = message_id + message.schedule(enqueue_time) + sender.send(message) - messages = [] - count = 0 - while not messages and count < 12: - messages = receiver.fetch_next(timeout=10) - receiver.renew_lock() - count += 1 + messages = [] + count = 0 + while not messages and count < 12: + messages = receiver.receive(max_wait_time=10) + receiver.session.renew_lock() + count += 1 - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id == message_id - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 1 + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id == message_id + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 1 + @pytest.mark.skip(reason='Requires schedule functionality') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_schedule_multiple_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - import uuid - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) - - with queue_client.get_receiver(session=session_id, prefetch=20) as receiver: - with queue_client.get_sender(session=session_id) as sender: - content = str(uuid.uuid4()) - message_id_a = uuid.uuid4() - message_a = Message(content) - message_a.properties.message_id = message_id_a - message_id_b = uuid.uuid4() - message_b = Message(content) - message_b.properties.message_id = message_id_b - tokens = sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 + def test_session_schedule_multiple_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + session_id = str(uuid.uuid4()) + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) + + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, prefetch=20) as receiver: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + content = str(uuid.uuid4()) + message_id_a = uuid.uuid4() + message_a = Message(content, session_id=session_id) + message_a.properties.message_id = message_id_a + message_id_b = uuid.uuid4() + message_b = Message(content, session_id=session_id) + message_b.properties.message_id = message_id_b + tokens = sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + + messages = [] + count = 0 + while len(messages) < 2 and count < 12: + receiver.session.renew_lock() + messages = receiver.receive(max_wait_time=15) + time.sleep(5) + count += 1 - messages = [] - count = 0 - while len(messages) < 2 and count < 12: - receiver.renew_lock() - messages = receiver.fetch_next(timeout=15) - time.sleep(5) - count += 1 - - data = str(messages[0]) - assert data == content - assert messages[0].properties.message_id in (message_id_a, message_id_b) - assert messages[0].scheduled_enqueue_time == enqueue_time - assert messages[0].scheduled_enqueue_time == messages[0].enqueued_time.replace(microsecond=0) - assert len(messages) == 2 + data = str(messages[0]) + assert data == content + assert messages[0].properties.message_id in (message_id_a, message_id_b) + assert messages[0].scheduled_enqueue_time_utc == enqueue_time + assert messages[0].scheduled_enqueue_time_utc == messages[0].enqueued_time_utc.replace(microsecond=0) + assert len(messages) == 2 + @pytest.mark.skip(reason='Requires schedule functionality') @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_cancel_scheduled_messages(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - session_id = str(uuid.uuid4()) - queue_client = client.get_queue(servicebus_queue.name) - enqueue_time = (datetime.utcnow() + timedelta(minutes=2)).replace(microsecond=0) + def test_session_cancel_scheduled_messages(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - with queue_client.get_sender(session=session_id) as sender: - message_a = Message("Test scheduled message") - message_b = Message("Test scheduled message") - tokens = sender.schedule(enqueue_time, message_a, message_b) - assert len(tokens) == 2 - sender.cancel_scheduled_messages(*tokens) + session_id = str(uuid.uuid4()) + enqueue_time = (utc_now() + timedelta(minutes=2)).replace(microsecond=0) - with queue_client.get_receiver(session=session_id) as receiver: - messages = [] - count = 0 - while not messages and count < 13: - messages = receiver.fetch_next(timeout=10) - receiver.renew_lock() - count += 1 - assert len(messages) == 0 + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + message_a = Message("Test scheduled message", session_id=session_id) + message_b = Message("Test scheduled message", session_id=session_id) + tokens = sender.schedule(enqueue_time, message_a, message_b) + assert len(tokens) == 2 + sender.cancel_scheduled_messages(*tokens) + + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + messages = [] + count = 0 + while not messages and count < 13: + messages = receiver.receive(max_wait_time=10) + receiver.session.renew_lock() + count += 1 + assert len(messages) == 0 @pytest.mark.liveTest @@ -781,94 +718,85 @@ def test_session_cancel_scheduled_messages(self, servicebus_namespace, servicebu @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) def test_session_get_set_state_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(3): - message = Message("Handler message no. {}".format(i)) - sender.send(message) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - with queue_client.get_receiver(session=session_id, idle_timeout=5) as session: - assert session.get_session_state() == None - session.set_session_state("first_state") - count = 0 - for m in session: - assert m.properties.group_id == session_id.encode('utf-8') - count += 1 - with pytest.raises(InvalidHandlerState): - session.get_session_state() - assert count == 3 + session_id = str(uuid.uuid4()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + message = Message("Handler message no. {}".format(i), session_id=session_id) + sender.send(message) + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, idle_timeout=5) as session: + assert session.session.get_session_state() == None + session.session.set_session_state("first_state") + count = 0 + for m in session: + assert m.properties.group_id == session_id.encode('utf-8') + count += 1 + session.session.get_session_state() + assert count == 3 + + @pytest.mark.skip(reasion="Needs list sessions") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_list_sessions_with_receiver(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - sessions = [] - start_time = datetime.now() - for i in range(5): - sessions.append(str(uuid.uuid4())) - - for session in sessions: - with queue_client.get_sender(session=session) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) - for session in sessions: - with queue_client.get_receiver(session=session) as receiver: - receiver.set_session_state("SESSION {}".format(session)) + def test_session_by_servicebus_client_list_sessions_with_receiver(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + sessions = [] + start_time = utc_now() + for i in range(5): + sessions.append(str(uuid.uuid4())) - with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: - current_sessions = receiver.list_sessions(updated_since=start_time) - assert len(current_sessions) == 5 - assert current_sessions == sessions + for session_id in sessions: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i), session_id=session_id) + sender.send(message) + for session_id in sessions: + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id) as receiver: + receiver.set_session_state("SESSION {}".format(session_id)) + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=NEXT_AVAILABLE, idle_timeout=5, mode=ReceiveSettleMode.PeekLock) as receiver: + current_sessions = receiver.list_sessions(updated_since=start_time) + assert len(current_sessions) == 5 + assert current_sessions == sessions + + @pytest.mark.skip("Requires list sessions") @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_list_sessions_with_client(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - queue_client = client.get_queue(servicebus_queue.name) - sessions = [] - start_time = datetime.now() - for i in range(5): - sessions.append(str(uuid.uuid4())) - - for session in sessions: - with queue_client.get_sender(session=session) as sender: - for i in range(5): - message = Message("Test message no. {}".format(i)) - sender.send(message) - for session in sessions: - with queue_client.get_receiver(session=session) as receiver: - receiver.set_session_state("SESSION {}".format(session)) + def test_session_by_servicebus_client_list_sessions_with_client(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + + sessions = [] + start_time = utc_now() + for i in range(5): + sessions.append(str(uuid.uuid4())) - current_sessions = queue_client.list_sessions(updated_since=start_time) - assert len(current_sessions) == 5 - assert current_sessions == sessions + for session in sessions: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(5): + message = Message("Test message no. {}".format(i), session_id=session) + sender.send(message) + for session in sessions: + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session) as receiver: + receiver.set_session_state("SESSION {}".format(session)) + + current_sessions = receiver.list_sessions(updated_since=start_time) + assert len(current_sessions) == 5 + assert current_sessions == sessions @pytest.mark.liveTest @@ -876,16 +804,15 @@ def test_session_by_servicebus_client_list_sessions_with_client(self, servicebus @CachedResourceGroupPreparer(name_prefix='servicebustest') @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) - def test_session_by_servicebus_client_session_pool(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_queue, **kwargs): - + def test_session_by_servicebus_client_session_pool(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): messages = [] errors = [] concurrent_receivers = 5 - def message_processing(queue_client): + def message_processing(sb_client): while True: try: - with queue_client.get_receiver(session=NEXT_AVAILABLE, idle_timeout=5) as session: + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=NEXT_AVAILABLE, idle_timeout=5) as session: for message in session: print("Message: {}".format(message)) messages.append(message) @@ -896,29 +823,25 @@ def message_processing(queue_client): errors.append(e) raise - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - queue_client = client.get_queue(servicebus_queue.name) - sessions = [str(uuid.uuid4()) for i in range(concurrent_receivers)] + sessions = [str(uuid.uuid4()) for i in range(concurrent_receivers)] - for session in sessions: - with queue_client.get_sender(session=session) as sender: - for i in range(20): - message = Message("Test message no. {}".format(i)) - sender.send(message) + for session_id in sessions: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(20): + message = Message("Test message no. {}".format(i), session_id=session_id) + sender.send(message) - futures = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_receivers) as thread_pool: - for _ in range(concurrent_receivers): - futures.append(thread_pool.submit(message_processing, queue_client)) - concurrent.futures.wait(futures) + futures = [] + with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_receivers) as thread_pool: + for _ in range(concurrent_receivers): + futures.append(thread_pool.submit(message_processing, sb_client)) + concurrent.futures.wait(futures) - assert not errors - assert len(messages) == 100 + assert not errors + assert len(messages) == 100 @pytest.mark.liveTest @pytest.mark.live_test_only @@ -926,24 +849,21 @@ def message_processing(queue_client): @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') @ServiceBusQueuePreparer(name_prefix='servicebustest', requires_session=True) def test_session_by_session_client_conn_str_receive_handler_peeklock_abandon(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): - queue_client = QueueClient.from_connection_string( - servicebus_namespace_connection_string, - name=servicebus_queue.name, - debug=False) - queue_client.get_properties() + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: - session_id = str(uuid.uuid4()) - with queue_client.get_sender(session=session_id) as sender: - for i in range(3): - message = Message("Handler message no. {}".format(i)) - sender.send(message) + session_id = str(uuid.uuid4()) + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + for i in range(3): + message = Message("Handler message no. {}".format(i), session_id=session_id) + sender.send(message) - with queue_client.get_receiver(session=session_id, prefetch=0) as receiver: - message = receiver.next() - assert message.sequence_number == 1 - message.abandon() - second_message = receiver.next() - assert second_message.sequence_number == 1 + with sb_client.get_queue_receiver(servicebus_queue.name, session_id=session_id, prefetch=0) as receiver: + message = receiver.next() + assert message.sequence_number == 1 + message.abandon() + second_message = receiver.next() + assert second_message.sequence_number == 1 diff --git a/sdk/servicebus/azure-servicebus/tests/test_subscriptions.py b/sdk/servicebus/azure-servicebus/tests/test_subscriptions.py deleted file mode 100644 index a8c8b2b3c6c8..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/test_subscriptions.py +++ /dev/null @@ -1,122 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import logging -import sys -import os -import pytest -import time -from datetime import datetime, timedelta - -from azure.servicebus import ServiceBusClient, TopicClient, SubscriptionClient -from azure.servicebus.common.message import Message, PeekMessage -from azure.servicebus.common.constants import ReceiveSettleMode -from azure.servicebus.common.errors import ServiceBusError - -from devtools_testutils import AzureMgmtTestCase, RandomNameResourceGroupPreparer -from servicebus_preparer import ServiceBusNamespacePreparer, ServiceBusTopicPreparer, ServiceBusSubscriptionPreparer - - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger - -_logger = get_logger(logging.DEBUG) - -class ServiceBusSubscriptionTests(AzureMgmtTestCase): - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - @ServiceBusSubscriptionPreparer(name_prefix='servicebustest') - def test_subscription_by_subscription_client_conn_str_receive_basic(self, servicebus_namespace_connection_string, servicebus_topic, servicebus_subscription, **kwargs): - - topic_client = TopicClient.from_connection_string(servicebus_namespace_connection_string, name=servicebus_topic.name, debug=False) - with topic_client.get_sender() as sender: - message = Message(b"Sample topic message") - sender.send(message) - - sub_client = SubscriptionClient.from_connection_string(servicebus_namespace_connection_string, servicebus_subscription.name, topic=servicebus_topic.name, debug=False) - with sub_client.get_receiver(idle_timeout=5) as receiver: - count = 0 - for message in receiver: - count += 1 - message.complete() - assert count == 1 - - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - @ServiceBusSubscriptionPreparer(name_prefix='servicebustest') - def test_subscription_by_servicebus_client_conn_str_send_basic(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_topic, servicebus_subscription, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - topic_client = client.get_topic(servicebus_topic.name) - sub_client = client.get_subscription(servicebus_topic.name, servicebus_subscription.name) - - with topic_client.get_sender() as sender: - message = Message(b"Sample topic message") - sender.send(message) - - with sub_client.get_receiver(idle_timeout=5) as receiver: - count = 0 - for message in receiver: - count += 1 - message.complete() - assert count == 1 - - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - @ServiceBusSubscriptionPreparer(name_prefix='servicebustest') - def test_subscription_by_servicebus_client_list_subscriptions(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_topic, servicebus_subscription, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - subs = client.list_subscriptions(servicebus_topic.name) - assert len(subs) >= 1 - assert all(isinstance(s, SubscriptionClient) for s in subs) - assert subs[0].name == servicebus_subscription.name - assert subs[0].topic_name == servicebus_topic.name - - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - @ServiceBusSubscriptionPreparer(name_prefix='servicebustest') - def test_subscription_by_subscription_client_conn_str_send_fail(self, servicebus_namespace_connection_string, servicebus_topic, servicebus_subscription, **kwargs): - - sub_client = SubscriptionClient.from_connection_string(servicebus_namespace_connection_string, servicebus_subscription.name, topic=servicebus_topic.name, debug=False) - with pytest.raises(AttributeError): - sub_client.get_sender() diff --git a/sdk/servicebus/azure-servicebus/tests/test_topics.py b/sdk/servicebus/azure-servicebus/tests/test_topics.py deleted file mode 100644 index ecc29a0de339..000000000000 --- a/sdk/servicebus/azure-servicebus/tests/test_topics.py +++ /dev/null @@ -1,99 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -#-------------------------------------------------------------------------- - -import logging -import sys -import os -import pytest -import time -from datetime import datetime, timedelta - -from devtools_testutils import AzureMgmtTestCase, RandomNameResourceGroupPreparer - -from azure.servicebus import ServiceBusClient, TopicClient -from azure.servicebus.common.message import Message, PeekMessage -from azure.servicebus.common.constants import ReceiveSettleMode -from azure.servicebus.common.errors import ServiceBusError -from servicebus_preparer import ServiceBusNamespacePreparer, ServiceBusTopicPreparer - -def get_logger(level): - azure_logger = logging.getLogger("azure") - if not azure_logger.handlers: - azure_logger.setLevel(level) - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) - azure_logger.addHandler(handler) - - uamqp_logger = logging.getLogger("uamqp") - if not uamqp_logger.handlers: - uamqp_logger.setLevel(logging.INFO) - uamqp_logger.addHandler(handler) - return azure_logger - -_logger = get_logger(logging.DEBUG) - - -class ServiceBusTopicsTests(AzureMgmtTestCase): - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - def test_topic_by_topic_client_conn_str_send_basic(self, servicebus_namespace_connection_string, servicebus_topic, **kwargs): - - topic_client = TopicClient.from_connection_string(servicebus_namespace_connection_string, name=servicebus_topic.name, debug=False) - with topic_client.get_sender() as sender: - message = Message(b"Sample topic message") - sender.send(message) - message = Message(b"Another sample topic message") - topic_client.send(message) - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - def test_topic_by_servicebus_client_conn_str_send_basic(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_topic, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - topic_client = client.get_topic(servicebus_topic.name) - with topic_client.get_sender() as sender: - message = Message(b"Sample topic message") - sender.send(message) - message = Message(b"Another sample topic message") - topic_client.send(message) - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - def test_topic_by_servicebus_client_list_topics(self, servicebus_namespace, servicebus_namespace_key_name, servicebus_namespace_primary_key, servicebus_topic, **kwargs): - - client = ServiceBusClient( - service_namespace=servicebus_namespace.name, - shared_access_key_name=servicebus_namespace_key_name, - shared_access_key_value=servicebus_namespace_primary_key, - debug=False) - - topics = client.list_topics() - assert len(topics) >= 1 - assert all(isinstance(t, TopicClient) for t in topics) - - @pytest.mark.liveTest - @pytest.mark.live_test_only - @RandomNameResourceGroupPreparer() - @ServiceBusNamespacePreparer(name_prefix='servicebustest') - @ServiceBusTopicPreparer(name_prefix='servicebustest') - def test_topic_by_topic_client_conn_str_receive_fail(self, servicebus_namespace_connection_string, servicebus_topic, **kwargs): - topic_client = TopicClient.from_connection_string(servicebus_namespace_connection_string, name=servicebus_topic.name, debug=False) - with pytest.raises(AttributeError): - topic_client.get_receiver() diff --git a/sdk/servicebus/azure-servicebus/tests/utilities.py b/sdk/servicebus/azure-servicebus/tests/utilities.py new file mode 100644 index 000000000000..44075fae55e3 --- /dev/null +++ b/sdk/servicebus/azure-servicebus/tests/utilities.py @@ -0,0 +1,41 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +#-------------------------------------------------------------------------- + +import logging +import sys + + +def get_logger(level): + azure_logger = logging.getLogger("azure") + if not azure_logger.handlers: + azure_logger.setLevel(level) + handler = logging.StreamHandler(stream=sys.stdout) + handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')) + azure_logger.addHandler(handler) + + uamqp_logger = logging.getLogger("uamqp") + if not uamqp_logger.handlers: + uamqp_logger.setLevel(logging.INFO) + uamqp_logger.addHandler(handler) + return azure_logger + + +def print_message(_logger, message): + _logger.info("Receiving: {}".format(message)) + _logger.debug("Time to live: {}".format(message.time_to_live)) + _logger.debug("Sequence number: {}".format(message.sequence_number)) + _logger.debug("Enqueue Sequence numger: {}".format(message.enqueue_sequence_number)) + _logger.debug("Partition ID: {}".format(message.partition_id)) + _logger.debug("Partition Key: {}".format(message.partition_key)) + _logger.debug("User Properties: {}".format(message.user_properties)) + _logger.debug("Annotations: {}".format(message.annotations)) + _logger.debug("Delivery count: {}".format(message.header.delivery_count)) + try: + _logger.debug("Locked until: {}".format(message.locked_until_utc)) + _logger.debug("Lock Token: {}".format(message.lock_token)) + except (TypeError, AttributeError): + pass + _logger.debug("Enqueued time: {}".format(message.enqueued_time_utc))