initial commit from tarball, likely doesn't work at all...
new file: .gitignore new file: setup.cfg new file: setup.py new file: virt-back new file: virt_back.egg-info/PKG-INFO new file: virt_back.egg-info/SOURCES.txt new file: virt_back.egg-info/dependency_links.txt new file: virt_back.egg-info/top_level.txt new file: virtback.py
This commit is contained in:
commit
338c565c35
9 changed files with 466 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
venv
|
||||
5
setup.cfg
Normal file
5
setup.cfg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
||||
49
setup.py
Normal file
49
setup.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# installation:
|
||||
# * pip install virt-back
|
||||
# * easy_install virt-back
|
||||
from setuptools import setup
|
||||
|
||||
DESCRIPTION = """A backup utility for QEMU, KVM, XEN, and Virtualbox guests.
|
||||
Virt-back is a python application that uses the libvirt api to safely
|
||||
shutdown, gzip, and restart guests. The backup process logs to syslog
|
||||
for auditing and virt-back works great with cron for scheduling outages.
|
||||
Virt-back has been placed in the public domain and
|
||||
the latest version may be downloaded here:
|
||||
https://bitbucket.org/russellballestrini/virt-back
|
||||
"""
|
||||
|
||||
setup(
|
||||
name = 'virt-back',
|
||||
version = '0.1.0',
|
||||
description = 'virt-back: A backup utility for QEMU, KVM, XEN, and Virtualbox guests',
|
||||
keywords = 'backup virtual hypervisor QEMU KVM XEN Virtualbox',
|
||||
long_description = DESCRIPTION,
|
||||
|
||||
author = 'Russell Ballestrini',
|
||||
author_email = 'russell@ballestrini.net',
|
||||
url = 'https://bitbucket.org/russellballestrini/virt-back',
|
||||
|
||||
platforms = ['All'],
|
||||
license = 'Public Domain',
|
||||
|
||||
py_modules = ['virtback'],
|
||||
include_package_data = True,
|
||||
|
||||
scripts=['virt-back'],
|
||||
)
|
||||
|
||||
"""
|
||||
setup()
|
||||
keyword args: http://peak.telecommunity.com/DevCenter/setuptools
|
||||
|
||||
configure pypi username and password in ~/.pypirc::
|
||||
|
||||
[pypi]
|
||||
username:
|
||||
password:
|
||||
|
||||
|
||||
build and upload to pypi with this::
|
||||
|
||||
python setup.py sdist bdist_egg register upload
|
||||
"""
|
||||
383
virt-back
Executable file
383
virt-back
Executable file
|
|
@ -0,0 +1,383 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
############################################
|
||||
# Program: virt-back.py
|
||||
# Author: Russell.Ballestrini.net
|
||||
# Date: Fri Mar 18 15:56:59 EDT 2011
|
||||
# License: Public Domain
|
||||
############################################
|
||||
|
||||
DESCRIPTION = """A backup utility for QEMU, KVM, XEN, and Virtualbox guests.
|
||||
Virt-back is a python application that uses the libvirt api to safely
|
||||
shutdown, gzip, and restart guests. The backup process logs to syslog
|
||||
for auditing and virt-back works great with cron for scheduling outages.
|
||||
Virt-back has been placed in the public domain and
|
||||
the latest version may be downloaded here:
|
||||
https://bitbucket.org/russellballestrini/virt-back
|
||||
"""
|
||||
|
||||
"""The varible doms represents a list of libvirt dom objects.
|
||||
Use the Domfetcher class to aquire lists of dom objects."""
|
||||
|
||||
import libvirt
|
||||
import tarfile
|
||||
import syslog
|
||||
from re import findall
|
||||
from time import sleep
|
||||
from datetime import date
|
||||
from sys import exit
|
||||
from optparse import OptionParser, OptionGroup
|
||||
from os import path
|
||||
from os import remove
|
||||
from shutil import move, copy2
|
||||
|
||||
try:
|
||||
from operator import methodcaller
|
||||
except ImportError:
|
||||
def methodcaller(name, *args, **kwargs):
|
||||
def caller(obj):
|
||||
return getattr(obj, name)(*args, **kwargs)
|
||||
return caller
|
||||
|
||||
class Domfetcher(object):
|
||||
"""Abstract libvirt API, supply methods to return dom object lists"""
|
||||
def __init__(self, uri = None):
|
||||
"""Connect to hypervisor uri with read write access"""
|
||||
# register logit as the error handler
|
||||
libvirt.registerErrorHandler( logit, 'libvirt error' )
|
||||
|
||||
try:
|
||||
self.c = libvirt.open( uri )
|
||||
except:
|
||||
self.c = None
|
||||
logit( "libvirt error", "Failed to open connection to the hypervisor" )
|
||||
|
||||
def get_running_doms(self):
|
||||
"""Return a list of running dom objects"""
|
||||
doms = []
|
||||
for id in self.c.listDomainsID(): # loop over the running ids
|
||||
dom = self.c.lookupByID( id ) # fetch dom object by id
|
||||
if 'Domain-' not in dom.name(): # prevent actions on the Xen hypervisor
|
||||
doms.append( dom ) # append dom object to doms
|
||||
return doms
|
||||
|
||||
def get_shutoff_doms(self):
|
||||
"""Return a list of all shutoff but defined dom objects"""
|
||||
return [self.c.lookupByName( name ) for name in self.c.listDefinedDomains()]
|
||||
|
||||
def get_doms_by_names(self, guest_names):
|
||||
"""Accept a list of guest_names, return a list of related dom objects"""
|
||||
doms = []
|
||||
for name in guest_names:
|
||||
try:
|
||||
dom = self.c.lookupByName( name )
|
||||
doms.append( dom )
|
||||
except libvirt.libvirtError: pass # logit reg'd as libvirt error handler
|
||||
return doms
|
||||
|
||||
def get_all_doms(self):
|
||||
"""Return a list of all dom objects"""
|
||||
return self.get_running_doms() + self.get_shutoff_doms()
|
||||
|
||||
|
||||
def invoke( doms, method ):
|
||||
"""Pattern to invoke shutdown, destroy, and start on a list of doms"""
|
||||
f = methodcaller( method )
|
||||
for dom in doms:
|
||||
try:
|
||||
logit( method, 'invoking %s on %s' % (method, dom.name()) )
|
||||
retcode = f(dom)
|
||||
if retcode: # log retcode
|
||||
logit(
|
||||
method,
|
||||
'{0} returned {1} on {2}'.format( method, retcode, dom.name() )
|
||||
)
|
||||
except libvirt.libvirtError:
|
||||
pass
|
||||
|
||||
def backup( doms ):
|
||||
"""Accept a list of dom objects, run backup proceedure on each"""
|
||||
for dom in doms:
|
||||
recreate = dom.isActive()
|
||||
|
||||
if dom.isActive(): # if dom is active, shutdown
|
||||
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 )
|
||||
xmlfile = path.join( options.backpath, dom.name() + '.xml' )
|
||||
f = open( xmlfile, 'w')
|
||||
f.write( xml )
|
||||
f.close()
|
||||
disklist = findall( "<source file='(.*)'/>\n", xml )
|
||||
|
||||
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:
|
||||
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()
|
||||
|
||||
logit( 'backup', 'finished backup for ' + dom.name() )
|
||||
|
||||
def shutdown( doms, wait=180 ):
|
||||
"""Accept a list of dom objects, attempt to shutdown the active ones"""
|
||||
# get all running guests from list and invoke shutdown
|
||||
invoke( get_all_running( doms ), 'shutdown' )
|
||||
|
||||
"""loop until all guests are shut off or destroy
|
||||
all active guests if wait timer is reached."""
|
||||
|
||||
secs = 10
|
||||
wait /= secs # divide wait by secs
|
||||
|
||||
for i in range( 0, wait + 1 ):
|
||||
|
||||
# if all doms are shut off, leave loop
|
||||
if check_all_shutoff( doms ):
|
||||
break
|
||||
else:
|
||||
logit(
|
||||
'shutdown',
|
||||
'waited ' + str( i * secs ) + ' 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 i == wait:
|
||||
invoke( get_all_running( doms ) ,'destroy' )
|
||||
|
||||
sleep( secs )
|
||||
|
||||
def create( doms ):
|
||||
"""Accept a list of dom objects, attempt to start the inactive ones"""
|
||||
# get all shutoff guests from list and invoke create
|
||||
invoke( get_all_shutoff( doms ), 'create' )
|
||||
|
||||
def reboot( doms ):
|
||||
"""Accept a list of dom objects, attempt to shutdown then start"""
|
||||
shutdown( doms )
|
||||
create( doms )
|
||||
|
||||
def info( doms ):
|
||||
"""Accept a list of dom objects, attempt to display info for all"""
|
||||
#invoke( doms, 'name' )
|
||||
#invoke( doms, 'info')
|
||||
if check_all_running( doms ): print "NOTE: All guests are running"
|
||||
if check_all_shutoff( doms ): print "NOTE: All guests are shut off"
|
||||
|
||||
print ''
|
||||
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 'DomName'.ljust(16) + 'Memory MB'.rjust(12) + 'vCPUs'.rjust(8) + 'CPUtime ms'.rjust(18)
|
||||
print '======================================================'
|
||||
for dom in doms:
|
||||
name = dom.name()
|
||||
rams = str(dom.info()[2]/1024) + '/' + str(dom.info()[1]/1024)
|
||||
cpus = str(dom.info()[3])
|
||||
time = str(dom.info()[4]/1000000)
|
||||
print name.ljust(16) + rams.rjust(12) + cpus.rjust(8) + time.rjust(18)
|
||||
|
||||
def check_all_running( doms ):
|
||||
"""Accept a list of dom objects, check if all guest dom are active"""
|
||||
if sum( [dom.isActive() for dom in doms] ) == len( doms ):
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_all_shutoff( doms ):
|
||||
"""Accept a list of dom objects, check if all guest dom are shut off"""
|
||||
if sum( [dom.isActive() for dom in doms] ):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_all_running( doms ):
|
||||
"""Accept a list of dom objects, return a list of running dom objects"""
|
||||
return [ dom for dom in doms if dom.isActive() ]
|
||||
|
||||
def get_all_shutoff( doms ):
|
||||
"""Accept a list of dom objects, return a list of shutoff dom objects"""
|
||||
return [ dom for dom in doms if not dom.isActive() ]
|
||||
|
||||
def logit( context, message, quiet = False ):
|
||||
"""syslog and error handler"""
|
||||
if type( message ) is tuple:
|
||||
message = message[2] # libvirt message is a tuple
|
||||
|
||||
try: quiet = options.quiet
|
||||
except NameError: pass
|
||||
|
||||
if quiet: pass
|
||||
else: print context + ': ' + message
|
||||
|
||||
syslog.openlog( 'virt-back', 0, syslog.LOG_LOCAL3 )
|
||||
syslog.syslog( message )
|
||||
syslog.closelog()
|
||||
|
||||
def rotate( target, retention = 3 ):
|
||||
"""file rotation routine"""
|
||||
for i in range( retention-2, 0, -1 ): # count backwards
|
||||
old_name = "%s.%s" % ( target, i )
|
||||
new_name = "%s.%s" % ( target, i + 1 )
|
||||
try: move( old_name, new_name )
|
||||
except IOError: pass
|
||||
move( target, target + '.1' )
|
||||
|
||||
def getoptions():
|
||||
"""Fetch cli args, parse and map to python, test sanity"""
|
||||
|
||||
# create an option parcer object
|
||||
p = OptionParser()
|
||||
|
||||
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,
|
||||
help='append date to tar filename [default: no date]' )
|
||||
|
||||
p.add_option( '-g', '--no-gzip', dest='nogzip', action='store_true', default=False,
|
||||
help='do not gzip the resulting tar file' )
|
||||
|
||||
p.add_option( '-a', '--retention', dest='retention', 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.
|
||||
|
||||
g0 = OptionGroup( p, "Actions for info testing", "These options display info or test a list of 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
|
||||
|
||||
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,
|
||||
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
|
||||
p.add_option_group( g0 )
|
||||
p.add_option_group( g1 )
|
||||
p.add_option_group( g2 )
|
||||
|
||||
# parse options and args
|
||||
options, guest_names = p.parse_args()
|
||||
|
||||
# the actionsum should be 1 to continue, bool math ftw
|
||||
actions = [ options.backup, options.reboot, options.shutdown,
|
||||
options.create, options.info, options.backupall, options.rebootall,
|
||||
options.shutdownall, options.createall, options.infoall, ]
|
||||
actionsum = sum( actions )
|
||||
|
||||
if actionsum == 1:
|
||||
return options, guest_names
|
||||
else:
|
||||
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() )
|
||||
# Get the list of options and list of guest_names from cli
|
||||
options, guest_names = getoptions()
|
||||
# connect to hypervisor with Domfetcher (read-only)
|
||||
domfetcher = Domfetcher( options.uri )
|
||||
|
||||
if options.backup 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()
|
||||
|
||||
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 )
|
||||
|
||||
|
||||
18
virt_back.egg-info/PKG-INFO
Normal file
18
virt_back.egg-info/PKG-INFO
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: virt-back
|
||||
Version: 0.1.0
|
||||
Summary: virt-back: A backup utility for QEMU, KVM, XEN, and Virtualbox guests
|
||||
Home-page: https://bitbucket.org/russellballestrini/virt-back
|
||||
Author: Russell Ballestrini
|
||||
Author-email: russell@ballestrini.net
|
||||
License: Public Domain
|
||||
Description: A backup utility for QEMU, KVM, XEN, and Virtualbox guests.
|
||||
Virt-back is a python application that uses the libvirt api to safely
|
||||
shutdown, gzip, and restart guests. The backup process logs to syslog
|
||||
for auditing and virt-back works great with cron for scheduling outages.
|
||||
Virt-back has been placed in the public domain and
|
||||
the latest version may be downloaded here:
|
||||
https://bitbucket.org/russellballestrini/virt-back
|
||||
|
||||
Keywords: backup virtual hypervisor QEMU KVM XEN Virtualbox
|
||||
Platform: All
|
||||
7
virt_back.egg-info/SOURCES.txt
Normal file
7
virt_back.egg-info/SOURCES.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
setup.py
|
||||
virt-back
|
||||
virtback.py
|
||||
virt_back.egg-info/PKG-INFO
|
||||
virt_back.egg-info/SOURCES.txt
|
||||
virt_back.egg-info/dependency_links.txt
|
||||
virt_back.egg-info/top_level.txt
|
||||
1
virt_back.egg-info/dependency_links.txt
Normal file
1
virt_back.egg-info/dependency_links.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
1
virt_back.egg-info/top_level.txt
Normal file
1
virt_back.egg-info/top_level.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
virtback
|
||||
1
virtback.py
Symbolic link
1
virtback.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
virt-back
|
||||
Loading…
Add table
Add a link
Reference in a new issue