Commit 6de2fa5e by kimth

LMS interface to xqueue in separate file

parent c7084e52
......@@ -24,6 +24,7 @@ import abc
# specific library imports
from calc import evaluator, UndefinedVariable
from correctmap import CorrectMap
from courseware import xqueue_interface
from util import *
from lxml import etree
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
......@@ -798,10 +799,10 @@ class SymbolicResponse(CustomResponse):
class CodeResponse(LoncapaResponse):
'''
Grade student code using an external server, called 'xqueue'
In contrast to ExternalResponse, CodeResponse has following behavior:
1) Goes through a queueing system
2) Does not do external request for 'get_answers'
Grade student code using an external queueing server, called 'xqueue'
External requests are only submitted for student submission grading
(i.e. and not for getting reference answers)
'''
response_tag = 'coderesponse'
......@@ -857,9 +858,33 @@ class CodeResponse(LoncapaResponse):
self.context.update({'submission': submission})
extra_payload = {'edX_student_response': submission}
r, queuekey = self._send_to_queue(extra_payload) # TODO: Perform checks on the xqueue response
# Prepare xqueue request
#------------------------------------------------------------
# Queuekey generation
h = hashlib.md5()
h.update(str(self.system.seed))
h.update(str(time.time()))
queuekey = h.hexdigest()
# Non-null CorrectMap['queuekey'] indicates that the problem has been submitted
# Generate header
xheader = xqueue_interface.make_xheader(lms_callback_url=self.system.xqueue_callback_url,
lms_key=queuekey,
queue_name=self.queue_name)
# Generate body
# NOTE: Currently specialized to 6.00x's pyxservers, which follows the ExternalResponse interface
contents = {'xml': etree.tostring(self.xml, pretty_print=True),
'edX_cmd': 'get_score',
'edX_tests': self.tests,
'processor': self.code,
'edX_student_response': submission}
# Submit request
xqueue_interface.send_to_queue(header=xheader,
body=json.dumps(contents))
# Non-null CorrectMap['queuekey'] indicates that the problem has been queued
cmap = CorrectMap()
cmap.set(self.answer_id, queuekey=queuekey, msg='Submitted to queue')
......@@ -883,7 +908,7 @@ class CodeResponse(LoncapaResponse):
self.context['correct'][0] = admap[ad]
# Replace 'oldcmap' with new grading results if queuekey matches.
# If queuekey does not match, we keep waiting for the score_msg that will match
# If queuekey does not match, we keep waiting for the score_msg whose key actually matchs
if oldcmap.is_right_queuekey(self.answer_id, queuekey):
msg = rxml.find('message').text.replace(' ', ' ')
oldcmap.set(self.answer_id, correctness=self.context['correct'][0], msg=msg, queuekey=None) # Queuekey is consumed
......@@ -892,8 +917,6 @@ class CodeResponse(LoncapaResponse):
return oldcmap
# CodeResponse differentiates from ExternalResponse in the behavior of 'get_answers'. CodeResponse.get_answers
# does NOT require a queue submission, and the answer is computed (extracted from problem XML) locally.
def get_answers(self):
anshtml = '<font color="blue"><span class="code-answer"><br/><pre>%s</pre><br/></span></font>' % self.answer
return {self.answer_id: anshtml}
......@@ -901,41 +924,6 @@ class CodeResponse(LoncapaResponse):
def get_initial_display(self):
return {self.answer_id: self.initial_display}
# CodeResponse._send_to_queue implements the same interface as defined for ExternalResponse's 'get_score'
def _send_to_queue(self, extra_payload):
# Prepare payload
xmlstr = etree.tostring(self.xml, pretty_print=True)
header = {'lms_callback_url': self.system.xqueue_callback_url,
'queue_name': self.queue_name,
}
# Queuekey generation
h = hashlib.md5()
h.update(str(self.system.seed))
h.update(str(time.time()))
queuekey = h.hexdigest()
header.update({'lms_key': queuekey})
body = {'xml': xmlstr,
'edX_cmd': 'get_score',
'edX_tests': self.tests,
'processor': self.code,
}
body.update(extra_payload)
payload = {'xqueue_header': json.dumps(header),
'xqueue_body' : json.dumps(body),
}
# Contact queue server
try:
r = requests.post(self.url, data=payload)
except Exception as err:
msg = "Error in CodeResponse %s: cannot connect to queue server url=%s" % (err, self.url)
log.error(msg)
raise Exception(msg)
return r, queuekey
#-----------------------------------------------------------------------------
......
#
# LMS Interface to external queueing system (xqueue)
#
import json
import requests
# TODO: Collection of parameters to be hooked into rest of edX system
XQUEUE_SUBMIT_URL = 'http://xqueue.edx.org/xqueue/submit/'
def upload_files_to_s3():
print ' THK: xqueue_interface.upload_files_to_s3'
def make_xheader(lms_callback_url, lms_key, queue_name):
'''
Generate header for delivery and reply of queue request.
Xqueue header is a JSON-serialized dict:
{ 'lms_callback_url': url to which xqueue will return the request (string),
'lms_key': secret key used by LMS to protect its state (string),
'queue_name': designate a specific queue within xqueue server, e.g. 'MITx-6.00x' (string)
}
'''
return json.dumps({ 'lms_callback_url': lms_callback_url,
'lms_key': lms_key,
'queue_name': queue_name })
def send_to_queue(header, body, xqueue_url=None):
'''
Submit a request to xqueue.
header: JSON-serialized dict in the format described in 'xqueue_interface.make_xheader'
body: Serialized data for the receipient behind the queueing service. The operation of
xqueue is agnostic to the contents of 'body'
'''
if xqueue_url is None:
xqueue_url = XQUEUE_SUBMIT_URL
# Contact queue server
payload = {'xqueue_header': header,
'xqueue_body' : body}
try:
r = requests.post(xqueue_url, data=payload)
except Exception as err:
msg = 'Error in xqueue_interface.send_to_queue %s: Cannot connect to server url=%s' % (err, xqueue_url)
raise Exception(msg)
#print r.text
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment