Skip to content

Commit 4075aaf

Browse files
committed
Initial Commit
0 parents  commit 4075aaf

File tree

8 files changed

+152
-0
lines changed

8 files changed

+152
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea/
2+
*.iml

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Timothy Lusk
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
recursive-include src/raid_status_notifier *

example.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[settings]
2+
pushover_api_token=abcdefghijklmnopqrstuvwxyz
3+
pushover_user_key=abcdefghijklmnopqrstuvwxyz
4+
5+
[btrfs_mount_points]
6+
mount1=/mnt/data
7+
mount2=/mnt/data2

requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
python-pushover>=0.2
2+
3+
--index-url https://pypi.python.org/simple/
4+
5+
-e .

setup.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env python3
2+
3+
from setuptools import setup
4+
5+
reqs = ['python-pushover>=0.2']
6+
7+
setup(
8+
name='raid-status-notifier',
9+
version="1.0",
10+
url='https://github.com/tlusk/raid-status-notifier',
11+
author='Timothy Lusk',
12+
author_email='[email protected]',
13+
description='BTRFS/ZFS Raid Status Notifier',
14+
license='MIT',
15+
packages=['raid_status_notifier'],
16+
package_dir={'': 'src'},
17+
include_package_data=True,
18+
entry_points={'console_scripts': [
19+
'raid-status-notifier = raid_status_notifier.main:main'
20+
]},
21+
install_requires=reqs,
22+
zip_safe=False,
23+
test_suite='unittest2.collector',
24+
classifiers=[
25+
'Environment :: Console',
26+
'Intended Audience :: Developers',
27+
'License :: OSI Approved :: MIT License',
28+
'Operating System :: OS Independent',
29+
'Programming Language :: Python',
30+
'Topic :: Software Development :: Libraries :: Python Modules'
31+
],
32+
)

src/raid_status_notifier/__init__.py

Whitespace-only changes.

src/raid_status_notifier/main.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import re
5+
import os
6+
import pickle
7+
8+
from argparse import ArgumentParser
9+
from subprocess import check_output
10+
from pushover import init, Client
11+
from configparser import ConfigParser
12+
13+
14+
class RaidStatusChecker(object):
15+
def __init__(self, config):
16+
init(config.get("settings", "pushover_api_token"))
17+
self.client = Client(config.get("settings", "pushover_user_key"))
18+
self.btrfs_mount_points = [path for key, path in config.items("btrfs_mount_points")]
19+
20+
def check_btrfs_stats(self):
21+
for mount_point in self.btrfs_mount_points:
22+
stats_filename = "data/btrfs-stats_%s.p" % mount_point[1:].replace("/", "-")
23+
24+
device_stats = {}
25+
if os.path.exists(stats_filename):
26+
device_stats = pickle.load(open(stats_filename, "rb"))
27+
28+
status = check_output(["sudo", "btrfs", "device", "stats", mount_point]).decode("utf-8").strip()
29+
new_errors = False
30+
regex = re.compile('\[/dev/(.*)\]\.(\S*)\s*(\d*)')
31+
32+
for line in status.split('\n'):
33+
match = regex.match(line)
34+
if match is not None:
35+
if match.group(1) not in device_stats:
36+
device_stats[match.group(1)] = {}
37+
previous_stats = device_stats[match.group(1)].get(match.group(2), 0)
38+
if int(match.group(3)) > previous_stats:
39+
device_stats[match.group(1)][match.group(2)] = int(match.group(3))
40+
new_errors = True
41+
42+
if not os.path.exists("data"):
43+
os.mkdir("data")
44+
45+
pickle.dump(device_stats, open(stats_filename, "wb"))
46+
47+
if new_errors is not False:
48+
self.client.send_message(status, title="BTRFS Errors: %s" % mount_point)
49+
50+
def check_btrfs_drives(self):
51+
status = check_output(["sudo", "btrfs", "fi", "show", "-d"]).decode("utf-8").strip()
52+
53+
regex = re.compile('(missing|warning)')
54+
if regex.match(status) is not None:
55+
self.client.send_message(status, title="BTRFS Array Error")
56+
57+
def check_zfs_drives(self):
58+
status = check_output(["sudo", "zpool", "status", "-x"])
59+
if status != "all pools are healthy":
60+
self.client.send_message(status, title="ZFS Array Error")
61+
62+
def run(self):
63+
self.check_zfs_drives()
64+
self.check_btrfs_stats()
65+
self.check_btrfs_drives()
66+
67+
68+
def main(argv=None):
69+
if argv is None:
70+
argv = sys.argv[1:]
71+
72+
parser = ArgumentParser()
73+
parser.add_argument("-c", "--config", action="store", help="Configuration File", metavar="CONFIGFILE")
74+
parser.set_defaults(config="settings.cfg")
75+
76+
options = parser.parse_args(argv)
77+
78+
config = ConfigParser()
79+
config.read(options.config)
80+
81+
RaidStatusChecker(config).run()
82+
83+
if __name__ == "__main__":
84+
sys.exit(main())

0 commit comments

Comments
 (0)