Can I couple only_if and with_item using a register variable?

I would like to use a playbook like the following to quickly switch a fixed set of virtual machine running one OS to another. But AFAICS, unless I go lower level to libvirt Python binding, I would get errors like the following:

TASK: [Destroy them one by one] *********************
fatal: [barn0] => failed to parse: {“msg”: “Requested operation is not valid: domain is not running”, “failed”: true}
libvir: QEMU error : Requested operation is not valid: domain is not running

tasks:

  • name: Get all existing guest domains, regardless their states
    action: shell …
    register: all_vm_names
  • name: Get all active guest domains
    action: shell …
    register: active_vm_names
  • name: Destroy active domains one by one
    action: virt name=$item command=destroy
    with_items: ${active_vm_names.stdout_lines}
    only_if: ${active_vm_names}
  • name: Undefine all domains one by one
    action: virt name=$item command=undefine
    with_items: ${all_vm_names.stdout_lines}
    only_if: ${all_vm_names}

I am using 0.8 devel, and read about the following in CHANGELOG.md:

[…]

  • only_if using register variables that are booleans now works in a boolean way like you’d expect

[…]

I understand for what I would like to do to work, the only_if must have higher precedence than with_items. My sense is that as of now, this is not the case. With YAML being not a programming language, is there a simple way for me to accomplish what I would like to do?

Thanks,

– Zack

I would like to use a playbook like the following to quickly switch a fixed
set of virtual machine running one OS to another. But AFAICS, unless I go
lower level to libvirt Python binding, I would get errors like the
following:

TASK: [Destroy them one by one] *********************
fatal: [barn0] => failed to parse: {"msg": "Requested operation is not
valid: domain is not running", "failed": true}
libvir: QEMU error : Requested operation is not valid: domain is not running

  tasks:
    - name: Get all existing guest domains, regardless their states
      action: shell ...
      register: all_vm_names
    - name: Get all active guest domains
      action: shell ...
      register: active_vm_names
    - name: Destroy active domains one by one
      action: virt name=$item command=destroy
      with_items: ${active_vm_names.stdout_lines}
      only_if: ${active_vm_names}
    - name: Undefine all domains one by one
      action: virt name=$item command=undefine
      with_items: ${all_vm_names.stdout_lines}
      only_if: ${all_vm_names}

Is your intention here to determine if there /are/ any VM names?

all_vm_names as you have registered it is a hash and you need to get
some element out of it. like ${all_vm_names.rc} or something.

I am using 0.8 devel, and read about the following in CHANGELOG.md:

[...]
* only_if using register variables that are booleans now works in a boolean
way like you'd expect
[...]

I understand for what I would like to do to work, the only_if must have
higher precedence than with_items. My sense is that as of now, this is not
the case.

I don't think it needs to be the case.

with_items expanding first will expand to multiple task executions,
but there is a "only_if" on each expansion.

Though to be honest it seems quite easier to give the virt module a
command for 'undefine_all'. That's what I'd do. (Send me a patch?)

[...]

Is your intention here to determine if there /are/ any VM names?

The intention is:

0. Determine whether there are /any/ active domains. Put these into a
register variable.
1. Determine /all/ domain names. Put these into another register
variable.

The reason why I took two steps (and used two register variables) is
that these domains could assume various states, e.g.

0) defined
1) shutoff
2) suspended (aka paused)
3) running.

Before all these domains can be undefined, it's necessary to destroy
domains on state 2) and 3) above. Since all guests are of
"disposable" nature, the brutal 'destroy' command is OK. Without
this step, libvirt would throw an error if an domains is on state 2.
or 3, e.g. using the virsh command in shell:

root@barn0:~# virsh undefine ctos0
error: Failed to undefine domain ctos0
error: Requested operation is not valid: cannot delete active domain

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

all_vm_names as you have registered it is a hash and you need to get
some element out of it. like ${all_vm_names.rc} or something.

That's my understanding.

[...]

> I understand for what I would like to do to work, the only_if must have
> higher precedence than with_items. My sense is that as of now, this is not
> the case.

I don't think it needs to be the case.

with_items expanding first will expand to multiple task executions,
but there is a "only_if" on each expansion.

Excellent! Thanks for the tip. I will change my syntax and give it
another try.

Though to be honest it seems quite easier to give the virt module a
command for 'undefine_all'. That's what I'd do. (Send me a patch?)

Let me go over the virt module source more. Yes. A 'undefine_all'
would be handy.

Regards,

-- Zack

I got some time tonight to go over the following:

tasks:

  • name: Get all existing guest domains, regardless their states
    action: shell …
    register: all_vm_names
  • name: Get all active guest domains
    action: shell …
    register: active_vm_names
  • name: Destroy active domains one by one
    action: virt name=$item command=destroy
    with_items: ${active_vm_names.stdout_lines}
    only_if: ${active_vm_names}

I changed the above to only_if: ${active_vm_names.stdout}

  • name: Undefine all domains one by one
    action: virt name=$item command=undefine
    with_items: ${all_vm_names.stdout_lines}
    only_if: ${all_vm_names}

ditto: only_if: ${all_vm_names.stdout}

If I understand the following statement correctly, then each with_items task would be executed only if the ${…} is True.

with_items expanding first will expand to multiple task executions,
but there is a “only_if” on each expansion.

But, I got the following by running the above with ansible-playbook -vv:

TASK: [Get all existing guest domains, regardless their states, except sl0] *********************
REMOTE_MODULE command … #USE_SHELL
changed: [barn0] => {“changed”: true, “cmd”: “…”, “delta”: “0:00:00.018831”, “end”: “2012-10-15 23:01:27.838914”, “rc”: 0, “start”: “2012-10-15 23:01:27.820083”, “stderr”: “”, “stdout”: “ctos1\nctos2”} … (1)

TASK: [Get all active guest domains, except sl0] *********************
REMOTE_MODULE command … #USE_SHELL
changed: [barn0] => {“changed”: true, “cmd”: …, “delta”: “0:00:00.020940”, “end”: “2012-10-15 23:01:27.983329”, “rc”: 0, “start”: “2012-10-15 23:01:27.962389”, “stderr”: “”, “stdout”: “”} … (2)

TASK: [Destroy them one by one] *********************
fatal: [zettar3] => Traceback (most recent call last):
File “/usr/lib/pymodules/python2.7/ansible/runner/init.py”, line 227, in _executor
exec_rc = self._executor_internal(host)
File “/usr/lib/pymodules/python2.7/ansible/runner/init.py”, line 276, in _executor_internal
return self._executor_internal_inner(host, self.module_name, self.module_args, inject, port)
File “/usr/lib/pymodules/python2.7/ansible/runner/init.py”, line 327, in _executor_internal_inner
if not utils.check_conditional(conditional):
File “/usr/lib/pymodules/python2.7/ansible/utils.py”, line 146, in check_conditional
return eval(conditional.replace(“\n”, “\n”))
File “”, line 0

^
SyntaxError: unexpected EOF while parsing

FATAL: all hosts have already failed – aborting

shell commands in (1) and (2) above have return code (rc) 0, and thus they are constructed correctly for the shell module. The register variable all_vm_names’s stdout consists of valid entries. The active_vm_names’s stdout is empty, so the task named Destroy active domains one by one shouldn’t execute. But before it gets to that, the core parsing code errored out. Any other suggestion please?

Though to be honest it seems quite easier to give the virt module a
command for ‘undefine_all’. That’s what I’d do. (Send me a patch?)

I gave this aspect some more deliberation, and came to the conclusion that a undefine_all is not enough. At least based on our usage, from time to time, we may wish to preserve a few VMs. But so far I don’t have a clean syntax for a list of exceptions. I don’t wish to introduce that pollute ansible’s current overall clean syntax. I will think more…

Regards,

– Zack

You have to quote variables that are strings. Since variables are substituted (rather than interpreted), so an empty string results in nothing, which is giving you this error.

Hi Dag,

Many thanks! Your quoting suggestion did the trick.

[…]

You have to quote variables that are strings. Since variables are
substituted (rather than interpreted), so an empty string results in
nothing, which is giving you this error.

[…]

Regards,

– Zack

Zack,
  I was just catching up on list mail. Are you planning on posting this playbook somewhere when you're done? I've got a similar usecase and if I can implement all of it in a playbook rather than in a script calling ansible as an api then that would be fantastic.

thanks,
-sv

Hi SV,

I will, after polishing it a bit. Yeah, squeezing a bash script into an one liner for the shell module is a bit ugly. Having all such capabilities in a module would be awesome!

Lets do this, with the wiki space that Michael recently made available, I think the playbook example belongs to the wiki at least before you are done with your module. Right now, I am working with Micheal to figure out an annoying and puzzling sudo keyword related bug that I started experiencing since the late stage of 0,8 devel. Am loaded lately, otherwise, I would do more in-depth debugging.

Regards,

– Zack