Skip to content
This repository was archived by the owner on Dec 5, 2018. It is now read-only.

Commit b8ff9b4

Browse files
author
Walt Shands
committed
Merge branch 'release/1.0.7'
2 parents 8227d66 + 3278c7a commit b8ff9b4

File tree

9 files changed

+1881
-1623
lines changed

9 files changed

+1881
-1623
lines changed

action.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from flask import Flask, jsonify, request, session, Blueprint
2+
from flask_login import LoginManager, login_required, \
3+
current_user, UserMixin
4+
# import json
5+
# from flask_sqlalchemy import SQLAlchemy
6+
from flask_cors import CORS, cross_origin
7+
# from flask_migrate import Migrate
8+
# import flask_excel as excel
9+
from flask.ext.elasticsearch import Elasticsearch
10+
# import ast
11+
# from decimal import Decimal
12+
# import copy
13+
14+
import os
15+
# from models import Billing, db
16+
# from utility import get_compute_costs, get_storage_costs, create_analysis_costs_json, create_storage_costs_json
17+
import datetime
18+
# import calendar
19+
# import click
20+
# TEST database call
21+
import logging
22+
from database import db, login_db, login_manager, User
23+
from monitordb_lib import luigiDBInit
24+
from sqlalchemy import select, desc
25+
26+
actionbp = Blueprint('actionbp', 'actionbp')
27+
28+
logging.basicConfig()
29+
30+
es_service = os.environ.get("ES_SERVICE", "localhost")
31+
es = Elasticsearch(['http://' + es_service + ':9200/'])
32+
33+
@login_manager.user_loader
34+
def load_user(user_id):
35+
return User.query.get(int(user_id))
36+
37+
@actionbp.route('/login')
38+
def login():
39+
if current_user.is_authenticated:
40+
redirect('https://{}'.format(os.getenv('DCC_DASHBOARD_HOST')))
41+
else:
42+
redirect('https://{}/login'.format(os.getenv('DCC_DASHBOARD_HOST')))
43+
44+
@actionbp.route('/action/service')
45+
@login_required
46+
@cross_origin()
47+
def get_action_service():
48+
monitordb_connection, monitordb_table = luigiDBInit()
49+
select_query = select([monitordb_table]).order_by(desc("last_updated"))
50+
select_result = monitordb_connection.execute(select_query)
51+
result_list = [dict(row) for row in select_result]
52+
return jsonify(result_list)
53+
54+
@actionbp.route('/')
55+
@cross_origin()
56+
def hello():
57+
return 'Hello World!'
58+

billing.py

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
from flask import Flask, jsonify, request, session, Blueprint
2+
from flask_login import LoginManager, login_required, \
3+
current_user, UserMixin
4+
# import json
5+
# from flask_sqlalchemy import SQLAlchemy
6+
from flask_cors import CORS, cross_origin
7+
from flask_migrate import Migrate
8+
# import flask_excel as excel
9+
from flask.ext.elasticsearch import Elasticsearch
10+
# import ast
11+
from decimal import Decimal
12+
# import copy
13+
14+
import os
15+
from models import Billing, db
16+
from utility import get_compute_costs, get_storage_costs, create_analysis_costs_json, create_storage_costs_json
17+
import datetime
18+
import calendar
19+
import click
20+
# TEST database call
21+
# from sqlalchemy import create_engine, MetaData, String, Table, Float, Column, select
22+
import logging
23+
from database import db, login_db, login_manager, User
24+
25+
billingbp = Blueprint('billingbp', 'billingbp')
26+
27+
logging.basicConfig()
28+
29+
es_service = os.environ.get("ES_SERVICE", "localhost")
30+
es = Elasticsearch(['http://' + es_service + ':9200/'])
31+
32+
@login_manager.user_loader
33+
def load_user(user_id):
34+
return User.query.get(int(user_id))
35+
36+
@billingbp.route('/login')
37+
def login():
38+
if current_user.is_authenticated:
39+
redirect('https://{}'.format(os.getenv('DCC_DASHBOARD_HOST')))
40+
else:
41+
redirect('https://{}/login'.format(os.getenv('DCC_DASHBOARD_HOST')))
42+
43+
@billingbp.route('/invoices')
44+
@login_required
45+
@cross_origin()
46+
def find_invoices():
47+
project = str(request.args.get('project'))
48+
if project:
49+
invoices = [invoice.to_json() for invoice in Billing.query.filter(Billing.project == project).order_by(
50+
Billing.end_date.desc()).all()]
51+
return jsonify(invoices)
52+
else:
53+
return None, 401
54+
55+
56+
@billingbp.route('/projects')
57+
@cross_origin()
58+
def get_projects():
59+
es_resp = es.search(index='billing_idx', body={"query": {"match_all": {}}, "aggs": {
60+
"projects": {
61+
"terms": {
62+
"field": "project.keyword",
63+
"size": 9999
64+
}
65+
}
66+
}}, size=0)
67+
68+
projects = []
69+
for project in es_resp['aggregations']['projects']['buckets']:
70+
projects.append(project['key'])
71+
return jsonify(projects)
72+
73+
74+
def get_projects_list():
75+
with billingbp.billingbp_context():
76+
es_resp = es.search(index='billing_idx', body={"query": {"match_all": {}}, "aggs": {
77+
"projects": {
78+
"terms": {
79+
"field": "project.keyword",
80+
"size": 9999
81+
}
82+
}
83+
}}, size=0)
84+
85+
projects = []
86+
for project in es_resp['aggregations']['projects']['buckets']:
87+
projects.append(project['key'])
88+
return projects
89+
90+
91+
def make_search_filter_query(timefrom, timetil, project):
92+
"""
93+
94+
:param timefrom: datetime object, filters all values less than this
95+
:param timetil: datetime object, filters all values greater than or equal to this
96+
:param project: string, this is the name of the particular project that we are trying to generate for
97+
:return:
98+
"""
99+
with billingbp.billingbp_context():
100+
timestartstring = timefrom.strftime('%Y-%m-%dT%H:%M:%S')
101+
timeendstring = timetil.strftime('%Y-%m-%dT%H:%M:%S')
102+
es_resp = es.search(index='billing_idx', body={
103+
"query": {
104+
"bool": {
105+
"must": [
106+
{
107+
"term": {
108+
"project.keyword": project
109+
}
110+
},
111+
{
112+
"nested": {
113+
"path": "specimen.samples.analysis",
114+
"score_mode": "max",
115+
"query": {
116+
"range": {
117+
"specimen.samples.analysis.timing_metrics.overall_stop_time_utc": {
118+
"gte": timestartstring,
119+
"lt": timeendstring,
120+
"format": "yyy-MM-dd'T'HH:mm:ss"
121+
}
122+
}
123+
}
124+
}
125+
}
126+
]
127+
}
128+
},
129+
"aggs": {
130+
"filtered_nested_timestamps": {
131+
"nested": {
132+
"path": "specimen.samples.analysis"
133+
},
134+
"aggs": {
135+
"filtered_range": {
136+
"filter": {
137+
"range": {
138+
"specimen.samples.analysis.timing_metrics.overall_stop_time_utc": {
139+
"gte": timestartstring,
140+
"lt": timeendstring,
141+
"format": "yyy-MM-dd'T'HH:mm:ss"
142+
}}
143+
},
144+
"aggs": {
145+
"vmtype": {
146+
"terms": {
147+
"field": "specimen.samples.analysis.host_metrics.vm_instance_type.raw",
148+
"size": 9999
149+
},
150+
"aggs": {
151+
"regions": {
152+
"terms": {
153+
"field": "specimen.samples.analysis.host_metrics.vm_region.raw",
154+
"size": 9999
155+
},
156+
"aggs": {
157+
"totaltime": {
158+
"sum": {
159+
"field": "specimen.samples.analysis.timing_metrics.overall_walltime_seconds"
160+
}
161+
}
162+
}
163+
}
164+
}
165+
}
166+
}
167+
}
168+
}
169+
}
170+
}
171+
}, size=9999)
172+
173+
return es_resp
174+
175+
176+
def get_previous_file_sizes(timeend, project):
177+
timeendstring = timeend.strftime('%Y-%m-%dT%H:%M:%S')
178+
es_resp = es.search(index='billing_idx', body={
179+
"query": {
180+
"bool": {
181+
"must": [
182+
{
183+
"term": {
184+
"project.keyword": project
185+
}
186+
},
187+
{
188+
"range": {
189+
"timestamp": {
190+
"lt": timeendstring,
191+
}
192+
}
193+
}
194+
]
195+
196+
}
197+
},
198+
"aggs": {
199+
"filtered_nested_timestamps": {
200+
"nested": {
201+
"path": "specimen.samples.analysis"
202+
},
203+
"aggs": {
204+
"sum_sizes": {
205+
"sum": {
206+
"field": "specimen.samples.analysis.workflow_outputs.file_size"
207+
}
208+
}
209+
}
210+
}
211+
}
212+
}, size=9999)
213+
return es_resp
214+
215+
216+
def get_months_uploads(project, timefrom, timetil):
217+
with billingbp.billingbp_context():
218+
timestartstring = timefrom.strftime('%Y-%m-%dT%H:%M:%S')
219+
timeendstring = timetil.strftime('%Y-%m-%dT%H:%M:%S')
220+
es_resp = es.search(index='billing_idx', body=
221+
{
222+
"query": {
223+
"bool": {
224+
"must": [
225+
{
226+
"range": {
227+
"timestamp": {
228+
"gte": timestartstring,
229+
"lt": timeendstring
230+
}
231+
}
232+
},
233+
{
234+
"term": {
235+
"project.keyword": project
236+
}
237+
}
238+
]
239+
}
240+
},
241+
"aggs": {
242+
"filtered_nested_timestamps": {
243+
"nested": {
244+
"path": "specimen.samples.analysis"
245+
},
246+
"aggs": {
247+
"times": {
248+
"terms": {
249+
"field": "specimen.samples.analysis.timestamp"
250+
},
251+
"aggs": {
252+
"sum_sizes": {
253+
"sum": {
254+
"field": "specimen.samples.analysis.workflow_outputs.file_size"
255+
}
256+
}
257+
}
258+
}
259+
}
260+
}
261+
}
262+
}, size=9999)
263+
return es_resp
264+
265+
266+
@click.command()
267+
@click.option("--date", default="", type=str)
268+
def generate_daily_reports(date):
269+
# Need to pass app context around because of how flask works
270+
# can take a single argument date as follows
271+
# flask generate_daily_reports --date 2017/01/31 will compute the billings for jan 2017, up to the 31st day of
272+
# January
273+
274+
with billingbp.billingbp_context():
275+
try:
276+
timeend = datetime.datetime.strptime(date, '%Y/%m/%d')
277+
except:
278+
timeend = datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
279+
280+
# HANDLE CLOSING OUT BILLINGS at end of month
281+
if timeend.day == 1:
282+
projects = get_projects_list()
283+
for project in projects:
284+
bill = Billing.query.filter(Billing.end_data.month == (timeend.month - 1) % 12) \
285+
.filter(Billing.closed_out is False).filter(Billing.project == project).first()
286+
if bill:
287+
bill.update(end_date=timeend, closed_out=True)
288+
289+
monthstart = timeend.replace(day=1)
290+
projects = get_projects_list()
291+
seconds_into_month = (timeend - monthstart).total_seconds()
292+
daysinmonth = calendar.monthrange(timeend.year, timeend.month)[1]
293+
portion_of_month = Decimal(seconds_into_month) / Decimal(daysinmonth * 3600 * 24)
294+
295+
for project in projects:
296+
print(project)
297+
file_size = get_previous_file_sizes(monthstart, project=project)
298+
this_months_files = get_months_uploads(project, monthstart, timeend)
299+
compute_cost_search = make_search_filter_query(monthstart, timeend, project)
300+
compute_costs = get_compute_costs(compute_cost_search)
301+
analysis_compute_json = create_analysis_costs_json(compute_cost_search['hits']['hits'], monthstart, timeend)
302+
303+
all_proj_files = get_previous_file_sizes(timeend, project)['hits']['hits']
304+
analysis_storage_json = create_storage_costs_json(all_proj_files, monthstart, timeend,
305+
daysinmonth * 3600 * 24)
306+
storage_costs = get_storage_costs(file_size, portion_of_month,
307+
this_months_files, timeend, daysinmonth * 3600 * 24)
308+
309+
bill = Billing.query.filter(Billing.project == project).filter(Billing.start_date == monthstart).first()
310+
itemized_costs = {
311+
"itemized_compute_costs": analysis_compute_json,
312+
"itemized_storage_costs": analysis_storage_json
313+
}
314+
if bill:
315+
bill.update(compute_cost=compute_costs, storage_cost=storage_costs, end_date=timeend,
316+
cost_by_analysis=itemized_costs)
317+
else:
318+
Billing.create(compute_cost=compute_costs, storage_cost=storage_costs, start_date=monthstart,
319+
end_date=timeend, project=project, closed_out=False,
320+
cost_by_analysis=itemized_costs)
321+
322+
# billingbp.cli.add_command(generate_daily_reports)
323+

0 commit comments

Comments
 (0)