Skip to content

Commit e95827c

Browse files
committed
Remote Poller: Initial version
1 parent 1640b2d commit e95827c

8 files changed

+379
-0
lines changed
5.11 KB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*/5 * * * * root /usr/bin/env python3 /opt/nmsprime/remotepoller/pollercontroller.py > /dev/null 2>&1
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/var/log/nmsprime/remotepoller.log {
2+
weekly
3+
missingok
4+
rotate 9
5+
compress
6+
delaycompress
7+
create 640 root root
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# the URL the telegraf config is donwloaded from
2+
# get it from InfraMon⇒RemotePollers in your NMS Prime installation
3+
TELEGRAF_CONFIG_URL = "https://your-nmsprime.tld/path/to/config"
4+
5+
# if you are using a self-signed certificate set this to False; defaults to True
6+
CHECK_CERTIFICATE = True
7+
8+
# logs can be found in /var/log/nmsprime/remotepoller.log
9+
# set to one out of DEBUG, INFO, WARNING, ERROR, default is INFO
10+
LOG_LEVEL = "INFO"
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
#!/usr/bin/env python3
2+
3+
import logging
4+
from pathlib import Path
5+
from pprint import pprint
6+
import re
7+
import ssl
8+
import subprocess
9+
import sys
10+
import traceback
11+
import urllib.request
12+
13+
"""
14+
This is a so called NMS Prime Remote Poller.
15+
It controls the telegraf config – data collected on this machine is sent to a Kafka server.
16+
"""
17+
18+
19+
################################################################################
20+
################################################################################
21+
class NmsPrimeRemotePollerController:
22+
23+
log_level = logging.INFO
24+
25+
log_file = "/var/log/nmsprime/remotepoller.log"
26+
config_file = "/etc/nmsprime/remotepoller.conf"
27+
28+
config_cache_dir = Path("/var/cache/nmsprime/remotepoller/")
29+
telegraf_config_dir = Path("/etc/telegraf/telegraf.d")
30+
31+
nms_config_url = None
32+
nms_checksum_url = None
33+
nms_check_certificate = True
34+
35+
log = None
36+
config = None
37+
ssl_context = None
38+
checksum_new = None
39+
checksum_old = None
40+
41+
################################################################################
42+
def ex_to_str(self, ex):
43+
"""Converts an exception to a human readable string containing relevant informations.
44+
Use e.g. to create meaningful log entries."""
45+
46+
# get type and message of risen exception
47+
ex_type = f"{type(ex).__name__}"
48+
ex_args = [f"{a}" for a in ex.args]
49+
ex_args = ", ".join(ex_args)
50+
51+
# get the command where the exception has been raised
52+
tb = traceback.extract_tb(sys.exc_info()[2], limit=2)
53+
ex_cmd = tb[0][3]
54+
ex_file = tb[0][0]
55+
ex_line = tb[0][1]
56+
57+
nice_ex = f"{ex_type} ({ex_args})"
58+
59+
return nice_ex
60+
61+
############################################################################
62+
def __init__(self):
63+
# initalize logger here to be able to log errors in reading config
64+
# log level may be changed once the configured log level is known
65+
self._init_logger()
66+
67+
############################################################################
68+
def _init_logger(self):
69+
self.log = logging.getLogger("NMS Prime Remote Poller")
70+
logging.basicConfig(
71+
filename=self.log_file,
72+
encoding="utf-8",
73+
level=self.log_level,
74+
format="%(asctime)s %(message)s",
75+
datefmt="%Y-%m-%d %H:%M-%S",
76+
)
77+
78+
############################################################################
79+
def read_config(self):
80+
try:
81+
with open(self.config_file, "r") as fh:
82+
lines = fh.readlines()
83+
84+
for line in lines:
85+
line = line.strip()
86+
87+
if not line:
88+
continue
89+
90+
if line[0] in ("#", ";"):
91+
continue
92+
93+
parts = line.split("=")
94+
if len(parts) != 2:
95+
continue
96+
97+
key = parts[0].strip()
98+
value = parts[1].strip().replace('"', "").replace("'", "")
99+
100+
if key == "TELEGRAF_CONFIG_URL":
101+
if not value.endswith("/"):
102+
value += "/"
103+
self.nms_config_url = value + "telegraf.d.tar.gz"
104+
self.nms_checksum_url = value + "telegraf.d.tar.gz.sha512sum"
105+
if key == "CHECK_CERTIFICATE":
106+
if value.lower() == "false":
107+
self.nms_check_certificate = False
108+
if key == "LOG_LEVEL":
109+
level = re.sub(r"[^a-zA-Z]", "_", value).upper()
110+
self.log.setLevel(level)
111+
112+
if self.nms_config_url is None:
113+
raise ValueError("Key TELEGRAF_CONFIG_URL missing in config file")
114+
115+
except Exception as ex:
116+
self.log.error(f"Error reading {self.config_file}: {self.ex_to_str(ex)}")
117+
sys.exit(1)
118+
119+
self.log.debug("Configuration done")
120+
121+
############################################################################
122+
def run(self):
123+
124+
self._create_ssl_context()
125+
self._read_remote_checksum()
126+
self._get_old_checksum()
127+
128+
if self.checksum_new == self.checksum_old:
129+
self.log.debug("Config did not change – nothing to do")
130+
sys.exit(0)
131+
132+
self.log.info(
133+
"Remote config has changed – starting process to renew local config"
134+
)
135+
136+
self._get_new_config()
137+
self._extract_new_config()
138+
self._delete_old_config()
139+
self._move_new_config()
140+
self._restart_telegraf()
141+
self._move_compressed_config()
142+
143+
self.log.info("Telegraf now running with new configuration.")
144+
145+
############################################################################
146+
def _get_checksum(self, filepath):
147+
output = subprocess.check_output(f"/usr/bin/sha512sum {filepath}", shell=True)
148+
return output.decode("utf-8").strip().split(" ")[0]
149+
150+
############################################################################
151+
def _create_ssl_context(self):
152+
"""
153+
If the server's certificate shall not be checked we need a context to be
154+
passed to urllib.request.open().
155+
"""
156+
if not self.nms_check_certificate:
157+
self.ssl_context = ssl.create_default_context()
158+
self.ssl_context.check_hostname = False
159+
self.ssl_context.verify_mode = ssl.CERT_NONE
160+
161+
############################################################################
162+
def _read_remote_checksum(self):
163+
self.log.debug("Getting checksum file.")
164+
try:
165+
response = urllib.request.urlopen(
166+
self.nms_checksum_url, context=self.ssl_context
167+
)
168+
self.checksum_new = response.read().decode("utf-8").strip()
169+
except Exception as ex:
170+
self.log.warning(f"Error getting checksum file: {self.ex_to_str(ex)}")
171+
sys.exit(1)
172+
173+
############################################################################
174+
def _get_old_checksum(self):
175+
self.log.debug("Getting checksum of old config.")
176+
177+
old_file = self.config_cache_dir / "telegraf.d.tar.gz"
178+
if not old_file.is_file():
179+
self.checksum_old = ""
180+
return
181+
182+
try:
183+
self.checksum_old = self._get_checksum(old_file)
184+
except Exception as ex:
185+
self.log.warning(
186+
f"Could not determine checksum for old config: {self.ex_to_str(ex)}"
187+
)
188+
self.checksum_old = ""
189+
return
190+
191+
############################################################################
192+
def _get_new_config(self):
193+
self.log.debug("Getting new config file.")
194+
195+
new_file = self.config_cache_dir / "new.telegraf.d.tar.gz"
196+
try:
197+
response = urllib.request.urlopen(
198+
self.nms_config_url, context=self.ssl_context
199+
)
200+
data = response.read()
201+
with open(new_file, "wb") as fh:
202+
fh.write(data)
203+
checksum_downloaded = self._get_checksum(new_file)
204+
if checksum_downloaded != self.checksum_new:
205+
raise Exception(
206+
"Checksum of downloaded file differs from remote checksum."
207+
)
208+
except Exception as ex:
209+
self.log.warning(f"Error getting new config file: {self.ex_to_str(ex)}")
210+
sys.exit(1)
211+
212+
############################################################################
213+
def _extract_new_config(self):
214+
self.log.debug("Extracting new config.")
215+
216+
extract_file = self.config_cache_dir / "new.telegraf.d.tar.gz"
217+
extract_dir = self.config_cache_dir / "telegraf.d"
218+
try:
219+
subprocess.run(f"/usr/bin/rm -rf {extract_dir}", shell=True, check=True)
220+
subprocess.run(
221+
f"/usr/bin/tar xf {extract_file} -C {self.config_cache_dir}",
222+
shell=True,
223+
check=True,
224+
)
225+
except Exception as ex:
226+
pprint(ex)
227+
self.log.warning(f"Error getting new config file: {self.ex_to_str(ex)}")
228+
sys.exit(1)
229+
230+
############################################################################
231+
def _delete_old_config(self):
232+
self.log.debug(f"Deleting old config in {self.telegraf_config_dir}")
233+
try:
234+
subprocess.run(
235+
f"/usr/bin/rm -rf {self.telegraf_config_dir}/nmsprime__*",
236+
shell=True,
237+
check=True,
238+
)
239+
except Exception as ex:
240+
self.log.warning(f"Error deleting old config file: {self.ex_to_str(ex)}")
241+
sys.exit(1)
242+
243+
############################################################################
244+
def _move_new_config(self):
245+
self.log.debug(f"Moving new config to {self.telegraf_config_dir}")
246+
extract_dir = self.config_cache_dir / "telegraf.d"
247+
try:
248+
subprocess.run(
249+
f"/usr/bin/mv -f {extract_dir}/nmsprime__* {self.telegraf_config_dir}",
250+
shell=True,
251+
check=True,
252+
)
253+
subprocess.run(
254+
f"/usr/bin/chown root:telegraf {self.telegraf_config_dir}/nmsprime__*",
255+
shell=True,
256+
check=True,
257+
)
258+
subprocess.run(
259+
f"/usr/bin/chmod 640 {self.telegraf_config_dir}/nmsprime__*",
260+
shell=True,
261+
check=True,
262+
)
263+
except Exception as ex:
264+
self.log.warning(f"Error moving telegraf config: {self.ex_to_str(ex)}")
265+
sys.exit(1)
266+
267+
############################################################################
268+
def _restart_telegraf(self):
269+
self.log.debug(f"Restarting telegraf")
270+
try:
271+
subprocess.run(
272+
f"/usr/bin/systemctl restart telegraf.service", shell=True, check=True
273+
)
274+
except Exception as ex:
275+
self.log.warning(f"Error restarting telegraf: {self.ex_to_str(ex)}")
276+
sys.exit(1)
277+
278+
############################################################################
279+
def _move_compressed_config(self):
280+
self.log.debug(f"Replacing compressed telegraf config")
281+
src = self.config_cache_dir / "new.telegraf.d.tar.gz"
282+
dst = self.config_cache_dir / "telegraf.d.tar.gz"
283+
try:
284+
subprocess.run(f"/usr/bin/mv -f {src} {dst}", shell=True, check=True)
285+
except Exception as ex:
286+
self.log.warning(
287+
f"Error moving compressed telegraf config: {self.ex_to_str(ex)}"
288+
)
289+
sys.exit(1)
290+
291+
292+
################################################################################
293+
################################################################################
294+
################################################################################
295+
if __name__ == "__main__":
296+
rp = NmsPrimeRemotePollerController()
297+
rp.read_config()
298+
rp.run()

SOURCES/nmsprime-remotepoller.var.log.nmsprime.remotepoller.log

Whitespace-only changes.
10.5 KB
Binary file not shown.

SPECS/nmsprime-remotepoller.spec

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
Name: nmsprime-remotepoller
2+
Version: 1.0.0
3+
Release: 1
4+
Summary: Turns your machine into a NMS Prime Remote Poller
5+
6+
Source0: nmsprime-remotepoller.var.log.nmsprime.remotepoller.log
7+
Source1: nmsprime-remotepoller.opt.nmsprime.remotepoller.pollercontroller.py
8+
Source2: nmsprime-remotepoller.etc.cron.d.nmsprime-remotepoller
9+
Source3: nmsprime-remotepoller.etc.logrotate.d.nmsprime-remotepoller
10+
Source4: nmsprime-remotepoller.etc.nmsprime.remotepoller.conf
11+
12+
Group: Applications/Communications
13+
License: GPLv3
14+
15+
Requires: python3
16+
Requires: telegraf
17+
Requires: tar
18+
19+
%description
20+
Makes your machine download telegraf config from nmsprime and sending collected data to a Kafka server
21+
22+
# this is needed to prevent python compilation error on CentOS (#2235)
23+
# otherwise it tries to execute %{buildroot}/opt/nmsprime/remotepoller/pollercontroller.py (using python 2.7)
24+
# thanks to: https://github.com/scylladb/scylladb/issues/2235
25+
%global __os_install_post \
26+
/usr/lib/rpm/redhat/brp-compress \
27+
%{!?__debug_package:\
28+
/usr/lib/rpm/redhat/brp-strip %{__strip} \
29+
/usr/lib/rpm/redhat/brp-strip-comment-note %{__strip} %{__objdump} \
30+
} \
31+
/usr/lib/rpm/redhat/brp-strip-static-archive %{__strip} \
32+
%{!?__jar_repack:/usr/lib/rpm/redhat/brp-java-repack-jars} \
33+
%{nil}
34+
35+
%prep
36+
# Nothing to prep
37+
38+
%build
39+
# Nothing to build
40+
41+
%install
42+
43+
# first create the logfile
44+
install -D -m 0640 %{SOURCE0} %{buildroot}/var/log/nmsprime/remotepoller.log
45+
46+
# add the script
47+
install -D -m 0644 %{SOURCE1} %{buildroot}/opt/nmsprime/remotepoller/pollercontroller.py
48+
49+
# add config files
50+
install -D -m 0644 %{SOURCE2} %{buildroot}/etc/cron.d/nmsprime-remotepoller
51+
install -D -m 0644 %{SOURCE3} %{buildroot}/etc/logrotate.d/nmsprime-remotepoller
52+
install -D -m 0640 %{SOURCE4} %{buildroot}/etc/nmsprime/remotepoller.conf
53+
54+
%files
55+
/var/log/nmsprime/remotepoller.log
56+
/opt/nmsprime/remotepoller/pollercontroller.py
57+
%config(noreplace) /etc/cron.d/nmsprime-remotepoller
58+
%config(noreplace) /etc/logrotate.d/nmsprime-remotepoller
59+
%config(noreplace) /etc/nmsprime/remotepoller.conf
60+
61+
%changelog
62+

0 commit comments

Comments
 (0)