1# Copyright 2020 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""cvd_runtime_config class."""
15
16import json
17import os
18import re
19
20from acloud import errors
21
22_CFG_KEY_CROSVM_BINARY = "crosvm_binary"
23_CFG_KEY_DISPLAY_CONFIGS = "display_configs"
24_CFG_KEY_VIRTUAL_DISK_PATHS = "virtual_disk_paths"
25_CFG_KEY_INSTANCES = "instances"
26_CFG_KEY_ADB_IP_PORT = "adb_ip_and_port"
27_CFG_KEY_INSTANCE_DIR = "instance_dir"
28_CFG_KEY_ROOT_DIR = "root_dir"
29_CFG_KEY_VNC_PORT = "vnc_server_port"
30# The adb port field name changes from "host_port" to "adb_host_port".
31_CFG_KEY_ADB_PORT = "host_port"
32_CFG_KEY_ADB_HOST_PORT = "adb_host_port"
33_CFG_KEY_ENABLE_WEBRTC = "enable_webrtc"
34# TODO(148648620): Check instance_home_[id] for backward compatible.
35_RE_LOCAL_INSTANCE_ID = re.compile(r".+(?:local-instance-|instance_home_)"
36                                   r"(?P<ins_id>\d+).+")
37
38
39def _GetIdFromInstanceDirStr(instance_dir, config_dict):
40    """Look for instance id from the path of instance dir.
41
42    Args:
43        instance_dir: String, path of instance_dir.
44
45    Returns:
46        String of instance id.
47    """
48    match = _RE_LOCAL_INSTANCE_ID.match(instance_dir)
49    if match:
50        return match.group("ins_id")
51
52    # To support the device which is not created by acloud.
53    if os.path.expanduser("~") in instance_dir:
54        if config_dict:
55            instances = config_dict.get(_CFG_KEY_INSTANCES)
56            if instances:
57                return min(instances.keys())
58            else:
59                # Old runtime config doesn't have "instances" information.
60                return "1"
61        else:
62            return "1"
63
64    return None
65
66
67class CvdRuntimeConfig():
68    """The class that hold the information from cuttlefish_config.json.
69
70    The example of cuttlefish_config.json
71    {
72    "memory_mb" : 4096,
73    "cpus" : 2,
74    "display_configs" :
75    [
76        {
77            "dpi" : 160,
78            "x_res" : 1280,
79            "y_res" : 700
80        }
81    ],
82    "dpi" : 320,
83    "virtual_disk_paths" :
84        [
85            "/path-to-image"
86        ],
87    "adb_ip_and_port" : "0.0.0.0:6520",
88    "instance_dir" : "/path-to-instance-dir",
89    }
90
91    If we launched multiple local instances, the config will be as below:
92    {
93    "memory_mb" : 4096,
94    "cpus" : 2,
95    "dpi" : 320,
96    "instances" :
97        {
98            "1" :
99            {
100                "adb_ip_and_port" : "0.0.0.0:6520",
101                "instance_dir" : "/path-to-instance-dir",
102                "webrtc_device_id" : "cvd-1",
103                "virtual_disk_paths" :
104                [
105                    "/path-to-image"
106                ],
107            }
108        }
109    }
110
111    If the avd enable the webrtc, the config will be as below:
112    {
113    "enable_webrtc" : true,
114    "vnc_server_binary" : "/home/vsoc-01/bin/vnc_server",
115    "webrtc_assets_dir" : "/home/vsoc-01/usr/share/webrtc/assets",
116    "webrtc_binary" : "/home/vsoc-01/bin/webRTC",
117    "webrtc_certs_dir" : "/home/vsoc-01/usr/share/webrtc/certs",
118    "webrtc_public_ip" : "0.0.0.0",
119    }
120
121    """
122
123    def __init__(self, config_path=None, raw_data=None):
124        if not config_path and not raw_data:
125            raise errors.ConfigError("No cuttlefish config found!")
126        self._config_path = config_path
127        self._config_dict = self._GetCuttlefishRuntimeConfig(config_path,
128                                                             raw_data)
129        self._instance_id = "1" if raw_data else _GetIdFromInstanceDirStr(
130            config_path, self._config_dict)
131        self._instances = self._config_dict.get(_CFG_KEY_INSTANCES)
132        # Old runtime config doesn't have "instances" information.
133        self._instance_ids = list(self._instances.keys()) if self._instances else ["1"]
134        self._display_configs = self._config_dict.get(_CFG_KEY_DISPLAY_CONFIGS, {})
135        self._root_dir = self._config_dict.get(_CFG_KEY_ROOT_DIR)
136        crosvm_bin = self._config_dict.get(_CFG_KEY_CROSVM_BINARY)
137        self._cvd_tools_path = (os.path.dirname(crosvm_bin)
138                                if crosvm_bin else None)
139
140        # Below properties will be collected inside of instance id node if there
141        # are more than one instance.
142        self._instance_dir = self._config_dict.get(_CFG_KEY_INSTANCE_DIR)
143        self._vnc_port = self._config_dict.get(_CFG_KEY_VNC_PORT)
144        self._adb_port = (self._config_dict.get(_CFG_KEY_ADB_PORT) or
145                          self._config_dict.get(_CFG_KEY_ADB_HOST_PORT))
146        self._adb_ip_port = self._config_dict.get(_CFG_KEY_ADB_IP_PORT)
147        self._virtual_disk_paths = self._config_dict.get(
148            _CFG_KEY_VIRTUAL_DISK_PATHS)
149        self._enable_webrtc = self._config_dict.get(_CFG_KEY_ENABLE_WEBRTC)
150        if not self._instance_dir:
151            ins_dict = self._instances.get(self._instance_id)
152            if not ins_dict:
153                raise errors.ConfigError("instances[%s] property does not exist"
154                                         " in: %s" %
155                                         (self._instance_id, config_path))
156            self._instance_dir = ins_dict.get(_CFG_KEY_INSTANCE_DIR)
157            self._vnc_port = ins_dict.get(_CFG_KEY_VNC_PORT)
158            self._adb_port = (ins_dict.get(_CFG_KEY_ADB_PORT) or
159                              ins_dict.get(_CFG_KEY_ADB_HOST_PORT))
160            self._adb_ip_port = ins_dict.get(_CFG_KEY_ADB_IP_PORT)
161            self._virtual_disk_paths = ins_dict.get(_CFG_KEY_VIRTUAL_DISK_PATHS)
162            if not self._cvd_tools_path:
163                self._cvd_tools_path = os.path.dirname(
164                    ins_dict.get(_CFG_KEY_CROSVM_BINARY))
165
166    @staticmethod
167    def _GetCuttlefishRuntimeConfig(runtime_cf_config_path, raw_data=None):
168        """Get and parse cuttlefish_config.json.
169
170        Args:
171            runtime_cf_config_path: String, path of the cvd runtime config.
172            raw_data: String, data of the cvd runtime config.
173
174        Returns:
175            A dictionary that parsed from cuttlefish runtime config.
176
177        Raises:
178            errors.ConfigError: if file not found or config load failed.
179        """
180        if raw_data:
181            # if remote instance couldn't fetch the config will return message such as
182            # 'cat: .../cuttlefish_config.json: No such file or directory'.
183            # Add this condition to prevent from JSONDecodeError.
184            try:
185                return json.loads(raw_data)
186            except ValueError as e:
187                raise errors.ConfigError(
188                    "An exception happened when loading the raw_data of the "
189                    "cvd runtime config:\n%s" % str(e))
190        if not os.path.exists(runtime_cf_config_path):
191            raise errors.ConfigError(
192                "file does not exist: %s" % runtime_cf_config_path)
193        with open(runtime_cf_config_path, "r") as cf_config:
194            return json.load(cf_config)
195
196    @property
197    def cvd_tools_path(self):
198        """Return string of the path to the cvd tools."""
199        return self._cvd_tools_path
200
201    @property
202    def display_configs(self):
203        """Return display_configs."""
204        return self._display_configs
205
206    @property
207    def adb_ip_port(self):
208        """Return adb_ip_port."""
209        return self._adb_ip_port
210
211    @property
212    def instance_dir(self):
213        """Return instance_dir."""
214        return self._instance_dir
215
216    @property
217    def root_dir(self):
218        """Return root_dir."""
219        return self._root_dir
220
221    @property
222    def vnc_port(self):
223        """Return vnc_port."""
224        return self._vnc_port
225
226    @property
227    def adb_port(self):
228        """Return adb_port."""
229        return self._adb_port
230
231    @property
232    def config_path(self):
233        """Return config_path."""
234        return self._config_path
235
236    @property
237    def virtual_disk_paths(self):
238        """Return virtual_disk_paths"""
239        return self._virtual_disk_paths
240
241    @property
242    def instance_id(self):
243        """Return _instance_id"""
244        return self._instance_id
245
246    @property
247    def instance_ids(self):
248        """Return _instance_ids"""
249        return self._instance_ids
250
251    @property
252    def instances(self):
253        """Return _instances"""
254        return self._instances
255
256    @property
257    def enable_webrtc(self):
258        """Return _enable_webrtc"""
259        return self._enable_webrtc
260