1# Copyright 2020 The ChromiumOS Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Constraint checks related to program and project ids. 5 6A note on how some of the checks relate: 7 8- check_design_config_id_segments: Checks that a project's ids fall within the 9segment specified in the program config. 10- check_design_config_id_segments_overlap: Checks that no id segments overlap. 11- check_design_config_ids_unique: Checks that ids within a project are unique. 12 13Together, these constraints enforce uniqueness across a program. If two ids 14within a project are the same, this is rejected by 15check_design_config_ids_unique. If two ids in different projects are the same, 16at least one of them must be out of the specified segment, because no two 17segments can overlap. 18""" 19 20import itertools 21import logging 22import pathlib 23 24from checker import constraint_suite 25 26from chromiumos.config.payload import config_bundle_pb2 27 28 29class IdConstraintSuite(constraint_suite.ConstraintSuite): 30 """Constraint checks related to program and project ids.""" 31 32 def check_ids_consistent( 33 self, 34 program_config: config_bundle_pb2.ConfigBundle, 35 project_config: config_bundle_pb2.ConfigBundle, 36 factory_dir: pathlib.Path, 37 ): 38 """Checks all project ids are consistent with the program.""" 39 del factory_dir 40 program_ids = [program.id.value for program in program_config.program_list] 41 for design in project_config.design_list: 42 self.assertIn(design.program_id.value, program_ids) 43 44 def check_design_config_id_segments( 45 self, 46 program_config: config_bundle_pb2.ConfigBundle, 47 project_config: config_bundle_pb2.ConfigBundle, 48 factory_dir: pathlib.Path, 49 ): 50 """Check that all DesignConfigIds fall within their segment.""" 51 del factory_dir 52 segment_map = {} 53 for program in program_config.program_list: 54 for segment in program.design_config_id_segments: 55 segment_map[segment.design_id.value] = segment 56 57 for design in project_config.design_list: 58 # It is valid for designs to not have a corresponding segment. 59 segment = segment_map.get(design.id.value) 60 if not segment: 61 logging.warning( 62 'No DesignConfigIdSegment found for design %s, constraints on ids ' 63 'will not be enforced', 64 design.id.value, 65 ) 66 continue 67 68 self.assertLess(segment.min_id, segment.max_id) 69 70 for config in design.configs: 71 # DesignConfigIds should have the form "<name>:<id>" 72 _, id_num = config.id.value.split(':') 73 id_num = int(id_num) 74 75 # Unprovisioned config ids are exempt from the check. 76 if id_num == 0x7FFFFFFF: 77 continue 78 79 self.assertGreaterEqual( 80 id_num, segment.min_id, 81 'DesignConfigId must be >= {}, got {}'.format( 82 segment.min_id, id_num)) 83 self.assertLessEqual( 84 id_num, segment.max_id, 85 'DesignConfigId must be <= {}, got {}'.format( 86 segment.max_id, id_num)) 87 88 def check_design_config_id_segments_overlap( 89 self, 90 program_config: config_bundle_pb2.ConfigBundle, 91 project_config: config_bundle_pb2.ConfigBundle, 92 factory_dir: pathlib.Path, 93 ): 94 """Check that no DesignConfigIdSegments overlap.""" 95 del project_config, factory_dir 96 programs = program_config.program_list 97 # Flatten design config ID segments across programs 98 design_config_id_segments = itertools.chain.from_iterable( 99 program.design_config_id_segments for program in programs) 100 # Get all pairs as permutations, so we can assume that one segment is lower 101 # than the other. 102 for seg_a, seg_b in itertools.permutations(design_config_id_segments, 2): 103 error_message = 'Segments {} and {} overlap'.format(seg_a, seg_b) 104 105 # Segment min_ids can never be equal. 106 self.assertNotEqual(seg_a.min_id, seg_b.min_id, error_message) 107 108 # Only check the case where a's min_id is lower than b's min_id; the other 109 # case will be checked by the opposite permutation. 110 if seg_a.min_id < seg_b.min_id: 111 # If a's min_id is lower than b's, a's max id must be lower than b's 112 # min_id. 113 self.assertLess(seg_a.max_id, seg_b.min_id, error_message) 114 115 def check_design_config_ids_unique( 116 self, 117 program_config: config_bundle_pb2.ConfigBundle, 118 project_config: config_bundle_pb2.ConfigBundle, 119 factory_dir: pathlib.Path, 120 ): 121 """Checks all project DesignConfigIds are unique.""" 122 del program_config, factory_dir 123 124 design_config_ids = set() 125 for design in project_config.design_list: 126 for config in design.configs: 127 self.assertNotIn( 128 config.id.value, design_config_ids, 129 "Found multiple configs with id '{}'".format(config.id.value)) 130 design_config_ids.add(config.id.value) 131