k3nny
737f9bea38
All checks were successful
lint / docker (push) Successful in 9m14s
fix code passing ruff linter pre-commit ruff pre-commit ruff format
288 lines
12 KiB
Python
Executable File
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)
|