Skip to content

Instantly share code, notes, and snippets.

@nikiink
Last active December 11, 2018 19:47
Show Gist options
  • Save nikiink/97356430e6fd5025ca217afc00831ef6 to your computer and use it in GitHub Desktop.
Save nikiink/97356430e6fd5025ca217afc00831ef6 to your computer and use it in GitHub Desktop.
Python 3 script for backup Xenserver and XCP-ng VMs while running (tested with Xenserver 7.0 and XCP-ng 7.5 VMs)
six
http.client
beautifulsoup4
xenapi-python
# This script reads virtual machines to backup from an xml
# configuration file by default named XenBackup.xml.
# The configuration file can be given as parameter -config, for example
#
# XenBackup.py -config "XenBackup-test.xml"
#
# [If not given is used the file XenBackup.xml]
#
# The values of maxBackups and backupDir
# can be overridden on specific machine.
# This is an example:
#
# <?xml version="1.0"?>
# <XenBackup>
# <backupDir>C:\BACKUPS</backupDir>
# <maxBackups>2</maxBackups>
# <logFile>XenBackup.log</logFile>
# <server host="xenserver001" user="root" password="password">
# <vm name="TEST2"/>
# <!-- override maxBackups and backupDir -->
# <vm name="Test VM" maxBackups="2" backupDir="C:\BACKUPS2"/>
# <vm name="Ubuntu Xenial" />
# <vm name="Knoppix" />
# </server>
# <server host="xenserver002" user="root" password="password">
# <vm name="Knoppix2" />
# </server>
# </XenBackup>
# pip install beautifulsoup4
# pip install six
# pip install xenapi-python
# Python 3 required
import sys, os, glob, logging, argparse, ssl, http.client, XenAPI
from datetime import datetime
from bs4 import BeautifulSoup
#NO CERTIFICATE VALIDATION
ssl._create_default_https_context = ssl._create_unverified_context
config = None
def main(argv):
parser = argparse.ArgumentParser(description='XenBackup script to backup running VMs')
parser.add_argument('-config', default='XenBackup.xml', help='Configuration file for xenserver backup')
args = parser.parse_args(argv)
configFile = args.config
#Load configuration
global config
print ("Using config file: %s" % configFile)
with open(configFile) as fp:
config = BeautifulSoup(fp, features="html.parser")
#Init Log
logging.basicConfig(filename = config.logfile.string, level = logging.INFO, format = '%(asctime)s %(levelname)s %(message)s')
logging.info("Configuration loaded from %s" % configFile)
for server in config.find_all('server'):
#Connect to XenServer
session = XenAPI.Session("https://%s" % server['host'])
session.xenapi.login_with_password(server['user'], server['password'], "1.0", "XenBackup.py")
logging.info("CONNECTED TO SERVER: %s" % server['host'])
vms = server.find_all('vm')
for vm in vms:
logging.info("BACKUPING VM: %s" % vm['name'])
backupVm(session, server, vm)
cleanOldBackups(vm)
#Disconnect from XenServer
session.xenapi.session.logout()
logging.info("DISCONNECTED FROM SERVER: %s" % server['host'])
def backupVm(session, server, vm):
logging.info("backupVm %s %s" % (server['host'], vm['name']))
bckdir = config.backupdir.string
if (vm.has_attr('backupdir')): bckdir = vm['backupdir']
timestamp = datetime.now().strftime("%Y-%m-%dT%H%M%S")
snapshotName = "%s_bck_%s" % ( vm['name'], timestamp )
logging.info("Temporary snapshot name: %s" % snapshotName)
#Create temporary snapshot for hot backup
_vm = session.xenapi.VM.get_by_name_label(vm['name'])[0]
snapshot = session.xenapi.VM.snapshot(_vm, snapshotName)
#Set is-a-template and ha-always-run to false
session.xenapi.VM.set_is_a_template(snapshot, False)
session.xenapi.VM.set_ha_always_run(snapshot, False)
#Export snapshot
logging.info("Donwloading snapshot VM: %s (uuid: %s) to %s" % (session.xenapi.VM.get_name_label(snapshot), session.xenapi.VM.get_uuid(snapshot), bckdir) )
downloadVm(session, server['host'], session.xenapi.VM.get_uuid(snapshot), "%s/%s-%s.xva" % ( os.path.abspath(bckdir), vm['name'], timestamp ) )
#Get disk snapshots (for removal)
vdis = []
vbds = session.xenapi.VM.get_VBDs(snapshot)
for vbd in vbds:
vdbType = session.xenapi.VBD.get_type(vbd)
if vdbType == "Disk":
vdi = session.xenapi.VBD.get_VDI(vbd)
logging.info("Found VDI snapshot: %s (%s)" % (session.xenapi.VDI.get_name_label(vdi), session.xenapi.VDI.get_uuid(vdi)))
vdis.append(vdi)
#Remove vm snapshot (this does not remove disk snapshots)
session.xenapi.VM.destroy(snapshot)
#Remove disks snapshots
for vdi in vdis:
logging.info("Deleting snapshot disk: %s (%s)" % (session.xenapi.VDI.get_name_label(vdi), session.xenapi.VDI.get_uuid(vdi)))
session.xenapi.VDI.destroy(vdi)
def downloadVm(session, host, vmUuid, filename):
sessionId = session._session
conn = http.client.HTTPConnection(host)
conn.request("GET", "/export?session_id=%s&uuid=%s&use_compression=false" % (sessionId, vmUuid) );
resp = conn.getresponse()
#print(resp.status, resp.reason) # 200 OK
#reading data in chunks.
f = open(filename, 'wb')
while True:
data = resp.read(65536)
if not data: break
f.write( data ) # 65536 bytes
f.close()
def cleanOldBackups(vm):
logging.info("cleanOldBackups %s" % vm['name'])
maxbkps = config.maxbackups.string
bckdir = config.backupdir.string
if (vm.has_attr('maxbackups')): maxbkps = vm['maxbackups']
if (vm.has_attr('backupdir')): bckdir = vm['backupdir']
logging.info("MAXBACKUPS: %s, BACKUPDIR: %s" % (maxbkps, bckdir))
vmbackupfiles = sorted( glob.glob( "%s/%s-*.xva" % (os.path.abspath(bckdir), vm['name']) ) , reverse=True )
count = 0
for f in vmbackupfiles:
count += 1
logging.info(count)
if (count > int(maxbkps)):
logging.info("DELETING " + f)
os.remove(f)
else:
logging.info("LEAVING " + f)
if __name__ == "__main__":
main(sys.argv[1:])
<?xml version="1.0"?>
<XenBackup>
<backupDir>C:\BACKUPS</backupDir>
<maxBackups>2</maxBackups>
<logFile>XenBackup.log</logFile>
<server host="xenserver001" user="root" password="password">
<vm name="TEST2"/>
<!-- override maxBackups and backupDir -->
<vm name="Test VM" maxBackups="2" backupDir="C:\BACKUPS2"/>
<vm name="Ubuntu Xenial" />
<vm name="Knoppix" />
</server>
<server host="xenserver002" user="root" password="password">
<vm name="Knoppix2" />
</server>
</XenBackup>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment