Skip to content

Commit b286324

Browse files
jonathan-r-thorpelo-simongarethsbalaboucristian-recoseanu
authored
BCP-008-01/02 Status Monitoring test suites (#869)
Co-authored-by: Simon Lo <[email protected]> Co-authored-by: Gareth Sylvester-Bradley <[email protected]> Co-authored-by: Gareth Sylvester-Bradley <[email protected]> Co-authored-by: alabou <[email protected]> Co-authored-by: cristian-recoseanu <[email protected]>
1 parent 8861b09 commit b286324

File tree

11 files changed

+1850
-13
lines changed

11 files changed

+1850
-13
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The following test suites are currently supported.
3434
| IS-10-01 | IS-10 Authorization API | | | | Authorization Server |
3535
| IS-11-01 | IS-11 Stream Compatibility Management API | X | | | |
3636
| IS-12-01 | IS-12 Control Protocol API | X | | | See [Invasive Device Model Testing](docs/2.10%20Usage%20-%20Invasive%20Device%20Model%20Testing.md) |
37-
| IS-14-01 | IS-14 Device Configuration API | X | | | |
37+
| IS-14-01 | IS-14 Device Configuration API | X | | | See [Invasive Device Model Testing](docs/2.10%20Usage%20-%20Invasive%20Device%20Model%20Testing.md) |
3838
| - | BCP-002-01 Natural Grouping | X | | | Included in IS-04 Node API suite |
3939
| - | BCP-002-02 Asset Distinguishing Information | X | | | Included in IS-04 Node API suite |
4040
| BCP-003-01 | BCP-003-01 Secure Communication | X | X | | See [Testing TLS](docs/2.2.%20Usage%20-%20Testing%20BCP-003-01%20TLS.md) |
@@ -43,6 +43,8 @@ The following test suites are currently supported.
4343
| BCP-006-01-01 | BCP-006-01 NMOS With JPEG XS | X | | | |
4444
| BCP-006-01-02 | BCP-006-01 Controller | | | X | See [Testing Controllers](docs/2.8.%20Usage%20-%20Testing%20Controllers.md) |
4545
| BCP-006-04 | BCP-006-04 NMOS With MPEG-TS | X | | | |
46+
| BCP-008-01-01 | BCP-008-01 Receiver Status Monitoring | X | | | |
47+
| BCP-008-02-01 | BCP-008-02 Sender Status Monitoring | X | | | |
4648

4749
When testing any of the above APIs it is important that they contain representative data. The test results will generate 'Could Not Test' results if no testable entities can be located. In addition, if devices support many modes of operation (including multiple video/audio formats) it is strongly recommended to re-test them in multiple modes.
4850

docs/2.10 Usage - Invasive Device Model Testing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Invasive tests are those that attempt to change the values of properties, and execute methods on the Device Model of a Node under Test (NuT).
66
Non-invasive tests are limited to changing only the value of User Labels in the NuT's Device Model.
77

8-
By default, the `IS-12-01` test suite will __NOT__ execute invasive tests; these can be enabled by setting `MS05_INVASIVE_TESTING` to `True` in your `UserConfig.py` file as described in [Installation](1.0.%20Installation.md).
8+
By default, the `IS-12-01` and `IS-14-01` test suites will __NOT__ execute invasive tests; these can be enabled by setting `MS05_INVASIVE_TESTING` to `True` in your `UserConfig.py` file as described in [Installation](1.0.%20Installation.md).
99

1010
## Running Interactive Invasive Tests
1111

@@ -21,4 +21,4 @@ python3 nmos-testing-facade.py
2121

2222
By default the Testing Façade will run on localhost port 5001. This can be altered by adding `TESTING_FACADE_PORT` to your `UserConfig.py` file.
2323

24-
In the NMOS Testing Tool, when running the `IS-12-01` test suite, provide the IP/hostname of the Testing Façade (e.g. localhost) and the port of the Testing Façade (use 5001 by default).
24+
In the NMOS Testing Tool, when running the `IS-12-01` or `IS-14-01` test suites, provide the IP/hostname of the Testing Façade (e.g. localhost) and the port of the Testing Façade (use 5001 by default).

nmostesting/Config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,26 @@
386386
}
387387
}
388388
},
389+
"bcp-008-01": {
390+
"repo": "bcp-008-01",
391+
"versions": ["v1.0"],
392+
"default_version": "v1.0",
393+
"apis": {
394+
"receivermonitor": {
395+
"name": "Receiver Monitor"
396+
}
397+
}
398+
},
399+
"bcp-008-02": {
400+
"repo": "bcp-008-02",
401+
"versions": ["v1.0"],
402+
"default_version": "v1.0",
403+
"apis": {
404+
"sendermonitor": {
405+
"name": "Sender Monitor"
406+
}
407+
}
408+
},
389409
"bcp-005-01": {
390410
"repo": "bcp-005-01",
391411
"versions": ["v1.0"],

nmostesting/IS12Utils.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ def __str__(self):
5858

5959

6060
class IS12Notification():
61-
def __init__(self, notification_json):
61+
def __init__(self, notification_json, received_time):
6262
self.oid = notification_json["oid"]
6363
self.eventId = NcEventId(notification_json["eventId"])
6464
self.eventData = NcPropertyChangedEventData(notification_json["eventData"])
65+
self.received_time = received_time
6566

6667

6768
class IS12Utils(MS05Utils):
@@ -167,8 +168,8 @@ def send_command(self, test, command_json):
167168
continue
168169

169170
# find the response to our request
170-
for message in self.ncp_websocket.get_messages():
171-
parsed_message = json.loads(message)
171+
for tm in self.ncp_websocket.get_timestamped_messages():
172+
parsed_message = json.loads(tm.message)
172173

173174
if self.message_type_to_schema_name(parsed_message.get("messageType")):
174175
self._validate_is12_schema(
@@ -190,7 +191,8 @@ def send_command(self, test, command_json):
190191
if parsed_message["messageType"] == MessageTypes.SubscriptionResponse:
191192
results.append(parsed_message["subscriptions"])
192193
if parsed_message["messageType"] == MessageTypes.Notification:
193-
self.notifications += [IS12Notification(n) for n in parsed_message["notifications"]]
194+
self.notifications += [IS12Notification(n, tm.received_time)
195+
for n in parsed_message["notifications"]]
194196
if parsed_message["messageType"] == MessageTypes.Error:
195197
raise NMOSTestException(test.FAIL( # Append the IS12Error so it can be used in negative tests
196198
f"IS-I2 Error: {str(parsed_message)} for command: {str(command_json)}",
@@ -225,8 +227,17 @@ def send_command(self, test, command_json):
225227
return results[0]
226228

227229
def get_notifications(self):
230+
# Get any timestamped messages that have arrived in the interim period
231+
for tm in self.ncp_websocket.get_timestamped_messages():
232+
parsed_message = json.loads(tm.message)
233+
if parsed_message["messageType"] == MessageTypes.Notification:
234+
self.notifications += [IS12Notification(n, tm.received_time)
235+
for n in parsed_message["notifications"]]
228236
return self.notifications
229237

238+
def reset_notifications(self):
239+
self.notifications = []
240+
230241
def start_logging_notifications(self, oid, property):
231242
self.expect_notifications = True
232243
self.expect_notifications_oid = oid

nmostesting/MS05Utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ class NcDeviceManagerProperties(Enum):
171171
NCVERSION = NcPropertyId({"level": 3, "index": 1})
172172

173173

174+
class NcWorkerProperties(Enum):
175+
ENABLED = NcPropertyId({"level": 2, "index": 1})
176+
177+
174178
class NcObjectMethods(Enum):
175179
GENERIC_GET = NcMethodId({"level": 1, "index": 1})
176180
GENERIC_SET = NcMethodId({"level": 1, "index": 2})
@@ -515,6 +519,9 @@ def __init__(self, class_id: List[int], oid: int, owner: Optional[int], role: st
515519
self.runtime_constraints = runtime_constraints
516520
self.member_descriptor = member_descriptor
517521

522+
def __str__(self):
523+
return f"[oid={self.oid}, role_path={self.role_path}]"
524+
518525

519526
class NcBlock(NcObject):
520527
def __init__(self, class_id: List[int], oid: int, owner: Optional[int], role: str, role_path: List[str],

nmostesting/NMOSTesting.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
from .suites import BCP0060101Test
9191
from .suites import BCP0060102Test
9292
from .suites import BCP00604Test
93+
from .suites import BCP0080101Test
94+
from .suites import BCP0080201Test
9395

9496

9597
FLASK_APPS = []
@@ -457,6 +459,66 @@
457459
}],
458460
"class": BCP0060102Test.BCP0060102Test
459461
},
462+
"BCP-008-01": {
463+
"name": "BCP-008-01 Receiver Status Monitoring",
464+
"specs": [{
465+
"spec_key": "is-04",
466+
"api_key": "node",
467+
"disable_fields": ["urlpath"]
468+
}, {
469+
"spec_key": "is-05",
470+
"api_key": "connection",
471+
"disable_fields": ["urlpath"]
472+
}, {
473+
"spec_key": "is-12",
474+
"api_key": "ncp",
475+
"websocket": True,
476+
}, {
477+
"spec_key": "ms-05-02",
478+
"api_key": "controlframework",
479+
"disable_fields": ["host", "port", "urlpath"]
480+
}],
481+
"extra_specs": [{
482+
"spec_key": "nmos-control-feature-sets",
483+
"api_key": "featuresets"
484+
}, {
485+
"spec_key": "bcp-008-01",
486+
"api_key": "receivermonitor",
487+
"disable_fields": ["host", "port", "urlpath"]
488+
}],
489+
"class": BCP0080101Test.BCP0080101Test,
490+
"urlpath": True
491+
},
492+
"BCP-008-02": {
493+
"name": "BCP-008-02 Sender Status Monitoring",
494+
"specs": [{
495+
"spec_key": "is-04",
496+
"api_key": "node",
497+
"disable_fields": ["urlpath"]
498+
}, {
499+
"spec_key": "is-05",
500+
"api_key": "connection",
501+
"disable_fields": ["urlpath"]
502+
}, {
503+
"spec_key": "is-12",
504+
"api_key": "ncp",
505+
"websocket": True,
506+
}, {
507+
"spec_key": "ms-05-02",
508+
"api_key": "controlframework",
509+
"disable_fields": ["host", "port", "urlpath"]
510+
}],
511+
"extra_specs": [{
512+
"spec_key": "nmos-control-feature-sets",
513+
"api_key": "featuresets"
514+
}, {
515+
"spec_key": "bcp-008-02",
516+
"api_key": "sendermonitor",
517+
"disable_fields": ["host", "port", "urlpath"]
518+
}],
519+
"class": BCP0080201Test.BCP0080201Test,
520+
"urlpath": True
521+
},
460522
"BCP-006-04": {
461523
"name": "BCP-006-04 NMOS With MPEG TS",
462524
"specs": [{
@@ -471,7 +533,7 @@
471533
"api_key": "sender-register"
472534
}],
473535
"class": BCP00604Test.BCP00604Test
474-
},
536+
}
475537
}
476538

477539

nmostesting/TestHelper.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525
import jsonref
2626
import netifaces
2727
import paho.mqtt.client as mqtt
28-
from copy import copy
28+
from copy import deepcopy
2929
from pathlib import Path
3030
from enum import IntEnum
3131
from numbers import Number
3232
from functools import cmp_to_key
3333
from collections.abc import KeysView
34+
from threading import Lock
35+
from time import time
3436
from urllib.parse import urlparse
3537

3638
from . import Config as CONFIG
@@ -287,6 +289,11 @@ def check_content_type(headers, expected_type="application/json"):
287289
class WebsocketWorker(threading.Thread):
288290
"""Websocket Client Worker Thread"""
289291

292+
class Message():
293+
def __init__(self, message):
294+
self.received_time = time()
295+
self.message = message
296+
290297
def __init__(self, ws_href):
291298
"""
292299
Initializer
@@ -313,6 +320,7 @@ def __init__(self, ws_href):
313320
self.error_occurred = False
314321
self.connected = False
315322
self.error_message = ""
323+
self.mutex = Lock()
316324

317325
def run(self):
318326
url = urlparse(self.ws.url)
@@ -326,7 +334,8 @@ def on_open(self, ws):
326334
self.connected = True
327335

328336
def on_message(self, ws, message):
329-
self.messages.append(message)
337+
with self.mutex:
338+
self.messages.append(WebsocketWorker.Message(message))
330339

331340
def on_close(self, ws, close_status, close_message):
332341
self.connected = False
@@ -347,8 +356,15 @@ def is_open(self):
347356
return self.connected
348357

349358
def get_messages(self):
350-
msg_cpy = copy(self.messages)
351-
self.clear_messages() # Reset message list after reading
359+
with self.mutex:
360+
msg_cpy = [m.message for m in self.messages]
361+
self.messages.clear() # Reset message list after reading
362+
return msg_cpy
363+
364+
def get_timestamped_messages(self):
365+
with self.mutex:
366+
msg_cpy = deepcopy(self.messages)
367+
self.messages.clear() # Reset message list after reading
352368
return msg_cpy
353369

354370
def did_error_occur(self):
@@ -361,7 +377,8 @@ def is_messages_received(self):
361377
return len(self.messages) > 0
362378

363379
def clear_messages(self):
364-
self.messages.clear()
380+
with self.mutex:
381+
self.messages.clear()
365382

366383

367384
class MQTTClientWorker:

0 commit comments

Comments
 (0)