def make_quota_contract(gcp_agent, project_quota, regions): """Create a json_contract.Contract that checks GCP quota. Args: gcp_agent: [GcpAgent] Observation agent on the desired project. project_quota: [dict] Minimum desired values keyed by quota metric for the observed project. regions: [array of (name, dict) tuple]: A list of regions and their individual quotas to check. Returns: json_contract.Contract that will test the project quota. """ quotas_field = 'quotas' + DONT_ENUMERATE_TERMINAL builder = GcpContractBuilder(gcp_agent) (builder.new_clause_builder('Has Project Quota').inspect_resource( 'projects', resource_id=gcp_agent.default_variables['project']).add_constraint( PathPredicate(quotas_field, QuotaPredicate(project_quota)))) for region, quota in regions: (builder.new_clause_builder( 'Has Regional Quota for {0}'.format(region)).inspect_resource( 'regions', region).add_constraint( PathPredicate(quotas_field, QuotaPredicate(quota)))) return builder.build()
def test_collect_from_nested_list_found(self): # """Ambiguous path through nested lists.""" context = ExecutionContext() source = {'outer': [_LETTER_DICT, _NUMBER_DICT]} pred = PathPredicate(PATH_SEP.join(['outer', 'a'])) values = pred(context, source) self.assertEqual([ PathValue(PATH_SEP.join(['outer[0]', 'a']), 'A'), PathValue(PATH_SEP.join(['outer[1]', 'a']), 1) ], values.path_values) pred = PathPredicate(PATH_SEP.join(['outer', 'z'])) values = pred(context, source) self.assertEqual([PathValue(PATH_SEP.join(['outer[0]', 'z']), 'Z')], values.path_values) self.assertEqual([ MissingPathError(_NUMBER_DICT, 'z', path_value=PathValue('outer[1]', _NUMBER_DICT)) ], values.path_failures) pred = PathPredicate(PATH_SEP.join(['outer', 'three'])) values = pred(context, source) self.assertEqual([PathValue(PATH_SEP.join(['outer[1]', 'three']), 3)], values.path_values) self.assertEqual([ MissingPathError(_LETTER_DICT, 'three', path_value=PathValue('outer[0]', _LETTER_DICT)) ], values.path_failures)
def test_collect_from_list_identity(self): context = ExecutionContext() letters = ['A', 'B', 'C'] pred = PathPredicate('') values = pred(context, letters) self.assertEqual([ PathValue('[0]', 'A'), PathValue('[1]', 'B'), PathValue('[2]', 'C') ], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate(DONT_ENUMERATE_TERMINAL) values = pred(context, letters) self.assertEqual([PathValue('', letters)], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate(PATH_SEP) values = pred(context, letters) self.assertEqual([ PathValue('[0]', 'A'), PathValue('[1]', 'B'), PathValue('[2]', 'C') ], values.path_values) self.assertEqual([], values.path_failures)
def test_indirect_path(self): context = ExecutionContext(test_path='b') source = _LETTER_DICT indirect_result = PathPredicate(lambda x: x['test_path'])(context, source) self.assertTrue(indirect_result) direct_result = PathPredicate('b')(context, source) self.assertEqual(direct_result.path_values, indirect_result.path_values)
def test_collect_from_list_found(self): # """Ambiguous path passes through a list element.""" source = [_LETTER_DICT] pred = PathPredicate('a') values = pred(source) self.assertEqual([PathValue(PATH_SEP.join(['[0]', 'a']), 'A')], values.path_values) pred = PathPredicate('b') values = pred(source) self.assertEqual([PathValue(PATH_SEP.join(['[0]', 'b']), 'B')], values.path_values) self.assertEqual([], values.path_failures)
def test_collect_from_nested_dict_found(self): # """Nested dictionary attribute lookup.""" source = {'outer': {'inner': _LETTER_DICT}} pred = PathPredicate(PATH_SEP.join(['outer', 'inner', 'a'])) values = pred(source) self.assertEqual([PathValue(pred.path, 'A')], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate(PATH_SEP.join(['outer', 'inner', 'b'])) values = pred(source) self.assertEqual([PathValue(pred.path, 'B')], values.path_values) self.assertEqual([], values.path_failures)
def test_collect_from_dict_found(self): # """Normal dictionary attribute lookup.""" source = _LETTER_DICT pred = PathPredicate('a') values = pred(source) self.assertEqual([PathValue('a', 'A')], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate('b') values = pred(source) self.assertEqual([PathValue('b', 'B')], values.path_values) self.assertEqual([], values.path_failures)
def test_collect_plain_terminal_list(self): # """Path to a value that is a list.""" source = {'a': [_LETTER_DICT]} pred = PathPredicate('a' + DONT_ENUMERATE_TERMINAL) values = pred(source) self.assertEqual([PathValue('a', [_LETTER_DICT])], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate(PATH_SEP.join(['a', 'a'])) values = pred(source) self.assertEqual([PathValue(PATH_SEP.join(['a[0]', 'a']), 'A')], values.path_values) self.assertEqual([], values.path_failures)
def test_collect_filter_good(self): source = {'outer': [_LETTER_DICT, _NUMBER_DICT]} filter_pred = TestEqualsPredicate(2) pred = PathPredicate(PATH_SEP.join(['outer', 'b']), pred=filter_pred) # This is a precise test against the exact result returned. builder = PathPredicateResultBuilder(source, pred) path_0 = PATH_SEP.join(['outer[0]', 'b']) path_1 = PATH_SEP.join(['outer[1]', 'b']) bad_result = PathValueResult(source=source, target_path=pred.path, path_value=PathValue(path_0, 'B'), valid=False, pred=filter_pred) good_result = PathValueResult(source=source, target_path=pred.path, path_value=PathValue(path_1, 2), valid=True, pred=filter_pred) builder.add_result_candidate(PathValue(path_0, 'B'), bad_result) builder.add_result_candidate(PathValue(path_1, 2), good_result) expect = builder.build(True) pred_result = pred(source) self.assertEqual([PathValue(PATH_SEP.join(['outer[1]', 'b']), 2)], pred_result.path_values) self.assertEqual(expect, pred_result) self.assertEqual( [jp.PathPredicateResultCandidate(PathValue(path_0, 'B'), bad_result)], pred_result.invalid_candidates) self.assertEqual( [jp.PathPredicateResultCandidate(PathValue(path_1, 2), good_result)], pred_result.valid_candidates) self.assertEqual([], pred_result.path_failures)
def excludes_path_pred(self, path, pred, max=0, **kwargs): enumerate_terminals = kwargs.pop('enumerate_terminals', True) self.add_constraint( CardinalityPredicate( PathPredicate( path, pred, enumerate_terminals=enumerate_terminals), min=0, max=max)) return self
def contains_path_pred(self, path, pred, min=1, max=None, **kwargs): enumerate_terminals = kwargs.pop('enumerate_terminals', True) self.add_constraint( CardinalityPredicate( PathPredicate( path, pred, enumerate_terminals=enumerate_terminals), min=min, max=max)) return self
def test_collect_from_list_with_index(self): # """Path with explicit list indexes to traverse.""" source = [_LETTER_DICT, _NUMBER_DICT] pred = PathPredicate('[0]') values = pred(source) self.assertEqual([PathValue('[0]', _LETTER_DICT)], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate('[1]') values = pred(source) self.assertEqual([PathValue('[1]', _NUMBER_DICT)], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate(PATH_SEP.join(['[1]', 'a'])) values = pred(source) self.assertEqual([PathValue('[1]/a', 1)], values.path_values) self.assertEqual([], values.path_failures)
def test_collect_from_dict_not_found(self): # """Normal dictionary attribute lookup with missing attribute.""" source = _LETTER_DICT pred = PathPredicate('Z') values = pred(source) self.assertEqual([], values.path_values) self.assertEqual([MissingPathError(_LETTER_DICT, 'Z', path_value=('', _LETTER_DICT))], values.path_failures)
def test_path_predicate_result_builder(self): source = {} for valid in [True, False]: for path_pred in [PathPredicate('x'), None]: builder = PathPredicateResultBuilder(source, path_pred) self.assertEqual(PathPredicateResult(valid, path_pred, source, valid_candidates=[], invalid_candidates=[]), builder.build(valid))
def test_collect_from_nested_dict_not_found(self): # """Nested dictionary attribute lookup with missing element.""" source = _LETTER_DICT pred = PathPredicate(PATH_SEP.join(['a', 'b'])) values = pred(source) self.assertEqual([], values.path_values) self.assertEqual( [MissingPathError('A', 'b', path_value=PathValue('a', 'A'))], values.path_failures)
def test_collect_enumerated_terminal_list(self): # """Enumerated path to a value that is a list.""" array = ['A', 'B', 'C'] source = {'a': array} pred = PathPredicate('a' + PATH_SEP) values = pred(source) self.assertEqual([PathValue('a[0]', 'A'), PathValue('a[1]', 'B'), PathValue('a[2]', 'C')], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate('a') values = pred(source) self.assertEqual([PathValue('a[0]', 'A'), PathValue('a[1]', 'B'), PathValue('a[2]', 'C')], values.path_values) self.assertEqual([], values.path_failures)
def test_collect_from_dict_identity(self): source = _LETTER_DICT pred = PathPredicate('') values = pred(source) builder = PathPredicateResultBuilder(source, pred) builder.add_result_candidate( PathValue('', source), PathValueResult(source=source, target_path='', path_value=PathValue('', source), valid=True)) self.assertEqual(builder.build(True), values) self.assertEqual([PathValue('', source)], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate('/') values = pred(source) self.assertEqual([PathValue('', source)], values.path_values) self.assertEqual([], values.path_failures)
def test_collect_from_list_not_found(self): # """Ambiguous path passes through a list element but cannot be resolved.""" source = [_LETTER_DICT] pred = PathPredicate('Z') values = pred(source) self.assertEqual([], values.path_values) self.assertEqual( [MissingPathError( _LETTER_DICT, 'Z', path_value=PathValue('[0]', _LETTER_DICT))], values.path_failures)
def test_path_predicate_eq(self): pred_a = PathPredicate('x') pred_b = PathPredicate('y') self.assertNotEqual(pred_a, pred_b) pred_a = PathPredicate('x') pred_b = PathPredicate('x') self.assertEqual(pred_a, pred_b) pred_a = PathPredicate('x', pred=PathPredicate('X')) self.assertNotEqual(pred_a, pred_b) pred_b = PathPredicate('x', pred=PathPredicate('X')) self.assertEqual(pred_a, pred_b)
def test_collect_from_nested_list_not_found(self): # """Path through nested lists that cannot be resolved.""" source = {'outer': [_LETTER_DICT, _NUMBER_DICT]} pred = PathPredicate(PATH_SEP.join(['outer', 'X'])) values = pred(source) self.assertEqual([], values.path_values) self.assertEqual( [MissingPathError( _LETTER_DICT, 'X', path_value=PathValue('outer[0]', _LETTER_DICT)), MissingPathError( _NUMBER_DICT, 'X', path_value=PathValue('outer[1]', _NUMBER_DICT))], values.path_failures)
def make_quota_result(context, valid, source, require, metric): """Construct the values returned by QuotaPredicate. These are what is currently implemented, but the current implementation is not correct because it uses PathValue of made up paths rather than expressing that we are looking for the difference among two paths. """ diff = FieldDifference('limit', 'usage') for value in source: if value['metric'] == metric: pred = PathPredicate('', pred=NUM_GE(require[metric]), transform=diff) return pred(context, value) raise ValueError('Missing metric')
def test_collect_from_dict_transform(self): context = ExecutionContext() source = {'a': 7, 'b': 4} pred = PathPredicate('', transform=lambda ctxt, val: val['a'] - val['b']) expect = 7 - 4 values = pred(context, source) builder = PathPredicateResultBuilder(source, pred) builder.add_result_candidate( PathValue('', source), PathValueResult(source=source, target_path='', path_value=PathValue('', expect), valid=True)) self.assertEqual(builder.build(True), values)
def __call__(self, context, value): """Implements ValuePredicate. Args: context: [ExecutionContext] The execution context to evaluate within. values: [array of dict] A list of dictionary resource quota descriptions following the format returned described by the "quotas" attribute of https://cloud.google.com/compute/docs/reference/latest/projects#resource """ builder = KeyedPredicateResultBuilder(self) # NOTE(20180710): # b/111302333: get() because 'metric' is not always provided. dictified = {elem.get('metric'): elem for elem in value} bad_metrics = [] for metric, expect in self.__minimum_quota.items(): found = dictified.get(metric) if found is None: bad_metrics.append(metric) builder.add_result( metric, PathValueResult(source=None, target_path='metric', path_value=PathValue('', value), valid=False, pred=EQUIVALENT(metric), comment='Missing metric value.')) continue pred = PathPredicate('', pred=NUM_GE(expect), transform=self.__diff) result = pred(context, found) builder.add_result(metric, result) if not result: bad_metrics.append(metric) if bad_metrics: builder.comment = 'Insufficient {0}.'.format( ' and '.join(bad_metrics)) valid = False else: valid = True builder.comment = 'Satisfied all {0} metrics.'.format( len(self.__minimum_quota)) return builder.build(valid)
def test_collect_from_list_of_list_with_index(self): # """Path with explicit list indexes to traverse through nested lists.""" context = ExecutionContext() upper = ['A', 'B', 'C'] lower = ['a', 'b', 'c'] letters = [upper, lower] arabic = [1, 2, 3] roman = ['i', 'ii', 'iii'] numbers = [arabic, roman] source = [letters, numbers] # By default, values that are lists get expanded (one level) pred = PathPredicate('[1]') values = pred(context, source) self.assertEquals( [PathValue('[1][0]', arabic), PathValue('[1][1]', roman)], values.path_values) self.assertEqual([], values.path_failures) # If we dont want to expand, then decorate with DONT_ENUMERATE_TERMINAL. pred = PathPredicate('[0]' + DONT_ENUMERATE_TERMINAL) values = pred(context, source) self.assertEqual([PathValue('[0]', letters)], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate('[1]' + DONT_ENUMERATE_TERMINAL) values = pred(context, source) self.assertEqual([PathValue('[1]', numbers)], values.path_values) self.assertEqual([], values.path_failures) # Go out one more level pred = PathPredicate('[1][0]' + DONT_ENUMERATE_TERMINAL) values = pred(context, source) self.assertEqual([PathValue('[1][0]', arabic)], values.path_values) self.assertEqual([], values.path_failures) pred = PathPredicate('[1][0]') values = pred(context, source) self.assertEqual([ PathValue('[1][0][0]', 1), PathValue('[1][0][1]', 2), PathValue('[1][0][2]', 3) ], values.path_values) self.assertEqual([], values.path_failures) # Go all the way down. pred = PathPredicate('[1][0][2]') values = pred(context, source) self.assertEqual([PathValue('[1][0][2]', 3)], values.path_values) self.assertEqual([], values.path_failures)
def excludes_match(self, match_spec, max=0, **kwargs): match_kwargs = kwargs.pop('match_kwargs', {}) if isinstance(match_spec, list): enumerate_terminals = kwargs.pop('enumerate_terminals', False) constraint = LIST_MATCHES(match_spec, **match_kwargs) self.add_constraint( CardinalityPredicate( PathPredicate( '', constraint, enumerate_terminals=enumerate_terminals), min=0, max=max)) elif isinstance(match_spec, dict): self.add_constraint( CardinalityPredicate( DICT_MATCHES(match_spec, **match_kwargs), min=0, max=max)) else: raise ValueError('match_spec must be a dict or list, not a {0}' .format(match_spec.__class__.__name__)) return self
def test_path_conjunction(self): context = ExecutionContext() text = 'x=X, a=A, b=B, z=Z' aA = jp.STR_SUBSTR('a=A') bB = jp.STR_SUBSTR('b=B') conjunction = jp.AND([aA, bB]) pred = PathPredicate('value', conjunction) source = {'value': text, 'wrong': 'a=A', 'another': 'b=B'} conjunction_result = conjunction(context, text) builder = PathPredicateResultBuilder(source, pred) builder.add_result_candidate( PathValue('value', text), conjunction_result.clone_with_source(source=source, base_target_path='value', base_value_path='value')) expect = builder.build(True) pred_result = pred(context, source) self.assertEqual(expect, pred_result)
def test_path_predicate_result_eq(self): source = {} path_pred = PathPredicate('x') a_result = path_pred(source) # dont care, just something well formed. result_a = PathPredicateResult( True, path_pred, source, valid_candidates=[], invalid_candidates=[]) result_b = PathPredicateResult( True, path_pred, source, valid_candidates=[], invalid_candidates=[]) self.assertEqual(result_a, result_b) # Only valid different. result_b = PathPredicateResult( False, path_pred, source, valid_candidates=[], invalid_candidates=[]) self.assertNotEqual(result_a, result_b) # Only filter different. result_b = PathPredicateResult( True, PathPredicate('y'), source, valid_candidates=[], invalid_candidates=[]) self.assertNotEqual(result_a, result_b) # Only source different. result_b = PathPredicateResult( True, path_pred, [source], valid_candidates=[], invalid_candidates=[]) self.assertNotEqual(result_a, result_b) # Only valid candidates different. result_b = PathPredicateResult( True, path_pred, source, valid_candidates=[ PathPredicateResultCandidate(PathValue('x', 0), a_result)], invalid_candidates=[]) self.assertNotEqual(result_a, result_b) # Only invalid candidates different. result_b = PathPredicateResult( True, path_pred, source, valid_candidates=[], invalid_candidates=[ PathPredicateResultCandidate(PathValue('x', 0), a_result)]) self.assertNotEqual(result_a, result_b) # More fully defined equal. result_a = PathPredicateResult( True, path_pred, source, valid_candidates=[ PathPredicateResultCandidate(PathValue('x', 0), a_result)], invalid_candidates=[ PathPredicateResultCandidate(PathValue('x', 0), a_result)]) result_b = PathPredicateResult( True, path_pred, source, valid_candidates=[ PathPredicateResultCandidate(PathValue('x', 0), a_result)], invalid_candidates=[ PathPredicateResultCandidate(PathValue('x', 0), a_result)]) self.assertEqual(result_a, result_b)