blob: b0f188224c1ee67857c6afa147f602f416855e92 [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
Sam Chiu46ea3112018-05-18 10:47:52 +080049from google.protobuf import text_format
50
herbertxue18c9b262018-07-12 19:14:49 +080051# pylint: disable=no-name-in-module,import-error
Sam Chiu7de3b232018-12-06 19:45:52 +080052from acloud import errors
herbertxuefd15dfd2018-12-04 11:26:27 +080053from acloud.internal import constants
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070054from acloud.internal.proto import internal_config_pb2
55from acloud.internal.proto import user_config_pb2
Sam Chiu58dad6e2018-08-27 19:50:33 +080056from acloud.create import create_args
Sam Chiu46ea3112018-05-18 10:47:52 +080057
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070058_CONFIG_DATA_PATH = os.path.join(
59 os.path.dirname(os.path.abspath(__file__)), "data")
herbertxue18c9b262018-07-12 19:14:49 +080060_DEFAULT_CONFIG_FILE = "acloud.config"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070061
62logger = logging.getLogger(__name__)
63
64
herbertxue18c9b262018-07-12 19:14:49 +080065def GetDefaultConfigFile():
Kevin Cheng99cf3d32018-10-05 02:09:21 -070066 """Return path to default config file."""
67 config_path = os.path.join(os.path.expanduser("~"), ".config", "acloud")
68 # Create the default config dir if it doesn't exist.
69 if not os.path.exists(config_path):
70 os.makedirs(config_path)
herbertxue18c9b262018-07-12 19:14:49 +080071 return os.path.join(config_path, _DEFAULT_CONFIG_FILE)
72
73
Sam Chiuc64f3432018-08-17 11:19:06 +080074def GetAcloudConfig(args):
75 """Helper function to initialize Config object.
76
77 Args:
78 args: Namespace object from argparse.parse_args.
79
80 Return:
81 An instance of AcloudConfig.
82 """
83 config_mgr = AcloudConfigManager(args.config_file)
84 cfg = config_mgr.Load()
85 cfg.OverrideWithArgs(args)
86 return cfg
87
88
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070089class AcloudConfig(object):
90 """A class that holds all configurations for acloud."""
91
92 REQUIRED_FIELD = [
herbertxue18c9b262018-07-12 19:14:49 +080093 "machine_type", "network", "min_machine_size",
94 "disk_image_name", "disk_image_mime_type"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070095 ]
96
Kevin Chengf13e8be2019-05-10 14:17:32 -070097 # pylint: disable=too-many-statements
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070098 def __init__(self, usr_cfg, internal_cfg):
99 """Initialize.
100
101 Args:
102 usr_cfg: A protobuf object that holds the user configurations.
103 internal_cfg: A protobuf object that holds internal configurations.
104 """
105 self.service_account_name = usr_cfg.service_account_name
Sam Chiu46ea3112018-05-18 10:47:52 +0800106 # pylint: disable=invalid-name
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700107 self.service_account_private_key_path = (
108 usr_cfg.service_account_private_key_path)
xingdai8a00d462018-07-30 14:24:48 -0700109 self.service_account_json_private_key_path = (
110 usr_cfg.service_account_json_private_key_path)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700111 self.creds_cache_file = internal_cfg.creds_cache_file
112 self.user_agent = internal_cfg.user_agent
113 self.client_id = usr_cfg.client_id
114 self.client_secret = usr_cfg.client_secret
115
116 self.project = usr_cfg.project
117 self.zone = usr_cfg.zone
118 self.machine_type = (usr_cfg.machine_type or
119 internal_cfg.default_usr_cfg.machine_type)
Sam Chiu46ea3112018-05-18 10:47:52 +0800120 self.network = usr_cfg.network or internal_cfg.default_usr_cfg.network
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700121 self.ssh_private_key_path = usr_cfg.ssh_private_key_path
Fang Dengfed6a6f2017-03-01 18:27:28 -0800122 self.ssh_public_key_path = usr_cfg.ssh_public_key_path
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700123 self.storage_bucket_name = usr_cfg.storage_bucket_name
124 self.metadata_variable = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800125 key: val for key, val in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700126 internal_cfg.default_usr_cfg.metadata_variable.iteritems()
127 }
128 self.metadata_variable.update(usr_cfg.metadata_variable)
129
130 self.device_resolution_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800131 device: resolution for device, resolution in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700132 internal_cfg.device_resolution_map.iteritems()
133 }
134 self.device_default_orientation_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800135 device: orientation for device, orientation in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700136 internal_cfg.device_default_orientation_map.iteritems()
137 }
Fang Dengcef4b112017-03-02 11:20:17 -0800138 self.no_project_access_msg_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800139 project: msg for project, msg in
140 internal_cfg.no_project_access_msg_map.iteritems()
141 }
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700142 self.min_machine_size = internal_cfg.min_machine_size
143 self.disk_image_name = internal_cfg.disk_image_name
144 self.disk_image_mime_type = internal_cfg.disk_image_mime_type
145 self.disk_image_extension = internal_cfg.disk_image_extension
146 self.disk_raw_image_name = internal_cfg.disk_raw_image_name
147 self.disk_raw_image_extension = internal_cfg.disk_raw_image_extension
148 self.valid_branch_and_min_build_id = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800149 branch: min_build_id for branch, min_build_id in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700150 internal_cfg.valid_branch_and_min_build_id.iteritems()
151 }
152 self.precreated_data_image_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800153 size_gb: image_name for size_gb, image_name in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700154 internal_cfg.precreated_data_image.iteritems()
155 }
156 self.extra_data_disk_size_gb = (
157 usr_cfg.extra_data_disk_size_gb or
158 internal_cfg.default_usr_cfg.extra_data_disk_size_gb)
159 if self.extra_data_disk_size_gb > 0:
160 if "cfg_sta_persistent_data_device" not in usr_cfg.metadata_variable:
161 # If user did not set it explicity, use default.
162 self.metadata_variable["cfg_sta_persistent_data_device"] = (
163 internal_cfg.default_extra_data_disk_device)
164 if "cfg_sta_ephemeral_data_size_mb" in usr_cfg.metadata_variable:
165 raise errors.ConfigError(
166 "The following settings can't be set at the same time: "
167 "extra_data_disk_size_gb and"
168 "metadata variable cfg_sta_ephemeral_data_size_mb.")
169 if "cfg_sta_ephemeral_data_size_mb" in self.metadata_variable:
170 del self.metadata_variable["cfg_sta_ephemeral_data_size_mb"]
171
172 # Fields that can be overriden by args
173 self.orientation = usr_cfg.orientation
174 self.resolution = usr_cfg.resolution
175
Kevin Chengb5963882018-05-09 00:06:27 -0700176 self.stable_host_image_name = (
177 usr_cfg.stable_host_image_name or
178 internal_cfg.default_usr_cfg.stable_host_image_name)
179 self.stable_host_image_project = (
180 usr_cfg.stable_host_image_project or
181 internal_cfg.default_usr_cfg.stable_host_image_project)
182 self.kernel_build_target = internal_cfg.kernel_build_target
183
184 self.emulator_build_target = internal_cfg.emulator_build_target
185 self.stable_goldfish_host_image_name = (
186 usr_cfg.stable_goldfish_host_image_name or
187 internal_cfg.default_usr_cfg.stable_goldfish_host_image_name)
188 self.stable_goldfish_host_image_project = (
189 usr_cfg.stable_goldfish_host_image_project or
190 internal_cfg.default_usr_cfg.stable_goldfish_host_image_project)
191
Richard Fung97503b22019-02-06 13:43:38 -0800192 self.stable_cheeps_host_image_name = (
193 usr_cfg.stable_cheeps_host_image_name or
194 internal_cfg.default_usr_cfg.stable_cheeps_host_image_name)
195 self.stable_cheeps_host_image_project = (
196 usr_cfg.stable_cheeps_host_image_project or
197 internal_cfg.default_usr_cfg.stable_cheeps_host_image_project)
198
Sam Chiu58dad6e2018-08-27 19:50:33 +0800199 self.common_hw_property_map = internal_cfg.common_hw_property_map
200 self.hw_property = usr_cfg.hw_property
201
Kevin Chengd3083bf2019-05-08 15:50:57 -0700202 self.launch_args = usr_cfg.launch_args
Kevin Chengf13e8be2019-05-10 14:17:32 -0700203 self.instance_name_pattern = (
204 usr_cfg.instance_name_pattern or
205 internal_cfg.default_usr_cfg.instance_name_pattern)
206
Kevin Chengd3083bf2019-05-08 15:50:57 -0700207
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700208 # Verify validity of configurations.
209 self.Verify()
210
211 def OverrideWithArgs(self, parsed_args):
212 """Override configuration values with args passed in from cmd line.
213
214 Args:
215 parsed_args: Args parsed from command line.
216 """
Sam Chiu58dad6e2018-08-27 19:50:33 +0800217 if parsed_args.which == create_args.CMD_CREATE and parsed_args.spec:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700218 if not self.resolution:
219 self.resolution = self.device_resolution_map.get(
220 parsed_args.spec, "")
221 if not self.orientation:
222 self.orientation = self.device_default_orientation_map.get(
223 parsed_args.spec, "")
224 if parsed_args.email:
225 self.service_account_name = parsed_args.email
xingdai8a00d462018-07-30 14:24:48 -0700226 if parsed_args.service_account_json_private_key_path:
227 self.service_account_json_private_key_path = (
228 parsed_args.service_account_json_private_key_path)
Kevin Chengbced4af2018-06-26 10:35:01 -0700229 if parsed_args.which == "create_gf" and parsed_args.base_image:
230 self.stable_goldfish_host_image_name = parsed_args.base_image
Sam Chiu58dad6e2018-08-27 19:50:33 +0800231 if parsed_args.which == create_args.CMD_CREATE and not self.hw_property:
herbertxuefd15dfd2018-12-04 11:26:27 +0800232 flavor = parsed_args.flavor or constants.FLAVOR_PHONE
233 self.hw_property = self.common_hw_property_map.get(flavor, "")
Kevin Chengc9424a82018-10-25 11:34:55 -0700234 if parsed_args.which in [create_args.CMD_CREATE, "create_cf"]:
235 if parsed_args.network:
236 self.network = parsed_args.network
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700237
herbertxuefd15dfd2018-12-04 11:26:27 +0800238 def OverrideHwPropertyWithFlavor(self, flavor):
239 """Override hw configuration values with flavor name.
240
241 HwProperty will be overrided according to the change of flavor.
242 If flavor is None, set hw configuration with phone(default flavor).
243
244 Args:
245 flavor: string of flavor name.
246 """
247 self.hw_property = self.common_hw_property_map.get(
248 flavor, constants.FLAVOR_PHONE)
249
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700250 def Verify(self):
251 """Verify configuration fields."""
252 missing = [f for f in self.REQUIRED_FIELD if not getattr(self, f)]
253 if missing:
254 raise errors.ConfigError(
255 "Missing required configuration fields: %s" % missing)
Sam Chiu46ea3112018-05-18 10:47:52 +0800256 if (self.extra_data_disk_size_gb and self.extra_data_disk_size_gb not in
257 self.precreated_data_image_map):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700258 raise errors.ConfigError(
259 "Supported extra_data_disk_size_gb options(gb): %s, "
260 "invalid value: %d" % (self.precreated_data_image_map.keys(),
261 self.extra_data_disk_size_gb))
262
263
264class AcloudConfigManager(object):
265 """A class that loads configurations."""
266
267 _DEFAULT_INTERNAL_CONFIG_PATH = os.path.join(_CONFIG_DATA_PATH,
268 "default.config")
269
270 def __init__(self,
271 user_config_path,
272 internal_config_path=_DEFAULT_INTERNAL_CONFIG_PATH):
herbertxue18c9b262018-07-12 19:14:49 +0800273 """Initialize with user specified paths to configs.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700274
275 Args:
276 user_config_path: path to the user config.
277 internal_config_path: path to the internal conifg.
278 """
herbertxue34776bb2018-07-03 21:57:48 +0800279 self.user_config_path = user_config_path
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700280 self._internal_config_path = internal_config_path
281
282 def Load(self):
herbertxue18c9b262018-07-12 19:14:49 +0800283 """Load the configurations.
284
285 Load user config with some special design.
286 1. User specified user config:
287 a.User config exist: Load config.
288 b.User config didn't exist: Raise exception.
289 2. User didn't specify user config, use default config:
290 a.Default config exist: Load config.
291 b.Default config didn't exist: provide empty usr_cfg.
292 """
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700293 internal_cfg = None
294 usr_cfg = None
295 try:
296 with open(self._internal_config_path) as config_file:
297 internal_cfg = self.LoadConfigFromProtocolBuffer(
298 config_file, internal_config_pb2.InternalConfig)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700299 except OSError as e:
Sam Chiu46ea3112018-05-18 10:47:52 +0800300 raise errors.ConfigError("Could not load config files: %s" % str(e))
herbertxue18c9b262018-07-12 19:14:49 +0800301 # Load user config file
herbertxue34776bb2018-07-03 21:57:48 +0800302 if self.user_config_path:
303 if os.path.exists(self.user_config_path):
304 with open(self.user_config_path, "r") as config_file:
herbertxue18c9b262018-07-12 19:14:49 +0800305 usr_cfg = self.LoadConfigFromProtocolBuffer(
306 config_file, user_config_pb2.UserConfig)
307 else:
308 raise errors.ConfigError("The file doesn't exist: %s" %
herbertxue34776bb2018-07-03 21:57:48 +0800309 (self.user_config_path))
herbertxue18c9b262018-07-12 19:14:49 +0800310 else:
herbertxue34776bb2018-07-03 21:57:48 +0800311 self.user_config_path = GetDefaultConfigFile()
312 if os.path.exists(self.user_config_path):
313 with open(self.user_config_path, "r") as config_file:
herbertxue18c9b262018-07-12 19:14:49 +0800314 usr_cfg = self.LoadConfigFromProtocolBuffer(
315 config_file, user_config_pb2.UserConfig)
316 else:
317 usr_cfg = user_config_pb2.UserConfig()
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700318 return AcloudConfig(usr_cfg, internal_cfg)
319
320 @staticmethod
321 def LoadConfigFromProtocolBuffer(config_file, message_type):
322 """Load config from a text-based protocol buffer file.
323
324 Args:
325 config_file: A python File object.
326 message_type: A proto message class.
327
328 Returns:
329 An instance of type "message_type" populated with data
330 from the file.
331 """
332 try:
333 config = message_type()
334 text_format.Merge(config_file.read(), config)
335 return config
336 except text_format.ParseError as e:
337 raise errors.ConfigError("Could not parse config: %s" % str(e))