VirtOps #3: Ansible with Xen Orchestra
Drive your whole Xen stack with infrastructure-as-code, by combining superpowers of Ansible and Xen Orchestra!
With the release of Ansible Community 4.1.0 came a new inventory plugin for Xen Orchestra. This plugin allows the listing and grouping of XOA virtual machines, hosts and pools. In this article we will see how to install and use it.
Ansible: a quick intro

Ansible is an open-source software provisioning, configuration management, and application-deployment tool enabling infrastructure as code.
It helps to manage multiple machines by selecting portions of Ansible's inventory stored in simple ASCII text files. The inventory is configurable, and target machine inventory can be sourced dynamically or from cloud-based sources in different formats (YAML, INI).
If you want to learn more about it, check the Wikipedia page, or the official website.
Ansible Collections
Collections are a distribution format for Ansible content that can include playbooks, roles, modules, and plugins.
Ansible + Xen Orchestra = 💖
We decided to add Ansible capabilities on top of Xen Orchestra for the same reasons we created a Terraform plugin for it: XO is acting as a real central point -or middleware- for your entire infrastructure.

At some point, that's the same architecture choice VMware did with vCenter.
Xen Orchestra Inventory plugin
This package is not included in ansible-core, you will have to install it using ansible-galaxy:
$ ansible-galaxy collection install community.generalLet's start by creating the plugin configuration file, its filename must end with xen_orchestra.yml or xen_orchestra.yaml:
plugin: community.general.xen_orchestra
api_host: 192.168.1.255
user: xo
password: xo_pwdYou can now explore our inventory:
ansible-inventory -i my.xen_orchestra.yml --list {
"_meta": {
"hostvars": {
"099c01b4-6e21-4466-a6c8-037d41d36d7c": {
"address": "192.168.1.11",
"cpus": {
"cores": 16,
"sockets": 1
},
"enabled": true,
"hostname": "r740",
"memory": {
"size": 34287759360,
"usage": 7765843968
},
"power_state": "running",
"product_brand": "XCP-ng",
"tags": [],
"type": "host",
"version": "8.2.0"
},
"df4577f2-efa2-685f-af98-e82da3050dc0": {
"ansible_host": "192.168.1.12",
"cpus": 1,
"has_ip": true,
"ip": "192.168.1.12",
"is_managed": true,
"memory": 2147463168,
"name_label": "Test machine",
"os_version": {
"distro": "ubuntu",
"major": "20",
"minor": "04",
"name": "Ubuntu 20.04.3 LTS",
"uname": "5.4.0-90-generic"
},
"power_state": "running",
"tags": [],
"type": "VM",
"uuid": "df4577f2-efa2-685f-af98-e82da3050dc0"
}
}
},
"all": {
"children": [
"halted",
"paused",
"running",
"suspended",
"ungrouped",
"with_ip",
"xo_host_r740",
"xo_hosts",
"xo_pool_xcp_ng_main",
"xo_pools"
]
},
"running": {
"hosts": [
"df4577f2-efa2-685f-af98-e82da3050dc0"
]
},
"with_ip": {
"hosts": [
"df4577f2-efa2-685f-af98-e82da3050dc0"
]
},
"xo_host_r740": {
"hosts": [
"df4577f2-efa2-685f-af98-e82da3050dc0"
]
},
"xo_hosts": {
"hosts": [
"099c01b4-6e21-4466-a6c8-037d41d36d7c"
]
},
"xo_pool_xcp_ng_main": {
"hosts": [
"099c01b4-6e21-4466-a6c8-037d41d36d7c",
"df4577f2-efa2-685f-af98-e82da3050dc0"
]
}
}
Let's break it down!
halted,running,pausedandsuspendedgroup Virtual Machines by their power stateswith_ipandwithout_ipgroup Virtual Machines by our ability to get an IP address from them. If we cannot find the IP (most likely because theguest-toolsare not installed), then the Virtual Machine is added to thewithout_ipgroup.xo_host_*will group Virtual Machines that are hosted on a given hostxo_hostsare the UUIDs of all the known hostsxo_pool_*will group Virtual Machines that are hosted in a given pool
Going further
The default groups would allow you to ping every known running machines for example:
$ ansible running -i my.xen_orchestra.yml -m pingdf4577f2-efa2-685f-af98-e82da3050dc0 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
But we could also do something more useful, let's go back to our plugin configuration:
plugin: community.general.xen_orchestra
api_host: 192.168.1.255
user: xo
password: xo_pwd
groups:
webservers: "'webserver' in tags"And add a webserver tag on our Virtual Machine

If we explore our inventory again:
$ ansible-inventory -i my.xen_orchestra.yml --listWe now have a webservers group
...
"webservers": {
"hosts": [
"df4577f2-efa2-685f-af98-e82da3050dc0"
]
},
...
Real world usage
Let's create a playbook and run it:
---
- name: Update web servers
hosts: webservers
remote_user: root
tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest
- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf$ ansible-playbook main.yml -i my.xen_orchestra.ymlPLAY [Update web servers]
TASK [Update web servers : Ensure apache is at the latest version]
ok: [df4577f2-efa2-685f-af98-e82da3050dc0]
TASK [Update web servers : Write the apache config file]
ok: [df4577f2-efa2-685f-af98-e82da3050dc0]
A small note
We use uuid as what Ansible calls host, this is because we cannot guarantee that all the Virtual Machines returned by the Xen Orchestra API will have an IP address, for example if guest-tools are not installed.
For Virtual Machines that do not have an known IP address, the value of the magic variable ansible_host will not be defined and therefore Ansible will try to use the value of the uuid variable to connect. This will fail.
For this reason, you may need to disable fact gathering with gather_facts: no.
Doing more
With that list of Virtual Machines, Hosts and Pools, you could imagine doing:
- Applying updates to a list of outdated machines 🚨
- Doing specific tasks on a group of distros 🧑🔧
- Patching hosts 🩹
- And many other things! 🚀
Finally, this initial inventory plugin is just a start. We'll add more Ansible-related features, through our great community input. On that, please share your feedback on our dedicated forum thread!