data_export.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. # Copyright (C) 2014 Ion Torrent Systems, Inc. All Rights Reserved
  2. from __future__ import absolute_import
  3. import json
  4. import os
  5. import os.path
  6. import boto
  7. import boto.s3.key
  8. import celery.states
  9. import requests
  10. from celery.result import AsyncResult
  11. from celery.task import task
  12. from celery.utils.log import get_task_logger
  13. from django import forms
  14. from django.conf import settings
  15. from django.http import Http404, HttpResponse
  16. from django.shortcuts import get_object_or_404, redirect, render
  17. from django.views.decorators.http import require_POST
  18. from ion.utils import makeCSA
  19. from iondb.rundb import models
  20. from iondb.rundb.api import SupportUploadResource
  21. from iondb.utils import makePDF
  22. logger = get_task_logger(__name__)
  23. def list_export_uploads(request, tag):
  24. uploads = models.FileMonitor.objects.filter(tags__contains="upload").order_by(
  25. "-created"
  26. )
  27. if tag:
  28. uploads = uploads.filter(tags_contains=tag)
  29. ctx = {"uploads": uploads}
  30. return render(request, "rundb/data/data_export.html", ctx)
  31. def md5_stats_file(path):
  32. with open(path, "rb", 8192) as fp:
  33. digest_hex, diges_64, size = boto.s3.key.compute_md5(fp)
  34. return digest_hex, diges_64, size
  35. @task
  36. def export_upload_file(monitor_id):
  37. monitor = models.FileMonitor.objects.get(pk=monitor_id)
  38. full = monitor.full_path()
  39. monitor.status = "Checking"
  40. monitor.save()
  41. if not os.path.exists(full):
  42. logger.error("OS Error in file uploader")
  43. monitor.status = "Error: file does not exist"
  44. monitor.save()
  45. return
  46. digest_hex, diges_64, size = md5_stats_file(full)
  47. monitor.size = size
  48. monitor.md5sum = digest_hex
  49. monitor.url = "{0}:{1}".format(monitor.name, monitor.md5sum)
  50. monitor.status = "Connecting"
  51. monitor.save()
  52. try:
  53. con = boto.connect_s3(settings.AWS_ACCESS_KEY, settings.AWS_SECRET_KEY)
  54. bucket = con.get_bucket(settings.AWS_BUCKET_NAME)
  55. key = bucket.get_key(monitor.url)
  56. if key is not None:
  57. monitor.status = "Complete"
  58. monitor.save()
  59. return
  60. key = bucket.new_key(monitor.url)
  61. except Exception as err:
  62. logger.exception("Connecting error")
  63. monitor.status = "Error: {0}".format(err)
  64. monitor.save()
  65. return
  66. monitor.status = "Uploading"
  67. monitor.save()
  68. # Rewrite this into a class or a callable object
  69. # last_time = time.time()
  70. def get_progress(current, total):
  71. # now = time.time()
  72. # if now - last_time >= 0.5:
  73. monitor.progress = current
  74. monitor.save()
  75. # last_time = now
  76. try:
  77. key.set_contents_from_filename(
  78. full, cb=get_progress, num_cb=1000, md5=(digest_hex, diges_64)
  79. )
  80. except Exception as err:
  81. logger.exception("Uploading error")
  82. monitor.status = "Error: Uploading {0}".format(err)[:60]
  83. monitor.save()
  84. return
  85. monitor.progress = monitor.size
  86. monitor.status = "Complete"
  87. monitor.save()
  88. def export_upload_report(request):
  89. try:
  90. report_pk = int(request.POST.get("report"))
  91. except ValueError:
  92. raise Http404("'{0}' is not a valid report ID".format(report_pk))
  93. path = request.POST.get("file_path")
  94. report = get_object_or_404(models.Results, pk=report_pk)
  95. root = report.get_report_dir()
  96. full_path = os.path.join(root, path)
  97. if not os.path.exists(full_path):
  98. raise Http404(
  99. "'{0}' does not exist as a file in report {1}".format(path, report_pk)
  100. )
  101. tag = "report/{0}/".format(report_pk)
  102. monitor = models.FileMonitor(
  103. local_dir=os.path.dirname(full_path),
  104. name=os.path.basename(full_path),
  105. tags="upload," + tag,
  106. status="Queued",
  107. )
  108. monitor.save()
  109. result = export_upload_file.delay(monitor.id)
  110. monitor.celery_task_id = result.task_id
  111. monitor.save()
  112. return redirect("list_export_uploads", tag="")
  113. class SupportUploadForm(forms.Form):
  114. contact_email = forms.EmailField(required=True)
  115. description = forms.CharField(required=True)
  116. result = forms.IntegerField(required=True)
  117. @task
  118. def filemonitor_errback(task_id, monitor_pk):
  119. try:
  120. monitor = models.FileMonitor.objects.get(pk=monitor_pk)
  121. except Exception:
  122. logger.exception("Monitor error callback failed for pk={0}".format(monitor_pk))
  123. return
  124. monitor.status = "Error"
  125. monitor.save()
  126. @task
  127. def generate_csa(result_pk, monitor_pk=None):
  128. result = models.Results.objects.get(pk=result_pk)
  129. report_dir = result.get_report_dir()
  130. raw_data_dir = result.experiment.expDir
  131. try:
  132. monitor = models.FileMonitor.objects.get(pk=monitor_pk)
  133. except models.FileMonitor.DoesNotExist:
  134. monitor = models.FileMonitor()
  135. monitor.tags = "generate_csa"
  136. csa_file_name = "csa_{0:04d}.zip".format(int(result_pk))
  137. monitor.status = "Generating"
  138. monitor.local_dir = report_dir
  139. monitor.name = csa_file_name
  140. monitor.save()
  141. # Generate report PDF file.
  142. # This will create a file named report.pdf in results directory
  143. makePDF.write_report_pdf(result_pk)
  144. csa_path = makeCSA.makeCSA(report_dir, raw_data_dir, monitor.name)
  145. digest_hex, digest_64, size = md5_stats_file(csa_path)
  146. monitor.md5sum = digest_hex
  147. monitor.size = size
  148. monitor.status = "Generated"
  149. monitor.save()
  150. def get_ts_info():
  151. path = "/etc/torrentserver/tsconf.conf"
  152. d = dict()
  153. if os.path.exists(path):
  154. for l in open(path):
  155. row = map(str.strip, l.split(":", 1))
  156. if len(row) == 2:
  157. d[row[0]] = row[1]
  158. return d
  159. def make_auth_header(account):
  160. return {"Authorization": "Bearer {0}".format(account.access_token)}
  161. @task
  162. def is_authenticated(account_pk):
  163. account = models.RemoteAccount.objects.get(pk=account_pk)
  164. if account.has_access():
  165. url = settings.SUPPORT_AUTH_URL
  166. auth_header = make_auth_header(account)
  167. info = get_ts_info()
  168. params = {
  169. "version": info.get("version", "Version Missing"),
  170. "serial_number": info.get("serialnumber", "Serial Missing"),
  171. }
  172. try:
  173. response = requests.get(
  174. url, params=params, headers=auth_header, verify=False
  175. )
  176. except requests.exceptions.RequestException as err:
  177. logger.error("Request Exception: {0}".format(err))
  178. else:
  179. if response.ok:
  180. return True
  181. else:
  182. logger.error("Authentication failure at {0}: {1}".format(url, response))
  183. return False
  184. @task
  185. def check_authentication(support_upload_pk):
  186. upload = models.SupportUpload.objects.get(pk=support_upload_pk)
  187. upload.local_status = "Authenticating"
  188. upload.save()
  189. if is_authenticated(upload.account_id):
  190. upload.local_status = "Authenticated"
  191. upload.save()
  192. return True
  193. else:
  194. upload.local_status = "Access Denied"
  195. upload.save()
  196. return False
  197. @task
  198. def upload_to_support(support_upload_pk):
  199. upload = models.SupportUpload.objects.select_related("file", "account").get(
  200. pk=support_upload_pk
  201. )
  202. upload.local_status = "Uploading"
  203. upload.save()
  204. url = settings.SUPPORT_UPLOAD_URL
  205. auth_header = make_auth_header(upload.account)
  206. info = get_ts_info()
  207. form_data = {
  208. "contact_email": upload.contact_email,
  209. "description": upload.description,
  210. "version": info.get("version", "Version Missing"),
  211. "serial_number": info.get("serialnumber", "Serial Missing"),
  212. }
  213. path = upload.file.full_path()
  214. files = {"file": open(path, "rb")}
  215. try:
  216. response = requests.post(
  217. url, data=form_data, files=files, headers=auth_header, verify=False
  218. )
  219. except Exception as err:
  220. upload.local_status = "Error"
  221. upload.local_message = str(err)
  222. else:
  223. if response.ok:
  224. try:
  225. tick = response.json()
  226. except ValueError:
  227. tick = {}
  228. upload.local_status = "Complete"
  229. upload.ticket_id = tick.get("ticket_id", "None")
  230. upload.ticket_status = tick.get("ticket_status", "Remote Error")
  231. upload.ticket_message = tick.get(
  232. "ticket_message",
  233. "There was an error in the support server. Your Torrent Server is working fine, and you should contact your support representative.",
  234. )
  235. else:
  236. upload.local_status = "Error"
  237. upload.local_message = response.reason
  238. finally:
  239. upload.save()
  240. @task(max_retries=120)
  241. def check_and_upload(support_upload_pk, auth_task, gen_task):
  242. auth = AsyncResult(auth_task)
  243. gen_csa = AsyncResult(gen_task)
  244. if not (auth.ready() and gen_csa.ready()):
  245. check_and_upload.retry(countdown=1)
  246. elif auth.status == "SUCCESS" and gen_csa.status == "SUCCESS":
  247. if auth.result:
  248. upload_to_support(support_upload_pk)
  249. else:
  250. return
  251. @require_POST
  252. def report_support_upload(request):
  253. form = SupportUploadForm(request.POST)
  254. # check for existing support upload
  255. data = {"created": False}
  256. account = models.RemoteAccount.objects.get(remote_resource="support.iontorrent")
  257. if not account.has_access():
  258. data["error"] = "invalid_auth"
  259. if not form.is_valid():
  260. data["error"] = "invalid_form"
  261. data["form_errors"] = form.errors
  262. if "error" not in data:
  263. result_pk = form.cleaned_data["result"]
  264. support_upload = (
  265. models.SupportUpload.objects.filter(result=result_pk)
  266. .order_by("-id")
  267. .first()
  268. )
  269. if not support_upload:
  270. data["created"] = True
  271. support_upload = models.SupportUpload(
  272. account=account,
  273. result_id=result_pk,
  274. user=request.user,
  275. local_status="Preparing",
  276. contact_email=form.cleaned_data["contact_email"],
  277. description=form.cleaned_data["description"],
  278. )
  279. support_upload.save()
  280. async_result = AsyncResult(support_upload.celery_task_id)
  281. if (
  282. not support_upload.celery_task_id
  283. or async_result.status in celery.states.READY_STATES
  284. ):
  285. if not support_upload.file:
  286. monitor = models.FileMonitor()
  287. monitor.save()
  288. support_upload.file = monitor
  289. support_upload.save()
  290. support_upload.file.status = ("Queued",)
  291. support_upload.file.tags = "support_upload,generate_csa"
  292. support_upload.file.save()
  293. generate = generate_csa.s(result_pk, support_upload.file.pk)
  294. errback = filemonitor_errback.s(support_upload.file.pk)
  295. gen_result = generate.apply_async(link_error=errback)
  296. support_upload.file.celery_task_id = gen_result.task_id
  297. auth_result = check_authentication.delay(support_upload.pk)
  298. upload_result = check_and_upload.apply_async(
  299. (support_upload.pk, auth_result.task_id, gen_result.task_id),
  300. countdown=1,
  301. )
  302. support_upload.celery_task_id = upload_result.task_id
  303. support_upload.save()
  304. resource = SupportUploadResource()
  305. uri = resource.get_resource_uri(support_upload)
  306. data["resource_uri"] = uri
  307. # make CSA
  308. # check auth
  309. # start upload task
  310. # respond with support upload object for JS polling
  311. return HttpResponse(
  312. json.dumps(data, indent=4, sort_keys=True), mimetype="application/json"
  313. )