diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 508e991f8..1314b86fd 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -6901,7 +6901,7 @@ - [X] delete_log_stream - [ ] delete_metric_filter - [ ] delete_query_definition -- [ ] delete_resource_policy +- [X] delete_resource_policy - [X] delete_retention_policy - [X] delete_subscription_filter - [ ] describe_destinations @@ -6911,7 +6911,7 @@ - [ ] describe_metric_filters - [ ] describe_queries - [ ] describe_query_definitions -- [ ] describe_resource_policies +- [X] describe_resource_policies - [X] describe_subscription_filters - [ ] disassociate_kms_key - [X] filter_log_events @@ -6931,6 +6931,7 @@ - [X] start_query - [ ] stop_query - [X] tag_log_group +- [ ] tail - [ ] test_metric_filter - [X] untag_log_group diff --git a/moto/logs/exceptions.py b/moto/logs/exceptions.py index 022b3a411..f9248751f 100644 --- a/moto/logs/exceptions.py +++ b/moto/logs/exceptions.py @@ -9,7 +9,7 @@ class LogsClientError(JsonRESTError): class ResourceNotFoundException(LogsClientError): def __init__(self, msg=None): self.code = 400 - super(ResourceNotFoundException, self).__init__( + super().__init__( "ResourceNotFoundException", msg or "The specified log group does not exist" ) @@ -17,7 +17,7 @@ class ResourceNotFoundException(LogsClientError): class InvalidParameterException(LogsClientError): def __init__(self, msg=None): self.code = 400 - super(InvalidParameterException, self).__init__( + super().__init__( "InvalidParameterException", msg or "A parameter is specified incorrectly." ) @@ -25,7 +25,7 @@ class InvalidParameterException(LogsClientError): class ResourceAlreadyExistsException(LogsClientError): def __init__(self): self.code = 400 - super(ResourceAlreadyExistsException, self).__init__( + super().__init__( "ResourceAlreadyExistsException", "The specified log group already exists" ) @@ -33,6 +33,4 @@ class ResourceAlreadyExistsException(LogsClientError): class LimitExceededException(LogsClientError): def __init__(self): self.code = 400 - super(LimitExceededException, self).__init__( - "LimitExceededException", "Resource limit exceeded." - ) + super().__init__("LimitExceededException", "Resource limit exceeded.") diff --git a/moto/logs/models.py b/moto/logs/models.py index e00ef0c3e..fcdac7784 100644 --- a/moto/logs/models.py +++ b/moto/logs/models.py @@ -1,20 +1,23 @@ +import uuid + from boto3 import Session from moto import core as moto_core from moto.core import BaseBackend, BaseModel from moto.core.utils import unix_time_millis -from .exceptions import ( +from moto.logs.exceptions import ( ResourceNotFoundException, ResourceAlreadyExistsException, InvalidParameterException, LimitExceededException, ) -import uuid + +MAX_RESOURCE_POLICIES_PER_REGION = 10 class LogQuery(BaseModel): - def __init__(self, id, start_time, end_time, query): - self.id = id + def __init__(self, query_id, start_time, end_time, query): + self.query_id = query_id self.start_time = start_time self.end_time = end_time self.query = query @@ -24,16 +27,16 @@ class LogEvent(BaseModel): _event_id = 0 def __init__(self, ingestion_time, log_event): - self.ingestionTime = ingestion_time + self.ingestion_time = ingestion_time self.timestamp = log_event["timestamp"] self.message = log_event["message"] - self.eventId = self.__class__._event_id + self.event_id = self.__class__._event_id self.__class__._event_id += 1 def to_filter_dict(self): return { - "eventId": str(self.eventId), - "ingestionTime": self.ingestionTime, + "eventId": str(self.event_id), + "ingestionTime": self.ingestion_time, # "logStreamName": "message": self.message, "timestamp": self.timestamp, @@ -41,7 +44,7 @@ class LogEvent(BaseModel): def to_response_dict(self): return { - "ingestionTime": self.ingestionTime, + "ingestionTime": self.ingestion_time, "message": self.message, "timestamp": self.timestamp, } @@ -58,13 +61,13 @@ class LogStream(BaseModel): log_group=log_group, log_stream=name, ) - self.creationTime = int(unix_time_millis()) - self.firstEventTimestamp = None - self.lastEventTimestamp = None - self.lastIngestionTime = None - self.logStreamName = name - self.storedBytes = 0 - self.uploadSequenceToken = ( + self.creation_time = int(unix_time_millis()) + self.first_event_timestamp = None + self.last_event_timestamp = None + self.last_ingestion_time = None + self.log_stream_name = name + self.stored_bytes = 0 + self.upload_sequence_token = ( 0 # I'm guessing this is token needed for sequenceToken by put_events ) self.events = [] @@ -75,10 +78,10 @@ class LogStream(BaseModel): def _update(self): # events can be empty when stream is described soon after creation - self.firstEventTimestamp = ( + self.first_event_timestamp = ( min([x.timestamp for x in self.events]) if self.events else None ) - self.lastEventTimestamp = ( + self.last_event_timestamp = ( max([x.timestamp for x in self.events]) if self.events else None ) @@ -88,16 +91,16 @@ class LogStream(BaseModel): res = { "arn": self.arn, - "creationTime": self.creationTime, - "logStreamName": self.logStreamName, - "storedBytes": self.storedBytes, + "creationTime": self.creation_time, + "logStreamName": self.log_stream_name, + "storedBytes": self.stored_bytes, } if self.events: rest = { - "firstEventTimestamp": self.firstEventTimestamp, - "lastEventTimestamp": self.lastEventTimestamp, - "lastIngestionTime": self.lastIngestionTime, - "uploadSequenceToken": str(self.uploadSequenceToken), + "firstEventTimestamp": self.first_event_timestamp, + "lastEventTimestamp": self.last_event_timestamp, + "lastIngestionTime": self.last_ingestion_time, + "uploadSequenceToken": str(self.upload_sequence_token), } res.update(rest) return res @@ -107,21 +110,23 @@ class LogStream(BaseModel): ): # TODO: ensure sequence_token # TODO: to be thread safe this would need a lock - self.lastIngestionTime = int(unix_time_millis()) + self.last_ingestion_time = int(unix_time_millis()) # TODO: make this match AWS if possible - self.storedBytes += sum([len(log_event["message"]) for log_event in log_events]) + self.stored_bytes += sum( + [len(log_event["message"]) for log_event in log_events] + ) events = [ - LogEvent(self.lastIngestionTime, log_event) for log_event in log_events + LogEvent(self.last_ingestion_time, log_event) for log_event in log_events ] self.events += events - self.uploadSequenceToken += 1 + self.upload_sequence_token += 1 if self.destination_arn and self.destination_arn.split(":")[2] == "lambda": from moto.awslambda import lambda_backends # due to circular dependency lambda_log_events = [ { - "id": event.eventId, + "id": event.event_id, "timestamp": event.timestamp, "message": event.message, } @@ -136,7 +141,7 @@ class LogStream(BaseModel): lambda_log_events, ) - return "{:056d}".format(self.uploadSequenceToken) + return "{:056d}".format(self.upload_sequence_token) def get_log_events( self, @@ -243,7 +248,7 @@ class LogStream(BaseModel): filter(filter_func, self.events), key=lambda x: x.timestamp ): event_obj = event.to_filter_dict() - event_obj["logStreamName"] = self.logStreamName + event_obj["logStreamName"] = self.log_stream_name events.append(event_obj) return events @@ -252,10 +257,8 @@ class LogGroup(BaseModel): def __init__(self, region, name, tags, **kwargs): self.name = name self.region = region - self.arn = "arn:aws:logs:{region}:1:log-group:{log_group}".format( - region=region, log_group=name - ) - self.creationTime = int(unix_time_millis()) + self.arn = f"arn:aws:logs:{region}:{moto_core.ACCOUNT_ID}:log-group:{name}" + self.creation_time = int(unix_time_millis()) self.tags = tags self.streams = dict() # {name: LogStream} self.retention_in_days = kwargs.get( @@ -289,7 +292,7 @@ class LogGroup(BaseModel): next_token, order_by, ): - # responses only logStreamName, creationTime, arn, storedBytes when no events are stored. + # responses only log_stream_name, creation_time, arn, stored_bytes when no events are stored. log_streams = [ (name, stream.to_describe_dict()) @@ -432,7 +435,7 @@ class LogGroup(BaseModel): ) searched_streams = [ - {"logStreamName": stream.logStreamName, "searchedCompletely": True} + {"logStreamName": stream.log_stream_name, "searchedCompletely": True} for stream in streams ] return events_page, next_token, searched_streams @@ -440,10 +443,10 @@ class LogGroup(BaseModel): def to_describe_dict(self): log_group = { "arn": self.arn, - "creationTime": self.creationTime, + "creationTime": self.creation_time, "logGroupName": self.name, "metricFilterCount": 0, - "storedBytes": sum(s.storedBytes for s in self.streams.values()), + "storedBytes": sum(s.stored_bytes for s in self.streams.values()), } # AWS only returns retentionInDays if a value is set for the log group (ie. not Never Expire) if self.retention_in_days: @@ -483,7 +486,7 @@ class LogGroup(BaseModel): if self.subscription_filters[0]["filterName"] == filter_name: creation_time = self.subscription_filters[0]["creationTime"] else: - raise LimitExceededException + raise LimitExceededException() for stream in self.streams.values(): stream.destination_arn = destination_arn @@ -686,9 +689,49 @@ class LogsBackend(BaseBackend): log_group = self.groups[log_group_name] return log_group.set_retention_policy(None) + def describe_resource_policies( + self, next_token, limit + ): # pylint: disable=unused-argument + """Return list of resource policies. + + The next_token and limit arguments are ignored. The maximum + number of resource policies per region is a small number (less + than 50), so pagination isn't needed. + """ + limit = limit or MAX_RESOURCE_POLICIES_PER_REGION + + policies = [] + for policy_name, policy_info in self.resource_policies.items(): + policies.append( + { + "policyName": policy_name, + "policyDocument": policy_info["policyDocument"], + "lastUpdatedTime": policy_info["lastUpdatedTime"], + } + ) + return policies + def put_resource_policy(self, policy_name, policy_doc): - policy = {"policyName": policy_name, "policyDocument": policy_doc} + """Create resource policy and return dict of policy name and doc.""" + if len(self.resource_policies) == MAX_RESOURCE_POLICIES_PER_REGION: + raise LimitExceededException() + + policy = { + "policyName": policy_name, + "policyDocument": policy_doc, + "lastUpdatedTime": int(unix_time_millis()), + } self.resource_policies[policy_name] = policy + return {"resourcePolicy": policy} + + def delete_resource_policy(self, policy_name): + """Remove resource policy with a policy name matching given name.""" + if policy_name not in self.resource_policies: + raise ResourceNotFoundException( + msg=f"Policy with name [{policy_name}] does not exist" + ) + del self.resource_policies[policy_name] + return "" def list_tags_log_group(self, log_group_name): if log_group_name not in self.groups: @@ -760,9 +803,13 @@ class LogsBackend(BaseBackend): logs_backends = {} -for region in Session().get_available_regions("logs"): - logs_backends[region] = LogsBackend(region) -for region in Session().get_available_regions("logs", partition_name="aws-us-gov"): - logs_backends[region] = LogsBackend(region) -for region in Session().get_available_regions("logs", partition_name="aws-cn"): - logs_backends[region] = LogsBackend(region) +for available_region in Session().get_available_regions("logs"): + logs_backends[available_region] = LogsBackend(available_region) +for available_region in Session().get_available_regions( + "logs", partition_name="aws-us-gov" +): + logs_backends[available_region] = LogsBackend(available_region) +for available_region in Session().get_available_regions( + "logs", partition_name="aws-cn" +): + logs_backends[available_region] = LogsBackend(available_region) diff --git a/moto/logs/responses.py b/moto/logs/responses.py index 1d50df9e9..a7daa063c 100644 --- a/moto/logs/responses.py +++ b/moto/logs/responses.py @@ -1,8 +1,9 @@ -from moto.core.responses import BaseResponse -from .models import logs_backends import json + from .exceptions import InvalidParameterException +from moto.core.responses import BaseResponse +from .models import logs_backends # See http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/Welcome.html @@ -167,10 +168,21 @@ class LogsResponse(BaseResponse): self.logs_backend.delete_retention_policy(log_group_name) return "" + def describe_resource_policies(self): + next_token = self._get_param("nextToken") + limit = self._get_param("limit") + policies = self.logs_backend.describe_resource_policies(next_token, limit) + return json.dumps({"resourcePolicies": policies}) + def put_resource_policy(self): policy_name = self._get_param("policyName") policy_doc = self._get_param("policyDocument") - self.logs_backend.put_resource_policy(policy_name, policy_doc) + result = self.logs_backend.put_resource_policy(policy_name, policy_doc) + return json.dumps(result) + + def delete_resource_policy(self): + policy_name = self._get_param("policyName") + self.logs_backend.delete_resource_policy(policy_name) return "" def list_tags_log_group(self): diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py index 77b7addea..fc04f2910 100644 --- a/tests/test_logs/test_logs.py +++ b/tests/test_logs/test_logs.py @@ -1,15 +1,39 @@ +import json import os import time from unittest import SkipTest import boto3 import pytest -import sure # noqa from botocore.exceptions import ClientError from moto import mock_logs, settings +from moto.core.utils import unix_time_millis +from moto.logs.models import MAX_RESOURCE_POLICIES_PER_REGION -_logs_region = "us-east-1" if settings.TEST_SERVER_MODE else "us-west-2" +TEST_REGION = "us-east-1" if settings.TEST_SERVER_MODE else "us-west-2" + + +@pytest.fixture +def json_policy_doc(): + """Returns a policy document in JSON format. + + The ARN is bogus, but that shouldn't matter for the test. + """ + return json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": {"Service": ["route53.amazonaws.com"]}, + "Action": "logs:PutLogEvents", + "Resource": "log_arn", + } + ], + } + ) @mock_logs @@ -22,7 +46,7 @@ _logs_region = "us-east-1" if settings.TEST_SERVER_MODE else "us-west-2" ) def test_create_log_group(kms_key_id): # Given - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) create_logs_params = dict(logGroupName="dummy") if kms_key_id: @@ -45,7 +69,7 @@ def test_create_log_group(kms_key_id): @mock_logs def test_exceptions(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" log_stream_name = "dummp-stream" conn.create_log_group(logGroupName=log_group_name) @@ -79,7 +103,7 @@ def test_exceptions(): @mock_logs def test_put_logs(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" log_stream_name = "stream" conn.create_log_group(logGroupName=log_group_name) @@ -88,22 +112,22 @@ def test_put_logs(): {"timestamp": 0, "message": "hello"}, {"timestamp": 0, "message": "world"}, ] - putRes = conn.put_log_events( + put_results = conn.put_log_events( logGroupName=log_group_name, logStreamName=log_stream_name, logEvents=messages ) res = conn.get_log_events( logGroupName=log_group_name, logStreamName=log_stream_name ) events = res["events"] - nextSequenceToken = putRes["nextSequenceToken"] - assert isinstance(nextSequenceToken, str) == True - assert len(nextSequenceToken) == 56 + next_sequence_token = put_results["nextSequenceToken"] + assert isinstance(next_sequence_token, str) + assert len(next_sequence_token) == 56 events.should.have.length_of(2) @mock_logs def test_filter_logs_interleaved(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" log_stream_name = "stream" conn.create_log_group(logGroupName=log_group_name) @@ -129,7 +153,7 @@ def test_filter_logs_interleaved(): def test_filter_logs_raises_if_filter_pattern(): if os.environ.get("TEST_SERVER_MODE", "false").lower() == "true": raise SkipTest("Does not work in server mode due to error in Workzeug") - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" log_stream_name = "stream" conn.create_log_group(logGroupName=log_group_name) @@ -151,7 +175,7 @@ def test_filter_logs_raises_if_filter_pattern(): @mock_logs def test_filter_logs_paging(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "/aws/dummy" log_stream_name = "stream/stage" conn.create_log_group(logGroupName=log_group_name) @@ -210,7 +234,7 @@ def test_filter_logs_paging(): @mock_logs def test_put_retention_policy(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" response = conn.create_log_group(logGroupName=log_group_name) @@ -225,7 +249,7 @@ def test_put_retention_policy(): @mock_logs def test_delete_retention_policy(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" response = conn.create_log_group(logGroupName=log_group_name) @@ -239,14 +263,130 @@ def test_delete_retention_policy(): response = conn.describe_log_groups(logGroupNamePrefix=log_group_name) assert len(response["logGroups"]) == 1 - assert response["logGroups"][0].get("retentionInDays") == None + assert response["logGroups"][0].get("retentionInDays") is None - response = conn.delete_log_group(logGroupName=log_group_name) + conn.delete_log_group(logGroupName=log_group_name) + + +@mock_logs +def test_put_resource_policy(): + client = boto3.client("logs", TEST_REGION) + + # For this test a policy document with a valid ARN will be used. + log_group_name = "test_log_group" + client.create_log_group(logGroupName=log_group_name) + log_group_info = client.describe_log_groups(logGroupNamePrefix=log_group_name) + + policy_name = "test_policy" + policy_doc = json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": {"Service": ["route53.amazonaws.com"]}, + "Action": "logs:PutLogEvents", + "Resource": log_group_info["logGroups"][0]["arn"], + } + ], + } + ) + response = client.put_resource_policy( + policyName=policy_name, policyDocument=policy_doc + ) + + assert response["resourcePolicy"]["policyName"] == policy_name + assert response["resourcePolicy"]["policyDocument"] == policy_doc + assert response["resourcePolicy"]["lastUpdatedTime"] <= int(unix_time_millis()) + + client.delete_log_group(logGroupName=log_group_name) + + +@mock_logs +def test_put_resource_policy_too_many(json_policy_doc): + client = boto3.client("logs", TEST_REGION) + + # Create the maximum number of resource policies. + for idx in range(MAX_RESOURCE_POLICIES_PER_REGION): + policy_name = f"test_policy_{idx}" + client.put_resource_policy( + policyName=policy_name, policyDocument=json.dumps(json_policy_doc) + ) + + # Now create one more policy, which should generate an error. + with pytest.raises(ClientError) as exc: + client.put_resource_policy( + policyName="too_many", policyDocument=json.dumps(json_policy_doc) + ) + exc_value = exc.value + exc_value.operation_name.should.equal("PutResourcePolicy") + exc_value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + exc_value.response["Error"]["Code"].should.equal("LimitExceededException") + exc_value.response["Error"]["Message"].should.contain("Resource limit exceeded.") + + +@mock_logs +def test_delete_resource_policy(json_policy_doc): + client = boto3.client("logs", TEST_REGION) + + # Create a bunch of resource policies so we can give delete a workout. + base_policy_name = "test_policy" + for idx in range(MAX_RESOURCE_POLICIES_PER_REGION): + client.put_resource_policy( + policyName=f"{base_policy_name}_{idx}", policyDocument=json_policy_doc + ) + + # Verify that all those resource policies can be deleted. + for idx in range(MAX_RESOURCE_POLICIES_PER_REGION): + client.delete_resource_policy(policyName=f"{base_policy_name}_{idx}") + + # Verify there are no resource policies. + response = client.describe_resource_policies() + policies = response["resourcePolicies"] + assert not policies + + # Try deleting a non-existent resource policy. + with pytest.raises(ClientError) as exc: + client.delete_resource_policy(policyName="non-existent") + exc_value = exc.value + exc_value.operation_name.should.equal("DeleteResourcePolicy") + exc_value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + exc_value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + exc_value.response["Error"]["Message"].should.contain( + "Policy with name [non-existent] does not exist" + ) + + +@mock_logs +def test_describe_resource_policies(json_policy_doc): + client = boto3.client("logs", TEST_REGION) + + # Create the maximum number of resource policies so there's something + # to retrieve. + for idx in range(MAX_RESOURCE_POLICIES_PER_REGION): + policy_name = f"test_policy_{idx}" + client.put_resource_policy( + policyName=policy_name, policyDocument=json_policy_doc + ) + + # Retrieve all of the resource policies that were just created. + response = client.describe_resource_policies(limit=50) + assert "resourcePolicies" in response + policies = response["resourcePolicies"] + assert len(policies) == MAX_RESOURCE_POLICIES_PER_REGION + + # Verify the retrieved list is valid. + now_millis = int(unix_time_millis()) + for idx, policy in enumerate(policies): + assert policy["policyName"] == f"test_policy_{idx}" + assert policy["policyDocument"] == json_policy_doc + assert policy["lastUpdatedTime"] <= now_millis @mock_logs def test_get_log_events(): - client = boto3.client("logs", "us-west-2") + client = boto3.client("logs", TEST_REGION) log_group_name = "test" log_stream_name = "stream" client.create_log_group(logGroupName=log_group_name) @@ -326,7 +466,7 @@ def test_get_log_events(): @mock_logs def test_get_log_events_with_start_from_head(): - client = boto3.client("logs", "us-west-2") + client = boto3.client("logs", TEST_REGION) log_group_name = "test" log_stream_name = "stream" client.create_log_group(logGroupName=log_group_name) @@ -409,44 +549,44 @@ def test_get_log_events_with_start_from_head(): @mock_logs def test_get_log_events_errors(): - client = boto3.client("logs", "us-west-2") + client = boto3.client("logs", TEST_REGION) log_group_name = "test" log_stream_name = "stream" client.create_log_group(logGroupName=log_group_name) client.create_log_stream(logGroupName=log_group_name, logStreamName=log_stream_name) - with pytest.raises(ClientError) as e: + with pytest.raises(ClientError) as exc: client.get_log_events( logGroupName=log_group_name, logStreamName=log_stream_name, nextToken="n/00000000000000000000000000000000000000000000000000000000", ) - ex = e.value - ex.operation_name.should.equal("GetLogEvents") - ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) - ex.response["Error"]["Code"].should.equal("InvalidParameterException") - ex.response["Error"]["Message"].should.contain( + exc_value = exc.value + exc_value.operation_name.should.equal("GetLogEvents") + exc_value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + exc_value.response["Error"]["Code"].should.equal("InvalidParameterException") + exc_value.response["Error"]["Message"].should.contain( "The specified nextToken is invalid." ) - with pytest.raises(ClientError) as e: + with pytest.raises(ClientError) as exc: client.get_log_events( logGroupName=log_group_name, logStreamName=log_stream_name, nextToken="not-existing-token", ) - ex = e.value - ex.operation_name.should.equal("GetLogEvents") - ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) - ex.response["Error"]["Code"].should.equal("InvalidParameterException") - ex.response["Error"]["Message"].should.contain( + exc_value = exc.value + exc_value.operation_name.should.equal("GetLogEvents") + exc_value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + exc_value.response["Error"]["Code"].should.equal("InvalidParameterException") + exc_value.response["Error"]["Message"].should.contain( "The specified nextToken is invalid." ) @mock_logs def test_list_tags_log_group(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" tags = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"} @@ -464,7 +604,7 @@ def test_list_tags_log_group(): @mock_logs def test_tag_log_group(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" tags = {"tag_key_1": "tag_value_1"} response = conn.create_log_group(logGroupName=log_group_name) @@ -492,7 +632,7 @@ def test_tag_log_group(): @mock_logs def test_untag_log_group(): - conn = boto3.client("logs", "us-west-2") + conn = boto3.client("logs", TEST_REGION) log_group_name = "dummy" response = conn.create_log_group(logGroupName=log_group_name) @@ -530,15 +670,15 @@ def test_describe_subscription_filters_errors(): client = boto3.client("logs", "us-east-1") # when - with pytest.raises(ClientError) as e: + with pytest.raises(ClientError) as exc: client.describe_subscription_filters(logGroupName="not-existing-log-group",) # then - ex = e.value - ex.operation_name.should.equal("DescribeSubscriptionFilters") - ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) - ex.response["Error"]["Code"].should.contain("ResourceNotFoundException") - ex.response["Error"]["Message"].should.equal( + exc_value = exc.value + exc_value.operation_name.should.equal("DescribeSubscriptionFilters") + exc_value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + exc_value.response["Error"]["Code"].should.contain("ResourceNotFoundException") + exc_value.response["Error"]["Message"].should.equal( "The specified log group does not exist" ) @@ -604,7 +744,7 @@ def test_describe_log_streams_paging(): resp["logStreams"].should.have.length_of(2) resp["logStreams"][0]["arn"].should.contain(log_group_name) resp["nextToken"].should.equal( - u"{}@{}".format(log_group_name, resp["logStreams"][1]["logStreamName"]) + "{}@{}".format(log_group_name, resp["logStreams"][1]["logStreamName"]) ) resp = client.describe_log_streams( @@ -613,7 +753,7 @@ def test_describe_log_streams_paging(): resp["logStreams"].should.have.length_of(1) resp["logStreams"][0]["arn"].should.contain(log_group_name) resp["nextToken"].should.equal( - u"{}@{}".format(log_group_name, resp["logStreams"][0]["logStreamName"]) + "{}@{}".format(log_group_name, resp["logStreams"][0]["logStreamName"]) ) resp = client.describe_log_streams( @@ -652,7 +792,7 @@ def test_start_query(): assert "queryId" in response - with pytest.raises(ClientError) as e: + with pytest.raises(ClientError) as exc: client.start_query( logGroupName="/aws/codebuild/lowercase-dev-invalid", startTime=int(time.time()), @@ -661,8 +801,8 @@ def test_start_query(): ) # then - ex = e.value - ex.response["Error"]["Code"].should.contain("ResourceNotFoundException") - ex.response["Error"]["Message"].should.equal( + exc_value = exc.value + exc_value.response["Error"]["Code"].should.contain("ResourceNotFoundException") + exc_value.response["Error"]["Message"].should.equal( "The specified log group does not exist" )