Creating a Model Component for a New Image

For a quick refresher on what a model component is, see Model Components. You can also review the model components that were created in the ACME repository. In the model component that this tutorial builds, we are going to focus on create a model component that provides objects to plugins.

Generating a Model Component Skeleton

Create a directory for our model component within a repository and then run the firewheel mc generate function with the appropriate parameters. Since we’re building an Ubuntu Server it makes sense to place this model component in the linux repository:

$ mkdir -p /opt/firewheel/model_components/linux/ubuntu/cosmic
$ mv ubuntu-18.10-server-amd64.qcow2.xz /opt/firewheel/model_components/linux/ubuntu/cosmic
$ firewheel mc generate --name linux.ubuntu1810 \
    --location /opt/firewheel/linux/ubuntu/cosmic \
    --image ubuntu-18.10-server-amd64.qcow2.xz \
    --arch x86_64 \
    --model_component_depends linux.ubuntu \
    --model_component_objects model_component_objects.py

We’ll walk through the parameters:

  • name: Choose the name of the model component. In this case we use linux.ubuntu1810.

  • location: The absolute path the directory that should contain this model component.

  • image: The path (relative to the model component) to the image file that was created in Building a New Image from ISO. In this case it is expected that the disk image lives within the cosmic directory.

  • model_component_depends: We want to be able to use all the functions that a base Linux VM provides, therefore we depend on linux.base_objects.

  • model_component_objects: This specifies the name of the file that holds the objects that we want to provide to a topology. It can be named anything you like, but we have found that using model_component_objects.py is a reasonable convention to follow.

As mentioned above, this model component assumes that the ubuntu-18.10-server-amd64.qcow2.xz file is located within the cosmic directory. It is best to have the image within the model component because then everything associated with the component is in one place. You should now see all the relevant model component files in /opt/firewheel/linux/ubuntu/cosmic.

Creating the Ubuntu1810Server object

The MANIFEST file defined that the model component objects are located in a file called model_component_objects.py. Create that file to look as follows:

from firewheel.control.experiment_graph import require_class
from linux.ubuntu import UbuntuServer

@require_class(UbuntuServer)
class Ubuntu1810Server(object):
    def __init__(self):
        try:
            self.vm
        except AttributeError:
            self.vm = {}

        if 'architecture' not in self.vm:
            self.vm['architecture'] = 'x86_64'
        if 'vcpu' not in self.vm:
            self.vm['vcpu'] = {
                'model': 'qemu64',
                'sockets': 1,
                'cores': 1,
                'threads': 1
            }
        if 'mem' not in self.vm:
            self.vm['mem'] = 256
        if 'drives' not in self.vm:
            self.vm['drives'] = [
                {
                    'db_path': 'ubuntu-18.10-server-amd64.qcow2.xz',
                    'file': 'ubuntu-18.10-server-amd64.qcow2'
                }
            ]
        if 'vga' not in self.vm:
            self.vm['vga'] = 'std'

        self.set_image('ubuntu1810server')

Let’s walk through the object. First, we import two things, require_class and UbuntuServer. The require_class function is a decorator that we use to indicate inter-class dependencies. So, in this case, we’re saying that the Ubuntu1810Server class requires the UbuntuServer class. You can think of this as reverse inheritance. In inheritance, the object being instantiated gets to run first and passes control to its parent through a call to super(). When using the require_class decorator it gives control to the object that is required before handing it over to the object being instantiated. Similar to inheritance, any function that is defined in UbuntuServer is available to Ubuntu1810Server. This allows us to create “layers” that can be “stacked” in different ways to make complex objects. It also allows us to define functions at an appropriate layer so that they can have the widest possible usage. Therefore, UbuntuServer has functions that are relevant to any image that uses Ubuntu Server as well as any model components that are higher in the stack. An example of such a function is configure_ips(). The configure_ips() function in the LinuxHost object will ensure that the correct commands get run on a Linux VM to set its IP address. This differs from the configure_ips() function in the WindowsHost object since the mechanism for setting an IP on Windows is different than on Linux. The UbuntuServer class requires UbuntuHost which requires LinuxHost Therefore, we can require the LinuxHost class from many different objects and duplicating effort. We recommend that you use the most specific class to enable all available functions. For example, the UbuntuHost, which UbuntuServer requires, contains the install_debs() function which installs Debian packages.

Next, we check to see if the vm variable already exists on the object. Remember, decorators are reversed from inheritance in the sense that the required classes are executed first. Therefore, we have to be careful not to accidentally undo things previous “layers” did unless it is intended. Once the vm dictionary is present, we set parameters such as architecture, vcpu, mem, drives, and vga. Feel free to change any of these values to fit the needs of the image that is being created. Note that db_path needs to be the same as the paths parameter that was set in the MANIFEST file. The file parameter allows the name of the decompressed disk to be something different than the base name of the compressed disk, although it is doubtful that they’ll be different in most cases (other than the xz suffix of course).

The set_image() function takes an arbitrary string. Feel free to set it to anything that you like, but it is good convention to make it consistent with the name of the class. This value tells FIREWHEEL that the VM decorated by this class does not need a default image because we’re explicitly setting its image type in the class.

At this point this class is usable within a topology. All that a topology would need to do is import this class via from linux.ubuntu1810 import Ubuntu1810Server and to decorate a vertex with the Ubuntu1810Server class. Don’t forget to add linux.ubuntu1810 as a model component dependency in the topology model component’s MANIFEST file.