From 2d6675fa3bc419b1798dd7dcac53b0c72fc0b1a2 Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Sun, 18 Jan 2015 18:21:26 +0100 Subject: [PATCH] Add view (MVC speeking) for the plugins. Add API methods and Unitary test. --- NEWS | 1 + glances/core/glances_server.py | 6 +++- glances/core/glances_stats.py | 11 +++++++ glances/outputs/glances_bottle.py | 52 +++++++++++++++++++++++++++--- glances/plugins/glances_diskio.py | 2 +- glances/plugins/glances_fs.py | 2 +- glances/plugins/glances_network.py | 2 +- glances/plugins/glances_raid.py | 3 ++ glances/plugins/glances_sensors.py | 11 +++---- unitest-restful.py | 39 ++++++++++++++++++++-- unitest-xmlrpc.py | 30 ++++++++++++++--- 11 files changed, 136 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index b2c1d556..4c75b902 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,7 @@ Enhancements and news features: * Add the Docker plugin (issue #440) * It is possible, through the configuration file, to define if an alarm should be logged or not (using the _log option) (issue #437) * You can now set alarm for Disk IO + * API: add getAllLimits and getAllViews methods (issue#481) Bugs corrected: diff --git a/glances/core/glances_server.py b/glances/core/glances_server.py index 91d627bb..6cf8176a 100644 --- a/glances/core/glances_server.py +++ b/glances/core/glances_server.py @@ -160,7 +160,11 @@ class GlancesInstance(object): def getAllLimits(self): # Return all the plugins limits - return json.dumps(self.stats.getAllLimits()) + return json.dumps(self.stats.getAllLimitsAsDict()) + + def getAllViews(self): + # Return all the plugins views + return json.dumps(self.stats.getAllViewsAsDict()) def getAllMonitored(self): # Return the processes monitored list diff --git a/glances/core/glances_stats.py b/glances/core/glances_stats.py index 859dc0f9..8b92334f 100644 --- a/glances/core/glances_stats.py +++ b/glances/core/glances_stats.py @@ -190,6 +190,17 @@ class GlancesStats(object): ret[p] = self._plugins[p].get_limits() return ret + def getAllViews(self): + """Return the plugins views""" + return [self._plugins[p].get_views() for p in self._plugins] + + def getAllViewsAsDict(self): + """Return all the stats views (dict)""" + ret = {} + for p in self._plugins: + ret[p] = self._plugins[p].get_views() + return ret + def get_plugin_list(self): """Return the plugin list.""" self._plugins diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index dded2b54..4256efd1 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -70,8 +70,10 @@ class GlancesBottle(object): self._app.route('/api/2/pluginslist', method="GET", callback=self._api_plugins) self._app.route('/api/2/all', method="GET", callback=self._api_all) self._app.route('/api/2/all/limits', method="GET", callback=self._api_all_limits) + self._app.route('/api/2/all/views', method="GET", callback=self._api_all_views) self._app.route('/api/2/:plugin', method="GET", callback=self._api) self._app.route('/api/2/:plugin/limits', method="GET", callback=self._api_limits) + self._app.route('/api/2/:plugin/views', method="GET", callback=self._api_views) self._app.route('/api/2/:plugin/:item', method="GET", callback=self._api_item) self._app.route('/api/2/:plugin/:item/:value', method="GET", callback=self._api_value) @@ -151,7 +153,7 @@ class GlancesBottle(object): self.stats.update() try: - # Get the JSON value of the stat ID + # Get the JSON value of the stat value statval = json.dumps(self.stats.getAllAsDict()) except Exception as e: abort(404, "Cannot get stats (%s)" % str(e)) @@ -168,12 +170,29 @@ class GlancesBottle(object): response.content_type = 'application/json' try: - # Get the JSON value of the stat ID + # Get the JSON value of the stat limits limits = json.dumps(self.stats.getAllLimitsAsDict()) except Exception as e: abort(404, "Cannot get limits (%s)" % (str(e))) return limits + def _api_all_views(self): + """ + Glances API RESTFul implementation + Return the JSON representation of all the plugins views + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + response.content_type = 'application/json' + + try: + # Get the JSON value of the stat view + limits = json.dumps(self.stats.getAllViewsAsDict()) + except Exception as e: + abort(404, "Cannot get views (%s)" % (str(e))) + return limits + def _api(self, plugin): """ Glances API RESTFul implementation @@ -214,11 +233,34 @@ class GlancesBottle(object): # self.stats.update() try: - # Get the JSON value of the stat ID - limits = self.stats.get_plugin(plugin).get_limits() + # Get the JSON value of the stat limits + ret = self.stats.get_plugin(plugin).get_limits() except Exception as e: abort(404, "Cannot get limits for plugin %s (%s)" % (plugin, str(e))) - return limits + return ret + + def _api_views(self, plugin): + """ + Glances API RESTFul implementation + Return the JSON views of a given plugin + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + response.content_type = 'application/json' + + if plugin not in self.plugins_list: + abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) + + # Update the stat + # self.stats.update() + + try: + # Get the JSON value of the stat views + ret = self.stats.get_plugin(plugin).get_views() + except Exception as e: + abort(404, "Cannot get views for plugin %s (%s)" % (plugin, str(e))) + return ret def _api_item(self, plugin, item): """ diff --git a/glances/plugins/glances_diskio.py b/glances/plugins/glances_diskio.py index 6404aa24..e0ee3cb5 100644 --- a/glances/plugins/glances_diskio.py +++ b/glances/plugins/glances_diskio.py @@ -134,7 +134,7 @@ class Plugin(GlancesPlugin): # Add specifics informations # Alert - for i in sorted(self.stats, key=operator.itemgetter(self.get_key())): + for i in self.stats: disk_real_name = i['disk_name'] self.views[i[self.get_key()]]['read_bytes']['decoration'] = self.get_alert(int(i['read_bytes'] // i['time_since_update']), header=disk_real_name + '_rx') diff --git a/glances/plugins/glances_fs.py b/glances/plugins/glances_fs.py index ac2df401..e4dfbcff 100644 --- a/glances/plugins/glances_fs.py +++ b/glances/plugins/glances_fs.py @@ -180,7 +180,7 @@ class Plugin(GlancesPlugin): # Add specifics informations # Alert - for i in sorted(self.stats, key=operator.itemgetter(self.get_key())): + for i in self.stats: self.views[i[self.get_key()]]['used']['decoration'] = self.get_alert(i['used'], max=i['size'], header=i['mnt_point']) def msg_curse(self, args=None, max_width=None): diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index 0083f198..0f311015 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -195,7 +195,7 @@ class Plugin(GlancesPlugin): # Add specifics informations # Alert - for i in sorted(self.stats, key=operator.itemgetter(self.get_key())): + for i in self.stats: ifrealname = i['interface_name'].split(':')[0] self.views[i[self.get_key()]]['rx']['decoration'] = self.get_alert(int(i['rx'] // i['time_since_update'] * 8), header=ifrealname + '_rx') diff --git a/glances/plugins/glances_raid.py b/glances/plugins/glances_raid.py index 20743c36..a31baccf 100644 --- a/glances/plugins/glances_raid.py +++ b/glances/plugins/glances_raid.py @@ -72,6 +72,9 @@ class Plugin(GlancesPlugin): # No standard way for the moment... pass + # Update the view + self.update_views() + return self.stats def msg_curse(self, args=None): diff --git a/glances/plugins/glances_sensors.py b/glances/plugins/glances_sensors.py index 9cbae8b8..526d0980 100644 --- a/glances/plugins/glances_sensors.py +++ b/glances/plugins/glances_sensors.py @@ -172,13 +172,10 @@ class Plugin(GlancesPlugin): msg = '{0:18}'.format(label) ret.append(self.curse_add_line(msg)) msg = '{0:>5}'.format(i['value']) - try: - ret.append(self.curse_add_line( - msg, self.get_views(item=i[self.get_key()], - key='value', - option='decoration'))) - except TypeError: - pass + ret.append(self.curse_add_line( + msg, self.get_views(item=i[self.get_key()], + key='value', + option='decoration'))) return ret diff --git a/unitest-restful.py b/unitest-restful.py index 9b154e34..55fb5292 100755 --- a/unitest-restful.py +++ b/unitest-restful.py @@ -124,8 +124,6 @@ class TestGlances(unittest.TestCase): plist = requests.get("%s/%s" % (URL, method)) - print(plist.json()) - for p in plist.json(): print("HTTP RESTFul request: %s/%s" % (URL, p)) req = requests.get("%s/%s" % (URL, p)) @@ -175,6 +173,43 @@ class TestGlances(unittest.TestCase): self.assertTrue(req.ok) self.assertIsInstance(req.json(), types.DictType) + def test_007_all_views(self): + """All""" + method = "all/views" + print('INFO: [TEST_007] Get all views') + + print("HTTP RESTFul request: %s/%s" % (URL, method)) + req = requests.get("%s/%s" % (URL, method)) + + self.assertTrue(req.ok) + self.assertIsInstance(req.json(), types.DictType) + + def test_008_plugins_limits(self): + """Plugins limits""" + method = "pluginslist" + print('INFO: [TEST_008] Plugins limits') + + plist = requests.get("%s/%s" % (URL, method)) + + for p in plist.json(): + print("HTTP RESTFul request: %s/%s/limits" % (URL, p)) + req = requests.get("%s/%s/limits" % (URL, p)) + self.assertTrue(req.ok) + self.assertIsInstance(req.json(), types.DictType) + + def test_009_plugins_views(self): + """Plugins views""" + method = "pluginslist" + print('INFO: [TEST_009] Plugins views') + + plist = requests.get("%s/%s" % (URL, method)) + + for p in plist.json(): + print("HTTP RESTFul request: %s/%s/views" % (URL, p)) + req = requests.get("%s/%s/views" % (URL, p)) + self.assertTrue(req.ok) + self.assertIsInstance(req.json(), types.DictType) + def test_999_stop_server(self): """Stop the Glances Web Server""" print('INFO: [TEST_999] Stop the Glances Web Server') diff --git a/unitest-xmlrpc.py b/unitest-xmlrpc.py index 4d14ce13..4e7a86bb 100755 --- a/unitest-xmlrpc.py +++ b/unitest-xmlrpc.py @@ -31,7 +31,7 @@ import json import types try: from xmlrpc.client import ServerProxy -except ImportError: +except ImportError: # Python 2 from xmlrpclib import ServerProxy @@ -50,7 +50,8 @@ pid = None # Unitary test is only available from a GNU/Linus machine if not is_linux: - print('ERROR: XML/RPC API unitaries tests should be ran on GNU/Linux operating system') + print( + 'ERROR: XML/RPC API unitaries tests should be ran on GNU/Linux operating system') sys.exit(2) else: print('Unitary tests for {0} {1}'.format(appname, version)) @@ -77,6 +78,7 @@ client = ServerProxy(URL) # Unitest class # ============== + class TestGlances(unittest.TestCase): """Test Glances class.""" @@ -88,16 +90,16 @@ class TestGlances(unittest.TestCase): def test_000_start_server(self): """Start the Glances Web Server""" print('INFO: [TEST_000] Start the Glances Web Server') - + global pid - + cmdline = "/usr/bin/python -m glances -s -p %s" % SERVER_PORT print("Run the Glances Server on port %s" % SERVER_PORT) args = shlex.split(cmdline) pid = subprocess.Popen(args) print("Please wait...") time.sleep(1) - + self.assertTrue(pid is not None) def test_001_all(self): @@ -195,6 +197,24 @@ class TestGlances(unittest.TestCase): req = json.loads(client.getProcessList()) self.assertIsInstance(req, types.ListType) + def test_010_all_limits(self): + """All limits""" + method = "getAllLimits()" + print('INFO: [TEST_010] Method: %s' % method) + + req = json.loads(client.getAllLimits()) + self.assertIsInstance(req, types.DictType) + self.assertIsInstance(req['cpu'], types.DictType) + + def test_011_all_views(self): + """All views""" + method = "getAllViews()" + print('INFO: [TEST_011] Method: %s' % method) + + req = json.loads(client.getAllViews()) + self.assertIsInstance(req, types.DictType) + self.assertIsInstance(req['cpu'], types.DictType) + def test_999_stop_server(self): """Stop the Glances Web Server""" print('INFO: [TEST_999] Stop the Glances Server')