Skip to content

Commit c0a279e

Browse files
authored
Merge pull request #4 from stackhpc/will/updates
Various tweaks
2 parents 7ce719a + af3cc16 commit c0a279e

File tree

3 files changed

+228
-2
lines changed

3 files changed

+228
-2
lines changed

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ RUN apt-get update && apt-get install --yes sudo python3-dev python3-pip vim git
1010
echo "rally ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/00-rally-user && \
1111
mkdir /rally && chown -R rally:rally /rally
1212

13-
RUN pip install git+https://github.com/openstack/rally-openstack.git --constraint https://raw.githubusercontent.com/openstack/rally-openstack/master/upper-constraints.txt --no-cache-dir && \
13+
RUN pip install git+https://github.com/openstack/rally-openstack.git --no-cache-dir && \
1414
pip3 install pymysql psycopg2-binary --no-cache-dir
1515

1616
COPY ./etc/motd_for_docker /etc/motd
@@ -32,6 +32,7 @@ RUN rally verify create-verifier --name default --type tempest
3232

3333
COPY bin/rally-verify-wrapper.sh /usr/bin/rally-verify-wrapper.sh
3434
COPY bin/rally-extract-tests.sh /usr/bin/rally-extract-tests.sh
35+
COPY bin/rally-normalize.py /usr/bin/rally-normalize.py
3536

3637
# Data generated during the image creation is copied to volume only when it's
3738
# attached for the first time (volume initialization)

bin/rally-normalize.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env python3
2+
3+
# Originally from: https://opendev.org/osf/refstack-client/src/branch/master/refstack_client/list_parser.py
4+
5+
# Copyright (c) 2015 IBM Corp.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
8+
# not use this file except in compliance with the License. You may obtain
9+
# a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16+
# License for the specific language governing permissions and limitations
17+
# under the License.
18+
#
19+
20+
21+
import atexit
22+
import logging
23+
import os
24+
import re
25+
import requests
26+
import subprocess
27+
import tempfile
28+
29+
30+
class TestListParser(object):
31+
32+
"""This class is for normalizing test lists to match the tests in the
33+
current Tempest environment.
34+
"""
35+
36+
def __init__(self, insecure=False):
37+
"""
38+
Initialize the TestListParser.
39+
:param tempest_dir: Absolute path of the Tempest directory.
40+
:param insecure: Whether https requests, if any, should be insecure.
41+
"""
42+
self.logger = logging.getLogger(__name__)
43+
self.insecure = insecure
44+
45+
def _get_tempest_test_ids(self):
46+
"""This does a 'testr list-tests' or 'stestr list' according to
47+
Tempest version on the Tempest directory in order to get a list
48+
of full test IDs for the current Tempest environment. Test ID
49+
mappings are then formed for these tests.
50+
"""
51+
cmd = ('rally', 'verify', 'list-verifier-tests')
52+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
53+
(stdout, stderr) = process.communicate()
54+
55+
if process.returncode != 0:
56+
self.logger.error(stdout)
57+
self.logger.error(stderr)
58+
raise subprocess.CalledProcessError(process.returncode,
59+
' '.join(cmd))
60+
try:
61+
return self._form_test_id_mappings(stdout.split('\n'))
62+
except TypeError:
63+
return self._form_test_id_mappings(stdout.decode().split('\n'))
64+
65+
def _form_test_id_mappings(self, test_list):
66+
"""This takes in a list of full test IDs and forms a dict containing
67+
base test IDs mapped to their attributes. A full test ID also contains
68+
test attributes such as '[gate,smoke]'
69+
Ex:
70+
'tempest.api.test1': '[gate]'
71+
'tempest.api.test2': ''
72+
'tempest.api.test3(some_scenario)': '[smoke,gate]'
73+
:param test_list: List of full test IDs
74+
"""
75+
test_mappings = {}
76+
for testcase in test_list:
77+
if testcase.startswith("tempest"):
78+
# Search for any strings like '[smoke, gate]' in the test ID.
79+
match = re.search('(\[.*\])', testcase)
80+
81+
if match:
82+
testcase = re.sub('\[.*\]', '', testcase)
83+
test_mappings[testcase] = match.group(1)
84+
else:
85+
test_mappings[testcase] = ""
86+
return test_mappings
87+
88+
def _get_base_test_ids_from_list_file(self, list_location):
89+
"""This takes in a test list file and finds all the base test IDs
90+
for the tests listed.
91+
Ex:
92+
'tempest.test1[gate,id-2]' -> 'tempest.test1'
93+
'tempest.test2[gate,id-3](scenario)' -> 'tempest.test2(scenario)'
94+
:param list_location: file path or URL location of list file
95+
"""
96+
try:
97+
response = requests.get(list_location,
98+
verify=not self.insecure)
99+
testcase_list = response.text.split('\n')
100+
test_mappings = self._form_test_id_mappings(testcase_list)
101+
# If the location isn't a valid URL, we assume it is a file path.
102+
except requests.exceptions.MissingSchema:
103+
try:
104+
with open(list_location) as data_file:
105+
testcase_list = [line.rstrip('\n') for line in data_file]
106+
test_mappings = self._form_test_id_mappings(testcase_list)
107+
except Exception:
108+
self.logger.error("Error reading the passed in test list " +
109+
"file.")
110+
raise
111+
except Exception:
112+
self.logger.error("Error reading the passed in test list file.")
113+
raise
114+
115+
return list(test_mappings.keys())
116+
117+
def _get_full_test_ids(self, tempest_ids, base_ids):
118+
"""This will remake the test ID list with the full IDs of the current
119+
Tempest environment. The Tempest test ID dict should have the correct
120+
mappings.
121+
:param tempest_ids: dict containing test ID mappings
122+
:param base_ids: list containing base test IDs
123+
"""
124+
test_list = []
125+
for test_id in base_ids:
126+
try:
127+
attr = tempest_ids[test_id]
128+
# If the test has a scenario in the test ID, but also has some
129+
# additional attributes, the attributes need to go before the
130+
# scenario.
131+
if '(' in test_id and attr:
132+
components = test_id.split('(', 1)
133+
test_portion = components[0]
134+
scenario = "(" + components[1]
135+
test_list.append(test_portion + attr + scenario)
136+
else:
137+
test_list.append(test_id + attr)
138+
except KeyError:
139+
self.logger.warning("Test %s not found in Tempest list." %
140+
test_id)
141+
self.logger.debug("Number of tests: " + str(len(test_list)))
142+
return test_list
143+
144+
def _write_normalized_test_list(self, test_ids):
145+
"""Create a temporary file to pass into testr containing a list of test
146+
IDs that should be tested.
147+
:param test_ids: list of full test IDs
148+
"""
149+
temp = tempfile.NamedTemporaryFile(delete=False)
150+
for test_id in test_ids:
151+
temp.write(("%s\n" % test_id).encode('utf-8'))
152+
temp.flush()
153+
154+
# Register the created file for cleanup.
155+
atexit.register(self._remove_test_list_file, temp.name)
156+
return temp.name
157+
158+
def _remove_test_list_file(self, file_path):
159+
"""Delete the given file.
160+
:param file_path: string containing the location of the file
161+
"""
162+
if os.path.isfile(file_path):
163+
os.remove(file_path)
164+
165+
def get_normalized_test_list(self, list_location):
166+
"""This will take in the user's test list and will normalize it
167+
so that the test cases in the list map to actual full test IDS in
168+
the Tempest environment.
169+
:param list_location: file path or URL of the test list
170+
"""
171+
tempest_test_ids = self._get_tempest_test_ids()
172+
#raise ValueError(tempest_test_ids)
173+
if not tempest_test_ids:
174+
return None
175+
base_test_ids = self._get_base_test_ids_from_list_file(list_location)
176+
full_capability_test_ids = self._get_full_test_ids(tempest_test_ids,
177+
base_test_ids)
178+
list_file = self._write_normalized_test_list(full_capability_test_ids)
179+
return list_file
180+
181+
def create_whitelist(self, list_location):
182+
"""This takes in a test list file, get normalized, and get whitelist
183+
regexes using full qualified test names (one per line).
184+
Ex:
185+
'tempest.test1[id-2,gate]' -> tempest.test1\[
186+
'tempest.test2[id-3,smoke](scenario)' -> tempest.test2\[
187+
'tempest.test3[compute,id-4]' -> tempest.test3\[
188+
:param list_location: file path or URL location of list file
189+
"""
190+
normalized_list = open(self.get_normalized_test_list(list_location),
191+
'r').read()
192+
# Keep the names
193+
tests_list = [re.sub("\[", "\[", test)
194+
for test in re.findall(".*\[", normalized_list)]
195+
196+
return self._write_normalized_test_list(tests_list)
197+
198+
199+
if __name__ == "__main__":
200+
import sys
201+
a = TestListParser()
202+
result = open(a.get_normalized_test_list(sys.argv[1]),
203+
'r').read()
204+
with open(sys.argv[1], 'w') as f:
205+
print(result, file=f)
206+

bin/rally-verify-wrapper.sh

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ load_list=""
3838
# You can't have a load list and a pattern, pattern takes priority
3939
if [ -f ~/tempest-load-list ] && [ -z ${TEMPEST_PATTERN:+x} ]; then
4040
load_list="--load-list /home/rally/tempest-load-list"
41+
if [ $(wc -l /home/rally/tempest-load-list | cut -d ' ' -f 1) -lt 1]; then
42+
echo >&2 "The load list appears to be empty, exiting..."
43+
exit -1
44+
fi
4145
fi
4246

4347
skip_list=""
@@ -75,6 +79,10 @@ set -x
7579
unset OS_CACERT
7680

7781
crudini --set ~/.rally/rally.conf DEFAULT openstack_client_http_timeout 300
82+
crudini --set ~/.rally/rally.conf openstack flavor_ref_ram 128
83+
crudini --set ~/.rally/rally.conf openstack flavor_ref_alt_ram 256
84+
crudini --set ~/.rally/rally.conf openstack flavor_ref_disk 1
85+
crudini --set ~/.rally/rally.conf openstack flavor_ref_alt_disk 1
7886

7987
rally deployment create --fromenv --name openstack
8088

@@ -86,7 +94,18 @@ if [ -f ~/tempest-overrides.conf ]; then
8694
rally verify configure-verifier --reconfigure --extend ~/tempest-overrides.conf
8795
fi
8896

89-
rally verify start $skip_list $load_list $pattern $concurrency > >(tee -a $artifacts_dir/stdout.log) 2> >(tee -a $artifacts_dir/stderr.log >&2)
97+
if [ -f ~/tempest-load-list ] && [ -z ${TEMPEST_PATTERN:+x} ]; then
98+
if [ ${TEMPEST_NORMALIZE_LOAD_LIST:-1} -eq 1 ]; then
99+
echo normalizing load-list
100+
rally-normalize.py /home/rally/tempest-load-list
101+
fi
102+
if [ $(wc -l /home/rally/tempest-load-list | cut -d ' ' -f 1) -lt 1 ]; then
103+
echo >&2 "The load list appears to be empty, exiting..."
104+
exit -1
105+
fi
106+
fi
107+
108+
rally verify start $skip_list $load_list $pattern $concurrency > >(tee -a $artifacts_dir/stdout.log) 2> >(tee -a $artifacts_dir/stderr.log >&2) || export failed=1
90109

91110
rally verify report --type html --to $artifacts_dir/rally-verify-report.html
92111
rally verify report --type json --to $artifacts_dir/rally-verify-report.json

0 commit comments

Comments
 (0)