Monday 8 June 2015

GNS3 architecture and writing a new VM implementation

Last time I wrote a post I talked about what GNS3 does and how Docker fits into this. What I failed to mention and some you already familiar with GNS3 may know, the GNS3 software suite actually comes in two relatively separate parts:

  1. https://github.com/GNS3/gns3-server
  2. https://github.com/GNS3/gns3-gui
The GUI is a Qt-based management interface that sends HTTP requests to specific endpoints to the server defined in one of its files. These endpoints normally handle the basic stuff you would expect for VM instance to do: start/stop/suspend/restart/delete. For example, sending a POST request to /projects/{project_id}/virtualbox/vms creates a new Virtualbox instance handled in virtualbox_handler.py. You might run into some trouble getting the GUI to run, especially if you're using the latest development code like me because with the latest development version Qt4 was replaced with Qt5 and a lot of Linux distributions out there don't yet have Qt5 in their repositories. The installation instructions only deal with Qt4 and Ubuntu so it's up to you to trudge through numerous compile and requirement errors.

Generally, every virtualization technology (Dynamips, VirtualBox, QEMU) has the request handler, the VM manager responsible for managing available VM instances and the VM handler that knows how to start/stop/suspend/restart/delete. Going back to the request handler, if we wanted to start a previously created VM instance, sending a POST request to /projects/{project_id}/docker/images/{id}/start would do it. Once the request gets routed to a specific method, it usually fetches the singleton manager object responsible for that particular VM technology like VirtualBox or Docker that can fetch the Python object representing the VM instance based on the ID in the request. This VM instance object has the methods that can do various things with the instance, but are specific for VirtualBox or Docker or Qemu. 

Here are some important files that the current Docker implementation uses but there are equivalent files for other kinds of virtual devices:

SERVER
  • handlers/docker_handler.py - HTTP request handlers calling the Docker manager
  • modules/docker/__init__.py - Manager class that knows how to fetch and list Docker containers
  • modules/docker/docker_vm.py - Docker VM class whose methods manipulate specific containers
  • modules/docker/docker_error.py - Error class that mostly just overrides a base error class
  • schemas/docker.py - request schemas determining allowed and required arguments in requests
GUI
  • modules/docker/dialogs - folder containing code for GUI Qt dialogs
    • docker_vm_wizard.py - Wizard to create a new VM type, configure it and save it as a template from which the VM instances will be instantiated. Concretely, for Docker you choose from a list of available images from which a container will be created but this really depends on what you're using for virtualization.
  • modules/docker/ui - folder with Qt specifications that generate Python files that are then used to define how the GUI reacts to user interactions
  • modules/docker/pages - GUI pages and interactions are defined here by manipulating the previously generated Python Qt class objects
  • modules/docker/__init__.py - classes that handles the Docker specific functionality of the GUI  like loading and saving of settings
  • modules/docker/docker_vm.py - this part does the actual server requests and handles the responses
  • modules/docker/settings.py - general Docker and also container specific settings
This seems like quite a complicated setup but the important thing to remember is that if you want to add your own virtualization technology you have to make equivalent files to those above in a new folder. My advice is to copy the files of an already existing similar VM technology and go from there. All of the classes inherit from base classes that require some methods to exist, otherwise it will fail spectacularly. As is true with every object oriented language, you should try to leave most of the work to the base classes by overriding the required methods but if the methods they use make no sense, write custom code that circumvents their usage completely. A lot of the code used in one technology may seem useless and redundant in another. For example, VirtualBox has a lot of boilerplate code that manages the location of its vboxmanage command that's completely useless in Docker which uses docker-py to handle all container related actions. The core of GNS3 is written with modularity in mind but with all the (very) different virtualization technologies it supports you're bound to do a hack here or there if you don't want to completely retumble the rest of the code a couple of times.

Cheers until next time when I'll be talking about how to connect vastly different VMs via links.