class FlowLogsReaderTestCase(TestCase):
    @patch('flowlogs_reader.flowlogs_reader.boto3', autospec=True)
    def setUp(self, mock_boto3):
        self.mock_session = MagicMock()
        mock_boto3.session.Session.return_value = self.mock_session
        self.mock_client = MagicMock()
        self.mock_session.client.return_value = self.mock_client

        self.start_time = datetime(2015, 8, 12, 12, 0, 0)
        self.end_time = datetime(2015, 8, 12, 13, 0, 0)

        self.inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
        )

    def test_init(self):
        self.assertEqual(self.inst.log_group_name, 'group_name')

        self.assertEqual(
            datetime.utcfromtimestamp(self.inst.start_ms // 1000),
            self.start_time
        )

        self.assertEqual(
            datetime.utcfromtimestamp(self.inst.end_ms // 1000),
            self.end_time
        )

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_region_name(self, mock_session):
        # Region specified
        FlowLogsReader('some_group', region_name='some-region')
        mock_session.Session.assert_called_with(region_name='some-region')

        # No region specified - assume configuration file worked
        FlowLogsReader('some_group')
        mock_session.Session.assert_called_with()

        # Region specified in boto_client_kwargs
        FlowLogsReader(
            'some_group', boto_client_kwargs={'region_name': 'my-region'}
        )
        mock_session.Session.assert_called_with(region_name='my-region')

        # No region specified and no configuration file
        def mock_response(*args, **kwargs):
            if 'region_name' not in kwargs:
                raise NoRegionError
        mock_session.Session().client.side_effect = mock_response
        FlowLogsReader('some_group')
        mock_session.Session().client.assert_called_with(
            'logs', region_name=DEFAULT_REGION_NAME
        )

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_profile_name(self, mock_session):
        # profile_name specified
        FlowLogsReader('some_group', profile_name='my-profile')
        mock_session.Session.assert_called_with(profile_name='my-profile')

        # No profile specified
        FlowLogsReader('some_group')
        mock_session.Session.assert_called_with()

    def test_read_streams(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {'events': [0]}, {'events': [1, 2]}, {'events': [3, 4, 5]},
        ]

        self.mock_client.get_paginator.return_value = paginator

        actual = list(self.inst._read_streams())
        expected = [0, 1, 2, 3, 4, 5]
        self.assertEqual(actual, expected)

    def test_iteration(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[0]},
                    {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[1]},
                ],
            },
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[2]},
                    {'logStreamName': 'log_1', 'message': SAMPLE_RECORDS[3]},
                    {'logStreamName': 'log_2', 'message': SAMPLE_RECORDS[4]},
                ],
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        # Calling list on the instance causes it to iterate through all records
        actual = list(self.inst)
        expected = [FlowRecord.from_message(x) for x in SAMPLE_RECORDS]
        self.assertEqual(actual, expected)
class FlowLogsReaderTestCase(TestCase):
    @patch('flowlogs_reader.flowlogs_reader.boto3', autospec=True)
    def setUp(self, mock_boto3):
        self.mock_session = MagicMock()
        mock_boto3.session.Session.return_value = self.mock_session
        self.mock_client = MagicMock()
        self.mock_session.client.return_value = self.mock_client

        self.start_time = datetime(2015, 8, 12, 12, 0, 0)
        self.end_time = datetime(2015, 8, 12, 13, 0, 0)

        self.inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
        )

    def test_init(self):
        self.assertEqual(self.inst.log_group_name, 'group_name')

        self.assertEqual(datetime.utcfromtimestamp(self.inst.start_ms // 1000),
                         self.start_time)

        self.assertEqual(datetime.utcfromtimestamp(self.inst.end_ms // 1000),
                         self.end_time)

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_region_name(self, mock_session):
        # Region specified
        FlowLogsReader('some_group', region_name='some-region')
        mock_session.Session.assert_called_with(region_name='some-region')

        # No region specified - assume configuration file worked
        FlowLogsReader('some_group')
        mock_session.Session.assert_called_with()

        # Region specified in boto_client_kwargs
        FlowLogsReader('some_group',
                       boto_client_kwargs={'region_name': 'my-region'})
        mock_session.Session.assert_called_with(region_name='my-region')

        # No region specified and no configuration file
        def mock_response(*args, **kwargs):
            if 'region_name' not in kwargs:
                raise NoRegionError

        mock_session.Session().client.side_effect = mock_response
        FlowLogsReader('some_group')
        mock_session.Session().client.assert_called_with(
            'logs', region_name=DEFAULT_REGION_NAME)

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_profile_name(self, mock_session):
        # profile_name specified
        FlowLogsReader('some_group', profile_name='my-profile')
        mock_session.Session.assert_called_with(profile_name='my-profile')

        # No profile specified
        FlowLogsReader('some_group')
        mock_session.Session.assert_called_with()

    def test_read_streams(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [0]
            },
            {
                'events': [1, 2]
            },
            {
                'events': [3, 4, 5]
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        actual = list(self.inst._read_streams())
        expected = [0, 1, 2, 3, 4, 5]
        self.assertEqual(actual, expected)

    def test_iteration(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[0]
                    },
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[1]
                    },
                ],
            },
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[2]
                    },
                    {
                        'logStreamName': 'log_1',
                        'message': SAMPLE_RECORDS[3]
                    },
                    {
                        'logStreamName': 'log_2',
                        'message': SAMPLE_RECORDS[4]
                    },
                ],
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        # Calling list on the instance causes it to iterate through all records
        actual = list(self.inst)
        expected = [FlowRecord.from_message(x) for x in SAMPLE_RECORDS]
        self.assertEqual(actual, expected)
class FlowLogsReaderTestCase(TestCase):
    def setUp(self):
        self.mock_client = MagicMock()

        self.start_time = datetime(2015, 8, 12, 12, 0, 0)
        self.end_time = datetime(2015, 8, 12, 13, 0, 0)

        self.inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
            filter_pattern='REJECT',
            boto_client=self.mock_client,
        )

    def test_init(self):
        self.assertEqual(self.inst.log_group_name, 'group_name')

        self.assertEqual(
            datetime.utcfromtimestamp(self.inst.start_ms // 1000),
            self.start_time
        )

        self.assertEqual(
            datetime.utcfromtimestamp(self.inst.end_ms // 1000),
            self.end_time
        )

        self.assertEqual(
            self.inst.paginator_kwargs['filterPattern'],
            'REJECT'
        )

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_region_name(self, mock_session):
        # Region specified for session
        FlowLogsReader('some_group', region_name='some-region')
        mock_session.Session.assert_called_with(region_name='some-region')

        # Region specified for client, not for session
        FlowLogsReader(
            'some_group', boto_client_kwargs={'region_name': 'my-region'}
        )
        mock_session.Session().client.assert_called_with(
            'logs', region_name='my-region'
        )

        # No region specified for session or client - use the default
        def mock_response(*args, **kwargs):
            if 'region_name' not in kwargs:
                raise NoRegionError
        mock_session.Session().client.side_effect = mock_response

        FlowLogsReader('some_group')
        mock_session.Session().client.assert_called_with(
            'logs', region_name=DEFAULT_REGION_NAME
        )

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_profile_name(self, mock_session):
        # profile_name specified
        FlowLogsReader('some_group', profile_name='my-profile')
        mock_session.Session.assert_called_with(profile_name='my-profile')

        # No profile specified
        FlowLogsReader('some_group')
        mock_session.Session.assert_called_with()

    def test_read_streams(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {'events': [0]}, {'events': [1, 2]}, {'events': [3, 4, 5]},
        ]

        self.mock_client.get_paginator.return_value = paginator

        actual = list(self.inst._read_streams())
        expected = [0, 1, 2, 3, 4, 5]
        self.assertEqual(actual, expected)

    def test_iteration(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[0]},
                    {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[1]},
                ],
            },
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[2]},
                    {'logStreamName': 'log_1', 'message': SAMPLE_RECORDS[3]},
                    {'logStreamName': 'log_2', 'message': SAMPLE_RECORDS[4]},
                ],
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        # Calling list on the instance causes it to iterate through all records
        actual = [next(self.inst)] + list(self.inst)
        expected = [FlowRecord.from_message(x) for x in SAMPLE_RECORDS]
        self.assertEqual(actual, expected)

    def test_iteration_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[0]}
            event_1 = {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[1]}
            for item in [{'events': [event_0, event_1]}]:
                yield item

            err_msg = '{}: {}'.format(DUPLICATE_NEXT_TOKEN_MESSAGE, 'token')
            raise PaginationError(message=err_msg)

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator
        )

        # Don't fail if botocore's paginator raises a PaginationError
        actual = [next(self.inst)] + list(self.inst)
        expected = [FlowRecord.from_message(x) for x in SAMPLE_RECORDS[:2]]
        self.assertEqual(actual, expected)

    def test_iteration_unexpecetd_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[0]}
            yield {'events': [event_0]}
            raise PaginationError(message='other error')

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator
        )

        # Fail for unexpected PaginationError
        self.assertRaises(PaginationError, lambda: list(self.inst))
Example #4
0
class FlowLogsReaderTestCase(TestCase):
    @patch('flowlogs_reader.flowlogs_reader.boto3', autospec=True)
    def setUp(self, mock_boto3):
        self.mock_client = MagicMock()
        mock_boto3.client.return_value = self.mock_client

        self.start_time = datetime(2015, 8, 12, 12, 0, 0)
        self.end_time = datetime(2015, 8, 12, 13, 0, 0)

        self.inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
        )

    def test_init(self):
        self.assertEqual(self.inst.log_group_name, 'group_name')

        self.assertEqual(datetime.utcfromtimestamp(self.inst.start_ms // 1000),
                         self.start_time)

        self.assertEqual(datetime.utcfromtimestamp(self.inst.end_ms // 1000),
                         self.end_time)

    @patch('flowlogs_reader.flowlogs_reader.boto3.client', autospec=True)
    def test_region(self, mock_client):
        # Region specified
        FlowLogsReader('some_group', region_name='some-region')
        mock_client.assert_called_with('logs', region_name='some-region')

        # No region specified - assume configuration file worked
        FlowLogsReader('some_group')
        mock_client.assert_called_with('logs')

        # No region specified and no configuration file
        def mock_response(*args, **kwargs):
            if 'region_name' not in kwargs:
                raise NoRegionError

        mock_client.side_effect = mock_response
        FlowLogsReader('some_group')
        mock_client.assert_called_with('logs', region_name=DEFAULT_REGION_NAME)

    def test_read_streams(self):
        response_list = [
            {
                'events': [0],
                'nextToken': 'token_0'
            },
            {
                'events': [1, 2],
                'nextToken': 'token_1'
            },
            {
                'events': [3, 4, 5],
                'nextToken': None
            },
            {
                'events': [6],
                'nextForwardToken': 'token_2'
            },  # Unreachable
        ]

        def mock_filter(*args, **kwargs):
            return response_list.pop(0)

        self.mock_client.filter_log_events.side_effect = mock_filter

        actual = list(self.inst._read_streams())
        expected = [0, 1, 2, 3, 4, 5]
        self.assertEqual(actual, expected)

    def test_iteration(self):
        response_list = [
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[0]
                    },
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[1]
                    },
                ],
                'nextToken':
                'token_0',
            },
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[2]
                    },
                    {
                        'logStreamName': 'log_1',
                        'message': SAMPLE_RECORDS[3]
                    },
                    {
                        'logStreamName': 'log_2',
                        'message': SAMPLE_RECORDS[4]
                    },
                ],
            },
        ]

        def mock_filter(*args, **kwargs):
            return response_list.pop(0)

        self.mock_client.filter_log_events.side_effect = mock_filter

        # Calling list on the instance causes it to iterate through all records
        actual = list(self.inst)
        expected = [FlowRecord.from_message(x) for x in SAMPLE_RECORDS]
        self.assertEqual(actual, expected)
Example #5
0
class FlowLogsReaderTestCase(TestCase):
    def setUp(self):
        self.mock_client = MagicMock()

        self.start_time = datetime(2015, 8, 12, 12, 0, 0)
        self.end_time = datetime(2015, 8, 12, 13, 0, 0)

        self.inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
            filter_pattern='REJECT',
            boto_client=self.mock_client,
        )

    def test_init(self):
        self.assertEqual(self.inst.log_group_name, 'group_name')

        self.assertEqual(datetime.utcfromtimestamp(self.inst.start_ms // 1000),
                         self.start_time)

        self.assertEqual(datetime.utcfromtimestamp(self.inst.end_ms // 1000),
                         self.end_time)

        self.assertEqual(self.inst.paginator_kwargs['filterPattern'], 'REJECT')

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_region_name(self, mock_session):
        # Region specified for session
        FlowLogsReader('some_group', region_name='some-region')
        mock_session.Session.assert_called_with(region_name='some-region')

        # Region specified for client, not for session
        FlowLogsReader('some_group',
                       boto_client_kwargs={'region_name': 'my-region'})
        mock_session.Session().client.assert_called_with(
            'logs', region_name='my-region')

        # No region specified for session or client - use the default
        def mock_response(*args, **kwargs):
            if 'region_name' not in kwargs:
                raise NoRegionError

        mock_session.Session().client.side_effect = mock_response

        FlowLogsReader('some_group')
        mock_session.Session().client.assert_called_with(
            'logs', region_name=DEFAULT_REGION_NAME)

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_profile_name(self, mock_session):
        # profile_name specified
        FlowLogsReader('some_group', profile_name='my-profile')
        mock_session.Session.assert_called_with(profile_name='my-profile')

        # No profile specified
        FlowLogsReader('some_group')
        mock_session.Session.assert_called_with()

    def test_read_streams(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [0]
            },
            {
                'events': [1, 2]
            },
            {
                'events': [3, 4, 5]
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        actual = list(self.inst._read_streams())
        expected = [0, 1, 2, 3, 4, 5]
        self.assertEqual(actual, expected)

    def test_iteration(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[0]
                    },
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[1]
                    },
                ],
            },
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': SAMPLE_RECORDS[2]
                    },
                    {
                        'logStreamName': 'log_1',
                        'message': SAMPLE_RECORDS[3]
                    },
                    {
                        'logStreamName': 'log_2',
                        'message': SAMPLE_RECORDS[4]
                    },
                ],
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        # Calling list on the instance causes it to iterate through all records
        actual = [next(self.inst)] + list(self.inst)
        expected = [FlowRecord.from_message(x) for x in SAMPLE_RECORDS]
        self.assertEqual(actual, expected)

    def test_iteration_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[0]}
            event_1 = {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[1]}
            for item in [{'events': [event_0, event_1]}]:
                yield item

            err_msg = '{}: {}'.format(DUPLICATE_NEXT_TOKEN_MESSAGE, 'token')
            raise PaginationError(message=err_msg)

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator)

        # Don't fail if botocore's paginator raises a PaginationError
        actual = [next(self.inst)] + list(self.inst)
        expected = [FlowRecord.from_message(x) for x in SAMPLE_RECORDS[:2]]
        self.assertEqual(actual, expected)

    def test_iteration_unexpecetd_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': SAMPLE_RECORDS[0]}
            yield {'events': [event_0]}
            raise PaginationError(message='other error')

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator)

        # Fail for unexpected PaginationError
        self.assertRaises(PaginationError, lambda: list(self.inst))
Example #6
0
class FlowLogsReaderTestCase(TestCase):
    def setUp(self):
        self.mock_client = MagicMock()

        self.start_time = datetime(2015, 8, 12, 12, 0, 0)
        self.end_time = datetime(2015, 8, 12, 13, 0, 0)

        self.inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
            filter_pattern='REJECT',
            boto_client=self.mock_client,
        )

    def test_init(self):
        self.assertEqual(self.inst.log_group_name, 'group_name')

        self.assertEqual(
            datetime.utcfromtimestamp(self.inst.start_ms // 1000),
            self.start_time,
        )

        self.assertEqual(
            datetime.utcfromtimestamp(self.inst.end_ms // 1000), self.end_time
        )

        self.assertEqual(self.inst.paginator_kwargs['filterPattern'], 'REJECT')

    @patch('flowlogs_reader.flowlogs_reader.boto3.client', autospec=True)
    def test_region_name(self, mock_client):
        # Region specified for session
        FlowLogsReader('some_group', region_name='some-region')
        mock_client.assert_called_with('logs', region_name='some-region')

        # None specified
        FlowLogsReader('some_group')
        mock_client.assert_called_with('logs')

    @patch('flowlogs_reader.flowlogs_reader.boto3.client', autospec=True)
    def test_get_fields(self, mock_client):
        cwl_client = MagicMock()
        ec2_client = mock_client.return_value
        ec2_client.describe_flow_logs.return_value = {
            'FlowLogs': [
                {'LogFormat': '${srcaddr} ${dstaddr} ${start} ${log-status}'}
            ]
        }
        reader = FlowLogsReader(
            'some_group',
            boto_client=cwl_client,
            fields=None,
        )
        self.assertEqual(
            reader.fields, ('srcaddr', 'dstaddr', 'start', 'log_status')
        )
        ec2_client.describe_flow_logs.assert_called_once_with(
            Filters=[{'Name': 'log-group-name', 'Values': ['some_group']}]
        )

    def test_read_streams(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': V2_RECORDS[0]},
                    {'logStreamName': 'log_0', 'message': V2_RECORDS[1]},
                ],
            },
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': V2_RECORDS[2]},
                    {'logStreamName': 'log_1', 'message': V2_RECORDS[3]},
                    {'logStreamName': 'log_2', 'message': V2_RECORDS[4]},
                ],
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        actual = list(self.inst._read_streams())
        expected = []
        for page in paginator.paginate.return_value:
            expected += page['events']
        self.assertEqual(actual, expected)

    def test_iteration(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': V2_RECORDS[0]},
                    {'logStreamName': 'log_0', 'message': V2_RECORDS[1]},
                ],
            },
            {
                'events': [
                    {'logStreamName': 'log_0', 'message': V2_RECORDS[2]},
                    {'logStreamName': 'log_1', 'message': V2_RECORDS[3]},
                    {'logStreamName': 'log_2', 'message': V2_RECORDS[4]},
                ],
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        # Calling list on the instance causes it to iterate through all records
        actual = [next(self.inst)] + list(self.inst)
        expected = [
            FlowRecord.from_cwl_event({'message': x}) for x in V2_RECORDS
        ]
        self.assertEqual(actual, expected)

        expected_bytes = 0
        all_pages = paginator.paginate.return_value
        expected_bytes = sum(
            len(e['message']) for p in all_pages for e in p['events']
        )
        self.assertEqual(self.inst.bytes_processed, expected_bytes)

    def test_iteration_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': V2_RECORDS[0]}
            event_1 = {'logStreamName': 'log_0', 'message': V2_RECORDS[1]}
            for item in [{'events': [event_0, event_1]}]:
                yield item

            err_msg = '{}: {}'.format(DUPLICATE_NEXT_TOKEN_MESSAGE, 'token')
            raise PaginationError(message=err_msg)

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator
        )

        # Don't fail if botocore's paginator raises a PaginationError
        actual = [next(self.inst)] + list(self.inst)
        records = V2_RECORDS[:2]
        expected = [FlowRecord.from_cwl_event({'message': x}) for x in records]
        self.assertEqual(actual, expected)

    def test_iteration_unexpecetd_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': V2_RECORDS[0]}
            yield {'events': [event_0]}
            raise PaginationError(message='other error')

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator
        )

        # Fail for unexpected PaginationError
        self.assertRaises(PaginationError, lambda: list(self.inst))

    def test_threads(self):
        inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
            filter_pattern='REJECT',
            boto_client=self.mock_client,
            thread_count=1,
        )

        paginators = []

        def _get_paginator(operation):
            nonlocal paginators

            paginator = MagicMock()
            if operation == 'describe_log_streams':
                paginator.paginate.return_value = [
                    {
                        'logStreams': [
                            {
                                'logStreamName': 'too_late',
                                'firstEventTimestamp': inst.end_ms,
                                'lastEventTimestamp': inst.start_ms,
                            },
                            {
                                'logStreamName': 'too_late',
                                'firstEventTimestamp': inst.end_ms - 1,
                                'lastEventTimestamp': (
                                    inst.start_ms - LAST_EVENT_DELAY_MSEC - 1
                                ),
                            },
                        ],
                    },
                    {
                        'logStreams': [
                            {
                                'logStreamName': 'first_stream',
                                'firstEventTimestamp': inst.start_ms,
                                'lastEventTimestamp': inst.end_ms,
                            },
                            {
                                'logStreamName': 'second_stream',
                                'firstEventTimestamp': inst.start_ms,
                                'lastEventTimestamp': inst.end_ms,
                            },
                        ],
                    },
                ]
            elif operation == 'filter_log_events':
                paginator.paginate.return_value = [
                    {
                        'events': [
                            {'message': V2_RECORDS[0]},
                            {'message': V2_RECORDS[1]},
                        ],
                    },
                    {
                        'events': [
                            {'message': V2_RECORDS[2]},
                            {'message': V2_RECORDS[3]},
                        ],
                    },
                ]
            else:
                self.fail('invalid operation')

            paginators.append(paginator)
            return paginator

        self.mock_client.get_paginator.side_effect = _get_paginator
        events = list(inst)
        self.assertEqual(len(events), 8)

        paginators[0].paginate.assert_called_once_with(
            logGroupName='group_name',
            orderBy='LastEventTime',
            descending=True,
        )
        paginators[1].paginate.assert_called_once_with(
            logGroupName='group_name',
            startTime=inst.start_ms,
            endTime=inst.end_ms,
            interleaved=True,
            filterPattern='REJECT',
            logStreamNames=['first_stream'],
        )
        paginators[2].paginate.assert_called_once_with(
            logGroupName='group_name',
            startTime=inst.start_ms,
            endTime=inst.end_ms,
            interleaved=True,
            filterPattern='REJECT',
            logStreamNames=['second_stream'],
        )
class FlowLogsReaderTestCase(TestCase):
    def setUp(self):
        self.mock_client = MagicMock()

        self.start_time = datetime(2015, 8, 12, 12, 0, 0)
        self.end_time = datetime(2015, 8, 12, 13, 0, 0)

        self.inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
            filter_pattern='REJECT',
            boto_client=self.mock_client,
        )

    def test_init(self):
        self.assertEqual(self.inst.log_group_name, 'group_name')

        self.assertEqual(datetime.utcfromtimestamp(self.inst.start_ms // 1000),
                         self.start_time)

        self.assertEqual(datetime.utcfromtimestamp(self.inst.end_ms // 1000),
                         self.end_time)

        self.assertEqual(self.inst.paginator_kwargs['filterPattern'], 'REJECT')

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_region_name(self, mock_session):
        # Region specified for session
        FlowLogsReader('some_group', region_name='some-region')
        mock_session.Session.assert_called_with(region_name='some-region')

        # Region specified for client, not for session
        FlowLogsReader('some_group',
                       boto_client_kwargs={'region_name': 'my-region'})
        mock_session.Session().client.assert_called_with(
            'logs', region_name='my-region')

        # No region specified for session or client - use the default
        def mock_response(*args, **kwargs):
            if 'region_name' not in kwargs:
                raise NoRegionError

        mock_session.Session().client.side_effect = mock_response

        FlowLogsReader('some_group')
        mock_session.Session().client.assert_called_with(
            'logs', region_name=DEFAULT_REGION_NAME)

    @patch('flowlogs_reader.flowlogs_reader.boto3.session', autospec=True)
    def test_profile_name(self, mock_session):
        # profile_name specified
        FlowLogsReader('some_group', profile_name='my-profile')
        mock_session.Session.assert_called_with(profile_name='my-profile')

        # No profile specified
        FlowLogsReader('some_group')
        mock_session.Session.assert_called_with()

    def test_read_streams(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [0]
            },
            {
                'events': [1, 2]
            },
            {
                'events': [3, 4, 5]
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        actual = list(self.inst._read_streams())
        expected = [0, 1, 2, 3, 4, 5]
        self.assertEqual(actual, expected)

    def test_iteration(self):
        paginator = MagicMock()
        paginator.paginate.return_value = [
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': V2_RECORDS[0]
                    },
                    {
                        'logStreamName': 'log_0',
                        'message': V2_RECORDS[1]
                    },
                ],
            },
            {
                'events': [
                    {
                        'logStreamName': 'log_0',
                        'message': V2_RECORDS[2]
                    },
                    {
                        'logStreamName': 'log_1',
                        'message': V2_RECORDS[3]
                    },
                    {
                        'logStreamName': 'log_2',
                        'message': V2_RECORDS[4]
                    },
                ],
            },
        ]

        self.mock_client.get_paginator.return_value = paginator

        # Calling list on the instance causes it to iterate through all records
        actual = [next(self.inst)] + list(self.inst)
        expected = [
            FlowRecord.from_cwl_event({'message': x}) for x in V2_RECORDS
        ]
        self.assertEqual(actual, expected)

    def test_iteration_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': V2_RECORDS[0]}
            event_1 = {'logStreamName': 'log_0', 'message': V2_RECORDS[1]}
            for item in [{'events': [event_0, event_1]}]:
                yield item

            err_msg = '{}: {}'.format(DUPLICATE_NEXT_TOKEN_MESSAGE, 'token')
            raise PaginationError(message=err_msg)

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator)

        # Don't fail if botocore's paginator raises a PaginationError
        actual = [next(self.inst)] + list(self.inst)
        records = V2_RECORDS[:2]
        expected = [FlowRecord.from_cwl_event({'message': x}) for x in records]
        self.assertEqual(actual, expected)

    def test_iteration_unexpecetd_error(self):
        # Simulate the paginator failing
        def _get_paginator(*args, **kwargs):
            event_0 = {'logStreamName': 'log_0', 'message': V2_RECORDS[0]}
            yield {'events': [event_0]}
            raise PaginationError(message='other error')

        self.mock_client.get_paginator.return_value.paginate.side_effect = (
            _get_paginator)

        # Fail for unexpected PaginationError
        self.assertRaises(PaginationError, lambda: list(self.inst))

    def test_threads(self):
        inst = FlowLogsReader(
            'group_name',
            start_time=self.start_time,
            end_time=self.end_time,
            filter_pattern='REJECT',
            boto_client=self.mock_client,
            thread_count=1,
        )

        paginators = []

        def _get_paginator(operation):
            nonlocal paginators

            paginator = MagicMock()
            if operation == 'describe_log_streams':
                paginator.paginate.return_value = [
                    {
                        'logStreams': [
                            {
                                'logStreamName': 'too_late',
                                'firstEventTimestamp': inst.end_ms,
                                'lastEventTimestamp': inst.start_ms,
                            },
                            {
                                'logStreamName':
                                'too_late',
                                'firstEventTimestamp':
                                inst.end_ms - 1,
                                'lastEventTimestamp':
                                (inst.start_ms - LAST_EVENT_DELAY_MSEC - 1),
                            },
                        ],
                    },
                    {
                        'logStreams': [
                            {
                                'logStreamName': 'first_stream',
                                'firstEventTimestamp': inst.start_ms,
                                'lastEventTimestamp': inst.end_ms,
                            },
                            {
                                'logStreamName': 'second_stream',
                                'firstEventTimestamp': inst.start_ms,
                                'lastEventTimestamp': inst.end_ms,
                            },
                        ],
                    },
                ]
            elif operation == 'filter_log_events':
                paginator.paginate.return_value = [
                    {
                        'events': [
                            {
                                'message': V2_RECORDS[0]
                            },
                            {
                                'message': V2_RECORDS[1]
                            },
                        ],
                    },
                    {
                        'events': [
                            {
                                'message': V2_RECORDS[2]
                            },
                            {
                                'message': V2_RECORDS[3]
                            },
                        ],
                    },
                ]
            else:
                self.fail('invalid operation')

            paginators.append(paginator)
            return paginator

        self.mock_client.get_paginator.side_effect = _get_paginator
        events = list(inst)
        self.assertEqual(len(events), 8)

        paginators[0].paginate.assert_called_once_with(
            logGroupName='group_name',
            orderBy='LastEventTime',
            descending=True,
        )
        paginators[1].paginate.assert_called_once_with(
            logGroupName='group_name',
            startTime=inst.start_ms,
            endTime=inst.end_ms,
            interleaved=True,
            filterPattern='REJECT',
            logStreamNames=['first_stream'],
        )
        paginators[2].paginate.assert_called_once_with(
            logGroupName='group_name',
            startTime=inst.start_ms,
            endTime=inst.end_ms,
            interleaved=True,
            filterPattern='REJECT',
            logStreamNames=['second_stream'],
        )