• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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