1#!/usr/bin/env vpython3 2# Copyright 2021 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Marshal various scheduling configs. Store them into the UFS datastore 6through the Google datastore API. 7 8By default, this script converts a few config-related protos into datastore 9entities: 10 111. ConfigBundleList from 'hw_design/generated/configs.jsonproto' 122. DutAttributeList from 13 '.../chromiumos/src/config/generated/dut_attributes.jsonproto' 143. FlatConfigList from 'hw_design/generated/flattened.jsonproto' 154. DeviceStabilityList from 16 '.../chromiumos/infra/config/testingconfig/generated/device_stability.cfg' 17 18The lists are parsed and individual entities are extracted. Using the datastore 19client specified, it encodes the protos as datastore entities and stores them 20into the UFS datastore. 21""" 22 23import argparse 24import datetime 25import logging 26import os 27 28from google.cloud import datastore 29 30from checker import io_utils 31from common import proto_utils 32 33# type constants 34CB_INPUT_TYPE = 'chromiumos.config.payload.ConfigBundleList' 35CB_OUTPUT_TYPE = 'chromiumos.config.payload.ConfigBundle' 36DA_INPUT_TYPE = 'chromiumos.test.api.DutAttributeList' 37DA_OUTPUT_TYPE = 'chromiumos.test.api.DutAttribute' 38FC_INPUT_TYPE = 'chromiumos.config.payload.FlatConfigList' 39FC_OUTPUT_TYPE = 'chromiumos.config.payload.FlatConfig' 40DEV_STAB_INPUT_TYPE = 'chromiumos.test.dut.DeviceStabilityList' 41DEV_STAB_OUTPUT_TYPE = 'chromiumos.test.dut.DeviceStability' 42 43# UFS services 44UFS_DEV_PROJECT = 'unified-fleet-system-dev' 45UFS_PROD_PROJECT = 'unified-fleet-system' 46 47# datastore constants 48CONFIG_BUNDLE_KIND = 'ConfigBundle' 49DUT_ATTRIBUTE_KIND = 'DutAttribute' 50FLAT_CONFIG_KIND = 'FlatConfig' 51DEVICE_STABILITY_KIND = 'DeviceStability' 52 53 54def get_ufs_project(env): 55 """Return project name based on env argument.""" 56 if env == 'dev': 57 return UFS_DEV_PROJECT 58 if env == 'prod': 59 return UFS_PROD_PROJECT 60 raise RuntimeError('get_ufs_project: environment %s not supported' % env) 61 62 63def generate_config_bundle_id(bundle): 64 """Generate ConfigBundleEntity id as ${program_id}-${design_id}. 65 66 It is possible the ConfigBundle has an empty design_list (e.g. because it is 67 from a program repo). In this case, return None. 68 """ 69 if not bundle.design_list: 70 return None 71 72 return (bundle.design_list[0].program_id.value + '-' + 73 bundle.design_list[0].id.value).lower() 74 75 76def handle_config_bundle_list(cb_list_path, client): 77 """Take a path to a ConfigBundleList, iterate through the list and store into 78 UFS datastore based on env. 79 """ 80 cb_list = io_utils.read_json_proto( 81 protodb.GetSymbol(CB_INPUT_TYPE)(), cb_list_path) 82 83 for config_bundle in cb_list.values: 84 update_config(config_bundle, client, flat=False) 85 86 87def generate_flat_config_id(bundle): 88 """Generate FlatConfigEntity id as ${program_id}-${design_id}-${design_config_id} 89 if design_config_id is available. Else ${program_id}-${design_id}.""" 90 if bundle.hw_design_config.id.value: 91 return (bundle.hw_design.program_id.value + '-' \ 92 + bundle.hw_design.id.value \ 93 + '-' + bundle.hw_design_config.id.value).lower() 94 return (bundle.hw_design.program_id.value + '-' \ 95 + bundle.hw_design.id.value).lower() 96 97 98def handle_flat_config_list(fc_list_path, client): 99 """Take a path to a FlatConfigList, iterate through the list and store into 100 UFS datastore based on env. 101 """ 102 fc_list = io_utils.read_json_proto( 103 protodb.GetSymbol(FC_INPUT_TYPE)(), fc_list_path) 104 105 for flat_config in fc_list.values: 106 update_config(flat_config, client, flat=True) 107 108 109def update_config(config, client, flat=False): 110 """Take a ConfigBundle or FlatConfig and store it an an entity in the UFS datastore.""" 111 if flat: 112 kind = FLAT_CONFIG_KIND 113 eid = generate_flat_config_id(config) 114 else: 115 kind = CONFIG_BUNDLE_KIND 116 eid = generate_config_bundle_id(config) 117 118 if not eid: 119 logging.info('no eid for config %s, skipping', config) 120 return 121 122 logging.info('update_config: handling %s', eid) 123 124 key = client.key(kind, eid) 125 entity = datastore.Entity( 126 key=key, 127 exclude_from_indexes=['ConfigData'], 128 ) 129 entity['ConfigData'] = config.SerializeToString() 130 entity['Updated'] = datetime.datetime.now() 131 132 logging.info('update_config: putting entity into datastore for %s', eid) 133 client.put(entity) 134 135 136def handle_dut_attribute_list(dut_attr_list_path, client): 137 """Take a path to a DutAttributeList, iterate through the list and store into 138 UFS datastore based on env. 139 """ 140 dut_attr_list = io_utils.read_json_proto( 141 protodb.GetSymbol(DA_INPUT_TYPE)(), dut_attr_list_path) 142 143 for dut_attribute in dut_attr_list.dut_attributes: 144 update_dut_attribute(dut_attribute, client) 145 146 147def update_dut_attribute(attr, client): 148 """Take a DutAttribute and store it in the UFS datastore as a DutAttributeEntity.""" 149 eid = attr.id.value 150 logging.info('update_dut_attribute: handling %s', eid) 151 152 key = client.key(DUT_ATTRIBUTE_KIND, eid) 153 entity = datastore.Entity( 154 key=key, 155 exclude_from_indexes=['AttributeData'], 156 ) 157 entity['AttributeData'] = attr.SerializeToString() 158 entity['Updated'] = datetime.datetime.now() 159 160 logging.info('update_dut_attribute: putting entity into datastore for %s', 161 eid) 162 client.put(entity) 163 164 165def handle_device_stability_list(dev_stab_list_path, client): 166 """Take a path to a DeviceStabilityList, iterate through the list and store 167 into UFS datastore based on env. 168 """ 169 dev_stab_list = io_utils.read_json_proto( 170 protodb.GetSymbol(DEV_STAB_INPUT_TYPE)(), dev_stab_list_path) 171 172 all_boards = {} 173 for dev_stab in dev_stab_list.values: 174 update_device_stability(dev_stab, client) 175 for eid in dev_stab.dut_criteria[0].values: 176 all_boards[eid] = True 177 178 clean_up_device_stability(all_boards, client) 179 180 181def clean_up_device_stability(all_boards, client): 182 """Clean up boards/models that are deleted from device stability config files""" 183 query = client.query(kind=DEVICE_STABILITY_KIND) 184 query.keys_only() 185 to_delete_keys = [] 186 for record in query.fetch(): 187 if record.key.name not in all_boards: 188 to_delete_keys.append(record.key) 189 190 client.delete_multi(to_delete_keys) 191 192 193def update_device_stability(dev_stab, client): 194 """Take a DeviceStability and store it in the UFS datastore as a 195 DeviceStabilityEntity. 196 """ 197 # TODO (justinsuen): May need to change this eventually. This assumes the use 198 # of a single model (DutAttribute ID design_id) when defining a 199 # DeviceStability entry. 200 # http://cs/chromeos_internal/infra/config/testingconfig/target_test_requirements_config_helper.star?l=155 201 for eid in dev_stab.dut_criteria[0].values: 202 logging.info('update_device_stability: handling %s', eid) 203 204 key = client.key(DEVICE_STABILITY_KIND, eid) 205 entity = datastore.Entity( 206 key=key, 207 exclude_from_indexes=['StabilityData'], 208 ) 209 entity['StabilityData'] = dev_stab.SerializeToString() 210 entity['Updated'] = datetime.datetime.now() 211 212 logging.info( 213 'update_device_stability: putting entity into datastore for %s', eid) 214 client.put(entity) 215 216 217if __name__ == '__main__': 218 logging.basicConfig(level=logging.INFO) 219 parser = argparse.ArgumentParser( 220 description=__doc__, 221 formatter_class=argparse.RawDescriptionHelpFormatter, 222 ) 223 224 parser.add_argument( 225 '--env', 226 type=str, 227 default='dev', 228 help='environment flag for UFS service', 229 ) 230 231 # load database of protobuffer name -> Type 232 protodb = proto_utils.create_symbol_db() 233 options = parser.parse_args() 234 ufs_ds_client = datastore.Client( 235 project=get_ufs_project(options.env), 236 namespace="os", 237 ) 238 script_dir = os.path.dirname(os.path.realpath(__file__)) 239 240 handle_config_bundle_list("hw_design/generated/configs.jsonproto", 241 ufs_ds_client) 242 handle_dut_attribute_list( 243 os.path.realpath( 244 os.path.join( 245 script_dir, 246 "../generated/dut_attributes.jsonproto", 247 )), ufs_ds_client) 248 handle_flat_config_list("hw_design/generated/flattened.jsonproto", 249 ufs_ds_client) 250 handle_device_stability_list( 251 os.path.realpath( 252 os.path.join( 253 script_dir, 254 "../../config-internal/board_config/generated/device_stability.cfg" 255 )), ufs_ds_client) 256