Exemplo n.º 1
0
    def setUp(self):
        # Set up a mock request object
        self._issues = [{'key': self.ISSUE_KEY}]
        self._comments = []
        self._res = MagicMock(
            status_code=200,
            json=MagicMock(
                # GOTCHA: res.json() is called 3 times total to get the data from HTTP call.
                # return_vale cannot return different values based on the order, so use side_effect.
                side_effect=[
                    {'issues': self._issues},
                    {'comments': self._comments},
                    {},
                ]
            ),
        )
        self._request = MagicMock(
            return_value=self._res,
        )

        # Set up a mock logger. This is used to verify code execution
        self._logger = MagicMock()

        # Set up the listener to be tested
        self._listener = JiraListener(
            username=self.APP_USERNAME,
            password=self.APP_PASSWORD,
            base_url=self.APP_BASE_URL,
            logger=self._logger,
        )

        # Set up a mock instance that is having a maintenance
        self._instance = MagicMock(
            id=self.INSTANCE_ID,
            tags={'Name': self.INSTANCE_NAME},
        )

        # Set up a mock maintenance event
        self._event = MagicMock(
            not_before=DateTime().ISO8601(),
            not_after=DateTime().ISO8601(),
        )
Exemplo n.º 2
0
class JiraListenerTest(unittest.TestCase):
    APP_USERNAME = '******'
    APP_PASSWORD = '******'
    APP_BASE_URL = 'https://unit-test.example.com/'
    APP_SEARCH_URL = '/rest/api/2/search'
    APP_COMMENT_URL = '/rest/api/2/issue/{issue}/comment'
    INSTANCE_NAME = 'unit-test.krxd.net'
    INSTANCE_ID = 'i-a1b2c3d4'
    ISSUE_KEY = 'FAKE-1234'
    EXCEPTION_STATUS = 400
    EXCEPTION_REASON = 'Bad Request'
    EXCEPTION_CONTENT = 'Unit test failure'

    JQL_TEMPLATE = 'description ~ "{instance_id}" AND type = "Maintenance Task" AND createdDate >= "{yesterday}"'
    COMMENT_TEMPLATE = '{instance_name}\r\n\r\nPlease schedule Icinga downtime from {start_time} to {end_time}.'
    EXCEPTION_TEMPLATE = 'Something went wrong. {status_code} {reason} was returned. Body: {body}'

    def setUp(self):
        # Set up a mock request object
        self._issues = [{'key': self.ISSUE_KEY}]
        self._comments = []
        self._res = MagicMock(
            status_code=200,
            json=MagicMock(
                # GOTCHA: res.json() is called 3 times total to get the data from HTTP call.
                # return_vale cannot return different values based on the order, so use side_effect.
                side_effect=[
                    {'issues': self._issues},
                    {'comments': self._comments},
                    {},
                ]
            ),
        )
        self._request = MagicMock(
            return_value=self._res,
        )

        # Set up a mock logger. This is used to verify code execution
        self._logger = MagicMock()

        # Set up the listener to be tested
        self._listener = JiraListener(
            username=self.APP_USERNAME,
            password=self.APP_PASSWORD,
            base_url=self.APP_BASE_URL,
            logger=self._logger,
        )

        # Set up a mock instance that is having a maintenance
        self._instance = MagicMock(
            id=self.INSTANCE_ID,
            tags={'Name': self.INSTANCE_NAME},
        )

        # Set up a mock maintenance event
        self._event = MagicMock(
            not_before=DateTime().ISO8601(),
            not_after=DateTime().ISO8601(),
        )

    @classmethod
    def _generate_request_call(cls, **kwargs):
        """
        Fill out all the basic data that is shared amongst all the request calls
        This verifies that every call is using the correct HTTP header
        """
        kwargs['url'] = cls.APP_BASE_URL + kwargs['url']
        return call(
            auth=(cls.APP_USERNAME, cls.APP_PASSWORD),
            headers={
                'Content-Type': 'application/json',
                'Accept': 'application/json',
            },
            **kwargs
        )

    def _get_jql_search(self):
        """
        Returns a tuple of logger call and request call regarding JQL search
        """
        yesterday_str = (DateTime() - 1).Date()
        jql_search = self.JQL_TEMPLATE.format(instance_id=self.INSTANCE_ID, yesterday=yesterday_str)
        return (
            call(
                'Found %s issues that matches the JQL search: %s',
                len(self._issues),
                jql_search,
            ),
            self._generate_request_call(
                method='POST',
                url=self.APP_SEARCH_URL,
                json={
                    'jql': jql_search,
                    'fields': [],
                },
            )
        )

    def _get_retrieve_comment_request_call(self):
        return self._generate_request_call(
            method='GET',
            url=self.APP_COMMENT_URL.format(issue=self.ISSUE_KEY),
        )

    def _get_comment_body(self, end_time):
        start_time = DateTime(self._event.not_before)
        return self.COMMENT_TEMPLATE.format(
            instance_name=self.INSTANCE_NAME,
            # GOTCHA: Change the time to Pacific time for easier calculation
            # This should handle Daylight Savings Time graciously on its own
            start_time=str(start_time.toZone('US/Pacific')),
            end_time=str(end_time.toZone('US/Pacific')),
        )

    def _verify_full_execution(self, end_time):
        """
        Calls handle_event(), assumes that the whole method is getting executed, and verifies accordingly
        end_time is required to check the body of the comment
        """
        # Calls handle_event()
        with patch('aws_analysis_tools.ec2_events.jira_listener.request', self._request):
            self._listener.handle_event(self._instance, self._event)

        jql_search_log_call, jql_search_requst_call = self._get_jql_search()

        # Checks the debug log is correct
        debug_calls = [
            jql_search_log_call,
            call('Determined issue %s needs a comment', self.ISSUE_KEY),
        ]
        self.assertEqual(debug_calls, self._logger.debug.call_args_list)

        # Generates the correct comment using the end time
        comment = self._get_comment_body(end_time)

        # Checks the body of the comment is getting logged as info
        self._logger.info.assert_called_once_with('Added comment to issue %s: %s', self.ISSUE_KEY, comment)

        # Checks the request is called 3 times with correct parameters
        request_calls = [
            jql_search_requst_call,
            self._get_retrieve_comment_request_call(),
            self._generate_request_call(
                method='POST',
                url=self.APP_COMMENT_URL.format(issue=self.ISSUE_KEY),
                json={
                    'body': comment
                },
            ),
        ]
        self.assertEqual(request_calls, self._request.call_args_list)

    def test_handle_event_no_issue(self):
        """
        handle_event() is correctly omitted if there is no JIRA issue for the given instance
        """
        del self._issues[:]

        with patch('aws_analysis_tools.ec2_events.jira_listener.request', self._request):
            self._listener.handle_event(self._instance, self._event)

        jql_search_log_call, jql_search_requst_call = self._get_jql_search()

        debug_calls = [jql_search_log_call]
        self.assertEqual(debug_calls, self._logger.debug.call_args_list)

        request_calls = [jql_search_requst_call]
        self.assertEqual(request_calls, self._request.call_args_list)

    def test_handle_event_issue_with_comments(self):
        """
        Duplicate comment is not posted
        """
        self._comments.append({'body': self.INSTANCE_NAME})

        with patch('aws_analysis_tools.ec2_events.jira_listener.request', self._request):
            self._listener.handle_event(self._instance, self._event)

        jql_search_log_call, jql_search_requst_call = self._get_jql_search()

        debug_calls = [jql_search_log_call]
        self.assertEqual(debug_calls, self._logger.debug.call_args_list)

        request_calls = [
            jql_search_requst_call,
            self._get_retrieve_comment_request_call(),
        ]
        self.assertEqual(request_calls, self._request.call_args_list)

    def test_handle_event_issue_with_other_comments(self):
        """
        handle_event() correctly posts a comment even though there are other comments
        """
        self._comments.append({'body': 'Some random comment'})

        self._verify_full_execution(end_time=DateTime(self._event.not_after))

    def test_handle_event_no_end_time(self):
        """
        handle_event() correctly handles the event with no end time
        """
        self._event.not_after = None

        self._verify_full_execution(end_time=DateTime(9999, 12, 31))

    def test_handle_event_pass(self):
        """
        handle_event() corrects posts a comment if there is no other comment
        """
        self._verify_full_execution(end_time=DateTime(self._event.not_after))

    def test_request_fail(self):
        """
        Exception is thrown with the body of the HTTP response if a request call fails
        """
        self._res.status_code = self.EXCEPTION_STATUS
        self._res.reason = self.EXCEPTION_REASON
        self._res.content = self.EXCEPTION_CONTENT

        with patch('aws_analysis_tools.ec2_events.jira_listener.request', self._request):
            with self.assertRaises(ValueError) as e:
                self._listener.handle_event(self._instance, self._event)

        self.assertEqual(self.EXCEPTION_TEMPLATE.format(status_code=self.EXCEPTION_STATUS, reason=self.EXCEPTION_REASON, body=self.EXCEPTION_CONTENT), e.exception.message)

    def test_handle_complete(self):
        """
        handle_complete() method can be called
        """
        self._listener.handle_complete()