Azure service bus message auto renewal not working as expected - python-3.x

I have a python service which sends and receives message from azure service bus queue. As per my setup the received will take long time to do some precess and finally completing the message.
To tackle this scenario added auto lock renewal with max_lock_renewal_duration=3600 and set message lock duration as 1 minute.
when i check the log for the first time the lock renewal worked fine but for the second time it errored out. But when i printed the error it just logging the received message not the error.
Code:
class AzureMessageQueue(MessageQueue):
async def on_renew_error(renewable, error, _):
print("On renew error -\n renewable: ", renewable,"\n error: ", error,"\n type: ", type(error), "\n message: ", error.message)
if type(error) == MessageAlreadySettled:
logger.warn("Message already settled")
else:
logger.warn("Error renewing lock: %s", error)
def __init__(self, conn_str: str, queue_name: str) -> None:
self.sb_client: ServiceBusClient = ServiceBusClient.from_connection_string(
conn_str=conn_str,
logging_enable=True,
retry_total=10,
retry_backoff_factor=1,
)
self.sender: ServiceBusSender = self.sb_client.get_queue_sender(queue_name)
self.receiver: ServiceBusReceiver = self.sb_client.get_queue_receiver(
queue_name
)
self.renewer: AutoLockRenewer = AutoLockRenewer(
max_lock_renewal_duration=3600, on_lock_renew_failure=self.on_renew_error
)
async def send(self, message: str) -> None:
sb_message = ServiceBusMessage(message)
await self.sender.send_messages(sb_message)
async def process(self, processor) -> AsyncIterator:
async with self.receiver:
async for msg in self.receiver:
self.renewer.register(self.receiver, msg)
message = str(msg)
try:
result = await processor(message)
await self.receiver.complete_message(msg)
yield message, None, result
except Exception as e:
yield message, e, None
Log:
On renew error -
renewable: <api.message_queue.AzureMessageQueue object at 0x7fe34bdaea90>
error: {"message": "test"}
type: <class 'azure.servicebus._common.message.ServiceBusReceivedMessage'>
message: {"message": "test"}
i'm trying to understand what caused the issue?
Im using python azure service bus sdk(azure-servicebus~=7.6.0).
Thanks in advance!

Related

websocket messages appears at once rather than individual messages

I want my backend send progress message to UI via websocket.
My problem is all messages,which produced by calling
automate_algorithm()
function appear together at once at the end of process, instead of appear one by one. Is there any wrong with my code.
This class create a dictionary which key is project id, and value is the opened websocket
class ConnectionManager:
def __init__(
self
):
self.connections: dict[str, WebSocket] = {}
async def connect(
self,
id: str,
websocket: WebSocket
):
"""To add new open socket to memory storage
Args:
id:(str)the
"""
await websocket.accept()
self.connections[id] = websocket
async def disconnect(self, id: str):
if id in self.connections:
await self.connections[id].close(code=100,reason=None)
del self.connections[id]
async def send_response(
self,
id: str,
data: str,
status:str='running'
):
print(
f"tries to send response for client with id :{id}. Response is {data}")
try:
await self.connections[id].send_json(data=dict(
timestamp=time.strftime("%H:%M:%S", time.localtime()),
message=data,
id=id,
status=status
)
)
if status=="completed":
await self.disconnect(id)
except Exception as e:
print(str(e))
self.disconnect(id)
manager = ConnectionManager()#create a context for web socket manager
This method get user HTTP request, and start process
#router.websocket("/auto_algo/{client_id}")
async def auto_algo(
websocket: WebSocket,
client_id: str,
):
await manager.connect(client_id, websocket)
# HANDLE FUNCTION*****
await automate_algorithm(idt=client_id)
This is the main method which produce the messages,that should write in websocket.
async def send_message_to_socket(
client_id: str,
what: str,
status:str='running'
):
global manager
await manager.send_response(client_id, what,status)
# automate to algorithm ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
async def automate_algorithm(idt,language='en'):
from controllers.dispatcher_controller import send_message_to_socket
await send_message_to_socket(client_id=idt,what="process starting")#This message appear at start correctly
mds2 = create_mds(idt,mapper=False)
await send_message_to_socket(client_id=idt,what="main_data_structure 2 created...")#the rest of message appear together at the end of process
sample_data = create_sample_data(idt,mapper=False)
await send_message_to_socket(client_id=idt,what="sample data created...")
corr = correlation_matrix(idt,mapper=False)
await send_message_to_socket(client_id=idt,what="correlation created...")
mds3 = accomplish_mds(idt,mapper=False)
await send_message_to_socket(client_id=idt,what="main_data_structure 3 created...")
When the code is executed, the control is never returned to the event loop. There is an easy fix for this; add some await asyncio.sleep(0) to semi-manually return control to the event loop, so it has a chance to execute the send() method of other tasks.So I add await asyncio.sleep(0) right after each await send_message_to_socket(...) line, and the code works correctly

Python, Azure Servicebus how to catch an exception handling?

I'm writing an azure function in python. After receiving a message I will forward n-messages.
My code is:
def sendToQueue(data):
logging.info('sendToQueue: %s', data)
msg = Message(encodeData(data))
d = json.loads(data)
batchId = d['BatchId']
logging.info('sending message with batch Id: %s', batchId)
try:
logging.info('Sending oos message to queue %s', queue_oos_mssql_inbox)
sbs.send_queue_message(queue_oos_mssql_inbox, msg)
logging.info('Done Sending oos message to queue %s', queue_oos_mssql_inbox)
except :
logging.error('Unable to process message %s', batchId)
errorDescription = 'Unable to sent message to ' + queue_oos_mssql_inbox
logging.error('errorDescription message %s', errorDescription)
error = createErrorMessage(batchId, '404', str(errorDescription))
logging.error('error message %s', error)
sendToErrorQueue(json.loads(error))
logging.debug('done sending message: ')
When it's not able to sent the message I would like to log the error. How do I do something like this: 'except pyodbc.Error as ex:', but then with ServiceBus.error?
I can't find any samples or documentation.
Solution is rather simple, just added
exception Exception as e:
logging.error("Error msg: " + str(e))
.
You can use the root error class ServiceBusError:
try:
logging.info('Sending oos message to queue %s', queue_oos_mssql_inbox)
sbs.send_queue_message(queue_oos_mssql_inbox, msg)
logging.info('Done Sending oos message to queue %s', queue_oos_mssql_inbox)
except ServiceBusError as e:
logging.error('Unable to process message %s', batchId)
logging.exception('error %s', e)
ServiceBusError: All other Service Bus related errors. It is the root error class of all the errors described above.
Or any other described in:
https://pypi.org/project/azure-servicebus/
or in the reference docs:
https://azuresdkdocs.blob.core.windows.net/$web/python/azure-servicebus/latest/azure.servicebus.html#module-azure.servicebus.exceptions

Azure Functions HTTP Trigger : How to return exception from python worker log to the API caller

I'm new to Azure functions
Wished to know how to return exception from python worker log to the API caller .
In a HTTP Trigger with COSMOS DB binding , on firing an insert call to the binding , if data already exists , it fails with
"System.Private.CoreLib: Exception while executing function: Functions.insertEntityData. Microsoft.Azure.DocumentDB.Core: Entity with the specified id already exists in the system."
How can this message be sent back to the end user ? It is not getting captured anywhere.
def main(req: func.HttpRequest, cosmosdata: func.Out[func.Document]) -> func.HttpResponse:
try:
message = ""
logging.info('Python HTTP trigger function processed a request.')
entity_name = req.route_params['entity']
status_code = 500
payload = req.get_json()
if payload:
try:
logging.info(payload)
resultant = cosmosdata.set(func.Document.from_dict(payload))
logging.info(resultant)
status_code = 200
message = "Insert Successful to %s" % (entity_name)
except Exception as e:
return func.HttpResponse(str(e), status_code=500)
else:
status_code = 400
message = "Please pass data in the POST Request"
except Exception as e:
return func.HttpResponse(str(e), status_code=500)
return func.HttpResponse(message, status_code=500)
The try / catch block is not working because you're using an Output binding to Cosmos Db, which is the one that is failing. However, it also looks weird to me because by default it performs and Upsert operation.
I believe the problem relates to your partition Key defined in the function.json file.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-cosmosdb-v2#input---python-examples

Lambda Function to Call Database

I am new to AWS/Lambda/Amazon Connect and trying my way around it. I have the below code to request Information from my dynamoDB table and update the table in case of a new entry. My issue is that the function only seems to run the except part and completely ignores the try. Need to understand why
CODE
import boto3
import json
from boto3.dynamodb.conditions import Key, Attr
def lambda_handler(event, context):
print("Lambda Trigger event: " + json.dumps(event))
try:
phoneNumber = event['Details']['ContactData']['CustomerEndpoint']['Address']
print("Customer Phone Number : " + phoneNumber)
dynamodb = boto3.resource('dynamodb',region_name='ap-southeast-2')
table = dynamodb.Table('data_dip_table')
response = table.get_item(Key={
'phone-number': phoneNumber
})
print("dynamodb response: " + json.dumps(response))
if 'Item' in response:
# TODO: Match Found
print("Phone number match found!")
firstName = response['Item']['first-name']
print("Customer First Name: " + firstName)
welcomeMessage = 'Welcome' + firstName + ' to Our data dip'
print("welcome message :" + welcomeMessage)
return {'welcomeMessage' : welcomeMessage }
else:
print("Phone Number was not Found")
return { 'welcomeMessage' : 'Welcome!' }
except Exception as e:
print("An Error Has Occurred")
print(e)
return {'welcomeMessage' : 'Welcome !'}
My Output is
Response:
{
"welcomeMessage": "Welcome !"
}
Request ID:
"0d9b6bf6-62f8-4385-81f0-f1d36ee489c8"
Function Logs:
START RequestId: 0d9b6bf6-62f8-4385-81f0-f1d36ee489c8 Version: $LATEST
Lambda Trigger event: {}
An Error Has Occurred
'Details'
END RequestId: 0d9b6bf6-62f8-4385-81f0-f1d36ee489c8
I have an entry for my phone number in the DD table but still get an error.Any ideas ??
The event object is empty. Check this line in CloudWatchLogs:
Lambda Trigger event: {}
Your code is definitely not ignoring the try block, however, on the first line of your try block, you have phoneNumber = event['Details']['ContactData']['CustomerEndpoint']['Address'] which automatically fails because your event is empty, therefore redirecting the execution to the except block.
How is this function being triggered? If you are doing it manually from AWS's Console, you will have to create a test event that contains the body that your method expects.

TooManyRequestsException for Boto3 Client Organization

I am fetch all child account from the Master AWS Account by boto3 Organization.
Code is working fine. I am able to get child account list.
But if you run my AWS Lambda function again then it fail to get Child Accounts.
Getting following error:
Error while getting AWS Accounts : An error occurred (TooManyRequestsException) when calling the ListAccounts operation: AWS Organizations can't complete your request because another request is already in progress. Try again later
After 20 to 30 minutes, I can see my code work for once and again raise above exception.
I am Run this code by AWS Gateway + AWS Lambda.
Any idea?
Code:
import boto3
class Organizations(object):
"""AWS Organization"""
def __init__(self, access_key, secret_access_key, session_token=None):
self.client = boto3.client('organizations',
aws_access_key_id=access_key,
aws_secret_access_key=secret_access_key,
aws_session_token=session_token
)
def get_accounts(self, next_token=None, max_results=None):
"""Get Accounts List"""
if next_token and max_results:
result = self.client.list_accounts(NextToken=next_token,
MaxResults=max_results)
elif next_token:
result = self.client.list_accounts(NextToken=next_token)
elif max_results:
result = self.client.list_accounts(MaxResults=max_results)
else:
result = self.client.list_accounts()
return result
class AWSAccounts(object):
""" Return AWS Accounts information. """
def get_aws_accounts(self, access_key, secret_access_key, session_token):
""" Return List of AWS account Details."""
org_obj = Organizations(access_key=access_key,
secret_access_key=secret_access_key,
session_token=session_token)
aws_accounts = []
next_token = None
next_result = None
while True:
response = org_obj.get_accounts(next_token, next_result)
for account in response['Accounts']:
account_details = {"name": account["Name"],
"id": account["Id"],
"admin_role_name": self.account_role_name
}
aws_accounts.append(account_details)
if "NextToken" not in response:
break
next_token = response["NextToken"]
return aws_accounts
By Exception Handling, my code is running successfully.
Catch TooManyRequestsException exception by ClientError exception and retry to call AWS list_accounts API by boto3.
We can add time sleep of 0.1 seconds.
Code:
class AWSAccounts(object):
""" Return AWS Accounts information. """
def get_accounts(self, next_token=None, max_results=None):
"""Get Accounts List"""
# If Master AWS account contain more child accounts(150+) then
# Too-Many-Request Exception is raised by the AWS API(boto3).
# So to fix this issue, we are calling API again by Exception Handling.
result = None
while True:
try:
if next_token and max_results:
result = self.client.list_accounts(NextToken=next_token,
MaxResults=max_results)
elif next_token:
result = self.client.list_accounts(NextToken=next_token)
elif max_results:
result = self.client.list_accounts(MaxResults=max_results)
else:
result = self.client.list_accounts()
except botocore.exceptions.ClientError as err:
response = err.response
print("Failed to list accounts:", response)
if (response and response.get("Error", {}).get("Code") ==
"TooManyRequestsException"):
print("Continue for TooManyRequestsException exception.")
continue
break
return result
Configure your boto3 client to use the built-in standard retry mode:
import boto3
from botocore.config import Config
config = Config(
retries = {
'max_attempts': 10,
'mode': 'standard'
}
)
ec2 = boto3.client('ec2', config=config)
Per the documentation, the default mode is 'legacy' which doesn't handle TooManyRequestsException.
See boto3 documentation about retry configuration here: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html

Resources