Skip to content

Commit 6c3ae7d

Browse files
committed
previous commit failed to account for return code
1 which is unreachable
1 parent 0287ed4 commit 6c3ae7d

File tree

1 file changed

+187
-187
lines changed
  • openwisp_monitoring/check/classes

1 file changed

+187
-187
lines changed
Lines changed: 187 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,187 @@
1-
import subprocess
2-
3-
from django.core.exceptions import ValidationError
4-
from jsonschema import draft7_format_checker, validate
5-
from jsonschema.exceptions import ValidationError as SchemaError
6-
from swapper import load_model
7-
8-
from openwisp_utils.utils import deep_merge_dicts
9-
10-
from ... import settings as monitoring_settings
11-
from .. import settings as app_settings
12-
from ..exceptions import OperationalError
13-
from .base import BaseCheck
14-
15-
Chart = load_model('monitoring', 'Chart')
16-
Metric = load_model('monitoring', 'Metric')
17-
AlertSettings = load_model('monitoring', 'AlertSettings')
18-
19-
DEFAULT_PING_CHECK_CONFIG = {
20-
'count': {
21-
'type': 'integer',
22-
'default': 5,
23-
'minimum': 2,
24-
# chosen to avoid slowing down the queue
25-
'maximum': 20,
26-
},
27-
'interval': {
28-
'type': 'integer',
29-
'default': 25,
30-
'minimum': 10,
31-
# chosen to avoid slowing down the queue
32-
'maximum': 1000,
33-
},
34-
'bytes': {'type': 'integer', 'default': 56, 'minimum': 12, 'maximum': 65508},
35-
'timeout': {
36-
'type': 'integer',
37-
'default': 800,
38-
'minimum': 5,
39-
# arbitrary chosen to avoid slowing down the queue
40-
'maximum': 1500,
41-
},
42-
}
43-
44-
45-
def get_ping_schema():
46-
schema = {
47-
'$schema': 'http://json-schema.org/draft-07/schema#',
48-
'type': 'object',
49-
'additionalProperties': False,
50-
}
51-
schema['properties'] = deep_merge_dicts(
52-
DEFAULT_PING_CHECK_CONFIG, app_settings.PING_CHECK_CONFIG
53-
)
54-
return schema
55-
56-
57-
class Ping(BaseCheck):
58-
schema = get_ping_schema()
59-
60-
def validate_params(self):
61-
try:
62-
validate(self.params, self.schema, format_checker=draft7_format_checker)
63-
except SchemaError as e:
64-
message = 'Invalid param'
65-
path = '/'.join(e.path)
66-
if path:
67-
message = '{0} in "{1}"'.format(message, path)
68-
message = '{0}: {1}'.format(message, e.message)
69-
raise ValidationError({'params': message}) from e
70-
71-
def check(self, store=True):
72-
count = self._get_param('count')
73-
interval = self._get_param('interval')
74-
bytes_ = self._get_param('bytes')
75-
timeout = self._get_param('timeout')
76-
ip = self._get_ip()
77-
# if the device has no available IP
78-
if not ip:
79-
monitoring = self.related_object.monitoring
80-
# device not known yet, ignore
81-
if monitoring.status == 'unknown':
82-
return
83-
# device is known, simulate down
84-
result = {'reachable': 0, 'loss': 100.0}
85-
if store:
86-
self.store_result(result)
87-
return result
88-
command = [
89-
'fping',
90-
'-e', # show elapsed (round-trip) time of packets
91-
'-c', str(count), # count of pings to send to each target
92-
'-p', str(interval), # interval between sending pings (in ms)
93-
'-b', str(bytes_), # amount of ping data to send
94-
'-t', str(timeout), # individual target initial timeout (in ms)
95-
'-q',
96-
ip
97-
]
98-
stdout, stderr, returncode = self._command(command)
99-
# Check the return code and raise an exception if it's not zero
100-
if returncode != 0:
101-
message = f'fping command failed with return code {returncode}:\n\n{stderr.decode("utf8")}'
102-
raise OperationalError(message)
103-
# fpings shows statistics on stderr
104-
output = stderr.decode('utf8')
105-
try:
106-
parts = output.split('=')
107-
if len(parts) > 2:
108-
min, avg, max = parts[-1].strip().split('/')
109-
i = -2
110-
else:
111-
i = -1
112-
sent, received, loss = parts[i].strip().split(',')[0].split('/')
113-
loss = float(loss.strip('%'))
114-
except (IndexError, ValueError) as e:
115-
message = 'Unrecognized fping output:\n\n{0}'.format(output)
116-
raise OperationalError(message) from e
117-
result = {'reachable': int(loss < 100), 'loss': loss}
118-
if result['reachable']:
119-
result.update(
120-
{'rtt_min': float(min), 'rtt_avg': float(avg), 'rtt_max': float(max)}
121-
)
122-
if store:
123-
self.store_result(result)
124-
return result
125-
126-
def store_result(self, result):
127-
"""
128-
store result in the DB
129-
"""
130-
metric = self._get_metric()
131-
copied = result.copy()
132-
reachable = copied.pop('reachable')
133-
metric.write(reachable, extra_values=copied)
134-
135-
def _get_param(self, param):
136-
"""
137-
Gets specified param or its default value according to the schema
138-
"""
139-
return self.params.get(param, self.schema['properties'][param]['default'])
140-
141-
def _get_ip(self):
142-
"""
143-
Figures out ip to use or fails raising OperationalError
144-
"""
145-
device = self.related_object
146-
ip = device.management_ip
147-
if not ip and not app_settings.MANAGEMENT_IP_ONLY:
148-
ip = device.last_ip
149-
return ip
150-
151-
def _command(self, command):
152-
"""
153-
Executes command (easier to mock)
154-
"""
155-
try:
156-
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
157-
stdout, stderr = p.communicate()
158-
return stdout, stderr, p.returncode
159-
except Exception as e:
160-
return None, str(e), 99
161-
162-
def _get_metric(self):
163-
"""
164-
Gets or creates metric
165-
"""
166-
metric, created = self._get_or_create_metric()
167-
if created:
168-
self._create_alert_settings(metric)
169-
self._create_charts(metric)
170-
return metric
171-
172-
def _create_alert_settings(self, metric):
173-
alert_settings = AlertSettings(metric=metric)
174-
alert_settings.full_clean()
175-
alert_settings.save()
176-
177-
def _create_charts(self, metric):
178-
"""
179-
Creates device charts if necessary
180-
"""
181-
charts = ['uptime', 'packet_loss', 'rtt']
182-
for chart in charts:
183-
if chart not in monitoring_settings.AUTO_CHARTS:
184-
continue
185-
chart = Chart(metric=metric, configuration=chart)
186-
chart.full_clean()
187-
chart.save()
1+
import subprocess
2+
3+
from django.core.exceptions import ValidationError
4+
from jsonschema import draft7_format_checker, validate
5+
from jsonschema.exceptions import ValidationError as SchemaError
6+
from swapper import load_model
7+
8+
from openwisp_utils.utils import deep_merge_dicts
9+
10+
from ... import settings as monitoring_settings
11+
from .. import settings as app_settings
12+
from ..exceptions import OperationalError
13+
from .base import BaseCheck
14+
15+
Chart = load_model('monitoring', 'Chart')
16+
Metric = load_model('monitoring', 'Metric')
17+
AlertSettings = load_model('monitoring', 'AlertSettings')
18+
19+
DEFAULT_PING_CHECK_CONFIG = {
20+
'count': {
21+
'type': 'integer',
22+
'default': 5,
23+
'minimum': 2,
24+
# chosen to avoid slowing down the queue
25+
'maximum': 20,
26+
},
27+
'interval': {
28+
'type': 'integer',
29+
'default': 25,
30+
'minimum': 10,
31+
# chosen to avoid slowing down the queue
32+
'maximum': 1000,
33+
},
34+
'bytes': {'type': 'integer', 'default': 56, 'minimum': 12, 'maximum': 65508},
35+
'timeout': {
36+
'type': 'integer',
37+
'default': 800,
38+
'minimum': 5,
39+
# arbitrary chosen to avoid slowing down the queue
40+
'maximum': 1500,
41+
},
42+
}
43+
44+
45+
def get_ping_schema():
46+
schema = {
47+
'$schema': 'http://json-schema.org/draft-07/schema#',
48+
'type': 'object',
49+
'additionalProperties': False,
50+
}
51+
schema['properties'] = deep_merge_dicts(
52+
DEFAULT_PING_CHECK_CONFIG, app_settings.PING_CHECK_CONFIG
53+
)
54+
return schema
55+
56+
57+
class Ping(BaseCheck):
58+
schema = get_ping_schema()
59+
60+
def validate_params(self):
61+
try:
62+
validate(self.params, self.schema, format_checker=draft7_format_checker)
63+
except SchemaError as e:
64+
message = 'Invalid param'
65+
path = '/'.join(e.path)
66+
if path:
67+
message = '{0} in "{1}"'.format(message, path)
68+
message = '{0}: {1}'.format(message, e.message)
69+
raise ValidationError({'params': message}) from e
70+
71+
def check(self, store=True):
72+
count = self._get_param('count')
73+
interval = self._get_param('interval')
74+
bytes_ = self._get_param('bytes')
75+
timeout = self._get_param('timeout')
76+
ip = self._get_ip()
77+
# if the device has no available IP
78+
if not ip:
79+
monitoring = self.related_object.monitoring
80+
# device not known yet, ignore
81+
if monitoring.status == 'unknown':
82+
return
83+
# device is known, simulate down
84+
result = {'reachable': 0, 'loss': 100.0}
85+
if store:
86+
self.store_result(result)
87+
return result
88+
command = [
89+
'fping',
90+
'-e', # show elapsed (round-trip) time of packets
91+
'-c', str(count), # count of pings to send to each target
92+
'-p', str(interval), # interval between sending pings (in ms)
93+
'-b', str(bytes_), # amount of ping data to send
94+
'-t', str(timeout), # individual target initial timeout (in ms)
95+
'-q',
96+
ip
97+
]
98+
stdout, stderr, returncode = self._command(command)
99+
# Check the return code and raise an exception if it's not zero
100+
if returncode not in [0, 1]:
101+
message = f'fping command failed with return code {returncode}:\n\n{stderr.decode("utf8")}'
102+
raise OperationalError(message)
103+
# fpings shows statistics on stderr
104+
output = stderr.decode('utf8')
105+
try:
106+
parts = output.split('=')
107+
if len(parts) > 2:
108+
min, avg, max = parts[-1].strip().split('/')
109+
i = -2
110+
else:
111+
i = -1
112+
sent, received, loss = parts[i].strip().split(',')[0].split('/')
113+
loss = float(loss.strip('%'))
114+
except (IndexError, ValueError) as e:
115+
message = 'Unrecognized fping output:\n\n{0}'.format(output)
116+
raise OperationalError(message) from e
117+
result = {'reachable': int(loss < 100), 'loss': loss}
118+
if result['reachable']:
119+
result.update(
120+
{'rtt_min': float(min), 'rtt_avg': float(avg), 'rtt_max': float(max)}
121+
)
122+
if store:
123+
self.store_result(result)
124+
return result
125+
126+
def store_result(self, result):
127+
"""
128+
store result in the DB
129+
"""
130+
metric = self._get_metric()
131+
copied = result.copy()
132+
reachable = copied.pop('reachable')
133+
metric.write(reachable, extra_values=copied)
134+
135+
def _get_param(self, param):
136+
"""
137+
Gets specified param or its default value according to the schema
138+
"""
139+
return self.params.get(param, self.schema['properties'][param]['default'])
140+
141+
def _get_ip(self):
142+
"""
143+
Figures out ip to use or fails raising OperationalError
144+
"""
145+
device = self.related_object
146+
ip = device.management_ip
147+
if not ip and not app_settings.MANAGEMENT_IP_ONLY:
148+
ip = device.last_ip
149+
return ip
150+
151+
def _command(self, command):
152+
"""
153+
Executes command (easier to mock)
154+
"""
155+
try:
156+
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
157+
stdout, stderr = p.communicate()
158+
return stdout, stderr, p.returncode
159+
except Exception as e:
160+
return None, str(e), 99
161+
162+
def _get_metric(self):
163+
"""
164+
Gets or creates metric
165+
"""
166+
metric, created = self._get_or_create_metric()
167+
if created:
168+
self._create_alert_settings(metric)
169+
self._create_charts(metric)
170+
return metric
171+
172+
def _create_alert_settings(self, metric):
173+
alert_settings = AlertSettings(metric=metric)
174+
alert_settings.full_clean()
175+
alert_settings.save()
176+
177+
def _create_charts(self, metric):
178+
"""
179+
Creates device charts if necessary
180+
"""
181+
charts = ['uptime', 'packet_loss', 'rtt']
182+
for chart in charts:
183+
if chart not in monitoring_settings.AUTO_CHARTS:
184+
continue
185+
chart = Chart(metric=metric, configuration=chart)
186+
chart.full_clean()
187+
chart.save()

0 commit comments

Comments
 (0)