[virt-tools-list] libvirt profiles (a.k.a. virtuned) design ideas draft

Daniel P. Berrangé berrange at redhat.com
Thu Jul 5 16:58:46 UTC 2018

On Tue, Jul 03, 2018 at 04:41:52PM -0400, Cole Robinson wrote:
> Nice work on the document! I'll duplicate the docs here and comment
> in-line, might help to get more eyeballs.
> On 06/20/2018 09:15 AM, Martin Kletzander wrote:
> > After some discussion with people from various management apps we 
> > decided to try and create something that would help with common,
> > duplicated, and perhaps easy to extract things for which the code
> > exists in some (or most) of the projects. I'd be glad for any
> > feedback, so here is the rendered document:
> > 
> > https://github.com/nertpinx/virt-manager/blob/virtuned-docs/docs/virtuned.md
> >
> > and here you can comment:
> > 
> > https://github.com/nertpinx/virt-manager/pull/1/files
> > 
> > I'm sending this to this list because it might be beneficial to base 
> > this on top of virt-manager's codebase.  We'll see how that goes, but
> > for now any feedback is welcome.
> > 
> Before I get into specific replies/questions, let me make sure I have
> the right idea:
> virtuned is an API for applying preset VM config changes (profiles) to
> libvirt domain XML. Current plan is to implement it in python, but
> provide a REST API wrapper for access from other languages. The library
> will ship with a set of profiles initially based on a collection of what
> existing apps are already doing. Apps using virtuned can mix and match
> which profiles they want to apply to new or existing libvirt domains.
> Sound correct so far?
> > # Design document for virtuned implementation
> > 
> > This document serves as a place to have open discussion about the design of so
> > called *libvirt profiles*, a.k.a. **virtuned**. The project should provide a
> > common ground to few problems which are currently being fixed on different
> > layers (mostly up the stack). Starting from the small things first, virtuned is
> > currently supposed to manage policies for creating XML definitions for VMs
> > (domains).
> > 
> One small quibble: do we expect this to ever be a daemon? I gather the
> name is a play off of the existing 'tuned' project but the 'd' throws me
> off a bit

FWIW, I find the naming rather misleading since this really is separate from
tuned. A better project name choice might simply be libvirt-profile or
something along those lines. Whether this project then provides a library
of binary or both is an impl detail.

> > ## Brief specification of functionality
> > 
> > Currently virtuned aims to provide a consistent way of applying profiles to
> > libvirt VM definitions.  That way management applications don't need to
> > duplicate the implementation in their codebases.
> > 
> > ### Functions
> > 
> > As a starting point virtuned exposes one function.  As input the function
> > accepts a VM definition with the only restriction being that it is a libvirt
> > domain XML.  However it doesn't have to be complete.  The function applies all
> > relevant profiles to that XML and produces a complete libvirt domain XML.
> > 
> > The outcome of this is twofold:
> > - Every libvirt domain XML is already working virtuned XML.
> > - Applications can select, by arbitrarily small steps, how much functionality
> >   they want to use from virtuned.

I'm not sure I understand this second point. IIUC, the contents of the profiles
are supposed to be opaque to the mgmt application. So while they use virtuned,
they'll be exposed to whatever arbitrary XML the profile contains, whether
they understand it or not.

> > 
> > The main advantage of this approach is that applications can adopt the new
> > functionality incrementally by small steps that they themselves can choose,
> > starting from nothing.  Simply plugging virtuned into the process of domain XML
> > creation shouldn't change anything until you explicitly start using profiles.

If you're not using profiles, what is virtuned actally doing for you ?

> This bit confused me for a while, describing a function that only takes
> domain XML as the input. But after a re-read I see the requested
> profiles are specified as their own xmlns in the domain <metadata>
> section. That makes sense to me and provides a lot of flexibility

> > ### API endpoints ###
> > 
> > For now the API will be exposed as:
> > 
> > 1. Python module - trivial if we're basing it on virt-manager codebase which is
> >    using python

What's the key reasons/benefit to be part of virt-manager codebase as opposed
to a standalone project ?

Personally I'm pretty disillusioned with the maint burden of python (and dynamic
languages in general). For a new effort like this I'd encourage consideration
of Go or Rust. That said since I'm not working on it, don't consider this a
blocker, just an opinion.

> > 2. RESTful service - this will just be a separate file handing the glue between
> >    the python module and the different way of API exposure.  For that reason
> >    (and simplicity's sake) this could be outsourced (to
> >    e.g. [hug](https://www.hug.rest/)), at least for now<sup
> >    id='fn1'>[[1]](#fn1d)</sup>.
> > 
> > This will make it usable by most known projects and additional APIs can be added
> > later on without much friction (similarly to the REST API).  If virt-manager's
> > codebase is used as a base, then it will also simplify the exposure of other
> > parts of virt-manager under that RESTful service, for example virt-xml and
> > virt-install.  As far as I know this is something cockpit project would like a
> > lot <sup id='fn2'>[[2]](#fn2d)</sup>.
> > 
> > <sub id='fn1d'>1. <a href='#fn1'>**^**</a>
> > With hug we'll get CLI almost automatically as well.
> > </sub>  
> > <sub id='fn2d'>2. <a href='#fn2'>**^**</a>
> > This is not a strict requirement for virtuned, just a helpful side-effect.
> > </sub>
> > 
> > ## Data specifications
> > 
> > There are several data formats that need to be specified.  What is discussed
> > below is mainly:
> > - **Format of the profiles** - Syntax of the format itself
> > - **Profile behaviours** - Possible changes profiles can do to VM definitions
> > - **Connection between profiles and VM definitions** - how to select changes
> >   which should be applied
> > - **Behaviour of the above connection** - How to specify multiple profiles and
> >   under what circumstances do they mutually exclude.
> > 
> > For a TL;DR example, see [Full Example](#full-example) below.
> > 
> > ### Profile specification
> > 
> > The profile itself can influence the VM definition in three different ways:
> > 
> > - Requesting a specific part of the XML to (not) exist.  This includes:
> >   - Adding a specific XML snippet
> >   - Making sure specific XML snippet exists (without necessarily adding it)
> >   - Removing a specific XML snippet
> > - Setting default for existing parts of the XML (setting if unset)
> > 
> > Due to the fact that the profiles will influence how the resulting XML will look
> > like, virtuned profiles use XML as well, however that does not prevent the
> > support for other formats to be added later on.
> > 
> > Simple profile can look like this:
> > 
> > ``` xml
> > <profile name='add-qxl'>
> >   <add>
> >     <devices>
> >       <video>
> >         <model type='qxl'/>
> >       </video>
> >     <devices>
> >   </add>
> > </profile>
> > ```
> > 
> > The above example will request a video card with model QXL to exist in the VM
> > definition.  The precise outcome of this depends on the existing devices in the
> > VM definition:
> > 
> > - **VM has no video device:** the XML snippet (`qxl` video card) will simply be
> >   added to the list of devices.
> > - **VM has video device with no model specified:** Just fill in the video model
> >   for the existing video card.
> > - **VM has video device with different model:** Add one more video device with
> >   the specified model since multiple video cards are perfectly fine.
> > 
> > The above is very concrete example, but it can be very easily and efficiently
> > generalized for any `<add/>` sub-element.  The only information which is
> > required for said generalization is the knowledge of libvirt's domain XML
> > format.  This could be one of the reasons for virtuned to be spun off of
> > virt-manager's codebase (since most of that information is already there).  The
> > other option would be using
> > [libvirt-go-xml](https://libvirt.org/git/?p=libvirt-go-xml.git) as that should
> > have enough information for this as well <sup id='fn3'>[[3]](#fn3d)</sup>.

FYI, libvirt-go-xml should have 100% coverage of all XML constructs in the
libvirt schema. Any ommissions are entirely due to libvirt's own master XML
test files being incomplete. libvirt-go-xml unit tests check that it can
roundtrip all XML files in libvirt.git without data loss. I don't think any
other XML parser impl for libvirt has the same level of coverage, principally
because none of them do similar kind of testing to prove it.

> > As mentioned above this is not the only type of action that the profile format
> > supports.  Here is the proposed list of actions with optional attributes:
> > 
> > - **`add`** - Make sure such XML snippet exists.  Can have attribute `multiple`
> >   with the following values:
> >   - **`yes`** - Unconditionally add the snippet if it can exist multiple times
> >     (the lowest level that can exist multiple times to be precise) or fail if it
> >     cannot (machine type)
> >   - **`no`** - Adjust existing part of the XML so that it matches the
> >     requirements from the snippet, overriding values if needed.
> >   - **`auto`** (default) - Try adjusting existing part of the XML so that it
> >     matches the requirements, but only override values if there is no part of
> >     that snippet that could be specified multiple times.  If any part of it can
> >     be specified multiple times, then find the lowest such part and append that.
> > - **`remove`** - Make sure such XML snippet does not exists.  All matching XML
> >   snippets (even if they have more attributes or sub-elements) will be removed.
> > - **`default`** - If VM definition has an XML snippet which does fit this
> >   description except some values not existing, then fill in those values.  This
> >   can be used for example for default device model types or machine types.
> > 
> > The `<add multiple='X'/>` is just a naming and it can be changed in any way that
> > suits others, for example instead of having:
> > 
> > ``` xml
> > <add multiple='auto'/>
> > <add multiple='yes'/>
> > <add multiple='no'/>
> > ```
> > 
> > There could be:
> > 
> > ``` xml
> > <append/>
> > <set/>
> > <force/> <!-- or <replace/> -->
> > ```
> > 
> > All action elements can have optional attribute `constraint` with the following
> > possible values:
> > - **`soft`** (default) - Profiles with higher priority can override this value.
> >   This is the default and should be used whenever it is not absolutely necessary
> >   for the XML snippet to be kept.
> > - **`hard`** - If profile with higher priority needs to override this value,
> >   then error out.  This should be selected only when it is absolutely necessary
> >   for the XML snippet to exist in this way.  For example in the following cases:
> >   - The system would be unstable.
> >   - Data corruption might occur.
> >   - Other parts of the profile would cause harm without this set.
> > 
> > Yet another simple profile can look like this:
> > ``` xml
> > <profile name='some-interesting-things'>
> >   <add>
> >     <iothreads>2</iothreads>
> >   </add>
> >   <add>
> >     <devices>
> >       <disk device='cdrom'>
> >     </devices>
> >   </add>
> >   <add multiple='yes'>
> >     <devices>
> >       <redirdev bus='usb' type='spicevmc'/>
> >       <redirdev bus='usb' type='spicevmc'/>
> >     </devices>
> >   </add>
> >   <remove type='hard'>
> >     <features>
> >       <apic/>
> >     </features>
> >   </remove>
> >   <defaults>
> >     <devices>
> >       <interface>
> >         <model type='virtio'/>
> >       </interface>
> >     <devices>
> >   </defaults>
> > </profile>
> > ```

This is where I really start to get very concerned. The examples you're giving
a nice and simple, so composition of arbitrary profiles, together with application
written XML looks like it'll work.

I think it will be all too easy, however, to write profiles where the result of
composition profiles and merging with app XML is an XML document that is
semantically invalid / unrunnable.

Consider if you have a two profiles, one sets up a XML doc with 'pc' machine
type and other profile sets up an XML doc with 'q35' machine type.

Now a third profile wants to setup NUMA for the guest such that PCI devices
are associated with NUMA nodes. The way you do this is very different for
'pc' and 'q35' machine types due to PCI vs PCI-Express topology changes.
So if the 'numa' profile assumes 'pc' it will break if the app composes it
with the 'q35' profile, or vica-verca.

Now consider you have a 'networking-nfv' profile that is supposed to setup
NICs in a way that is optimized for NFV use cases. This profile now needs
to know if it should put the NICs in the default PCI bus, or in the NUMA
specific PCI bus. So the result may or may not do the right thing if you
compose it with the 'numa' profile.

Solving these problems would require a combinatorial expansion in the
number of profiles. eg a numa-pc, numa-q35 profile, and then a
networking-nfv-pc, networking-nfv-q46, networking-nfv-numa-pc, and
networking-nfv-numa-q35 profiles. There would then have to be dependancies
expressed to tell the app which profiles can be composed with each other.

This still only solves the problem of composing profiles, and does not
consider how to merge with the application defined XML parts. The only
way an application can know if the XML it wants to write, is compatible
with the profiles it has used, is if it parses and understands all the
parts of the profile.

If something was used in the profile that the app doesn't know about,
it could ignore it, but the resulting VM config may well be unrunnable,
or worse, runnable but doing something completely inappropriate.

I think these kind of problems are inherant in any approach which allows
arbitrary user defined XML as the schema for the profiles.

This is one of reasons why libosinfo didn't base the information it
provides around the libvirt XML schema. Instead it defines its own
domain specific language, and applications only use the features in
it that they actually know how to handle.

This means if we add some new concept to libosinfo database, applications
are not going to automagically use it, and instead have to add explicit
support. As above though, I think this is inevitable, because it is too
easy to create unrunnable/nonsensical XML configs if you allow arbitrary
user specified XML inputs.

> > This profile consists of various actions and has the following implications for
> > the VM definition:
> > 
> > - The VM should have 2 I/O Threads (profiles with higher priority can override
> >   this setting)
> > - The VM should have a CDROM drive.  It will not be added multiple times if it
> >   already exists.
> > - Two spice redirdev ports will be added to the VM definition.  If there were
> >   some existing ones, these will be added<sup id='fn4'>[[4]](#fn4d)</sup>.
> > - The VM must not have APIC (cannot be overridden)
> > - Any interface should default to virtio model type.  That means model will be
> >   set to `virtio` unless already specified.
> > 
> > There are some open questions related to more actions being specified, however
> > they should be limited to minimum.
> > 
> > <sub id='fn3d'>3. <a href='#fn3'>**^**</a>
> > Actually maybe even more since virt-manager's info is also incomplete.
> > </sub>  
> > <sub id='fn4d'>4. <a href='#fn4'>**^**</a>
> > Without the `multiple='yes'` this would mean that **at least** 2 such ports
> > should exist.
> > </sub>
> > 
> > ### VM <-> Profile connection
> > 
> > Not all profiles need to be applied to all VM definitions.  In order to select
> > only the relevant ones we need to specify the connection between the VM
> > definition and the profile.  That can can be done in multiple different ways
> > depending on the preference, however each approach has pros and cons so they are
> > discussed in this section.
> > 
> > Since multiple profiles can be applied to the same VM definition at the same
> > time, there also needs to be a way to deal with conflicts.  Even though this
> > issue seems orthogonal to the connection itself, it can be dealt with in
> > different ways depending on the connection specification used.  What is proposed
> > below are two ways how to handle the connection with a way how to deal with
> > profile clashes together with two ways that were removed from the consideration
> > (just to makes sure the decisions are covered for future observers).
> > 
> > #### Selectors in profiles
> > 
> > Similarly to KubeVirt's approach to [VM
> > Presets](https://github.com/kubevirt/kubevirt/blob/master/docs/vm-presets.md)
> > this is something that has a great power.  Each profile specification includes a
> > selector based on which that particular profile will (not) be selected.
> > 
> > Multiple profiles clash and error out in case they cannot be combined.  For this
> > we propose a solution in the later section.
> > 
> > Example:
> > 
> > ``` xml
> > <profile name='add-qxl-for-spice'>
> >   <match>
> >     <devices>
> >       <graphics type='spice'/>
> >     <devices>
> >   </match>
> >   <add>
> >     <devices>
> >       <video>
> >         <model type='qxl'/>
> >       </video>
> >     <devices>
> >   </add>
> > </profile>
> > ```
> > 
> > This profile is similar to the one in [Profile
> > specification](#profile-specification) with one difference, which is a
> > `<match/>` element.   That element includes a condition under which the profile
> > actions will be executed.  In this particular case the profiles says that a QXL
> > video card should be present in case the VM has a SPICE graphics device.
> > 
> > These matches might include any part of the XML, even metadata, so this can
> > match guest OS (if provided as part of the VM metadata).
> > 
> > For example this condition:
> > 
> > ``` xml
> > <match>
> >   <metadata>
> >     <myapp:myapp>
> >       <myapp:guest os_type='windows'/>
> >     </myapp:myapp>
> >   <metadata>
> > </match>
> > ```
> > 
> > would be matched on this VM definition:
> > 
> > ``` xml
> > <domain>
> >   <name>Win10</name>
> >   <metadata>
> >     <myapp:myapp xmlns:myapp='http://example.org/myapp'>
> >       <myapp:guest os_type='windows' os_version='10'/>
> >     </myapp:myapp>
> >   </metadata>
> >   ...
> > </domain>
> > ```
> > 
> > As you can see the metadata used for the condition don't need to be virtuned's
> > specific metadata, but rather any management applications metadata.
> > 
> I didn't really know where to cut in so this is a big comment...
> The idea here is that virtuned will ship with something like a
> profile/add-qxl.xml, and profile=add-qxl will then effectively be part
> of the virtuned API, like an osinfo ID value is to libosinfo; the
> profile will never go away, so apps can depend on it being there.
> Presumably we can extend the profile as necessary as long as it
> accomplishes its stated goal and we confirm it doesn't break apps.
> Using XML for this kind of thing makes me nervous, trying to model
> conditional actions with XML. I feel like it's a real quick slippery
> slope to implementing a turing complete schema. For example how would we
> handle complex examples like:
> * set graphics=spice. Except spice is only available for qemu depending
> on the _host_ arch. qemu-system-aarch64 on x86 has spice, but not
> qemu-system-aarch64
> * Give me USB tablet, if (os-supports-usb-tablet && (arch == x86 ||
> (arch == aarch64 && machine.startswith("virt")))
> * Give me USB3, if os-supports-usb3. If qemu version > X,
> model=qemu-xhci, else model=nec-xhci
> What's the motivation for doing this in XML? So apps or distros can drop
> in their own profiles? Or extend system profiles? I'm wondering why XML
> over privately implemented. Maybe you can explain some specific app
> usecases that motivated this? I feel like I missed a lot in the previous
> discussion
> Also do we expect the API to talk directly to libvirt? Like for checking
> domcapabilities?

I think there's similar issues with have this overlaps / integrates with
libosinfo. When using libosinfo we can dynamically query which type of
device is appropriate on a per guest OS basis. We can also query the
libvirt domain capabilities, and then determine the compatible overlap
between libosinfo and QEMU and libvirt. 

The profiles appear to largely loose this ability, going back to having
to write different protocols to cover different types of guest OS.

A the very least I think this means profiles need to be able allow for
complex conditional expressions, along with variable subsistitutions
from external data sources.

This gets hard though when you try to compose profiles with the app
being opaque about what's in the profile. eg if the guest supports
virtio-scsi the result is very different from if the guest supports

So if one base profile sets up controllers, a later add-on profile
that works with disks needs to be able to write an expression to
determine whether the existing XML as a virtio-scsi or IDE controller
present and use those, vs deciding to add controller-less virtio-blk
disks.  This rapidly becomes a turing complete problemspace I think.

> > ### Open Questions
> > 
> > There are still couple of things to be discussed, which I will only cover
> > slightly <sup id='fn5'>[[5]](#fn5d)</sup>.
> > 
> > 1. **How are profiles added**  
> >    They can be files in a filesystem, there could be separate API for defining
> >    profiles.  Some of them will most probably be shared in the repository
> >    together with virtuned, but applications need to be able to define their own
> >    ones.  Filesystem-based storage seems fine for usage in a container for
> >    example, but might not be usable for some deployments.
> > 
> I get that applications will want to add their own profiles and tweak
> defaults. But is writing <profile> XML going to be much easier than
> editing the XML directly? It isn't clear to me one way or the other.

I tend to think writing the profiles is going to be more complex and
error prone than directly writing the XML, because of the composability
problems I mention above.

My gut feeling is that it would be a more tractable problem if the profiles
used a domain specific language (DSL), possibly still XML, but not libvirt
domain XML. Applications would have to explicitly know about individual
features in the DSL, but they could consume it in a way that the way they
generate libvirt XML is more fully data-driven.

ie, taking my example above, applications would need explicit knowledge
of machine types, NUMA topologies, and attaching devices to NUMA nodes.
Given that knowledge though, the decision about /when/ to use these
respective features would be data driven from profiles that simply
stated desired traits.

|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

More information about the virt-tools-list mailing list