3 import os
, sys
, time
, random
, glob
, traceback
5 opt_verbose
= '-v' in sys
.argv
6 opt_force
= '-f' in sys
.argv
8 omd_site
= os
.environ
['OMD_SITE']
9 omd_root
= os
.environ
['OMD_ROOT']
11 config_file
= omd_root
+ '/etc/diskspace.conf'
12 plugin_dir
= omd_root
+ '/share/diskspace'
13 plugin_dir_local
= omd_root
+ '/local/share/diskspace'
15 # Initial configuration
23 sys
.stderr
.write('ERROR: %s\n' % s
)
30 sys
.stdout
.write('%s\n' % s
)
38 execfile(config_file
, globals(), globals())
40 pass # ignore non existant config
42 terminate('Invalid configuration: %s' % e
)
45 for plugin
in plugins
.values():
47 for path
in plugin
.get('cleanup_paths', []):
48 # Make relative paths absolute ones
50 path
= omd_root
+ '/' + path
52 # This resolves given path patterns to really existing files.
53 # It also ensures that the files in the resolved list do really exist.
54 resolved
+= glob
.glob(path
)
57 plugin
['cleanup_paths'] = resolved
58 elif 'cleanup_paths' in plugin
:
59 del plugin
['cleanup_paths']
63 local_plugins
= os
.listdir(plugin_dir_local
)
65 local_plugins
= [] # this is optional
67 plugin_files
= [ p
for p
in os
.listdir(plugin_dir
) if p
not in local_plugins
]
69 for base_dir
, file_list
in [ (plugin_dir
, plugin_files
), (plugin_dir_local
, local_plugins
) ]:
76 path
= base_dir
+ '/' + f
77 verbose('Loading plugin: %s' % path
)
79 execfile(path
, plugins
[f
], plugins
[f
])
81 error('Exception while loading plugin "%s": %s' % (path
, e
))
83 # Now transform all path patterns to absolute paths for really existing files
86 def collect_file_infos():
87 for plugin
in plugins
.values():
88 for path
in plugin
.get('cleanup_paths', []):
89 result
= os
.stat(path
)
90 plugin
.setdefault('file_infos', {})[path
] = (result
.st_size
, result
.st_mtime
)
95 if b
>= base
* base
* base
* base
:
96 return '%.2fTB' % (b
/ base
/ base
/ base
/ base
)
97 elif b
>= base
* base
* base
:
98 return '%.2fGB' % (b
/ base
/ base
/ base
)
99 elif b
>= base
* base
:
100 return '%.2fMB' % (b
/ base
/ base
)
102 return '%.2fkB' % (b
/ base
)
106 def get_free_space():
107 # FIXME: Take eventual root reserved space into account
108 for l
in os
.popen('df -P -B1 ' + omd_root
).readlines():
110 vol
, size_bytes
, used_bytes
, free_bytes
, used_perc
, mp
= l
.split()
111 return int(free_bytes
)
113 def above_threshold(b
):
114 return b
>= min_free_bytes
116 def delete_file(path
, reason
):
118 log('Deleting file (%s): %s' % (reason
, path
))
122 error('Error while deleting %s: %s' % (path
, e
))
125 # Loop all files to check wether or not files are older than
126 # max_age. Simply remove all of them.
128 if max_file_age
is None:
129 verbose('Not cleaning up too old files (max_file_age not configured)')
131 max_age
= time
.time() - max_file_age
133 for plugin
in plugins
.values():
134 for path
, (size
, mtime
) in plugin
.get('file_infos', {}).items():
136 if delete_file(path
, 'too old'):
137 del plugin
['file_infos'][path
]
139 verbose('Not deleting %s' % path
)
141 def oldest_candidate(file_infos
):
143 # Sort by modification time
144 sorted_infos
= sorted(file_infos
.items(), key
= lambda i
: i
[1][1])
145 oldest
= sorted_infos
[0]
146 if oldest
[1][1] < time
.time() - min_file_age
:
150 if min_file_age
is None:
151 terminate('Not cleaning up oldest files of plugins (min_file_age not configured). terminating.')
153 # the scheduling of the cleanup job is supposed to be equal for
154 # all sites. To ensure that not only one single site is always
155 # cleaning up, we add a a random wait before cleanup.
156 sleep_sec
= float(random
.randint(0, 10000)) / 1000
157 verbose('Sleeping for %0.3f seconds' % sleep_sec
)
158 time
.sleep(sleep_sec
)
160 # Loop all cleanup plugins to find the oldest candidate per plugin
161 # which is older than min_age and delete this file.
162 for plugin_name
, plugin
in plugins
.items():
163 oldest
= oldest_candidate(plugin
.get('file_infos', {}))
165 delete_file(oldest
, plugin_name
+ ': my oldest')
171 # get used diskspace of the sites volume
172 bytes_free
= get_free_space()
173 verbose('Free space: %s' % fmt_bytes(bytes_free
))
177 bytes_free
= get_free_space()
178 verbose('Free space (after max_age cleanup): %s' % fmt_bytes(bytes_free
))
180 # check diskspace against configuration
181 if not opt_force
and above_threshold(bytes_free
):
183 verbose('Free space is above threshold of %s. Nothing to be done.' % fmt_bytes(min_free_bytes
))
186 # free diskspace is below threshold, start cleanup
189 # #############################################################################
193 if min_free_bytes
is None:
194 verbose('minimal free bytes (min_free_bytes) not configured. terminating.')
202 terminate('Unexpected exception: %s' % traceback
.format_exc())