qubes.storage – Qubes data storage

Qubes provide extensible API for domains data storage. Each domain have multiple storage volumes, for different purposes. Each volume is provided by some storage pool. Qubes support different storage pool drivers, and it’s possible to register additional 3rd-party drivers.

Domain’s storage volumes:

  • root - this is where operating system is installed. The volume is available read-write to all domain classes. It could be made read-only for AppVM and DispVM to implement an untrusted storage domain in the future, but doing so will cause such VMs to set up a device-mapper based copy-on-write layer that redirects writes to the volatile volume. Whose storage driver may already do CoW, leading to an inefficient CoW-on-CoW setup. For this reason, root is currently read-write in all cases.
  • private - this is where domain’s data live. The volume is available read-write to all domain classes (including DispVM, but data written there is discarded on domain shutdown).
  • volatile - this is used for any data that do not to persist. This include swap, copy-on-write layer for a future read-only root volume etc.
  • kernel - domain boot files - operating system kernel, initial ramdisk, kernel modules etc. This volume is provided read-only and should be provided by a storage pool respecting qubes.vm.qubesvm.QubesVM.kernel property.

Storage pool concept

Storage pool is responsible for managing its volumes. Qubes have defined storage pool driver API, allowing to put domains storage in various places. By default three drivers are provided: qubes.storage.file.FilePool (named file), qubes.storage.reflink.ReflinkPool (named file-reflink), and qubes.storage.lvm.ThinPool (named lvm_thin). But the API allow to implement variety of other drivers (like additionally encrypted storage, external disk, drivers using special features of some filesystems, etc).

Most of storage API focus on storage volumes. Each volume have at least those properties:

  • rw - should the volume be available read-only or read-write to the domain
  • snap_on_start - should the domain start with its own state of the volume, or rather a snapshot of its template volume (pointed by a source property). This can be set to True only if a domain do have template property (AppVM and DispVM). If the domain’s template is running already, the snapshot should be made out of the template’s before its startup.
  • save_on_stop - should the volume state be saved or discarded on domain stop. In either case, while the domain is running, volume’s current state should not be committed immediately. This is to allow creating snapshots of the volume’s state from before domain start (see snap_on_start).
  • revisions_to_keep - number of volume revisions to keep. If greater than zero, at each domain stop (and if save_on_stop is True) new revision is saved and old ones exceeding revisions_to_keep limit are removed.
  • source - source volume for snap_on_start volumes
  • vid - pool specific volume identifier, must be unique inside given pool
  • pool - storage pool object owning this volume
  • name - name of the volume inside owning domain (like root, or private)
  • size - size of the volume, in bytes

Storage pool driver may define additional properties.

Storage pool driver API

Storage pool driver need to implement two classes:

Pool class should be registered with qubes.storage entry_point, under the name of storage pool driver. Volume class instances should be returned by qubes.storage.Pool.init_volume() method of pool class instance.

Methods required to be implemented by the pool class:
  • init_volume() - return instance of appropriate volume class; this method should not alter any persistent disk state, it is used to instantiate both existing volumes and create new ones
  • setup() - setup new storage pool
  • destroy() - destroy storage pool
Methods and properties required to be implemented by the volume class:
  • create() - create volume on disk
  • remove() - remove volume from disk
  • start() - prepare the volume for domain start; this include making a snapshot if snap_on_start is True
  • stop() - cleanup after domain shutdown; this include committing changes to the volume if save_on_stop is True
  • export() - return a path to be read to extract volume data; for complex formats, this can be a pipe (connected to some data-extracting process)
  • import_data() - return a path the data should be written to, to import volume data; for complex formats, this can be pipe (connected to some data-importing process)
  • import_data_end() - finish data import operation (cleanup temporary files etc); this methods is called always after import_data() regardless if operation was successful or not
  • import_volume() - import data from another volume
  • resize() - resize volume
  • revert() - revert volume state to a given revision
  • revisions - collection of volume revisions (to use with qubes.storage.Volume.revert())
  • is_dirty() - is volume properly committed after domain shutdown? Applies only to volumes with save_on_stop set to True
  • is_outdated() - have the source volume started since domain startup? applies only to volumes with snap_on_start set to True
  • config - volume configuration, this should be enough to later reinstantiate the same volume object
  • block_device() - return qubes.storage.BlockDevice instance required to configure volume in libvirt

Some storage pool drivers can provide limited functionality only - for example support only volatile volumes (those with snap_on_start is False, save_on_stop is False, and rw is True). In that case, it should raise NotImplementedError in qubes.storage.Pool.init_volume() when trying to instantiate unsupported volume.

Note that pool driver should be prepared to recover from power loss before stopping a domain - so, if volume have save_on_stop is True, and qubes.storage.Volume.stop() wasn’t called, next start() should pick up previous (not committed) state.

See specific methods documentation for details.

Module contents

Qubes storage system

exception qubes.storage.StoragePoolException[source]

Bases: qubes.exc.QubesException

A general storage exception

class qubes.storage.BlockDevice(path, name, script=None, rw=True, domain=None, devtype='disk')[source]

Bases: object

Represents a storage block device.

class qubes.storage.DirectoryThinPool[source]

Bases: object

The thin pool containing the device of given filesystem

classmethod thin_pool(dir_path)[source]

Thin tuple (volume group, pool name) containing given filesystem

class qubes.storage.Pool(name, revisions_to_keep=1, **kwargs)[source]

Bases: object

A Pool is used to manage different kind of volumes (File based/LVM/Btrfs/…).

3rd Parties providing own storage implementations will need to extend this class.

destroy()[source]

Called when removing the pool. Use this for implementation specific clean up.

get_volume(vid)[source]

Return a volume with vid from this pool

Raises:KeyError – if no volume is found
included_in(app)[source]

Check if this pool is physically included in another one

This works on best-effort basis, because one pool driver may not know all the other drivers.

Parameters:app – Qubes() object to lookup other pools in

:returns pool or None

init_volume(vm, volume_config)[source]

Initialize a qubes.storage.Volume from volume_config.

list_volumes()[source]

Return a list of volumes managed by this pool

setup()[source]

Called when adding a pool to the system. Use this for implementation specific set up.

config

Returns the pool config to be written to qubes.xml

size

Storage pool size in bytes, or None if unknown

usage

Space used in the pool in bytes, or None if unknown

volumes

Return a collection of volumes managed by this pool

class qubes.storage.Storage(vm)[source]

Bases: object

Class for handling VM virtual disks.

This is base class for all other implementations, mostly with Xen on Linux in mind.

attach(volume, rw=False)[source]

Attach a volume to the domain

clone(src_vm)[source]

Clone volumes from the specified vm

clone_volume(src_vm, name)[source]

Clone single volume from the specified vm

Parameters:
  • src_vm (QubesVM) – source VM
  • name (str) – name of volume to clone (‘root’, ‘private’ etc)

:return cloned volume object

create()[source]

Creates volumes on disk

detach(volume)[source]

Detach a volume from domain

export(volume)[source]

Helper function to export volume (pool.export(volume))

get_disk_utilization()[source]

Returns summed up disk utilization for all domain volumes

import_data(volume)[source]

Helper function to import volume data (pool.import_data(volume))

import_data_end(volume, success)[source]

Helper function to finish/cleanup data import (pool.import_data_end( volume))

init_volume(name, volume_config)[source]

Initialize Volume instance attached to this domain

remove()[source]

Remove all the volumes.

Errors on removal are catched and logged.

resize(volume, size)[source]

Resizes volume a read-writable volume

start()[source]

Execute the start method on each volume

stop()[source]

Execute the stop method on each volume

unused_frontend()[source]

Find an unused device name

verify()[source]

Verify that the storage is sane.

On success, returns normally. On failure, raises exception.

drive = None

Additional drive (currently used only by HVM)

kernels_dir

Directory where kernel resides.

If self.vm.kernel is None, the this points inside self.vm.dir_path

outdated_volumes

Returns a list of outdated volumes

used_frontends

Used device names

vm = None

Domain for which we manage storage

class qubes.storage.VmCreationManager(vm)[source]

Bases: object

A ContextManager which cleans up if volume creation fails.

class qubes.storage.Volume(name, pool, vid, revisions_to_keep=0, rw=False, save_on_stop=False, size=0, snap_on_start=False, source=None, **kwargs)[source]

Bases: object

Encapsulates all data about a volume for serialization to qubes.xml and libvirt config.

Keep in mind! volatile = not snap_on_start and not save_on_stop snapshot = snap_on_start and not save_on_stop origin = not snap_on_start and save_on_stop origin_snapshot = snap_on_start and save_on_stop

block_device()[source]

Return BlockDevice for serialization in the libvirt XML template as <disk>.

create()[source]

Create the given volume on disk.

This method is called only once in the volume lifetime. Before calling this method, no data on disk should be touched (in context of this volume).

This can be implemented as a coroutine.

export()[source]

Returns a path to read the volume data from.

Reading from this path when domain owning this volume is running (i.e. when is_dirty() is True) should return the data from before domain startup.

Reading from the path returned by this method should return the volume data. If extracting volume data require something more than just reading from file (for example connecting to some other domain, or decompressing the data), the returned path may be a pipe.

import_data()[source]

Returns a path to overwrite volume data.

This method is called after volume was already create()-ed.

Writing to this path should overwrite volume data. If importing volume data require something more than just writing to a file ( for example connecting to some other domain, or converting data on the fly), the returned path may be a pipe.

This can be implemented as a coroutine.

import_data_end(success)[source]

End the data import operation. This may be used by pool implementation to commit changes, cleanup temporary files etc.

This method is called regardless the operation was successful or not.

This can be implemented as a coroutine.

Parameters:success – True if data import was successful, otherwise False
import_volume(src_volume)[source]

Imports data from a different volume (possibly in a different pool.

The volume needs to be create()d first.

This can be implemented as a coroutine.

is_dirty()[source]

Return True if volume was not properly shutdown and committed.

This include the situation when domain owning the volume is still running.

is_outdated()[source]

Returns True if this snapshot of a source volume (for `snap_on_start`=True) is outdated.

remove()[source]

Remove volume.

This can be implemented as a coroutine.

resize(size)[source]

Expands volume, throws qubes.storage.StoragePoolException if given size is less than current_size

This can be implemented as a coroutine.

Parameters:size (int) – new size in bytes
revert(revision=None)[source]

Revert volume to previous revision

This can be implemented as a coroutine.

Parameters:revision – revision to revert volume to, see revisions
start()[source]

Do what ever is needed on start.

This include making a snapshot of template’s volume if snap_on_start is set.

This can be implemented as a coroutine.

stop()[source]

Do what ever is needed on stop.

This include committing data if save_on_stop is set.

This can be implemented as a coroutine.

verify()[source]

Verifies the volume.

This function is supposed to either return True, or raise an exception.

This can be implemented as a coroutine.

config

return config data for serialization to qubes.xml

name = None

Name of the volume in a domain it’s attached to (like root or private).

pool = None

Pool instance owning this volume

revisions

Returns a dict containing revision identifiers and time of their creation

rw = None

Should this volume be writable by domain.

save_on_stop = None

Should volume state be saved or discarded at stop()

size

Volume size in bytes

snap_on_start = None

Should the volume state be initialized with a snapshot of same-named volume of domain’s template.

source = None

source volume for snap_on_start volumes

usage = 0

disk space used by this volume, can be smaller than size for sparse volumes

vid = None

Volume unique (inside given pool) identifier

class qubes.storage.VolumesCollection(pool)[source]

Bases: object

Convenient collection wrapper for pool.get_volume and pool.list_volumes

keys()[source]

Return list of volume IDs

values()[source]

Return list of Volumes

qubes.storage.driver_parameters(name)[source]

Get __init__ parameters from a driver with out self & name.

qubes.storage.isodate(seconds)[source]

Helper method which returns an iso date

qubes.storage.pool_drivers()[source]

Return a list of EntryPoints names

qubes.storage.search_pool_containing_dir(pools, dir_path)[source]

Helper function looking for a pool containing given directory.

This is useful for implementing Pool.included_in method