1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2020-2021 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19import re 20import telnetlib 21import time 22import os 23import threading 24 25from _core.constants import DeviceOsType 26from _core.constants import ConfigConst 27from _core.constants import ComType 28from _core.constants import DeviceLabelType 29from _core.constants import ModeType 30from _core.environment.dmlib_lite import LiteHelper 31from _core.exception import LiteDeviceConnectError 32from _core.exception import LiteDeviceTimeout 33from _core.exception import LiteParamError 34from _core.interface import IDevice 35from _core.logger import platform_logger 36from _core.environment.manager_env import DeviceAllocationState 37from _core.plugin import Plugin 38from _core.utils import exec_cmd 39from _core.utils import convert_serial 40from _core.utils import convert_ip 41from _core.utils import check_mode 42 43LOG = platform_logger("DeviceLite") 44TIMEOUT = 90 45RETRY_ATTEMPTS = 0 46HDC = "hdc_std.exe" 47DEFAULT_BAUD_RATE = 115200 48 49 50def get_hdc_path(): 51 from xdevice import Variables 52 user_path = os.path.join(Variables.exec_dir, "resource/tools") 53 top_user_path = os.path.join(Variables.top_dir, "config") 54 config_path = os.path.join(Variables.res_dir, "config") 55 paths = [user_path, top_user_path, config_path] 56 57 file_path = "" 58 for path in paths: 59 if os.path.exists(os.path.abspath(os.path.join( 60 path, HDC))): 61 file_path = os.path.abspath(os.path.join( 62 path, HDC)) 63 break 64 65 if os.path.exists(file_path): 66 return file_path 67 else: 68 raise LiteParamError("litehdc.exe not found", error_no="00108") 69 70 71def parse_available_com(com_str): 72 com_str = com_str.replace("\r", " ") 73 com_list = com_str.split("\n") 74 for index, item in enumerate(com_list): 75 com_list[index] = item.strip().strip(b'\x00'.decode()) 76 return com_list 77 78 79def perform_device_action(func): 80 def device_action(self, *args, **kwargs): 81 if not self.get_recover_state(): 82 LOG.debug("device %s %s is false" % (self.device_sn, 83 ConfigConst.recover_state)) 84 return "", "", "" 85 86 tmp = int(kwargs.get("retry", RETRY_ATTEMPTS)) 87 retry = tmp + 1 if tmp > 0 else 1 88 exception = None 89 for num in range(retry): 90 try: 91 result = func(self, *args, **kwargs) 92 return result 93 except LiteDeviceTimeout as error: 94 LOG.error(error) 95 exception = error 96 if num: 97 self.recover_device() 98 except Exception as error: 99 LOG.error(error) 100 exception = error 101 raise exception 102 103 return device_action 104 105 106@Plugin(type=Plugin.DEVICE, id=DeviceOsType.lite) 107class DeviceLite(IDevice): 108 """ 109 Class representing an device lite device. 110 111 Each object of this class represents one device lite device in xDevice. 112 113 Attributes: 114 device_connect_type: A string that's the type of lite device 115 """ 116 device_os_type = DeviceOsType.lite 117 device_allocation_state = DeviceAllocationState.available 118 119 def __init__(self): 120 self.device_sn = "" 121 self.label = "" 122 self.device_connect_type = "" 123 self.device_kernel = "" 124 self.device = None 125 self.ifconfig = None 126 self.extend_value = {} 127 self.device_lock = threading.RLock() 128 129 def __set_serial__(self, device=None): 130 for item in device: 131 if "ip" in item.keys() and "port" in item.keys(): 132 self.device_sn = "remote_%s_%s" % \ 133 (item.get("ip"), item.get("port")) 134 break 135 elif "type" in item.keys() and "com" in item.keys(): 136 self.device_sn = "local_%s" % item.get("com") 137 break 138 139 def __get_serial__(self): 140 return self.device_sn 141 142 def get(self, key=None, default=None): 143 if not key: 144 return default 145 value = getattr(self, key, None) 146 if value: 147 return value 148 else: 149 return self.extend_value.get(key, default) 150 151 def __set_device_kernel__(self, kernel_type=""): 152 self.device_kernel = kernel_type 153 154 def __get_device_kernel__(self): 155 return self.device_kernel 156 157 @staticmethod 158 def _check_watchgt(device): 159 for item in device: 160 if "label" not in item.keys(): 161 if "com" not in item.keys() or ("com" in item.keys() and 162 not item.get("com")): 163 error_message = "watchGT local com cannot be " \ 164 "empty, please check" 165 raise LiteParamError(error_message, error_no="00108") 166 else: 167 hdc = get_hdc_path() 168 result = exec_cmd([hdc]) 169 com_list = parse_available_com(result) 170 if item.get("com").upper() in com_list: 171 return True 172 else: 173 error_message = "watchGT local com does not exist" 174 raise LiteParamError(error_message, error_no="00108") 175 176 @staticmethod 177 def _check_wifiiot_config(device): 178 com_type_set = set() 179 for item in device: 180 if "label" not in item.keys(): 181 if "com" not in item.keys() or ("com" in item.keys() and 182 not item.get("com")): 183 error_message = "wifiiot local com cannot be " \ 184 "empty, please check" 185 raise LiteParamError(error_message, error_no="00108") 186 187 if "type" not in item.keys() or ("type" not in item.keys() and 188 not item.get("type")): 189 error_message = "wifiiot com type cannot be " \ 190 "empty, please check" 191 raise LiteParamError(error_message, error_no="00108") 192 else: 193 com_type_set.add(item.get("type")) 194 if len(com_type_set) < 2: 195 error_message = "wifiiot need cmd com and deploy com" \ 196 " at the same time, please check" 197 raise LiteParamError(error_message, error_no="00108") 198 199 @staticmethod 200 def _check_ipcamera_local(device): 201 for item in device: 202 if "label" not in item.keys(): 203 if "com" not in item.keys() or ("com" in item.keys() and 204 not item.get("com")): 205 error_message = "ipcamera local com cannot be " \ 206 "empty, please check" 207 raise LiteParamError(error_message, error_no="00108") 208 209 @staticmethod 210 def _check_ipcamera_remote(device=None): 211 for item in device: 212 if "label" not in item.keys(): 213 if "port" in item.keys() and item.get("port") and not item.get( 214 "port").isnumeric(): 215 error_message = "ipcamera remote port should be " \ 216 "a number, please check" 217 raise LiteParamError(error_message, error_no="00108") 218 elif "port" not in item.keys(): 219 error_message = "ipcamera remote port cannot be" \ 220 " empty, please check" 221 raise LiteParamError(error_message, error_no="00108") 222 223 @staticmethod 224 def _check_agent(device=None): 225 for item in device: 226 if "label" not in item.keys(): 227 if "port" in item.keys() and item.get("port") and not item.get( 228 "port").isnumeric(): 229 error_message = "agent port should be " \ 230 "a number, please check" 231 raise LiteParamError(error_message, error_no="00108") 232 elif "port" not in item.keys(): 233 error_message = "ipcamera agent port cannot be" \ 234 " empty, please check" 235 raise LiteParamError(error_message, error_no="00108") 236 237 def __check_config__(self, device=None): 238 if "agent" == device[0].get("type"): 239 self.device_connect_type = "agent" 240 self.label = device[0].get("label") 241 else: 242 self.set_connect_type(device) 243 if self.label == DeviceLabelType.wifiiot: 244 self._check_wifiiot_config(device) 245 elif self.label == DeviceLabelType.ipcamera and \ 246 self.device_connect_type == "local": 247 self._check_ipcamera_local(device) 248 elif self.label == DeviceLabelType.ipcamera and \ 249 self.device_connect_type == "remote": 250 self._check_ipcamera_remote(device) 251 elif self.label == DeviceLabelType.watch_gt: 252 self._check_watchgt(device) 253 elif self.label == DeviceLabelType.ipcamera and \ 254 self.device_connect_type == "agent": 255 self._check_agent(device) 256 257 def set_connect_type(self, device): 258 pattern = r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[' \ 259 r'01]?\d\d?)$' 260 for item in device: 261 if "label" in item.keys(): 262 self.label = item.get("label") 263 if "com" in item.keys(): 264 self.device_connect_type = "local" 265 if "ip" in item.keys(): 266 if re.match(pattern, item.get("ip")): 267 self.device_connect_type = "remote" 268 else: 269 error_message = "Remote device ip not in right" \ 270 "format, please check user_config.xml" 271 raise LiteParamError(error_message, error_no="00108") 272 if not self.label: 273 error_message = "device label cannot be empty, " \ 274 "please check" 275 raise LiteParamError(error_message, error_no="00108") 276 else: 277 if self.label != DeviceLabelType.wifiiot and \ 278 self.label != DeviceLabelType.ipcamera and \ 279 self.label != DeviceLabelType.watch_gt: 280 error_message = "device label should be ipcamera or" \ 281 " wifiiot, please check" 282 raise LiteParamError(error_message, error_no="00108") 283 if not self.device_connect_type: 284 error_message = "device com or ip cannot be empty, " \ 285 "please check" 286 raise LiteParamError(error_message, error_no="00108") 287 288 def __init_device__(self, device): 289 self.__check_config__(device) 290 self.__set_serial__(device) 291 if self.device_connect_type == "remote": 292 self.device = CONTROLLER_DICT.get("remote")(device) 293 elif self.device_connect_type == "agent": 294 self.device = CONTROLLER_DICT.get("agent")(device) 295 else: 296 self.device = CONTROLLER_DICT.get("local")(device) 297 298 self.ifconfig = device[1].get("ifconfig") 299 300 def connect(self): 301 """ 302 connect the device 303 304 """ 305 try: 306 self.device.connect() 307 except LiteDeviceConnectError: 308 if check_mode(ModeType.decc): 309 LOG.debug("set device %s recover state to false" % 310 self.device_sn) 311 self.device_allocation_state = DeviceAllocationState.unusable 312 self.set_recover_state(False) 313 raise 314 315 @perform_device_action 316 def execute_command_with_timeout(self, command="", case_type="", 317 timeout=TIMEOUT, **kwargs): 318 """Executes command on the device. 319 320 Args: 321 command: the command to execute 322 case_type: CTest or CppTest 323 timeout: timeout for read result 324 **kwargs: receiver - parser handler input 325 326 Returns: 327 (filter_result, status, error_message) 328 329 filter_result: command execution result 330 status: true or false 331 error_message: command execution error message 332 """ 333 receiver = kwargs.get("receiver", None) 334 if self.device_connect_type == "remote": 335 LOG.info("%s execute command shell %s with timeout %ss" % 336 (convert_serial(self.__get_serial__()), command, 337 str(timeout))) 338 filter_result, status, error_message = \ 339 self.device.execute_command_with_timeout( 340 command=command, 341 timeout=timeout, 342 receiver=receiver) 343 elif self.device_connect_type == "agent": 344 filter_result, status, error_message = \ 345 self.device.execute_command_with_timeout( 346 command=command, 347 case_type=case_type, 348 timeout=timeout, 349 receiver=receiver, type="cmd") 350 else: 351 filter_result, status, error_message = \ 352 self.device.execute_command_with_timeout( 353 command=command, 354 case_type=case_type, 355 timeout=timeout, 356 receiver=receiver) 357 if not receiver: 358 LOG.debug("%s execute result:%s" % ( 359 convert_serial(self.__get_serial__()), filter_result)) 360 if not status: 361 LOG.debug("%s error_message:%s" % ( 362 convert_serial(self.__get_serial__()), error_message)) 363 return filter_result, status, error_message 364 365 def recover_device(self): 366 self.reboot() 367 368 def reboot(self): 369 self.connect() 370 filter_result, status, error_message = self. \ 371 execute_command_with_timeout(command="reset", timeout=30) 372 if not filter_result: 373 if check_mode(ModeType.decc): 374 LOG.debug("Set device %s recover state to false" % 375 self.device_sn) 376 self.device_allocation_state = DeviceAllocationState.unusable 377 self.set_recover_state(False) 378 379 if self.ifconfig: 380 self.execute_command_with_timeout(command=self.ifconfig, 381 timeout=60) 382 383 def close(self): 384 """ 385 Close the telnet connection with device server or close the local 386 serial 387 """ 388 self.device.close() 389 390 def set_recover_state(self, state): 391 with self.device_lock: 392 setattr(self, ConfigConst.recover_state, state) 393 394 def get_recover_state(self, default_state=True): 395 with self.device_lock: 396 state = getattr(self, ConfigConst.recover_state, default_state) 397 return state 398 399 400class RemoteController: 401 """ 402 Class representing an device lite remote device. 403 Each object of this class represents one device lite remote device 404 in xDevice. 405 """ 406 407 def __init__(self, device): 408 self.host = device[1].get("ip") 409 self.port = int(device[1].get("port")) 410 self.telnet = None 411 412 def connect(self): 413 """ 414 connect the device server 415 416 """ 417 try: 418 if self.telnet: 419 return self.telnet 420 self.telnet = telnetlib.Telnet(self.host, self.port, 421 timeout=TIMEOUT) 422 except Exception as err_msgs: 423 error_message = "Connect remote lite device failed, host is %s, " \ 424 "port is %s, error is %s" % \ 425 (convert_ip(self.host), self.port, str(err_msgs)) 426 raise LiteDeviceConnectError(error_message, error_no="00401") 427 time.sleep(2) 428 self.telnet.set_debuglevel(0) 429 return self.telnet 430 431 def execute_command_with_timeout(self, command="", timeout=TIMEOUT, 432 receiver=None): 433 """ 434 Executes command on the device. 435 436 Parameters: 437 command: the command to execute 438 timeout: timeout for read result 439 receiver: parser handler 440 """ 441 return LiteHelper.execute_remote_cmd_with_timeout( 442 self.telnet, command, timeout, receiver) 443 444 def close(self): 445 """ 446 Close the telnet connection with device server 447 """ 448 try: 449 if not self.telnet: 450 return 451 self.telnet.close() 452 self.telnet = None 453 except (ConnectionError, Exception): 454 error_message = "Remote device is disconnected abnormally" 455 LOG.error(error_message, error_no="00401") 456 457 458class LocalController: 459 def __init__(self, device): 460 """ 461 Init Local device. 462 Parameters: 463 device: local device 464 """ 465 self.com_dict = {} 466 for item in device: 467 if "com" in item.keys(): 468 if "type" in item.keys() and ComType.cmd_com == item.get( 469 "type"): 470 self.com_dict[ComType.cmd_com] = ComController(item) 471 elif "type" in item.keys() and ComType.deploy_com == item.get( 472 "type"): 473 self.com_dict[ComType.deploy_com] = ComController(item) 474 475 def connect(self, key=ComType.cmd_com): 476 """ 477 Open serial. 478 """ 479 self.com_dict.get(key).connect() 480 481 def close(self, key=ComType.cmd_com): 482 """ 483 Close serial. 484 """ 485 if self.com_dict and self.com_dict.get(key): 486 self.com_dict.get(key).close() 487 488 def execute_command_with_timeout(self, **kwargs): 489 """ 490 Execute command on the serial and read all the output from the serial. 491 """ 492 args = kwargs 493 key = args.get("key", ComType.cmd_com) 494 command = args.get("command", None) 495 case_type = args.get("case_type", "") 496 receiver = args.get("receiver", None) 497 timeout = args.get("timeout", TIMEOUT) 498 return self.com_dict.get(key).execute_command_with_timeout( 499 command=command, case_type=case_type, 500 timeout=timeout, receiver=receiver) 501 502 503class ComController: 504 def __init__(self, device): 505 """ 506 Init serial. 507 Parameters: 508 device: local com 509 """ 510 self.is_open = False 511 self.com = None 512 self.serial_port = device.get("com") if device.get("com") else None 513 self.baud_rate = int(device.get("baud_rate")) if device.get( 514 "baud_rate") else DEFAULT_BAUD_RATE 515 self.timeout = int(device.get("timeout")) if device.get( 516 "timeout") else TIMEOUT 517 518 def connect(self): 519 """ 520 Open serial. 521 """ 522 try: 523 if not self.is_open: 524 import serial 525 self.com = serial.Serial(self.serial_port, 526 baudrate=self.baud_rate, 527 timeout=self.timeout) 528 self.is_open = True 529 except Exception as error_msg: 530 error = "connect %s serial failed, please make sure this port is" \ 531 " not occupied, error is %s[00401]" % \ 532 (self.serial_port, str(error_msg)) 533 raise LiteDeviceConnectError(error, error_no="00401") 534 535 def close(self): 536 """ 537 Close serial. 538 """ 539 try: 540 if not self.com: 541 return 542 if self.is_open: 543 self.com.close() 544 self.is_open = False 545 except (ConnectionError, Exception): 546 error_message = "Local device is disconnected abnormally" 547 LOG.error(error_message, error_no="00401") 548 549 def execute_command_with_timeout(self, **kwargs): 550 """ 551 Execute command on the serial and read all the output from the serial. 552 """ 553 return LiteHelper.execute_local_cmd_with_timeout(self.com, **kwargs) 554 555 def execute_command(self, command): 556 """ 557 Execute command on the serial and read all the output from the serial. 558 """ 559 LiteHelper.execute_local_command(self.com, command) 560 561 562class AgentController: 563 def __init__(self, device): 564 """ 565 Init serial. 566 Parameters: 567 device: local com 568 """ 569 LOG.info("AgentController begin") 570 self.com_dict = {} 571 self.host = device[1].get("ip") 572 self.port = str(device[1].get("port")) 573 self.headers = {'Content-Type': 'application/json'} 574 self.local_source_dir = "" 575 self.target_save_path = "" 576 self.base_url = "http" + "://" + self.host + ":" + self.port 577 578 def connect(self): 579 """ 580 Open serial. 581 """ 582 583 def close(self): 584 """ 585 Close serial. 586 """ 587 pass 588 589 def execute_command_with_timeout(self, command, **kwargs): 590 """ 591 Executes command on the device. 592 593 Parameters: 594 command: the command to execute 595 timeout: timeout for read result 596 receiver: parser handler 597 """ 598 return LiteHelper.execute_agent_cmd_with_timeout(self, 599 command, 600 **kwargs) 601 602 603CONTROLLER_DICT = { 604 "local": LocalController, 605 "remote": RemoteController, 606 "agent": AgentController 607} 608