Skip to content

Commit 2bd965e

Browse files
committed
add openstack_test.py
Signed-off-by: Matthias Büchse <[email protected]>
1 parent 8086486 commit 2bd965e

File tree

3 files changed

+174
-4
lines changed

3 files changed

+174
-4
lines changed

Tests/iaas/openstack_test.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
# vim: set ts=4 sw=4 et:
3+
4+
"""Openstack testcase runner
5+
6+
(c) Matthias Büchse <[email protected]>, 8/2025
7+
SPDX-License-Identifier: CC-BY-SA 4.0
8+
"""
9+
10+
import logging
11+
import os
12+
import sys
13+
import typing
14+
import getopt
15+
import openstack
16+
17+
from scs_0100_flavor_naming.flavor_names_check import \
18+
TESTCASES as SCS_0100_TESTCASES, \
19+
compute_scs_flavors, compute_scs_0100_syntax_check, compute_scs_0100_semantics_check, compute_flavor_name_check
20+
21+
22+
logger = logging.getLogger(__name__)
23+
24+
25+
def usage(rcode=1):
26+
"""help output"""
27+
print("Usage: openstack_test.py [options] testcase-id1 ... testcase-idN", file=sys.stderr)
28+
print("Options: [-c/--os-cloud OS_CLOUD] sets cloud environment (default from OS_CLOUD env)", file=sys.stderr)
29+
print("This tool retrieves the list of flavors from the OpenStack cloud OS_CLOUD", file=sys.stderr)
30+
print(" and reports inconsistencies, errors etc. It returns 0 on success.", file=sys.stderr)
31+
sys.exit(rcode)
32+
33+
34+
TESTCASES = set(
35+
SCS_0100_TESTCASES
36+
)
37+
38+
39+
def make_container(cloud):
40+
c = Container()
41+
# scs_0100_flavor_naming
42+
c.add_function('conn', lambda _: openstack.connect(cloud=cloud, timeout=32))
43+
c.add_function('flavors', lambda c: list(c.conn.compute.flavors()))
44+
c.add_function('scs_flavors', lambda c: compute_scs_flavors(c.flavors))
45+
c.add_function('scs_0100_syntax_check', lambda c: compute_scs_0100_syntax_check(c.scs_flavors))
46+
c.add_function('scs_0100_semantics_check', lambda c: compute_scs_0100_semantics_check(c.scs_flavors))
47+
c.add_function('flavor_name_check', lambda c: compute_flavor_name_check(c.scs_0100_syntax_check, c.scs_0100_semantics_check))
48+
return c
49+
50+
51+
class Container:
52+
"""
53+
This class does lazy evaluation and memoization. You register any potential value either
54+
by giving the value directly, using `add_value`, or
55+
by specifying how it is computed using `add_function`,
56+
which expects a function that takes a container (so other values may be referred to).
57+
In each case, you have to give the value a name.
58+
59+
The value will be available as a normal member variable under this name.
60+
If given via a function, this function will only be evaluated when the value is accessed,
61+
and the value will be memoized, so the function won't be called twice.
62+
If the function raises an exception, then this will be memoized just as well.
63+
64+
For instance,
65+
66+
>>>> container = Container()
67+
>>>> container.add_function('pi', lambda _: 22/7)
68+
>>>> container.add_function('pi_squared', lambda c: c.pi * c.pi)
69+
>>>> assert container.pi_squared == 22/7 * 22/7
70+
"""
71+
def __init__(self):
72+
self._values = {}
73+
self._functions = {}
74+
75+
def __getattr__(self, key):
76+
val = self._values.get(key)
77+
if val is None:
78+
try:
79+
ret = self._functions[key](self)
80+
except BaseException as e:
81+
val = (True, e)
82+
else:
83+
val = (False, ret)
84+
self._values[key] = val
85+
error, ret = val
86+
if error:
87+
raise ret
88+
return ret
89+
90+
def add_function(self, name, fn):
91+
if name in self._functions:
92+
raise RuntimeError(f"fn {name} already registered")
93+
self._functions[name] = fn
94+
95+
def add_value(self, name, value):
96+
if name in self._values:
97+
raise RuntimeError(f"value {name} already registered")
98+
self._values[name] = value
99+
100+
101+
def harness(name, *check_fns):
102+
"""Harness for evaluating testcase `name`.
103+
104+
Logs beginning of computation.
105+
Calls each fn in `check_fns`.
106+
Prints (to stdout) 'name: RESULT', where RESULT is one of
107+
108+
- 'ABORT' if an exception occurs during the function calls
109+
- 'FAIL' if one of the functions has a falsy result
110+
- 'PASS' otherwise
111+
"""
112+
logger.debug(f'** {name}')
113+
try:
114+
result = all(check_fn() for check_fn in check_fns)
115+
except BaseException:
116+
logger.debug('exception during check', exc_info=True)
117+
result = 'ABORT'
118+
else:
119+
result = ['FAIL', 'PASS'][min(1, result)]
120+
# this is quite redundant
121+
# logger.debug(f'** computation end for {name}')
122+
print(f"{name}: {result}")
123+
124+
125+
def main(argv):
126+
"""Entry point -- main loop going over flavors"""
127+
# configure logging, disable verbose library logging
128+
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
129+
openstack.enable_logging(debug=False)
130+
cloud = None
131+
132+
try:
133+
cloud = os.environ["OS_CLOUD"]
134+
except KeyError:
135+
pass
136+
try:
137+
opts, args = getopt.gnu_getopt(argv, "c:C:", ("os-cloud=", ))
138+
except getopt.GetoptError as exc:
139+
print(f"CRITICAL: {exc!r}", file=sys.stderr)
140+
usage(1)
141+
for opt in opts:
142+
if opt[0] == "-h" or opt[0] == "--help":
143+
usage(0)
144+
elif opt[0] == "-c" or opt[0] == "--os-cloud":
145+
cloud = opt[1]
146+
else:
147+
usage(2)
148+
149+
testcases = [t for t in args if t in TESTCASES]
150+
if len(testcases) != len(args):
151+
unknown = [a for a in args if a not in TESTCASES]
152+
logger.warning(f"ignoring unknown testcases: {','.join(unknown)}")
153+
154+
if not cloud:
155+
print("CRITICAL: You need to have OS_CLOUD set or pass --os-cloud=CLOUD.", file=sys.stderr)
156+
sys.exit(1)
157+
158+
c = make_container(cloud)
159+
for testcase in testcases:
160+
harness(testcase, lambda: getattr(c, testcase.replace('-', '_')))
161+
return 0
162+
163+
164+
if __name__ == "__main__":
165+
try:
166+
sys.exit(main(sys.argv[1:]))
167+
except SystemExit:
168+
raise
169+
except BaseException as exc:
170+
print(f"CRITICAL: {exc!r}", file=sys.stderr)
171+
sys.exit(1)

Tests/iaas/scs_0100_flavor_naming/flavor_names_check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import openstack
88

9-
import flavor_names
9+
from . import flavor_names
1010

1111

1212
logger = logging.getLogger(__name__)

Tests/scs-compatible-iaas.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ modules:
1515
name: Flavor naming v3.1
1616
url: https://docs.scs.community/standards/scs-0100-v3-flavor-naming
1717
run:
18-
- executable: ./iaas/scs_0100_flavor_naming/flavor-names-openstack.py
19-
args: -c {os_cloud} --mand=./iaas/scs-0100-v3-flavors.yaml
20-
# Note: --v2plus would outlaw the v1 flavor names. Don't do this yet.
18+
- executable: ./iaas/openstack_test.py
19+
args: -c {os_cloud} flavor-name-check
2120
testcases:
2221
- id: flavor-name-check
2322
tags: [mandatory]

0 commit comments

Comments
 (0)