Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the connection plugin optional and add the option to filter domains / uuids using regex. #101

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

yorick1989
Copy link

…ins / uuids using regex.

SUMMARY

I ran into issues that the connection plugin was not working for out-of-the-box Red Hat servers. Red Hat has disabled the required Qemu Guest Agent commands for the libvirt connection plugin by default:

[root@test01 ~]# cat /etc/redhat-release 
Red Hat Enterprise Linux release 8.5 (Ootpa)
[root@test01 ~]# dnf download --downloadonly --destdir=/tmp qemu-guest-agent
Updating Subscription Management repositories.
Last metadata expiration check: 2:05:11 ago on Thu 06 Jan 2022 07:05:05 PM CET.
qemu-guest-agent-4.2.0-59.module+el8.5.0+13495+8166cdf8.1.x86_64.rpm                                                                    601 kB/s | 257 kB     00:00    
[root@test01 ~]# rpm2cpio /tmp/qemu-guest-agent-4.2.0-59.module+el8.5.0+13495+8166cdf8.1.x86_64.rpm | cpio -i --to-stdout ./etc/sysconfig/qemu-ga | grep BLACKLIST
BLACKLIST_RPC=guest-file-open,guest-file-close,guest-file-read,guest-file-write,guest-file-seek,guest-file-flush,guest-exec,guest-exec-status
741 blocks

Therefor I require to pull the IP addresses for each domain and set the first IP address found as the ansible_host variable.

I also added the ansible_libvirt_ifaces variable which contains all the network interfaces information of the domain. It's not necessary, but I was already working on it to get ansible_host to work anyway :) .

ISSUE TYPE
  • Feature Pull Request
COMPONENT NAME

no_connection_plugin

ADDITIONAL INFORMATION

Connection plugin:

Before:

$ ansible-inventory --host test01
{
    "ansible_connection": "community.libvirt.libvirt_qemu",
    "ansible_libvirt_uri": "qemu:///system"
}

After (set: use_connection_plugin: False):

$ ansible-inventory --host test01
{
    "ansible_host": "192.168.122.182",
    "ansible_libvirt_ifaces": {
        "vnet39": {
            "addrs": [
                {
                    "addr": "192.168.122.182",
                    "prefix": 24,
                    "type": 0
                }
            ],
            "hwaddr": "52:54:00:12:54:a2"
        }
    }
}

Filter

Before:

$ ansible-inventory --graph | grep -v -- '-@'
@all:
  |  |--test01
  |  |--test03
  |  |--test01
  |  |--test01
  |  |--test03
  |  |--kali
  |  |--test02
  |  |--test03

After (set: filter: "test0(1|3)+"):

$ ansible-inventory --graph | grep -v -- '-@' | sort -n | uniq
@all:
  |  |--test01
  |  |--test03
  |  |--test01
  |  |--test01
  |  |--test03
  |  |--test03

@odyssey4me
Copy link
Collaborator

It seems to me that there is already functionality to not use the connection plugin, by simply removing the connection plugin configuration file?

Also, the resolving of libvirt domains to IP addresses is the sort of thing that belongs in an inventory plugin, surely?

If you're wanting to connect via SSH then the inventory plugin should resolve libvirt domains to IP addresses, then the normal SSH connection plugin does the usual from there.

@yorick1989
Copy link
Author

yorick1989 commented Jan 21, 2022

Thank you for your feedback.

How do I remove the 'connection plugin configuration file'? I only use the inventory filename that contains:

plugin: community.libvirt.libvirt
uri: 'qemu:///system'
use_connection_plugin: False
filter: "test0(1|3)+"

By the way, my changes are done in the inventory code, not in the connection plugin code. There was no option to skip the connection plugin within the libvirt inventory file, that is why I added the option to skip the connection plugin by adding the use_connection_plugin option to use an alternative way which pulls the IP address and adds the first found IP address to the host variables (as ansible_host); so this IP address can used for SSH connections. There was also a comment in the inventory code to make the connection plugin optional. This is why I made these changes and I would like to give it back to the upstream; if it is a beneficial addition.

If there is a better alternative, please let me know. I just tried to create a way to get this inventory plugin to work without using the connection plugin; since that's by default not working for Red Hat servers (since the 'guest-file-open,guest-file-close,guest-file-read,guest-file-write,guest-file-seek,guest-file-flush,guest-exec,guest-exec-status' options are blocked within the configuration of the qemu-guest-agent package / installation).

Thanks!

@csmart
Copy link
Collaborator

csmart commented Jan 31, 2022

Hi @yorick1989 thanks for this contribution, I will test it out. i can see that on RHEL machines qemu guest agent does not support the required options so I think another option is useful 👍 I may have confused @odyssey4me when I was talking to him about this, as I incorrectly said it was in the connection not the inventory code. Sorry!

If we are going to rely on getting the IP from libvirt then I feel like it has to be very robust. Also, how will we handle failure scenarios? I'm wondering how this might work in edge cases, such as when an IP is not available yet, or perhaps only an IPv6 (still waiting for IPv4), or guests which have multiple network interfaces, etc. Also, how will it work with multiple inventories, or where one has specified ansible_host already elsewhere? Have you had a chance to test any of those scenarios?

Also, are there any additional requirements for this to work? Thanks!

@csmart
Copy link
Collaborator

csmart commented Jan 31, 2022

I've done some basic testing with an inventory file like so:

---
plugin: community.libvirt.libvirt
uri: 'qemu:///system'
use_connection_plugin: False
filter: "ansible-fedora-35"

it works as expected, SSHing into the guest:

(3.9) [09:40 csmart@dev ~/.../community/libvirt (connection_filter $%)]$ date
Tue 01 Feb 2022 09:40:05 AEDT

(3.9) [09:40 csmart@dev ~/.../community/libvirt (connection_filter $%)]$ ansible -i ../../kvm.yml all --limit ansible-fedora-35 -m command -a 'whoami'
ansible-fedora-35 | CHANGED | rc=0 >>
csmart

(3.9) [09:40 csmart@dev ~/.../community/libvirt (connection_filter $%)]$ ssh ansible-fedora-35 last |head -1
csmart   pts/0        192.168.112.1    Tue Feb  1 09:40 - 09:40  (00:00)

I have tested with a filter applied but use_connection_plugin set to false so that it uses guest agent rather than SSH:

---
plugin: community.libvirt.libvirt
uri: 'qemu:///system'
#use_connection_plugin: False
filter: "ansible-fedora-35"

It works as expected.

(3.9) [09:45 csmart@dev ~/.../community/libvirt (connection_filter $%)]$ ansible -i ../../kvm.yml all -m command -a 'whoami'
ansible-fedora-35 | CHANGED | rc=0 >>
root

I have tested using SSH but when there is no filter specified in the inventory:

---
plugin: community.libvirt.libvirt
uri: 'qemu:///system'
use_connection_plugin: False
#filter: "ansible-fedora-35"

However Ansible is not able to parse the inventory. I think we would need to make sure that by default, an empty or missing filter resulted in a complete list of all VMs (the default without this code).

(3.9) [09:42 csmart@dev ~/.../community/libvirt (connection_filter $%)]$ ansible -i ../../kvm.yml all -m command -a 'whoami'
libvirt: Domain Config error : Requested operation is not valid: domain is not running
[WARNING]:  * Failed to parse /home/csmart/ansible_collections/kvm.yml with
ansible_collections.community.libvirt.plugins.inventory.libvirt plugin: Requested operation is not valid: domain is not running
[WARNING]: Unable to parse /home/csmart/ansible_collections/kvm.yml as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

I've also tested with use_connection_plugin: True and it works as expected.

Cheers!

@Andersson007
Copy link
Contributor

Andersson007 commented Feb 1, 2022

@csmart thanks for testing and the great report! @yorick1989 @odyssey4me any thoughts / related questions/updates?

# Set the interface information.
ifaces = {}

for iface, iface_info in (connection.lookupByName(server.name())).interfaceAddresses(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of work to go through to throw away if the connection plugin is going to be used. Are we doing this so that we add the interface information to the inventory, whether we use the connection plugin or not?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was my intention to have the interface information available in both cases: connection plugin on or off.I can leave it out in case that's a bit overhead. But I can imagine that in some cases you would like to know this kind of information (if someone would like to make modifications within libvirt itself for example).

Another option is to leave it out when the connection plugin is on and add it later on back, together with all the other information that's available from libvirt about a host; so you have a full set of information about each host from libvirt. For example: CPU info, RAM info, DISK info, etc.

I'm fine with any of these options. Or if you have a better one, please let me know!

@odyssey4me
Copy link
Collaborator

Aha, I get it now. I love the addition of the filter... that's a great idea! Then the ability to enable/disable the connection plugin seems like it makes sense. My apologies for the previous misunderstanding!

@yorick1989
Copy link
Author

Thank you all for the feedback!

@csmart Thank you for testing! I also tested it myself and it works for me:

[yorick@yorick-pc ansible]$ cat libvirt_qemu.yml 
plugin: community.libvirt.libvirt
uri: 'qemu:///system'
use_connection_plugin: False
#filter: "test-4[0-9]+"
[yorick@yorick-pc ansible]$ ansible -i libvirt_qemu.yml all -m ping
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
kali | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: [email protected]: Permission denied (publickey,password).",
    "unreachable": true
}
test04 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
test01 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
test03 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

So, it's not clear to me why it failed on your side. The filter uses regex and by default the filter value is set to .*; so that should match every host by default.

Also thanks for the feedback in your first reply. Here my replies to your questions:

  • How will we handle failure scenarios?
    Do you mean that I have to add more debug and error handling to my code?
  • I'm wondering how this might work in edge cases, such as when an IP is not available yet, or perhaps only an IPv6 (still waiting for IPv4), or guests which have multiple network interfaces, etc.
    This was indeed also a struggle point for me: decide which IP can be reached over SSH if there are multiple ones. I don't know how to add dynamic filters to the inventory file / plugin script to tell which interface or IP-address should be used for the SSH-connection; since network interfaces can differ for each of the available servers. That's why I also added the ansible_libvirt_ifaces dict which can be used to filter out the proper IP-addresses and set that proper one as ansible_host yourself in a (pre-executed) playbook. I know it's ugly, but if one of you can give me a better solution, please feel free to advice me!
    And I don't know how to act on servers which don't have an IP-address available (,yet). If the inventory plugin simply can't find it in libvirt, then the logic within the inventory plugin should set the ansible_libvirt_ifaces variable to an empty list. Or is that too simple? =)
    And IPv6 addresses shouldn't be a problem I guess, if the host can be reached on it. Or am I missing something?
  • How will it work with multiple inventories, or where one has specified ansible_host already elsewhere?
    I can add a check for ansible_host, before setting it. But shouldn't the last sourced inventory overwrite the already existing variables by default?

As you can see, I'm pretty new with this kind of modifications. I needed this modification to deploy / destroy my servers on a quickly manner using terraform. So any help to improve my changes is very welcome!

@csmart
Copy link
Collaborator

csmart commented Feb 1, 2022

There's an issue with a request for filtering by VM state, for example, power off. I think if we're adding filtering here, it would be great to have this in mind so that we can filter by name (current) but also work in states at some point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants