qubes.storage
– Qubes data storage¶
Qubes OS provides extensible API for domains data storage. Each domain has multiple storage volumes for different purposes. Each volume is provided by some storage pool. Qubes OS supports different storage pool drivers, and it’s possible to register additional 3rd-party drivers.
Domain’s storage volumes:
root - this is where the operating system is installed. The volume is available in read-write mode to all domain classes. In order to implement an untrusted storage domain, in the future, it could be made read-only for
AppVM
andDispVM
. Doing so, however, will cause such VMs to set up a device-mapper based copy-on-write layer redirecting writes to the volatile volume, whose storage driver may already be doing 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 is located. The volume is available in read-write mode to all domain classes (including
DispVM
, but the data written there is discarded on domain shutdown).volatile - this is used for any non-persistent data. This includes 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 in read-only mode and should be provided by a storage pool respecting
qubes.vm.qubesvm.QubesVM.kernel
property.
Storage pool concept¶
The storage pool is responsible for managing its volumes. Qubes OS has a
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 allows to implement a variety of other drivers (like additionally
encrypted storage, external disk, drivers using special features of some
filesystems, etc.)
Most of the storage API is focused on storage volumes. Each volume has at least those properties:
rw
- should the volume be available in read-only or read-write mode to the domain
snap_on_start
- should the domain start with its own state of the volume, or rather with a snapshot of its template volume (pointed by asource
property). This can be set to True only if a domain has a template property (AppVM and DispVM). If the domain’s template is already running, the snapshot should be made out of the template’s before its startup.
save_on_stop
- should the volume’s state be saved or discarded on domain’s stop. In either case, while the domain is running, volume’s current state should not be committed immediately. This allows to create snapshots of the volume’s state from before domain’s start (seesnap_on_start
).
revisions_to_keep
- the number of volume revisions to keep. If greater than zero, at each domain stop (and ifsave_on_stop
is True) a new revision is saved and old ones exceeding therevisions_to_keep
limit are removed. This defaults torevisions_to_keep
.
source
- source volume forsnap_on_start
volumes
vid
- pool specific volume identifier, must be unique inside a 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
ephemeral
- whether the volume is automatically encrypted with an ephemeral key. This can be set only on volumes which have bothsnap_on_start
andsave_on_stop
set to False, namely - volatile volume. This property of DispVM’s volatile volume is inherited from the template (but not for other types of VMs). For volatile volumes, this property defaults toephemeral_volatile
.
The storage pool driver may define additional properties.
Storage pool driver API¶
- The storage pool driver needs to implement two classes:
pool class - inheriting from
qubes.storage.Pool
volume class - inheriting from
qubes.storage.Volume
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 onessetup()
- setup new storage pooldestroy()
- destroy storage pool
- Methods and properties required to be implemented by the volume class:
create()
- create volume on diskremove()
- remove volume from diskstart()
- prepare the volume for domain start; this include making a snapshot ifsnap_on_start
is Truestop()
- cleanup after domain shutdown; this include committing changes to the volume ifsave_on_stop
is Trueexport()
- return a path to be read to extract volume data; for complex formats, this can be a pipe (connected to some data-extracting process)export_end()
- cleanup after exporting the data; this function is called when the path returned byexport()
is not used anymore. This method optional - some storage drivers may not implement it if not needed.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 afterimport_data()
regardless if operation was successful or notimport_volume()
- import data from another volumeresize()
- resize volumerevert()
- revert volume state to a given revisionrevisions
- collection of volume revisions (to use withqubes.storage.Volume.revert()
)is_dirty()
- is volume properly committed after domain shutdown? Applies only to volumes withsave_on_stop
set to Trueis_outdated()
- have the source volume started since domain startup? applies only to volumes withsnap_on_start
set to Trueconfig
- volume configuration, this should be enough to later reinstantiate the same volume objectblock_device()
- returnqubes.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:
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
- class qubes.storage.Pool(*, name, revisions_to_keep=1, ephemeral_volatile=False)[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.
- async destroy()[source]¶
Called when removing the pool. Use this for implementation specific clean up.
This can be implemented as a coroutine.
- 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.
- async setup()[source]¶
Called when adding a pool to the system. Use this for implementation specific set up.
This can be implemented as a coroutine.
- property config¶
Returns the pool config to be written to qubes.xml
- property size¶
Storage pool size in bytes, or None if unknown
- property usage¶
Space used in the pool in bytes, or None if unknown
- property usage_details¶
Detailed information about pool usage as a dictionary Contains data_usage for usage in bytes and data_size for pool size; other implementations may add more implementation-specific detail
- property 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.
- block_devices()[source]¶
Return all
qubes.storage.BlockDevice
for current domain for serialization in the libvirt XML template as <disk>.
- async clone_volume(src_vm, name)[source]¶
Clone single volume from the specified vm
- Parameters:
:return cloned volume object
- async export_end(volume, export_path)[source]¶
Cleanup after exporting data from the volume
- Parameters:
volume – volume that was exported
export_path – path returned by the export() call
- async import_data(volume, size)[source]¶
Helper function to import volume data.
- Size:
new size in bytes, or None if using old size
- async verify()[source]¶
Verify that the storage is sane.
On success, returns normally. On failure, raises exception.
- drive¶
Additional drive (currently used only by HVM)
- property kernels_dir¶
Directory where kernel resides.
If
self.vm.kernel
isNone
, the this points insideself.vm.dir_path
- property outdated_volumes¶
Returns a list of outdated volumes
- property used_frontends¶
Used device names
- vm¶
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, ephemeral=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>.
- async 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.
- encrypted_volume_path(qube_name, device_name)[source]¶
Find the name of the encrypted volatile volume
- async 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.
This can be implemented as a coroutine.
- async export_end(path)[source]¶
Cleanup after exporting data.
This method is called after exporting the volume data (using
export()
), when the path is not needed anymore.This can be implemented as a coroutine.
- Parameters:
path – path to cleanup, returned by
export()
- async import_data(size)[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.
- Parameters:
size (int) – size of new data in bytes
- async 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
- async 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.
- make_encrypted_device(device, qube_name)[source]¶
Takes
BlockDevice
and returns its encrypted version for serialization in the libvirt XML template as <disk>. The qube name is available to help construct the device path.
- async resize(size)[source]¶
Expands volume, throws
qubes.storage.StoragePoolException
if given size is less than current_sizeThis can be implemented as a coroutine.
- Parameters:
size (int) – new size in bytes
- async 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
- async 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.
- async start_encrypted(name)[source]¶
Start a volume encrypted with an ephemeral key. This can be implemented as a coroutine.
The default implementation of this method uses
cryptsetup(8)
with a key taken from/dev/urandom
. This is highly secure and works with any storage pool implementation. Volume implementations should override this method if they can provide a secure and more efficient implementation.
- async 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.
- async stop_encrypted(name)[source]¶
Stop an encrypted, ephemeral volume. This can be implemented as a coroutine.
The default implementation of this method uses
cryptsetup(8)
. Volume implementations that overridestart_encrypted()
MUST override this method as well.
- async verify()[source]¶
Verifies the volume.
This function is supposed to either return
True
, or raise an exception.This can be implemented as a coroutine.
- property config¶
return config data for serialization to qubes.xml
- property ephemeral¶
Should this volume be encrypted with an ephemeral key in dom0 (if enabled with encrypted_volatile property)?
- name¶
Name of the volume in a domain it’s attached to (like root or private).
- property revisions¶
Returns a dict containing revision identifiers and time of their creation
- rw¶
Should this volume be writable by domain.
- property size¶
Volume size in bytes
- snap_on_start¶
Should the volume state be initialized with a snapshot of same-named volume of domain’s template.
- source¶
source volume for
snap_on_start
volumes
- vid¶
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