lms.testing.serverrunner

  1import logging
  2import typing
  3
  4import edq.core.errors
  5import edq.net.exchange
  6import edq.net.request
  7import edq.net.settings
  8import edq.testing.serverrunner
  9import edq.util.parse
 10import edq.util.reflection
 11
 12import lms.backend.instance
 13import lms.cli.parser
 14import lms.model.backend
 15import lms.model.constants
 16import lms.util.net
 17
 18BACKEND_REQUEST_CLEANING_FUNCS: typing.Dict[typing.Union[lms.model.constants.BackendType, None], typing.Callable] = {
 19    lms.model.constants.BackendType.BLACKBOARD: lms.util.net.clean_blackboard_response,
 20    lms.model.constants.BackendType.CANVAS: lms.util.net.clean_canvas_response,
 21    lms.model.constants.BackendType.MOODLE: lms.util.net.clean_moodle_response,
 22}
 23
 24BACKEND_EXCHANGE_FINALIZING_FUNCS: typing.Dict[typing.Union[lms.model.constants.BackendType, None], typing.Callable] = {
 25    lms.model.constants.BackendType.CANVAS: lms.util.net.finalize_canvas_exchange,
 26    lms.model.constants.BackendType.MOODLE: lms.util.net.finalize_moodle_exchange,
 27}
 28
 29class LMSServerRunner(edq.testing.serverrunner.ServerRunner):
 30    """ A server runner specifically for LMS servers. """
 31
 32    def __init__(self,
 33            backend_type: typing.Union[lms.model.constants.BackendType, str, None] = None,
 34            **kwargs: typing.Any) -> None:
 35        super().__init__(**kwargs)
 36
 37        if (isinstance(backend_type, str)):
 38            backend_type = lms.model.constants.BackendType(backend_type)
 39
 40        self.backend_type: typing.Union[lms.model.constants.BackendType, None] = backend_type
 41        """
 42        The type of server being run.
 43        This value will be resolved after the server is started
 44        (since part of resolution may involve pinging the server.
 45        """
 46
 47        self.backend: typing.Union[lms.model.backend.APIBackend, None] = None
 48        """
 49        The backend currently being used while this server runner is active.
 50        This may not always be set.
 51        """
 52
 53        self._old_exchanges_clean_response_func: typing.Union[str, None] = None
 54        """
 55        The value of edq.net.settings.get_exchanges_clean_response_func() when start() is called.
 56        The original value may be changed in start(), and will be reset in stop().
 57        """
 58
 59        self._old_exchanges_finalize_func: typing.Union[str, None] = None
 60        """
 61        The value of edq.net.settings.get_exchanges_finalize_func() when start() is called.
 62        The original value may be changed in start(), and will be reset in stop().
 63        """
 64
 65        self._old_set_exchanges_clean_response_func: bool = False
 66        """
 67        The value of lms.cli.parser._set_exchanges_clean_response_func when start() is called.
 68        The original value may be changed in start(), and will be reset in stop().
 69        """
 70
 71        self._old_request_complete_callback: typing.Union[edq.net.exchange.HTTPExchangeComplete, None] = None
 72        """
 73        The value of edq.net.settings.get_request_complete_callback() when start() is called.
 74        The original value may be changed in start(), and will be reset in stop().
 75        """
 76
 77        self._old_serverrunner_logging_level: typing.Union[int, None] = None
 78        """
 79        The logging level for the edq server runner befoe tests are run.
 80        The original value may be changed in start(), and will be reset in stop().
 81        """
 82
 83    def start(self) -> None:
 84        # Set configs.
 85
 86        exchange_clean_func = BACKEND_REQUEST_CLEANING_FUNCS.get(self.backend_type, lms.util.net.clean_lms_response)
 87        exchange_clean_func_name = edq.util.reflection.get_qualified_name(exchange_clean_func)
 88        self._old_exchanges_clean_response_func = edq.net.settings.get_exchanges_clean_response_func()
 89        edq.net.settings.set_exchanges_clean_response_func(exchange_clean_func_name)
 90
 91        self._old_exchanges_finalize_func = edq.net.settings.get_exchanges_finalize_func()
 92        exchange_finalize_func = BACKEND_EXCHANGE_FINALIZING_FUNCS.get(self.backend_type, None)
 93        if (exchange_finalize_func is not None):
 94            exchange_finalize_func_name = edq.util.reflection.get_qualified_name(exchange_finalize_func)
 95            edq.net.settings.set_exchanges_finalize_func(exchange_finalize_func_name)
 96        else:
 97            edq.net.settings.set_exchanges_finalize_func()
 98
 99        self._old_set_exchanges_clean_response_func = lms.cli.parser._set_exchanges_clean_response_func
100        lms.cli.parser._set_exchanges_clean_response_func = False
101
102        def _request_complete_callback(exchange: edq.net.exchange.HTTPExchange) -> None:
103            # Restart if the request is a write.
104            if (edq.util.parse.boolean(exchange.headers.get(lms.model.constants.HEADER_KEY_WRITE, False))):
105                self.restart()
106
107        self._old_request_complete_callback = edq.net.settings.get_request_complete_callback()
108        edq.net.settings.set_request_complete_callback(typing.cast(edq.net.exchange.HTTPExchangeComplete, _request_complete_callback))
109
110        # Disable logging from the runner, since it may disrupt CLI tests.
111        logger = logging.getLogger('edq.testing.serverrunner')
112        self._old_serverrunner_logging_level = logger.level
113        logger.setLevel(logging.WARNING)
114
115        # Start the server.
116        super().start()
117
118    def stop(self) -> bool:
119        if (self._old_serverrunner_logging_level is not None):
120            logger = logging.getLogger('edq.testing.serverrunner')
121            logger.setLevel(self._old_serverrunner_logging_level)
122            self._old_serverrunner_logging_level = None
123
124        if (not super().stop()):
125            return False
126
127        # Restore old configs.
128
129        edq.net.settings.set_exchanges_clean_response_func(self._old_exchanges_clean_response_func)
130        self._old_exchanges_clean_response_func = None
131
132        lms.cli.parser._set_exchanges_clean_response_func = self._old_set_exchanges_clean_response_func
133        self._old_set_exchanges_clean_response_func = False
134
135        edq.net.settings.set_request_complete_callback(self._old_request_complete_callback)
136        self._old_request_complete_callback = None
137
138        edq.net.settings.set_exchanges_finalize_func(self._old_exchanges_finalize_func)
139        self._old_exchanges_finalize_func = None
140
141        return True
142
143    def restart(self) -> None:
144        super().restart()
145
146        # Inform the backend that a restart happened.
147        if (self.backend is not None):
148            self.backend.reset_connection()
149
150    def identify_server(self) ->  bool:
151        try:
152            backend_type = lms.backend.instance.guess_backend_type_from_request(self.server, timeout_secs = self.identify_wait_secs)
153        except edq.core.errors.RetryError:
154            return False
155
156        return (backend_type is not None)
BACKEND_REQUEST_CLEANING_FUNCS: Dict[Optional[lms.model.constants.BackendType], Callable] = {<BackendType.BLACKBOARD: 'blackboard'>: <function clean_blackboard_response>, <BackendType.CANVAS: 'canvas'>: <function clean_canvas_response>, <BackendType.MOODLE: 'moodle'>: <function clean_moodle_response>}
BACKEND_EXCHANGE_FINALIZING_FUNCS: Dict[Optional[lms.model.constants.BackendType], Callable] = {<BackendType.CANVAS: 'canvas'>: <function finalize_canvas_exchange>, <BackendType.MOODLE: 'moodle'>: <function finalize_moodle_exchange>}
class LMSServerRunner(edq.testing.serverrunner.ServerRunner):
 30class LMSServerRunner(edq.testing.serverrunner.ServerRunner):
 31    """ A server runner specifically for LMS servers. """
 32
 33    def __init__(self,
 34            backend_type: typing.Union[lms.model.constants.BackendType, str, None] = None,
 35            **kwargs: typing.Any) -> None:
 36        super().__init__(**kwargs)
 37
 38        if (isinstance(backend_type, str)):
 39            backend_type = lms.model.constants.BackendType(backend_type)
 40
 41        self.backend_type: typing.Union[lms.model.constants.BackendType, None] = backend_type
 42        """
 43        The type of server being run.
 44        This value will be resolved after the server is started
 45        (since part of resolution may involve pinging the server.
 46        """
 47
 48        self.backend: typing.Union[lms.model.backend.APIBackend, None] = None
 49        """
 50        The backend currently being used while this server runner is active.
 51        This may not always be set.
 52        """
 53
 54        self._old_exchanges_clean_response_func: typing.Union[str, None] = None
 55        """
 56        The value of edq.net.settings.get_exchanges_clean_response_func() when start() is called.
 57        The original value may be changed in start(), and will be reset in stop().
 58        """
 59
 60        self._old_exchanges_finalize_func: typing.Union[str, None] = None
 61        """
 62        The value of edq.net.settings.get_exchanges_finalize_func() when start() is called.
 63        The original value may be changed in start(), and will be reset in stop().
 64        """
 65
 66        self._old_set_exchanges_clean_response_func: bool = False
 67        """
 68        The value of lms.cli.parser._set_exchanges_clean_response_func when start() is called.
 69        The original value may be changed in start(), and will be reset in stop().
 70        """
 71
 72        self._old_request_complete_callback: typing.Union[edq.net.exchange.HTTPExchangeComplete, None] = None
 73        """
 74        The value of edq.net.settings.get_request_complete_callback() when start() is called.
 75        The original value may be changed in start(), and will be reset in stop().
 76        """
 77
 78        self._old_serverrunner_logging_level: typing.Union[int, None] = None
 79        """
 80        The logging level for the edq server runner befoe tests are run.
 81        The original value may be changed in start(), and will be reset in stop().
 82        """
 83
 84    def start(self) -> None:
 85        # Set configs.
 86
 87        exchange_clean_func = BACKEND_REQUEST_CLEANING_FUNCS.get(self.backend_type, lms.util.net.clean_lms_response)
 88        exchange_clean_func_name = edq.util.reflection.get_qualified_name(exchange_clean_func)
 89        self._old_exchanges_clean_response_func = edq.net.settings.get_exchanges_clean_response_func()
 90        edq.net.settings.set_exchanges_clean_response_func(exchange_clean_func_name)
 91
 92        self._old_exchanges_finalize_func = edq.net.settings.get_exchanges_finalize_func()
 93        exchange_finalize_func = BACKEND_EXCHANGE_FINALIZING_FUNCS.get(self.backend_type, None)
 94        if (exchange_finalize_func is not None):
 95            exchange_finalize_func_name = edq.util.reflection.get_qualified_name(exchange_finalize_func)
 96            edq.net.settings.set_exchanges_finalize_func(exchange_finalize_func_name)
 97        else:
 98            edq.net.settings.set_exchanges_finalize_func()
 99
100        self._old_set_exchanges_clean_response_func = lms.cli.parser._set_exchanges_clean_response_func
101        lms.cli.parser._set_exchanges_clean_response_func = False
102
103        def _request_complete_callback(exchange: edq.net.exchange.HTTPExchange) -> None:
104            # Restart if the request is a write.
105            if (edq.util.parse.boolean(exchange.headers.get(lms.model.constants.HEADER_KEY_WRITE, False))):
106                self.restart()
107
108        self._old_request_complete_callback = edq.net.settings.get_request_complete_callback()
109        edq.net.settings.set_request_complete_callback(typing.cast(edq.net.exchange.HTTPExchangeComplete, _request_complete_callback))
110
111        # Disable logging from the runner, since it may disrupt CLI tests.
112        logger = logging.getLogger('edq.testing.serverrunner')
113        self._old_serverrunner_logging_level = logger.level
114        logger.setLevel(logging.WARNING)
115
116        # Start the server.
117        super().start()
118
119    def stop(self) -> bool:
120        if (self._old_serverrunner_logging_level is not None):
121            logger = logging.getLogger('edq.testing.serverrunner')
122            logger.setLevel(self._old_serverrunner_logging_level)
123            self._old_serverrunner_logging_level = None
124
125        if (not super().stop()):
126            return False
127
128        # Restore old configs.
129
130        edq.net.settings.set_exchanges_clean_response_func(self._old_exchanges_clean_response_func)
131        self._old_exchanges_clean_response_func = None
132
133        lms.cli.parser._set_exchanges_clean_response_func = self._old_set_exchanges_clean_response_func
134        self._old_set_exchanges_clean_response_func = False
135
136        edq.net.settings.set_request_complete_callback(self._old_request_complete_callback)
137        self._old_request_complete_callback = None
138
139        edq.net.settings.set_exchanges_finalize_func(self._old_exchanges_finalize_func)
140        self._old_exchanges_finalize_func = None
141
142        return True
143
144    def restart(self) -> None:
145        super().restart()
146
147        # Inform the backend that a restart happened.
148        if (self.backend is not None):
149            self.backend.reset_connection()
150
151    def identify_server(self) ->  bool:
152        try:
153            backend_type = lms.backend.instance.guess_backend_type_from_request(self.server, timeout_secs = self.identify_wait_secs)
154        except edq.core.errors.RetryError:
155            return False
156
157        return (backend_type is not None)

A server runner specifically for LMS servers.

LMSServerRunner( backend_type: Union[lms.model.constants.BackendType, str, NoneType] = None, **kwargs: Any)
33    def __init__(self,
34            backend_type: typing.Union[lms.model.constants.BackendType, str, None] = None,
35            **kwargs: typing.Any) -> None:
36        super().__init__(**kwargs)
37
38        if (isinstance(backend_type, str)):
39            backend_type = lms.model.constants.BackendType(backend_type)
40
41        self.backend_type: typing.Union[lms.model.constants.BackendType, None] = backend_type
42        """
43        The type of server being run.
44        This value will be resolved after the server is started
45        (since part of resolution may involve pinging the server.
46        """
47
48        self.backend: typing.Union[lms.model.backend.APIBackend, None] = None
49        """
50        The backend currently being used while this server runner is active.
51        This may not always be set.
52        """
53
54        self._old_exchanges_clean_response_func: typing.Union[str, None] = None
55        """
56        The value of edq.net.settings.get_exchanges_clean_response_func() when start() is called.
57        The original value may be changed in start(), and will be reset in stop().
58        """
59
60        self._old_exchanges_finalize_func: typing.Union[str, None] = None
61        """
62        The value of edq.net.settings.get_exchanges_finalize_func() when start() is called.
63        The original value may be changed in start(), and will be reset in stop().
64        """
65
66        self._old_set_exchanges_clean_response_func: bool = False
67        """
68        The value of lms.cli.parser._set_exchanges_clean_response_func when start() is called.
69        The original value may be changed in start(), and will be reset in stop().
70        """
71
72        self._old_request_complete_callback: typing.Union[edq.net.exchange.HTTPExchangeComplete, None] = None
73        """
74        The value of edq.net.settings.get_request_complete_callback() when start() is called.
75        The original value may be changed in start(), and will be reset in stop().
76        """
77
78        self._old_serverrunner_logging_level: typing.Union[int, None] = None
79        """
80        The logging level for the edq server runner befoe tests are run.
81        The original value may be changed in start(), and will be reset in stop().
82        """
backend_type: Optional[lms.model.constants.BackendType]

The type of server being run. This value will be resolved after the server is started (since part of resolution may involve pinging the server.

backend: Optional[lms.model.backend.APIBackend]

The backend currently being used while this server runner is active. This may not always be set.

def start(self) -> None:
 84    def start(self) -> None:
 85        # Set configs.
 86
 87        exchange_clean_func = BACKEND_REQUEST_CLEANING_FUNCS.get(self.backend_type, lms.util.net.clean_lms_response)
 88        exchange_clean_func_name = edq.util.reflection.get_qualified_name(exchange_clean_func)
 89        self._old_exchanges_clean_response_func = edq.net.settings.get_exchanges_clean_response_func()
 90        edq.net.settings.set_exchanges_clean_response_func(exchange_clean_func_name)
 91
 92        self._old_exchanges_finalize_func = edq.net.settings.get_exchanges_finalize_func()
 93        exchange_finalize_func = BACKEND_EXCHANGE_FINALIZING_FUNCS.get(self.backend_type, None)
 94        if (exchange_finalize_func is not None):
 95            exchange_finalize_func_name = edq.util.reflection.get_qualified_name(exchange_finalize_func)
 96            edq.net.settings.set_exchanges_finalize_func(exchange_finalize_func_name)
 97        else:
 98            edq.net.settings.set_exchanges_finalize_func()
 99
100        self._old_set_exchanges_clean_response_func = lms.cli.parser._set_exchanges_clean_response_func
101        lms.cli.parser._set_exchanges_clean_response_func = False
102
103        def _request_complete_callback(exchange: edq.net.exchange.HTTPExchange) -> None:
104            # Restart if the request is a write.
105            if (edq.util.parse.boolean(exchange.headers.get(lms.model.constants.HEADER_KEY_WRITE, False))):
106                self.restart()
107
108        self._old_request_complete_callback = edq.net.settings.get_request_complete_callback()
109        edq.net.settings.set_request_complete_callback(typing.cast(edq.net.exchange.HTTPExchangeComplete, _request_complete_callback))
110
111        # Disable logging from the runner, since it may disrupt CLI tests.
112        logger = logging.getLogger('edq.testing.serverrunner')
113        self._old_serverrunner_logging_level = logger.level
114        logger.setLevel(logging.WARNING)
115
116        # Start the server.
117        super().start()

Start the server.

def stop(self) -> bool:
119    def stop(self) -> bool:
120        if (self._old_serverrunner_logging_level is not None):
121            logger = logging.getLogger('edq.testing.serverrunner')
122            logger.setLevel(self._old_serverrunner_logging_level)
123            self._old_serverrunner_logging_level = None
124
125        if (not super().stop()):
126            return False
127
128        # Restore old configs.
129
130        edq.net.settings.set_exchanges_clean_response_func(self._old_exchanges_clean_response_func)
131        self._old_exchanges_clean_response_func = None
132
133        lms.cli.parser._set_exchanges_clean_response_func = self._old_set_exchanges_clean_response_func
134        self._old_set_exchanges_clean_response_func = False
135
136        edq.net.settings.set_request_complete_callback(self._old_request_complete_callback)
137        self._old_request_complete_callback = None
138
139        edq.net.settings.set_exchanges_finalize_func(self._old_exchanges_finalize_func)
140        self._old_exchanges_finalize_func = None
141
142        return True

Stop the server. Return true if child classes should perform shutdown behavior.

def restart(self) -> None:
144    def restart(self) -> None:
145        super().restart()
146
147        # Inform the backend that a restart happened.
148        if (self.backend is not None):
149            self.backend.reset_connection()

Restart the server.

def identify_server(self) -> bool:
151    def identify_server(self) ->  bool:
152        try:
153            backend_type = lms.backend.instance.guess_backend_type_from_request(self.server, timeout_secs = self.identify_wait_secs)
154        except edq.core.errors.RetryError:
155            return False
156
157        return (backend_type is not None)

Attempt to identify the target server and return true on a successful attempt. This is used on startup to wait for the server to complete startup.

Child classes must implement this or set self.startup_skip_identify to true.