Here is an example with actually valid YAML.

- foo: 123
- bar: [1,2,3]
- !mux-group hw:
     - !mux-choice cpu:
         - intel:
             - !mux-default true
             - cpu: 'SandyBridge'
         - amd:
             - cpu: 'Opteron_G1'
         - arm:
             - !mux-requires-only { 'env': 'debug', 'os': 'linux' }
             - !mux-requires-no { 'hw.net.nic_model' : [ 'e1000', 'rtl8139' ] }
             - cpu: 'cortex-a15 '
     - !mux-choice disk:
             - scsi:
             - virtio:
                 - !mux-default true
                 - !mux-requires-only { 'os' : 'linux' }
     - !mux-group net:
          - !mux-choice backend:
                 - vhost:
                     - !mux-default true
                 - tap:
                 - user:
          - !mux-choice nic_model:
                 - virtio:
                     - !mux-default true
                     - nic_model: virtio
                 - rtl8139:
                     - nic_model: rtl8139
                 - e1000:
                     - nic_model: e1000
- !mux-choice os:
   - linux:
       - dev-tools: 'gcc'
       - !mux-choice distro:
         - fedora:
             - !mux-default true
             - package-manager: 'yum'
             - init: 'systemd'
             - !mux-choice version:
                   - 13:
         - mint:
             - package-manager: 'apt-get'
             - init: 'init'
             - !mux-choice version:
                   - 7:
   - win:
       - bsod: true
       - !mux-choice dev-tools:
          - cygwin:
              - dev-tools: Cygwin
          - mingw:
              - dev-tools: MinGW
          - vs:
              - dev-tools: VisualStudio
       - !mux-choice version:
          - win7:
          - win8:
              - bsod: false
- !mux-choice env:
   - debug:
       - !mux-requires-only {'os.distro': 'fedora' }

   - prod:
       - !mux-default true
- !mux-choice test:
   - ping_test:
       - !mux-expand [ 'hw.net' ]


The next step would be to define an API to receive the instructions 
from the YAML parser, like

class MuxBuilder(object):
    def startGroup(self, name)
    def startChoice(self, name)
    def startChoiceValue(self, name)
    def makeChoiceDefault(self)
    def endChoiceValue(self)
    def endChoice(self, name)
    def expand(self, path)
    def startIf(self, kv)
    def requireOnly(self, expandKeys, kv)
    def requireNo(self, expandKeys, kv)
    def endIf(self)
    def __iter__(self):

The case above would require invocations like

startGroup('hw')
  startChoice('cpu')
    startChoiceValue('intel')
      makeChoiceDefault()
    endChoiceValue()
    startChoiceValue('amd')
    endChoiceValue()
    startChoiceValue('arm')
      requireOnly(false, { 'env': 'debug', 'os': 'linux' })
      requireNo(false, { 'hw.net.nic_model' : [ 'e1000', 'rtl8139' ] }
    endChoiceValue()
  endChoice()
  startChoice('disk')
    startChoiceValue('scsi')
    endChoiceValue()
    startChoiceValue('virtio')
      makeChoiceDefault()
      requireOnly(false, { 'os': 'linux' })
    endChoiceValue()
  endChoice()
  startGroup('net')
    startChoice('backend')
      startChoiceValue('vhost')
        makeChoiceDefault()
      endChoiceValue()
      startChoiceValue('tap')
      endChoiceValue()
      startChoiceValue('user')
      endChoiceValue()
    endChoice()
    startChoice('nic_model')
      startChoiceValue('virtio')
        makeChoiceDefault()
      endChoiceValue()
      startChoiceValue('rtl8139')
      endChoiceValue()
      startChoiceValue('e1000')
      endChoiceValue()
    endChoice()
  endGroup()
endGroup()
startChoice('os')
  startChoiceValue('linux')
    startChoice('distro')
      startChoiceValue('fedora')
        makeChoiceDefault()
        startChoice('version')
          startChoiceValue('13')
          endChoiceValue()
        endChoice()
      endChoiceValue()
      startChoiceValue('mint')
        startChoice('version')
          startChoiceValue('7')
          endChoiceValue()
        endChoice()
      endChoiceValue()
    endChoice()
  endChoiceValue()
  startChoiceValue('win')
    startChoice('dev-tools')
      startChoiceValue('cygwin')
      endChoiceValue()
      startChoiceValue('mingw')
      endChoiceValue()
      startChoiceValue('vs')
      endChoiceValue()
    endChoice()
    startChoice('version')
      startChoiceValue('win7')
      endChoiceValue()
      startChoiceValue('win8')
      endChoiceValue()
    endChoice()
endChoice()
startChoice('env')
  startChoiceValue('debug')
    requireOnly(false, { 'os.distro': 'fedora' })
  endChoiceValue()
  startChoiceValue('prod')
    makeChoiceDefault()
  endChoiceValue()
endChoice()
startChoice('test')
  startChoiceValue('ping_test')
    expand('hw.net')
  endChoiceValue('ping_test')
endChoice()

(indentation does not imply recursion, it is just to make the matching
start/end calls more visible).  The calls can be generated through a
visit of the parse tree produced by yaml.compose, or the object produced
by yaml.load.  (I played a bit with subclassing the YAML parser or composer,
but it seems messy).  Splitting out the builder object allows for simpler
unit testing.

BTW, I found a couple of bugs in the conversion to CNF (conjunctive 
normal form).  In this fixed version, you have the following:

- each item of a choice is given a variable

- each choice is given an "expand" variable

Each choice is translated as in the following example.  Say you are
translating the dev-tools choice in the following snippet:

- !mux-choice os:
    - win:
       - !mux-choice dev-tools:
          - cygwin:
          - mingw:
          - vs:

Variables:

    os=win                      (written below as PARENT)
    os=win+os.dev-tools=cygwin  (written below as CYGWIN, default)
    os=win+os.dev-tools=mingw   (written below as MINGW)
    os=win+os.dev-tools=vs      (written below as VS)
    expand(os.dev-tools)        (written below as EXPAND)

The following formula is added to the CNF:

   exactly one of (not PARENT, CYGWIN, MINGW, VS)  &
   (EXPAND | not PARENT | CYGWIN)

where the "CYGWIN" in the second line is the default value.  The first 
half can be synthesized as many "~A | ~B" pairs, as in the previous 
message:

    PARENT | not CYGWIN      &
    PARENT | not MINGW       &
    PARENT | not VS          &
    not CYGWIN | not MINGW   &
    not CYGWIN | not VS      &
    not MINGW | not VS

In addition you need to handle conflicting names (e.g. Fedora 6 and 
RHEL 6 both have os.distro.version == 6), so we'll need an extra 
variable for each  (key, value) tuple.  In this case for example you 
will have from the previous step:

    os=linux+os.distro=fedora+os.distro.version=6    (below: FEDORA6)
    os=linux+os.distro=rhel+os.distro.version=6      (below: RHEL6)

You then add the variable (referenced by if/only/no clauses):

    os.distro.version=6        (below: DISTROV6)

and you add the following to the big formula

    DISTROV6 iff (FEDORA6 | RHEL6)

where "iff" is "if and only if" (also known as xnor).

Paolo

_______________________________________________
Virt-test-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/virt-test-devel

Reply via email to