blob: d538230aa1596355a1fae596c0df9e2900823d18 [file] [log] [blame]
Keun Soo Yimb293fdb2016-09-21 16:03:44 -07001#!/usr/bin/env python
2#
3# Copyright 2016 - 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
17"""Config manager.
18
19Three protobuf messages are defined in
20 driver/internal/config/proto/internal_config.proto
21 driver/internal/config/proto/user_config.proto
22
23Internal config file User config file
24 | |
25 v v
26 InternalConfig UserConfig
27 (proto message) (proto message)
28 | |
29 | |
30 |-> AcloudConfig <-|
31
32At runtime, AcloudConfigManager performs the following steps.
33- Load driver config file into a InternalConfig message instance.
34- Load user config file into a UserConfig message instance.
35- Create AcloudConfig using InternalConfig and UserConfig.
36
Kevin Chengb5963882018-05-09 00:06:27 -070037TODO:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070038 1. Add support for override configs with command line args.
39 2. Scan all configs to find the right config for given branch and build_id.
40 Raise an error if the given build_id is smaller than min_build_id
41 only applies to release build id.
42 Raise an error if the branch is not supported.
43
44"""
45
46import logging
47import os
48
chojoyce35556da2019-10-25 16:02:36 +080049import six
50
Sam Chiu46ea3112018-05-18 10:47:52 +080051from google.protobuf import text_format
52
herbertxue18c9b262018-07-12 19:14:49 +080053# pylint: disable=no-name-in-module,import-error
Sam Chiu7de3b232018-12-06 19:45:52 +080054from acloud import errors
herbertxuefd15dfd2018-12-04 11:26:27 +080055from acloud.internal import constants
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070056from acloud.internal.proto import internal_config_pb2
57from acloud.internal.proto import user_config_pb2
Sam Chiu58dad6e2018-08-27 19:50:33 +080058from acloud.create import create_args
Sam Chiu46ea3112018-05-18 10:47:52 +080059
herbertxue1512f8a2019-06-27 13:56:23 +080060
61logger = logging.getLogger(__name__)
62
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070063_CONFIG_DATA_PATH = os.path.join(
64 os.path.dirname(os.path.abspath(__file__)), "data")
herbertxue18c9b262018-07-12 19:14:49 +080065_DEFAULT_CONFIG_FILE = "acloud.config"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070066
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070067
herbertxue18c9b262018-07-12 19:14:49 +080068def GetDefaultConfigFile():
Kevin Cheng99cf3d32018-10-05 02:09:21 -070069 """Return path to default config file."""
70 config_path = os.path.join(os.path.expanduser("~"), ".config", "acloud")
71 # Create the default config dir if it doesn't exist.
72 if not os.path.exists(config_path):
73 os.makedirs(config_path)
herbertxue18c9b262018-07-12 19:14:49 +080074 return os.path.join(config_path, _DEFAULT_CONFIG_FILE)
75
76
Sam Chiuc64f3432018-08-17 11:19:06 +080077def GetAcloudConfig(args):
78 """Helper function to initialize Config object.
79
80 Args:
81 args: Namespace object from argparse.parse_args.
82
83 Return:
84 An instance of AcloudConfig.
85 """
86 config_mgr = AcloudConfigManager(args.config_file)
87 cfg = config_mgr.Load()
88 cfg.OverrideWithArgs(args)
89 return cfg
90
91
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070092class AcloudConfig(object):
93 """A class that holds all configurations for acloud."""
94
95 REQUIRED_FIELD = [
herbertxue18c9b262018-07-12 19:14:49 +080096 "machine_type", "network", "min_machine_size",
97 "disk_image_name", "disk_image_mime_type"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070098 ]
99
Kevin Chengf13e8be2019-05-10 14:17:32 -0700100 # pylint: disable=too-many-statements
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700101 def __init__(self, usr_cfg, internal_cfg):
102 """Initialize.
103
104 Args:
105 usr_cfg: A protobuf object that holds the user configurations.
106 internal_cfg: A protobuf object that holds internal configurations.
107 """
108 self.service_account_name = usr_cfg.service_account_name
Sam Chiu46ea3112018-05-18 10:47:52 +0800109 # pylint: disable=invalid-name
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700110 self.service_account_private_key_path = (
111 usr_cfg.service_account_private_key_path)
xingdai8a00d462018-07-30 14:24:48 -0700112 self.service_account_json_private_key_path = (
113 usr_cfg.service_account_json_private_key_path)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700114 self.creds_cache_file = internal_cfg.creds_cache_file
115 self.user_agent = internal_cfg.user_agent
116 self.client_id = usr_cfg.client_id
117 self.client_secret = usr_cfg.client_secret
118
119 self.project = usr_cfg.project
120 self.zone = usr_cfg.zone
121 self.machine_type = (usr_cfg.machine_type or
122 internal_cfg.default_usr_cfg.machine_type)
Sam Chiu46ea3112018-05-18 10:47:52 +0800123 self.network = usr_cfg.network or internal_cfg.default_usr_cfg.network
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700124 self.ssh_private_key_path = usr_cfg.ssh_private_key_path
Fang Dengfed6a6f2017-03-01 18:27:28 -0800125 self.ssh_public_key_path = usr_cfg.ssh_public_key_path
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700126 self.storage_bucket_name = usr_cfg.storage_bucket_name
127 self.metadata_variable = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800128 key: val for key, val in
chojoyce35556da2019-10-25 16:02:36 +0800129 six.iteritems(internal_cfg.default_usr_cfg.metadata_variable)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700130 }
131 self.metadata_variable.update(usr_cfg.metadata_variable)
132
133 self.device_resolution_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800134 device: resolution for device, resolution in
chojoyce35556da2019-10-25 16:02:36 +0800135 six.iteritems(internal_cfg.device_resolution_map)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700136 }
137 self.device_default_orientation_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800138 device: orientation for device, orientation in
chojoyce35556da2019-10-25 16:02:36 +0800139 six.iteritems(internal_cfg.device_default_orientation_map)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700140 }
Fang Dengcef4b112017-03-02 11:20:17 -0800141 self.no_project_access_msg_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800142 project: msg for project, msg in
chojoyce35556da2019-10-25 16:02:36 +0800143 six.iteritems(internal_cfg.no_project_access_msg_map)
Sam Chiu46ea3112018-05-18 10:47:52 +0800144 }
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700145 self.min_machine_size = internal_cfg.min_machine_size
146 self.disk_image_name = internal_cfg.disk_image_name
147 self.disk_image_mime_type = internal_cfg.disk_image_mime_type
148 self.disk_image_extension = internal_cfg.disk_image_extension
149 self.disk_raw_image_name = internal_cfg.disk_raw_image_name
150 self.disk_raw_image_extension = internal_cfg.disk_raw_image_extension
151 self.valid_branch_and_min_build_id = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800152 branch: min_build_id for branch, min_build_id in
chojoyce35556da2019-10-25 16:02:36 +0800153 six.iteritems(internal_cfg.valid_branch_and_min_build_id)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700154 }
155 self.precreated_data_image_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800156 size_gb: image_name for size_gb, image_name in
chojoyce35556da2019-10-25 16:02:36 +0800157 six.iteritems(internal_cfg.precreated_data_image)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700158 }
159 self.extra_data_disk_size_gb = (
160 usr_cfg.extra_data_disk_size_gb or
161 internal_cfg.default_usr_cfg.extra_data_disk_size_gb)
162 if self.extra_data_disk_size_gb > 0:
163 if "cfg_sta_persistent_data_device" not in usr_cfg.metadata_variable:
164 # If user did not set it explicity, use default.
165 self.metadata_variable["cfg_sta_persistent_data_device"] = (
166 internal_cfg.default_extra_data_disk_device)
167 if "cfg_sta_ephemeral_data_size_mb" in usr_cfg.metadata_variable:
168 raise errors.ConfigError(
169 "The following settings can't be set at the same time: "
170 "extra_data_disk_size_gb and"
171 "metadata variable cfg_sta_ephemeral_data_size_mb.")
172 if "cfg_sta_ephemeral_data_size_mb" in self.metadata_variable:
173 del self.metadata_variable["cfg_sta_ephemeral_data_size_mb"]
174
Kevin Chengc330f6f2019-05-13 09:32:42 -0700175 # Additional scopes to be passed to the created instance
176 self.extra_scopes = usr_cfg.extra_scopes
177
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700178 # Fields that can be overriden by args
179 self.orientation = usr_cfg.orientation
180 self.resolution = usr_cfg.resolution
181
Kevin Chengb5963882018-05-09 00:06:27 -0700182 self.stable_host_image_name = (
183 usr_cfg.stable_host_image_name or
184 internal_cfg.default_usr_cfg.stable_host_image_name)
185 self.stable_host_image_project = (
186 usr_cfg.stable_host_image_project or
187 internal_cfg.default_usr_cfg.stable_host_image_project)
188 self.kernel_build_target = internal_cfg.kernel_build_target
189
190 self.emulator_build_target = internal_cfg.emulator_build_target
191 self.stable_goldfish_host_image_name = (
192 usr_cfg.stable_goldfish_host_image_name or
193 internal_cfg.default_usr_cfg.stable_goldfish_host_image_name)
194 self.stable_goldfish_host_image_project = (
195 usr_cfg.stable_goldfish_host_image_project or
196 internal_cfg.default_usr_cfg.stable_goldfish_host_image_project)
197
Richard Fung97503b22019-02-06 13:43:38 -0800198 self.stable_cheeps_host_image_name = (
199 usr_cfg.stable_cheeps_host_image_name or
200 internal_cfg.default_usr_cfg.stable_cheeps_host_image_name)
201 self.stable_cheeps_host_image_project = (
202 usr_cfg.stable_cheeps_host_image_project or
203 internal_cfg.default_usr_cfg.stable_cheeps_host_image_project)
204
cyland370db22019-07-17 16:04:00 +0800205 self.extra_args_ssh_tunnel = usr_cfg.extra_args_ssh_tunnel
206
Sam Chiu58dad6e2018-08-27 19:50:33 +0800207 self.common_hw_property_map = internal_cfg.common_hw_property_map
208 self.hw_property = usr_cfg.hw_property
209
Kevin Chengd3083bf2019-05-08 15:50:57 -0700210 self.launch_args = usr_cfg.launch_args
Kevin Chengf13e8be2019-05-10 14:17:32 -0700211 self.instance_name_pattern = (
212 usr_cfg.instance_name_pattern or
213 internal_cfg.default_usr_cfg.instance_name_pattern)
Cody Schuffelen102b3b52019-07-17 10:26:35 -0700214 self.fetch_cvd_version = (
215 usr_cfg.fetch_cvd_version or
216 internal_cfg.default_usr_cfg.fetch_cvd_version)
217 if usr_cfg.HasField("enable_multi_stage") is not None:
218 self.enable_multi_stage = usr_cfg.enable_multi_stage
219 elif internal_cfg.default_usr_cfg.HasField("enable_multi_stage"):
220 self.enable_multi_stage = internal_cfg.default_usr_cfg.enable_multi_stage
221 else:
222 self.enable_multi_stage = False
Kevin Chengd3083bf2019-05-08 15:50:57 -0700223
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700224 # Verify validity of configurations.
225 self.Verify()
226
227 def OverrideWithArgs(self, parsed_args):
228 """Override configuration values with args passed in from cmd line.
229
230 Args:
231 parsed_args: Args parsed from command line.
232 """
Sam Chiu58dad6e2018-08-27 19:50:33 +0800233 if parsed_args.which == create_args.CMD_CREATE and parsed_args.spec:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700234 if not self.resolution:
235 self.resolution = self.device_resolution_map.get(
236 parsed_args.spec, "")
237 if not self.orientation:
238 self.orientation = self.device_default_orientation_map.get(
239 parsed_args.spec, "")
240 if parsed_args.email:
241 self.service_account_name = parsed_args.email
xingdai8a00d462018-07-30 14:24:48 -0700242 if parsed_args.service_account_json_private_key_path:
243 self.service_account_json_private_key_path = (
244 parsed_args.service_account_json_private_key_path)
Kevin Chengbced4af2018-06-26 10:35:01 -0700245 if parsed_args.which == "create_gf" and parsed_args.base_image:
246 self.stable_goldfish_host_image_name = parsed_args.base_image
Sam Chiu58dad6e2018-08-27 19:50:33 +0800247 if parsed_args.which == create_args.CMD_CREATE and not self.hw_property:
herbertxuefd15dfd2018-12-04 11:26:27 +0800248 flavor = parsed_args.flavor or constants.FLAVOR_PHONE
249 self.hw_property = self.common_hw_property_map.get(flavor, "")
Kevin Chengc9424a82018-10-25 11:34:55 -0700250 if parsed_args.which in [create_args.CMD_CREATE, "create_cf"]:
251 if parsed_args.network:
252 self.network = parsed_args.network
Cody Schuffelen102b3b52019-07-17 10:26:35 -0700253 if parsed_args.multi_stage_launch is not None:
254 self.enable_multi_stage = parsed_args.multi_stage_launch
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700255
herbertxuefd15dfd2018-12-04 11:26:27 +0800256 def OverrideHwPropertyWithFlavor(self, flavor):
257 """Override hw configuration values with flavor name.
258
259 HwProperty will be overrided according to the change of flavor.
260 If flavor is None, set hw configuration with phone(default flavor).
261
262 Args:
263 flavor: string of flavor name.
264 """
265 self.hw_property = self.common_hw_property_map.get(
266 flavor, constants.FLAVOR_PHONE)
267
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700268 def Verify(self):
269 """Verify configuration fields."""
270 missing = [f for f in self.REQUIRED_FIELD if not getattr(self, f)]
271 if missing:
272 raise errors.ConfigError(
273 "Missing required configuration fields: %s" % missing)
Sam Chiu46ea3112018-05-18 10:47:52 +0800274 if (self.extra_data_disk_size_gb and self.extra_data_disk_size_gb not in
275 self.precreated_data_image_map):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700276 raise errors.ConfigError(
277 "Supported extra_data_disk_size_gb options(gb): %s, "
278 "invalid value: %d" % (self.precreated_data_image_map.keys(),
279 self.extra_data_disk_size_gb))
280
Sam Chiu43d81c52020-02-04 10:49:47 +0800281 def SupportRemoteInstance(self):
282 """Return True if gcp project is provided in config."""
283 return True if self.project else False
284
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700285
286class AcloudConfigManager(object):
287 """A class that loads configurations."""
288
289 _DEFAULT_INTERNAL_CONFIG_PATH = os.path.join(_CONFIG_DATA_PATH,
290 "default.config")
291
292 def __init__(self,
293 user_config_path,
294 internal_config_path=_DEFAULT_INTERNAL_CONFIG_PATH):
herbertxue18c9b262018-07-12 19:14:49 +0800295 """Initialize with user specified paths to configs.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700296
297 Args:
298 user_config_path: path to the user config.
299 internal_config_path: path to the internal conifg.
300 """
herbertxue34776bb2018-07-03 21:57:48 +0800301 self.user_config_path = user_config_path
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700302 self._internal_config_path = internal_config_path
303
304 def Load(self):
herbertxue18c9b262018-07-12 19:14:49 +0800305 """Load the configurations.
306
307 Load user config with some special design.
308 1. User specified user config:
309 a.User config exist: Load config.
310 b.User config didn't exist: Raise exception.
311 2. User didn't specify user config, use default config:
312 a.Default config exist: Load config.
313 b.Default config didn't exist: provide empty usr_cfg.
314 """
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700315 internal_cfg = None
316 usr_cfg = None
317 try:
318 with open(self._internal_config_path) as config_file:
319 internal_cfg = self.LoadConfigFromProtocolBuffer(
320 config_file, internal_config_pb2.InternalConfig)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700321 except OSError as e:
Sam Chiu46ea3112018-05-18 10:47:52 +0800322 raise errors.ConfigError("Could not load config files: %s" % str(e))
herbertxue18c9b262018-07-12 19:14:49 +0800323 # Load user config file
herbertxue34776bb2018-07-03 21:57:48 +0800324 if self.user_config_path:
325 if os.path.exists(self.user_config_path):
326 with open(self.user_config_path, "r") as config_file:
herbertxue18c9b262018-07-12 19:14:49 +0800327 usr_cfg = self.LoadConfigFromProtocolBuffer(
328 config_file, user_config_pb2.UserConfig)
329 else:
330 raise errors.ConfigError("The file doesn't exist: %s" %
herbertxue34776bb2018-07-03 21:57:48 +0800331 (self.user_config_path))
herbertxue18c9b262018-07-12 19:14:49 +0800332 else:
herbertxue34776bb2018-07-03 21:57:48 +0800333 self.user_config_path = GetDefaultConfigFile()
334 if os.path.exists(self.user_config_path):
335 with open(self.user_config_path, "r") as config_file:
herbertxue18c9b262018-07-12 19:14:49 +0800336 usr_cfg = self.LoadConfigFromProtocolBuffer(
337 config_file, user_config_pb2.UserConfig)
338 else:
339 usr_cfg = user_config_pb2.UserConfig()
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700340 return AcloudConfig(usr_cfg, internal_cfg)
341
342 @staticmethod
343 def LoadConfigFromProtocolBuffer(config_file, message_type):
344 """Load config from a text-based protocol buffer file.
345
346 Args:
347 config_file: A python File object.
348 message_type: A proto message class.
349
350 Returns:
351 An instance of type "message_type" populated with data
352 from the file.
353 """
354 try:
355 config = message_type()
356 text_format.Merge(config_file.read(), config)
357 return config
358 except text_format.ParseError as e:
359 raise errors.ConfigError("Could not parse config: %s" % str(e))