Init commit for standalone enodebd
Change-Id: I88eeef5135dd7ba8551ddd9fb6a0695f5325337b
diff --git a/tr069/server.py b/tr069/server.py
new file mode 100644
index 0000000..ecda15b
--- /dev/null
+++ b/tr069/server.py
@@ -0,0 +1,155 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import _thread
+import socket
+from wsgiref.simple_server import (
+ ServerHandler,
+ WSGIRequestHandler,
+ WSGIServer,
+ make_server,
+)
+
+from common.misc_utils import get_ip_from_if
+from configuration.service_configs import load_service_config
+from logger import EnodebdLogger as logger
+from state_machines.enb_acs_manager import StateMachineManager
+from spyne.server.wsgi import WsgiApplication
+
+from .models import CWMP_NS
+from .rpc_methods import AutoConfigServer
+from .spyne_mods import Tr069Application, Tr069Soap11
+
+# Socket timeout in seconds. Should be set larger than the longest TR-069
+# response time (typically for a GetParameterValues of the entire data model),
+# measured at 168secs. Should also be set smaller than ENB_CONNECTION_TIMEOUT,
+# to avoid incorrectly detecting eNodeB timeout.
+SOCKET_TIMEOUT = 240
+
+
+class tr069_WSGIRequestHandler(WSGIRequestHandler):
+ timeout = 10
+ # pylint: disable=attribute-defined-outside-init
+
+ def handle_single(self):
+ """Handle a single HTTP request"""
+ self.raw_requestline = self.rfile.readline(65537)
+ if len(self.raw_requestline) > 65536:
+ self.requestline = ''
+ self.request_version = ''
+ self.command = ''
+ self.close_connection = 1
+ self.send_error(414)
+ return
+
+ if not self.parse_request(): # An error code has been sent, just exit
+ return
+
+ handler = ServerHandler(
+ self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
+ )
+ handler.http_version = "1.1"
+ handler.request_handler = self # backpointer for logging
+
+ # eNodeB will sometimes close connection to enodebd.
+ # The cause of this is unknown, but we can safely ignore the
+ # closed connection, and continue as normal otherwise.
+ #
+ # While this throws a BrokenPipe exception in wsgi server,
+ # it also causes an AttributeError to be raised because of a
+ # bug in the wsgi server.
+ # https://bugs.python.org/issue27682
+ try:
+ handler.run(self.server.get_app())
+ except BrokenPipeError:
+ self.log_error("eNodeB has unexpectedly closed the TCP connection.")
+
+ def handle(self):
+ self.protocol_version = "HTTP/1.1"
+ self.close_connection = 0
+
+ try:
+ while not self.close_connection:
+ self.handle_single()
+
+ except socket.timeout as e:
+ self.log_error("tr069 WSGI Server Socket Timeout: %r", e)
+ self.close_connection = 1
+ return
+
+ except socket.error as e:
+ self.log_error("tr069 WSGI Server Socket Error: %r", e)
+ self.close_connection = 1
+ return
+
+ # Disable pylint warning because we are using same parameter name as built-in
+ # pylint: disable=redefined-builtin
+ def log_message(self, format, *args):
+ """ Overwrite message logging to use python logging framework rather
+ than stderr """
+ logger.debug("%s - %s", self.client_address[0], format % args)
+
+ # Disable pylint warning because we are using same parameter name as built-in
+ # pylint: disable=redefined-builtin
+ def log_error(self, format, *args):
+ """ Overwrite message logging to use python logging framework rather
+ than stderr """
+ logger.warning("%s - %s", self.client_address[0], format % args)
+
+
+def tr069_server(state_machine_manager: StateMachineManager) -> None:
+ """
+ TR-069 server
+ Inputs:
+ - acs_to_cpe_queue = instance of Queue
+ containing messages from parent process/thread to be sent to CPE
+ - cpe_to_acs_queue = instance of Queue
+ containing messages from CPE to be sent to parent process/thread
+ """
+ config = load_service_config("enodebd")
+
+ AutoConfigServer.set_state_machine_manager(state_machine_manager)
+
+ app = Tr069Application(
+ [AutoConfigServer], CWMP_NS,
+ in_protocol=Tr069Soap11(validator='soft'),
+ out_protocol=Tr069Soap11(),
+ )
+ wsgi_app = WsgiApplication(app)
+
+ try:
+ ip_address = get_ip_from_if(config['tr069']['interface'])
+ except (ValueError, KeyError) as e:
+ # Interrupt main thread since process should not continue without TR-069
+ _thread.interrupt_main()
+ raise e
+
+ socket.setdefaulttimeout(SOCKET_TIMEOUT)
+ logger.info(
+ 'Starting TR-069 server on %s:%s',
+ ip_address, config['tr069']['port'],
+ )
+ server = make_server(
+ ip_address,
+ config['tr069']['port'], wsgi_app,
+ WSGIServer, tr069_WSGIRequestHandler,
+ )
+
+ # Note: use single-thread server, to avoid state contention
+ try:
+ server.serve_forever()
+ finally:
+ # Log error and interrupt main thread, to ensure that entire process
+ # is restarted if this thread exits
+ logger.error('Hit error in TR-069 thread. Interrupting main thread.')
+ _thread.interrupt_main()