Skip to content

Commit

Permalink
Merge 'origin/master' into 'origin/udns'
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin163168 committed Feb 20, 2019
2 parents 9553ae8 + 7ee93e4 commit 8677830
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 38 deletions.
15 changes: 9 additions & 6 deletions Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Enable or disable mDNS advertisements. Browsing is always permitted.
# The IS-04 Node tests create a mock registry on the network unless the `ENABLE_MDNS` parameter is set to `False`.
# Enable or disable DNS-SD advertisements. Browsing is always permitted.
# The IS-04 Node tests create a mock registry on the network unless the `ENABLE_DNS_SD` parameter is set to `False`.
# If set to `False`, make sure to update the Query API hostname/IP and port via `QUERY_API_HOST` and `QUERY_API_PORT`.
ENABLE_MDNS = True
ENABLE_DNS_SD = True

# Number of seconds to wait after an mDNS advert is created for a client to notice and perform an action
MDNS_ADVERT_TIMEOUT = 5
# Set the DNS-SD mode to either 'multicast' or 'unicast'
DNS_SD_MODE = 'unicast'

# Number of seconds to wait after a DNS-SD advert is created for a client to notice and perform an action
DNS_SD_ADVERT_TIMEOUT = 5

# Number of seconds expected between heartbeats
HEARTBEAT_INTERVAL = 5

# Number of seconds to wait for the garbage collection
GARBAGE_COLLECTION_TIMEOUT = 12

# Set a Query API hostname/IP and port for use when operating without mDNS
# Set a Query API hostname/IP and port for use when operating without DNS-SD
QUERY_API_HOST = "127.0.0.1"
QUERY_API_PORT = 80

Expand Down
74 changes: 45 additions & 29 deletions IS0401Test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from TestResult import Test
from GenericTest import GenericTest, NMOSTestException
from IS04Utils import IS04Utils
from Config import ENABLE_MDNS, QUERY_API_HOST, QUERY_API_PORT, MDNS_ADVERT_TIMEOUT, HEARTBEAT_INTERVAL
from Config import ENABLE_DNS_SD, QUERY_API_HOST, QUERY_API_PORT, DNS_SD_MODE, DNS_SD_ADVERT_TIMEOUT, HEARTBEAT_INTERVAL

NODE_API_KEY = "node"

Expand All @@ -40,6 +40,8 @@ def __init__(self, apis, registries, node):
self.node_url = self.apis[NODE_API_KEY]["url"]
self.registry_basics_done = False
self.is04_utils = IS04Utils(self.node_url)
self.zc = None
self.zc_listener = None

def set_up_tests(self):
self.zc = Zeroconf()
Expand All @@ -54,7 +56,6 @@ def _registry_mdns_info(self, port, priority=0):
"""Get an mDNS ServiceInfo object in order to create an advertisement"""
default_gw_interface = netifaces.gateways()['default'][netifaces.AF_INET][1]
default_ip = netifaces.ifaddresses(default_gw_interface)[netifaces.AF_INET][0]['addr']

# TODO: Add another test which checks support for parsing CSV string in api_ver
txt = {'api_ver': self.apis[NODE_API_KEY]["version"], 'api_proto': 'http', 'pri': str(priority)}

Expand All @@ -71,15 +72,16 @@ def _registry_mdns_info(self, port, priority=0):
def do_registry_basics_prereqs(self):
"""Advertise a registry and collect data from any Nodes which discover it"""

if self.registry_basics_done or not ENABLE_MDNS:
if self.registry_basics_done or not ENABLE_DNS_SD:
return

registry_mdns = []
priority = 0
for registry in self.registries:
info = self._registry_mdns_info(registry.get_port(), priority)
registry_mdns.append(info)
priority += 10
if DNS_SD_MODE == "multicast":
registry_mdns = []
priority = 0
for registry in self.registries:
info = self._registry_mdns_info(registry.get_port(), priority)
registry_mdns.append(info)
priority += 10

# Reset all registries to clear previous heartbeats, etc.
for registry in self.registries:
Expand All @@ -88,11 +90,12 @@ def do_registry_basics_prereqs(self):
registry = self.registries[0]
self.registries[0].enable()

# Advertise a registry at pri 0 and allow the Node to do a basic registration
self.zc.register_service(registry_mdns[0])
if DNS_SD_MODE == "multicast":
# Advertise a registry at pri 0 and allow the Node to do a basic registration
self.zc.register_service(registry_mdns[0])

# Wait for n seconds after advertising the service for the first POST from a Node
time.sleep(MDNS_ADVERT_TIMEOUT)
time.sleep(DNS_SD_ADVERT_TIMEOUT)

# Wait until we're sure the Node has registered everything it intends to, and we've had at least one heartbeat
while (time.time() - self.registries[0].last_time) < HEARTBEAT_INTERVAL + 1:
Expand All @@ -107,7 +110,8 @@ def do_registry_basics_prereqs(self):
# Once registered, advertise all other registries at different (ascending) priorities
for index, registry in enumerate(self.registries[1:]):
registry.enable()
self.zc.register_service(registry_mdns[index + 1])
if DNS_SD_MODE == "multicast":
self.zc.register_service(registry_mdns[index + 1])

# Kill registries one by one to collect data around failover
for index, registry in enumerate(self.registries):
Expand All @@ -129,18 +133,20 @@ def do_registry_basics_prereqs(self):

# Clean up mDNS advertisements and disable registries
for index, registry in enumerate(self.registries):
self.zc.unregister_service(registry_mdns[index])
if DNS_SD_MODE == "multicast":
self.zc.unregister_service(registry_mdns[index])
registry.disable()

self.registry_basics_done = True

def test_01(self):
"""Node can discover network registration service via mDNS"""
"""Node can discover network registration service via multicast DNS"""

test = Test("Node can discover network registration service via mDNS")
test = Test("Node can discover network registration service via multicast DNS")

if not ENABLE_MDNS:
return test.DISABLED("This test cannot be performed when ENABLE_MDNS is False")
if not ENABLE_DNS_SD or DNS_SD_MODE != "multicast":
return test.DISABLED("This test cannot be performed when ENABLE_DNS_SD is False or DNS_SD_MODE is not "
"'multicast'")

self.do_registry_basics_prereqs()

Expand All @@ -153,17 +159,27 @@ def test_01(self):
def test_02(self):
"""Node can discover network registration service via unicast DNS"""

# TODO: Provide an option for the user to set up their own unicast DNS server?
test = Test("Node can discover network registration service via unicast DNS")
return test.MANUAL()

if not ENABLE_DNS_SD or DNS_SD_MODE != "unicast":
return test.DISABLED("This test cannot be performed when ENABLE_DNS_SD is False or DNS_SD_MODE is not "
"'unicast'")

self.do_registry_basics_prereqs()

registry = self.registries[0]
if len(registry.get_data()) > 0:
return test.PASS()

return test.FAIL("Node did not attempt to register with the advertised registry.")

def test_03(self):
"""Registration API interactions use the correct Content-Type"""

test = Test("Registration API interactions use the correct Content-Type")

if not ENABLE_MDNS:
return test.DISABLED("This test cannot be performed when ENABLE_MDNS is False")
if not ENABLE_DNS_SD:
return test.DISABLED("This test cannot be performed when ENABLE_DNS_SD is False")

self.do_registry_basics_prereqs()

Expand Down Expand Up @@ -195,7 +211,7 @@ def check_mdns_ver(self):

def get_registry_resource(self, res_type, res_id):
found_resource = None
if ENABLE_MDNS:
if ENABLE_DNS_SD:
# Look up data in local mock registry
registry = self.registries[0]
for resource in registry.get_data():
Expand Down Expand Up @@ -269,8 +285,8 @@ def test_05(self):

test = Test("Node maintains itself in the registry via periodic calls to the health resource")

if not ENABLE_MDNS:
return test.DISABLED("This test cannot be performed when ENABLE_MDNS is False")
if not ENABLE_DNS_SD:
return test.DISABLED("This test cannot be performed when ENABLE_DNS_SD is False")

self.do_registry_basics_prereqs()

Expand Down Expand Up @@ -508,8 +524,8 @@ def test_15(self):

test = Test("Node correctly selects a Registration API based on advertised priorities")

if not ENABLE_MDNS:
return test.DISABLED("This test cannot be performed when ENABLE_MDNS is False")
if not ENABLE_DNS_SD:
return test.DISABLED("This test cannot be performed when ENABLE_DNS_SD is False")

self.do_registry_basics_prereqs()

Expand Down Expand Up @@ -537,8 +553,8 @@ def test_16(self):

test = Test("Node correctly fails over between advertised Registration APIs when one fails")

if not ENABLE_MDNS:
return test.DISABLED("This test cannot be performed when ENABLE_MDNS is False")
if not ENABLE_DNS_SD:
return test.DISABLED("This test cannot be performed when ENABLE_DNS_SD is False")

self.do_registry_basics_prereqs()

Expand Down
1 change: 1 addition & 0 deletions IS0402Test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self, apis):
self.reg_url = self.apis[REG_API_KEY]["url"]
self.query_url = self.apis[QUERY_API_KEY]["url"]
self.zc = None
self.zc_listener = None
self.is04_reg_utils = IS04Utils(self.reg_url)
self.is04_query_utils = IS04Utils(self.query_url)
self.test_data = self.load_resource_data()
Expand Down
6 changes: 5 additions & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The following test sets are currently supported:
When testing any of the above APIs it is important that they contain representative data. The test results will generate 'N/A' results if no testable entities can be located. In addition, if device support many modes of operation (including multiple video/audio formats) it is strongly recommended to re-test them in multiple modes.

**Attention:**
* The IS-04 Node tests create a mock registry on the network unless the `Config.py` `ENABLE_MDNS` parameter is set to `False`. It is critical that these tests are only run in isolated network segments away from production Nodes and registries. Only one Node can be tested at a single time. If `ENABLE_MDNS` is set to `False`, make sure to update the Query API hostname/IP and port via `QUERY_API_HOST` and `QUERY_API_PORT` in the `Config.py`.
* The IS-04 Node tests create mock registry mDNS announcements on the network unless the Config.py `ENABLE_DNS_SD` parameter is set to `False`, or the `DNS_SD_MODE` parameter is set to `'unicast'`. It is critical that these tests are only run in isolated network segments away from production Nodes and registries. Only one Node can be tested at a single time.
* For IS-05 tests #29 and #30 (absolute activation), make sure the time of the test device and the time of the device hosting the tests is synchronized.

## Usage
Expand All @@ -25,6 +25,10 @@ Install the dependencies with `pip3 install -r requirements.txt` and start the s
This tool provides a simple web service which is available on `http://localhost:5000`.
Provide the URL of the relevant API under test (see the detailed description on the webpage) and select a test from the checklist. The result of the test will be shown after a few seconds.

### Testing Unicast discovery

In order to test unicast discovery, ensure the `DNS_SD_MODE` is set to `'unicast'`. Additionally, ensure that the unit under test has its search domain set to 'testsuite.nmos.tv' and the DNS server IP to the IP address of the server which is running the test suite instance.

## External Dependencies

* Python 3
Expand Down
29 changes: 28 additions & 1 deletion nmos-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@
from wtforms import Form, validators, StringField, SelectField, IntegerField, HiddenField, FormField, FieldList
from Registry import NUM_REGISTRIES, REGISTRIES, REGISTRY_API
from Node import NODE, NODE_API
from Config import CACHE_PATH, SPECIFICATIONS
from Config import CACHE_PATH, SPECIFICATIONS, ENABLE_DNS_SD, DNS_SD_MODE
from datetime import datetime, timedelta
from dnslib.server import DNSServer
from dnslib.zoneresolver import ZoneResolver

import git
import os
import json
import copy
import pickle
import threading
import sys
import netifaces

import IS0401Test
import IS0402Test
Expand Down Expand Up @@ -247,6 +251,10 @@ def index_page():


if __name__ == '__main__':
if ENABLE_DNS_SD and DNS_SD_MODE == "unicast" and os.geteuid() != 0:
print(" * ERROR: In order to test DNS-SD in unicast mode, the test suite must be run with elevated permissions")
sys.exit(1)

print(" * Initialising specification repositories...")

if not os.path.exists(CACHE_PATH):
Expand Down Expand Up @@ -299,4 +307,23 @@ def index_page():
t.start()
port += 1

dns_server = None
if ENABLE_DNS_SD and DNS_SD_MODE == "unicast":
default_gw_interface = netifaces.gateways()['default'][netifaces.AF_INET][1]
default_ip = netifaces.ifaddresses(default_gw_interface)[netifaces.AF_INET][0]['addr']
print(" * Starting DNS server on {}:53".format(default_ip))
zone_file = open("test_data/IS0401/dns.zone").read()
zone_file.replace("127.0.0.1", default_ip)
resolver = ZoneResolver(zone_file)
try:
dns_server = DNSServer(resolver, port=53, address=default_ip)
dns_server.start_thread()
except Exception as e:
print(" * ERROR: Unable to bind to port 53. DNS server could not start: {}".format(e))

# This call will block until interrupted
core_app.run(host='0.0.0.0', port=5000, threaded=True)

print(" * Exiting")
if dns_server:
dns_server.stop()
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ gitpython
ramlfications
websocket-client
jsonref

dnslib
69 changes: 69 additions & 0 deletions test_data/IS0401/dns.zone
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
$ORIGIN testsuite.nmos.tv.
$TTL 60s

testsuite.nmos.tv. IN SOA ns.testsuite.nmos.tv. postmaster.testsuite.nmos.tv. ( 2007120710 1d 2h 4w 1h )
testsuite IN A 127.0.0.1

; These lines indicate to clients that this server supports DNS Service Discovery
b._dns-sd._udp IN PTR @
lb._dns-sd._udp IN PTR @

; These lines indicate to clients which service types this server may advertise
_services._dns-sd._udp PTR _nmos-registration._tcp
_services._dns-sd._udp PTR _nmos-register._tcp
_services._dns-sd._udp PTR _nmos-query._tcp

; These lines give the fully qualified DNS names to the IP addresses of the hosts which we'd like to discover
registration5001.testsuite.nmos.tv. IN A 127.0.0.1
query5001.testsuite.nmos.tv. IN A 127.0.0.1
registration5002.testsuite.nmos.tv. IN A 127.0.0.1
query5002.testsuite.nmos.tv. IN A 127.0.0.1
registration5003.testsuite.nmos.tv. IN A 127.0.0.1
query5003.testsuite.nmos.tv. IN A 127.0.0.1
registration5004.testsuite.nmos.tv. IN A 127.0.0.1
query5004.testsuite.nmos.tv. IN A 127.0.0.1
registration5005.testsuite.nmos.tv. IN A 127.0.0.1
query5005.testsuite.nmos.tv. IN A 127.0.0.1

; There should be one PTR record for each instance of the service you wish to advertise.
_nmos-registration._tcp PTR reg-api-5001._nmos-registration._tcp
_nmos-register._tcp PTR reg-api-5001._nmos-registration._tcp
_nmos-query._tcp PTR qry-api-5001._nmos-query._tcp
_nmos-registration._tcp PTR reg-api-5002._nmos-registration._tcp
_nmos-register._tcp PTR reg-api-5002._nmos-registration._tcp
_nmos-query._tcp PTR qry-api-5002._nmos-query._tcp
_nmos-registration._tcp PTR reg-api-5003._nmos-registration._tcp
_nmos-register._tcp PTR reg-api-5003._nmos-registration._tcp
_nmos-query._tcp PTR qry-api-5003._nmos-query._tcp
_nmos-registration._tcp PTR reg-api-5004._nmos-registration._tcp
_nmos-register._tcp PTR reg-api-5004._nmos-registration._tcp
_nmos-query._tcp PTR qry-api-5004._nmos-query._tcp
_nmos-registration._tcp PTR reg-api-5005._nmos-registration._tcp
_nmos-register._tcp PTR reg-api-5005._nmos-registration._tcp
_nmos-query._tcp PTR qry-api-5005._nmos-query._tcp

; Next we have a SRV and a TXT record corresponding to each PTR above, first the Registration API
; The SRV links the PTR name to a resolvable DNS name (see the A records above) and identify the port which the API runs on
; The TXT records indicate additional metadata relevant to the IS-04 spec
reg-api-5001._nmos-registration._tcp SRV 0 0 5001 registration5001.testsuite.nmos.tv.
reg-api-5001._nmos-registration._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=0"
reg-api-5002._nmos-registration._tcp SRV 0 0 5002 registration5002.testsuite.nmos.tv.
reg-api-5002._nmos-registration._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=10"
reg-api-5003._nmos-registration._tcp SRV 0 0 5003 registration5003.testsuite.nmos.tv.
reg-api-5003._nmos-registration._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=20"
reg-api-5004._nmos-registration._tcp SRV 0 0 5004 registration5004.testsuite.nmos.tv.
reg-api-5004._nmos-registration._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=30"
reg-api-5005._nmos-registration._tcp SRV 0 0 5005 registration5005.testsuite.nmos.tv.
reg-api-5005._nmos-registration._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=40"

; Finally, the SRV and TXT for the Query API
qry-api-5001._nmos-query._tcp SRV 0 0 5001 query5001.testsuite.nmos.tv.
qry-api-5001._nmos-query._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=0"
qry-api-5002._nmos-query._tcp SRV 0 0 5002 query5002.testsuite.nmos.tv.
qry-api-5002._nmos-query._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=10"
qry-api-5003._nmos-query._tcp SRV 0 0 5003 query5003.testsuite.nmos.tv.
qry-api-5003._nmos-query._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=20"
qry-api-5004._nmos-query._tcp SRV 0 0 5004 query5004.testsuite.nmos.tv.
qry-api-5004._nmos-query._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=30"
qry-api-5005._nmos-query._tcp SRV 0 0 5005 query5005.testsuite.nmos.tv.
qry-api-5005._nmos-query._tcp TXT "api_ver=v1.0,v1.1,v1.2,v1.3" "api_proto=http" "pri=40"

0 comments on commit 8677830

Please sign in to comment.