Let the server decide which NIC is a PC's address, and keep the rest
A CMM reported 100.0.0.1 as its address and never reported 10.48.255.210 at all. Both halves of that were the same mistake. THE BAY WAS CHOOSING. The collector schema took a single `ipaddress`, so the script picked one NIC and dropped the others - preferring an address inside a configured range, else whichever interface carried a default route. A bay cannot make that judgement: it does not know the site's addressing. On this CMM the Marvell AQtion 10Gbit link to its controller is a real hardware NIC, up, and carries a default route of its own, so it satisfied every test the script had. It won, and the corporate address was discarded before the request was even built - which is why it is not in ShopDB even as a second row. So the bay now reports EVERY connected hardware NIC with a routable IPv4, and takes no view. The server picks the primary from collector_corporate_ranges, a new per-site setting, and writes a Communication row for each of the others. Naming the ranges is what turns the answer from a guess into a decision; with none set it still falls back to the default-route flag and then to the first reported, both guesses, both better than before only because nothing is thrown away now. Rows are keyed on the MAC where the bay sends one, because an address moves between adapters and a MAC does not. A NIC that is down at check-in is left alone rather than deleted - being down is not evidence the record is wrong, and hand-entered gateway, DNS and serial settings live on those same rows. Exactly one row ends up flagged primary, and only a row this report touched can claim it, so a check-in cannot silently demote an address set by hand. -AllowedRanges and the CollectorRanges registry value are now accepted and ignored, logging a line that says where the setting moved. The parameter stays because a scheduled task still passing it would otherwise fail on an unknown argument. A bay on an older collector sends one address and no interfaces; that keeps working and is pinned by a test. Scope: product
This commit is contained in:
111
tests/test_plugins/test_collector_network_interfaces.py
Normal file
111
tests/test_plugins/test_collector_network_interfaces.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""Which NIC becomes the PC's address, and what happens to the others.
|
||||
|
||||
The bay used to choose. It cannot: it does not know the site's addressing. A
|
||||
CMM whose Marvell AQtion 10Gbit link to its controller sits on 100.0.0.1
|
||||
reported THAT as the PC's address - the link carries a default route too - and
|
||||
the corporate address was never sent at all, so ShopDB could not show it even
|
||||
as a second row.
|
||||
|
||||
The bay now reports every NIC and takes no view. These pin the server side.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from shopdb.core.models import Asset, AssetType, Communication, CommunicationType
|
||||
|
||||
KEY = 'network-key'
|
||||
CORP = '10.48.255.210'
|
||||
MACHINE_LINK = '100.0.0.1'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def collector_key(app):
|
||||
old = app.config.get('COLLECTOR_API_KEY')
|
||||
app.config['COLLECTOR_API_KEY'] = KEY
|
||||
yield KEY
|
||||
app.config['COLLECTOR_API_KEY'] = old
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rig(db):
|
||||
if not AssetType.query.filter_by(assettype='computer').first():
|
||||
db.session.add(AssetType(assettype='computer'))
|
||||
if not CommunicationType.query.filter_by(comtype='IP').first():
|
||||
db.session.add(CommunicationType(comtype='IP'))
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _report(client, key, hostname, interfaces):
|
||||
return client.post('/api/collector/computers',
|
||||
json={'hostname': hostname, 'interfaces': interfaces},
|
||||
headers={'X-API-Key': key})
|
||||
|
||||
|
||||
def _comms(hostname):
|
||||
asset = Asset.query.filter_by(assetnumber=hostname).first()
|
||||
return Communication.query.filter_by(assetid=asset.assetid).all()
|
||||
|
||||
|
||||
BENS_CMM = [
|
||||
{'ipaddress': MACHINE_LINK, 'macaddress': 'AA:BB:CC:00:00:01',
|
||||
'adaptername': 'Marvell AQtion 10Gbit Network Adapter', 'isdefaultroute': True},
|
||||
{'ipaddress': CORP, 'macaddress': 'AA:BB:CC:00:00:02',
|
||||
'adaptername': 'Intel I219-LM', 'isdefaultroute': True},
|
||||
]
|
||||
|
||||
|
||||
def test_both_addresses_are_recorded(client, db, rig, collector_key):
|
||||
"""The one that is not primary must still EXIST. It used to be discarded."""
|
||||
_report(client, collector_key, 'CMM01', BENS_CMM)
|
||||
addresses = {c.ipaddress for c in _comms('CMM01')}
|
||||
assert addresses == {MACHINE_LINK, CORP}
|
||||
|
||||
|
||||
def test_the_configured_range_decides_the_primary(client, db, rig, collector_key):
|
||||
"""With the site's range named, the machine link cannot win."""
|
||||
from shopdb.core.models import Setting
|
||||
Setting.set('collector_corporate_ranges', '10.48.0.0/16',
|
||||
valuetype='string', category='computers')
|
||||
db.session.commit()
|
||||
|
||||
_report(client, collector_key, 'CMM02', BENS_CMM)
|
||||
primary = [c for c in _comms('CMM02') if c.isprimary]
|
||||
assert len(primary) == 1
|
||||
assert primary[0].ipaddress == CORP
|
||||
|
||||
|
||||
def test_exactly_one_primary_even_when_both_claim_a_default_route(
|
||||
client, db, rig, collector_key):
|
||||
"""Ben's CMM: two NICs, both routing. Ambiguous, but never two primaries."""
|
||||
_report(client, collector_key, 'CMM03', BENS_CMM)
|
||||
assert sum(1 for c in _comms('CMM03') if c.isprimary) == 1
|
||||
|
||||
|
||||
def test_a_second_checkin_does_not_duplicate_rows(client, db, rig, collector_key):
|
||||
"""Keyed on the MAC, so the same NIC updates rather than accumulating."""
|
||||
_report(client, collector_key, 'CMM04', BENS_CMM)
|
||||
_report(client, collector_key, 'CMM04', BENS_CMM)
|
||||
assert len(_comms('CMM04')) == 2
|
||||
|
||||
|
||||
def test_an_address_moving_to_another_nic_follows_the_mac(
|
||||
client, db, rig, collector_key):
|
||||
"""A MAC outlives an address, which is why it is the key."""
|
||||
_report(client, collector_key, 'CMM05', BENS_CMM)
|
||||
moved = [dict(entry) for entry in BENS_CMM]
|
||||
moved[1]['ipaddress'] = '10.48.255.211'
|
||||
_report(client, collector_key, 'CMM05', moved)
|
||||
|
||||
rows = _comms('CMM05')
|
||||
assert len(rows) == 2, 'a changed address must not mint a second row'
|
||||
assert '10.48.255.211' in {c.ipaddress for c in rows}
|
||||
|
||||
|
||||
def test_an_older_bay_sending_one_address_still_works(client, db, rig, collector_key):
|
||||
"""Bays run whatever collector they were imaged with."""
|
||||
client.post('/api/collector/computers',
|
||||
json={'hostname': 'CMM06', 'ipaddress': CORP},
|
||||
headers={'X-API-Key': collector_key})
|
||||
rows = _comms('CMM06')
|
||||
assert len(rows) == 1
|
||||
assert rows[0].ipaddress == CORP and rows[0].isprimary
|
||||
Reference in New Issue
Block a user