Passed
Pull Request — master (#452)
by Valessio Soares de
02:27
created

APIServer.register_rest_endpoint()   B

Complexity

Conditions 5

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 13.2951

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
dl 0
loc 27
ccs 4
cts 13
cp 0.3076
crap 13.2951
rs 8.0894
c 1
b 0
f 0
1
"""Module used to handle a API Server."""
2 1
import logging
3
import os
4 1
import sys
5 1
from urllib.error import URLError
6
from urllib.request import urlopen
7 1
8 1
from flask import Flask, request, send_from_directory
9
from flask_socketio import SocketIO, join_room, leave_room
10
11 1
12
class APIServer:
13
    """Api server used to provide Kytos Controller routes."""
14 1
15
    def __init__(self, app_name, listen='0.0.0.0', port=8181):
16
        """Constructor of APIServer.
17
18
        This method will instantiate a server with SocketIO+Flask.
19
20
        Parameters:
21
            app_name(string): String representing a App Name
22
            listen (string): host name used by api server instance
23
            port (int): Port number used by api server instance
24
        """
25
        dirname = os.path.dirname(os.path.abspath(__file__))
26
        self.flask_dir = os.path.join(dirname, '../web-ui')
27
        self.log = logging.getLogger('werkzeug')
28 1
29
        self.listen = listen
30
        self.port = port
31
32 1
        self.app = Flask(app_name, root_path=self.flask_dir)
33
        self.server = SocketIO(self.app, async_mode='threading')
34
        self._enable_websocket_rooms()
35
36
    def _enable_websocket_rooms(self):
37
        socket = self.server
38
        socket.on_event('join', join_room)
39
        socket.on_event('leave', leave_room)
40
41
    def run(self):
42
        """Run the Flask API Server."""
43
        try:
44
            self.server.run(self.app, self.listen, self.port)
45
        except OSError as e:
46
            msg = "Couldn't start API Server: {}".format(e)
47
            self.log.critical(msg)
48
            sys.exit(msg)
49 1
50
    def register_rest_endpoint(self, url, function, methods):
51
        r"""Register a new rest endpoint in Api Server.
52
53
        To register new endpoints is needed to have a url, function to handle
54
        the requests and type of method allowed.
55 1
56
        Parameters:
57
            url (string):        String with partner of route. e.g.: '/status'
58
            function (function): Function pointer used to handle the requests.
59 1
            methods (list):      List of request methods allowed.
60
                                 e.g: ['GET', 'PUT', 'POST', 'DELETE', 'PATCH']
61
        """
62
        new_endpoint_url = "/kytos{}".format(url)
63
64 1
        for endpoint in self.app.url_map.iter_rules():
65
            if endpoint.rule == new_endpoint_url:
66
                for method in methods:
67
                    if method in endpoint.methods:
68
                        message = ("Method '{}' already registered for " +
69 1
                                   "URL '{}'").format(method, new_endpoint_url)
70
                        self.log.warning(message)
71
                        self.log.warning("WARNING: Overlapping endpoint was " +
72
                                         "NOT registered.")
73
                        return
74
75
        self.app.add_url_rule(new_endpoint_url, function.__name__, function,
76
                              methods=methods)
77
78
    def register_web_ui(self):
79
        """Method used to register routes to the admin-ui homepage."""
80
        self.app.add_url_rule('/', self.web_ui.__name__, self.web_ui)
81
        self.app.add_url_rule('/index.html', self.web_ui.__name__, self.web_ui)
82
83 1
    @property
84
    def rest_endpoints(self):
85
        """Return string with routes registered by Api Server."""
86
        return [x.rule for x in self.app.url_map.iter_rules()]
87 1
88
    def register_api_server_routes(self):
89
        """Register initial routes from kytos using ApiServer.
90
91
        Initial routes are: ['/kytos/status/', '/kytos/shutdown/']
92
        """
93
        if '/kytos/status/' not in self.rest_endpoints:
94 1
            self.register_rest_endpoint('/status/',
95
                                        self.status_api, methods=['GET'])
96
97
        if '/kytos/shutdown/' not in self.rest_endpoints:
98
            self.register_rest_endpoint('/shutdown/',
99
                                        self.shutdown_api, methods=['GET'])
100
101
    @staticmethod
102
    def status_api():
103
        """Display json with kytos status using the route '/kytos/status/'."""
104
        return '{"response": "running"}', 201
105
106
    def stop_api_server(self):
107
        """Method used to send a shutdown request to stop Api Server."""
108 1
        try:
109
            url = 'http://{}:{}/kytos/shutdown'.format('127.0.0.1', self.port)
110
            urlopen(url)
111
        except URLError:
112
            pass
113
114
    def shutdown_api(self):
115
        """Handle shutdown requests received by Api Server.
116
117
        This method must be called by kytos using the method
118
        stop_api_server, otherwise this request will be ignored.
119
        """
120
        allowed_host = ['127.0.0.1:'+str(self.port),
121
                        'localhost:'+str(self.port)]
122
        if request.host not in allowed_host:
123
            return "", 403
124
125
        self.server.stop()
126
127
        return 'Server shutting down...', 200
128
129
    def web_ui(self):
130
        """Method used to serve the index.html page for the admin-ui."""
131
        return send_from_directory(self.flask_dir, 'index.html')
132