• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import json
18import socket
19
20from typing import Any, Mapping
21from urllib.parse import urlparse
22from urllib.request import Request, urlopen
23
24from acts import logger, utils
25from acts.libs.proc import job
26
27DEFAULT_SL4F_RESPONSE_TIMEOUT_SEC = 30
28
29
30class DeviceOffline(Exception):
31    """Exception if the device is no longer reachable via the network."""
32
33
34class SL4FCommandFailed(Exception):
35    """A SL4F command to the server failed."""
36
37
38class BaseLib():
39
40    def __init__(self, addr: str, logger_tag: str) -> None:
41        self.address = addr
42        self.log = logger.create_tagged_trace_logger(f"SL4F | {self.address} | {logger_tag}")
43
44    def send_command(
45        self,
46        cmd: str,
47        args: Mapping[str, Any],
48        response_timeout: int = DEFAULT_SL4F_RESPONSE_TIMEOUT_SEC
49    ) -> Mapping[str, Any]:
50        """Builds and sends a JSON command to SL4F server.
51
52        Args:
53            cmd: SL4F method name of command.
54            args: Arguments required to execute cmd.
55            response_timeout: Seconds to wait for a response before
56                throwing an exception.
57
58        Returns:
59            Response from SL4F server.
60
61        Throws:
62            TimeoutError: The HTTP request timed out waiting for a response
63        """
64        data = {
65            "jsonrpc": "2.0",
66            # id is required by the SL4F server to parse test_data but is not
67            # currently used.
68            "id": "",
69            "method": cmd,
70            "params": args
71        }
72        data_json = json.dumps(data).encode("utf-8")
73        req = Request(self.address,
74                      data=data_json,
75                      headers={
76                          "Content-Type": "application/json; charset=utf-8",
77                          "Content-Length": len(data_json),
78                      })
79
80        self.log.debug(f'Sending request "{cmd}" with {args}')
81        try:
82            response = urlopen(req, timeout=response_timeout)
83        except (TimeoutError, socket.timeout) as e:
84            host = urlparse(self.address).hostname
85            if not utils.can_ping(job, host):
86                raise DeviceOffline(
87                    f'FuchsiaDevice {host} is not reachable via the network.')
88            if type(e) == socket.timeout:
89                # socket.timeout was aliased to TimeoutError in Python 3.10. For
90                # older versions of Python, we need to cast to TimeoutError to
91                # provide a version-agnostic API.
92                raise TimeoutError("socket timeout") from e
93            raise e
94
95        response_body = response.read().decode("utf-8")
96        try:
97            response_json = json.loads(response_body)
98            self.log.debug(f'Received response for "{cmd}": {response_json}')
99        except json.JSONDecodeError as e:
100            raise SL4FCommandFailed(response_body) from e
101
102        # If the SL4F command fails it returns a str, without an 'error' field
103        # to get.
104        if not isinstance(response_json, dict):
105            raise SL4FCommandFailed(response_json)
106
107        return response_json
108