def execute_query(conn, query_expr: dict) -> str: """A helper function to execute a query against Dynamo db. Args: conn (func): A connection to the Dynamodb table (from db_connector). query_expr (dict): A dictionary containing Dynamodb-specific query attributes: KeyConditionExpression and IndexName (if applicable). Raises: RegistryClientError if connecting to or querying the database fails. Returns: The Dynamodb query API results as JSON.""" try: if 'IndexName' not in query_expr.keys(): response = conn.query( KeyConditionExpression=query_expr['KeyConditionExpression']) else: response = conn.query( IndexName=query_expr['IndexName'], KeyConditionExpression=query_expr['KeyConditionExpression']) except ClientError as e: logger.error("crud: Unexpected error: %s" % e) raise RegistryClientError(f"Unexpected error querying the " f"registry: {str(e)}") logger.debug(f"Dynamodb response={response}") return response
def delete_bond(conn, bond_id: str) -> None: """Delete a bond. Does nothing if the bond does not exist. Args: conn (func): A connection to the Dynamodb table (from db_connector). bond_id (str): The bond id of the bond to delete. Raises: RegistryClientError if connecting to or querying the database fails.""" logger.debug(f"crud: delete bond: bond_id={bond_id}") try: response = conn.delete_item(Key={'bond_id': bond_id}) except ClientError as e: logger.error("crud: Unexpected error: %s" % e) raise RegistryClientError(f"Unexpected error querying the " f"registry: {str(e)}") logger.debug(f"Dynamodb response={response}")
def remove_subscriber(bond_id: str, sid: str) -> Bond: """Remove a subscriber from a bond. Ignore if the subscriber is not a current subscriber attached to the bond. Args: bond_id (str): The bond id of the bond to remove the subscriber from. sid (str): The subscriber identifier of the subscriber to be removed. Raises: RegistryClientError if connecting to or querying the database fails. ConditionalCheckError if the bond does not exist. Returns: The updated bond object.""" logger.debug(f"crud: remove subscriber: bond_id={bond_id}, sid={sid}") bond = get_bond(bond_id) if bond is None: raise ConditionalCheckError(f'Bond {bond_id} not found. ' f'Cannot remove subscriber {sid}.') bond.remove_subscriber(sid) return update_bond(bond)
def add_subscriber(bond_id: str, sub: Subscriber) -> Bond: """Add a subscriber to a bond. Overwrite if the subscriber is already present. Args: bond_id (str): The bond id of the bond to add the subscriber to. sub (Subscriber): The subscriber object to add to the bond. Raises: RegistryClientError if connecting to or querying the database fails. ConditionalCheckError if the bond does not exist. Returns: The updated bond object.""" logger.debug(f"crud: add subscriber: bond_id={bond_id}, sub={sub}") bond = get_bond(bond_id) if bond is None: raise ConditionalCheckError(f'Bond {bond_id} not found. ' f'Cannot add subscriber {sub.sid}.') bond.add_subscriber(sub) return update_bond(bond)
def get_bond(bond_id: str) -> Bond: """Get a bond with the given id. Args: bond_id (str): The bond id of the bond to fetch. Raises: RegistryClientError if connecting to or querying the database fails. Returns: The requested bond object or None if not found.""" logger.debug(f"crud: get bond: bond_id={bond_id}") query_expr = {'KeyConditionExpression': Key('bond_id').eq(bond_id)} response = execute_query(query_expr) if len(response["Items"]) == 0: logger.debug(f"crud: bond not found: bond_id={bond_id}") return None return bond_from_item(response["Items"][0])
def with_connection_(*args, **kwargs): """ Open a connection to DynamoDB. If IS_OFFLINE is set, then connect to the local instance of DynamoDB running in Docker; Otherwise, connect to the cloud service. (Note: moto mock DynamoDB uses this path, i.e. IS_OFFLINE is false.) """ is_offline = os.environ.get("IS_OFFLINE") try: if is_offline: logger.debug(f'DynamoDB: OFFLINE mode. Connecting to local db') dynamodb = \ boto3.resource( "dynamodb", endpoint_url='http://dynamodb-local:8000/', region_name='us-west-2', aws_access_key_id='AWS_ACCESS_KEY_ID', aws_secret_access_key='AWS_SECRET_ACCESS_KEY') else: logger.debug(f'DynamoDB: ONLINE mode. Connecting to cloud db.') dynamodb = boto3.resource("dynamodb") table = dynamodb.Table("bond") logger.debug(f'DynamoDB: Connected to {table.table_arn}') rv = func(table, *args, **kwargs) except ClientError as err: logger.exception("DynamoDB: Failed to connect to the bond " "table: %s" % err) raise RegistryClientError(f"Failed to connect to the database: " f"{str(err)}") return rv
def get_bonds_by_sub_account_id(sub_account_id: str) -> List[Bond]: """Get all bonds for the given *subscriber* account. Args: sub_account_id (str): The subscriber account identifier to search on. Raises: RegistryClientError if connecting to or querying the database fails. Returns: A list of bond objects or an empty list if none are found.""" logger.debug(f"crud: get bonds by sub account id: " f"sub_account_id={sub_account_id}") bonds = [] query_expr = { 'IndexName': "bond-sub_account_id-index", 'KeyConditionExpression': Key('sub_account_id').eq(sub_account_id) } response = execute_query(query_expr) for item in response['Items']: bonds.append(bond_from_item(item)) return bonds
def get_bonds_by_host_cost_center(host_cost_center: str) -> List[Bond]: """Get all bonds for the given *host* cost center. Args: host_cost_center (str): The host cost center identifier to search on. Raises: RegistryClientError if connecting to or querying the database fails. Returns: A list of bond objects or an empty list if none are found.""" logger.debug(f"crud: get bonds by host cost center: " f"host_cost_center={host_cost_center}") bonds = [] query_expr = { 'IndexName': "bond-host_cost_center-index", 'KeyConditionExpression': Key('host_cost_center').eq(host_cost_center) } response = execute_query(query_expr) for item in response['Items']: bonds.append(bond_from_item(item)) return bonds
def update_bond(conn, bond: Bond) -> Bond: """Update a bond. Args: conn (func): A connection to the Dynamodb table (from db_connector). bond (Bond): The bond object to update. Raises: RegistryClientError if connecting to or querying the database fails. ConditionalCheckError if the bond does not exist. Returns: The bond object updated.""" logger.debug(f"crud: update bond: bond={bond}") try: response = conn.update_item( Key={'bond_id': bond.bond_id}, UpdateExpression="set host_account_id = :had, " + "sub_account_id = :sad, " + "host_cost_center = :hcc, " + "sub_cost_center = :scc, " + "subscribers = :sub", ExpressionAttributeValues={ ':had': bond.host_account_id, ':sad': bond.sub_account_id, ':hcc': bond.host_cost_center, ':scc': bond.sub_cost_center, ':sub': jsonable_encoder(bond.subscribers) }, ConditionExpression='attribute_exists(bond_id)', ReturnValues="UPDATED_NEW") except ClientError as e: if e.response['Error']['Code'] == 'ConditionalCheckFailedException': logger.debug(f"crud: bond not found: bond_id={bond.bond_id}") raise ConditionalCheckError(f"Bond not found: " f"bond_id={bond.bond_id}") else: logger.error("crud: Unexpected error: %s" % e) raise RegistryClientError(f"Unexpected error querying the " f"registry: {str(e)}") logger.debug(f"Dynamodb response={response}") return bond
def create_bond(conn, bond: Bond) -> Bond: """Insert a new bond. Args: conn (func): A connection to the Dynamodb table (from db_connector). bond (Bond): The bond object to insert. Raises: RegistryClientError if connecting to or querying the database fails. ConditionalCheckError if the bond already exists. Returns: The bond object inserted.""" logger.debug(f"crud: create bond: bond={bond}") try: response = conn.put_item( Item={ 'bond_id': bond.bond_id, 'host_account_id': bond.host_account_id, 'sub_account_id': bond.sub_account_id, 'host_cost_center': bond.host_cost_center, 'sub_cost_center': bond.sub_cost_center, 'subscribers': jsonable_encoder(bond.subscribers) }, ConditionExpression='attribute_not_exists(bond_id)') except ClientError as e: if e.response['Error']['Code'] == 'ConditionalCheckFailedException': logger.debug(f"crud: bond already exists: bond_id={bond.bond_id}") raise ConditionalCheckError(f"Bond already exists: " f"bond_id={bond.bond_id}") else: logger.error("crud: Unexpected error: %s" % e) raise RegistryClientError(f"Unexpected error querying the " f"registry: {str(e)}") logger.debug(f"Dynamodb response={response}") return bond