Skip to content
This repository has been archived by the owner on Apr 22, 2024. It is now read-only.

Commit

Permalink
Merge 484036a into d301fa5
Browse files Browse the repository at this point in the history
  • Loading branch information
rmotitsuki committed May 22, 2019
2 parents d301fa5 + 484036a commit 45c961f
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 20 deletions.
159 changes: 152 additions & 7 deletions kytos/core/api_server.py
Expand Up @@ -8,6 +8,7 @@
import zipfile
from datetime import datetime
from glob import glob
from http import HTTPStatus
from urllib.error import HTTPError, URLError
from urllib.request import urlopen, urlretrieve

Expand All @@ -24,17 +25,23 @@ class APIServer:
_NAPP_PREFIX = "/api/{napp.username}/{napp.name}/"
_CORE_PREFIX = "/api/kytos/core/"

# pylint: disable=too-many-arguments
def __init__(self, app_name, listen='0.0.0.0', port=8181,
napps_dir=None):
napps_manager=None, napps_dir=None):
"""Start a Flask+SocketIO server.
Require controller to get NApps dir and NAppsManager
Args:
app_name(string): String representing a App Name
listen (string): host name used by api server instance
port (int): Port number used by api server instance
napps_dir(string): napps path directory
controller(kytos.core.controller): A controller instance.
"""
dirname = os.path.dirname(os.path.abspath(__file__))
self.napps_manager = napps_manager
self.napps_dir = napps_dir

self.flask_dir = os.path.join(dirname, '../web-ui')
self.log = logging.getLogger('api_server')

Expand All @@ -53,7 +60,6 @@ def __init__(self, app_name, listen='0.0.0.0', port=8181,

# Update web-ui if necessary
self.update_web_ui(force=False)
self.napps_dir = napps_dir

def _enable_websocket_rooms(self):
socket = self.server
Expand Down Expand Up @@ -90,6 +96,9 @@ def start_api(self):
self.register_core_endpoint('web/update/',
self.update_web_ui,
methods=['POST'])

self.register_core_napp_services()

self._register_web_ui()

def register_core_endpoint(self, rule, function, **options):
Expand All @@ -112,7 +121,7 @@ def _register_web_ui(self):
@staticmethod
def status_api():
"""Display kytos status using the route ``/kytos/status/``."""
return '{"response": "running"}', 201
return '{"response": "running"}', HTTPStatus.CREATED.value

def stop_api_server(self):
"""Send a shutdown request to stop Api Server."""
Expand All @@ -131,18 +140,18 @@ def shutdown_api(self):
allowed_host = ['127.0.0.1:'+str(self.port),
'localhost:'+str(self.port)]
if request.host not in allowed_host:
return "", 403
return "", HTTPStatus.FORBIDDEN.value

self.server.stop()

return 'Server shutting down...', 200
return 'Server shutting down...', HTTPStatus.OK.value

def static_web_ui(self, username, napp_name, filename):
"""Serve static files from installed napps."""
path = f"{self.napps_dir}/{username}/{napp_name}/ui/{filename}"
if os.path.exists(path):
return send_file(path)
return "", 404
return "", HTTPStatus.NOT_FOUND.value

def get_ui_components(self, section_name):
"""Return all napps ui components from an specific section.
Expand Down Expand Up @@ -332,3 +341,139 @@ def remove_napp_endpoints(self, napp):
# pylint: enable=protected-access

self.log.info('The Rest endpoints from %s were disabled.', prefix)

def register_core_napp_services(self):
"""
Register /kytos/core/ services over NApps.
It registers enable, disable, install, uninstall NApps that will
be used by kytos-utils.
"""
self.register_core_endpoint("napps/<username>/<napp_name>/enable",
self._enable_napp)
self.register_core_endpoint("napps/<username>/<napp_name>/disable",
self._disable_napp)
self.register_core_endpoint("napps/<username>/<napp_name>/install",
self._install_napp)
self.register_core_endpoint("napps/<username>/<napp_name>/uninstall",
self._uninstall_napp)
self.register_core_endpoint("napps_enabled",
self._list_enabled_napps)
self.register_core_endpoint("napps_installed",
self._list_installed_napps)
self.register_core_endpoint(
"napps/<username>/<napp_name>/metadata/<key>",
self._get_napp_metadata)

def _enable_napp(self, username, napp_name):
"""
Enable an installed NApp.
:param username: NApps user name
:param napp_name: NApp name
:return: JSON content and return code
"""
# Check if the NApp is installed
if not self.napps_manager.is_installed(username, napp_name):
return '{"response": "not installed"}', \
HTTPStatus.BAD_REQUEST.value

# Check if the NApp is already been enabled
if not self.napps_manager.is_enabled(username, napp_name):
self.napps_manager.enable(username, napp_name)

# Check if NApp is enabled
if not self.napps_manager.is_enabled(username, napp_name):
# If it is not enabled an admin user must check the log file
return '{"response": "error"}', \
HTTPStatus.INTERNAL_SERVER_ERROR.value

return '{"response": "enabled"}', HTTPStatus.OK.value

def _disable_napp(self, username, napp_name):
"""
Disable an installed NApp.
:param username: NApps user name
:param napp_name: NApp name
:return: JSON content and return code
"""
# Check if the NApp is installed
if not self.napps_manager.is_installed(username, napp_name):
return '{"response": "not installed"}', \
HTTPStatus.BAD_REQUEST.value

# Check if the NApp is enabled
if self.napps_manager.is_enabled(username, napp_name):
self.napps_manager.disable(username, napp_name)

# Check if NApp is still enabled
if self.napps_manager.is_enabled(username, napp_name):
# If it is still enabled an admin user must check the log file
return '{"response": "error"}', \
HTTPStatus.INTERNAL_SERVER_ERROR.value

return '{"response": "disabled"}', \
HTTPStatus.OK.value

def _install_napp(self, username, napp_name):
# Check if the NApp is installed
if self.napps_manager.is_installed(username, napp_name):
return '{"response": "installed"}', HTTPStatus.OK.value

napp = "{}/{}".format(username, napp_name)

# Try to install the napp
if not self.napps_manager.install(napp, enable=False):
# If it is not installed an admin user must check the log file
return '{"response": "error"}', \
HTTPStatus.INTERNAL_SERVER_ERROR.value

return '{"response": "installed"}', HTTPStatus.OK.value

def _uninstall_napp(self, username, napp_name):
# Check if the NApp is installed
if self.napps_manager.is_installed(username, napp_name):
# Try to unload/uninstall the napp
if not self.napps_manager.uninstall(username, napp_name):
# If it is not uninstalled admin user must check the log file
return '{"response": "error"}', \
HTTPStatus.INTERNAL_SERVER_ERROR.value

return '{"response": "uninstalled"}', HTTPStatus.OK.value

def _list_enabled_napps(self):
"""Sorted list of (username, napp_name) of enabled napps."""
serialized_dict = json.dumps(
self.napps_manager.get_enabled_napps(),
default=lambda a: [a.username, a.name])

return '{"napps": %s}' % serialized_dict, HTTPStatus.OK.value

def _list_installed_napps(self):
"""Sorted list of (username, napp_name) of installed napps."""
serialized_dict = json.dumps(
self.napps_manager.get_installed_napps(),
default=lambda a: [a.username, a.name])

return '{"napps": %s}' % serialized_dict, HTTPStatus.OK.value

def _get_napp_metadata(self, username, napp_name, key):
"""Get NApp metadata value.
For safety reasons, only some keys can be retrieved:
napp_dependencies, description, version.
"""
valid_keys = ['napp_dependencies', 'description', 'version']

if not self.napps_manager.is_installed(username, napp_name):
return "NApp is not installed.", HTTPStatus.BAD_REQUEST.value

if key not in valid_keys:
return "Invalid key.", HTTPStatus.BAD_REQUEST.value

data = self.napps_manager.get_napp_metadata(username, napp_name, key)
serialized_dict = json.dumps({key: data})

return '%s' % serialized_dict, HTTPStatus.OK.value
2 changes: 1 addition & 1 deletion kytos/core/connection.py
Expand Up @@ -2,8 +2,8 @@
import logging
from enum import Enum
from errno import EBADF, ENOTCONN
from socket import error as SocketError
from socket import SHUT_RDWR
from socket import error as SocketError

__all__ = ('Connection', 'ConnectionProtocol', 'ConnectionState')

Expand Down
17 changes: 8 additions & 9 deletions kytos/core/controller.py
Expand Up @@ -22,8 +22,8 @@
import sys
import threading
from concurrent.futures import ThreadPoolExecutor
from importlib import reload as reload_module
from importlib import import_module
from importlib import reload as reload_module
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path

Expand Down Expand Up @@ -110,18 +110,17 @@ def __init__(self, options=None, loop=None):
#: logging.Logger: Logger instance used by Kytos.
self.log = None

#: API Server used to expose rest endpoints.
self.api_server = APIServer(__name__, self.options.listen,
self.options.api_port,
napps_dir=self.options.napps)

self._register_endpoints()

#: Observer that handle NApps when they are enabled or disabled.
self.napp_dir_listener = NAppDirListener(self)

self.napps_manager = NAppsManager(self)

#: API Server used to expose rest endpoints.
self.api_server = APIServer(__name__, self.options.listen,
self.options.api_port,
self.napps_manager, self.options.napps)

self._register_endpoints()
#: Adding the napps 'enabled' directory into the PATH
#: Now you can access the enabled napps with:
#: from napps.<username>.<napp_name> import ?....
Expand Down Expand Up @@ -572,7 +571,7 @@ def set_switch_options(self, dpid):
self.log.error("Invalid vlan_pool settings: %s", err)

if vlan_pool.get(dpid):
self.log.info(f"Loading vlan_pool configuration for dpid {dpid}")
self.log.info("Loading vlan_pool configuration for dpid %s", dpid)
for intf_num, port_list in vlan_pool[dpid].items():
if not switch.interfaces.get((intf_num)):
vlan_ids = set()
Expand Down
1 change: 1 addition & 0 deletions kytos/core/interface.py
Expand Up @@ -107,6 +107,7 @@ def id(self): # pylint: disable=invalid-name
Returns:
string: Interface id.
"""
return "{}:{}".format(self.switch.dpid, self.port_number)

Expand Down
24 changes: 23 additions & 1 deletion kytos/core/napps/manager.py
Expand Up @@ -211,6 +211,28 @@ def get_installed_napps(self):
"""Return all NApps installed on this controller FS."""
return self.get_napps_from_path(self._installed_path)

def get_napp_metadata(self, username, napp_name, key):
"""Return a value from kytos.json.
Args:
username (string): A Username.
napp_name (string): A NApp name
key (string): Key used to get the value within kytos.json.
Returns:
meta (object): Value stored in kytos.json.
"""
napp_id = "{}/{}".format(username, napp_name)
kytos_json = self._installed_path / napp_id / 'kytos.json'
try:
with kytos_json.open() as file_descriptor:
meta = json.load(file_descriptor)
return meta[key]
except (FileNotFoundError, json.JSONDecodeError, KeyError):
LOG.warning("NApp metadata load failed: %s/kytos.json", napp_id)
return ''

@staticmethod
def get_napps_from_path(path: Path):
"""List all NApps found in ``napps_dir``."""
Expand All @@ -233,7 +255,7 @@ def _create_module(path: Path):
(path / '__init__.py').touch()

@staticmethod
def _find_napp(napp, root: Path=None) -> Path:
def _find_napp(napp, root: Path = None) -> Path:
"""Return local NApp root folder.
Search for kytos.json in _./_ folder and _./user/napp_.
Expand Down
2 changes: 1 addition & 1 deletion tests/test_core/test_interface.py
Expand Up @@ -5,7 +5,7 @@

from pyof.v0x04.common.port import PortFeatures

from kytos.core.interface import Interface, TAG, TAGType
from kytos.core.interface import TAG, Interface, TAGType
from kytos.core.switch import Switch

logging.basicConfig(level=logging.CRITICAL)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_core/test_switch.py
Expand Up @@ -28,7 +28,7 @@ def tearDown(self):

def test_switch_vlan_pool_default(self):
"""Test default vlan_pool value."""
self.assertEqual(self.options.vlan_pool, {})
self.assertEqual(self.options.vlan_pool, '{}')

def test_switch_vlan_pool_options(self):
"""Test switch with the example from kytos.conf."""
Expand Down

0 comments on commit 45c961f

Please sign in to comment.