Coverage for nova/api/validation/extra_specs/base.py: 91%

58 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +0000

1# Copyright 2020 Red Hat, Inc. All rights reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); you may 

4# not use this file except in compliance with the License. You may obtain 

5# a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15import dataclasses 

16import re 

17import typing as ty 

18 

19from oslo_utils import strutils 

20 

21from nova import exception 

22 

23 

24@dataclasses.dataclass 

25class ExtraSpecValidator: 

26 name: str 

27 description: str 

28 value: ty.Dict[str, ty.Any] 

29 deprecated: bool = False 

30 parameters: ty.List[ty.Dict[str, ty.Any]] = dataclasses.field( 

31 default_factory=list 

32 ) 

33 

34 name_regex: str = None 

35 value_regex: str = None 

36 

37 def __post_init__(self): 

38 # generate a regex for the name 

39 

40 name_regex = self.name 

41 # replace the human-readable patterns with named regex groups; this 

42 # will transform e.g. 'hw:numa_cpus.{id}' to 'hw:numa_cpus.(?P<id>\d+)' 

43 for param in self.parameters: 

44 pattern = f'(?P<{param["name"]}>{param["pattern"]})' 

45 name_regex = name_regex.replace(f'{ {param["name"]}} ', pattern) 

46 

47 self.name_regex = name_regex 

48 

49 # ...and do the same for the value, but only if we're using strings 

50 

51 if self.value['type'] not in (int, str, bool): 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true

52 raise ValueError( 

53 f"Unsupported parameter type '{self.value['type']}'" 

54 ) 

55 

56 value_regex = None 

57 if self.value['type'] == str and self.value.get('pattern'): 

58 value_regex = self.value['pattern'] 

59 

60 self.value_regex = value_regex 

61 

62 def _validate_str(self, value): 

63 if 'pattern' in self.value: 

64 value_match = re.fullmatch(self.value_regex, value) 

65 if not value_match: 

66 raise exception.ValidationError( 

67 f"Validation failed; '{value}' is not of the format " 

68 f"'{self.value_regex}'." 

69 ) 

70 elif 'enum' in self.value: 70 ↛ exitline 70 didn't return from function '_validate_str' because the condition on line 70 was always true

71 if value not in self.value['enum']: 

72 values = ', '.join(str(x) for x in self.value['enum']) 

73 raise exception.ValidationError( 

74 f"Validation failed; '{value}' is not one of: {values}." 

75 ) 

76 

77 def _validate_int(self, value): 

78 try: 

79 value = int(value) 

80 except ValueError: 

81 raise exception.ValidationError( 

82 f"Validation failed; '{value}' is not a valid integer value." 

83 ) 

84 

85 if 'max' in self.value and self.value['max'] < value: 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true

86 raise exception.ValidationError( 

87 f"Validation failed; '{value}' is greater than the max value " 

88 f"of '{self.value['max']}'." 

89 ) 

90 

91 if 'min' in self.value and self.value['min'] > value: 

92 raise exception.ValidationError( 

93 f"Validation failed; '{value}' is less than the min value " 

94 f"of '{self.value['min']}'." 

95 ) 

96 

97 def _validate_bool(self, value): 

98 try: 

99 strutils.bool_from_string(value, strict=True) 

100 except ValueError: 

101 raise exception.ValidationError( 

102 f"Validation failed; '{value}' is not a valid boolean-like " 

103 f"value." 

104 ) 

105 

106 def validate(self, name, value): 

107 name_match = re.fullmatch(self.name_regex, name) 

108 if not name_match: 108 ↛ 110line 108 didn't jump to line 110 because the condition on line 108 was never true

109 # NOTE(stephenfin): This is mainly here for testing purposes 

110 raise exception.ValidationError( 

111 f"Validation failed; expected a name of format '{self.name}' " 

112 f"but got '{name}'." 

113 ) 

114 

115 if self.value['type'] == int: 

116 self._validate_int(value) 

117 elif self.value['type'] == bool: 

118 self._validate_bool(value) 

119 else: # str 

120 self._validate_str(value)