Skip to content

Commit 86e4610

Browse files
committed
factor out test logic into separate module
Signed-off-by: Matthias Büchse <[email protected]>
1 parent 57da47c commit 86e4610

File tree

2 files changed

+93
-163
lines changed

2 files changed

+93
-163
lines changed

Tests/iaas/flavor-naming/flavor-names-openstack.py

Lines changed: 11 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -21,171 +21,25 @@
2121
import sys
2222
import typing
2323
import getopt
24+
2425
import openstack
2526

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
2729

2830

2931
logger = logging.getLogger(__name__)
3032

3133

3234
def usage(rcode=1):
33-
"help output"
35+
"""help output"""
3436
print("Usage: flavor-names-openstack.py [options]", file=sys.stderr)
3537
print("Options: [-c/--os-cloud OS_CLOUD] sets cloud environment (default from OS_CLOUD env)", file=sys.stderr)
3638
print("This tool retrieves the list of flavors from the OpenStack cloud OS_CLOUD", file=sys.stderr)
3739
print(" and reports inconsistencies, errors etc. It returns 0 on success.", file=sys.stderr)
3840
sys.exit(rcode)
3941

4042

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-
18943
def main(argv):
19044
"""Entry point -- main loop going over flavors"""
19145
# configure logging, disable verbose library logging
@@ -235,19 +89,13 @@ def main(argv):
23589
print("CRITICAL: You need to have OS_CLOUD set or pass --os-cloud=CLOUD.", file=sys.stderr)
23690
sys.exit(1)
23791

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)])
25199
return 0
252100

253101

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python3
2+
# vim: set ts=4 sw=4 et:
3+
4+
import logging
5+
import typing
6+
7+
import openstack
8+
9+
import flavor_names
10+
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
TESTCASES = ('scs-0100-syntax-check', 'scs-0100-semantics-check', 'flavor-name-check')
16+
STRATEGY = flavor_names.ParsingStrategy(
17+
vstr='v3',
18+
parsers=(flavor_names.parser_v3, ),
19+
tolerated_parsers=(flavor_names.parser_v2, flavor_names.parser_v1),
20+
)
21+
ACC_DISK = (0, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
22+
23+
24+
def compute_scs_flavors(flavors: typing.List[openstack.compute.v2.flavor.Flavor], parser=STRATEGY) -> list:
25+
result = []
26+
for flv in flavors:
27+
if not flv.name or flv.name[:4] != 'SCS-':
28+
continue # not an SCS flavor; none of our business
29+
try:
30+
flavorname = parser(flv.name)
31+
except ValueError as exc:
32+
logger.info(f"error parsing {flv.name}: {exc}")
33+
flavorname = None
34+
result.append((flv, flavorname))
35+
return result
36+
37+
38+
def compute_scs_0100_syntax_check(scs_flavors: list) -> bool:
39+
problems = [flv.name for flv, flavorname in scs_flavors if not flavorname]
40+
if problems:
41+
logger.error(f"scs-100-syntax-check: flavor(s) failed: {', '.join(sorted(problems))}")
42+
return not problems
43+
44+
45+
def compute_scs_0100_semantics_check(scs_flavors: list) -> bool:
46+
problems = set()
47+
for flv, flavorname in scs_flavors:
48+
if not flavorname:
49+
continue # this case is handled by syntax check
50+
cpuram = flavorname.cpuram
51+
if flv.vcpus < cpuram.cpus:
52+
logger.info(f"Flavor {flv.name} CPU overpromise: {flv.vcpus} < {cpuram.cpus}")
53+
problems.add(flv.name)
54+
elif flv.vcpus > cpuram.cpus:
55+
logger.info(f"Flavor {flv.name} CPU underpromise: {flv.vcpus} > {cpuram.cpus}")
56+
# RAM
57+
flvram = int((flv.ram + 51) / 102.4) / 10
58+
# Warn for strange sizes (want integer numbers, half allowed for < 10GiB)
59+
if flvram >= 10 and flvram != int(flvram) or flvram * 2 != int(flvram * 2):
60+
logger.info(f"Flavor {flv.name} uses discouraged uneven size of memory {flvram:.1f} GiB")
61+
if flvram < cpuram.ram:
62+
logger.info(f"Flavor {flv.name} RAM overpromise {flvram:.1f} < {cpuram.ram:.1f}")
63+
problems.add(flv.name)
64+
elif flvram > cpuram.ram:
65+
logger.info(f"Flavor {flv.name} RAM underpromise {flvram:.1f} > {cpuram.ram:.1f}")
66+
# Disk could have been omitted
67+
disksize = flavorname.disk.disksize if flavorname.disk else 0
68+
# We have a recommendation for disk size steps
69+
if disksize not in ACC_DISK:
70+
logger.info(f"Flavor {flv.name} non-standard disk size {disksize}, should have (5, 10, 20, 50, 100, 200, ...)")
71+
if flv.disk < disksize:
72+
logger.info(f"Flavor {flv.name} disk overpromise {flv.disk} < {disksize}")
73+
problems.add(flv.name)
74+
elif flv.disk > disksize:
75+
logger.info(f"Flavor {flv.name} disk underpromise {flv.disk} > {disksize}")
76+
if problems:
77+
logger.error(f"scs-100-semantics-check: flavor(s) failed: {', '.join(sorted(problems))}")
78+
return not problems
79+
80+
81+
def compute_flavor_name_check(syntax_check_result, semantics_check_result):
82+
return syntax_check_result and semantics_check_result

0 commit comments

Comments
 (0)