1#
2# Copyright (C) 2017 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import logging
18import socket
19import time
20
21from host_controller.tradefed import remote_operation
22
23LOCALHOST = "localhost"
24DEFAULT_PORT = 30103
25
26
27class RemoteClient(object):
28    """The class for sending remote operations to TradeFed."""
29
30    def __init__(self, host=LOCALHOST, port=DEFAULT_PORT, timeout=None):
31        """Initializes the client of TradeFed remote manager.
32
33        Args:
34            _host: The host name of the remote manager.
35            _port: The port of the remote manager.
36            _timeout: The connect and receive timeout in seconds
37        """
38        self._host = host
39        self._port = port
40        self._timeout = timeout if timeout else socket.getdefaulttimeout()
41
42    def SendOperations(self, *operations):
43        """Sends a list of operations and waits for each response.
44
45        Args:
46            *operations: A list of remote_operation.RemoteOperation objects.
47
48        Returns:
49            A list of JSON objects.
50
51        Raises:
52            socket.error if fails to communicate with remote manager.
53            remote_operation.RemoteOperationException if any operation fails or
54            has no response.
55        """
56        op_socket = socket.create_connection((self._host, self._port),
57                                             self._timeout)
58        logging.info("Connect to %s:%d", self._host, self._port)
59        try:
60            if self._timeout is not None:
61                op_socket.settimeout(self._timeout)
62            ops_str = "\n".join(str(op) for op in operations)
63            logging.info("Send: %s", ops_str)
64            op_socket.send(ops_str)
65            op_socket.shutdown(socket.SHUT_WR)
66            resp_str = ""
67            while True:
68                buf = op_socket.recv(4096)
69                if not buf:
70                    break
71                resp_str += buf
72        finally:
73            op_socket.close()
74        logging.info("Receive: %s", resp_str)
75        resp_lines = [x for x in resp_str.split("\n") if x]
76        if len(resp_lines) != len(operations):
77            raise remote_operation.RemoteOperationException(
78                    "Unexpected number of responses: %d" % len(resp_lines))
79        return [operations[i].ParseResponse(resp_lines[i])
80                for i in range(len(resp_lines))]
81
82    def SendOperation(self, operation):
83        """Sends one operation and waits for its response.
84
85        Args:
86            operation: A remote_operation.RemoteOperation object.
87
88        Returns:
89            A JSON object.
90
91        Raises:
92            socket.error if fails to communicate with remote manager.
93            remote_operation.RemoteOperationException if the operation fails or
94            has no response.
95        """
96        return self.SendOperations(operation)[0]
97
98    def ListDevices(self):
99        """Sends ListDevices operation.
100
101        Returns:
102            A list of device_info.DeviceInfo which are the devices connected to
103            the host.
104        """
105        json_obj = self.SendOperation(remote_operation.ListDevices())
106        return remote_operation.ParseListDevicesResponse(json_obj)
107
108    def WaitForCommandResult(self, serial, timeout, poll_interval=5):
109        """Sends a series of operations to wait until a command finishes.
110
111        Args:
112            serial: The serial number of the device.
113            timeout: A float, the timeout in seconds.
114            poll_interval: A float, the interval of each GetLastCommandResult
115                           operation in seconds.
116
117        Returns:
118            A JSON object which is the result of the command.
119            None if timeout.
120
121            Sample
122            {'status': 'INVOCATION_SUCCESS',
123             'free_device_state': 'AVAILABLE'}
124        """
125        deadline = time.time() + timeout
126        get_result_op = remote_operation.GetLastCommandResult(serial)
127        while True:
128            result = self.SendOperation(get_result_op)
129            if result["status"] != "EXECUTING":
130                return result
131            if time.time() > deadline:
132                return None
133            time.sleep(poll_interval)
134            if time.time() > deadline:
135                return None
136