• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# coding=utf-8
3
4#
5# Copyright (c) 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 threading
20
21from xdevice import DeviceOsType
22from xdevice import ProductForm
23from xdevice import ReportException
24from xdevice import IDevice
25from xdevice import platform_logger
26from xdevice import Plugin
27from xdevice import exec_cmd
28from xdevice import ConfigConst
29
30from xdevice_extension._core import utils
31from xdevice_extension._core.environment.dmlib import HdcHelper
32from xdevice_extension._core.exception import HdcError
33from xdevice_extension._core.environment.dmlib import CollectingOutputReceiver
34from xdevice_extension._core.environment.device_state import \
35    DeviceAllocationState
36from xdevice_extension._core.utils import check_path_legal
37from xdevice_extension._core.utils import convert_serial
38from xdevice_extension._core.constants import DeviceConnectorType
39
40__all__ = ["Device"]
41TIMEOUT = 90 * 1000
42RETRY_ATTEMPTS = 2
43DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000
44BACKGROUND_TIME = 2 * 60 * 1000
45LOG = platform_logger("Device")
46
47
48def perform_device_action(func):
49    def device_action(self, *args, **kwargs):
50        if not self.get_recover_state():
51            LOG.debug("device %s %s is false" % (self.device_sn,
52                                                 ConfigConst.recover_state))
53            return
54        # avoid infinite recursion, such as device reboot
55        abort_on_exception = bool(kwargs.get("abort_on_exception", False))
56        if abort_on_exception:
57            result = func(self, *args, **kwargs)
58            return result
59
60        tmp = int(kwargs.get("retry", RETRY_ATTEMPTS))
61        retry = tmp + 1 if tmp > 0 else 1
62        exception = None
63        for _ in range(retry):
64            try:
65                result = func(self, *args, **kwargs)
66                return result
67            except ReportException as error:
68                self.log.exception("Generate report error!", exc_info=False)
69                exception = error
70            except (ConnectionResetError, ConnectionRefusedError) as error:
71                self.log.error("error type: %s, error: %s" %
72                               (error.__class__.__name__, error))
73                if self.usb_type == DeviceConnectorType.hdc:
74                    cmd = "hdc reset"
75                    self.log.info("re-execute hdc reset")
76                    exec_cmd(cmd)
77                if not self.recover_device():
78                    LOG.debug("set device %s %s false" % (
79                        self.device_sn, ConfigConst.recover_state))
80                    self.set_recover_state(False)
81                    raise error
82                exception = error
83            except HdcError as error:
84                self.log.error("error type: %s, error: %s" %
85                               (error.__class__.__name__, error))
86                if not self.recover_device():
87                    LOG.debug("set device %s %s false" % (
88                        self.device_sn, ConfigConst.recover_state))
89                    self.set_recover_state(False)
90                    raise error
91                exception = error
92            except Exception as error:
93                self.log.exception("error type: %s, error: %s" % (
94                    error.__class__.__name__, error), exc_info=False)
95                exception = error
96
97    return device_action
98
99
100@Plugin(type=Plugin.DEVICE, id=DeviceOsType.default)
101class Device(IDevice):
102    """
103    Class representing a device.
104
105    Each object of this class represents one device in xDevice,
106    including handles to hdc, fastboot, and test agent.
107
108    Attributes:
109        device_sn: A string that's the serial number of the device.
110    """
111
112    device_sn = None
113    host = None
114    port = None
115    usb_type = None
116    is_timeout = False
117    device_hilog_proc = None
118    device_os_type = DeviceOsType.default
119    test_device_state = None
120    device_allocation_state = DeviceAllocationState.available
121    label = None
122    log = platform_logger("Device")
123    device_state_monitor = None
124    reboot_timeout = 2 * 60 * 1000
125    hilog_file_pipe = None
126
127    model_dict = {
128        'default': ProductForm.phone,
129        'car': ProductForm.car,
130        'tv': ProductForm.television,
131        'watch': ProductForm.watch,
132        'tablet':  ProductForm.tablet
133    }
134
135    def __init__(self):
136        self.extend_value = {}
137        self.device_lock = threading.RLock()
138
139    def __eq__(self, other):
140        return self.device_sn == other.__get_serial__() and \
141               self.device_os_type == other.device_os_type
142
143    def __set_serial__(self, device_sn=""):
144        self.device_sn = device_sn
145        return self.device_sn
146
147    def __get_serial__(self):
148        return self.device_sn
149
150    def get(self, key=None, default=None):
151        if not key:
152            return default
153        value = getattr(self, key, None)
154        if value:
155            return value
156        else:
157            return self.extend_value.get(key, default)
158
159    def recover_device(self):
160        if not self.get_recover_state():
161            LOG.debug("device %s %s is false, cannot recover device" % (
162                self.device_sn, ConfigConst.recover_state))
163            return
164
165        LOG.debug("wait device %s to recover" % self.device_sn)
166        return self.device_state_monitor.wait_for_device_available()
167
168    def get_device_type(self):
169        self.label = self.model_dict.get("default", None)
170
171    def get_property(self, prop_name, retry=RETRY_ATTEMPTS,
172                     abort_on_exception=False):
173        """
174        Hdc command, ddmlib function.
175        """
176        command = "param get %s" % prop_name
177        stdout = self.execute_shell_command(
178            command, timeout=5 * 1000, output_flag=False, retry=retry,
179            abort_on_exception=abort_on_exception).strip()
180        if stdout:
181            LOG.debug(stdout)
182        return stdout
183
184    @perform_device_action
185    def hdc_command(self, command, **kwargs):
186        timeout = int(kwargs.get("timeout", TIMEOUT)) / 1000
187        error_print = bool(kwargs.get("error_print", True))
188        join_result = bool(kwargs.get("join_result", False))
189        timeout_msg = '' if timeout == 300.0 else \
190            " with timeout %ss" % timeout
191        if self.usb_type == DeviceConnectorType.hdc:
192            LOG.debug("%s execute command hdc %s%s" % (
193                convert_serial(self.device_sn), command, timeout_msg))
194        cmd = ["hdc_std", "-t", self.device_sn]
195        if isinstance(command, list):
196            cmd.extend(command)
197        else:
198            command = command.strip()
199            cmd.extend(command.split(" "))
200        result = exec_cmd(cmd, timeout, error_print, join_result)
201        if not result:
202            return result
203        for line in str(result).split("\n"):
204            if line.strip():
205                LOG.debug(line.strip())
206        return result
207
208    @perform_device_action
209    def execute_shell_command(self, command, timeout=TIMEOUT,
210                              receiver=None, **kwargs):
211        if not receiver:
212            collect_receiver = CollectingOutputReceiver()
213            HdcHelper.execute_shell_command(
214                self, command, timeout=timeout,
215                receiver=collect_receiver, **kwargs)
216            return collect_receiver.output
217        else:
218            return HdcHelper.execute_shell_command(
219                self, command, timeout=timeout,
220                receiver=receiver, **kwargs)
221
222    def execute_shell_cmd_background(self, command, timeout=TIMEOUT,
223                                     receiver=None):
224        status = HdcHelper.execute_shell_command(self, command,
225                                                 timeout=timeout,
226                                                 receiver=receiver)
227
228        self.wait_for_device_not_available(DEFAULT_UNAVAILABLE_TIMEOUT)
229        self.device_state_monitor.wait_for_device_available(BACKGROUND_TIME)
230        cmd = "target mount" \
231            if self.usb_type == DeviceConnectorType.hdc else "remount"
232        self.hdc_command(cmd)
233        self.start_catch_device_log()
234        return status
235
236    def wait_for_device_not_available(self, wait_time):
237        return self.device_state_monitor.wait_for_device_not_available(
238            wait_time)
239
240    def _wait_for_device_online(self, wait_time=None):
241        return self.device_state_monitor.wait_for_device_online(wait_time)
242
243    def _do_reboot(self):
244        HdcHelper.reboot(self)
245        if not self.wait_for_device_not_available(
246                DEFAULT_UNAVAILABLE_TIMEOUT):
247            LOG.error("Did not detect device {} becoming unavailable "
248                      "after reboot".format(convert_serial(self.device_sn)))
249
250    def _reboot_until_online(self):
251        self._do_reboot()
252        self._wait_for_device_online()
253
254    def reboot(self):
255        self._reboot_until_online()
256        self.device_state_monitor.wait_for_device_available(
257            self.reboot_timeout)
258        self.enable_hdc_root()
259        self.start_catch_device_log()
260
261    @perform_device_action
262    def install_package(self, package_path, command=""):
263        if package_path is None:
264            raise HdcError(
265                "install package: package path cannot be None!")
266        return HdcHelper.install_package(self, package_path, command)
267
268    @perform_device_action
269    def uninstall_package(self, package_name):
270        return HdcHelper.uninstall_package(self, package_name)
271
272    @perform_device_action
273    def push_file(self, local, remote, **kwargs):
274        """
275        Push a single file.
276        The top directory won't be created if is_create is False (by default)
277        and vice versa
278        """
279        if local is None:
280            raise HdcError("XDevice Local path cannot be None!")
281
282        remote_is_dir = kwargs.get("remote_is_dir", False)
283        if remote_is_dir:
284            ret = self.execute_shell_command("test -d %s && echo 0" % remote)
285            if not (ret != "" and len(str(ret).split()) != 0 and
286                    str(ret).split()[0] == "0"):
287                self.execute_shell_command("mkdir -p %s" % remote)
288
289        is_create = kwargs.get("is_create", False)
290        timeout = kwargs.get("timeout", TIMEOUT)
291        HdcHelper.push_file(self, local, remote, is_create=is_create,
292                            timeout=timeout)
293
294    @perform_device_action
295    def pull_file(self, remote, local, **kwargs):
296        """
297        Pull a single file.
298        The top directory won't be created if is_create is False (by default)
299        and vice versa
300        """
301
302        is_create = kwargs.get("is_create", False)
303        timeout = kwargs.get("timeout", TIMEOUT)
304        HdcHelper.pull_file(self, remote, local, is_create=is_create,
305                            timeout=timeout)
306
307    def is_hdc_root(self):
308        output = self.execute_shell_command("id")
309        return "uid=0(root)" in output
310
311    def enable_hdc_root(self):
312        if self.is_hdc_root():
313            return True
314        for index in range(3):
315            cmd = "smode" \
316                if self.usb_type == DeviceConnectorType.hdc else "root"
317            output = self.hdc_command(cmd)
318            if self.is_hdc_root():
319                return True
320            LOG.debug(
321                "hdc root on %s unsuccessful on attempt %d. "
322                "Output: %s" % (
323                 convert_serial(self.device_sn), index, output))
324        return False
325
326    def is_directory(self, path):
327        path = check_path_legal(path)
328        output = self.execute_shell_command("ls -ld {}".format(path))
329        if output and output.startswith('d'):
330            return True
331        return False
332
333    def is_file_exist(self, file_path):
334        file_path = check_path_legal(file_path)
335        output = self.execute_shell_command("ls {}".format(file_path))
336        if output and "No such file or directory" not in output:
337            return True
338        return False
339
340    def start_catch_device_log(self, hilog_file_pipe=None):
341        """
342        Starts hdc log for each device in separate subprocesses and save
343        the logs in files.
344        """
345        if hilog_file_pipe:
346            self.hilog_file_pipe = hilog_file_pipe
347        self._start_catch_device_log()
348
349    def stop_catch_device_log(self):
350        """
351        Stops all hdc log subprocesses.
352        """
353        self._stop_catch_device_log()
354
355    def _start_catch_device_log(self):
356        if self.hilog_file_pipe:
357            command = "hilog"
358            if self.usb_type == DeviceConnectorType.hdc:
359                cmd = ['hdc_std', "-t", self.device_sn, "shell", command]
360                LOG.info("execute command: %s" % " ".join(cmd).replace(
361                    self.device_sn, convert_serial(self.device_sn)))
362                self.device_hilog_proc = utils.start_standing_subprocess(
363                    cmd, self.hilog_file_pipe)
364
365    def _stop_catch_device_log(self):
366        if self.device_hilog_proc:
367            utils.stop_standing_subprocess(self.device_hilog_proc)
368            self.device_hilog_proc = None
369            self.hilog_file_pipe = None
370
371    def get_recover_result(self, retry=RETRY_ATTEMPTS):
372        command = "param get sys.boot_completed"
373        stdout = self.execute_shell_command(command, timeout=5 * 1000,
374                                            output_flag=False, retry=retry,
375                                            abort_on_exception=True).strip()
376        if stdout:
377            LOG.debug(stdout)
378        return stdout
379
380    def set_recover_state(self, state):
381        with self.device_lock:
382            setattr(self, ConfigConst.recover_state, state)
383
384    def get_recover_state(self, default_state=True):
385        with self.device_lock:
386            state = getattr(self, ConfigConst.recover_state, default_state)
387            return state
388
389    def close(self):
390        pass
391