• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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