Skip to content

Commit 264fb7a

Browse files
BCP-007-03 Controller Test Suite (#899)
Signed-off-by: Jonathan Thorpe <jthorpe@nvidia.com>
1 parent e06ef78 commit 264fb7a

13 files changed

Lines changed: 1569 additions & 83 deletions

File tree

nmostesting/ControllerTest.py

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,17 @@
3333
CONN_API_KEY = "connection"
3434
REG_API_KEY = "registration"
3535

36+
RTP_RESOURCE_DATA_DIR = "test_data/controller"
37+
MXL_RESOURCE_DATA_DIR = "test_data/controller/mxl"
38+
MXL_TRANSPORT = "urn:x-nmos:transport:mxl"
39+
MXL_CONNECTION_API_VERSION = "v1.2"
40+
3641

3742
class ControllerTest(GenericTest):
3843
"""
3944
Testing initial set up of new test suite for controller testing
4045
"""
46+
4147
def __init__(self, apis, registries, node, dns_server, auths, disable_auto=True, **kwargs):
4248
# Remove the Testing Facade spec_path as there are no corresponding GitHub repos for the Testing Facade API
4349
apis[TESTING_FACADE_API_KEY].pop("spec_path", None)
@@ -52,7 +58,9 @@ def __init__(self, apis, registries, node, dns_server, auths, disable_auto=True,
5258
self.dns_server = dns_server
5359
self.mock_registry_base_url = ''
5460
self.mock_node_base_url = ''
55-
self.test_data = self.load_resource_data()
61+
self.rtp_test_data = self._load_resource_templates(RTP_RESOURCE_DATA_DIR)
62+
self.rtp_test_data['node']['id'] = self.node.id
63+
self.mxl_test_data = self._load_resource_templates(MXL_RESOURCE_DATA_DIR)
5664
self.senders = []
5765
self.sender_ip_addresses = {}
5866
self.receivers = []
@@ -137,26 +145,72 @@ def _format_device_metadata(self, label, description, id):
137145
""" Used to format answers based on device metadata """
138146
return label + ' (' + description + ', ' + id + ')'
139147

148+
def _sender_transport(self, sender):
149+
return sender.get("transport", self.rtp_test_data["sender"]["transport"])
150+
151+
def _receiver_transport(self, receiver):
152+
return receiver.get("transport", self.rtp_test_data["receiver"]["transport"])
153+
154+
def _uses_mxl_transport(self, transport):
155+
return transport == MXL_TRANSPORT
156+
157+
def _connection_api_version_for_transport(self, transport):
158+
if self._uses_mxl_transport(transport):
159+
return MXL_CONNECTION_API_VERSION
160+
return self.connection_api_version
161+
162+
def _device_controls(self, transport):
163+
connection_api_version = self._connection_api_version_for_transport(transport)
164+
return {
165+
"href": self.mock_node_base_url + "x-nmos/connection/" + connection_api_version + "/",
166+
"type": "urn:x-nmos:control:sr-ctrl/" + connection_api_version
167+
}
168+
169+
def _sender_uses_rtp_transport(self, sender):
170+
return self._sender_transport(sender).startswith("urn:x-nmos:transport:rtp")
171+
172+
def _sender_uses_mxl_transport(self, sender):
173+
return self._uses_mxl_transport(self._sender_transport(sender))
174+
175+
def _receiver_uses_rtp_transport(self, receiver):
176+
return self._receiver_transport(receiver).startswith("urn:x-nmos:transport:rtp")
177+
178+
def _receiver_uses_mxl_transport(self, receiver):
179+
return self._uses_mxl_transport(self._receiver_transport(receiver))
180+
181+
def _resource_templates_for_sender(self, sender):
182+
if self._sender_uses_mxl_transport(sender):
183+
return self.mxl_test_data
184+
return self.rtp_test_data
185+
186+
def _resource_templates_for_receiver(self, receiver):
187+
if self._receiver_uses_mxl_transport(receiver):
188+
return self.mxl_test_data
189+
return self.rtp_test_data
190+
140191
def _populate_registry(self, test):
141192
"""Populate registry and mock node with mock senders and receivers"""
142193
self.node.reset() # Ensure previously added senders and receivers are removed
143194
if self.primary_registry:
144195
self.primary_registry.common.reset() # Ensure any previously registered senders and receivers are removed
145196
sender_ip_final_octet = 159
197+
mxl_domain_id = str(uuid.uuid4())
146198

147199
# Register node
148200
self._register_node(test, self.node.id, "AMWA Test Suite Node", "AMWA Test Suite Node")
149201

150202
# self.senders should be initialized in the set_up_tests() override of derived test
151203
# each mock sender defined as: {'label': <unique label>, 'description': '',
152-
# 'registered': <is registered with mock Registry>}
204+
# 'registered': <is registered with mock Registry>,
205+
# 'transport': <optional; defaults to RTP template transport>}
153206
for sender in self.senders:
154207
sender["id"] = str(uuid.uuid4())
155208
sender["device_id"] = str(uuid.uuid4())
156209
sender["flow_id"] = str(uuid.uuid4())
157210
sender["source_id"] = str(uuid.uuid4())
158-
sender["manifest_href"] = self.mock_node_base_url + "x-nmos/connection/" + self.connection_api_version \
159-
+ "/single/senders/" + sender["id"] + "/transportfile"
211+
if self._sender_uses_rtp_transport(sender):
212+
sender["manifest_href"] = self.mock_node_base_url + "x-nmos/connection/" \
213+
+ self.connection_api_version + "/single/senders/" + sender["id"] + "/transportfile"
160214
sender["version"] = NMOSUtils.get_TAI_time()
161215
sender["display_answer"] = self._format_device_metadata(sender['label'], sender['description'],
162216
sender['id'])
@@ -165,22 +219,26 @@ def _populate_registry(self, test):
165219
time.sleep(0.1)
166220
if sender["registered"]:
167221
self._register_sender(test, sender)
168-
# Add sender to mock node
169-
sender_json = self._create_sender_json(sender)
170-
sender_ip_address = self.senders_ip_base + str(sender_ip_final_octet)
171-
self.node.add_sender(sender_json, sender_ip_address, sender.get("sdp_params", {}))
172-
self.sender_ip_addresses[sender["id"]] = sender_ip_address
173-
sender_ip_final_octet += 1
222+
# Add RTP senders to mock node (IS-05 Connection API)
223+
if self._sender_uses_rtp_transport(sender):
224+
sender_json = self._create_sender_json(sender)
225+
sender_ip_address = self.senders_ip_base + str(sender_ip_final_octet)
226+
self.node.add_sender(sender_json, sender_ip_address, sender.get("sdp_params", {}))
227+
self.sender_ip_addresses[sender["id"]] = sender_ip_address
228+
sender_ip_final_octet += 1
229+
elif self._sender_uses_mxl_transport(sender):
230+
self.node.add_mxl_sender(self._create_sender_json(sender), mxl_domain_id)
174231

175232
# self.receivers should be initialized in the set_up_tests() override of derived test
176233
# each mock receiver defined as: {'label': <unique label>, 'description': '',
177-
# 'connectable': <has IS-05 connection API>, 'registered': <is registered with mock Registry>}
234+
# 'connectable': <has IS-05 connection API>, 'registered': <is registered with mock Registry>,
235+
# 'transport': <optional; defaults to RTP template transport>}
178236
for receiver in self.receivers:
179237
receiver["id"] = str(uuid.uuid4())
180238
receiver["device_id"] = str(uuid.uuid4())
181-
receiver["controls_href"] = self.mock_node_base_url + "x-nmos/connection/" \
182-
+ self.connection_api_version + "/"
183-
receiver["controls_type"] = "urn:x-nmos:control:sr-ctrl/" + self.connection_api_version
239+
receiver_controls = self._device_controls(self._receiver_transport(receiver))
240+
receiver["controls_href"] = receiver_controls["href"]
241+
receiver["controls_type"] = receiver_controls["type"]
184242
receiver["version"] = NMOSUtils.get_TAI_time()
185243
receiver["display_answer"] = self._format_device_metadata(
186244
receiver['label'], receiver['description'], receiver['id'])
@@ -192,20 +250,20 @@ def _populate_registry(self, test):
192250
# Add receiver to mock node
193251
# Note: mock node is currently only a mock Connection API
194252
# so only add 'connectable' receivers
195-
if receiver["connectable"]:
253+
if receiver["connectable"] and self._receiver_uses_rtp_transport(receiver):
196254
receiver_json = self._create_receiver_json(receiver)
197255
self.node.add_receiver(receiver_json)
256+
elif receiver["connectable"] and self._receiver_uses_mxl_transport(receiver):
257+
self.node.add_mxl_receiver(self._create_receiver_json(receiver), mxl_domain_id)
198258

199-
def load_resource_data(self):
200-
"""Loads test data from files"""
201-
result_data = dict()
259+
def _load_resource_templates(self, directory):
260+
"""Loads IS-04 resource templates from a controller test data directory."""
261+
templates = {}
202262
resources = ["node", "device", "source", "flow", "sender", "receiver"]
203263
for resource in resources:
204-
with open("test_data/controller/v1.3_{}.json".format(resource)) as resource_data:
205-
resource_json = json.load(resource_data)
206-
result_data[resource] = resource_json
207-
result_data['node']['id'] = self.node.id
208-
return result_data
264+
with open("{}/v1.3_{}.json".format(directory, resource)) as resource_data:
265+
templates[resource] = json.load(resource_data)
266+
return templates
209267

210268
def post_resource(self, test, type, data=None, reg_url=None, codes=None, fail=Test.FAIL, headers=None):
211269
"""
@@ -214,7 +272,7 @@ def post_resource(self, test, type, data=None, reg_url=None, codes=None, fail=Te
214272
Otherwise, on success, returns values of the Location header and X-Paging-Timestamp debugging header.
215273
"""
216274
if not data:
217-
data = self.test_data[type]
275+
data = self.rtp_test_data[type]
218276

219277
if not reg_url:
220278
reg_url = self.mock_registry_base_url + 'x-nmos/registration/v1.3/'
@@ -258,15 +316,16 @@ def _register_node(self, test, node_id, label, description):
258316
"""
259317
Perform POST requests on the Registration API to create node registration
260318
"""
261-
node_data = deepcopy(self.test_data["node"])
319+
node_data = deepcopy(self.rtp_test_data["node"])
262320
node_data["id"] = node_id
263321
node_data["label"] = label
264322
node_data["description"] = description
265323
node_data["version"] = NMOSUtils.get_TAI_time()
266324
self.post_resource(test, "node", node_data, codes=[201])
267325

268326
def _create_sender_json(self, sender):
269-
sender_data = deepcopy(self.test_data["sender"])
327+
templates = self._resource_templates_for_sender(sender)
328+
sender_data = deepcopy(templates["sender"])
270329

271330
if "sdp_params" in sender:
272331
# Mapping sdp_params names to sender names
@@ -283,6 +342,7 @@ def _create_sender_json(self, sender):
283342
"device_id",
284343
"flow_id",
285344
"manifest_href",
345+
"transport",
286346
"version"]
287347

288348
for property in overridden_properties:
@@ -298,23 +358,24 @@ def _register_sender(self, test, sender, codes=[201], fail=Test.FAIL):
298358
Use to create sender [code=201] or to update existing sender [code=200]
299359
"""
300360
# use the test data as a template for creating new resources
361+
templates = self._resource_templates_for_sender(sender)
301362

302363
# Register device
303-
device_data = deepcopy(self.test_data["device"])
364+
device_data = deepcopy(templates["device"])
304365
device_data["id"] = sender["device_id"]
305366
device_data["label"] = "AMWA Test Device"
306367
device_data["description"] = "AMWA Test Device"
307368
device_data["node_id"] = self.node.id
308-
device_data["controls"][0]["href"] = self.mock_node_base_url + "x-nmos/connection/" \
309-
+ self.connection_api_version + "/"
310-
device_data["controls"][0]["type"] = "urn:x-nmos:control:sr-ctrl/" + self.connection_api_version
369+
sender_controls = self._device_controls(self._sender_transport(sender))
370+
device_data["controls"][0]["href"] = sender_controls["href"]
371+
device_data["controls"][0]["type"] = sender_controls["type"]
311372
device_data["senders"] = [sender["id"]]
312373
device_data["receivers"] = []
313374
device_data["version"] = sender["version"]
314375
self.post_resource(test, "device", device_data, codes=codes, fail=fail)
315376

316377
# Register source
317-
source_data = deepcopy(self.test_data["source"])
378+
source_data = deepcopy(templates["source"])
318379
source_data["id"] = sender["source_id"]
319380
source_data["label"] = "AMWA Test Source"
320381
source_data["description"] = "AMWA Test Source"
@@ -323,7 +384,7 @@ def _register_sender(self, test, sender, codes=[201], fail=Test.FAIL):
323384
self.post_resource(test, "source", source_data, codes=codes, fail=fail)
324385

325386
# Register flow
326-
flow_data = deepcopy(self.test_data["flow"])
387+
flow_data = deepcopy(templates["flow"])
327388
flow_data["id"] = sender["flow_id"]
328389
flow_data["label"] = "AMWA Test Flow"
329390
flow_data["description"] = "AMWA Test Flow"
@@ -351,10 +412,10 @@ def delete_sender(self, test, sender):
351412
raise NMOSTestException(test.FAIL("Registration API returned an unexpected response: {}".format(r)))
352413

353414
def _create_receiver_json(self, receiver):
354-
# Register receiver
355-
receiver_data = deepcopy(self.test_data["receiver"])
415+
templates = self._resource_templates_for_receiver(receiver)
416+
receiver_data = deepcopy(templates["receiver"])
356417

357-
overriden_properties = ["id", "label", "description", "device_id", "version", "caps"]
418+
overriden_properties = ["id", "label", "description", "device_id", "transport", "version", "caps"]
358419

359420
for property in overriden_properties:
360421
if property in receiver:
@@ -369,9 +430,10 @@ def _register_receiver(self, test, receiver, codes=[201], fail=Test.FAIL):
369430
Use to create receiver [code=201] or to update existing receiver [code=200]
370431
"""
371432
# use the test data as a template for creating new resources
433+
templates = self._resource_templates_for_receiver(receiver)
372434

373435
# Register device
374-
device_data = deepcopy(self.test_data["device"])
436+
device_data = deepcopy(templates["device"])
375437
device_data["id"] = receiver["device_id"]
376438
device_data["label"] = "AMWA Test Device"
377439
device_data["description"] = "AMWA Test Device"
@@ -448,7 +510,9 @@ def pre_tests_message(self):
448510
""")
449511

450512
try:
451-
self.testing_facade_utils.invoke_testing_facade(question, [], test_type="action")
513+
metadata = {'test_class': self.__class__.__name__}
514+
self.testing_facade_utils.invoke_testing_facade(
515+
question, [], test_type="action", metadata=metadata)
452516

453517
except TestingFacadeException:
454518
# pre_test_introducton timed out

nmostesting/NMOSTesting.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
from .suites import BCP0050101Test
9090
from .suites import BCP0060101Test
9191
from .suites import BCP0060102Test
92+
from .suites import BCP0070302Test
9293
from .suites import BCP00604Test
9394
from .suites import BCP0070301Test
9495
from .suites import BCP0080101Test
@@ -488,6 +489,22 @@
488489
}],
489490
"class": BCP0070301Test.BCP0070301Test
490491
},
492+
"BCP-007-03-02": {
493+
"name": "BCP-007-03 Controller",
494+
"specs": [{
495+
"spec_key": "testing-facade",
496+
"api_key": "testquestion"
497+
}, {
498+
"spec_key": "is-04",
499+
"api_key": "query",
500+
"disable_fields": ["host", "port"]
501+
}, {
502+
"spec_key": "is-05",
503+
"api_key": "connection",
504+
"disable_fields": ["host", "port"]
505+
}],
506+
"class": BCP0070302Test.BCP0070302Test
507+
},
491508
"BCP-008-01-01": {
492509
"name": "BCP-008-01 Receiver Status Monitoring",
493510
"specs": [{

0 commit comments

Comments
 (0)