diff --git a/moto/logs/exceptions.py b/moto/logs/exceptions.py new file mode 100644 index 000000000..cc83452ea --- /dev/null +++ b/moto/logs/exceptions.py @@ -0,0 +1,33 @@ +from __future__ import unicode_literals +from moto.core.exceptions import JsonRESTError + + +class LogsClientError(JsonRESTError): + code = 400 + + +class ResourceNotFoundException(LogsClientError): + def __init__(self): + self.code = 400 + super(ResourceNotFoundException, self).__init__( + "ResourceNotFoundException", + "The specified resource does not exist" + ) + + +class InvalidParameterException(LogsClientError): + def __init__(self, msg=None): + self.code = 400 + super(InvalidParameterException, self).__init__( + "InvalidParameterException", + msg or "A parameter is specified incorrectly." + ) + + +class ResourceAlreadyExistsException(LogsClientError): + def __init__(self): + self.code = 400 + super(ResourceAlreadyExistsException, self).__init__( + 'ResourceAlreadyExistsException', + 'The specified resource already exists.' + ) diff --git a/moto/logs/models.py b/moto/logs/models.py index 09dcb3645..6ff7f93bf 100644 --- a/moto/logs/models.py +++ b/moto/logs/models.py @@ -1,6 +1,10 @@ from moto.core import BaseBackend import boto.logs from moto.core.utils import unix_time_millis +from .exceptions import ( + ResourceNotFoundException, + ResourceAlreadyExistsException +) class LogEvent: @@ -49,23 +53,29 @@ class LogStream: self.__class__._log_ids += 1 def _update(self): - self.firstEventTimestamp = min([x.timestamp for x in self.events]) - self.lastEventTimestamp = max([x.timestamp for x in self.events]) + # events can be empty when stream is described soon after creation + self.firstEventTimestamp = min([x.timestamp for x in self.events]) if self.events else None + self.lastEventTimestamp = max([x.timestamp for x in self.events]) if self.events else None def to_describe_dict(self): # Compute start and end times self._update() - return { + res = { "arn": self.arn, "creationTime": self.creationTime, - "firstEventTimestamp": self.firstEventTimestamp, - "lastEventTimestamp": self.lastEventTimestamp, - "lastIngestionTime": self.lastIngestionTime, "logStreamName": self.logStreamName, "storedBytes": self.storedBytes, - "uploadSequenceToken": str(self.uploadSequenceToken), } + if self.events: + rest = { + "firstEventTimestamp": self.firstEventTimestamp, + "lastEventTimestamp": self.lastEventTimestamp, + "lastIngestionTime": self.lastIngestionTime, + "uploadSequenceToken": str(self.uploadSequenceToken), + } + res.update(rest) + return res def put_log_events(self, log_group_name, log_stream_name, log_events, sequence_token): # TODO: ensure sequence_token @@ -126,18 +136,22 @@ class LogGroup: self.streams = dict() # {name: LogStream} def create_log_stream(self, log_stream_name): - assert log_stream_name not in self.streams + if log_stream_name in self.streams: + raise ResourceAlreadyExistsException() self.streams[log_stream_name] = LogStream(self.region, self.name, log_stream_name) def delete_log_stream(self, log_stream_name): - assert log_stream_name in self.streams + if log_stream_name not in self.streams: + raise ResourceNotFoundException() del self.streams[log_stream_name] def describe_log_streams(self, descending, limit, log_group_name, log_stream_name_prefix, next_token, order_by): + # responses only logStreamName, creationTime, arn, storedBytes when no events are stored. + log_streams = [(name, stream.to_describe_dict()) for name, stream in self.streams.items() if name.startswith(log_stream_name_prefix)] def sorter(item): - return item[0] if order_by == 'logStreamName' else item[1]['lastEventTimestamp'] + return item[0] if order_by == 'logStreamName' else item[1].get('lastEventTimestamp', 0) if next_token is None: next_token = 0 @@ -151,18 +165,18 @@ class LogGroup: return log_streams_page, new_token def put_log_events(self, log_group_name, log_stream_name, log_events, sequence_token): - assert log_stream_name in self.streams + if log_stream_name not in self.streams: + raise ResourceNotFoundException() stream = self.streams[log_stream_name] return stream.put_log_events(log_group_name, log_stream_name, log_events, sequence_token) def get_log_events(self, log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head): - assert log_stream_name in self.streams + if log_stream_name not in self.streams: + raise ResourceNotFoundException() stream = self.streams[log_stream_name] return stream.get_log_events(log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head) def filter_log_events(self, log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved): - assert not filter_pattern # TODO: impl - streams = [stream for name, stream in self.streams.items() if not log_stream_names or name in log_stream_names] events = [] @@ -195,7 +209,8 @@ class LogsBackend(BaseBackend): self.__init__(region_name) def create_log_group(self, log_group_name, tags): - assert log_group_name not in self.groups + if log_group_name in self.groups: + raise ResourceAlreadyExistsException() self.groups[log_group_name] = LogGroup(self.region_name, log_group_name, tags) def ensure_log_group(self, log_group_name, tags): @@ -204,37 +219,44 @@ class LogsBackend(BaseBackend): self.groups[log_group_name] = LogGroup(self.region_name, log_group_name, tags) def delete_log_group(self, log_group_name): - assert log_group_name in self.groups + if log_group_name not in self.groups: + raise ResourceNotFoundException() del self.groups[log_group_name] def create_log_stream(self, log_group_name, log_stream_name): - assert log_group_name in self.groups + if log_group_name not in self.groups: + raise ResourceNotFoundException() log_group = self.groups[log_group_name] return log_group.create_log_stream(log_stream_name) def delete_log_stream(self, log_group_name, log_stream_name): - assert log_group_name in self.groups + if log_group_name not in self.groups: + raise ResourceNotFoundException() log_group = self.groups[log_group_name] return log_group.delete_log_stream(log_stream_name) def describe_log_streams(self, descending, limit, log_group_name, log_stream_name_prefix, next_token, order_by): - assert log_group_name in self.groups + if log_group_name not in self.groups: + raise ResourceNotFoundException() log_group = self.groups[log_group_name] return log_group.describe_log_streams(descending, limit, log_group_name, log_stream_name_prefix, next_token, order_by) def put_log_events(self, log_group_name, log_stream_name, log_events, sequence_token): # TODO: add support for sequence_tokens - assert log_group_name in self.groups + if log_group_name not in self.groups: + raise ResourceNotFoundException() log_group = self.groups[log_group_name] return log_group.put_log_events(log_group_name, log_stream_name, log_events, sequence_token) def get_log_events(self, log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head): - assert log_group_name in self.groups + if log_group_name not in self.groups: + raise ResourceNotFoundException() log_group = self.groups[log_group_name] return log_group.get_log_events(log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head) def filter_log_events(self, log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved): - assert log_group_name in self.groups + if log_group_name not in self.groups: + raise ResourceNotFoundException() log_group = self.groups[log_group_name] return log_group.filter_log_events(log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved) diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py index 392b3f7e9..1b2f5f75e 100644 --- a/tests/test_logs/test_logs.py +++ b/tests/test_logs/test_logs.py @@ -1,7 +1,9 @@ import boto3 import sure # noqa +from botocore.exceptions import ClientError from moto import mock_logs, settings +from nose.tools import assert_raises _logs_region = 'us-east-1' if settings.TEST_SERVER_MODE else 'us-west-2' @@ -12,3 +14,48 @@ def test_log_group_create(): log_group_name = 'dummy' response = conn.create_log_group(logGroupName=log_group_name) response = conn.delete_log_group(logGroupName=log_group_name) + + +@mock_logs +def test_exceptions(): + conn = boto3.client('logs', 'us-west-2') + log_group_name = 'dummy' + log_stream_name = 'dummp-stream' + conn.create_log_group(logGroupName=log_group_name) + with assert_raises(ClientError): + conn.create_log_group(logGroupName=log_group_name) + + # descrine_log_groups is not implemented yet + + conn.create_log_stream( + logGroupName=log_group_name, + logStreamName=log_stream_name + ) + with assert_raises(ClientError): + conn.create_log_stream( + logGroupName=log_group_name, + logStreamName=log_stream_name + ) + + conn.put_log_events( + logGroupName=log_group_name, + logStreamName=log_stream_name, + logEvents=[ + { + 'timestamp': 0, + 'message': 'line' + }, + ], + ) + + with assert_raises(ClientError): + conn.put_log_events( + logGroupName=log_group_name, + logStreamName="invalid-stream", + logEvents=[ + { + 'timestamp': 0, + 'message': 'line' + }, + ], + )