Skip to content

Commit 5817d52

Browse files
committed
Implements: #1353
1 parent ff818b2 commit 5817d52

8 files changed

+290
-0
lines changed

IM/InfrastructureManager.py

+19
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from IM.openid.JWT import JWT
4343
from IM.openid.OpenIDClient import OpenIDClient
4444
from IM.vault import VaultCredentials
45+
from IM.Stats import Stats
4546

4647

4748
if Config.MAX_SIMULTANEOUS_LAUNCHES > 1:
@@ -2036,3 +2037,21 @@ def EstimateResouces(radl_data, auth):
20362037
cont += 1
20372038

20382039
return res
2040+
2041+
@staticmethod
2042+
def GetStats(init_date, end_date, auth):
2043+
"""
2044+
Get the statistics from the IM DB.
2045+
Args:
2046+
- init_date(str): Only will be returned infrastructure created afther this date.
2047+
- end_date(str): Only will be returned infrastructure created before this date.
2048+
- auth(Authentication): parsed authentication tokens.
2049+
Return: a list of dict with the stats.
2050+
"""
2051+
# First check the auth data
2052+
auth = InfrastructureManager.check_auth_data(auth)
2053+
stats = Stats.get_stats(init_date, end_date, auth)
2054+
if not stats:
2055+
raise Exception("ERROR connecting with the database!.")
2056+
else:
2057+
return stats

IM/REST.py

+44
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import flask
2222
import os
2323
import yaml
24+
import datetime
2425

2526
from cheroot.wsgi import Server as WSGIServer, PathInfoDispatcher
2627
from cheroot.ssl.builtin import BuiltinSSLAdapter
@@ -1077,6 +1078,49 @@ def RESTChangeInfrastructureAuth(infid=None):
10771078
return return_error(400, "Error modifying infrastructure owner: %s" % get_ex_error(ex))
10781079

10791080

1081+
@app.route('/stats', methods=['GET'])
1082+
def RESTGetStats():
1083+
try:
1084+
auth = get_auth_header()
1085+
except Exception:
1086+
return return_error(401, "No authentication data provided")
1087+
1088+
try:
1089+
init_date = None
1090+
if "init_date" in flask.request.args.keys():
1091+
init_date = flask.request.args.get("init_date").lower()
1092+
init_date = init_date.replace("/", "-")
1093+
parts = init_date.split("-")
1094+
try:
1095+
year = int(parts[0])
1096+
month = int(parts[1])
1097+
day = int(parts[2])
1098+
datetime.date(year, month, day)
1099+
except Exception:
1100+
return return_error(400, "Incorrect format in init_date parameter: YYYY/MM/dd")
1101+
else:
1102+
init_date = "1970-01-01"
1103+
1104+
end_date = None
1105+
if "end_date" in flask.request.args.keys():
1106+
end_date = flask.request.args.get("end_date").lower()
1107+
end_date = end_date.replace("/", "-")
1108+
parts = end_date.split("-")
1109+
try:
1110+
year = int(parts[0])
1111+
month = int(parts[1])
1112+
day = int(parts[2])
1113+
datetime.date(year, month, day)
1114+
except Exception:
1115+
return return_error(400, "Incorrect format in end_date parameter: YYYY/MM/dd")
1116+
1117+
stats = InfrastructureManager.GetStats(init_date, end_date, auth)
1118+
return format_output(stats, default_type="application/json", field_name="stats")
1119+
except Exception as ex:
1120+
logger.exception("Error getting stats")
1121+
return return_error(400, "Error getting stats: %s" % get_ex_error(ex))
1122+
1123+
10801124
@app.errorhandler(403)
10811125
def error_mesage_403(error):
10821126
return return_error(403, error.description)

IM/ServiceRequests.py

+14
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class IMBaseRequest(AsyncRequest):
6060
CHANGE_INFRASTRUCTURE_AUTH = "ChangeInfrastructureAuth"
6161
GET_INFRASTRUCTURE_OWNERS = "GetInfrastructureOwners"
6262
ESTIMATE_RESOURCES = "EstimateResouces"
63+
GET_STATS = "GetStats"
6364

6465
@staticmethod
6566
def create_request(function, arguments=()):
@@ -119,6 +120,8 @@ def create_request(function, arguments=()):
119120
return Request_GetInfrastructureOwners(arguments)
120121
elif function == IMBaseRequest.ESTIMATE_RESOURCES:
121122
return Request_EstimateResouces(arguments)
123+
elif function == IMBaseRequest.GET_STATS:
124+
return Request_GetStats(arguments)
122125
else:
123126
raise NotImplementedError("Function not Implemented")
124127

@@ -473,3 +476,14 @@ def _call_function(self):
473476
self._error_mesage = "Error getting the resources estimation"
474477
(radl_data, auth_data) = self.arguments
475478
return IM.InfrastructureManager.InfrastructureManager.EstimateResouces(radl_data, Authentication(auth_data))
479+
480+
481+
class Request_GetStats(IMBaseRequest):
482+
"""
483+
Request class for the GetStats function
484+
"""
485+
486+
def _call_function(self):
487+
self._error_mesage = "Error getting stats"
488+
(init_date, end_date, auth_data) = self.arguments
489+
return IM.InfrastructureManager.InfrastructureManager.GetStats(init_date, end_date, Authentication(auth_data))

IM/Stats.py

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# IM - Infrastructure Manager
2+
# Copyright (C) 2011 - GRyCAP - Universitat Politecnica de Valencia
3+
#
4+
# This program is free software: you can redistribute it and/or modify
5+
# it under the terms of the GNU General Public License as published by
6+
# the Free Software Foundation, either version 3 of the License, or
7+
# (at your option) any later version.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import os.path
18+
import datetime
19+
import json
20+
import yaml
21+
import logging
22+
23+
from IM.db import DataBase
24+
from IM.auth import Authentication
25+
from IM.config import Config
26+
from IM.InfrastructureList import InfrastructureList
27+
from radl.radl_parse import parse_radl
28+
29+
30+
class Stats():
31+
32+
logger = logging.getLogger('InfrastructureManager')
33+
"""Logger object."""
34+
35+
@staticmethod
36+
def _get_data(str_data, init_date=None, end_date=None):
37+
dic = json.loads(str_data)
38+
resp = {'creation_date': None}
39+
if 'creation_date' in dic and dic['creation_date']:
40+
creation_date = datetime.datetime.fromtimestamp(float(dic['creation_date']))
41+
resp['creation_date'] = str(creation_date)
42+
if init_date and creation_date < init_date:
43+
return None
44+
if end_date and creation_date > end_date:
45+
return None
46+
47+
resp['tosca_name'] = None
48+
if 'extra_info' in dic and dic['extra_info'] and "TOSCA" in dic['extra_info']:
49+
try:
50+
tosca = yaml.safe_load(dic['extra_info']['TOSCA'])
51+
icon = tosca.get("metadata", {}).get("icon", "")
52+
resp['tosca_name'] = os.path.basename(icon)[:-4]
53+
except Exception:
54+
Stats.logger.exception("Error loading TOSCA.")
55+
56+
resp['vm_count'] = 0
57+
resp['cpu_count'] = 0
58+
resp['memory_size'] = 0
59+
resp['cloud_type'] = None
60+
resp['cloud_host'] = None
61+
resp['hybrid'] = False
62+
for str_vm_data in dic['vm_list']:
63+
vm_data = json.loads(str_vm_data)
64+
cloud_data = json.loads(vm_data["cloud"])
65+
66+
# only get the cloud of the first VM
67+
if not resp['cloud_type']:
68+
resp['cloud_type'] = cloud_data["type"]
69+
if not resp['cloud_host']:
70+
resp['cloud_host'] = cloud_data["server"]
71+
elif resp['cloud_host'] != cloud_data["server"]:
72+
resp['hybrid'] = True
73+
74+
vm_sys = parse_radl(vm_data['info']).systems[0]
75+
if vm_sys.getValue('cpu.count'):
76+
resp['cpu_count'] += vm_sys.getValue('cpu.count')
77+
if vm_sys.getValue('memory.size'):
78+
resp['memory_size'] += vm_sys.getFeature('memory.size').getValue('M')
79+
resp['vm_count'] += 1
80+
81+
inf_auth = Authentication.deserialize(dic['auth']).getAuthInfo('InfrastructureManager')[0]
82+
resp['im_user'] = inf_auth.get('username')
83+
return resp
84+
85+
@staticmethod
86+
def get_stats(init_date="1970-01-01", end_date=None, auth=None):
87+
"""
88+
Get the statistics from the IM DB.
89+
90+
Args:
91+
92+
- init_date(str): Only will be returned infrastructure created afther this date.
93+
- end_date(str): Only will be returned infrastructure created afther this date.
94+
- auth(Authentication): parsed authentication tokens.
95+
96+
Return: a list of dict with the stats with the following format:
97+
{'creation_date': '2022-03-07 13:16:14',
98+
'tosca_name': 'kubernetes',
99+
'vm_count': 2,
100+
'cpu_count': 4,
101+
'memory_size': 1024,
102+
'cloud_type': 'OSCAR',
103+
'cloud_host': 'sharp-elbakyan5.im.grycap.net',
104+
'hybrid': False,
105+
'im_user': '__OPENID__mcaballer',
106+
'inf_id': '1',
107+
'last_date': '2022-03-23'}
108+
"""
109+
stats = []
110+
db = DataBase(Config.DATA_DB)
111+
if db.connect():
112+
if db.db_type == DataBase.MONGO:
113+
filt = InfrastructureList._gen_filter_from_auth(auth)
114+
res = db.find("inf_list", filt, {"id": True, "date": True, "date": True}, [('id', -1)])
115+
else:
116+
where = InfrastructureList._gen_where_from_auth(auth)
117+
res = db.select("select data, date, id from inf_list %s order by rowid desc" % where)
118+
119+
for elem in res:
120+
if db.db_type == DataBase.MONGO:
121+
data = elem["data"]
122+
date = elem["date"]
123+
inf_id = elem["id"]
124+
else:
125+
data = elem[0]
126+
date = elem[1]
127+
inf_id = elem[2]
128+
try:
129+
init = datetime.datetime.strptime(init_date, "%Y-%m-%d")
130+
end = datetime.datetime.strptime(end_date, "%Y-%m-%d") if end_date else None
131+
res = Stats._get_data(data, init, end)
132+
if res:
133+
res['inf_id'] = inf_id
134+
res['last_date'] = str(date)
135+
stats.append(res)
136+
except Exception:
137+
Stats.logger.exception("ERROR reading infrastructure info from Inf ID: %s" % inf_id)
138+
db.close()
139+
return stats
140+
else:
141+
Stats.logger.error("ERROR connecting with the database!.")
142+
return None

IM/im_service.py

+7
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ def EstimateResources(radl_data, auth_data):
229229
return WaitRequest(request)
230230

231231

232+
def GetStats(init_date, end_date, auth_data):
233+
request = IMBaseRequest.create_request(
234+
IMBaseRequest.GET_STATS, (init_date, end_date, auth_data))
235+
return WaitRequest(request)
236+
237+
232238
def launch_daemon():
233239
"""
234240
Launch the IM daemon
@@ -290,6 +296,7 @@ def launch_daemon():
290296
server.register_function(ChangeInfrastructureAuth)
291297
server.register_function(GetInfrastructureOwners)
292298
server.register_function(EstimateResources)
299+
server.register_function(GetStats)
293300

294301
# Launch the API XMLRPC thread
295302
server.serve_forever_in_thread()

test/unit/REST.py

+15
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,21 @@ def test_GetInfrastructureOwners(self, GetInfrastructureOwners):
838838
res = self.client.get('/infrastructures/1/authorization', headers=headers)
839839
self.assertEqual(res.json, {"authorization": ["user1", "user2"]})
840840

841+
@patch("IM.InfrastructureManager.InfrastructureManager.GetStats")
842+
def test_GetStats(self, GetStats):
843+
"""Test REST GetStats."""
844+
headers = {"AUTHORIZATION": "type = InfrastructureManager; username = user; password = pass"}
845+
GetStats.return_value = [{"key": 1}]
846+
847+
res = self.client.get('/stats?init_date=2010-01-01&end_date=2022-01-01', headers=headers)
848+
849+
self.assertEqual(res.json, {"stats": [{"key": 1}]})
850+
self.assertEqual(GetStats.call_args_list[0][0][0], '2010-01-01')
851+
self.assertEqual(GetStats.call_args_list[0][0][1], '2022-01-01')
852+
self.assertEqual(GetStats.call_args_list[0][0][2].auth_list, [{"type": "InfrastructureManager",
853+
"username": "user",
854+
"password": "pass"}])
855+
841856

842857
if __name__ == "__main__":
843858
unittest.main()

test/unit/ServiceRequests.py

+7
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ def test_estimate_resources(self, inflist):
214214
IM.ServiceRequests.IMBaseRequest.ESTIMATE_RESOURCES, ("", ""))
215215
req._call_function()
216216

217+
@patch('IM.InfrastructureManager.InfrastructureManager')
218+
def test_get_stats(self, inflist):
219+
import IM.ServiceRequests
220+
req = IM.ServiceRequests.IMBaseRequest.create_request(
221+
IM.ServiceRequests.IMBaseRequest.GET_STATS, ("", "", ""))
222+
req._call_function()
223+
217224

218225
if __name__ == '__main__':
219226
unittest.main()

test/unit/test_im_logic.py

+42
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import sys
2424
import json
2525
import base64
26+
import yaml
2627

2728
from mock import Mock, patch, MagicMock
2829

@@ -1560,6 +1561,47 @@ def test_estimate_resources(self):
15601561
'storage': [{'sizeInGigabytes': 100}]
15611562
}})
15621563

1564+
@patch('IM.Stats.DataBase')
1565+
@patch('IM.InfrastructureManager.InfrastructureManager.check_auth_data')
1566+
def test_get_stats(self, check_auth_data, DataBase):
1567+
radl = """
1568+
system node (
1569+
memory.size = 512M and
1570+
cpu.count = 2
1571+
)"""
1572+
1573+
auth = Authentication([{'type': 'InfrastructureManager', 'token': 'atoken',
1574+
'username': '__OPENID__mcaballer', 'password': 'pass'}])
1575+
check_auth_data.return_value = auth
1576+
1577+
db = MagicMock()
1578+
inf_data = {
1579+
"id": "1",
1580+
"auth": auth.serialize(),
1581+
"creation_date": 1646655374,
1582+
"extra_info": {"TOSCA": yaml.dump({"metadata": {"icon": "kubernetes.png"}})},
1583+
"vm_list": [
1584+
json.dumps({"cloud": '{"type": "OSCAR", "server": "sharp-elbakyan5.im.grycap.net"}', "info": radl}),
1585+
json.dumps({"cloud": '{"type": "OSCAR", "server": "sharp-elbakyan5.im.grycap.net"}', "info": radl})
1586+
]
1587+
}
1588+
db.select.return_value = [(json.dumps(inf_data), '2022-03-23', '1')]
1589+
DataBase.return_value = db
1590+
1591+
stats = IM.GetStats('2001-01-01', '2122-01-01', auth)
1592+
expected_res = [{'creation_date': '2022-03-07 13:16:14',
1593+
'tosca_name': 'kubernetes',
1594+
'vm_count': 2,
1595+
'cpu_count': 4,
1596+
'memory_size': 1024,
1597+
'cloud_type': 'OSCAR',
1598+
'cloud_host': 'sharp-elbakyan5.im.grycap.net',
1599+
'hybrid': False,
1600+
'im_user': '__OPENID__mcaballer',
1601+
'inf_id': '1',
1602+
'last_date': '2022-03-23'}]
1603+
self.assertEqual(stats, expected_res)
1604+
15631605

15641606
if __name__ == "__main__":
15651607
unittest.main()

0 commit comments

Comments
 (0)