Skip to content

Instantly share code, notes, and snippets.

@cyberang3l
Last active February 23, 2024 13:41
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cyberang3l/f4c8b1ab6fc48374fbae9553d89e5eed to your computer and use it in GitHub Desktop.
Save cyberang3l/f4c8b1ab6fc48374fbae9553d89e5eed to your computer and use it in GitHub Desktop.
Necessary scripts and procedure to start libvirt VMs in a provided order, with an given delay before starting each subsequent VM.
  1. Ensure that your VMs can shutdown gracefully when the power-button is pressed.

  2. Place the file vm-boot-order.py in /opt directory

  3. Make it executable: chmod +x /opt/vm-boot-order.py

  4. Check that the script starts and shuts down your VMs as expected. The script can be executed as follows:

     /opt/vm-boot-order.py start
     /opt/vm-boot-order.py stop
     /opt/vm-boot-order.py status
    
  5. Make a systemd service to execute the script when booting or shutting down the hypervisor:

For RHEL/CentOS:

  • Place the file vm-boot-order.service in /etc/systemd/system/

  • Apply the correct permissions chmod 644 /etc/systemd/system/vm-boot-order.service

  • Reload systemd services and enable the newly added service:

        systemctl daemon-reload
        systemctl enable vm-boot-order.service
    
  • Start/Restart the service

        systemctl start vm-boot-order.service
        systemctl status vm-boot-order.service
        systemctl stop vm-boot-order.service
        systemctl status vm-boot-order.service
    
#!/usr/bin/env python
from __future__ import print_function
import libvirt
import sys
import time
vm_start_list = ['openmediavault', 'nextcloud'] # The list of VMs to start in the given order
vm_start_waiting_time_list = [90, 0] # A list of waiting times to wait after each VM reaches active
# state, before starting the next VM from the vm_start_list
vm_stop_list = reversed(vm_start_list) # By default, the VMs are stopped in the reverse start order.
# If you want a different order, add the VMs in the vm_stop_list
vm_force_stop_time = 240 # If the the VM shutdown command doesn't work or the VM got stuck,
# after 'vm_force_stop_time' seconds the VM will be 'destroyed'.
#----------------------------------------------------------------------
def start_vms(libvirt_conn):
for i in range(len(vm_start_list)):
vm_name = vm_start_list[i]
wait_time = vm_start_waiting_time_list[i]
try:
vm = libvirt_conn.lookupByName(vm_name)
except libvirt.libvirtError:
# Error code 42 = Domain not found
if (e.get_error_code() == 42):
print(e)
exit(1)
else:
raise(e)
if vm.isActive():
print("VM '{}' already started".format(vm_name))
while not vm.isActive():
print("Starting VM '{}'".format(vm_name))
vm.create()
time.sleep(1)
if vm.isActive():
try:
print("Waiting for {} seconds before trying to start the next VM '{}'".format(wait_time, vm_start_list[i + 1]))
time.sleep(wait_time)
except IndexError:
break
print("All VMs have been started.")
#----------------------------------------------------------------------
def stop_vms(libvirt_conn):
for vm_name in vm_stop_list:
try:
vm = libvirt_conn.lookupByName(vm_name)
except libvirt.libvirtError as e:
# Error code 42 = Domain not found
if (e.get_error_code() == 42):
print(e)
exit(1)
else:
raise(e)
if vm.isActive():
print("Stopping VM '{}'".format(vm_name))
else:
print("VM '{}' is already stopped".format(vm_name))
seconds_waited = 0
while vm.isActive():
try:
vm.shutdown()
time.sleep(1)
seconds_waited += 1
if seconds_waited >= vm_force_stop_time:
print("Timeout was reached and VM '{}' hasn't stopped yet. Destroying...".format(vm_name), file = sys.stderr)
vm.destroy()
except libvirt.libvirtError as e:
# Error code 55 = Not valid operation: domain is not running
if (e.get_error_code() == 55):
pass
else:
raise(e)
print("All VMs have been stopped.")
#----------------------------------------------------------------------
def vm_status(libvirt_conn):
domNames = libvirt_conn.listDefinedDomains()
domIDs = libvirt_conn.listDomainsID()
if len(domIDs) != 0:
for domID in domIDs:
dom = libvirt_conn.lookupByID(domID)
domNames.append('{} (Running)'.format(dom.name()))
print("Status for all VMs (active and inactive domain names):")
print("-----------------------------")
if len(domNames) == 0:
print(' None')
else:
for domName in domNames:
print(' {}'.format(domName))
print("-----------------------------")
if __name__ == '__main__':
#connect to hypervisor running on localhost
conn = libvirt.open('qemu:///system')
if len(sys.argv) > 1:
if sys.argv[1] == 'start':
start_vms(conn)
elif sys.argv[1] == 'stop':
stop_vms(conn)
elif sys.argv[1] == 'restart':
stop_vms(conn)
start_vms(conn)
elif sys.argv[1] == 'status':
vm_status(conn)
elif len(sys.argv) == 1:
start_vms(conn)
conn.close()
exit(0)
[Unit]
Description=Start/Stops VMs in order with the script vm-boot-order.py
Requires=libvirtd.service
After=libvirtd.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/python /opt/vm-boot-order.py start
ExecStop=/usr/bin/python /opt/vm-boot-order.py stop
[Install]
WantedBy=multi-user.target
@guidot
Copy link

guidot commented Sep 25, 2018

Thanks for this!
I modified the script a little to separate config from code. Replace the two lines that set vm_start_list and vm_start_waiting_time_list with:

configfile = '/etc/vm-boot-order.conf'                                                                            
line_list = []                                                                                                    
vm_start_list = []                                                                                                
vm_start_waiting_time_list = []                                                                                   
                                                                                                                  
with open(configfile, "r") as config:                                                                             
    for line in config:                                                                                           
        if line.strip() and not line.lstrip().startswith('#'):                                                    
                line_list = line.split()                                                                          
                vm_start_list.append(line_list[0])                                                                
                try:                                                                                              
                    vm_start_waiting_time_list.append(int(line_list[1]))                                          
                except IndexError:                                                                                
                    vm_start_waiting_time_list.append(0)

/etc/vm-boot-order.conf then looks like this:

# List of virtual machines to start at boot time
#
# Order is significant
#
# Optionally add an integer separated by whitespace
# as waiting time before the next VM will be started.

logmon    5
dns
pbx     15
ldap     5
file      10
backup

This of course does not handle a different stop order, neither can you set the timeout this way.

@Aikhjarto
Copy link

Aikhjarto commented Feb 6, 2021

Thank you this nice script!

You may want to disable Python's buffering in your *.service file like this:

[Service]
Environment=PYTHONUNBUFFERED=1

Otherwise, print-outputs to stdout/stderr will be written to systemd's log with an extensive delay, i.e. when vm-boot-order.py exits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment