|
21 | 21 | import sys |
22 | 22 | import typing |
23 | 23 | import getopt |
| 24 | + |
24 | 25 | import openstack |
25 | 26 |
|
26 | | -import flavor_names |
| 27 | +from flavor_names_check import \ |
| 28 | + compute_scs_flavors, compute_scs_0100_syntax_check, compute_scs_0100_semantics_check, compute_flavor_name_check |
27 | 29 |
|
28 | 30 |
|
29 | 31 | logger = logging.getLogger(__name__) |
30 | 32 |
|
31 | 33 |
|
32 | 34 | def usage(rcode=1): |
33 | | - "help output" |
| 35 | + """help output""" |
34 | 36 | print("Usage: flavor-names-openstack.py [options]", file=sys.stderr) |
35 | 37 | print("Options: [-c/--os-cloud OS_CLOUD] sets cloud environment (default from OS_CLOUD env)", file=sys.stderr) |
36 | 38 | print("This tool retrieves the list of flavors from the OpenStack cloud OS_CLOUD", file=sys.stderr) |
37 | 39 | print(" and reports inconsistencies, errors etc. It returns 0 on success.", file=sys.stderr) |
38 | 40 | sys.exit(rcode) |
39 | 41 |
|
40 | 42 |
|
41 | | -TESTCASES = ('scs-0100-syntax-check', 'scs-0100-semantics-check', 'flavor-name-check') |
42 | | -STRATEGY = flavor_names.ParsingStrategy( |
43 | | - vstr='v3', |
44 | | - parsers=(flavor_names.parser_v3, ), |
45 | | - tolerated_parsers=(flavor_names.parser_v2, flavor_names.parser_v1), |
46 | | -) |
47 | | -ACC_DISK = (0, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000) |
48 | | - |
49 | | - |
50 | | -def compute_scs_flavors(flavors: typing.List[openstack.compute.v2.flavor.Flavor], parser=STRATEGY) -> list: |
51 | | - result = [] |
52 | | - for flv in flavors: |
53 | | - if not flv.name or flv.name[:4] != 'SCS-': |
54 | | - continue # not an SCS flavor; none of our business |
55 | | - try: |
56 | | - flavorname = parser(flv.name) |
57 | | - except ValueError as exc: |
58 | | - logger.info(f"error parsing {flv.name}: {exc}") |
59 | | - flavorname = None |
60 | | - result.append((flv, flavorname)) |
61 | | - return result |
62 | | - |
63 | | - |
64 | | -def compute_scs_0100_syntax_check(scs_flavors: list) -> bool: |
65 | | - problems = [flv.name for flv, flavorname in scs_flavors if not flavorname] |
66 | | - if problems: |
67 | | - logger.error(f"scs-100-syntax-check: flavor(s) failed: {', '.join(sorted(problems))}") |
68 | | - return not problems |
69 | | - |
70 | | - |
71 | | -def compute_scs_0100_semantics_check(scs_flavors: list) -> bool: |
72 | | - problems = set() |
73 | | - for flv, flavorname in scs_flavors: |
74 | | - if not flavorname: |
75 | | - continue # this case is handled by syntax check |
76 | | - cpuram = flavorname.cpuram |
77 | | - if flv.vcpus < cpuram.cpus: |
78 | | - logger.info(f"Flavor {flv.name} CPU overpromise: {flv.vcpus} < {cpuram.cpus}") |
79 | | - problems.add(flv.name) |
80 | | - elif flv.vcpus > cpuram.cpus: |
81 | | - logger.info(f"Flavor {flv.name} CPU underpromise: {flv.vcpus} > {cpuram.cpus}") |
82 | | - # RAM |
83 | | - flvram = int((flv.ram + 51) / 102.4) / 10 |
84 | | - # Warn for strange sizes (want integer numbers, half allowed for < 10GiB) |
85 | | - if flvram >= 10 and flvram != int(flvram) or flvram * 2 != int(flvram * 2): |
86 | | - logger.info(f"Flavor {flv.name} uses discouraged uneven size of memory {flvram:.1f} GiB") |
87 | | - if flvram < cpuram.ram: |
88 | | - logger.info(f"Flavor {flv.name} RAM overpromise {flvram:.1f} < {cpuram.ram:.1f}") |
89 | | - problems.add(flv.name) |
90 | | - elif flvram > cpuram.ram: |
91 | | - logger.info(f"Flavor {flv.name} RAM underpromise {flvram:.1f} > {cpuram.ram:.1f}") |
92 | | - # Disk could have been omitted |
93 | | - disksize = flavorname.disk.disksize if flavorname.disk else 0 |
94 | | - # We have a recommendation for disk size steps |
95 | | - if disksize not in ACC_DISK: |
96 | | - logger.info(f"Flavor {flv.name} non-standard disk size {disksize}, should have (5, 10, 20, 50, 100, 200, ...)") |
97 | | - if flv.disk < disksize: |
98 | | - logger.info(f"Flavor {flv.name} disk overpromise {flv.disk} < {disksize}") |
99 | | - problems.add(flv.name) |
100 | | - elif flv.disk > disksize: |
101 | | - logger.info(f"Flavor {flv.name} disk underpromise {flv.disk} > {disksize}") |
102 | | - if problems: |
103 | | - logger.error(f"scs-100-semantics-check: flavor(s) failed: {', '.join(sorted(problems))}") |
104 | | - return not problems |
105 | | - |
106 | | - |
107 | | -def compute_flavor_name_check(syntax_check_result, semantics_check_result): |
108 | | - return syntax_check_result and semantics_check_result |
109 | | - |
110 | | - |
111 | | -# TODO see comment in main function about moving to another module |
112 | | - |
113 | | -class Container: |
114 | | - """ |
115 | | - This class does lazy evaluation and memoization. You register any potential value either |
116 | | - by giving the value directly, using `add_value`, or |
117 | | - by specifying how it is computed using `add_function`, |
118 | | - which expects a function that takes a container (so other values may be referred to). |
119 | | - In each case, you have to give the value a name. |
120 | | -
|
121 | | - The value will be available as a normal member variable under this name. |
122 | | - If given via a function, this function will only be evaluated when the value is accessed, |
123 | | - and the value will be memoized, so the function won't be called twice. |
124 | | - If the function raises an exception, then this will be memoized just as well. |
125 | | -
|
126 | | - For instance, |
127 | | -
|
128 | | - >>>> container = Container() |
129 | | - >>>> container.add_function('pi', lambda _: 22/7) |
130 | | - >>>> container.add_function('pi_squared', lambda c: c.pi * c.pi) |
131 | | - >>>> assert container.pi_squared == 22/7 * 22/7 |
132 | | - """ |
133 | | - def __init__(self): |
134 | | - self._values = {} |
135 | | - self._functions = {} |
136 | | - |
137 | | - def __getattr__(self, key): |
138 | | - val = self._values.get(key) |
139 | | - if val is None: |
140 | | - try: |
141 | | - ret = self._functions[key](self) |
142 | | - except BaseException as e: |
143 | | - val = (True, e) |
144 | | - else: |
145 | | - val = (False, ret) |
146 | | - self._values[key] = val |
147 | | - error, ret = val |
148 | | - if error: |
149 | | - raise ret |
150 | | - return ret |
151 | | - |
152 | | - def add_function(self, name, fn): |
153 | | - if name in self._functions: |
154 | | - raise RuntimeError(f"fn {name} already registered") |
155 | | - self._functions[name] = fn |
156 | | - |
157 | | - def add_value(self, name, value): |
158 | | - if name in self._values: |
159 | | - raise RuntimeError(f"value {name} already registered") |
160 | | - self._values[name] = value |
161 | | - |
162 | | - |
163 | | -# TODO see comment in main function about moving to another module |
164 | | - |
165 | | -def harness(name, *check_fns): |
166 | | - """Harness for evaluating testcase `name`. |
167 | | -
|
168 | | - Logs beginning of computation. |
169 | | - Calls each fn in `check_fns`. |
170 | | - Prints (to stdout) 'name: RESULT', where RESULT is one of |
171 | | -
|
172 | | - - 'ABORT' if an exception occurs during the function calls |
173 | | - - 'FAIL' if one of the functions has a falsy result |
174 | | - - 'PASS' otherwise |
175 | | - """ |
176 | | - logger.debug(f'** {name}') |
177 | | - try: |
178 | | - result = all(check_fn() for check_fn in check_fns) |
179 | | - except BaseException: |
180 | | - logger.debug('exception during check', exc_info=True) |
181 | | - result = 'ABORT' |
182 | | - else: |
183 | | - result = ['FAIL', 'PASS'][min(1, result)] |
184 | | - # this is quite redundant |
185 | | - # logger.debug(f'** computation end for {name}') |
186 | | - print(f"{name}: {result}") |
187 | | - |
188 | | - |
189 | 43 | def main(argv): |
190 | 44 | """Entry point -- main loop going over flavors""" |
191 | 45 | # configure logging, disable verbose library logging |
@@ -235,19 +89,13 @@ def main(argv): |
235 | 89 | print("CRITICAL: You need to have OS_CLOUD set or pass --os-cloud=CLOUD.", file=sys.stderr) |
236 | 90 | sys.exit(1) |
237 | 91 |
|
238 | | - # TODO in the future, the remainder should be moved to a central module `scs_compatible_iaas.py`, |
239 | | - # which would import the test logic (i.e., the functions called compute_XYZ) from here. |
240 | | - # Then this module wouldn't need to know about containers, and the central module can handle |
241 | | - # information sharing as well as running precisely the requested set of testcases. |
242 | | - c = Container() |
243 | | - c.add_function('conn', lambda _: openstack.connect(cloud=cloud, timeout=32)) |
244 | | - c.add_function('flavors', lambda c: list(c.conn.compute.flavors())) |
245 | | - c.add_function('scs_flavors', lambda c: compute_scs_flavors(c.flavors)) |
246 | | - c.add_function('scs_0100_syntax_check', lambda c: compute_scs_0100_syntax_check(c.scs_flavors)) |
247 | | - c.add_function('scs_0100_semantics_check', lambda c: compute_scs_0100_semantics_check(c.scs_flavors)) |
248 | | - c.add_function('flavor_name_check', lambda c: compute_flavor_name_check(c.scs_0100_syntax_check, c.scs_0100_semantics_check)) |
249 | | - for testcase in TESTCASES: |
250 | | - harness(testcase, lambda: getattr(c, testcase.replace('-', '_'))) |
| 92 | + with openstack.connect(cloud=cloud, timeout=32) as conn: |
| 93 | + scs_flavors = compute_scs_flavors(conn.compute.flavors()) |
| 94 | + result = compute_flavor_name_check( |
| 95 | + compute_scs_0100_syntax_check(scs_flavors), |
| 96 | + compute_scs_0100_semantics_check(scs_flavors), |
| 97 | + ) |
| 98 | + print("flavor-name-check: " + ('FAIL', 'PASS')[min(1, result)]) |
251 | 99 | return 0 |
252 | 100 |
|
253 | 101 |
|
|
0 commit comments