blob: 024dc201249c3f58df993b25a38986ab29ac0c2d [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
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070052from acloud.internal.proto import internal_config_pb2
53from acloud.internal.proto import user_config_pb2
Sam Chiu58dad6e2018-08-27 19:50:33 +080054from acloud.create import create_args
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070055from acloud.public import errors
Sam Chiu46ea3112018-05-18 10:47:52 +080056
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070057_CONFIG_DATA_PATH = os.path.join(
58 os.path.dirname(os.path.abspath(__file__)), "data")
herbertxue18c9b262018-07-12 19:14:49 +080059_DEFAULT_CONFIG_FILE = "acloud.config"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070060
61logger = logging.getLogger(__name__)
62
63
herbertxue18c9b262018-07-12 19:14:49 +080064def GetDefaultConfigFile():
65 """Get config default path."""
66 config_path = os.path.expanduser("~")
67 return os.path.join(config_path, _DEFAULT_CONFIG_FILE)
68
69
Sam Chiuc64f3432018-08-17 11:19:06 +080070def GetAcloudConfig(args):
71 """Helper function to initialize Config object.
72
73 Args:
74 args: Namespace object from argparse.parse_args.
75
76 Return:
77 An instance of AcloudConfig.
78 """
79 config_mgr = AcloudConfigManager(args.config_file)
80 cfg = config_mgr.Load()
81 cfg.OverrideWithArgs(args)
82 return cfg
83
84
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070085class AcloudConfig(object):
86 """A class that holds all configurations for acloud."""
87
88 REQUIRED_FIELD = [
herbertxue18c9b262018-07-12 19:14:49 +080089 "machine_type", "network", "min_machine_size",
90 "disk_image_name", "disk_image_mime_type"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070091 ]
92
93 def __init__(self, usr_cfg, internal_cfg):
94 """Initialize.
95
96 Args:
97 usr_cfg: A protobuf object that holds the user configurations.
98 internal_cfg: A protobuf object that holds internal configurations.
99 """
100 self.service_account_name = usr_cfg.service_account_name
Sam Chiu46ea3112018-05-18 10:47:52 +0800101 # pylint: disable=invalid-name
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700102 self.service_account_private_key_path = (
103 usr_cfg.service_account_private_key_path)
xingdai8a00d462018-07-30 14:24:48 -0700104 self.service_account_json_private_key_path = (
105 usr_cfg.service_account_json_private_key_path)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700106 self.creds_cache_file = internal_cfg.creds_cache_file
107 self.user_agent = internal_cfg.user_agent
108 self.client_id = usr_cfg.client_id
109 self.client_secret = usr_cfg.client_secret
110
111 self.project = usr_cfg.project
112 self.zone = usr_cfg.zone
113 self.machine_type = (usr_cfg.machine_type or
114 internal_cfg.default_usr_cfg.machine_type)
Sam Chiu46ea3112018-05-18 10:47:52 +0800115 self.network = usr_cfg.network or internal_cfg.default_usr_cfg.network
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700116 self.ssh_private_key_path = usr_cfg.ssh_private_key_path
Fang Dengfed6a6f2017-03-01 18:27:28 -0800117 self.ssh_public_key_path = usr_cfg.ssh_public_key_path
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700118 self.storage_bucket_name = usr_cfg.storage_bucket_name
119 self.metadata_variable = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800120 key: val for key, val in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700121 internal_cfg.default_usr_cfg.metadata_variable.iteritems()
122 }
123 self.metadata_variable.update(usr_cfg.metadata_variable)
124
125 self.device_resolution_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800126 device: resolution for device, resolution in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700127 internal_cfg.device_resolution_map.iteritems()
128 }
129 self.device_default_orientation_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800130 device: orientation for device, orientation in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700131 internal_cfg.device_default_orientation_map.iteritems()
132 }
Fang Dengcef4b112017-03-02 11:20:17 -0800133 self.no_project_access_msg_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800134 project: msg for project, msg in
135 internal_cfg.no_project_access_msg_map.iteritems()
136 }
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700137 self.min_machine_size = internal_cfg.min_machine_size
138 self.disk_image_name = internal_cfg.disk_image_name
139 self.disk_image_mime_type = internal_cfg.disk_image_mime_type
140 self.disk_image_extension = internal_cfg.disk_image_extension
141 self.disk_raw_image_name = internal_cfg.disk_raw_image_name
142 self.disk_raw_image_extension = internal_cfg.disk_raw_image_extension
143 self.valid_branch_and_min_build_id = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800144 branch: min_build_id for branch, min_build_id in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700145 internal_cfg.valid_branch_and_min_build_id.iteritems()
146 }
147 self.precreated_data_image_map = {
Sam Chiu46ea3112018-05-18 10:47:52 +0800148 size_gb: image_name for size_gb, image_name in
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700149 internal_cfg.precreated_data_image.iteritems()
150 }
151 self.extra_data_disk_size_gb = (
152 usr_cfg.extra_data_disk_size_gb or
153 internal_cfg.default_usr_cfg.extra_data_disk_size_gb)
154 if self.extra_data_disk_size_gb > 0:
155 if "cfg_sta_persistent_data_device" not in usr_cfg.metadata_variable:
156 # If user did not set it explicity, use default.
157 self.metadata_variable["cfg_sta_persistent_data_device"] = (
158 internal_cfg.default_extra_data_disk_device)
159 if "cfg_sta_ephemeral_data_size_mb" in usr_cfg.metadata_variable:
160 raise errors.ConfigError(
161 "The following settings can't be set at the same time: "
162 "extra_data_disk_size_gb and"
163 "metadata variable cfg_sta_ephemeral_data_size_mb.")
164 if "cfg_sta_ephemeral_data_size_mb" in self.metadata_variable:
165 del self.metadata_variable["cfg_sta_ephemeral_data_size_mb"]
166
167 # Fields that can be overriden by args
168 self.orientation = usr_cfg.orientation
169 self.resolution = usr_cfg.resolution
170
Kevin Chengb5963882018-05-09 00:06:27 -0700171 self.stable_host_image_name = (
172 usr_cfg.stable_host_image_name or
173 internal_cfg.default_usr_cfg.stable_host_image_name)
174 self.stable_host_image_project = (
175 usr_cfg.stable_host_image_project or
176 internal_cfg.default_usr_cfg.stable_host_image_project)
177 self.kernel_build_target = internal_cfg.kernel_build_target
178
179 self.emulator_build_target = internal_cfg.emulator_build_target
180 self.stable_goldfish_host_image_name = (
181 usr_cfg.stable_goldfish_host_image_name or
182 internal_cfg.default_usr_cfg.stable_goldfish_host_image_name)
183 self.stable_goldfish_host_image_project = (
184 usr_cfg.stable_goldfish_host_image_project or
185 internal_cfg.default_usr_cfg.stable_goldfish_host_image_project)
186
Sam Chiu58dad6e2018-08-27 19:50:33 +0800187 self.common_hw_property_map = internal_cfg.common_hw_property_map
188 self.hw_property = usr_cfg.hw_property
189
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700190 # Verify validity of configurations.
191 self.Verify()
192
193 def OverrideWithArgs(self, parsed_args):
194 """Override configuration values with args passed in from cmd line.
195
196 Args:
197 parsed_args: Args parsed from command line.
198 """
Sam Chiu58dad6e2018-08-27 19:50:33 +0800199 if parsed_args.which == create_args.CMD_CREATE and parsed_args.spec:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700200 if not self.resolution:
201 self.resolution = self.device_resolution_map.get(
202 parsed_args.spec, "")
203 if not self.orientation:
204 self.orientation = self.device_default_orientation_map.get(
205 parsed_args.spec, "")
206 if parsed_args.email:
207 self.service_account_name = parsed_args.email
xingdai8a00d462018-07-30 14:24:48 -0700208 if parsed_args.service_account_json_private_key_path:
209 self.service_account_json_private_key_path = (
210 parsed_args.service_account_json_private_key_path)
Kevin Chengbced4af2018-06-26 10:35:01 -0700211 if parsed_args.which == "create_gf" and parsed_args.base_image:
212 self.stable_goldfish_host_image_name = parsed_args.base_image
Sam Chiu58dad6e2018-08-27 19:50:33 +0800213 if parsed_args.which == create_args.CMD_CREATE and not self.hw_property:
214 self.hw_property = self.common_hw_property_map.get(
215 parsed_args.flavor, "")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700216
217 def Verify(self):
218 """Verify configuration fields."""
219 missing = [f for f in self.REQUIRED_FIELD if not getattr(self, f)]
220 if missing:
221 raise errors.ConfigError(
222 "Missing required configuration fields: %s" % missing)
Sam Chiu46ea3112018-05-18 10:47:52 +0800223 if (self.extra_data_disk_size_gb and self.extra_data_disk_size_gb not in
224 self.precreated_data_image_map):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700225 raise errors.ConfigError(
226 "Supported extra_data_disk_size_gb options(gb): %s, "
227 "invalid value: %d" % (self.precreated_data_image_map.keys(),
228 self.extra_data_disk_size_gb))
229
230
231class AcloudConfigManager(object):
232 """A class that loads configurations."""
233
234 _DEFAULT_INTERNAL_CONFIG_PATH = os.path.join(_CONFIG_DATA_PATH,
235 "default.config")
236
237 def __init__(self,
238 user_config_path,
239 internal_config_path=_DEFAULT_INTERNAL_CONFIG_PATH):
herbertxue18c9b262018-07-12 19:14:49 +0800240 """Initialize with user specified paths to configs.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700241
242 Args:
243 user_config_path: path to the user config.
244 internal_config_path: path to the internal conifg.
245 """
herbertxue34776bb2018-07-03 21:57:48 +0800246 self.user_config_path = user_config_path
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700247 self._internal_config_path = internal_config_path
248
249 def Load(self):
herbertxue18c9b262018-07-12 19:14:49 +0800250 """Load the configurations.
251
252 Load user config with some special design.
253 1. User specified user config:
254 a.User config exist: Load config.
255 b.User config didn't exist: Raise exception.
256 2. User didn't specify user config, use default config:
257 a.Default config exist: Load config.
258 b.Default config didn't exist: provide empty usr_cfg.
259 """
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700260 internal_cfg = None
261 usr_cfg = None
262 try:
263 with open(self._internal_config_path) as config_file:
264 internal_cfg = self.LoadConfigFromProtocolBuffer(
265 config_file, internal_config_pb2.InternalConfig)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700266 except OSError as e:
Sam Chiu46ea3112018-05-18 10:47:52 +0800267 raise errors.ConfigError("Could not load config files: %s" % str(e))
herbertxue18c9b262018-07-12 19:14:49 +0800268 # Load user config file
herbertxue34776bb2018-07-03 21:57:48 +0800269 if self.user_config_path:
270 if os.path.exists(self.user_config_path):
271 with open(self.user_config_path, "r") as config_file:
herbertxue18c9b262018-07-12 19:14:49 +0800272 usr_cfg = self.LoadConfigFromProtocolBuffer(
273 config_file, user_config_pb2.UserConfig)
274 else:
275 raise errors.ConfigError("The file doesn't exist: %s" %
herbertxue34776bb2018-07-03 21:57:48 +0800276 (self.user_config_path))
herbertxue18c9b262018-07-12 19:14:49 +0800277 else:
herbertxue34776bb2018-07-03 21:57:48 +0800278 self.user_config_path = GetDefaultConfigFile()
279 if os.path.exists(self.user_config_path):
280 with open(self.user_config_path, "r") as config_file:
herbertxue18c9b262018-07-12 19:14:49 +0800281 usr_cfg = self.LoadConfigFromProtocolBuffer(
282 config_file, user_config_pb2.UserConfig)
283 else:
284 usr_cfg = user_config_pb2.UserConfig()
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700285 return AcloudConfig(usr_cfg, internal_cfg)
286
287 @staticmethod
288 def LoadConfigFromProtocolBuffer(config_file, message_type):
289 """Load config from a text-based protocol buffer file.
290
291 Args:
292 config_file: A python File object.
293 message_type: A proto message class.
294
295 Returns:
296 An instance of type "message_type" populated with data
297 from the file.
298 """
299 try:
300 config = message_type()
301 text_format.Merge(config_file.read(), config)
302 return config
303 except text_format.ParseError as e:
304 raise errors.ConfigError("Could not parse config: %s" % str(e))