Skip to content

Commit 70d7d4c

Browse files
committed
record aborted testcases, part 1
Signed-off-by: Matthias Büchse <[email protected]>
1 parent 72e4f12 commit 70d7d4c

File tree

1 file changed

+174
-123
lines changed

1 file changed

+174
-123
lines changed

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

Lines changed: 174 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616
SPDX-License-Identifier: CC-BY-SA 4.0
1717
"""
1818

19+
import logging
1920
import os
2021
import sys
22+
import typing
2123
import getopt
22-
import yaml
2324
import openstack
2425

2526
import flavor_names
2627

2728

29+
logger = logging.getLogger(__name__)
30+
31+
2832
def usage(rcode=1):
2933
"help output"
3034
print("Usage: flavor-names-openstack.py [options]", file=sys.stderr)
@@ -41,11 +45,160 @@ def usage(rcode=1):
4145
sys.exit(rcode)
4246

4347

48+
TESTCASES = ('scs-0100-syntax-check', 'scs-0100-semantics-check', 'flavor-name-check')
49+
STRATEGY = flavor_names.ParsingStrategy(
50+
vstr='v3',
51+
parsers=(flavor_names.parser_v3, ),
52+
tolerated_parsers=(flavor_names.parser_v2, flavor_names.parser_v1),
53+
)
54+
ACC_DISK = (0, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
55+
56+
57+
def compute_scs_flavors(flavors: typing.List[openstack.compute.v2.flavor.Flavor], parser=STRATEGY) -> list:
58+
result = []
59+
for flv in flavors:
60+
if not flv.name or flv.name[:4] != 'SCS-':
61+
continue # not an SCS flavor; none of our business
62+
try:
63+
flavorname = parser(flv.name)
64+
except ValueError as exc:
65+
logger.info(f"error parsing {flv.name}: {exc}")
66+
flavorname = None
67+
result.append((flv, flavorname))
68+
return result
69+
70+
71+
def compute_scs_0100_syntax_check(scs_flavors: list) -> bool:
72+
problems = [flv.name for flv, flavorname in scs_flavors if not flavorname]
73+
if problems:
74+
logger.error(f"scs-100-syntax-check: flavor(s) failed: {', '.join(sorted(problems))}")
75+
return not problems
76+
77+
78+
def compute_scs_0100_semantics_check(scs_flavors: list) -> bool:
79+
problems = set()
80+
for flv, flavorname in scs_flavors:
81+
if not flavorname:
82+
continue # this case is handled by syntax check
83+
cpuram = flavorname.cpuram
84+
if flv.vcpus < cpuram.cpus:
85+
logger.info(f"Flavor {flv.name} CPU overpromise: {flv.vcpus} < {cpuram.cpus}")
86+
problems.add(flv.name)
87+
elif flv.vcpus > cpuram.cpus:
88+
logger.info(f"Flavor {flv.name} CPU underpromise: {flv.vcpus} > {cpuram.cpus}")
89+
# RAM
90+
flvram = int((flv.ram + 51) / 102.4) / 10
91+
# Warn for strange sizes (want integer numbers, half allowed for < 10GiB)
92+
if flvram >= 10 and flvram != int(flvram) or flvram * 2 != int(flvram * 2):
93+
logger.info(f"Flavor {flv.name} uses discouraged uneven size of memory {flvram:.1f} GiB")
94+
if flvram < cpuram.ram:
95+
logger.info(f"Flavor {flv.name} RAM overpromise {flvram:.1f} < {cpuram.ram:.1f}")
96+
problems.add(flv.name)
97+
elif flvram > cpuram.ram:
98+
logger.info(f"Flavor {flv.name} RAM underpromise {flvram:.1f} > {cpuram.ram:.1f}")
99+
# Disk could have been omitted
100+
disksize = flavorname.disk.disksize if flavorname.disk else 0
101+
# We have a recommendation for disk size steps
102+
if disksize not in ACC_DISK:
103+
logger.info(f"Flavor {flv.name} non-standard disk size {disksize}, should have (5, 10, 20, 50, 100, 200, ...)")
104+
if flv.disk < disksize:
105+
logger.info(f"Flavor {flv.name} disk overpromise {flv.disk} < {disksize}")
106+
problems.add(flv.name)
107+
elif flv.disk > disksize:
108+
logger.info(f"Flavor {flv.name} disk underpromise {flv.disk} > {disksize}")
109+
if problems:
110+
logger.error(f"scs-100-semantics-check: flavor(s) failed: {', '.join(sorted(problems))}")
111+
return not problems
112+
113+
114+
def compute_flavor_name_check(syntax_check_result, semantics_check_result):
115+
return syntax_check_result and semantics_check_result
116+
117+
118+
# TODO see comment in main function about moving to another module
119+
120+
class Container:
121+
"""
122+
This class does lazy evaluation and memoization. You register any potential value either
123+
by giving the value directly, using `add_value`, or
124+
by specifying how it is computed using `add_function`,
125+
which expects a function that takes a container (so other values may be referred to).
126+
In each case, you have to give the value a name.
127+
128+
The value will be available as a normal member variable under this name.
129+
If given via a function, this function will only be evaluated when the value is accessed,
130+
and the value will be memoized, so the function won't be called twice.
131+
If the function raises an exception, then this will be memoized just as well.
132+
133+
For instance,
134+
135+
>>>> container = Container()
136+
>>>> container.add_function('pi', lambda _: 22/7)
137+
>>>> container.add_function('pi_squared', lambda c: c.pi * c.pi)
138+
>>>> assert c.pi_squared == 22/7 * 22/7
139+
"""
140+
def __init__(self):
141+
self._values = {}
142+
self._functions = {}
143+
144+
def __getattr__(self, key):
145+
val = self._values.get(key)
146+
if val is None:
147+
try:
148+
ret = self._functions[key](self)
149+
except BaseException as e:
150+
val = (True, e)
151+
else:
152+
val = (False, ret)
153+
self._values[key] = val
154+
error, ret = val
155+
if error:
156+
raise ret
157+
return ret
158+
159+
def add_function(self, name, fn):
160+
if name in self._functions:
161+
raise RuntimeError(f"fn {name} already registered")
162+
self._functions[name] = fn
163+
164+
def add_value(self, name, value):
165+
if name in self._values:
166+
raise RuntimeError(f"value {name} already registered")
167+
self._values[name] = value
168+
169+
170+
# TODO see comment in main function about moving to another module
171+
172+
def harness(name, *check_fns):
173+
"""Harness for evaluating testcase `name`.
174+
175+
Logs beginning of computation.
176+
Calls each fn in `check_fns`.
177+
Prints (to stdout) 'name: RESULT', where RESULT is one of
178+
179+
- 'ABORT' if an exception occurs during the function calls
180+
- 'FAIL' if one of the functions has a falsy result
181+
- 'PASS' otherwise
182+
"""
183+
logger.debug(f'** {name}')
184+
try:
185+
result = all(check_fn() for check_fn in check_fns)
186+
except BaseException:
187+
logger.debug('exception during check', exc_info=True)
188+
result = 'ABORT'
189+
else:
190+
result = ['FAIL', 'PASS'][min(1, result)]
191+
# this is quite redundant
192+
# logger.debug(f'** computation end for {name}')
193+
print(f"{name}: {result}")
194+
195+
44196
def main(argv):
45197
"""Entry point -- main loop going over flavors"""
46-
fnmck = flavor_names.CompatLayer()
198+
# configure logging, disable verbose library logging
199+
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
200+
openstack.enable_logging(debug=False)
47201
cloud = None
48-
verbose = False
49202

50203
try:
51204
cloud = os.environ["OS_CLOUD"]
@@ -70,15 +223,15 @@ def main(argv):
70223
# fnmck.disallow_old = True
71224
print(f'ignoring obsolete argument: {opt[0]}', file=sys.stderr)
72225
elif opt[0] == "-2" or opt[0] == "--v2plus":
73-
fnmck.disallow_old = True
226+
print(f'ignoring obsolete argument: {opt[0]}', file=sys.stderr)
74227
elif opt[0] == "-1" or opt[0] == "--v1prefer":
75228
print(f'ignoring obsolete argument: {opt[0]}', file=sys.stderr)
76229
elif opt[0] == "-o" or opt[0] == "--accept-old-mandatory":
77230
print(f'ignoring obsolete argument: {opt[0]}', file=sys.stderr)
78231
elif opt[0] == "-v" or opt[0] == "--verbose":
79-
verbose = True
232+
print(f'ignoring obsolete argument: {opt[0]}', file=sys.stderr)
80233
elif opt[0] == "-q" or opt[0] == "--quiet":
81-
fnmck.quiet = True
234+
print(f'ignoring obsolete argument: {opt[0]}', file=sys.stderr)
82235
else:
83236
usage(2)
84237
if len(args) > 0:
@@ -88,123 +241,21 @@ def main(argv):
88241
if not cloud:
89242
print("CRITICAL: You need to have OS_CLOUD set or pass --os-cloud=CLOUD.", file=sys.stderr)
90243
sys.exit(1)
91-
conn = openstack.connect(cloud=cloud, timeout=32)
92-
flavors = conn.compute.flavors()
93-
94-
# Lists of flavors: mandatory, good-SCS, bad-SCS, non-SCS, with-warnings
95-
SCSFlv = []
96-
wrongFlv = []
97-
nonSCSFlv = []
98-
warnFlv = []
99-
errors = 0
100-
for flv in flavors:
101-
# Skip non-SCS flavors
102-
if flv.name and flv.name[:4] != "SCS-": # and flv.name[:4] != "SCSx"
103-
nonSCSFlv.append(flv.name)
104-
continue
105-
try:
106-
ret = fnmck.parsename(flv.name)
107-
assert ret
108-
# Parser error
109-
except ValueError as exc:
110-
errors += 1
111-
wrongFlv.append(flv.name)
112-
print(f"ERROR: Wrong flavor \"{flv.name}\": {exc}", file=sys.stderr)
113-
continue
114-
# We have a successfully parsed SCS- name now
115-
# See if the OpenStack provided data fulfills what we
116-
# expect from the flavor based on its name
117-
err = 0
118-
warn = 0
119-
# Split list for readability
120-
cpuram = ret.cpuram
121-
# next qwould be hype, hwvirt, cpubrand, gpu, ib
122-
# see flavor-name-check.py: parsename()
123-
# vCPUS
124-
if flv.vcpus < cpuram.cpus:
125-
print(f"ERROR: Flavor {flv.name} has only {flv.vcpus} vCPUs, "
126-
f"should have >= {cpuram.cpus}", file=sys.stderr)
127-
err += 1
128-
elif flv.vcpus > cpuram.cpus:
129-
print(f"WARNING: Flavor {flv.name} has {flv.vcpus} vCPUs, "
130-
f"only needs {cpuram.cpus}", file=sys.stderr)
131-
warn += 1
132-
# RAM
133-
flvram = int((flv.ram + 51) / 102.4) / 10
134-
# Warn for strange sizes (want integer numbers, half allowed for < 10GiB)
135-
if flvram >= 10 and flvram != int(flvram) or flvram * 2 != int(flvram * 2):
136-
print(f"WARNING: Flavor {flv.name} uses discouraged uneven size "
137-
f"of memory {flvram:.1f} GiB", file=sys.stderr)
138-
if flvram < cpuram.ram:
139-
print(f"ERROR: Flavor {flv.name} has only {flvram:.1f} GiB RAM, "
140-
f"should have >= {cpuram.ram:.1f} GiB", file=sys.stderr)
141-
err += 1
142-
elif flvram > cpuram.ram:
143-
print(f"WARNING: Flavor {flv.name} has {flvram:.1f} GiB RAM, "
144-
f"only needs {cpuram.ram:.1f} GiB", file=sys.stderr)
145-
warn += 1
146-
# DISK
147-
accdisk = (0, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
148-
# Disk could have been omitted
149-
disksize = ret.disk.disksize if ret.disk else 0
150-
# We have a recommendation for disk size steps
151-
if disksize not in accdisk:
152-
print(f"WARNING: Flavor {flv.name} advertizes disk size {disksize}, "
153-
f"should have (5, 10, 20, 50, 100, 200, ...)", file=sys.stderr)
154-
warn += 1
155-
if flv.disk < disksize:
156-
print(f"ERROR: Flavor {flv.name} has only {flv.disk} GB root disk, "
157-
f"should have >= {disksize} GB", file=sys.stderr)
158-
err += 1
159-
elif flv.disk > disksize:
160-
print(f"WARNING: Flavor {flv.name} has {flv.disk} GB root disk, "
161-
f"only needs {disksize} GB", file=sys.stderr)
162-
warn += 1
163-
# Ev'thing checked, react to errors by putting the bad flavors in the bad bucket
164-
if err:
165-
wrongFlv.append(flv.name)
166-
errors += 1
167-
else:
168-
SCSFlv.append(flv.name)
169-
if warn:
170-
warnFlv.append(flv.name)
171-
# This makes the output more readable
172-
SCSFlv.sort()
173-
nonSCSFlv.sort()
174-
wrongFlv.sort()
175-
warnFlv.sort()
176-
# Produce dicts for YAML reporting
177-
flvSCSList = {
178-
"SCSFlavorsValid": SCSFlv,
179-
"SCSFlavorsWrong": wrongFlv,
180-
"FlavorsWithWarnings": warnFlv,
181-
}
182-
flvOthList = {
183-
"OtherFlavors": nonSCSFlv
184-
}
185-
flvSCSRep = {
186-
"TotalAmount": len(SCSFlv) + len(wrongFlv),
187-
"FlavorsValid": len(SCSFlv),
188-
"FlavorsWrong": len(wrongFlv),
189-
"FlavorsWithWarnings": len(warnFlv),
190-
}
191-
flvOthRep = {
192-
"TotalAmount": len(nonSCSFlv),
193-
}
194-
totSummary = {
195-
"Errors": errors,
196-
"Warnings": len(warnFlv),
197-
}
198-
Report = {cloud: {"TotalSummary": totSummary}}
199-
if not fnmck.quiet:
200-
Report[cloud]["SCSFlavorSummary"] = flvSCSRep
201-
Report[cloud]["OtherFlavorSummary"] = flvOthRep
202-
if verbose:
203-
Report[cloud]["SCSFlavorReport"] = flvSCSList
204-
Report[cloud]["OtherFlavorReport"] = flvOthList
205-
print(f"{yaml.dump(Report, default_flow_style=False)}")
206-
print("flavor-name-check: " + ('PASS', 'FAIL')[min(1, errors)])
207-
return errors
244+
245+
# TODO in the future, the remainder should be moved to a central module `scs_compatible_iaas.py`,
246+
# which would import the test logic (i.e., the functions called compute_XYZ) from here.
247+
# Then this module wouldn't need to know about containers, and the central module can handle
248+
# information sharing as well as running precisely the requested set of testcases.
249+
c = Container()
250+
c.add_function('conn', lambda _: openstack.connect(cloud=cloud, timeout=32))
251+
c.add_function('flavors', lambda c: list(c.conn.compute.flavors()))
252+
c.add_function('scs_flavors', lambda c: compute_scs_flavors(c.flavors))
253+
c.add_function('scs_0100_syntax_check', lambda c: compute_scs_0100_syntax_check(c.scs_flavors))
254+
c.add_function('scs_0100_semantics_check', lambda c: compute_scs_0100_semantics_check(c.scs_flavors))
255+
c.add_function('flavor_name_check', lambda c: compute_flavor_name_check(c.scs_0100_syntax_check, c.scs_0100_semantics_check))
256+
for testcase in TESTCASES:
257+
harness(testcase, lambda: getattr(c, testcase.replace('-', '_')))
258+
return 0
208259

209260

210261
if __name__ == "__main__":

0 commit comments

Comments
 (0)