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
andDispVM
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 domainsnap_on_start
- should the domain start with its own state of the volume, or rather a snapshot of its template volume (pointed by asource
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 (seesnap_on_start
).revisions_to_keep
- number of volume revisions to keep. If greater than zero, at each domain stop (and ifsave_on_stop
is True) new revision is saved and old ones exceedingrevisions_to_keep
limit are removed. This defaults torevisions_to_keep
.source
- source volume forsnap_on_start
volumesvid
- pool specific volume identifier, must be unique inside given poolpool
- storage pool object owning this volumename
- name of the volume inside owning domain (like root, or private)size
- size of the volume, in bytesephemeral
- whether volume is automatically encrypted with an ephemeral key. This can be set only on volumes that have bothsnap_on_start
andsave_on_stop
set to False - namely, volatile volume. This property for DispVM’s volatile volume is inherited from the template (but not for other types of VMs). For volatile volumes, this property defaults toephemeral_volatile
.
Storage pool driver may define additional properties.
Storage pool driver API¶
- Storage pool driver need to implement two classes:
- pool class - inheriting from
qubes.storage.Pool
- volume class - inheriting from
qubes.storage.Volume
- pool class - inheriting from
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:
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
-
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.
-
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.
-
setup
()[source]¶ Called when adding a pool to the system. Use this for implementation specific set up.
This can be implemented as a coroutine.
-
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
-
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
-
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>.
-
clone_volume
(src_vm, name)[source]¶ Clone single volume from the specified vm
Parameters: :return cloned volume object
-
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
-
import_data
(volume, size)[source]¶ Helper function to import volume data.
Size: new size in bytes, or None if using old size
-
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
isNone
, the this points insideself.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, 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>.
-
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
-
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.
-
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()
-
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
-
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.
-
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.
-
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
-
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.
-
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.
-
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.
-
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.
-
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
-
ephemeral
¶ Should this volume be encrypted with an ephemeral key in dom0 (if enabled with encrypted_volatile property)?
-
name
= None¶ Name of the volume in a domain it’s attached to (like root or private).
-
revisions
¶ Returns a dict containing revision identifiers and time of their creation
-
rw
= None¶ Should this volume be writable by domain.
-
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
-
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