backup: use simple classes for data storage on restore too

This commit is contained in:
Marek Marczykowski-Górecki 2016-04-04 05:19:59 +02:00 committed by Wojtek Porczyk
parent 424d3054f3
commit 98c8b7cd22

View File

@ -1268,6 +1268,52 @@ class BackupRestore(object):
>>> restore_op.restore_do(restore_info) >>> restore_op.restore_do(restore_info)
""" """
class VMToRestore(object):
#: VM excluded from restore by user
EXCLUDED = object()
#: VM with such name already exists on the host
ALREADY_EXISTS = object()
#: NetVM used by the VM does not exists on the host
MISSING_NETVM = object()
#: TemplateVM used by the VM does not exists on the host
MISSING_TEMPLATE = object()
def __init__(self, vm):
self.vm = vm
if 'backup-path' in vm.features:
self.subdir = vm.features['backup-path']
else:
self.subdir = None
if 'backup-size' in vm.features and vm.features['backup-size']:
self.size = int(vm.features['backup-size'])
else:
self.size = 0
self.problems = set()
if hasattr(vm, 'template') and vm.template:
self.template = vm.template.name
else:
self.template = None
if vm.netvm:
self.netvm = vm.netvm.name
else:
self.netvm = None
self.rename_to = None
self.orig_template = None
@property
def good_to_go(self):
return len(self.problems) == 0
class Dom0ToRestore(VMToRestore):
#: backup was performed on system with different dom0 username
USERNAME_MISMATCH = object()
def __init__(self, vm, subdir=None):
super(BackupRestore.Dom0ToRestore, self).__init__(vm)
if subdir:
self.subdir = subdir
self.username = os.path.basename(subdir)
def __init__(self, app, backup_location, backup_vm, passphrase): def __init__(self, app, backup_location, backup_vm, passphrase):
super(BackupRestore, self).__init__() super(BackupRestore, self).__init__()
@ -1702,7 +1748,7 @@ class BackupRestore(object):
orig_name = orig_name[0:29] orig_name = orig_name[0:29]
new_name = orig_name new_name = orig_name
while (new_name in restore_info.keys() or while (new_name in restore_info.keys() or
new_name in map(lambda x: x.get('rename_to', None), new_name in map(lambda x: x.rename_to,
restore_info.values()) or restore_info.values()) or
new_name in self.app.domains): new_name in self.app.domains):
new_name = str('{}{}'.format(orig_name, number)) new_name = str('{}{}'.format(orig_name, number))
@ -1718,12 +1764,12 @@ class BackupRestore(object):
continue continue
vm_info = restore_info[vm] vm_info = restore_info[vm]
assert isinstance(vm_info, self.VMToRestore)
vm_info.pop('excluded', None) vm_info.problems.clear()
if vm in self.options.exclude: if vm in self.options.exclude:
vm_info['excluded'] = True vm_info.problems.add(self.VMToRestore.EXCLUDED)
vm_info.pop('already-exists', None)
if not self.options.verify_only and \ if not self.options.verify_only and \
vm in self.app.domains: vm in self.app.domains:
if self.options.rename_conflicting: if self.options.rename_conflicting:
@ -1731,16 +1777,15 @@ class BackupRestore(object):
vm, restore_info vm, restore_info
) )
if new_name is not None: if new_name is not None:
vm_info['rename-to'] = new_name vm_info.rename_to = new_name
else: else:
vm_info['already-exists'] = True vm_info.problems.add(self.VMToRestore.ALREADY_EXISTS)
else: else:
vm_info['already-exists'] = True vm_info.problems.add(self.VMToRestore.ALREADY_EXISTS)
# check template # check template
vm_info.pop('missing-template', None) if vm_info.template:
if vm_info['template']: template_name = vm_info.template
template_name = vm_info['template']
try: try:
host_template = self.app.domains[template_name] host_template = self.app.domains[template_name]
except KeyError: except KeyError:
@ -1748,18 +1793,18 @@ class BackupRestore(object):
if not host_template or not host_template.is_template(): if not host_template or not host_template.is_template():
# Maybe the (custom) template is in the backup? # Maybe the (custom) template is in the backup?
if not (template_name in restore_info.keys() and if not (template_name in restore_info.keys() and
restore_info[template_name]['vm'].is_template()): restore_info[template_name].vm.is_template()):
if self.options.use_default_template: if self.options.use_default_template:
if 'orig-template' not in vm_info.keys(): if vm_info.orig_template is None:
vm_info['orig-template'] = template_name vm_info.orig_template = template_name
vm_info['template'] = self.app.default_template.name vm_info.template = self.app.default_template.name
else: else:
vm_info['missing-template'] = True vm_info.problems.add(
self.VMToRestore.MISSING_TEMPLATE)
# check netvm # check netvm
vm_info.pop('missing-netvm', None) if vm_info.netvm:
if vm_info['netvm']: netvm_name = vm_info.netvm
netvm_name = vm_info['netvm']
try: try:
netvm_on_host = self.app.domains[netvm_name] netvm_on_host = self.app.domains[netvm_name]
@ -1771,39 +1816,34 @@ class BackupRestore(object):
# Maybe the (custom) netvm is in the backup? # Maybe the (custom) netvm is in the backup?
if not (netvm_name in restore_info.keys() and if not (netvm_name in restore_info.keys() and
restore_info[netvm_name]['vm'].is_netvm()): restore_info[netvm_name].vm.is_netvm()):
if self.options.use_default_netvm: if self.options.use_default_netvm:
if self.app.default_netvm: if self.app.default_netvm:
vm_info['netvm'] = self.app.default_netvm.name vm_info.netvm = self.app.default_netvm.name
else: else:
vm_info['netvm'] = None vm_info.netvm = None
vm_info['vm'].netvm = qubes.property.DEFAULT vm_info.vm.netvm = qubes.property.DEFAULT
elif self.options.use_none_netvm: elif self.options.use_none_netvm:
vm_info['netvm'] = None vm_info.netvm = None
else: else:
vm_info['missing-netvm'] = True vm_info.problems.add(self.VMToRestore.MISSING_NETVM)
vm_info['good-to-go'] = not any([(prop in vm_info.keys()) for
prop in ['missing-netvm',
'missing-template',
'already-exists',
'excluded']])
# update references to renamed VMs: # update references to renamed VMs:
for vm in restore_info.keys(): for vm in restore_info.keys():
if vm in ['dom0']: if vm in ['dom0']:
continue continue
vm_info = restore_info[vm] vm_info = restore_info[vm]
template_name = vm_info['template'] assert isinstance(vm_info, self.VMToRestore)
template_name = vm_info.template
if (template_name in restore_info and if (template_name in restore_info and
restore_info[template_name]['good-to-go'] and restore_info[template_name].good_to_go and
'rename-to' in restore_info[template_name]): restore_info[template_name].rename_to):
vm_info['template'] = restore_info[template_name]['rename-to'] vm_info.template = restore_info[template_name].rename_to
netvm_name = vm_info['netvm'] netvm_name = vm_info.netvm
if (netvm_name in restore_info and if (netvm_name in restore_info and
restore_info[netvm_name]['good-to-go'] and restore_info[netvm_name].good_to_go and
'rename-to' in restore_info[netvm_name]): restore_info[netvm_name].rename_to):
vm_info['netvm'] = restore_info[netvm_name]['rename-to'] vm_info.netvm = restore_info[netvm_name].rename_to
return restore_info return restore_info
@ -1860,24 +1900,16 @@ class BackupRestore(object):
if self._is_vm_included_in_backup(vm): if self._is_vm_included_in_backup(vm):
self.log.debug("{} is included in backup".format(vm.name)) self.log.debug("{} is included in backup".format(vm.name))
vms_to_restore[vm.name] = {} vms_to_restore[vm.name] = self.VMToRestore(vm)
vms_to_restore[vm.name]['vm'] = vm
if not hasattr(vm, 'template'): if hasattr(vm, 'template'):
vms_to_restore[vm.name]['template'] = None
else:
templatevm_name = self._find_template_name( templatevm_name = self._find_template_name(
vm.template.name) vm.template.name)
vms_to_restore[vm.name]['template'] = templatevm_name vms_to_restore[vm.name].template = templatevm_name
if vm.netvm is None: # Set to None to not confuse QubesVm object from backup
vms_to_restore[vm.name]['netvm'] = None # collection with host collection (further in clone_attrs).
else: vm.netvm = None
netvm_name = vm.netvm.name
vms_to_restore[vm.name]['netvm'] = netvm_name
# Set to None to not confuse QubesVm object from backup
# collection with host collection (further in clone_attrs).
vm.netvm = None
vms_to_restore = self.restore_info_verify(vms_to_restore) vms_to_restore = self.restore_info_verify(vms_to_restore)
@ -1885,29 +1917,18 @@ class BackupRestore(object):
if self.options.dom0_home and \ if self.options.dom0_home and \
self._is_vm_included_in_backup(self.backup_app.domains[0]): self._is_vm_included_in_backup(self.backup_app.domains[0]):
vm = self.backup_app.domains[0] vm = self.backup_app.domains[0]
vms_to_restore['dom0'] = {}
if self.header_data.version == 1: if self.header_data.version == 1:
vms_to_restore['dom0']['subdir'] = \ subdir = os.listdir(os.path.join(self.backup_location,
os.listdir(os.path.join( 'dom0-home'))[0]
self.backup_location, 'dom0-home'))[0]
vms_to_restore['dom0']['size'] = 0 # unknown
else: else:
vms_to_restore['dom0']['subdir'] = vm.features['backup-path'] subdir = None
vms_to_restore['dom0']['size'] = int(vm.features['backup-size']) vms_to_restore['dom0'] = self.Dom0ToRestore(vm, subdir)
local_user = grp.getgrnam('qubes').gr_mem[0] local_user = grp.getgrnam('qubes').gr_mem[0]
dom0_home = vms_to_restore['dom0']['subdir'] if vms_to_restore['dom0'].username != local_user:
if not self.options.ignore_username_mismatch:
vms_to_restore['dom0']['username'] = os.path.basename(dom0_home) vms_to_restore['dom0'].problems.add(
if vms_to_restore['dom0']['username'] != local_user: self.Dom0ToRestore.USERNAME_MISMATCH)
vms_to_restore['dom0']['username-mismatch'] = True
if self.options.ignore_username_mismatch:
vms_to_restore['dom0']['ignore-username-mismatch'] = True
else:
vms_to_restore['dom0']['good-to-go'] = False
if 'good-to-go' not in vms_to_restore['dom0']:
vms_to_restore['dom0']['good-to-go'] = True
return vms_to_restore return vms_to_restore
@ -1929,11 +1950,11 @@ class BackupRestore(object):
"updbl": {"func": "'Yes' if vm.updateable else ''"}, "updbl": {"func": "'Yes' if vm.updateable else ''"},
"template": {"func": "'n/a' if not hasattr(vm, 'template') is None " "template": {"func": "'n/a' if not hasattr(vm, 'template') is None "
"else vm_info['template']"}, "else vm_info.template"},
"netvm": {"func": "'n/a' if vm.is_netvm() and not vm.is_proxyvm() else\ "netvm": {"func": "'n/a' if vm.is_netvm() and not vm.is_proxyvm() else\
('*' if vm.property_is_default('netvm') else '') +\ ('*' if vm.property_is_default('netvm') else '') +\
vm_info['netvm'] if vm_info['netvm'] is not None " vm_info.netvm if vm_info.netvm is not None "
"else '-'"}, "else '-'"},
"label": {"func": "vm.label.name"}, "label": {"func": "vm.label.name"},
@ -1947,9 +1968,9 @@ class BackupRestore(object):
for f in fields_to_display: for f in fields_to_display:
fields[f]["max_width"] = len(f) fields[f]["max_width"] = len(f)
for vm_info in restore_info.values(): for vm_info in restore_info.values():
if 'vm' in vm_info.keys(): if vm_info.vm:
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
vm = vm_info['vm'] vm = vm_info.vm
l = len(unicode(eval(fields[f]["func"]))) l = len(unicode(eval(fields[f]["func"])))
if l > fields[f]["max_width"]: if l > fields[f]["max_width"]:
fields[f]["max_width"] = l fields[f]["max_width"] = l
@ -1977,34 +1998,37 @@ class BackupRestore(object):
summary += "\n" summary += "\n"
for vm_info in restore_info.values(): for vm_info in restore_info.values():
assert isinstance(vm_info, BackupRestore.VMToRestore)
# Skip non-VM here # Skip non-VM here
if 'vm' not in vm_info: if not vm_info.vm:
continue continue
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
vm = vm_info['vm'] vm = vm_info.vm
s = "" s = ""
for f in fields_to_display: for f in fields_to_display:
# noinspection PyTypeChecker # noinspection PyTypeChecker
fmt = "{{0:>{0}}} |".format(fields[f]["max_width"] + 1) fmt = "{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
s += fmt.format(eval(fields[f]["func"])) s += fmt.format(eval(fields[f]["func"]))
if 'excluded' in vm_info and vm_info['excluded']: if BackupRestore.VMToRestore.EXCLUDED in vm_info.problems:
s += " <-- Excluded from restore" s += " <-- Excluded from restore"
elif 'already-exists' in vm_info: elif BackupRestore.VMToRestore.ALREADY_EXISTS in vm_info.problems:
s += " <-- A VM with the same name already exists on the host!" s += " <-- A VM with the same name already exists on the host!"
elif 'missing-template' in vm_info: elif BackupRestore.VMToRestore.MISSING_TEMPLATE in \
vm_info.problems:
s += " <-- No matching template on the host " \ s += " <-- No matching template on the host " \
"or in the backup found!" "or in the backup found!"
elif 'missing-netvm' in vm_info: elif BackupRestore.VMToRestore.MISSING_NETVM in \
vm_info.problems:
s += " <-- No matching netvm on the host " \ s += " <-- No matching netvm on the host " \
"or in the backup found!" "or in the backup found!"
else: else:
if 'orig-template' in vm_info: if vm_info.orig_template:
s += " <-- Original template was '{}'".format( s += " <-- Original template was '{}'".format(
vm_info['orig-template']) vm_info.orig_template)
if 'rename-to' in vm_info: if vm_info.rename_to:
s += " <-- Will be renamed to '{}'".format( s += " <-- Will be renamed to '{}'".format(
vm_info['rename-to']) vm_info.rename_to)
summary += s + "\n" summary += s + "\n"
@ -2019,10 +2043,9 @@ class BackupRestore(object):
s += fmt.format("Home") s += fmt.format("Home")
else: else:
s += fmt.format("") s += fmt.format("")
if 'username-mismatch' in restore_info['dom0']: if BackupRestore.Dom0ToRestore.USERNAME_MISMATCH in \
restore_info['dom0'].problems:
s += " <-- username in backup and dom0 mismatch" s += " <-- username in backup and dom0 mismatch"
if 'ignore-username-mismatch' in restore_info['dom0']:
s += " (ignored)"
summary += s + "\n" summary += s + "\n"
@ -2049,11 +2072,12 @@ class BackupRestore(object):
vms_size = 0 vms_size = 0
vms = {} vms = {}
for vm_info in restore_info.values(): for vm_info in restore_info.values():
if 'vm' not in vm_info: assert isinstance(vm_info, self.VMToRestore)
if not vm_info.vm:
continue continue
if not vm_info['good-to-go']: if not vm_info.good_to_go:
continue continue
vm = vm_info['vm'] vm = vm_info.vm
if self.header_data.version >= 2: if self.header_data.version >= 2:
if vm.features['backup-size']: if vm.features['backup-size']:
vms_size += int(vm.features['backup-size']) vms_size += int(vm.features['backup-size'])
@ -2062,9 +2086,9 @@ class BackupRestore(object):
if self.header_data.version >= 2: if self.header_data.version >= 2:
if 'dom0' in restore_info.keys() and \ if 'dom0' in restore_info.keys() and \
restore_info['dom0']['good-to-go']: restore_info['dom0'].good_to_go:
vms_dirs.append(os.path.dirname(restore_info['dom0']['subdir'])) vms_dirs.append(os.path.dirname(restore_info['dom0'].subdir))
vms_size += restore_info['dom0']['size'] vms_size += restore_info['dom0'].size
try: try:
self._restore_vm_dirs(vms_dirs=vms_dirs, vms_size=vms_size) self._restore_vm_dirs(vms_dirs=vms_dirs, vms_size=vms_size)
@ -2108,14 +2132,14 @@ class BackupRestore(object):
kwargs = {} kwargs = {}
if hasattr(vm, 'template'): if hasattr(vm, 'template'):
if vm.template is not None: if vm.template is not None:
kwargs['template'] = restore_info[vm.name]['template'] kwargs['template'] = restore_info[vm.name].template
else: else:
kwargs['template'] = None kwargs['template'] = None
new_vm = None new_vm = None
vm_name = vm.name vm_name = vm.name
if 'rename-to' in restore_info[vm.name]: if restore_info[vm.name].rename_to:
vm_name = restore_info[vm.name]['rename-to'] vm_name = restore_info[vm.name].rename_to
try: try:
# first only minimal set, later clone_properties # first only minimal set, later clone_properties
@ -2197,8 +2221,8 @@ class BackupRestore(object):
# Set network dependencies - only non-default netvm setting # Set network dependencies - only non-default netvm setting
for vm in vms.values(): for vm in vms.values():
vm_name = vm.name vm_name = vm.name
if 'rename-to' in restore_info[vm.name]: if restore_info[vm.name].rename_to:
vm_name = restore_info[vm.name]['rename-to'] vm_name = restore_info[vm.name].rename_to
try: try:
host_vm = self.app.domains[vm_name] host_vm = self.app.domains[vm_name]
except KeyError: except KeyError:
@ -2206,8 +2230,8 @@ class BackupRestore(object):
continue continue
if not vm.property_is_default('netvm'): if not vm.property_is_default('netvm'):
if restore_info[vm.name]['netvm'] is not None: if restore_info[vm.name].netvm is not None:
host_vm.netvm = restore_info[vm.name]['netvm'] host_vm.netvm = restore_info[vm.name].netvm
else: else:
host_vm.netvm = None host_vm.netvm = None
@ -2221,8 +2245,8 @@ class BackupRestore(object):
raise BackupCanceledError("Restore canceled") raise BackupCanceledError("Restore canceled")
# ... and dom0 home as last step # ... and dom0 home as last step
if 'dom0' in restore_info.keys() and restore_info['dom0']['good-to-go']: if 'dom0' in restore_info.keys() and restore_info['dom0'].good_to_go:
backup_path = restore_info['dom0']['subdir'] backup_path = restore_info['dom0'].subdir
local_user = grp.getgrnam('qubes').gr_mem[0] local_user = grp.getgrnam('qubes').gr_mem[0]
home_dir = pwd.getpwnam(local_user).pw_dir home_dir = pwd.getpwnam(local_user).pw_dir
if self.header_data.version == 1: if self.header_data.version == 1: