qubes: more documentation and doc fixes
This commit is contained in:
parent
5d9b92a039
commit
e83d21c671
@ -1,15 +1,59 @@
|
|||||||
:py:mod:`qubes.vm` -- Different Virtual Machine types
|
:py:mod:`qubes.vm` -- Different Virtual Machine types
|
||||||
=====================================================
|
=====================================================
|
||||||
|
|
||||||
|
Qubes is composed of several virtual machines that are interconnected in
|
||||||
|
several ways. From now on they will be called „domains”, as they may not
|
||||||
|
actually be true virtual machines -- we plan to support LXC containers for
|
||||||
|
example. Because of Xen-only legacy of Qubes code, it is custom to refer to them
|
||||||
|
in long/plural as ``domains`` and in short/singular as ``vm``.
|
||||||
|
|
||||||
|
|
||||||
|
Domain object
|
||||||
|
-------------
|
||||||
|
|
||||||
|
There are couple of programming objects that refer to domain. The main is the
|
||||||
|
instance of :py:class:`qubes.vm.QubesVM`. This is the main „porcelain” object,
|
||||||
|
which carries other objects and supplies convenience methods like
|
||||||
|
:py:meth:`qubes.vm.qubesvm.QubesVM.start`. This class is actually divided in
|
||||||
|
two, the :py:class:`qubes.vm.qubesvm.QubesVM` cares about Qubes-specific
|
||||||
|
actions, that are more or less directly related to security model. It is
|
||||||
|
intended to be easily auditable by non-expert programmers (ie. we don't use
|
||||||
|
Python's magic there). The second class is its parent,
|
||||||
|
:py:class:`qubes.vm.BaseVM`, which is concerned about technicalities like XML
|
||||||
|
serialising/deserialising. It is of less concern to threat model auditors, but
|
||||||
|
still relevant to overall security of the Qubes OS. It is written for
|
||||||
|
programmers by programmers.
|
||||||
|
|
||||||
|
The second object is the XML node that refers to the domain. It can be accessed
|
||||||
|
as :py:attr:`Qubes.vm.BaseVM.xml` attribute of the domain object. The third one
|
||||||
|
is :py:attr:`Qubes.vm.qubesvm.QubesVM.libvirt_domain` object for directly
|
||||||
|
interacting with libvirt. Those objects are intended to be used from core and/or
|
||||||
|
plugins, but not directly by user or from qvm-tools. They are however public, so
|
||||||
|
there are no restrictions.
|
||||||
|
|
||||||
|
|
||||||
|
Domain classes
|
||||||
|
--------------
|
||||||
|
|
||||||
|
There are several different types of VM, because not every Qubes domain is equal
|
||||||
|
-- some of them perform specific functions, like NetVM; others have different
|
||||||
|
life cycle, like DisposableVM. For that, different domains have different Python
|
||||||
|
classes. They are all defined in this package, generally one class per module,
|
||||||
|
but some modules contain private globals that serve this particular class.
|
||||||
|
|
||||||
|
|
||||||
|
Package contents
|
||||||
|
----------------
|
||||||
|
|
||||||
Main public classes
|
Main public classes
|
||||||
-------------------
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. autoclass:: qubes.vm.BaseVM
|
.. autoclass:: qubes.vm.BaseVM
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
Helper classes and functions
|
Helper classes and functions
|
||||||
----------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. autoclass:: qubes.vm.BaseVMMeta
|
.. autoclass:: qubes.vm.BaseVMMeta
|
||||||
:members:
|
:members:
|
||||||
@ -24,7 +68,7 @@ Helper classes and functions
|
|||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
Particular VM classes
|
Particular VM classes
|
||||||
---------------------
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Main types:
|
Main types:
|
||||||
|
|
||||||
|
212
doc/qubes.rst
212
doc/qubes.rst
@ -1,6 +1,218 @@
|
|||||||
:py:mod:`qubes` -- Common concepts
|
:py:mod:`qubes` -- Common concepts
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
|
Global Qubes object
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Because all objects in Qubes' world are interconnected, there is no possibility
|
||||||
|
to instantiate them separately. They are all loaded together and contained in
|
||||||
|
the one ``app`` object, an instance of :py:class:`qubes.Qubes` class.
|
||||||
|
|
||||||
|
The loading from XML is done in stages, because Qubes domains are dependent on
|
||||||
|
each other in what can be even a circular dependency. Therefore some properties
|
||||||
|
(especcialy those that refer to another domains) are loaded later. Refer to
|
||||||
|
:py:class:`qubes.Qubes` class documentation to get description of every stage.
|
||||||
|
|
||||||
|
|
||||||
|
Properties
|
||||||
|
----------
|
||||||
|
|
||||||
|
Many parameters of Qubes can be changed -- from names of particular domains to
|
||||||
|
default NetVM for all AppVMs. All of those *configurable* parameters are called
|
||||||
|
*properties* and can be accessed like Python attributes on their owners::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> app = qubes.Qubes()
|
||||||
|
>>> app.domain[0] # docutils: +ELLIPSIS
|
||||||
|
<AdminVM ...>
|
||||||
|
>>> app.domain[0].name
|
||||||
|
'dom0'
|
||||||
|
|
||||||
|
Definition
|
||||||
|
^^^^^^^^^^
|
||||||
|
Properties are defined on global :py:class:`qubes.Qubes` application object and
|
||||||
|
on every domain. Those classess inherit from :py:class:`PropertyHolder` class,
|
||||||
|
which is responsible for operation of properties.
|
||||||
|
|
||||||
|
Each Qubes property is actually a *data descriptor* (a Python term), which means
|
||||||
|
they are attributes of their classess, but when trying to access it from
|
||||||
|
*instance*, they return it's underlying value instead. They can be thought of as
|
||||||
|
Python's builtin :py:class:`property`, but greatly enhanced. They are defined in
|
||||||
|
definition of their class::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop')
|
||||||
|
>>> instance = MyTestHolder()
|
||||||
|
>>> instance.testprop = 'aqq'
|
||||||
|
>>> instance.testprop
|
||||||
|
'aqq'
|
||||||
|
|
||||||
|
If you like to access some attributes of the property *itself*, you should refer
|
||||||
|
to instance's class::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop')
|
||||||
|
>>> instance = MyTestHolder()
|
||||||
|
>>> instance.testprop = 'aqq'
|
||||||
|
|
||||||
|
>>> type(instance.testprop)
|
||||||
|
<type 'str'>
|
||||||
|
>>> type(instance.__class__.testprop)
|
||||||
|
<class 'qubes.property'>
|
||||||
|
|
||||||
|
>>> instance.__class__.testprop.__name__
|
||||||
|
'testprop'
|
||||||
|
|
||||||
|
As a rule, properties are intended to be serialised and deserialised to/from XML
|
||||||
|
file. There are very few exceptions, but if you don't intend to save the
|
||||||
|
property to XML, you should generally go for builtin :py:class:`property`.
|
||||||
|
|
||||||
|
One important difference from builtin properties is that there is no getter
|
||||||
|
function, only setter. In other words, they are not dynamic, you cannot return
|
||||||
|
different value every time something wants to access it. This is to ensure that
|
||||||
|
while saving the property is not a moving target.
|
||||||
|
|
||||||
|
|
||||||
|
Property's properties
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
You can specify some parameters while defining the property. The most important
|
||||||
|
is the `type`: on setting, the value is coerced to this type. It is well suited
|
||||||
|
to builtin types like :py:class:`int`::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop')
|
||||||
|
>>> intprop = qubes.property('intprop', type=int)
|
||||||
|
|
||||||
|
>>> instance = MyTestHolder()
|
||||||
|
>>> instance.testprop = '123'
|
||||||
|
>>> instance.intprop = '123'
|
||||||
|
|
||||||
|
>>> instance.testprop
|
||||||
|
'123'
|
||||||
|
>>> instance.intprop
|
||||||
|
123
|
||||||
|
|
||||||
|
|
||||||
|
Every property should be documented. You should add a short description to your
|
||||||
|
property, which will appear, among others, in :program:`qvm-prefs` and
|
||||||
|
:program:`qvm-ls` programs. It should not use any Sphinx-specific markup::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop',
|
||||||
|
>>> doc='My new and shiny property.')
|
||||||
|
>>> MyTestHolder.testprop.__doc__
|
||||||
|
'My new and shiny property.'
|
||||||
|
|
||||||
|
|
||||||
|
In addition to `type`, properties also support `setter` parameter. It acts
|
||||||
|
similar to `type`, but is always executed (not only when types don't agree) and
|
||||||
|
accepts more parameters: `self`, `prop` and `value` being respectively: owners'
|
||||||
|
instance, property's instance and the value being set. There is also `saver`,
|
||||||
|
which does reverse: given value of the property it should return a string that
|
||||||
|
can be parsed by `saver`.
|
||||||
|
|
||||||
|
|
||||||
|
Unset properties and default values
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Properties may be unset, even if they are defined (that is, on access they raise
|
||||||
|
:py:exc:`AttributeError` -- that is the normal Python way to tell that the
|
||||||
|
attribute is absent). You can manually unset a property using Python's ``del``
|
||||||
|
statement::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop')
|
||||||
|
>>> instance = MyTestHolder()
|
||||||
|
>>> instance.testprop
|
||||||
|
AttributeError: ...
|
||||||
|
>>> instance.testprop = 123
|
||||||
|
>>> instance.testprop
|
||||||
|
123
|
||||||
|
>>> del instance.testprop
|
||||||
|
>>> instance.testprop
|
||||||
|
AttributeError: ...
|
||||||
|
|
||||||
|
Alternatively, some properties may return some other value instead, if that's
|
||||||
|
the reasonable thing to do. For example, when
|
||||||
|
:py:attr:`qubes.vm.qubesvm.QubesVM.netvm` is unset, we check global setting
|
||||||
|
:py:attr:`qubes.Qubes.default_netvm` instead. Returning :py:obj:`None` as
|
||||||
|
default would be wrong, as it is marker that means „no NetVM, machine
|
||||||
|
disconnected”.
|
||||||
|
|
||||||
|
You can define a default value either as constant or as a callable. In the
|
||||||
|
second case, the callable should accept one argument, the instance that owns the
|
||||||
|
property::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop')
|
||||||
|
>>> def1prop = qubes.property('testprop', default=123)
|
||||||
|
>>> netvm = qubes.property('testprop',
|
||||||
|
>>> default=(lambda self: self.app.default_netvm))
|
||||||
|
|
||||||
|
>>> instance = MyTestHolder()
|
||||||
|
>>> instance.testprop
|
||||||
|
AttributeError: ...
|
||||||
|
>>> instance.def1prop
|
||||||
|
123
|
||||||
|
>>> instance.netvm # doctest: +SKIP
|
||||||
|
<NetVM ...>
|
||||||
|
|
||||||
|
|
||||||
|
Setting netvm on particular domain of course does not affect global default, but
|
||||||
|
only this instance. But there are two problems:
|
||||||
|
|
||||||
|
- You don't know if the value of the property you just accessed was it's
|
||||||
|
true or default value.
|
||||||
|
- After ``del``'ing a property, you still will get a value on access. You
|
||||||
|
cannot count on `AttributeError` raised from them.
|
||||||
|
|
||||||
|
Therefore Qubes support alternative semantics. You can (and probably should,
|
||||||
|
wherever applicable) use no ``del``, but assignment of special magic object
|
||||||
|
:py:obj:`qubes.property.DEFAULT`. There is also method
|
||||||
|
:py:meth:`qubes.PropertyHolder.property_is_default`, which can be used to
|
||||||
|
distinguish unset from set properties::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop', default=123)
|
||||||
|
>>> instance.testprop
|
||||||
|
123
|
||||||
|
>>> instance.property_is_default('testprop')
|
||||||
|
True
|
||||||
|
>>> instance.testprop = 123
|
||||||
|
>>> instance.testprop
|
||||||
|
>>> instance.property_is_default('testprop')
|
||||||
|
False
|
||||||
|
>>> instance.testprop = qubes.property.DEFAULT
|
||||||
|
>>> instance.property_is_default('testprop')
|
||||||
|
True
|
||||||
|
|
||||||
|
|
||||||
|
Inheritance
|
||||||
|
^^^^^^^^^^^
|
||||||
|
Properties in subclassess overload properties from their parents, like
|
||||||
|
expected::
|
||||||
|
|
||||||
|
>>> import qubes
|
||||||
|
>>> class MyTestHolder(qubes.PropertyHolder):
|
||||||
|
>>> testprop = qubes.property('testprop')
|
||||||
|
|
||||||
|
>>> class MyOtherHolder(MyTestHolder):
|
||||||
|
>>> testprop = qubes.property('testprop', setter=qubes.property.forbidden)
|
||||||
|
|
||||||
|
>>> instance = MyOtherHolder()
|
||||||
|
>>> instane.testprop = 123
|
||||||
|
TypeError: ...
|
||||||
|
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
.. automodule:: qubes
|
.. automodule:: qubes
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
@ -153,7 +153,7 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
#: dictionary of services that are run on this domain
|
#: dictionary of services that are run on this domain
|
||||||
self.services = services or {}
|
self.services = services or {}
|
||||||
|
|
||||||
#: :py:class`DeviceManager` object keeping devices that are attached to
|
#: :py:class:`DeviceManager` object keeping devices that are attached to
|
||||||
#: this domain
|
#: this domain
|
||||||
self.devices = DeviceManager(self) if devices is None else devices
|
self.devices = DeviceManager(self) if devices is None else devices
|
||||||
|
|
||||||
@ -294,7 +294,7 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
|
|
||||||
:param str ip: IP address of the frontend
|
:param str ip: IP address of the frontend
|
||||||
:param str mac: MAC (Ethernet) address of the frontend
|
:param str mac: MAC (Ethernet) address of the frontend
|
||||||
:param qubes.vm.QubesVM backend: Backend domain
|
:param qubes.vm.qubesvm.QubesVM backend: Backend domain
|
||||||
:rtype: lxml.etree._Element
|
:rtype: lxml.etree._Element
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@ -314,7 +314,7 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
|
|
||||||
:param str ip: IP address of the frontend
|
:param str ip: IP address of the frontend
|
||||||
:param str mac: MAC (Ethernet) address of the frontend
|
:param str mac: MAC (Ethernet) address of the frontend
|
||||||
:param qubes.vm.QubesVM backend: Backend domain
|
:param qubes.vm.qubesvm.QubesVM backend: Backend domain
|
||||||
:rtype: lxml.etree._Element
|
:rtype: lxml.etree._Element
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -935,7 +935,7 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
**passio_popen** and **input** are mutually exclusive.
|
**passio_popen** and **input** are mutually exclusive.
|
||||||
|
|
||||||
:param str service: service name
|
:param str service: service name
|
||||||
:param qubes.vm.QubesVM: source domain as presented to this VM
|
:param qubes.vm.qubesvm.QubesVM: source domain as presented to this VM
|
||||||
:param str user: username to run service as
|
:param str user: username to run service as
|
||||||
:param bool passio_popen: passed verbatim to :py:meth:`run`
|
:param bool passio_popen: passed verbatim to :py:meth:`run`
|
||||||
:param str input: string passed as input to service
|
:param str input: string passed as input to service
|
||||||
@ -1117,7 +1117,7 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
def clone_disk_files(self, src):
|
def clone_disk_files(self, src):
|
||||||
'''Clone files from other vm.
|
'''Clone files from other vm.
|
||||||
|
|
||||||
:param qubes.vm.QubesVM src: source VM
|
:param qubes.vm.qubesvm.QubesVM src: source VM
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if src.is_running():
|
if src.is_running():
|
||||||
@ -1278,11 +1278,12 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
}
|
}
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
http://wiki.libvirt.org/page/VM_lifecycle
|
http://wiki.libvirt.org/page/VM_lifecycle
|
||||||
Description of VM life cycle from the point of view of libvirt.
|
Description of VM life cycle from the point of view of libvirt.
|
||||||
|
|
||||||
https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainState
|
https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainState
|
||||||
Libvirt API for changing state of a domain.
|
Libvirt's enum describing precise state of a domain.
|
||||||
''' # pylint: disable=too-many-return-statements
|
''' # pylint: disable=too-many-return-statements
|
||||||
|
|
||||||
libvirt_domain = self.libvirt_domain
|
libvirt_domain = self.libvirt_domain
|
||||||
|
Loading…
Reference in New Issue
Block a user