#!/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 base64 import datetime import logging import os import os.path import re import select import socket import ssl import time import urllib.error import urllib.parse import urllib.request from stat import * from . import XenAPI from .common import * 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 "Halted" not 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 "Halted" not 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 not session.xenapi.VBD.get_record(vbd)["empty"]: session.xenapi.VBD.eject(vbd) else: vdi = session.xenapi.VBD.get_VDI(vbd) if "NULL" not 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 not session.xenapi.VBD.get_record(vbd)["empty"]: session.xenapi.VBD.eject(vbd) else: vdi = session.xenapi.VBD.get_VDI(vbd) if "NULL" not 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 not session.xenapi.VBD.get_record(vbd)["empty"]: session.xenapi.VBD.eject(vbd) else: vdi = session.xenapi.VBD.get_VDI(vbd) if "NULL" not 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)