computers: a part marker is its own asset, under the operation it serves
Several Telesis markers serve one operation number - 0613, 0615 and WJPRT each have more than one - so treating the operation as the marker collapsed separate devices into a single record. Their configurations differ by COM port, so in the backup history they overwrote each other, and no question about an individual marker could be asked at all: how many there are, which port one is on, which one failed. There is one marker per PC, which makes the PC the marker's identity, so the collector can mint the marker the same way it already mints a CMM or a Keyence unit for a metrology PC. A marker PC now gets a Part Marker machine asset, the PC controls it, and the marker is partof the operation whose number the PC reports. An operation holds any number of markers. A marker PC therefore does not claim the operation directly. controls propagates through partof, which reference-data already seeds, so control of the operation still follows from controlling its marker - without two markers contesting a link only one of them can hold. Backups from a marker PC resolve to the marker rather than the operation, and fall back to the machine number whenever the marker cannot be resolved: no hostname on the payload, a lean build without the computers or machines plugin, or a marker PC that has not reported to the computers collector yet. Filing under the operation is the old behaviour and beats rejecting a backup. Moving a marker to another operation archives the old membership rather than deleting it, so where a marker used to live stays answerable.
This commit is contained in:
@@ -891,3 +891,120 @@ def test_machinelink_alerts_are_off_by_default(client, db, collector_key,
|
||||
assert alerts == []
|
||||
assert len(_links(machine_3015.assetid)) == 1
|
||||
assert any('taken over' in w for w in r.get_json()['data']['warnings'])
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Part markers: a marker is its own asset, under the operation it serves
|
||||
# =============================================================================
|
||||
|
||||
@pytest.fixture
|
||||
def operation_0615(db, machine_3015):
|
||||
"""An operation number that more than one part marker serves.
|
||||
|
||||
machine_3015 is depended on for the 'machine' asset type, the 'controls'
|
||||
relationship type and 'partof', which the marker links need.
|
||||
"""
|
||||
from shopdb.core.models import AssetType, Asset, RelationshipType
|
||||
|
||||
db.session.add(RelationshipType(relationshiptype='partof',
|
||||
isdirectional=True))
|
||||
mt = AssetType.query.filter_by(assettype='machine').first()
|
||||
asset = Asset(assetnumber='0615', name='Operation 0615',
|
||||
assettypeid=mt.assettypeid, statusid=1)
|
||||
db.session.add(asset)
|
||||
db.session.commit()
|
||||
return asset
|
||||
|
||||
|
||||
def _marker(hostname):
|
||||
return {'hostname': hostname, 'machinenumber': '0615',
|
||||
'pctype': 'gea-shopfloor-partmarker'}
|
||||
|
||||
|
||||
def test_a_marker_pc_gets_a_marker_asset_of_its_own(
|
||||
client, db, collector_key, computer_assettype, operation_0615):
|
||||
from shopdb.core.models import Asset
|
||||
client.post('/api/collector/computers', json=_marker('MARKERPC1'),
|
||||
headers={'X-API-Key': collector_key})
|
||||
|
||||
marker = Asset.query.filter_by(assetnumber='MARKERPC1-PARTMARKER').first()
|
||||
assert marker is not None
|
||||
assert marker.machine is not None
|
||||
assert marker.machine.machinetype.machinetype == 'Part Marker'
|
||||
|
||||
|
||||
def test_two_markers_share_an_operation_without_contesting_it(
|
||||
client, db, collector_key, computer_assettype, operation_0615):
|
||||
"""The reason markers became assets. Both markers serve 0615, and an
|
||||
operation holds any number of them, so neither takes the other's place."""
|
||||
from shopdb.core.models import Asset, AssetRelationship, RelationshipType
|
||||
for host in ('MARKERPC1', 'MARKERPC2'):
|
||||
client.post('/api/collector/computers', json=_marker(host),
|
||||
headers={'X-API-Key': collector_key})
|
||||
|
||||
partof = RelationshipType.query.filter_by(relationshiptype='partof').first()
|
||||
members = AssetRelationship.query.filter_by(
|
||||
targetassetid=operation_0615.assetid,
|
||||
relationshiptypeid=partof.relationshiptypeid,
|
||||
isactive=True).all()
|
||||
assert len(members) == 2
|
||||
|
||||
names = sorted(db.session.get(Asset, rel.sourceassetid).assetnumber
|
||||
for rel in members)
|
||||
assert names == ['MARKERPC1-PARTMARKER', 'MARKERPC2-PARTMARKER']
|
||||
|
||||
|
||||
def test_a_marker_pc_does_not_claim_the_operation_directly(
|
||||
client, db, collector_key, computer_assettype, operation_0615):
|
||||
"""Control of the operation follows the partof rail from the marker. A
|
||||
direct PC claim would be the link two markers cannot both hold."""
|
||||
from shopdb.core.models import AssetRelationship
|
||||
client.post('/api/collector/computers', json=_marker('MARKERPC1'),
|
||||
headers={'X-API-Key': collector_key})
|
||||
|
||||
direct = AssetRelationship.query.filter_by(
|
||||
targetassetid=operation_0615.assetid,
|
||||
label='collector:machine').all()
|
||||
assert direct == []
|
||||
|
||||
|
||||
def test_reporting_again_reuses_the_marker(
|
||||
client, db, collector_key, computer_assettype, operation_0615):
|
||||
"""GE-Enforce posts every cycle; a marker must not be minted each time."""
|
||||
from shopdb.core.models import Asset
|
||||
for _ in range(3):
|
||||
client.post('/api/collector/computers', json=_marker('MARKERPC1'),
|
||||
headers={'X-API-Key': collector_key})
|
||||
markers = Asset.query.filter(
|
||||
Asset.assetnumber.like('%-PARTMARKER')).all()
|
||||
assert len(markers) == 1
|
||||
|
||||
|
||||
def test_moving_a_marker_archives_the_old_operation(
|
||||
client, db, collector_key, computer_assettype, operation_0615,
|
||||
machine_3015):
|
||||
"""Where a marker used to live stays answerable."""
|
||||
from shopdb.core.models import AssetRelationship, RelationshipType
|
||||
client.post('/api/collector/computers', json=_marker('MARKERPC1'),
|
||||
headers={'X-API-Key': collector_key})
|
||||
moved = dict(_marker('MARKERPC1'), machinenumber='3015')
|
||||
client.post('/api/collector/computers', json=moved,
|
||||
headers={'X-API-Key': collector_key})
|
||||
|
||||
partof = RelationshipType.query.filter_by(relationshiptype='partof').first()
|
||||
rows = AssetRelationship.query.filter_by(
|
||||
relationshiptypeid=partof.relationshiptypeid).all()
|
||||
active = [r for r in rows if r.isactive]
|
||||
assert len(rows) == 2, 'the old membership must be kept, not deleted'
|
||||
assert len(active) == 1
|
||||
assert active[0].targetassetid == machine_3015.assetid
|
||||
|
||||
|
||||
def test_an_ordinary_pc_gets_no_marker(
|
||||
client, db, collector_key, computer_assettype, operation_0615):
|
||||
from shopdb.core.models import Asset
|
||||
client.post('/api/collector/computers',
|
||||
json={'hostname': 'PLAINPC', 'machinenumber': '0615',
|
||||
'pctype': 'gea-shopfloor-collections'},
|
||||
headers={'X-API-Key': collector_key})
|
||||
assert Asset.query.filter(Asset.assetnumber.like('%-PARTMARKER')).all() == []
|
||||
|
||||
Reference in New Issue
Block a user