• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# coding=utf-8
3
4#
5# Copyright (c) 2022 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#
18import re
19import threading
20
21from xdevice import ManagerType
22from xdevice import Plugin
23from xdevice import get_plugin
24from xdevice import IDeviceManager
25from xdevice import IFilter
26from xdevice import platform_logger
27from xdevice import ParamError
28from xdevice import ConfigConst
29from xdevice import HdcCommandRejectedException
30from xdevice import ShellCommandUnresponsiveException
31from xdevice import DeviceConnectorType
32from xdevice import DeviceEvent
33from xdevice import TestDeviceState
34from xdevice import DeviceState
35from xdevice import handle_allocation_event
36from xdevice import DeviceAllocationState
37from xdevice import DeviceStateMonitor
38from xdevice import convert_serial
39from xdevice import check_mode_in_sys
40from xdevice import DeviceNode
41from xdevice import DeviceSelector
42from xdevice import Variables
43
44from ohos.environment.dmlib import DeviceConnector
45from ohos.environment.dmlib import HDC_NAME
46from ohos.environment.dmlib import HDC_STD_NAME
47from ohos.config.config_manager import OHOSUserConfigManager
48
49__all__ = ["ManagerDevice"]
50
51LOG = platform_logger("ManagerDevice")
52
53
54@Plugin(type=Plugin.MANAGER, id=ManagerType.device)
55class ManagerDevice(IDeviceManager, IFilter):
56    """
57    Class representing device manager
58    managing the set of available devices for testing
59    """
60
61    instance = None
62
63    def __new__(cls):
64        """
65        Singleton instance
66        """
67        if cls.instance is None:
68            cls.instance = object.__new__(cls)
69        return cls.instance
70
71    def __init__(self):
72        self.devices_list = []
73        self.global_device_filter = []
74        self.lock_con = threading.Condition()
75        self.list_con = threading.Condition()
76        self.device_connectors = []
77        self.usb_type = "usb-hdc"
78        self.managed_device_listener = None
79        self.support_labels = ["phone", "watch", "car", "tv", "tablet", "ivi", "2in1"]
80        self.support_types = ["device"]
81        self.wait_times = 0
82        self.__device_alias = {}
83
84    def init_environment(self, environment: str = "", user_config_file: str = "") -> bool:
85        return self._start_device_monitor(environment, user_config_file)
86
87    def env_stop(self):
88        self._stop_device_monitor()
89
90    def _start_device_monitor(self, environment: str = "", user_config_file: str = "") -> bool:
91        self.managed_device_listener = ManagedDeviceListener(self)
92        ohos_manager = OHOSUserConfigManager(
93            config_file=user_config_file, env=environment)
94        devices = ohos_manager.get_devices("environment/device")
95        if devices:
96            self.__set_device_alias(devices)
97            try:
98                for device in devices:
99                    device_connector = DeviceConnector(
100                        device.get("ip"), device.get("port"), device.get("usb_type"))
101                    self.device_connectors.append(device_connector)
102                    self.global_device_filter.extend(
103                        ohos_manager.get_sn_list(device.get("sn")))
104                    device_connector.add_device_change_listener(
105                        self.managed_device_listener)
106                    device_connector.start()
107            except (ParamError, FileNotFoundError) as error:
108                self.env_stop()
109                for device in devices:
110                    LOG.debug("Start %s error: %s" % (
111                        device.get("usb_type"), error))
112                    device_connector = DeviceConnector(
113                        device.get("ip"), device.get("port"),
114                        DeviceConnectorType.hdc)
115                    self.device_connectors.append(device_connector)
116                    device_connector.add_device_change_listener(self.managed_device_listener)
117                    device_connector.start()
118            return True
119        else:
120            LOG.error("OHOS Manager device is not supported, please check config user_config.xml", error_no="00108")
121            return False
122
123    def _stop_device_monitor(self):
124        for device_connector in self.device_connectors:
125            device_connector.monitor_lock.acquire(1)
126            device_connector.remove_device_change_listener(self.managed_device_listener)
127            device_connector.terminate()
128            device_connector.monitor_lock.release()
129
130    def apply_device(self, device_option, timeout=3):
131        cnt = 0
132        max_reply_apply_time = 3
133        while cnt <= max_reply_apply_time:
134            LOG.debug("Apply device: apply lock con lock")
135            self.lock_con.acquire()
136            try:
137                device = self.allocate_device_option(device_option)
138                if device or cnt == max_reply_apply_time:
139                    return device
140                LOG.debug("Wait for available device founded")
141                self.lock_con.wait(cnt * 2 + 1)
142                cnt += 1
143            finally:
144                LOG.debug("Apply device: release lock con lock")
145                self.lock_con.release()
146
147    def allocate_device_option(self, device_option):
148        """
149        Request a device for testing that meets certain criteria.
150        """
151
152        LOG.debug("Allocate device option: apply list con lock")
153        if not self.list_con.acquire(timeout=5):
154            LOG.debug("Allocate device option: list con wait timeout")
155            return None
156        try:
157            allocated_device = None
158            LOG.debug("Require device label is: %s" % device_option.label)
159            for device in self.devices_list:
160                if device_option.matches(device):
161                    self.handle_device_event(device,
162                                             DeviceEvent.ALLOCATE_REQUEST)
163                    LOG.debug("Allocate device sn: %s, type: %s" % (
164                        device.__get_serial__(), device.__class__))
165                    return device
166            return allocated_device
167
168        finally:
169            LOG.debug("Allocate device option: release list con lock")
170            self.list_con.release()
171
172    def release_device(self, device):
173        LOG.debug("Release device: apply list con lock")
174        self.list_con.acquire()
175        try:
176            if device.test_device_state == TestDeviceState.ONLINE:
177                self.handle_device_event(device, DeviceEvent.FREE_AVAILABLE)
178            else:
179                self.handle_device_event(device, DeviceEvent.FREE_UNAVAILABLE)
180            LOG.debug("Free device sn: %s, type: %s" % (
181                device.__get_serial__(), device.__class__.__name__))
182        finally:
183            LOG.debug("Release_device: release list con lock")
184            self.list_con.release()
185
186    def lock_device(self, device):
187        LOG.debug("Apply device: apply list con lock")
188        self.list_con.acquire()
189        try:
190            if device.test_device_state == TestDeviceState.ONLINE:
191                self.handle_device_event(device, DeviceEvent.ALLOCATE_REQUEST)
192
193            LOG.debug("Lock device sn: %s, type: %s" % (
194                device.__get_serial__(), device.__class__.__name__))
195
196        finally:
197            LOG.debug("Lock_device: release list con lock")
198            self.list_con.release()
199
200    def reset_device(self, device):
201        if device and hasattr(device, "reset"):
202            device.reset()
203
204    def find_device(self, idevice):
205        LOG.debug("Find device: apply list con lock")
206        self.list_con.acquire()
207        try:
208            for device in self.devices_list:
209                if device == idevice:
210                    return device
211            return None
212        finally:
213            LOG.debug("Find device: release list con lock")
214            self.list_con.release()
215
216    def append_device_by_sort(self, device_instance):
217        LOG.debug("Append device: apply list con lock")
218        self.list_con.acquire()
219        try:
220            if (not self.global_device_filter or
221                    not self.devices_list or
222                    device_instance.device_sn not in self.global_device_filter):
223                self.devices_list.append(device_instance)
224            else:
225                device_dict = dict(zip(
226                    self.global_device_filter,
227                    list(range(1, len(self.global_device_filter) + 1))))
228                for index in range(len(self.devices_list)):
229                    if self.devices_list[index].device_sn not in \
230                            self.global_device_filter:
231                        self.devices_list.insert(index, device_instance)
232                        break
233                    if device_dict[device_instance.device_sn] < \
234                            device_dict[self.devices_list[index].device_sn]:
235                        self.devices_list.insert(index, device_instance)
236                        break
237                else:
238                    self.devices_list.append(device_instance)
239        finally:
240            LOG.debug("Append device: release list con lock")
241            self.list_con.release()
242
243    def find_or_create(self, idevice):
244        try:
245            device = self.find_device(idevice)
246            if device is None:
247                device = get_plugin(
248                    plugin_type=Plugin.DEVICE,
249                    plugin_id=idevice.device_os_type)[0]
250                device_sn = idevice.device_sn
251                device_instance = device.__class__()
252                device_instance.__set_serial__(device_sn)
253                device_instance.device_id = self.__device_alias.get(device_sn, "")
254                device_instance.host = idevice.host
255                device_instance.port = idevice.port
256                device_instance.usb_type = self.usb_type
257                LOG.debug("Create device(%s) host is %s, "
258                          "port is %s, device sn is %s, usb type is %s" %
259                          (device_instance, device_instance.host,
260                           device_instance.port, device_instance.device_sn,
261                           device_instance.usb_type))
262                device_instance.device_state = idevice.device_state
263                device_instance.test_device_state = \
264                    TestDeviceState.get_test_device_state(
265                        device_instance.device_state)
266                device_instance.device_state_monitor = \
267                    DeviceStateMonitor(device_instance)
268                if idevice.device_state == DeviceState.ONLINE or \
269                        idevice.device_state == DeviceState.CONNECTED:
270                    device_instance.get_device_type()
271                self.append_device_by_sort(device_instance)
272                device = device_instance
273            else:
274                LOG.debug("Find device(%s), host is %s, "
275                          "port is %s, device sn is %s, usb type is %s" %
276                          (device, device.host, device.port, device.device_sn,
277                           device.usb_type))
278            return device
279        except (HdcCommandRejectedException, ShellCommandUnresponsiveException) as hcr_error:
280            LOG.debug("%s occurs error. Reason:%s" %
281                      (idevice.device_sn, hcr_error))
282            return None
283
284    def remove(self, idevice):
285        LOG.debug("Remove: apply list con lock")
286        self.list_con.acquire()
287        try:
288            self.devices_list.remove(idevice)
289        finally:
290            LOG.debug("Remove: release list con lock")
291            self.list_con.release()
292
293    def handle_device_event(self, device, event):
294        state_changed = None
295        old_state = device.device_allocation_state
296        new_state = handle_allocation_event(old_state, event)
297
298        if new_state == DeviceAllocationState.checking_availability:
299            if self.global_device_filter and \
300                    device.device_sn not in self.global_device_filter:
301                event = DeviceEvent.AVAILABLE_CHECK_IGNORED
302            else:
303                event = DeviceEvent.AVAILABLE_CHECK_PASSED
304            new_state = handle_allocation_event(new_state, event)
305
306        if old_state != new_state:
307            state_changed = True
308            device.device_allocation_state = new_state
309
310        if state_changed is True and \
311                new_state == DeviceAllocationState.available:
312            # notify_device_state_change
313            LOG.debug("Handle device event apply lock con")
314            self.lock_con.acquire()
315            LOG.debug("Find available device")
316            self.lock_con.notify_all()
317            LOG.debug("Handle device event release lock con")
318            self.lock_con.release()
319
320        if device.device_allocation_state == \
321                DeviceAllocationState.unknown:
322            self.remove(device)
323        return
324
325    def launch_emulator(self):
326        pass
327
328    def kill_emulator(self):
329        pass
330
331    def list_devices(self):
332        for device_connector in self.device_connectors:
333            device_connector.monitor_lock.acquire(1)
334        try:
335            if check_mode_in_sys(ConfigConst.app_test):
336                return self.get_device_info()
337            else:
338                print("OHOS devices:")
339                print("{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}{6:<16}".format(
340                    "Serial", "OsType", "State", "Allocation", "Product", "host",
341                    "port"))
342                for device in self.devices_list:
343                    print(
344                        "{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}{6:<16}".format(
345                            convert_serial(device.device_sn),
346                            device.device_os_type,
347                            device.test_device_state.value,
348                            device.device_allocation_state,
349                            device.label if device.label else 'None',
350                            device.host, device.port))
351                return ""
352        finally:
353            for device_connector in self.device_connectors:
354                device_connector.monitor_lock.release()
355
356    def get_device_info(self):
357        devices_info = []
358        for device in self.devices_list:
359            result = device.get_device_params()
360            devices_info.append(result)
361        return devices_info
362
363    def __set_device_alias(self, devices):
364        for device in devices:
365            sn, alias = device.get("sn"), device.get("alias")
366            if sn and alias:
367                self.__device_alias.update({sn: alias})
368
369    def __filter_selector__(self, selector):
370        if isinstance(selector, DeviceSelector):
371            return True
372        return False
373
374    def __filter_xml_node__(self, node):
375        if isinstance(node, DeviceNode):
376            if HDC_NAME in node.get_connectors() or \
377                    HDC_STD_NAME in node.get_connectors():
378                return True
379        return False
380
381
382class ManagedDeviceListener(object):
383    """
384    A class to listen for and act on device presence updates from ddmlib
385    """
386
387    def __init__(self, manager):
388        self.manager = manager
389
390    def device_changed(self, idevice):
391        test_device = self.manager.find_or_create(idevice)
392        if test_device is None:
393            return
394        new_state = TestDeviceState.get_test_device_state(idevice.device_state)
395        test_device.test_device_state = new_state
396        if hasattr(test_device, "_is_root"):
397            test_device._is_root = None
398        if new_state == TestDeviceState.ONLINE:
399            self.manager.handle_device_event(test_device,
400                                             DeviceEvent.STATE_CHANGE_ONLINE)
401            LOG.debug("Set device %s %s to true" % (
402                convert_serial(test_device.device_sn), ConfigConst.recover_state))
403            test_device.set_recover_state(True)
404        elif new_state == TestDeviceState.NOT_AVAILABLE:
405            self.manager.handle_device_event(test_device,
406                                             DeviceEvent.STATE_CHANGE_OFFLINE)
407            if hasattr(test_device, "call_proxy_listener"):
408                test_device.call_proxy_listener()
409        test_device.device_state_monitor.set_state(
410            test_device.test_device_state)
411        LOG.debug("Device changed to %s: %s %s %s %s" % (
412            new_state, convert_serial(idevice.device_sn),
413            idevice.device_os_type, idevice.host, idevice.port))
414        self.report_device(idevice, new_state)
415
416    def device_connected(self, idevice):
417        test_device = self.manager.find_or_create(idevice)
418        if test_device is None:
419            return
420        new_state = TestDeviceState.get_test_device_state(idevice.device_state)
421        test_device.test_device_state = new_state
422        if test_device.test_device_state == TestDeviceState.ONLINE:
423            self.manager.handle_device_event(test_device,
424                                             DeviceEvent.CONNECTED_ONLINE)
425        elif new_state == TestDeviceState.NOT_AVAILABLE:
426            self.manager.handle_device_event(test_device,
427                                             DeviceEvent.CONNECTED_OFFLINE)
428        test_device.device_state_monitor.set_state(
429            test_device.test_device_state)
430        LOG.debug("Device connected: {} {} {} {}, state: {}".format(
431            convert_serial(idevice.device_sn), idevice.device_os_type,
432            idevice.host, idevice.port, test_device.test_device_state))
433        LOG.debug("Set device %s %s to true" % (
434            convert_serial(idevice.device_sn), ConfigConst.recover_state))
435        test_device.set_recover_state(True)
436        self.report_device(idevice, new_state)
437
438    def device_disconnected(self, idevice):
439        test_device = self.manager.find_device(idevice)
440        if test_device is not None:
441            test_device.test_device_state = TestDeviceState.NOT_AVAILABLE
442            self.manager.handle_device_event(test_device,
443                                             DeviceEvent.DISCONNECTED)
444            test_device.device_state_monitor.set_state(
445                TestDeviceState.NOT_AVAILABLE)
446        LOG.debug("Device disconnected: %s %s %s %s" % (
447            convert_serial(idevice.device_sn), idevice.device_os_type,
448            idevice.host, idevice.port))
449        self.report_device(idevice, TestDeviceState.NOT_AVAILABLE)
450
451    @staticmethod
452    def report_device(idevice, device_state):
453        """向controller上报设备的信息和状态"""
454        sn = idevice.device_sn
455        if not Variables.config.enable_cluster() \
456                or re.match(r'COM\d+', sn, flags=re.IGNORECASE):
457            return
458        from xdevice._core.cluster.utils import report_worker_device
459        device_os = idevice.device_os_type
460        device_type, report_state = "", DeviceState.OFFLINE
461        if device_state == TestDeviceState.ONLINE:
462            device_type, report_state = idevice.get_device_type(), DeviceState.ONLINE
463        report_worker_device(sn, "", "", device_os, device_type, report_state)
464