Last active
December 11, 2018 19:47
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
six | |
http.client | |
beautifulsoup4 | |
xenapi-python |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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:]) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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