root/xen/trunk/xen-vm-autosnapshot.py

Revision 68, 13.7 kB (checked in by mike, 7 months ago)

added snapshot-tag option, bumped version number to 1.2

Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3################################################################################
4# XenServer VM automatic snapshot rotation script
5# Copyright (c) 2009 Michael Conigliaro <mike [at] conigliaro [dot] org>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20################################################################################
21import getpass
22import logging
23import logging.handlers
24import optparse
25import re
26import sys
27import time
28
29import XenAPI
30
31
32APP_VERSION = "1.2"
33APP_AUTHOR = "Michael T. Conigliaro <mike [at] conigliaro [dot] org>"
34APP_WEBSITE = "http://conigliaro.org/"
35
36
37def snapshot():
38    """Take a snapshot of each VM."""
39
40    log.debug("Starting snapshot routine")
41
42    re_vmnames = re.compile(options.vm_regex)
43
44    all_vms = session.xenapi.VM.get_all_records()
45
46    # loop through vm record map
47    for vm in all_vms:
48        vm_record = all_vms[vm]
49
50        # select appropriate vm
51        if re_vmnames.match(vm_record["name_label"]) and \
52           not vm_record["is_a_template"] and \
53           not vm_record["is_control_domain"]:
54            log.debug("Selecting VM: %s (%s)" %
55                (vm_record["name_label"], vm_record["uuid"]))
56
57            # snapshot name is based on time/tag
58            snapshot_name = "%s: %s %s" % (vm_record["name_label"],
59                                        time.strftime("%Y-%m-%d %H:%M:%S"),
60                                        options.snapshot_tag)
61
62            # create snapshot
63            log.info("Creating VM snapshot: %s" % snapshot_name)
64            if not options.dry_run:
65                done = False
66                tries = 0
67                while not done and tries <= options.retry_max:
68                    if tries:
69                        log.info("Retrying in %d seconds [%d/%d]" %
70                            (options.retry_delay, tries, options.retry_max))
71                        time.sleep(options.retry_delay)
72                    try:
73                        tries += 1
74                        if options.snapshot_with_quiesce:
75                            session.xenapi.VM.snapshot_with_quiesce(vm, snapshot_name)
76                        else:
77                            session.xenapi.VM.snapshot(vm, snapshot_name)
78                        done = True
79                    except Exception, e:
80                        log.error("Unhandled exception: %s" % str(e))
81                        #raise
82
83
84def snapshot_rotate():
85    """Rotate old snapshots for each VM. When destroying old VM snapshots, all
86    corresponding VDI snapshots will be destroyed as well."""
87
88    log.debug("Starting snapshot rotation routine")
89
90    re_vmnames = re.compile(options.vm_regex)
91
92    all_vms = session.xenapi.VM.get_all_records()
93    all_vbds = session.xenapi.VBD.get_all_records()
94    all_vdis = session.xenapi.VDI.get_all_records()
95
96    # loop through vm record map
97    for vm in all_vms:
98        vm_record = all_vms[vm]
99
100        # select appropriate vm
101        if re_vmnames.match(vm_record["name_label"]) and \
102           not vm_record["is_a_template"] and \
103           not vm_record["is_control_domain"]:
104            log.debug("Selecting VM: %s (%s)" % (vm_record["name_label"],
105                                                 vm_record["uuid"]))
106
107            # create list of snapshots (with matching tag only)
108            snapshot_count = 0
109            vm_snapshots = {}
110            for snapshot in vm_record["snapshots"]:
111                if all_vms[snapshot]["name_label"].endswith(' ' + options.snapshot_tag):
112                    vm_snapshots[snapshot] = all_vms[snapshot]
113
114            # check snapshot count
115            snapshot_count = len(vm_snapshots)
116            log.debug("Found %d snapshot(s)" % snapshot_count)
117            if snapshot_count > options.snapshot_max:
118
119                # sort snapshots by date, oldest first
120                vm_snapshots = sorted(vm_snapshots,
121                               key=lambda x: all_vms[x]["snapshot_time"])
122
123                # loop through old snapshots
124                for snapshot in vm_snapshots[0:snapshot_count - options.snapshot_max]:
125
126                    # destroy old vm snapshot
127                    snapshot_record = all_vms[snapshot]
128                    log.debug("Selecting VM snapshot: %s (%s)" %
129                        (snapshot_record["name_label"], snapshot_record["uuid"]))
130                    log.info("Destroying VM snapshot: %s" % snapshot_record["name_label"])
131                    if not options.dry_run:
132                        done = False
133                        tries = 0
134                        while not done and tries <= options.retry_max:
135                            if tries:
136                                log.info("Retrying in %d seconds [%d/%d]" %
137                                    (options.retry_delay, tries, options.retry_max))
138                                time.sleep(options.retry_delay)
139                            try:
140                                tries += 1
141                                session.xenapi.VM.destroy(snapshot)
142                                done = True
143                            except Exception, e:
144                                log.error("Unhandled exception: %s" % str(e))
145                                #raise
146
147                    # loop through this snapshot's vbds (disks only)
148                    for vbd in snapshot_record["VBDs"]:
149                        vbd_record = all_vbds[vbd]
150                        if vbd_record["type"] == "Disk":
151
152                            # destroy corresponding vdi
153                            vdi_record = all_vdis[vbd_record["VDI"]]
154                            vdi = session.xenapi.VDI.get_by_uuid(vdi_record["uuid"])
155                            log.debug("Selecting VDI snapshot: %s (%s)" %
156                                (vdi_record["name_label"], vdi_record["uuid"]))
157                            log.info("Destroying VDI snapshot: %s" % vdi_record["name_label"])
158                            if not options.dry_run:
159                                done = False
160                                tries = 0
161                                while not done and tries <= options.retry_max:
162                                    if tries:
163                                        log.info("Retrying in %d seconds [%d/%d]" %
164                                            (options.retry_delay, tries, options.retry_max))
165                                        time.sleep(options.retry_delay)
166                                    try:
167                                        tries += 1
168                                        session.xenapi.VDI.destroy(vdi)
169                                        done = True
170                                    except Exception, e:
171                                        log.error("Unhandled exception: %s" % str(e))
172                                        #raise
173
174
175if __name__ == "__main__":
176
177    # define command line options
178    valid_args = ['snapshot', 'snapshot-rotate']
179    op = optparse.OptionParser("usage: %prog [options] <" +
180        ' '.join(map(lambda x: "[%s]" % x, valid_args)) + ">",
181        version="%%prog v%s\nAuthor: %s\nWebsite: %s" % (APP_VERSION, APP_AUTHOR, APP_WEBSITE))
182
183    og_sess = optparse.OptionGroup(op, "Session Options")
184    og_sess.add_option('--server',
185                       dest='server',
186                       help="xenserver host (default: %default)")
187    og_sess.add_option('--username',
188                       dest='username',
189                       help="xenserver username (default: %default)")
190    og_sess.add_option('--password',
191                       dest='password',
192                       help="xenserver password")
193    og_sess.add_option('--dry-run',
194                       dest='dry_run',
195                       action='store_true',
196                       help="perform a trial run with no changes")
197    op.add_option_group(og_sess)
198
199    og_vm = optparse.OptionGroup(op, "VM Selection Options")
200    og_vm.add_option('--vms',
201                     dest='vm_regex',
202                     help="regular expression for selecting VMs (default: %default)")
203    op.add_option_group(og_vm)
204
205    og_snap = optparse.OptionGroup(op, "Snapshot Options")
206    og_snap.add_option('--quiesce',
207                       dest="snapshot_with_quiesce",
208                       action='store_true',
209                       help="snapshot with quiesce")
210    og_snap.add_option('--snapshot-max',
211                       dest='snapshot_max',
212                       type="int",
213                       help="number of snapshots to keep when rotating (default: %default)")
214    og_snap.add_option('--snapshot-tag',
215                       dest="snapshot_tag",
216                       help="snapshot tag (default: %default)")
217    op.add_option_group(og_snap)
218
219    og_re = optparse.OptionGroup(op, "Retry Options")
220    og_re.add_option('--retry-max',
221                     dest='retry_max',
222                     type="int",
223                     help="number of times to retry failed operations (default: %default)")
224    og_re.add_option('--retry-delay',
225                     dest='retry_delay',
226                     type="int",
227                     help="seconds of delay between retries (default: %default)")
228    op.add_option_group(og_re)
229
230    og_log = optparse.OptionGroup(op, "Output and Logging Options")
231    og_log.add_option('--log-level',
232                      dest='log_level',
233                      help="critical, error, warning, info, debug (default: %default)")
234    og_log.add_option('--log-file-path',
235                      dest='log_file_path',
236                      help="path for optional log file")
237    og_log.add_option('--log-file-rotate-interval-type',
238                      dest='log_file_rotate_interval_type',
239                      help="s=seconds, m=minutes h=hours, d=days, w=week day (0=monday), midnight (default: %default)")
240    og_log.add_option('--log-file-rotate-interval',
241                      dest='log_file_rotate_interval',
242                      type="int",
243                      help="log rotation interval (default: %default)")
244    og_log.add_option('--log-file-max-backups',
245                      dest='log_file_max_backups',
246                      type="int",
247                      help="number of log files to keep when rotating (default: %default)")
248    op.add_option_group(og_log)
249
250    op.set_defaults(server = 'localhost',
251                    username = getpass.getuser(),
252                    password = '',
253                    retry_max = 2,
254                    retry_delay = 10,
255                    vm_regex = '^$',
256                    snapshot_max = 1,
257                    snapshot_tag = '(auto)',
258                    log_level = 'info',
259                    log_file_rotate_interval_type = 'd',
260                    log_file_rotate_interval = 7,
261                    log_file_max_backups = 4)
262
263    # parse and validate command line arguments
264    (options, args) = op.parse_args()
265    if (not len(args)):
266        op.error("You must supply an argument")
267    for arg in args:
268        if arg not in valid_args:
269            op.error("Invalid argument: " + arg)
270
271    # set up logging
272    log = logging.getLogger()
273    options.log_level = options.log_level.upper()
274    if options.log_level == 'CRITICAL':
275        log.setLevel(logging.CRITICAL)
276    elif options.log_level == 'ERROR':
277        log.setLevel(logging.ERROR)
278    elif options.log_level == 'WARNING':
279        log.setLevel(logging.WARNING)
280    elif options.log_level == 'INFO':
281        log.setLevel(logging.INFO)
282    elif options.log_level == 'DEBUG':
283        log.setLevel(logging.DEBUG)
284    consoleLogger = logging.StreamHandler()
285    consoleLogger.setFormatter(
286            logging.Formatter("%(levelname)s - %(message)s"))
287    log.addHandler(consoleLogger)
288    if options.log_file_path:
289        fileLogger = logging.handlers.TimedRotatingFileHandler(
290            filename = options.log_file_path,
291            when = options.log_file_rotate_interval_type,
292            interval = options.log_file_rotate_interval,
293            backupCount = options.log_file_max_backups)
294        fileLogger.setFormatter(
295            logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
296        log.addHandler(fileLogger)
297
298    log.debug("Running with: options=%s args=%s" % (options, args))
299
300    try:
301        # log in
302        log.debug("Starting XenServer session")
303        session = XenAPI.Session('https://' + options.server)
304        session.xenapi.login_with_password(options.username, options.password)
305
306    except Exception, e:
307        log.critical("Unable to start XenAPI session: %s" % str(e))
308        sys.exit(1)
309
310    try:
311        # map arguments to functions
312        for arg in args:
313            if (arg == 'snapshot'):
314                snapshot()
315            elif (arg == 'snapshot-rotate'):
316                snapshot_rotate()
317
318    except Exception, e:
319         log.critical("Unhandled exception: %s" % str(e))
320         raise
321
322    finally:
323        # log out
324        log.debug("Ending XenServer session")
325        session.xenapi.session.logout()
Note: See TracBrowser for help on using the browser.