-
Notifications
You must be signed in to change notification settings - Fork 0
/
doDomainSnapshots.py
executable file
·273 lines (222 loc) · 9.22 KB
/
doDomainSnapshots.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/env python
import argparse
import libvirt
import logging
import time
import sys
import os
from pprint import pformat
from heapq import nlargest
try:
import xml.etree.ElementTree as ET
except ImportError:
import elementtree.ElementTree as ET
LOG = logging.getLogger(__name__)
""" C style enumeration flags for create snapshot.
Source: https://libvirt.org/html/libvirt-libvirt-domain-snapshot.html#virDomainSnapshotCreateFlags
Some features like disk snapshot only or live snapshot
are not supported by the default version of qemu binary
shipped by Centos.
For other versions check rdo-qemu-ev repo
"""
virDomainSnapshotCreateFlags = {
1: 'VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE: restore or alter metadata',
2: 'VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT: with redefine, make snapshot current',
4: 'VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA: make snapshot without remembering it',
8: 'VIR_DOMAIN_SNAPSHOT_CREATE_HALT: stop running guest after snapshot',
16: 'VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY: disk snapshot, not system checkpoint',
32: 'VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT: reuse any existing external files',
64: 'VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE: use guest agent to quiesce all mounted file systems within the domain',
128: 'VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC: atomically avoid partial changes',
256: 'VIR_DOMAIN_SNAPSHOT_CREATE_LIVE: create the snapshot while the guest is running'
}
virDomainSnapshotDeleteFlags = {
1: 'VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN: Also delete children (exclusive flag)',
2: 'VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY: Delete just metadata',
4: 'VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY: Delete just children (exclusive flag)'
}
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
description='Create a snapshot for a domain.',
epilog="""Examples:
Snapshot 'snap_test' and keep one most-recent snapshot
{0} --domain snap_test --keep 1 --snapshot-name "mytest"
Snapshot 'snap_test' and keep no recent snapshots'
{0} --domain snap_test --keep 0
Snapshot 'snap_test' and keep 3 most-recent snapshots.
In addition use snapshot creation flags
{0} --domain snap_test --keep 3 --flags 8+16+128
Snapshot 'snap_test' and keep 3 most-recent snapshots.
In addition use snapshot creation and deletion flags
{0} --domain snap_test --keep 3 --flags 152 --del-flags 4
""".format(sys.argv[0]))
parser.add_argument('--snapshot-xml', metavar='xml',
type=snapshot_xml_type,
help="""Custom snapshot XML definition.
Will override snapshot name and description if provided.
File or XML tree. Example:
https://libvirt.org/formatsnapshot.html#example""")
parser.add_argument('--qemu-uri', metavar='uri', type=str,
default='qemu:///system',
help='Libvirt/Qemu connection URI. Default: %(default)s')
parser.add_argument('--domain', metavar='name', type=str,
required=True,
help='Domain name to snapshot.')
parser.add_argument('--keep', metavar='int', type=int,
default=2,
help='Number of snapshots to keep, excluding the new one.'
' Will delete older ones. Default: %(default)s')
parser.add_argument('--debug', '-d', action='store_true',
help='Debug output')
parser.add_argument('--snapshot-name', '-n', type=str,
help='Snapshot name. Defaults to unix time')
parser.add_argument('--desc', type=str,
default='DomSnapshot script',
help='Snapshot description. Default: %(default)s')
parser.add_argument('--del-flags', type=snapshot_flags_del_type,
default=0,
help='''Bitmask choices, c-style: provide string
Representing choices or the sum, e.g. syntax 1+8+16. Used for snapshot deletion.
%s''' % pformat(virDomainSnapshotDeleteFlags, width=80,indent=2))
parser.add_argument('--flags', type=snapshot_flags_type,
default=0,
help='''Bitmask choices, c-style: provide string
Representing choices or the sum, e.g. syntax 1+8+16. Used for snapshot creation.
%s''' % pformat(virDomainSnapshotCreateFlags, width=80,indent=2))
return parser.parse_args()
def snapshot_flags_del_type(flags):
try:
values = [int(v) for v in flags.split('+')]
except Exception as e:
LOG.error("Error in delete flag values: %s\n" %e)
raise
vsum = sum(values)
if vsum > sum(virDomainSnapshotDeleteFlags.keys()) or vsum < 0:
LOG.error("Delete flag values out of range\n")
raise
LOG.debug('Delete flags sum: %d' % vsum)
return vsum
def snapshot_flags_type(flags):
try:
values = [int(v) for v in flags.split('+')]
except Exception as e:
LOG.error("Error in flag values: %s\n" %e)
raise
vsum = sum(values)
if vsum > sum(virDomainSnapshotCreateFlags.keys()) or vsum < 0:
LOG.error("Flag values out of range\n")
raise
LOG.debug('Create flags sum: %d' % vsum)
return vsum
def snapshot_xml_type(xml):
if os.path.exists(xml) and os.path.isfile(xml):
try:
tree = ET.parse(xml)
root = tree.getroot()
except Exception as e:
LOG.error("Invalid XML file\n")
raise
LOG.debug('Loaded XML from file')
else:
try:
root = ET.fromstring(xml)
except Exception as e:
LOG.error("Invalid XML or file not found\n")
raise
LOG.debug('Loaded XML from string')
return root
def delete_older_snapshots(dom, keep, flags=0):
c_times = {}
snap_list = dom.snapshotListNames()
for name in snap_list:
try:
snapshot = dom.snapshotLookupByName(name)
except Exception as e:
LOG.warning("Skip item, not found? %s" % e)
continue
root = ET.fromstring(snapshot.getXMLDesc())
c_time = root.find('creationTime').text
c_times[c_time] = name
# Keep the newest n number of snapshots
to_keep = nlargest(keep, c_times)
for remove_newest in to_keep:
LOG.debug("Exclude newer snapshot '%s' time %s" %
(c_times[remove_newest],
time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(int(remove_newest))
)
))
del c_times[remove_newest]
for c_time, name in c_times.items():
LOG.debug('Get snapshot %s object' % name)
try:
snapshot = dom.snapshotLookupByName(name)
except Exception as e:
LOG.warning("Skip item, not found? %s" % e)
continue
LOG.info("Delete snapshot '%s' time %s" % (name,
time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(int(c_time))
)
))
try:
snapshot.delete(flags=flags)
except Exception as e:
LOG.error("Failed to delete snapshot %s: %s" %
(name, e))
def connect_libvirt(qemu_uri):
# Connect to libvirt
conn = libvirt.open(qemu_uri)
if conn is None:
LOG.error('Failed to open connection to the hypervisor\n')
sys.exit(1)
return conn
def set_logger(debug=False):
if debug is True:
LOG.setLevel(logging.DEBUG)
else:
LOG.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(levelname)s: %(message)s')
ch.setFormatter(formatter)
LOG.addHandler(ch)
LOG.debug('Set debug level')
def do_snapshot():
try:
args = parse_args()
except Exception as e:
sys.stderr.write("Error parsing arguments: %s\n" % e)
sys.exit(1)
# Set logger config
set_logger(debug=args.debug)
# Connect to libvirt
conn = connect_libvirt(args.qemu_uri)
try:
dom = conn.lookupByName(args.domain)
except libvirt.libvirtError:
LOG.error("Domain %s not found?\n" % args.domain)
sys.exit(1)
LOG.debug('Connected to libvirt')
# Delete older snapshots
delete_older_snapshots(dom, keep=args.keep, flags=args.del_flags)
# Prepare snapshot XML
snap_name = args.snapshot_name if args.snapshot_name else int(time.time())
if not args.snapshot_xml:
snapshot_xml = """<domainsnapshot>
<description>%s</description>
<name>%s</name>
</domainsnapshot>""" % \
(args.desc, snap_name)
LOG.debug("Snapshot XML: %s" % snapshot_xml)
LOG.info('Snapshotting domain %s' % dom.name())
try:
snapshot = dom.snapshotCreateXML(snapshot_xml, flags=args.flags)
except Exception as e:
LOG.error("Error snapshotting: %s" % e)
sys.exit(1)
LOG.debug(snapshot.getXMLDesc())
LOG.info("Snapshot %s for %s created successfully" %
(snap_name, dom.name()))
if __name__ == "__main__":
do_snapshot()