TISbackup/libtisbackup/copy_vm_xcp.py
k3nny 737f9bea38
All checks were successful
lint / docker (push) Successful in 9m14s
fix iniparse
fix code passing ruff linter
pre-commit ruff
pre-commit ruff format
2024-11-29 23:45:40 +01:00

288 lines
12 KiB
Python
Executable File

#!/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 <http://www.gnu.org/licenses/>.
#
# -----------------------------------------------------------------------
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)