80f2c935bbc7ef5f4304f2a0b5ba336df0031b35
[mirror/dsa-puppet.git] / modules / bacula / files / volumes-delete-old
1 #!/usr/bin/python3
2
3 # queries a bacula database for volumes to delete and deletes them using bconsole
4
5 # Copyright 2010, 2011, 2013, 2017, 2018 Peter Palfrader
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be
16 # included in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 import argparse
27 import os.path
28 import psycopg2
29 import psycopg2.extras
30 import re
31 import sys
32 import subprocess
33
34 DSA_BACULA_DB_CONNECT = '/etc/dsa/bacula-reader-database'
35 DSA_CLIENT_LIST_FILE = '/etc/bacula/dsa-clients'
36
37 parser = argparse.ArgumentParser()
38 parser.add_argument("-d", "--db-connect-string", metavar="connect-string", dest="db",
39   help="Database connect string")
40 parser.add_argument("-D", "--db-connect-string-file", metavar="FILE", dest="dbfile",
41   default=DSA_BACULA_DB_CONNECT,
42   help="File to read database connect string from (%s)"%(DSA_BACULA_DB_CONNECT,))
43 parser.add_argument("-c", "--client-list", metavar="FILE", dest="clientlist",
44   default=DSA_CLIENT_LIST_FILE,
45   help="File with a list of all clients (%s)"%(DSA_CLIENT_LIST_FILE,))
46 parser.add_argument("-v", "--verbose", dest="verbose",
47   default=False, action="store_true",
48   help="Be more verbose.")
49 parser.add_argument("-n", "--nodo", dest="nodo",
50   default=False, action="store_true",
51   help="Print to cat rather than bconsole.")
52 args = parser.parse_args()
53
54 if args.db is not None:
55     pass
56 elif args.dbfile is not None:
57     args.db = open(args.dbfile).read().rstrip()
58 else:
59     print >>sys.stderr, "Need one of -d or -D."
60     sys.exit(1)
61
62
63 conn = psycopg2.connect(args.db)
64 cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
65
66 cmd = []
67 # Error volumes
68 cursor.execute("""
69   SELECT volumename
70   FROM media
71   WHERE
72     volstatus='Error' AND
73     firstwritten < current_date - interval '2 weeks' AND
74     labeldate < current_date - interval '2 weeks' AND
75     (lastwritten IS NULL OR lastwritten < current_date - interval '6 weeks')
76 """, {})
77 for r in cursor.fetchall():
78   c = "delete volume=%s yes"%(r['volumename'],)
79   cmd.append(c)
80
81 # Append volumes - we should not have any of these
82 cursor.execute("""
83   SELECT volumename
84   FROM media
85   WHERE
86     volstatus='Append' AND
87     firstwritten IS NULL AND
88     labeldate IS NULL AND
89     lastwritten IS NULL AND
90     voljobs = 0 AND
91     volfiles = 0 AND
92     volblocks = 0 AND
93     volbytes = 0 AND
94     volwrites = 0
95 """, {})
96 for r in cursor.fetchall():
97   c = "delete volume=%s yes"%(r['volumename'],)
98   cmd.append(c)
99
100 cursor.execute("""
101   SELECT volumename
102   FROM media
103   WHERE
104     volstatus='Purged' AND
105     firstwritten < current_date - interval '18 weeks' AND
106     labeldate < current_date - interval '18 weeks' AND
107     lastwritten < current_date - interval '16 weeks' AND
108     volfiles = 0 AND
109     volbytes < 1000 AND
110     recycle=1
111 """, {})
112
113 for r in cursor.fetchall():
114   c = "delete volume=%s yes"%(r['volumename'],)
115   cmd.append(c)
116
117 # find obsolete pools, but only if we have a list of clients
118 ##
119 if os.path.exists(args.clientlist):
120   clients = set(open(args.clientlist).read().split())
121
122   cursor.execute("""
123     SELECT name
124     FROM pool
125     WHERE
126       name != 'Scratch' AND
127       numvols = 0 AND
128       poolid NOT IN (SELECT recyclepoolid FROM media)
129   """, {})
130
131   for r in cursor.fetchall():
132     poolname = r['name']
133     match = re.match('pool[a-z]*-debian-(.*)', poolname)
134     if match is not None:
135       hostname = match.group(1)
136       if hostname not in clients:
137         c = "delete pool=%s"%(poolname,)
138         cmd.append(c)
139         cmd.append("yes")
140       elif args.verbose:
141         print("Not expiring empty pool %s because client still exists"%(poolname,))
142     elif args.verbose:
143         print("Could not extract client name from poolname %s"%(poolname,))
144
145 if args.nodo:
146   print("\n".join(cmd))
147   sys.exit(0)
148
149 if args.verbose:
150     for c in cmd:
151       print("Will run: %s"%(c,))
152
153 p = subprocess.Popen(['/usr/sbin/bconsole'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
154 (out, err) = p.communicate("\n".join(cmd).encode())
155 if p.returncode != 0:
156     raise Exception("bconsole failed.  stdout:\n%s\nstderr:%s\n"%(out, err))
157
158 if args.verbose:
159     print("stdout:\n")
160     sys.stdout.buffer.write(out)
161     print("\n")
162
163 if err != b"":
164   print("bconsole said on stderr:\n", file=sys.stderr)
165   sys.stderr.buffer.write(out)
166   print("", file=sys.stderr)
167   sys.exit(1)