geenforce: a PC that has gone quiet stops reading as healthy
A report records how ONE cycle went. Nothing ages it, so a PC that stops reporting keeps the status of its last good cycle: switch a machine off after a clean run and it shows 'ok' indefinitely. One had been offline more than a day and still read 'ok'. Silence is a different fact from the last cycle's outcome, so it is computed separately rather than by rewriting the stored status. receivedat - the server's own clock, not anything a client asserts - is compared against geenforce_reportstaleminutes, default 30, which is roughly two missed cycles at the usual cadence. Set it to 0 to turn the check off. In the table 'stale' takes the badge, because a status from a machine that has not spoken since is not evidence of anything. What it last reported stays in the tooltip with the time it was heard. The stored status is untouched: it is still a true record of that cycle, just not proof the PC is alive. A site whose scope enforces less often than the threshold will read stale while healthy, which is what the setting is for.
This commit is contained in:
@@ -185,3 +185,103 @@ def test_report_missing_hostname_rejected(client, db, app, auth_headers):
|
||||
resp = client.post('/api/geenforce/report', json={'scopename': 'x'},
|
||||
headers={'X-API-Key': secret})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
# -- going quiet ------------------------------------------------------------
|
||||
#
|
||||
# A report records how one cycle went; nothing ages it. A PC switched off after
|
||||
# a clean run therefore kept 'ok' indefinitely and read as healthy - which is
|
||||
# how a machine offline for over a day still showed 'ok'.
|
||||
|
||||
def _age_report(db, hostname, minutes):
|
||||
"""Push a stored report's server-side receivedat back in time."""
|
||||
from datetime import timedelta
|
||||
from plugins.geenforce.models import ManifestEnforcementReport
|
||||
from plugins.geenforce.service import _utcnow
|
||||
(ManifestEnforcementReport.query
|
||||
.filter_by(hostname=hostname, iscurrent=True)
|
||||
.update({'receivedat': _utcnow() - timedelta(minutes=minutes)}))
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _post_clean_report(client, secret, hostname):
|
||||
return client.post('/api/geenforce/report', json={
|
||||
'hostname': hostname, 'scopename': 'gea-shopfloor-cmm',
|
||||
'appliedversion': 1, 'enforcerversion': '2.6',
|
||||
'counts': {'installed': 1, 'skipped': 0, 'failed': 0, 'filtered': 0},
|
||||
'results': [{'name': 'Alpha', 'action': 'installed'}],
|
||||
}, headers={'X-API-Key': secret})
|
||||
|
||||
|
||||
def test_a_pc_reporting_now_is_not_stale(client, db, app, auth_headers):
|
||||
_seed_and_publish(app)
|
||||
secret = _token(client, auth_headers, ['geenforce.report'])
|
||||
_post_clean_report(client, secret, 'WJCMM01')
|
||||
|
||||
row = client.get('/api/geenforce/reports',
|
||||
headers=auth_headers).get_json()['data'][0]
|
||||
assert row['status'] == 'ok'
|
||||
assert row['isstale'] is False
|
||||
assert row['staleafterminutes'] == 30
|
||||
|
||||
|
||||
def test_a_pc_that_stopped_reporting_goes_stale(client, db, app, auth_headers):
|
||||
_seed_and_publish(app)
|
||||
secret = _token(client, auth_headers, ['geenforce.report'])
|
||||
_post_clean_report(client, secret, 'WJCMM01')
|
||||
_age_report(db, 'WJCMM01', minutes=60 * 26) # offline over a day
|
||||
|
||||
row = client.get('/api/geenforce/reports',
|
||||
headers=auth_headers).get_json()['data'][0]
|
||||
assert row['isstale'] is True
|
||||
# The REPORTED status is left alone - it is still a true record of the last
|
||||
# cycle, and the UI shows it in the tooltip behind the stale badge.
|
||||
assert row['status'] == 'ok'
|
||||
|
||||
|
||||
def test_stale_boundary_is_the_configured_threshold(client, db, app, auth_headers):
|
||||
_seed_and_publish(app)
|
||||
secret = _token(client, auth_headers, ['geenforce.report'])
|
||||
_post_clean_report(client, secret, 'WJCMM01')
|
||||
|
||||
_age_report(db, 'WJCMM01', minutes=29)
|
||||
row = client.get('/api/geenforce/reports',
|
||||
headers=auth_headers).get_json()['data'][0]
|
||||
assert row['isstale'] is False, 'inside the window is not stale'
|
||||
|
||||
_age_report(db, 'WJCMM01', minutes=31)
|
||||
row = client.get('/api/geenforce/reports',
|
||||
headers=auth_headers).get_json()['data'][0]
|
||||
assert row['isstale'] is True, 'past the window is stale'
|
||||
|
||||
|
||||
def test_stale_threshold_is_a_setting(client, db, app, auth_headers):
|
||||
from shopdb.api import Setting
|
||||
_seed_and_publish(app)
|
||||
secret = _token(client, auth_headers, ['geenforce.report'])
|
||||
_post_clean_report(client, secret, 'WJCMM01')
|
||||
_age_report(db, 'WJCMM01', minutes=45)
|
||||
|
||||
Setting.set('geenforce_reportstaleminutes', '120', valuetype='integer',
|
||||
category='geenforce')
|
||||
db.session.commit()
|
||||
row = client.get('/api/geenforce/reports',
|
||||
headers=auth_headers).get_json()['data'][0]
|
||||
assert row['isstale'] is False
|
||||
assert row['staleafterminutes'] == 120
|
||||
|
||||
|
||||
def test_zero_disables_the_stale_check(client, db, app, auth_headers):
|
||||
from shopdb.api import Setting
|
||||
_seed_and_publish(app)
|
||||
secret = _token(client, auth_headers, ['geenforce.report'])
|
||||
_post_clean_report(client, secret, 'WJCMM01')
|
||||
_age_report(db, 'WJCMM01', minutes=60 * 24 * 7)
|
||||
|
||||
Setting.set('geenforce_reportstaleminutes', '0', valuetype='integer',
|
||||
category='geenforce')
|
||||
db.session.commit()
|
||||
row = client.get('/api/geenforce/reports',
|
||||
headers=auth_headers).get_json()['data'][0]
|
||||
assert row['isstale'] is False
|
||||
assert row['staleafterminutes'] == 0
|
||||
|
||||
Reference in New Issue
Block a user