Repository: kytos/kytos

Issue:
Kytos running in background only stop after two pkill
Hi, even though this seems to be similar to #1236 and #1235, I've decided to create this new issue because it just started happening after PR #1231. The problem is: when you start kytos in the background, you have to issue pkill kytos twice before be able to stop the process. 

This only happens to me if I install kytos from master branch. The pre-release 2021.1rc1 don't includes this bug.

Steps to reproduce:
1. Run kytos base docker image: `docker run -d --name k3 kytos/base:latest tail -f /dev/null`
2. Install the basic kytos packages:
```
python3.6 -m pip install --upgrade pip setuptools wheel
python3.6 -m pip install https://github.com/kytos/python-openflow/archive/master.zip
python3.6 -m pip install https://github.com/kytos/kytos-utils/archive/master.zip
python3.6 -m pip install https://github.com/kytos/kytos/archive/master.zip
```
3. Start kytos in background: `kytosd`
4. Try to shutdown kytos: `pkill kytosd`

Expected result: kytos should be shutdown

Actual result: kytos remains running

If I revert PR #1231, the issue stops happening. So, looks like some internal command is using the shutdown API endpoint but does not fit the authenticated decorator. For example the stop_api_server() ( https://github.com/kytos/kytos/blob/master/kytos/core/api_server.py#L148) which is called by kytos.controller.stop_controller() (https://github.com/kytos/kytos/blob/master/kytos/core/controller.py#L399)

Comments:

Patch:
diff --git a/kytos/core/api_server.py b/kytos/core/api_server.py
index a18e15e..2682535 100644
--- a/kytos/core/api_server.py
+++ b/kytos/core/api_server.py
@@ -12,7 +12,7 @@ from http import HTTPStatus
 from urllib.error import HTTPError, URLError
 from urllib.request import urlopen, urlretrieve
 
-from flask import Blueprint, Flask, jsonify, send_file
+from flask import Blueprint, Flask, jsonify, request, send_file
 from flask_cors import CORS
 from flask_socketio import SocketIO, join_room, leave_room
 from werkzeug.exceptions import HTTPException
@@ -107,9 +107,11 @@ class APIServer:
     def start_api(self):
         """Start this APIServer instance API.
 
-        Start /api/kytos/core/shutdown/ and status/ endpoints, web UI.
+        Start /api/kytos/core/_shutdown/ and status/ endpoints, web UI.
+        The '_shutdown' endpoint should not be public and is intended to
+        shutdown the APIServer.
         """
-        self.register_core_endpoint('shutdown/', self.shutdown_api)
+        self.register_core_endpoint('_shutdown/', self.shutdown_api)
         self.register_core_endpoint('status/', self.status_api)
         self.register_core_endpoint('web/update/<version>/',
                                     self.update_web_ui,
@@ -146,23 +148,27 @@ class APIServer:
         return '{"response": "running"}', HTTPStatus.OK.value
 
     def stop_api_server(self):
-        """Send a shutdown request to stop Api Server."""
+        """Send a shutdown request to stop API Server."""
         try:
-            url = f'http://127.0.0.1:{self.port}/api/kytos/core/shutdown'
+            url = f'http://127.0.0.1:{self.port}/api/kytos/core/_shutdown'
             urlopen(url)
         except URLError:
             pass
 
-    @authenticated
     def shutdown_api(self):
-        """Handle shutdown requests received by Api Server.
+        """Handle requests received to shutdown the API Server.
 
         This method must be called by kytos using the method
         stop_api_server, otherwise this request will be ignored.
         """
+        allowed_host = ['127.0.0.1:'+str(self.port),
+                        'localhost:'+str(self.port)]
+        if request.host not in allowed_host:
+            return "", HTTPStatus.FORBIDDEN.value
+
         self.server.stop()
 
-        return 'Server shutting down...', HTTPStatus.OK.value
+        return 'API Server shutting down...', HTTPStatus.OK.value
 
     def static_web_ui(self, username, napp_name, filename):
         """Serve static files from installed napps."""


Test patch:
diff --git a/tests/unit/test_core/test_api_server.py b/tests/unit/test_core/test_api_server.py
index 109aa09..85b23bd 100644
--- a/tests/unit/test_core/test_api_server.py
+++ b/tests/unit/test_core/test_api_server.py
@@ -54,29 +54,21 @@ class TestAPIServer(unittest.TestCase):
 
         mock_exit.assert_called()
 
-    @patch('kytos.core.auth.request')
-    @patch('kytos.core.auth.jwt.decode', return_value=True)
-    def test_shutdown_api(self, _, mock_request):
+    @patch('kytos.core.api_server.request')
+    def test_shutdown_api(self, mock_request):
         """Test shutdown_api method."""
+        mock_request.host = 'localhost:8181'
 
-        mock_request.headers = {'Authorization': 'Bearer 123'}
         self.api_server.shutdown_api()
 
         self.api_server.server.stop.assert_called()
 
-    @patch('kytos.core.auth.jsonify')
-    @patch('kytos.core.auth.request')
-    def test_shutdown_api__error(self, mock_request, mock_jsonify):
+    @patch('kytos.core.api_server.request')
+    def test_shutdown_api__error(self, mock_request):
         """Test shutdown_api method to error case."""
-
-        mock_request.headers = {'Authorization': None}
+        mock_request.host = 'any:port'
         self.api_server.shutdown_api()
 
-        exc_msg = "The attribute 'content' has an invalid value 'None'."
-        msg = f"Token not sent or expired: {exc_msg}"
-
-        mock_jsonify.assert_called_with({"error": msg})
-
         self.api_server.server.stop.assert_not_called()
 
     def test_status_api(self):
@@ -89,7 +81,7 @@ class TestAPIServer(unittest.TestCase):
         """Test stop_api_server method."""
         self.api_server.stop_api_server()
 
-        url = "%s/shutdown" % API_URI
+        url = "%s/_shutdown" % API_URI
         mock_urlopen.assert_called_with(url)
 
     @patch('kytos.core.api_server.send_file')