#!/usr/bin/python3 # -*- coding: utf-8 -*- # ----------------------------------------------------------------------- # This file is part of TISBackup # # TISBackup is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # TISBackup is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with TISBackup. If not, see . # # ----------------------------------------------------------------------- import os import datetime from .common import * from . import XenAPI import time import logging import re import os.path import os import datetime import select import urllib.request, urllib.error, urllib.parse import base64 import socket from stat import * import ssl if hasattr(ssl, '_create_unverified_context'): ssl._create_default_https_context = ssl._create_unverified_context class copy_vm_xcp(backup_generic): """Backup a VM running on a XCP server on a second SR (requires xe tools and XenAPI)""" type = 'copy-vm-xcp' required_params = backup_generic.required_params + ['server_name','storage_name','password_file','vm_name','network_name'] optional_params = backup_generic.optional_params + ['start_vm','max_copies', 'delete_snapshot', 'halt_vm'] start_vm = "no" max_copies = 1 halt_vm = "no" delete_snapshot = "yes" def read_config(self,iniconf): assert(isinstance(iniconf,ConfigParser)) backup_generic.read_config(self,iniconf) if self.start_vm in 'no' and iniconf.has_option('global','start_vm'): self.start_vm = iniconf.get('global','start_vm') if self.max_copies == 1 and iniconf.has_option('global','max_copies'): self.max_copies = iniconf.getint('global','max_copies') if self.delete_snapshot == "yes" and iniconf.has_option('global','delete_snapshot'): self.delete_snapshot = iniconf.get('global','delete_snapshot') def copy_vm_to_sr(self, vm_name, storage_name, dry_run, delete_snapshot="yes"): user_xen, password_xen, null = open(self.password_file).read().split('\n') session = XenAPI.Session('https://'+self.server_name) try: session.login_with_password(user_xen,password_xen) except XenAPI.Failure as error: msg,ip = error.details if msg == 'HOST_IS_SLAVE': server_name = ip session = XenAPI.Session('https://'+server_name) session.login_with_password(user_xen,password_xen) self.logger.debug("[%s] VM (%s) to backup in storage: %s",self.backup_name,vm_name,storage_name) now = datetime.datetime.now() #get storage opaqueRef try: storage = session.xenapi.SR.get_by_name_label(storage_name)[0] except IndexError as error: result = (1,"error get SR opaqueref %s"%(error)) return result #get vm to copy opaqueRef try: vm = session.xenapi.VM.get_by_name_label(vm_name)[0] except IndexError as error: result = (1,"error get VM opaqueref %s"%(error)) return result # get vm backup network opaqueRef try: networkRef = session.xenapi.network.get_by_name_label(self.network_name)[0] except IndexError as error: result = (1, "error get VM network opaqueref %s" % (error)) return result if str2bool(self.halt_vm): status_vm = session.xenapi.VM.get_power_state(vm) self.logger.debug("[%s] Status of VM: %s",self.backup_name,status_vm) if status_vm == "Running": self.logger.debug("[%s] Shutdown in progress",self.backup_name) if dry_run: print("session.xenapi.VM.clean_shutdown(vm)") else: session.xenapi.VM.clean_shutdown(vm) snapshot = vm else: #do the snapshot self.logger.debug("[%s] Snapshot in progress",self.backup_name) try: snapshot = session.xenapi.VM.snapshot(vm,"tisbackup-%s"%(vm_name)) except XenAPI.Failure as error: result = (1,"error when snapshot %s"%(error)) return result #get snapshot opaqueRef snapshot = session.xenapi.VM.get_by_name_label("tisbackup-%s"%(vm_name))[0] session.xenapi.VM.set_name_description(snapshot,"snapshot created by tisbackup on : %s"%(now.strftime("%Y-%m-%d %H:%M"))) vm_backup_name = "zzz-%s-"%(vm_name) #Check if old backup exit list_backups = [] for vm_ref in session.xenapi.VM.get_all(): name_lablel = session.xenapi.VM.get_name_label(vm_ref) if vm_backup_name in name_lablel: list_backups.append(name_lablel) list_backups.sort() if len(list_backups) >= 1: # Shutting last backup if started last_backup_vm = session.xenapi.VM.get_by_name_label(list_backups[-1])[0] if not "Halted" in session.xenapi.VM.get_power_state(last_backup_vm): self.logger.debug("[%s] Shutting down last backup vm : %s", self.backup_name, list_backups[-1] ) session.xenapi.VM.hard_shutdown(last_backup_vm) # Delete oldest backup if exist if len(list_backups) >= int(self.max_copies): for i in range(len(list_backups)-int(self.max_copies)+1): oldest_backup_vm = session.xenapi.VM.get_by_name_label(list_backups[i])[0] if not "Halted" in session.xenapi.VM.get_power_state(oldest_backup_vm): self.logger.debug("[%s] Shutting down old vm : %s", self.backup_name, list_backups[i] ) session.xenapi.VM.hard_shutdown(oldest_backup_vm) try: self.logger.debug("[%s] Deleting old vm : %s", self.backup_name, list_backups[i]) for vbd in session.xenapi.VM.get_VBDs(oldest_backup_vm): if session.xenapi.VBD.get_type(vbd) == 'CD'and session.xenapi.VBD.get_record(vbd)['empty'] == False: session.xenapi.VBD.eject(vbd) else: vdi = session.xenapi.VBD.get_VDI(vbd) if not 'NULL' in vdi: session.xenapi.VDI.destroy(vdi) session.xenapi.VM.destroy(oldest_backup_vm) except XenAPI.Failure as error: result = (1,"error when destroy old backup vm %s"%(error)) return result self.logger.debug("[%s] Copy %s in progress on %s",self.backup_name,vm_name,storage_name) try: backup_vm = session.xenapi.VM.copy(snapshot,vm_backup_name+now.strftime("%Y-%m-%d %H:%M"),storage) except XenAPI.Failure as error: result = (1,"error when copy %s"%(error)) return result # define VM as a template session.xenapi.VM.set_is_a_template(backup_vm,False) #change the network of the new VM try: vifDestroy = session.xenapi.VM.get_VIFs(backup_vm) except IndexError as error: result = (1,"error get VIF opaqueref %s"%(error)) return result for i in vifDestroy: vifRecord = session.xenapi.VIF.get_record(i) session.xenapi.VIF.destroy(i) data = {'MAC': vifRecord['MAC'], 'MAC_autogenerated': False, 'MTU': vifRecord['MTU'], 'VM': backup_vm, 'current_operations': vifRecord['current_operations'], 'currently_attached': vifRecord['currently_attached'], 'device': vifRecord['device'], 'ipv4_allowed': vifRecord['ipv4_allowed'], 'ipv6_allowed': vifRecord['ipv6_allowed'], 'locking_mode': vifRecord['locking_mode'], 'network': networkRef, 'other_config': vifRecord['other_config'], 'qos_algorithm_params': vifRecord['qos_algorithm_params'], 'qos_algorithm_type': vifRecord['qos_algorithm_type'], 'qos_supported_algorithms': vifRecord['qos_supported_algorithms'], 'runtime_properties': vifRecord['runtime_properties'], 'status_code': vifRecord['status_code'], 'status_detail': vifRecord['status_detail'] } try: session.xenapi.VIF.create(data) except Exception as error: result = (1,error) return result if self.start_vm in ['true', '1', 't', 'y', 'yes', 'oui']: session.xenapi.VM.start(backup_vm,False,True) session.xenapi.VM.set_name_description(backup_vm,"snapshot created by tisbackup on : %s"%(now.strftime("%Y-%m-%d %H:%M"))) size_backup = 0 for vbd in session.xenapi.VM.get_VBDs(backup_vm): if session.xenapi.VBD.get_type(vbd) == 'CD' and session.xenapi.VBD.get_record(vbd)['empty'] == False: session.xenapi.VBD.eject(vbd) else: vdi = session.xenapi.VBD.get_VDI(vbd) if not 'NULL' in vdi: size_backup = size_backup + int(session.xenapi.VDI.get_record(vdi)['physical_utilisation']) result = (0,size_backup) if self.delete_snapshot == 'no': return result #Disable automatic boot if 'auto_poweron' in session.xenapi.VM.get_other_config(backup_vm): session.xenapi.VM.remove_from_other_config(backup_vm, "auto_poweron") if not str2bool(self.halt_vm): #delete the snapshot try: for vbd in session.xenapi.VM.get_VBDs(snapshot): if session.xenapi.VBD.get_type(vbd) == 'CD' and session.xenapi.VBD.get_record(vbd)['empty'] == False: session.xenapi.VBD.eject(vbd) else: vdi = session.xenapi.VBD.get_VDI(vbd) if not 'NULL' in vdi: session.xenapi.VDI.destroy(vdi) session.xenapi.VM.destroy(snapshot) except XenAPI.Failure as error: result = (1,"error when destroy snapshot %s"%(error)) return result else: if status_vm == "Running": self.logger.debug("[%s] Starting in progress",self.backup_name) if dry_run: print("session.xenapi.VM.start(vm,False,True)") else: session.xenapi.VM.start(vm,False,True) return result def do_backup(self,stats): try: timestamp = int(time.time()) cmd = self.copy_vm_to_sr(self.vm_name, self.storage_name, self.dry_run, delete_snapshot=self.delete_snapshot) if cmd[0] == 0: timeExec = int(time.time()) - timestamp stats['log']='copy of %s to an other storage OK' % (self.backup_name) stats['status']='OK' stats['total_files_count'] = 1 stats['total_bytes'] = cmd[1] stats['backup_location'] = self.storage_name else: stats['status']='ERROR' stats['log']=cmd[1] except BaseException as e: stats['status']='ERROR' stats['log']=str(e) raise def register_existingbackups(self): """scan backup dir and insert stats in database""" #This backup is on target server, no data available on this server pass register_driver(copy_vm_xcp)