1#!/usr/bin/env python3 2# 3# Copyright (c) 2020, The OpenThread Authors. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 2. Redistributions in binary form must reproduce the above copyright 11# notice, this list of conditions and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# 3. Neither the name of the copyright holder nor the 14# names of its contributors may be used to endorse or promote products 15# derived from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29 30import urllib.request 31import urllib.error 32import ipaddress 33import json 34import re 35from threading import Thread 36 37rest_api_addr = "http://0.0.0.0:8081" 38 39 40def assert_is_ipv6_address(string): 41 assert (type(ipaddress.ip_address(string)) is ipaddress.IPv6Address) 42 43def get_data_from_url(url, result, index): 44 response = urllib.request.urlopen(urllib.request.Request(url)) 45 body = response.read() 46 data = json.loads(body) 47 result[index] = data 48 49 50def get_error_from_url(url, result, index): 51 try: 52 urllib.request.urlopen(urllib.request.Request(url)) 53 assert False 54 55 except urllib.error.HTTPError as e: 56 result[index] = e 57 58 59def create_multi_thread(func, url, thread_num, response_data): 60 threads = [None] * thread_num 61 62 for i in range(thread_num): 63 threads[i] = Thread(target=func, args=(url, response_data, i)) 64 65 for thread in threads: 66 thread.start() 67 68 for thread in threads: 69 thread.join() 70 71 72def error404_check(data): 73 assert data is not None 74 75 assert (data.code == 404) 76 77 return True 78 79 80def diagnostics_check(data): 81 assert data is not None 82 83 if len(data) == 0: 84 return 1 85 for diag in data: 86 expected_keys = [ 87 "ExtAddress", "Rloc16", "Mode", "Connectivity", "Route", 88 "LeaderData", "NetworkData", "IP6AddressList", "MACCounters", 89 "ChildTable", "ChannelPages" 90 ] 91 expected_value_type = [ 92 str, int, dict, dict, dict, dict, str, list, dict, list, 93 str 94 ] 95 expected_check_dict = dict(zip(expected_keys, expected_value_type)) 96 97 for key, value in expected_check_dict.items(): 98 assert (key in diag) 99 assert (type(diag[key]) == value) 100 101 assert (re.match(r'^[A-F0-9]{16}$', diag["ExtAddress"]) is not None) 102 103 mode = diag["Mode"] 104 mode_expected_keys = [ 105 "RxOnWhenIdle", "DeviceType", "NetworkData" 106 ] 107 for key in mode_expected_keys: 108 assert (key in mode) 109 assert (type(mode[key]) == int) 110 111 connectivity = diag["Connectivity"] 112 connectivity_expected_keys = [ 113 "ParentPriority", "LinkQuality3", "LinkQuality2", "LinkQuality1", 114 "LeaderCost", "IdSequence", "ActiveRouters", "SedBufferSize", 115 "SedDatagramCount" 116 ] 117 for key in connectivity_expected_keys: 118 assert (key in connectivity) 119 assert (type(connectivity[key]) == int) 120 121 route = diag["Route"] 122 assert ("IdSequence" in route) 123 assert (type(route["IdSequence"]) == int) 124 125 assert ("RouteData" in route) 126 route_routedata = route["RouteData"] 127 assert (type(route["RouteData"]) == list) 128 129 routedata_expected_keys = [ 130 "RouteId", "LinkQualityOut", "LinkQualityIn", "RouteCost" 131 ] 132 133 for item in route_routedata: 134 for key in routedata_expected_keys: 135 assert (key in item) 136 assert (type(item[key]) == int) 137 138 leaderdata = diag["LeaderData"] 139 leaderdata_expected_keys = [ 140 "PartitionId", "Weighting", "DataVersion", "StableDataVersion", 141 "LeaderRouterId" 142 ] 143 144 for key in leaderdata_expected_keys: 145 assert (key in leaderdata) 146 assert (type(leaderdata[key]) == int) 147 148 assert (re.match(r'^[A-F0-9]{12}$', diag["NetworkData"]) is not None) 149 150 ip6_address_list = diag["IP6AddressList"] 151 assert (type(ip6_address_list) == list) 152 153 for ip6_address in ip6_address_list: 154 assert (type(ip6_address) == str) 155 assert_is_ipv6_address(ip6_address) 156 157 mac_counters = diag["MACCounters"] 158 assert (type(mac_counters) == dict) 159 mac_counters_expected_keys = [ 160 "IfInUnknownProtos", "IfInErrors", "IfOutErrors", "IfInUcastPkts", 161 "IfInBroadcastPkts", "IfInDiscards", "IfOutUcastPkts", 162 "IfOutBroadcastPkts", "IfOutDiscards" 163 ] 164 for key in mac_counters_expected_keys: 165 assert (key in mac_counters) 166 assert (type(mac_counters[key]) == int) 167 168 child_table = diag["ChildTable"] 169 assert (type(child_table) == list) 170 171 for child in child_table: 172 assert ("ChildId" in child) 173 assert (type(child["ChildId"]) == int) 174 assert ("Timeout" in child) 175 assert (type(child["Timeout"]) == int) 176 assert ("Mode" in child) 177 mode = child["Mode"] 178 assert (type(mode) == dict) 179 for key in mode_expected_keys: 180 assert (key in mode) 181 assert (type(mode[key]) == int) 182 183 assert (type(diag["ChannelPages"]) == str) 184 assert (re.match(r'^[A-F0-9]{2}$', diag["ChannelPages"]) is not None) 185 186 return 2 187 188 189def node_check(data): 190 assert data is not None 191 192 expected_keys = [ 193 "State", "NumOfRouter", "RlocAddress", "NetworkName", "ExtAddress", 194 "Rloc16", "LeaderData", "ExtPanId" 195 ] 196 expected_value_type = [ 197 str, int, str, str, str, int, dict, str 198 ] 199 expected_check_dict = dict(zip(expected_keys, expected_value_type)) 200 201 for key, value in expected_check_dict.items(): 202 assert (key in data) 203 assert (type(data[key]) == value) 204 205 assert_is_ipv6_address(data["RlocAddress"]) 206 207 assert (re.match(r'^[A-F0-9]{16}$', data["ExtAddress"]) is not None) 208 assert (re.match(r'[A-F0-9]{16}', data["ExtPanId"]) is not None) 209 210 leaderdata = data["LeaderData"] 211 leaderdata_expected_keys = [ 212 "PartitionId", "Weighting", "DataVersion", "StableDataVersion", 213 "LeaderRouterId" 214 ] 215 216 for key in leaderdata_expected_keys: 217 assert (key in leaderdata) 218 assert (type(leaderdata[key]) == int) 219 220 return True 221 222 223def node_rloc_check(data): 224 assert data is not None 225 226 assert (type(data) == str) 227 228 assert_is_ipv6_address(data) 229 230 return True 231 232 233def node_rloc16_check(data): 234 assert data is not None 235 236 assert (type(data) == int) 237 238 return True 239 240 241def node_ext_address_check(data): 242 assert data is not None 243 244 assert (type(data) == str) 245 assert (re.match(r'^[A-F0-9]{16}$', data) is not None) 246 247 return True 248 249 250def node_state_check(data): 251 assert data is not None 252 253 assert (type(data) == str) 254 255 return True 256 257 258def node_network_name_check(data): 259 assert data is not None 260 261 assert (type(data) == str) 262 263 return True 264 265 266def node_leader_data_check(data): 267 assert data is not None 268 269 assert (type(data) == dict) 270 271 leaderdata_expected_keys = [ 272 "PartitionId", "Weighting", "DataVersion", "StableDataVersion", 273 "LeaderRouterId" 274 ] 275 276 for key in leaderdata_expected_keys: 277 assert (key in data) 278 assert (type(data[key]) == int) 279 280 return True 281 282 283def node_num_of_router_check(data): 284 assert data is not None 285 286 assert (type(data) == int) 287 288 return True 289 290 291def node_ext_panid_check(data): 292 assert data is not None 293 294 assert (type(data) == str) 295 296 return True 297 298 299def node_test(thread_num): 300 url = rest_api_addr + "/node" 301 302 response_data = [None] * thread_num 303 304 create_multi_thread(get_data_from_url, url, thread_num, response_data) 305 306 valid = [node_check(data) for data in response_data].count(True) 307 308 print(" /node : all {}, valid {} ".format(thread_num, valid)) 309 310 311def node_rloc_test(thread_num): 312 url = rest_api_addr + "/node/rloc" 313 314 response_data = [None] * thread_num 315 316 create_multi_thread(get_data_from_url, url, thread_num, response_data) 317 318 valid = [node_rloc_check(data) for data in response_data].count(True) 319 320 print(" /node/rloc : all {}, valid {} ".format(thread_num, valid)) 321 322 323def node_rloc16_test(thread_num): 324 url = rest_api_addr + "/node/rloc16" 325 326 response_data = [None] * thread_num 327 328 create_multi_thread(get_data_from_url, url, thread_num, response_data) 329 330 valid = [node_rloc16_check(data) for data in response_data].count(True) 331 332 print(" /node/rloc16 : all {}, valid {} ".format(thread_num, valid)) 333 334 335def node_ext_address_test(thread_num): 336 url = rest_api_addr + "/node/ext-address" 337 338 response_data = [None] * thread_num 339 340 create_multi_thread(get_data_from_url, url, thread_num, response_data) 341 342 valid = [node_ext_address_check(data) for data in response_data].count(True) 343 344 print(" /node/ext-address : all {}, valid {} ".format(thread_num, valid)) 345 346 347def node_state_test(thread_num): 348 url = rest_api_addr + "/node/state" 349 350 response_data = [None] * thread_num 351 352 create_multi_thread(get_data_from_url, url, thread_num, response_data) 353 354 valid = [node_state_check(data) for data in response_data].count(True) 355 356 print(" /node/state : all {}, valid {} ".format(thread_num, valid)) 357 358 359def node_network_name_test(thread_num): 360 url = rest_api_addr + "/node/network-name" 361 362 response_data = [None] * thread_num 363 364 create_multi_thread(get_data_from_url, url, thread_num, response_data) 365 366 valid = [node_network_name_check(data) for data in response_data 367 ].count(True) 368 369 print(" /node/network-name : all {}, valid {} ".format(thread_num, valid)) 370 371 372def node_leader_data_test(thread_num): 373 url = rest_api_addr + "/node/leader-data" 374 375 response_data = [None] * thread_num 376 377 create_multi_thread(get_data_from_url, url, thread_num, response_data) 378 379 valid = [node_leader_data_check(data) for data in response_data].count(True) 380 381 print(" /node/leader-data : all {}, valid {} ".format(thread_num, valid)) 382 383 384def node_num_of_router_test(thread_num): 385 url = rest_api_addr + "/node/num-of-router" 386 387 response_data = [None] * thread_num 388 389 create_multi_thread(get_data_from_url, url, thread_num, response_data) 390 391 valid = [node_num_of_router_check(data) for data in response_data 392 ].count(True) 393 394 print(" /v1/node/num-of-router : all {}, valid {} ".format(thread_num, valid)) 395 396 397def node_ext_panid_test(thread_num): 398 url = rest_api_addr + "/node/ext-panid" 399 400 response_data = [None] * thread_num 401 402 create_multi_thread(get_data_from_url, url, thread_num, response_data) 403 404 valid = [node_ext_panid_check(data) for data in response_data].count(True) 405 406 print(" /node/ext-panid : all {}, valid {} ".format(thread_num, valid)) 407 408 409def diagnostics_test(thread_num): 410 url = rest_api_addr + "/diagnostics" 411 412 response_data = [None] * thread_num 413 414 create_multi_thread(get_data_from_url, url, thread_num, response_data) 415 416 valid = 0 417 has_content = 0 418 for data in response_data: 419 420 ret = diagnostics_check(data) 421 if ret == 1: 422 valid += 1 423 elif ret == 2: 424 valid += 1 425 has_content += 1 426 427 print(" /diagnostics : all {}, has content {}, valid {} ".format( 428 thread_num, has_content, valid)) 429 430 431def error_test(thread_num): 432 url = rest_api_addr + "/hello" 433 434 response_data = [None] * thread_num 435 436 create_multi_thread(get_error_from_url, url, thread_num, response_data) 437 438 valid = [error404_check(data) for data in response_data].count(True) 439 440 print(" /v1/hello : all {}, valid {} ".format(thread_num, valid)) 441 442 443def main(): 444 node_test(200) 445 node_rloc_test(200) 446 node_rloc16_test(200) 447 node_ext_address_test(200) 448 node_state_test(200) 449 node_network_name_test(200) 450 node_leader_data_test(200) 451 node_num_of_router_test(200) 452 node_ext_panid_test(200) 453 diagnostics_test(20) 454 error_test(10) 455 456 return 0 457 458 459if __name__ == '__main__': 460 exit(main()) 461