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_coprocessor_version_check(data): 300 assert data is not None 301 302 assert (type(data) == str) 303 304 return True 305 306 307def node_test(thread_num): 308 url = rest_api_addr + "/node" 309 310 response_data = [None] * thread_num 311 312 create_multi_thread(get_data_from_url, url, thread_num, response_data) 313 314 valid = [node_check(data) for data in response_data].count(True) 315 316 print(" /node : all {}, valid {} ".format(thread_num, valid)) 317 318 319def node_rloc_test(thread_num): 320 url = rest_api_addr + "/node/rloc" 321 322 response_data = [None] * thread_num 323 324 create_multi_thread(get_data_from_url, url, thread_num, response_data) 325 326 valid = [node_rloc_check(data) for data in response_data].count(True) 327 328 print(" /node/rloc : all {}, valid {} ".format(thread_num, valid)) 329 330 331def node_rloc16_test(thread_num): 332 url = rest_api_addr + "/node/rloc16" 333 334 response_data = [None] * thread_num 335 336 create_multi_thread(get_data_from_url, url, thread_num, response_data) 337 338 valid = [node_rloc16_check(data) for data in response_data].count(True) 339 340 print(" /node/rloc16 : all {}, valid {} ".format(thread_num, valid)) 341 342 343def node_ext_address_test(thread_num): 344 url = rest_api_addr + "/node/ext-address" 345 346 response_data = [None] * thread_num 347 348 create_multi_thread(get_data_from_url, url, thread_num, response_data) 349 350 valid = [node_ext_address_check(data) for data in response_data].count(True) 351 352 print(" /node/ext-address : all {}, valid {} ".format(thread_num, valid)) 353 354 355def node_state_test(thread_num): 356 url = rest_api_addr + "/node/state" 357 358 response_data = [None] * thread_num 359 360 create_multi_thread(get_data_from_url, url, thread_num, response_data) 361 362 valid = [node_state_check(data) for data in response_data].count(True) 363 364 print(" /node/state : all {}, valid {} ".format(thread_num, valid)) 365 366 367def node_network_name_test(thread_num): 368 url = rest_api_addr + "/node/network-name" 369 370 response_data = [None] * thread_num 371 372 create_multi_thread(get_data_from_url, url, thread_num, response_data) 373 374 valid = [node_network_name_check(data) for data in response_data 375 ].count(True) 376 377 print(" /node/network-name : all {}, valid {} ".format(thread_num, valid)) 378 379 380def node_leader_data_test(thread_num): 381 url = rest_api_addr + "/node/leader-data" 382 383 response_data = [None] * thread_num 384 385 create_multi_thread(get_data_from_url, url, thread_num, response_data) 386 387 valid = [node_leader_data_check(data) for data in response_data].count(True) 388 389 print(" /node/leader-data : all {}, valid {} ".format(thread_num, valid)) 390 391 392def node_num_of_router_test(thread_num): 393 url = rest_api_addr + "/node/num-of-router" 394 395 response_data = [None] * thread_num 396 397 create_multi_thread(get_data_from_url, url, thread_num, response_data) 398 399 valid = [node_num_of_router_check(data) for data in response_data 400 ].count(True) 401 402 print(" /v1/node/num-of-router : all {}, valid {} ".format(thread_num, valid)) 403 404 405def node_ext_panid_test(thread_num): 406 url = rest_api_addr + "/node/ext-panid" 407 408 response_data = [None] * thread_num 409 410 create_multi_thread(get_data_from_url, url, thread_num, response_data) 411 412 valid = [node_ext_panid_check(data) for data in response_data].count(True) 413 414 print(" /node/ext-panid : all {}, valid {} ".format(thread_num, valid)) 415 416 417def node_coprocessor_version_test(thread_num): 418 url = rest_api_addr + "/node/coprocessor/version" 419 420 response_data = [None] * thread_num 421 422 create_multi_thread(get_data_from_url, url, thread_num, response_data) 423 424 valid = [node_coprocessor_version_check(data) for data in response_data].count(True) 425 426 print(" /node/coprocessor/version : all {}, valid {} ".format(thread_num, valid)) 427 428 429def diagnostics_test(thread_num): 430 url = rest_api_addr + "/diagnostics" 431 432 response_data = [None] * thread_num 433 434 create_multi_thread(get_data_from_url, url, thread_num, response_data) 435 436 valid = 0 437 has_content = 0 438 for data in response_data: 439 440 ret = diagnostics_check(data) 441 if ret == 1: 442 valid += 1 443 elif ret == 2: 444 valid += 1 445 has_content += 1 446 447 print(" /diagnostics : all {}, has content {}, valid {} ".format( 448 thread_num, has_content, valid)) 449 450 451def error_test(thread_num): 452 url = rest_api_addr + "/hello" 453 454 response_data = [None] * thread_num 455 456 create_multi_thread(get_error_from_url, url, thread_num, response_data) 457 458 valid = [error404_check(data) for data in response_data].count(True) 459 460 print(" /v1/hello : all {}, valid {} ".format(thread_num, valid)) 461 462 463def main(): 464 node_test(200) 465 node_rloc_test(200) 466 node_rloc16_test(200) 467 node_ext_address_test(200) 468 node_state_test(200) 469 node_network_name_test(200) 470 node_leader_data_test(200) 471 node_num_of_router_test(200) 472 node_ext_panid_test(200) 473 node_coprocessor_version_test(200) 474 diagnostics_test(20) 475 error_test(10) 476 477 return 0 478 479 480if __name__ == '__main__': 481 exit(main()) 482