Exemplo n.º 1
0
        def fn(*_, **__):
            thread_data['first_get'] = PySOALogContextFilter.get_logging_request_context()

            PySOALogContextFilter.set_logging_request_context(foo='bar', **{'baz': 'qux'})

            thread_data['second_get'] = PySOALogContextFilter.get_logging_request_context()

            if thread_data.get('do_clear'):
                PySOALogContextFilter.clear_logging_request_context()

            thread_data['third_get'] = PySOALogContextFilter.get_logging_request_context()
Exemplo n.º 2
0
    def test_threading(self):
        thread_data = {}

        def fn(*_, **__):
            thread_data['first_get'] = PySOALogContextFilter.get_logging_request_context()

            PySOALogContextFilter.set_logging_request_context(foo='bar', **{'baz': 'qux'})

            thread_data['second_get'] = PySOALogContextFilter.get_logging_request_context()

            if thread_data.get('do_clear'):
                PySOALogContextFilter.clear_logging_request_context()

            thread_data['third_get'] = PySOALogContextFilter.get_logging_request_context()

        self.assertIsNone(PySOALogContextFilter.get_logging_request_context())

        PySOALogContextFilter.set_logging_request_context(request_id=1234, **{'correlation_id': 'abc'})

        self.assertEqual(
            {'request_id': 1234, 'correlation_id': 'abc'},
            PySOALogContextFilter.get_logging_request_context()
        )

        thread = threading.Thread(target=fn)
        thread.start()
        thread.join()

        self.assertEqual(
            {'request_id': 1234, 'correlation_id': 'abc'},
            PySOALogContextFilter.get_logging_request_context()
        )

        self.assertIsNone(thread_data['first_get'])
        self.assertEqual({'foo': 'bar', 'baz': 'qux'}, thread_data['second_get'])
        self.assertEqual({'foo': 'bar', 'baz': 'qux'}, thread_data['third_get'])

        thread_data['do_clear'] = True

        thread = threading.Thread(target=fn)
        thread.start()
        thread.join()

        self.assertEqual(
            {'request_id': 1234, 'correlation_id': 'abc'},
            PySOALogContextFilter.get_logging_request_context()
        )

        self.assertIsNone(thread_data['first_get'])
        self.assertEqual({'foo': 'bar', 'baz': 'qux'}, thread_data['second_get'])
        self.assertIsNone(thread_data['third_get'])
Exemplo n.º 3
0
    def main(cls):
        """
        Command-line entry point for running a PySOA server. The chain of method calls is as follows::

            cls.main
              |
              -> cls.initialize => new_cls
              -> new_cls.__init__ => self
              -> self.run
                  |
                  -> self.setup
                  -> loop: self.handle_next_request while not self.shutting_down
                            |
                            -> transport.receive_request_message
                            -> self.perform_idle_actions (if no request)
                            -> self.perform_pre_request_actions
                            -> self.process_job
                                |
                                -> middleware(self.execute_job)
                            -> transport.send_response_message
                            -> self.perform_post_request_actions
        """
        parser = argparse.ArgumentParser(
            description='Server for the {} SOA service'.format(cls.service_name),
        )
        parser.add_argument(
            '-d', '--daemon',
            action='store_true',
            help='run the server process as a daemon',
        )
        if not cls.use_django:
            # If Django mode is turned on, we use the Django settings framework to get our settings, so the caller
            # needs to set DJANGO_SETTINGS_MODULE. Otherwise, the caller must pass in the -s/--settings argument.
            parser.add_argument(
                '-s', '--settings',
                help='The settings module to use',
                required=True,
            )
        cmd_options, _ = parser.parse_known_args(sys.argv[1:])

        # Load settings from the given file (or use Django and grab from its settings)
        if cls.use_django:
            # noinspection PyUnresolvedReferences
            if not django_settings:
                raise ImportError(
                    'Could not import Django. You must install Django if you enable Django support in your service.'
                )
            try:
                settings = cls.settings_class(django_settings.SOA_SERVER_SETTINGS)
            except AttributeError:
                raise ValueError('Cannot find `SOA_SERVER_SETTINGS` in the Django settings.')
        else:
            try:
                settings_module = importlib.import_module(cmd_options.settings)
            except ImportError as e:
                raise ValueError('Cannot import settings module `%s`: %s' % (cmd_options.settings, e))
            try:
                settings_dict = getattr(settings_module, 'SOA_SERVER_SETTINGS')
            except AttributeError:
                try:
                    settings_dict = getattr(settings_module, 'settings')
                except AttributeError:
                    raise ValueError(
                        "Cannot find `SOA_SERVER_SETTINGS` or `settings` variable in settings module `{}`.".format(
                            cmd_options.settings,
                        )
                    )
            settings = cls.settings_class(settings_dict)

        PySOALogContextFilter.set_service_name(cls.service_name)

        # Set up logging
        logging.config.dictConfig(settings['logging'])

        # Optionally daemonize
        if cmd_options.daemon:
            pid = os.fork()
            if pid > 0:
                print('PID={}'.format(pid))
                sys.exit()

        # Set up server and signal handling
        server = cls.initialize(settings)(settings)

        # Start server event loop
        server.run()
Exemplo n.º 4
0
    def handle_next_request(self):
        """
        Retrieves the next request from the transport, or returns if it times out (no request has been made), and then
        processes that request, sends its response, and returns when done.
        """
        if not self._idle_timer:
            # This method may be called multiple times before receiving a request, so we only create and start a timer
            # if it's the first call or if the idle timer was stopped on the last call.
            self._idle_timer = self.metrics.timer('server.idle_time', resolution=TimerResolution.MICROSECONDS)
            self._idle_timer.start()

        # Get the next JobRequest
        try:
            request_id, meta, job_request = self.transport.receive_request_message()
        except MessageReceiveTimeout:
            # no new message, nothing to do
            self.perform_idle_actions()
            return

        # We are no longer idle, so stop the timer and reset for the next idle period
        self._idle_timer.stop()
        self._idle_timer = None

        PySOALogContextFilter.set_logging_request_context(request_id=request_id, **job_request['context'])

        request_for_logging = self.logging_dict_wrapper_class(job_request)
        self.job_logger.log(self.request_log_success_level, 'Job request: %s', request_for_logging)

        try:
            self.perform_pre_request_actions()

            # Process and run the Job
            job_response = self.process_job(job_request)

            # Prepare the JobResponse for sending by converting it to a message dict
            try:
                response_message = attr.asdict(job_response, dict_factory=UnicodeKeysDict)
            except Exception as e:
                self.metrics.counter('server.error.response_conversion_failure').increment()
                job_response = self.handle_job_exception(e, variables={'job_response': job_response})
                response_message = attr.asdict(job_response, dict_factory=UnicodeKeysDict)

            response_for_logging = self.logging_dict_wrapper_class(response_message)

            # Send the response message
            try:
                if not job_request['control'].get('suppress_response', False):
                    self.transport.send_response_message(request_id, meta, response_message)
            except MessageTooLarge as e:
                self.metrics.counter('server.error.response_too_large').increment()
                job_response = self.handle_job_error_code(
                    ERROR_CODE_RESPONSE_TOO_LARGE,
                    'Could not send the response because it was too large',
                    request_for_logging,
                    response_for_logging,
                    extra={'serialized_length_in_bytes': e.message_size_in_bytes},
                )
                self.transport.send_response_message(
                    request_id,
                    meta,
                    attr.asdict(job_response, dict_factory=UnicodeKeysDict),
                )
            except InvalidField:
                self.metrics.counter('server.error.response_not_serializable').increment()
                job_response = self.handle_job_error_code(
                    ERROR_CODE_RESPONSE_NOT_SERIALIZABLE,
                    'Could not send the response because it failed to serialize',
                    request_for_logging,
                    response_for_logging,
                )
                self.transport.send_response_message(
                    request_id,
                    meta,
                    attr.asdict(job_response, dict_factory=UnicodeKeysDict),
                )
            finally:
                if job_response.errors or any(a.errors for a in job_response.actions):
                    if (
                        self.request_log_error_level > self.request_log_success_level and
                        self.job_logger.getEffectiveLevel() > self.request_log_success_level
                    ):
                        # When we originally logged the request, it may have been hidden because the effective logging
                        # level threshold was greater than the level at which we logged the request. So re-log the
                        # request at the error level, if set higher.
                        self.job_logger.log(self.request_log_error_level, 'Job request: %s', request_for_logging)
                    self.job_logger.log(self.request_log_error_level, 'Job response: %s', response_for_logging)
                else:
                    self.job_logger.log(self.request_log_success_level, 'Job response: %s', response_for_logging)
        finally:
            PySOALogContextFilter.clear_logging_request_context()
            self.perform_post_request_actions()
Exemplo n.º 5
0
 def tearDown(self):
     # Make sure that if anything goes wrong with these tests, that it doesn't affect any other tests
     PySOALogContextFilter.clear_logging_request_context()
     PySOALogContextFilter.clear_logging_request_context()
     PySOALogContextFilter.clear_logging_request_context()
     PySOALogContextFilter.clear_logging_request_context()
     PySOALogContextFilter.clear_logging_request_context()
     PySOALogContextFilter.clear_logging_request_context()
     PySOALogContextFilter.clear_logging_request_context()
Exemplo n.º 6
0
    def test_filter(self):
        record = mock.MagicMock()

        log_filter = PySOALogContextFilter()

        self.assertTrue(log_filter.filter(record))
        self.assertEqual('--', record.correlation_id)
        self.assertEqual('--', record.request_id)
        self.assertEqual('unknown', record.service_name)

        PySOALogContextFilter.set_service_name('foo_qux')
        PySOALogContextFilter.set_logging_request_context(
            filter='mine', **{'logger': 'yours'})
        self.assertEqual({
            'filter': 'mine',
            'logger': 'yours'
        }, PySOALogContextFilter.get_logging_request_context())

        record.reset_mock()

        self.assertTrue(log_filter.filter(record))
        self.assertEqual('--', record.correlation_id)
        self.assertEqual('--', record.request_id)
        self.assertEqual('foo_qux', record.service_name)

        PySOALogContextFilter.set_logging_request_context(
            request_id=4321, **{'correlation_id': 'abc1234'})
        self.assertEqual({
            'request_id': 4321,
            'correlation_id': 'abc1234'
        }, PySOALogContextFilter.get_logging_request_context())

        record.reset_mock()

        self.assertTrue(log_filter.filter(record))
        self.assertEqual('abc1234', record.correlation_id)
        self.assertEqual(4321, record.request_id)
        self.assertEqual('foo_qux', record.service_name)

        PySOALogContextFilter.clear_logging_request_context()
        self.assertEqual({
            'filter': 'mine',
            'logger': 'yours'
        }, PySOALogContextFilter.get_logging_request_context())

        record.reset_mock()

        self.assertTrue(log_filter.filter(record))
        self.assertEqual('--', record.correlation_id)
        self.assertEqual('--', record.request_id)
        self.assertEqual('foo_qux', record.service_name)

        PySOALogContextFilter.clear_logging_request_context()
        self.assertIsNone(PySOALogContextFilter.get_logging_request_context())

        record.reset_mock()

        self.assertTrue(log_filter.filter(record))
        self.assertEqual('--', record.correlation_id)
        self.assertEqual('--', record.request_id)
        self.assertEqual('foo_qux', record.service_name)
Exemplo n.º 7
0
    def main(cls, forked_process_id=None):
        """
        Command-line entry point for running a PySOA server. The chain of method calls is as follows::

            cls.main
              |
              -> cls.initialize => new_cls
              -> new_cls.__init__ => self
              -> self.run
                  |
                  -> self.setup
                  -> [async event loop started if Python 3.5+]
                  -> [heartbeat file created if configured]
                  -> loop: self.handle_next_request while not self.shutting_down
                            |
                            -> transport.receive_request_message
                            -> self.perform_idle_actions (if no request)
                            -> self.perform_pre_request_actions
                            -> self.process_job
                                |
                                -> middleware(self.execute_job)
                            -> transport.send_response_message
                            -> self.perform_post_request_actions
                  -> self.teardown
                  -> [async event loop joined in Python 3.5+; this make take a few seconds to finish running tasks]
                  -> [Django resources cleaned up]
                  -> [heartbeat file deleted if configured]

        :param forked_process_id: If multiple processes are forked by the same parent process, this will be set to a
                                  unique, deterministic (incremental) ID which can be used in logging, the heartbeat
                                  file, etc. For example, if the `--fork` argument is used with the value 5 (creating
                                  five child processes), this argument will have the values 1, 2, 3, 4, and 5 across
                                  the five respective child processes.
        :type forked_process_id: int
        """
        parser = argparse.ArgumentParser(
            description='Server for the {} SOA service'.format(cls.service_name),
        )
        parser.add_argument(
            '-d', '--daemon',
            action='store_true',
            help='run the server process as a daemon',
        )
        if not cls.use_django:
            # If Django mode is turned on, we use the Django settings framework to get our settings, so the caller
            # needs to set DJANGO_SETTINGS_MODULE. Otherwise, the caller must pass in the -s/--settings argument.
            parser.add_argument(
                '-s', '--settings',
                help='The settings module to use',
                required=True,
            )
        cmd_options, _ = parser.parse_known_args(sys.argv[1:])

        # Load settings from the given file (or use Django and grab from its settings)
        if cls.use_django:
            # noinspection PyUnresolvedReferences
            if not django_settings:
                raise ImportError(
                    'Could not import Django. You must install Django if you enable Django support in your service.'
                )
            try:
                settings = cls.settings_class(django_settings.SOA_SERVER_SETTINGS)
            except AttributeError:
                raise ValueError('Cannot find `SOA_SERVER_SETTINGS` in the Django settings.')
        else:
            try:
                settings_module = importlib.import_module(cmd_options.settings)
            except ImportError as e:
                raise ValueError('Cannot import settings module `%s`: %s' % (cmd_options.settings, e))
            try:
                settings_dict = getattr(settings_module, 'SOA_SERVER_SETTINGS')
            except AttributeError:
                try:
                    settings_dict = getattr(settings_module, 'settings')
                except AttributeError:
                    raise ValueError(
                        "Cannot find `SOA_SERVER_SETTINGS` or `settings` variable in settings module `{}`.".format(
                            cmd_options.settings,
                        )
                    )
            settings = cls.settings_class(settings_dict)

        PySOALogContextFilter.set_service_name(cls.service_name)

        # Set up logging
        logging.config.dictConfig(settings['logging'])

        # Optionally daemonize
        if cmd_options.daemon:
            pid = os.fork()
            if pid > 0:
                print('PID={}'.format(pid))
                sys.exit()

        # Set up server and signal handling
        server = cls.initialize(settings)(settings, forked_process_id)

        # Start server event loop
        server.run()