Autolab Development Blog Read on for release news, feature spotlights, development insights, and more! https://autolab.github.io/ Mon, 23 Apr 2018 03:59:16 +0000 Mon, 23 Apr 2018 03:59:16 +0000 Jekyll v3.7.3 Autolab and Docker <p><img src="/assets/docker-autolab.svg" alt="" /></p> <p>Autolab started in 2010 and had a very humble beginning. It was used for only one class with ~300 students and simply ran on one laptop! Following that, its merits were realized by other courses at Carnegie Mellon resulting in nothing short of a <em>success catastrophe</em>: ~3000 students use it every semester, 77 courses have used Autolab as of Fall 2014 and autograding jobs are run on five Dell R410 machines with 2x Intel E5520 CPUS and 8 Nehalem cores.</p> <p>While it’s really fun to mess around with these sweet, kickass machines, we realized we have to snap back to reality and think about a better way to run autograding jobs. This post outlines some basic autograding concepts, some problems we faced with the existing model and finally describes how Docker is integrated and how we hope it solves all our problems!</p> <h1 id="what-it-takes-to-autograde">What it takes to Autograde</h1> <p><strong>Terminology:</strong> Tango is the autograder used by Autolab.</p> <p>A guarantee that Tango must provide is that the environment in which a job runs remains homogeneous between jobs. Hence, Tango executes these jobs on virtual machines that are booted with user-specified images. In order to run jobs on a particular platform such as Amazon EC2, OpenStack or even RackSpace, the Virtual Machine Management (VMM) interface must be implemented for that platform. The lifecycle of a job is governed by the <a href="https://github.com/autolab/Tango/blob/master/vmms/localDocker.py">VMM interface</a> outlined below:</p> <p><img src="/assets/autograde.svg" alt="" /></p> <ul> <li> <p><code class="highlighter-rouge">initializeVM</code> - Initializes a VM for that platform.</p> </li> <li> <p><code class="highlighter-rouge">waitVM</code> - Wait for a VM to be accessible</p> </li> <li> <p><code class="highlighter-rouge">copyIn</code> - Copy all necessary input files to run the job.</p> </li> <li> <p><code class="highlighter-rouge">runJob</code> - Invoke the autodriver to run the job. The autodriver instruments the job, runs it in a controlled environment and restores the system state after job completion.</p> </li> <li> <p><code class="highlighter-rouge">copyOut</code> - Copy feedback file from the VM.</p> </li> <li> <p><code class="highlighter-rouge">destroyVM</code> - Destroy a given VM.</p> </li> <li> <p><code class="highlighter-rouge">getVMs</code> - Get a list of VMs in each VM pool.</p> </li> </ul> <p>At present, the VMM interface has been implemented for any local Linux machine and Amazon EC2. In order to execute a job, Tango’s job manager must check if there is an available VM to run the job (<code class="highlighter-rouge">getVMs</code>), ensure that the VM is reachable (<code class="highlighter-rouge">waitVM</code>), followed by performing the <code class="highlighter-rouge">copyIn</code>, <code class="highlighter-rouge">runJob</code> and <code class="highlighter-rouge">copyOut</code> operations.</p> <h1 id="integrating-docker">Integrating Docker</h1> <p>Docker containers provide process, network and memory isolation. They are also designed to be multi-tenant citizens on any host they run on. A combination of these properties makes Docker containers ideal for running autograding jobs. Furthermore, clients have the flexibility of <a href="https://docs.docker.com/userguide/dockerimages/">building and running jobs in any Docker image</a> with customized software.</p> <p>In the context of Docker, here is what the VMM interface does under the hood:</p> <ul> <li> <p><code class="highlighter-rouge">initializeVM</code>: Assign a host machine for container to run on. Use round-robin for assignment.</p> </li> <li> <p><code class="highlighter-rouge">waitVM</code>: Ensure host machine is reachable by SSH.</p> </li> <li> <p><code class="highlighter-rouge">copyIn</code>: Create a directory on host machine and copy all input files to that directory.</p> </li> <li><code class="highlighter-rouge">runJob</code>: Start a Docker container with the given Docker image and mount the previously created directory as a volume on to that container. Then, run the job and write the feedback file to that volume.</li> <li> <p><code class="highlighter-rouge">copyOut</code>: Copy feedback file from the volume to Tango’s directory of feedback files.</p> </li> <li> <p><code class="highlighter-rouge">destroyVM</code>: Destroy the Docker container (docker rm CONTAINER) and its corresponding volume directory on this host machine.</p> </li> <li><code class="highlighter-rouge">getVMs</code> - Get a list of volume directories that are currently used.</li> </ul> <p>Clearly, implementing the VMM interface using Docker containers has greatly simplified each function! With this simpler approach, getting an end-to-end instance of Autolab with autograding is significantly simpler. Be sure to check out the <a href="https://github.com/autolab/Tango/wiki/Tango-with-Docker">wiki</a> for specific setup instructions.</p> <h1 id="next-steps">Next Steps</h1> <p>Implementing the Docker VMMS was the easy part. What keeps most engineers awake at night is of course testing. While we have tested this implementation by running specific labs hundreds of times simultaneously, we know that is not enough. We are currently working on writing A/B tests that will run an incoming job on both Docker and Tashi. We hope this will give us some clarity on the correctness of this approach. Maybe then, we can think about performance testing.</p> <p>In the meantime, we’d love to hear any feedback, suggestions, criticisms, adulations or rants about what we have done so far and what you think would help us next.</p> <p>Thanks for reading!</p> Mon, 25 May 2015 05:40:07 +0000 https://autolab.github.io/2015/05/autolab-and-docker/ https://autolab.github.io/2015/05/autolab-and-docker/ autograding docker tango Making Autolab's Backend Scalable <p><a href="https://github.com/autolab/Tango">Tango</a> is a stand-alone, RESTful service that Autolab uses as the back-end for autograding. Tango receives grading jobs from Autolab’s front-end, adds them to a job queue, assigns them to available containers for grading and shepherds the job through the process. In its early days, Tango was mostly used for jobs that ran in under 5 seconds. However, over the recent semesters, Autolab has grown to host classes like Distributed Systems, Machine Learning and Storage Systems–classes with significantly higher compute requirements. As we looked into how to handle larger loads, we were running into the problem of how to manage queued jobs and distribute them to different instances in our back-end. To this end, we decided to take the initial step for turning Tango into a distributed system by implementing a persistent memory using Redis and using the producer consumer model.</p> <p><img src="/assets/redis1.svg" alt="Tango's initial Architecture" /> <em>Initial Tango architecture with in-memory job queue</em></p> <p>The existing monolithic model with a single process and an in-memory queue was posing multiple problems:</p> <ul> <li>Running a web server that handled large file uploads concurrently along with a job manager that dispatched jobs from the job queue to new threads made the process prone to crashes.</li> <li>Crashes caused tall the jobs stored in the in-memory queue to disappear.</li> <li>After a crash, all the containers running a job would suddenly be left in a so-called limbo state because they are no longer managed by any Tango process.</li> </ul> <p>Therefore we decided to improve our architecture by switching to multi-process model with a persistent queue.</p> <h2 id="persistent-memory-model-using-redis">Persistent Memory Model using Redis</h2> <p>The in-memory job queue was the barrier to making the system robust. We want to store jobs on an independent system and make it <em>persistent</em>.</p> <p><img src="/assets/redis2.svg" alt="Tango with Persistent Memory Model" /> <em>Tango with Persistent Memory Model</em></p> <p>In this persistent memory model, even if Tango restarts it will keep the same job queue. We could even spin up multiple instances of Tango and they can all work concurrently, sharing the same queue (with a couple problems which I will mention in the next section).</p> <h3 id="choice-of-redis">Choice of Redis</h3> <p>We started looking into possible solutions: traditional relational databases like MySQL, document-oriented databases such as MongoDB, messaging queues such as RabbitMQ and key-value stores such as Redis. Looking at the amount of data and the level of nestedness, we decided that we don’t need a database with full-range of functionality and robustness. We needed something fast and simple to use. Our old job queue was just a simple Python dictionary; therefore, a fast key-value store like <em>Redis</em> seemed like the right fit. Setting it up and playing around with it was a breeze, therefore we decided to go with this option.</p> <h3 id="implementation">Implementation</h3> <p>While making the switch, we wanted to keep the ability to run Tango without a dependency on the external service in order to keep backwards compatibility and simplify setting up a local development environment.</p> <p>In order to achieve this we created (pseudo)abstract classes that include the common methods and decide on what is used under the hood based on the configuration.</p> <p>Since Python doesn’t really have abstract classes, we have define methods that would initiate the appropriate class and return it.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This is an abstract class that decides on </span> <span class="c"># if we should initiate a TangoRemoteDictionary or TangoNativeDictionary</span> <span class="c"># Since there are no abstract classes in Python, we use a simple method</span> <span class="k">def</span> <span class="nf">TangoDictionary</span><span class="p">(</span><span class="n">object_name</span><span class="p">):</span> <span class="k">if</span> <span class="n">Config</span><span class="o">.</span><span class="n">USE_REDIS</span><span class="p">:</span> <span class="k">return</span> <span class="n">TangoRemoteDictionary</span><span class="p">(</span><span class="n">object_name</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="n">TangoNativeDictionary</span><span class="p">()</span> </code></pre></div></div> <p><a href="https://github.com/autolab/Tango/blob/master/tangoObjects.py#L232">See the full code here.</a></p> <p>In the future, we can have other implementations, such as MongoDB, and just add it as an option here. For example:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">TangoDictionary</span><span class="p">(</span><span class="n">object_name</span><span class="p">):</span> <span class="k">if</span> <span class="n">Config</span><span class="o">.</span><span class="n">USE_REDIS</span><span class="p">:</span> <span class="k">return</span> <span class="n">TangoRemoteDictionary</span><span class="p">(</span><span class="n">object_name</span><span class="p">)</span> <span class="k">elif</span> <span class="n">Config</span><span class="o">.</span><span class="n">USE_MONGO</span><span class="p">:</span> <span class="k">return</span> <span class="n">TangoMongoDictionary</span><span class="p">(</span><span class="n">object_name</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="n">TangoNativeDictionary</span><span class="p">()</span> </code></pre></div></div> <h3 id="serialization-and-marshalling">Serialization and Marshalling</h3> <p>Redis, being a key-value store, only accepts strings as values. Even though the objects we are trying to store are quite simple and flat, we do not want to create a new key-value pair for every attribute of the class. Instead we decided to serialize what we call the ‘remote objects’ using the <code class="highlighter-rouge">pickle</code> module included in the standard python library.</p> <p>A sample method to put the object into the store looks like</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">,</span> <span class="n">obj</span><span class="p">):</span> <span class="n">pickled_obj</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">r</span><span class="o">.</span><span class="n">hset</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">hash_name</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="nb">id</span><span class="p">),</span> <span class="n">pickled_obj</span><span class="p">)</span> <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span> </code></pre></div></div> <p>To get it back you would need:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span> <span class="n">unpickled_obj</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">r</span><span class="o">.</span><span class="n">hget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">hash_name</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="nb">id</span><span class="p">))</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">unpickled_obj</span><span class="p">)</span> <span class="k">return</span> <span class="n">obj</span> </code></pre></div></div> <p>Pickling seemed like a good solution at first, but when we needed to nest remote objects, e.g. put <em>TangoMachine</em>s into the remote <em>TangoQueue</em>, we ran into the problem of <code class="highlighter-rouge">self.r</code>(type of <em>redis.StrictRedis</em>) not being serializable. To get around this, we defined custom <strong>getstate</strong> and <strong>setstate</strong> methods that would not include self.r attribute in the serialized string, but set it back when the object is deserialized.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">__setstate__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">__db</span><span class="o">=</span> <span class="n">getRedisConnection</span><span class="p">()</span> <span class="bp">self</span><span class="o">.</span><span class="n">__dict__</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="nb">dict</span><span class="p">)</span> </code></pre></div></div> <p>Details of the implementation can be found on <a href="https://github.com/autolab/Tango/blob/master/tangoObjects.py">TangoObjects module</a>.</p> <h2 id="producer-consumer-model">Producer Consumer Model</h2> <p>So far we have covered how Tango receives grading jobs and stores them. The second most important part of the process is to read from the queue and distribute the jobs to the available containers(cluster of Virtual Machine or Docker). In the initial architecture, the consumer was part of the HTTP server process. A single Tango process would both produce and consume the jobs.</p> <p>Having a shared queue gave us the ability to run multiple <em>producers</em> as explained in the previous section. However, we only needed one <em>consumer</em> to distribute the jobs.</p> <p>Therefore, we decided to separate the consumer from the HTTP server (producer) as a standalone process.</p> <p><img src="/assets/redis3.svg" alt="Tango with Prod/Com Model" /></p> <p><em>Tango with Prod/Com processes and Persistent Memory Model</em></p> <p>The advantage of this architecture is that we can launch an arbitrary number of HTTP servers as we receive more load. There would be only one consumer process which is responsible for reading from the queue and assigning the jobs. This process gives us a lot of flexibility because it can be run on any node, as well as be stopped and migrated to a different machine at any time.</p> <hr /> <p>We are currently testing the new architecture and will be posting more about the outcomes as it gets used by more users and we come across interesting cases.</p> <p>Please feel free to ask questions in the comments below!</p> Fri, 03 Apr 2015 00:00:00 +0000 https://autolab.github.io/2015/04/making-backend-scalable/ https://autolab.github.io/2015/04/making-backend-scalable/ tango redis scalability Autolab: Autograding for All <p>Let’s face it: <strong>teaching is hard</strong>. There’s the obvious time commitment required for classes and lectures, but striving for excellence in education necessarily requires countless hours spent outside of the classroom. This time generally gets split between</p> <ul> <li>developing curriculum and assignments to stay relevant,</li> <li>meeting with students (in office hours, breakout sessions, tutoring, etc.),</li> <li>answering questions (whether in person or remotely),</li> <li>grading assignments, quizzes, and tests,</li> </ul> <p>and many more areas. This list doesn’t even begin to capture the inherently difficult problem of adapting to meet students’ unique needs, a challenge which grows exponentially the more students there are.</p> <p>Looking at that list of challenges above, one entry stands out: grading. Very few people sit down to grade assignments with enthusiasm and vigor, which is unfortunate; students can’t learn from their mistakes until they know what they are. The time required to grade fundamentally opposes student learning and growth.</p> <p>Teaching is hard, but at Autolab we help <strong>make it easier</strong>.</p> <h1 id="enhancing-learning">Enhancing Learning</h1> <p>For programming classes, we drastically cut down the submission-to-feedback time for students by exploiting autograding: programs evaluating other programs. In this model, teachers create autograder programs that define a number of tests to run on a student’s submission to assign it a grade.</p> <p>Autolab aims for flexibility: by utilizing user-customized Linux VMs, instructors have absolute control over their autograding environment. Autograders can be written in any language, using any software packages, frameworks, compilers, databases—the possibilities are endless. No matter the subject, Autolab can help make autograding a reality.</p> <p>Because of this flexibility, students can receive feedback on their assignments nearly instantaneously, closing the feedback loop in minutes rather than weeks. This opens the door to iterative learning: students are alerted to incorrect solutions immediately, enabling them to hone in on and fix troubling mistakes in their code. Over the course of one assignment, this feedback loop can run any number of times; each time, the student learns something above what they would have had the assignment been graded manually.</p> <h1 id="fostering-community">Fostering Community</h1> <p>In addition to kindling learning among individual students, we at Autolab aim to foster a number of communities in all aspects of our work.</p> <h2 id="community-among-classmates">Community among Classmates</h2> <p>Autolab provides class-wide scoreboards for autograded assignments. Scoreboards are a fun and powerful motivation for students, and an excellent way to build community. For the students at the top of the class, scoreboards encourage refinement and healthy competition to keep up engagement. For other students, scoreboards help to clearly outline what’s required for full credit. Of course, all names are anonymized by student-selected nicknames—some secretive and others clever. In our experience, a mix of curiosity and competitiveness foster a positive community everyone wins, regardless of skill level.</p> <h2 id="community-among-educators">Community among Educators</h2> <p>Because autograders are self-contained, Autolab can provide a new community for instructors collaborating around designing, building, and iterating on quality assignments and labs. In the future, we hope to develop a platform where instructors can upload, discuss, share, and develop these assignments. This would help foster a community where educators can help each other improve their students’ learning.</p> <h2 id="community-among-users">Community among Users</h2> <p>We’re dedicated to this mission of fostering community, so it was only natural when it came to the decision of <a href="https://github.com/autolab/Autolab">open-sourcing Autolab</a>. We actively seek feedback from our users about how to improve, and we welcome contributions! Head on over to <a href="https://github.com/autolab/Autolab">GitHub</a> to browse the project source, read up on documentation, report issues, or open pull requests. If you notice something out-of-place or are dying to see a particular feature, please let us know! We also actively read mail at the Autolab Dev mailing list (see the footer). We love helping out as much as possible; this is only possible with your input.</p> <h1 id="autograding-for-all">Autograding for All</h1> <p>Our vision is to bring Autolab and the benefits of autograding to all programming and computer science classes, at the secondary- and university-levels. At Carnegie Mellon, where Autolab was initially conceived and is currently developed, we’ve seen Autolab’s success in the classroom. Each semester, we reach 3,000 undergraduate computer science students, amounting to over 100,000 autograded jobs every semester.</p> <p>Despite this, we’ve only scratched the surface of autograding’s potential. If you’re interested in using Autolab for your class or school, reach out and we’d be happy to help you get up and running. Also be sure to check out the <a href="https://github.com/autolab/Autolab">source and documentation on GitHub</a>.</p> Thu, 26 Mar 2015 08:14:30 +0000 https://autolab.github.io/2015/03/autolab-autograding-for-all/ https://autolab.github.io/2015/03/autolab-autograding-for-all/ release