GSoC'22 Final Report
Google Summer of Code 2022
Final Report: Adding container support to lcitool
Introduction
Lcitool is a tool used internally by libvirt developers to prepare and manage virtualized test environments as well as generate templates for containerized test environments i.e it provides the tooling and configuration for creating VM images and container templates. It is housed in the libvirt-ci repository which contains the tools and configuration for managing the CI needs of libvirt and related projects.
During the Google Summer of Code (GSoC) ‘22 program, I worked on adding support for spawning containers and running workloads inside containers (either using Podman or Docker). The goal was to initially achieve this using the podman-py Python binding but we had to pivot. The reason is clearly detailed in this blog post.
I kept a series of blog posts to show different issues encountered during the program, and how I approached them.
Work Done
Here is my GSoC 2022 branch documenting all my work that happened over the project time frame.
Over the last couple of months, I used lcitool's ability to generate container templates to extend lcitool further into building custom container images which can be used to test features in development among other use cases. The work done during the GSoC program makes it possible to quickly test during local development.
Scenario
My name is Foo, a libvirt developer. I am working on adding a new feature to libvirt which would require "xyz" dependencies in order to work. In order to test libvirt's build with this new feature, I would have to add the new set of dependencies into lcitool, generate the Dockerfile, build the image locally, and use the libvirt.git/ci/helper script to test if the build works properly
During the GSoC program, I added the ability to do all these using only lcitool. The way it works is to introduce the new dependencies to lcitool, generate the Dockerfile and run the workload all with lcitool. As opposed to the old way of introducing changes to lcitool, then waiting for review before going back to work on the feature, the whole process would be faster
This makes it possible to show how the dependencies introduced in lcitool actually work with the new libvirt feature rather than waiting for review before actually working on the said feature. It just makes working on features easier for the developer involved.
Overview
The functionalities mentioned above were achieved by introducing a Python subpackage called container
to lcitool.
This subpackage contains a Container class which has check_engine, build, and run methods for checking if an engine is available and running, building container images, and running workloads respectively.
The Container class is subclassed by Podman and Docker classes for podman and docker engines respectively. So, any interaction with podman engine is taken care of by the Podman class while the docker engine uses the Docker class.
The build and run methods use the subprocess
module in Python to run the respective engine commands.
The build method is used internally to do the equivalence of:
podman build -t $TAG -f $Dockerfile /tmp/random
OR
docker build -t $TAG -f $Dockerfile /tmp/random
The run method is used to run workloads in a container or get access to the shell provided by a container. It does this by doing the equivalence of:
podman run --rm -i {image} {command}
OR
docker run --rm -i {image} {command}
lcitool being a command line application uses this newly introduced container subpackage to add a new command called container. This container command contains three subcommands:
engines: This is used to list all the available and running container engines
lcitool container engines
run: Run workloads in a container
lcitool container run -h
shell: Get access to the shell in a running container
lcitool container shell -h
For more details, check
Key Design Decisions
Research of the podman-py library turned out to be a dead end with its limitations requiring us to look for an alternative way of addressing the GSoC assignment thereby switching from using podman-py to wrapping the respective engines’ command line argument (CLI).
Providing an argument for SCRATCH_DIR (which is mounted into the container) in the CLI. This argument would contain all the files and folders used for running workloads
Using the inbuilt DockerfileFormatter class in lcitool to generate Dockerfile which is used for building custom images.
Removing the instance variable which defines a temporary directory from Container class because it does not conform with lcitool as a future Python library. Rather, introduce it as an argument for Container’s methods.
When building container images, use tags to name images instead of using the returned id from the engine (Podman/Docker) error log.
Having an explicit syntax for “lcitool container run” and “lcitool container shell”.
STATUS UPDATE
Almost all the architectural decisions have been made, the commits below show the current state of the project. All the work required for the project has been done, few rounds of review have been carried out but it's still currently under the review process. At this time of submission, the project hasn't been merged into the main repository.
NB: Watch this Merge Request for an update on the project.
Core functionality
- lcitool: Add subpackage for containers
- Add abstract wrapper for docker and podman engine
- Add Exception handler for container
- Add exception class variable to container class
- Add logging for containers subpackage
- commandline: Addd argument for image to use
- commandline: Add engine optional argument
- commandline: Add scratch dir optional argument
- commandline: Add script optional argument
- lcitool: Add CONTAINER command
- containers: Implement “_exec” method
- container: Implement “check_engine” method
- lcitool container: Add engines sub-command
- containers: Get user entry from Unix database
- add engine arguments for running container
- add podman-specific user namespace manipulation
- containers: Add abstract options method
- containers: Implement “run” method
- lcitool container: Add “run” subcommand
- validate CLI arguments for “run&shell” subcommand
- lcitool container: Add “shell” subcommand
- containers: Implement “build” method
- lcitool: Add logic to build container image
Tests
- tests: check container engine subcommand
- tests: unit test for Container.check_engine
- tests: unit test for Podman().podman_args
- tests: unit test for Container.options function
Documentation
What I gained during the GSoC'22 program
During the three-month-long program, I learned a LOT which is detailed below:
Better understanding of Python.
My main work was done using the Python programming language. I have gotten better at writing Python sub-packages, understanding OOP (object-oriented programming), using logging to give a codebase a nice debug structure, contributing to other open-source projects, etc.
My project was initially supposed to use podman-py (a Python wrapper around Podman API service) to achieve all the container functionalities; I fixed a bug in the podman-py library while trying to use the tool before continuing with my project. I had to quickly understand an aspect of a new codebase, and make changes to it. That was a really massive step for me.
Also, with the guidance of my mentor, we did a lot of research and converted the research to a solution. This end-to-end approach to birthing a project was another major milestone I reached during the GSoC program.
Improve my approach to thinking about problems.
I started coding not necessarily to solve problems but just to code. The idea always resonated with me, and that was how I approached programming for a while. During this program, I learned more about thinking in relation to properly solving problems. All the review sessions were really helpful, especially the two listed below:
We were reviewing the approach I employed to solve a temporary file/folder issue. My idea was to get the content of a couple of files into a folder, then mount the files into the container. My initial approach was to open the file, read the content, copy the content of the file into a temporary file, then mount each individual temporary file into the container. This approach works but imagines if you had to do this same process for a lot of files, this would be really tasking. The better approach which my mentor suggested was to copy the files into a temporary directory (this approach alone is already efficient by cutting out the need to open, read then copy). This was one of the better moments that I really knew there should be an effective approach to how I solve problems.
An occurrence happened during another review session when we were discussing writing tests, especially unit tests. I had written a couple of tests for a particular function that was just a wrapper for a system function. The function copies a string into a file by using some inbuild Python module. I felt there was a need to test it because it's one of the functions I wrote towards solving the problem, but that shouldn't be the case because that would just be "duplicating work", and all the projects out there would be testing system calls.
Thinking about it further, I decided that going forward, I would carefully think about aspects of a program before writing tests i.e "Don't just write tests because I want to".
Time management.
Another important skill I learned during the program is how to properly manage my time in order to get work done. The weekly meetings we had every week to discuss the project progress, and the task for the coming week were the blueprint I needed. After the meetings, I made a mental structure of how I would use the next seven (7) days before the next meeting to progress with the project. It also really helped that the tasks weren't overwhelming and also weren't too easy; my mentor had just the right amount of work to keep me engaged till the next meeting. Thanks a lot, Erik.
This led me to develop an attitude for properly using my time to get work done. Like everything in life, I'm still not the master manager of time properly but I have taken a huge step forward and I'll take the positives.
How to be more productive using Vi
A few months ago, I had just started using Vi ( a text editor used for various purposes including programming). Like every new tool, I was just using it like a beginner; only scratching the surface of what the editor is capable of. In one of the Google Meet call with my mentor, he shared his screen and I noticed he was using Vi.
Having a lot of experience with the tool, he had a lot of Vi plugins that he had been using to aid productivity. He shared these plugins with me, and it made it more enjoyable to work with Vi.
Conclusion
All these is to show that over the last three months, I have developed my programming skill, improved my approach to thinking and solving problems, gotten better at sharing my thoughts, kept a blog post, and gotten better all around. Thanks a lot to Erik Skultety (skultety.erik@gmail.com) for his guidance throughout the program (you were a superb mentor), Libvirt organization (libvirt.org), and GSoC.
Working with Erik, and the project has inspired me to keep contributing to my baby (as Erik called the project) until it gets merged and really, continue contributing to open-source projects.
The skills I have gained from the project have given me the confidence to start applying for remote software engineering roles which is one of the plans post-GSoC.
Subscribe to my newsletter
Read articles from Apalowo Abdulwasiu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Apalowo Abdulwasiu
Apalowo Abdulwasiu
Hi, I'm a Software Engineer with interest in virtualization and container technologies. I also develop back-end applications and API with Python.