black and fix print statements.

x#	modified:   virt-back
This commit is contained in:
Russell Ballestrini 2024-07-07 16:34:51 -04:00
parent 3eeb196a84
commit b42a1161ca

577
virt-back
View file

@ -34,248 +34,271 @@ from shutil import move, copy2
try: try:
from operator import methodcaller from operator import methodcaller
except ImportError: except ImportError:
def methodcaller(name, *args, **kwargs): def methodcaller(name, *args, **kwargs):
def caller(obj): def caller(obj):
return getattr(obj, name)(*args, **kwargs) return getattr(obj, name)(*args, **kwargs)
return caller return caller
class Domfetcher(object): class Domfetcher(object):
"""Abstract libvirt API, supply methods to return dom object lists""" """Abstract libvirt API, supply methods to return dom object lists"""
def __init__(self, uri = None):
def __init__(self, uri=None):
"""Connect to hypervisor uri with read write access""" """Connect to hypervisor uri with read write access"""
# register logit as the error handler # register logit as the error handler
libvirt.registerErrorHandler( logit, 'libvirt error' ) libvirt.registerErrorHandler(logit, "libvirt error")
try: try:
self.c = libvirt.open( uri ) self.c = libvirt.open(uri)
except: except:
self.c = None self.c = None
logit( "libvirt error", "Failed to open connection to the hypervisor" ) logit("libvirt error", "Failed to open connection to the hypervisor")
def get_running_doms(self): def get_running_doms(self):
"""Return a list of running dom objects""" """Return a list of running dom objects"""
doms = [] doms = []
for id in self.c.listDomainsID(): # loop over the running ids for id in self.c.listDomainsID(): # loop over the running ids
dom = self.c.lookupByID( id ) # fetch dom object by id dom = self.c.lookupByID(id) # fetch dom object by id
if 'Domain-' not in dom.name(): # prevent actions on the Xen hypervisor if "Domain-" not in dom.name(): # prevent actions on the Xen hypervisor
doms.append( dom ) # append dom object to doms doms.append(dom) # append dom object to doms
return doms return doms
def get_shutoff_doms(self): def get_shutoff_doms(self):
"""Return a list of all shutoff but defined dom objects""" """Return a list of all shutoff but defined dom objects"""
return [self.c.lookupByName( name ) for name in self.c.listDefinedDomains()] return [self.c.lookupByName(name) for name in self.c.listDefinedDomains()]
def get_doms_by_names(self, guest_names): def get_doms_by_names(self, guest_names):
"""Accept a list of guest_names, return a list of related dom objects""" """Accept a list of guest_names, return a list of related dom objects"""
doms = [] doms = []
for name in guest_names: for name in guest_names:
try: try:
dom = self.c.lookupByName( name ) dom = self.c.lookupByName(name)
doms.append( dom ) doms.append(dom)
except libvirt.libvirtError: pass # logit reg'd as libvirt error handler except libvirt.libvirtError:
pass # logit reg'd as libvirt error handler
return doms return doms
def get_all_doms(self): def get_all_doms(self):
"""Return a list of all dom objects""" """Return a list of all dom objects"""
return self.get_running_doms() + self.get_shutoff_doms() return self.get_running_doms() + self.get_shutoff_doms()
def invoke( doms, method ):
def invoke(doms, method):
"""Pattern to invoke shutdown, destroy, and start on a list of doms""" """Pattern to invoke shutdown, destroy, and start on a list of doms"""
f = methodcaller( method ) f = methodcaller(method)
for dom in doms: for dom in doms:
try: try:
logit( method, 'invoking %s on %s' % (method, dom.name()) ) logit(method, "invoking %s on %s" % (method, dom.name()))
retcode = f(dom) retcode = f(dom)
if retcode: # log retcode if retcode: # log retcode
logit( logit(
method, method,
'{0} returned {1} on {2}'.format( method, retcode, dom.name() ) "{0} returned {1} on {2}".format(method, retcode, dom.name()),
) )
except libvirt.libvirtError: except libvirt.libvirtError:
pass pass
def backup( doms ):
def backup(doms):
"""Accept a list of dom objects, run backup proceedure on each""" """Accept a list of dom objects, run backup proceedure on each"""
for dom in doms: for dom in doms:
recreate = dom.isActive() recreate = dom.isActive()
if dom.isActive(): # if dom is active, shutdown if dom.isActive(): # if dom is active, shutdown
shutdown([dom]) shutdown([dom])
if dom.isActive(): # if dom is active, error
logit(
'error',
'unable to shutdown or destroy %s and BACKUP FAILED!' % dom.name()
)
continue # skip to the next dom
xml = dom.XMLDesc( 0 ) if dom.isActive(): # if dom is active, error
xmlfile = path.join( options.backpath, dom.name() + '.xml' ) logit(
f = open( xmlfile, 'w') "error",
f.write( xml ) "unable to shutdown or destroy %s and BACKUP FAILED!" % dom.name(),
)
continue # skip to the next dom
xml = dom.XMLDesc(0)
xmlfile = path.join(options.backpath, dom.name() + ".xml")
f = open(xmlfile, "w")
f.write(xml)
f.close() f.close()
disklist = findall( "<source file='(.*)'/>\n", xml ) disklist = findall("<source file='(.*)'/>\n", xml)
logit( 'backup', 'invoking backup for ' + dom.name() ) logit("backup", "invoking backup for " + dom.name())
for disk_source in disklist:
disk_file = disk_source.split('/')[-1]
disk_dest = path.join( options.backpath, disk_file )
logit(
'backup',
'copying %s to %s for %s' % (disk_source, disk_dest, dom.name())
)
copy2( disk_source, disk_dest )
if recreate: # if true, start guest after backup
create([dom]) # start dom
ext, tarmode = '.tar.gz', 'w:gz'
if options.nogzip:
ext, tarmode = '.tar', 'w'
tarfilename = dom.name() + ext
if options.tardate:
tarfilename = dom.name() + '-' + TODAY + ext
tarpath = path.join( options.backpath, tarfilename )
if path.isfile( tarpath ): # if file exists, run rotate
logit( 'backup', 'rotating backup files for ' + dom.name() )
rotate( tarpath, options.retention )
logit(
'backup',
'archiving files for %s to %s' % (dom.name(), tarpath)
)
tar = tarfile.open( tarpath, tarmode )
logit(
'backup',
'archiving %s for %s' % (xmlfile, dom.name())
)
tar.add( xmlfile ) # add xml to tar
remove( xmlfile ) # cleanup tmp files
for disk_source in disklist: for disk_source in disklist:
disk_file = disk_source.split('/')[-1] disk_file = disk_source.split("/")[-1]
disk_dest = path.join( options.backpath, disk_file ) disk_dest = path.join(options.backpath, disk_file)
logit( logit(
'backup', "backup",
'archiving %s for %s' % (disk_dest, dom.name()) "copying %s to %s for %s" % (disk_source, disk_dest, dom.name()),
) )
tar.add( disk_dest ) # add img to tar copy2(disk_source, disk_dest)
remove( disk_dest ) # cleanup tmp files
if recreate: # if true, start guest after backup
create([dom]) # start dom
ext, tarmode = ".tar.gz", "w:gz"
if options.nogzip:
ext, tarmode = ".tar", "w"
tarfilename = dom.name() + ext
if options.tardate:
tarfilename = dom.name() + "-" + TODAY + ext
tarpath = path.join(options.backpath, tarfilename)
if path.isfile(tarpath): # if file exists, run rotate
logit("backup", "rotating backup files for " + dom.name())
rotate(tarpath, options.retention)
logit("backup", "archiving files for %s to %s" % (dom.name(), tarpath))
tar = tarfile.open(tarpath, tarmode)
logit("backup", "archiving %s for %s" % (xmlfile, dom.name()))
tar.add(xmlfile) # add xml to tar
remove(xmlfile) # cleanup tmp files
for disk_source in disklist:
disk_file = disk_source.split("/")[-1]
disk_dest = path.join(options.backpath, disk_file)
logit("backup", "archiving %s for %s" % (disk_dest, dom.name()))
tar.add(disk_dest) # add img to tar
remove(disk_dest) # cleanup tmp files
tar.close() tar.close()
logit( 'backup', 'finished backup for ' + dom.name() ) logit("backup", "finished backup for " + dom.name())
def shutdown( doms, wait=180 ):
def shutdown(doms, wait=180):
"""Accept a list of dom objects, attempt to shutdown the active ones""" """Accept a list of dom objects, attempt to shutdown the active ones"""
# get all running guests from list and invoke shutdown # get all running guests from list and invoke shutdown
invoke( get_all_running( doms ), 'shutdown' ) invoke(get_all_running(doms), "shutdown")
"""loop until all guests are shut off or destroy """loop until all guests are shut off or destroy
all active guests if wait timer is reached.""" all active guests if wait timer is reached."""
secs = 10 secs = 10
wait /= secs # divide wait by secs wait /= secs # divide wait by secs
for i in range( 0, wait + 1 ): for i in range(0, wait + 1):
# if all doms are shut off, leave loop # if all doms are shut off, leave loop
if check_all_shutoff( doms ): if check_all_shutoff(doms):
break break
else: else:
logit( logit(
'shutdown', "shutdown",
'waited ' + str( i * secs ) + ' seconds for ' + "waited "
', '.join( dom.name() for dom in get_all_running( doms ) ) + + str(i * secs)
' to shut off' + " seconds for "
+ ", ".join(dom.name() for dom in get_all_running(doms))
+ " to shut off",
) )
# if the wait time is reached, destroy all active doms # if the wait time is reached, destroy all active doms
if i == wait: if i == wait:
invoke( get_all_running( doms ) ,'destroy' ) invoke(get_all_running(doms), "destroy")
sleep( secs )
def create( doms ): sleep(secs)
def create(doms):
"""Accept a list of dom objects, attempt to start the inactive ones""" """Accept a list of dom objects, attempt to start the inactive ones"""
# get all shutoff guests from list and invoke create # get all shutoff guests from list and invoke create
invoke( get_all_shutoff( doms ), 'create' ) invoke(get_all_shutoff(doms), "create")
def reboot( doms ):
def reboot(doms):
"""Accept a list of dom objects, attempt to shutdown then start""" """Accept a list of dom objects, attempt to shutdown then start"""
shutdown( doms ) shutdown(doms)
create( doms ) create(doms)
def info( doms ):
def info(doms):
"""Accept a list of dom objects, attempt to display info for all""" """Accept a list of dom objects, attempt to display info for all"""
#invoke( doms, 'name' ) # invoke( doms, 'name' )
#invoke( doms, 'info') # invoke( doms, 'info')
if check_all_running( doms ): print "NOTE: All guests are running" if check_all_running(doms):
if check_all_shutoff( doms ): print "NOTE: All guests are shut off" print("NOTE: All guests are running")
if check_all_shutoff(doms):
print '' print("NOTE: All guests are shut off")
print 'running guests: ' + ', '.join( [ dom.name() for dom in get_all_running( doms ) ] )
print 'shutoff guests: ' + ', '.join( [ dom.name() for dom in get_all_shutoff( doms ) ] ) print("")
print '' print("running guests: " + ", ".join([dom.name() for dom in get_all_running(doms)]))
print 'DomName'.ljust(16) + 'Memory MB'.rjust(12) + 'vCPUs'.rjust(8) + 'CPUtime ms'.rjust(18) print("shutoff guests: " + ", ".join([dom.name() for dom in get_all_shutoff(doms)]))
print '======================================================' print("")
print(
"DomName".ljust(16)
+ "Memory MB".rjust(12)
+ "vCPUs".rjust(8)
+ "CPUtime ms".rjust(18)
)
print("======================================================")
for dom in doms: for dom in doms:
name = dom.name() name = dom.name()
rams = str(dom.info()[2]/1024) + '/' + str(dom.info()[1]/1024) rams = str(dom.info()[2] / 1024) + "/" + str(dom.info()[1] / 1024)
cpus = str(dom.info()[3]) cpus = str(dom.info()[3])
time = str(dom.info()[4]/1000000) time = str(dom.info()[4] / 1000000)
print name.ljust(16) + rams.rjust(12) + cpus.rjust(8) + time.rjust(18) print(name.ljust(16) + rams.rjust(12) + cpus.rjust(8) + time.rjust(18))
def check_all_running( doms ):
def check_all_running(doms):
"""Accept a list of dom objects, check if all guest dom are active""" """Accept a list of dom objects, check if all guest dom are active"""
if sum( [dom.isActive() for dom in doms] ) == len( doms ): if sum([dom.isActive() for dom in doms]) == len(doms):
return True return True
return False return False
def check_all_shutoff( doms ):
def check_all_shutoff(doms):
"""Accept a list of dom objects, check if all guest dom are shut off""" """Accept a list of dom objects, check if all guest dom are shut off"""
if sum( [dom.isActive() for dom in doms] ): if sum([dom.isActive() for dom in doms]):
return False return False
return True return True
def get_all_running( doms ):
def get_all_running(doms):
"""Accept a list of dom objects, return a list of running dom objects""" """Accept a list of dom objects, return a list of running dom objects"""
return [ dom for dom in doms if dom.isActive() ] return [dom for dom in doms if dom.isActive()]
def get_all_shutoff( doms ):
def get_all_shutoff(doms):
"""Accept a list of dom objects, return a list of shutoff dom objects""" """Accept a list of dom objects, return a list of shutoff dom objects"""
return [ dom for dom in doms if not dom.isActive() ] return [dom for dom in doms if not dom.isActive()]
def logit( context, message, quiet = False ):
def logit(context, message, quiet=False):
"""syslog and error handler""" """syslog and error handler"""
if type( message ) is tuple: if type(message) is tuple:
message = message[2] # libvirt message is a tuple message = message[2] # libvirt message is a tuple
try: quiet = options.quiet try:
except NameError: pass quiet = options.quiet
except NameError:
pass
if quiet: pass if quiet:
else: print context + ': ' + message pass
else:
print(context + ": " + message)
syslog.openlog( 'virt-back', 0, syslog.LOG_LOCAL3 ) syslog.openlog("virt-back", 0, syslog.LOG_LOCAL3)
syslog.syslog( message ) syslog.syslog(message)
syslog.closelog() syslog.closelog()
def rotate( target, retention = 3 ):
def rotate(target, retention=3):
"""file rotation routine""" """file rotation routine"""
for i in range( retention-2, 0, -1 ): # count backwards for i in range(retention - 2, 0, -1): # count backwards
old_name = "%s.%s" % ( target, i ) old_name = "%s.%s" % (target, i)
new_name = "%s.%s" % ( target, i + 1 ) new_name = "%s.%s" % (target, i + 1)
try: move( old_name, new_name ) try:
except IOError: pass move(old_name, new_name)
move( target, target + '.1' ) except IOError:
pass
move(target, target + ".1")
def getoptions(): def getoptions():
"""Fetch cli args, parse and map to python, test sanity""" """Fetch cli args, parse and map to python, test sanity"""
@ -283,101 +306,219 @@ def getoptions():
# create an option parcer object # create an option parcer object
p = OptionParser() p = OptionParser()
p.set_description( DESCRIPTION ) p.set_description(DESCRIPTION)
p.add_option( '-q', '--quiet', dest='quiet', action='store_true', default=False,
help='prevent output to stdout' )
p.add_option( '-d', '--date', dest='tardate', action='store_true', default=False, p.add_option(
help='append date to tar filename [default: no date]' ) "-q",
"--quiet",
dest="quiet",
action="store_true",
default=False,
help="prevent output to stdout",
)
p.add_option( '-g', '--no-gzip', dest='nogzip', action='store_true', default=False, p.add_option(
help='do not gzip the resulting tar file' ) "-d",
"--date",
dest="tardate",
action="store_true",
default=False,
help="append date to tar filename [default: no date]",
)
p.add_option( '-a', '--retention', dest='retention', metavar="amount", default=3, p.add_option(
type='int', help="backups to retain [default: 3]" ) "-g",
"--no-gzip",
dest="nogzip",
action="store_true",
default=False,
help="do not gzip the resulting tar file",
)
p.add_option( '-p', '--path', dest='backpath', metavar="'PATH'", default='/KVMBACK', p.add_option(
help="backup path [default: '/KVMBACK']" ) "-a",
"--retention",
p.add_option( '-u', '--uri', dest='uri', metavar="'URI'", dest="retention",
help="optional hypervisor uri" ) metavar="amount",
default=3,
type="int",
help="backups to retain [default: 3]",
)
p.add_option(
"-p",
"--path",
dest="backpath",
metavar="'PATH'",
default="/KVMBACK",
help="backup path [default: '/KVMBACK']",
)
p.add_option(
"-u", "--uri", dest="uri", metavar="'URI'", help="optional hypervisor uri"
)
# Actions for info testing: These options display info/ test a list of guests only. # Actions for info testing: These options display info/ test a list of guests only.
g0 = OptionGroup( p, "Actions for info testing", "These options display info or test a list of guests." ) g0 = OptionGroup(
p,
g0.add_option( '-i', '--info', dest='info', action='store_true', default=False, "Actions for info testing",
help='info/test a list of guests (space delimited dom names)' ) "These options display info or test a list of guests.",
)
g0.add_option( '--info-all', dest='infoall', action='store_true', default=False,
help='attempt to show info on ALL guests' ) g0.add_option(
"-i",
"--info",
dest="info",
action="store_true",
default=False,
help="info/test a list of guests (space delimited dom names)",
)
g0.add_option(
"--info-all",
dest="infoall",
action="store_true",
default=False,
help="attempt to show info on ALL guests",
)
# WARNING: Dangerous options below, option grouping for scary actions # WARNING: Dangerous options below, option grouping for scary actions
g1 = OptionGroup( p, "Actions for a list of dom names", "WARNING: These options WILL bring down guests!" )
g1.add_option( '-b', '--backup', dest='backup', action='store_true', default=False,
help='backup a list of guests (space delimited dom names)' )
g1.add_option( '-r', '--reboot', dest='reboot', action='store_true', default=False,
help='reboot a list of guests (space delimited dom names)' )
g1.add_option( '-s', '--shutdown', dest='shutdown', action='store_true', default=False,
help='shutdown a list of guests (space delimited dom names)' )
g1.add_option( '-c', '--create', dest='create', action='store_true', default=False,
help='start a list of guests (space delimited dom names)' )
g2 = OptionGroup( p, "Actions for all doms", "WARNING: These options WILL bring down ALL guests!" )
g2.add_option( '--backup-all', dest='backupall', action='store_true', default=False, g1 = OptionGroup(
help='attempt to shutdown, backup, and start ALL guests' ) p,
"Actions for a list of dom names",
"WARNING: These options WILL bring down guests!",
)
g2.add_option( '--reboot-all', dest='rebootall', action='store_true', default=False, g1.add_option(
help='attempt to shutdown and then start ALL guests' ) "-b",
"--backup",
g2.add_option( '--shutdown-all', dest='shutdownall', action='store_true', default=False, dest="backup",
help='attempt to shutdown ALL guests' ) action="store_true",
default=False,
help="backup a list of guests (space delimited dom names)",
)
g2.add_option( '--create-all', dest='createall', action='store_true', default=False, g1.add_option(
help='attempt to start ALL guests' ) "-r",
"--reboot",
dest="reboot",
action="store_true",
default=False,
help="reboot a list of guests (space delimited dom names)",
)
g1.add_option(
"-s",
"--shutdown",
dest="shutdown",
action="store_true",
default=False,
help="shutdown a list of guests (space delimited dom names)",
)
g1.add_option(
"-c",
"--create",
dest="create",
action="store_true",
default=False,
help="start a list of guests (space delimited dom names)",
)
g2 = OptionGroup(
p, "Actions for all doms", "WARNING: These options WILL bring down ALL guests!"
)
g2.add_option(
"--backup-all",
dest="backupall",
action="store_true",
default=False,
help="attempt to shutdown, backup, and start ALL guests",
)
g2.add_option(
"--reboot-all",
dest="rebootall",
action="store_true",
default=False,
help="attempt to shutdown and then start ALL guests",
)
g2.add_option(
"--shutdown-all",
dest="shutdownall",
action="store_true",
default=False,
help="attempt to shutdown ALL guests",
)
g2.add_option(
"--create-all",
dest="createall",
action="store_true",
default=False,
help="attempt to start ALL guests",
)
# attach groups g1 and g2 to the parser p object # attach groups g1 and g2 to the parser p object
p.add_option_group( g0 ) p.add_option_group(g0)
p.add_option_group( g1 ) p.add_option_group(g1)
p.add_option_group( g2 ) p.add_option_group(g2)
# parse options and args # parse options and args
options, guest_names = p.parse_args() options, guest_names = p.parse_args()
# the actionsum should be 1 to continue, bool math ftw # the actionsum should be 1 to continue, bool math ftw
actions = [ options.backup, options.reboot, options.shutdown, actions = [
options.create, options.info, options.backupall, options.rebootall, options.backup,
options.shutdownall, options.createall, options.infoall, ] options.reboot,
actionsum = sum( actions ) options.shutdown,
options.create,
options.info,
options.backupall,
options.rebootall,
options.shutdownall,
options.createall,
options.infoall,
]
actionsum = sum(actions)
if actionsum == 1: if actionsum == 1:
return options, guest_names return options, guest_names
else: else:
exit("\nYou must have 1 action, no more, no less.\n\nRun 'virt-back --help' for help.\n") exit(
"\nYou must have 1 action, no more, no less.\n\nRun 'virt-back --help' for help.\n"
)
if __name__ == '__main__':
TODAY = str( date.today() ) if __name__ == "__main__":
TODAY = str(date.today())
# Get the list of options and list of guest_names from cli # Get the list of options and list of guest_names from cli
options, guest_names = getoptions() options, guest_names = getoptions()
# connect to hypervisor with Domfetcher (read-only) # connect to hypervisor with Domfetcher (read-only)
domfetcher = Domfetcher( options.uri ) domfetcher = Domfetcher(options.uri)
if options.backup or options.reboot or options.shutdown or options.create or options.info: if (
doms = domfetcher.get_doms_by_names( guest_names ) options.backup
else: or options.reboot
or options.shutdown
or options.create
or options.info
):
doms = domfetcher.get_doms_by_names(guest_names)
else:
doms = domfetcher.get_all_doms() doms = domfetcher.get_all_doms()
if options.backup or options.backupall: backup( doms )
if options.reboot or options.rebootall: reboot( doms )
if options.shutdown or options.shutdownall: shutdown( doms )
if options.create or options.createall: create( doms )
if options.info or options.infoall: info( doms )
if options.backup or options.backupall:
backup(doms)
if options.reboot or options.rebootall:
reboot(doms)
if options.shutdown or options.shutdownall:
shutdown(doms)
if options.create or options.createall:
create(doms)
if options.info or options.infoall:
info(doms)