Skip to content

Storage Subsystem Design R4 #1842

@kalkin

Description

@kalkin

Design

This is a deisign proposal for the storage subsystem for QubesOS R4.0. For previous discussions see also the PR: Allow defining and using custom Storage types

Requirements

  • Multiple storage implementations like file based (current storage method), LVM Thin Pools, Btrfs, ZFS ...
  • 3rd parties can develop custom storage implementations
  • uses entry-points for discovery of available storage backends make extensions discoverable by setuptools' entrypoints #1238
  • Be independent enough to work on it's own in a separate storage domain
  • The AppVM needs to be able to access a read only root image of the TempalteVM, so it can verify it.
  • Import/Export storage volumes between pools
  • Domains can reuse different pools for different StorageVolumes
  • Differentiate between permanent and temporary attached devices
  • Handle removable devices (or should we instead save permanent?)
  • Pool should know all devices it handles.
  • Add Volume.verify()
  • Update here documented Interfaces
  • Revert, list revert points

Volume

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

Interface

class Volume:
    devtype = 'disk'  # Used for libvirt <disk device="devtype">
    domain = None  # None is currently the same as dom0
    name = 'root' #  (root|private|volatile|linux-kernel|foo)
    pool = 'qubes_dom0'  # Pool name
    rw = True
    script = None  # Libvirt' s <disk><script /></disk>
    size = 0
    usage = 0
    vid = None # some kind of id must be unique in a pool. XenStorage uses path for that
    volume_type = 'read-write'  # (read-write|read-only|origin|snapshot|volatile)

    @property
    def config(self):
        ''' return config data for serialization to qubes.xml as (<volume_config>) '''
        return {'name': self.name,
                'pool': self.pool,
                'volume_type': self.volume_type}

    def block_device(self):
        ''' Return :py:class:`qubes.devices.BlockDevice` for serialization in
            the libvirt XML template as <disk>.
        '''
        return BlockDevice(self.path, self.name, self.script, self.rw,
                           self.domain, self.devtype)

Pool

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

Interface

3rd Parties providing own storage implementations will need to implement the following interface.

class Pool:
    def init(self, vm, name, **kwargs) # TODO Refactor vm out
    def init_volume(volume_config) # Creates a Volume object from given volume_config
    def create(self, volume)  # Create volume on disk
    def commit_template_changes(volume)
    def clone(source_volume, target_volume)
    def rename(volume, old_name, new_name)
    def start(volume)
    def stop(volume) 
    @property
    def volumes(self): # all volumes managed by this pool, TODO imlement this

Storage

The Storage class provides managment methods for domain's volumes. The method are called by the volume at the appropriate time. Currently it's in qubes/storage/__init__.py, but I'm considering to move it somewhere else, or make it a part of QubesVM, because most of the methods just iterate over self.vm.volumes and execute a method. See also my current Storage version

Interface

class Storage:
    def __init__(vm):
        for conf in vm.volumes_config:  # simplified!
            vm.volumes[conf['name']] = pool.init_volume(conf)

    def kernels_dir() # backward compatibility will be removed

    def create_on_disk(self)  # Create volumes on disk, TODO rename to create()
        os.makedirs(self.vm.dir_path)
        for volume in self.vm.volumes.values():
            self.get_pool(volume).create(volume)

        os.umask(old_umask)

    def commit_template_changes()
        for v in self.vm.volumes.values():
            if v.volume_type == 'origin':
                self.get_pool(v).commit_template_changes(v)
    def clone(self, src_vm)
    def rename(old_name, new_name)
    def start()
    def stop() 
    def verify_files(): # Just does some sanity checks currently.
    def get_pool(volume): # Helper method
        return qubes.storage.get_pool(volume, self.vm) 
       # The above will be replaced once vm is refactored out of the pool with: 
       return qubes.storage.get_pool(volume, vm.pool_config[volume.pool])

Further Details

  • QubesVM.volume_config would contain a dict {'volume_name':{config}} from the xml configuration for the current domain.
  • QubesVM.volumes is a dict containing { 'root_img' : Volume, 'private_img' : Volume, ...}
  • Qubes.pool_config contains the pool config parsed from qubes.xml. Will be replaced Qubes.pool_config with Qubes.pools containing *Pool

Open Questions

  • What should be the parameter to Volume.resize()? There might be volume implementations which have strategies for shrinking. Should resize also accept smaller sizes as the current one? Or should we even have extend() and shrink()

EDITS:

  • 2016-03-17 Change signature of Storage.import()
  • 2016-04-10 Document the current API

Metadata

Metadata

Assignees

No one assigned

    Labels

    C: coreThis issue pertains to a Qubes core component.C: docThis issue pertains to the official Qubes OS documentation.P: majorPriority: major. Between "default" and "critical" in severity.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions