Refactor resource creation and cleanup in Tempest
[functest.git] / functest / opnfv_tests / openstack / tempest / tempest.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2015 All rights reserved
4 # This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10
11 from __future__ import division
12
13 import logging
14 import os
15 import re
16 import shutil
17 import subprocess
18 import time
19
20 import yaml
21
22 from functest.core import testcase
23 from functest.opnfv_tests.openstack.tempest import conf_utils
24 from functest.utils.constants import CONST
25 import functest.utils.functest_utils as ft_utils
26 import functest.utils.openstack_utils as os_utils
27
28 from snaps.openstack import create_flavor
29 from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor
30 from snaps.openstack.create_project import ProjectSettings
31 from snaps.openstack.create_network import NetworkSettings, SubnetSettings
32 from snaps.openstack.create_user import UserSettings
33 from snaps.openstack.tests import openstack_tests
34 from snaps.openstack.utils import deploy_utils
35
36
37 """ logging configuration """
38 logger = logging.getLogger(__name__)
39
40
41 class TempestCommon(testcase.TestCase):
42
43     def __init__(self, **kwargs):
44         super(TempestCommon, self).__init__(**kwargs)
45         self.resources = TempestResourcesManager(**kwargs)
46         self.MODE = ""
47         self.OPTION = ""
48         self.VERIFIER_ID = conf_utils.get_verifier_id()
49         self.VERIFIER_REPO_DIR = conf_utils.get_verifier_repo_dir(
50             self.VERIFIER_ID)
51         self.DEPLOYMENT_ID = conf_utils.get_verifier_deployment_id()
52         self.DEPLOYMENT_DIR = conf_utils.get_verifier_deployment_dir(
53             self.VERIFIER_ID, self.DEPLOYMENT_ID)
54         self.VERIFICATION_ID = None
55
56     @staticmethod
57     def read_file(filename):
58         with open(filename) as src:
59             return [line.strip() for line in src.readlines()]
60
61     def generate_test_list(self, verifier_repo_dir):
62         logger.debug("Generating test case list...")
63         if self.MODE == 'defcore':
64             shutil.copyfile(
65                 conf_utils.TEMPEST_DEFCORE, conf_utils.TEMPEST_RAW_LIST)
66         elif self.MODE == 'custom':
67             if os.path.isfile(conf_utils.TEMPEST_CUSTOM):
68                 shutil.copyfile(
69                     conf_utils.TEMPEST_CUSTOM, conf_utils.TEMPEST_RAW_LIST)
70             else:
71                 raise Exception("Tempest test list file %s NOT found."
72                                 % conf_utils.TEMPEST_CUSTOM)
73         else:
74             if self.MODE == 'smoke':
75                 testr_mode = "smoke"
76             elif self.MODE == 'full':
77                 testr_mode = ""
78             else:
79                 testr_mode = 'tempest.api.' + self.MODE
80             cmd = ("cd {0};"
81                    "testr list-tests {1} > {2};"
82                    "cd -;".format(verifier_repo_dir,
83                                   testr_mode,
84                                   conf_utils.TEMPEST_RAW_LIST))
85             ft_utils.execute_command(cmd)
86
87     def apply_tempest_blacklist(self):
88         logger.debug("Applying tempest blacklist...")
89         cases_file = self.read_file(conf_utils.TEMPEST_RAW_LIST)
90         result_file = open(conf_utils.TEMPEST_LIST, 'w')
91         black_tests = []
92         try:
93             installer_type = CONST.__getattribute__('INSTALLER_TYPE')
94             deploy_scenario = CONST.__getattribute__('DEPLOY_SCENARIO')
95             if (bool(installer_type) * bool(deploy_scenario)):
96                 # if INSTALLER_TYPE and DEPLOY_SCENARIO are set we read the
97                 # file
98                 black_list_file = open(conf_utils.TEMPEST_BLACKLIST)
99                 black_list_yaml = yaml.safe_load(black_list_file)
100                 black_list_file.close()
101                 for item in black_list_yaml:
102                     scenarios = item['scenarios']
103                     installers = item['installers']
104                     if (deploy_scenario in scenarios and
105                             installer_type in installers):
106                         tests = item['tests']
107                         for test in tests:
108                             black_tests.append(test)
109                         break
110         except Exception:
111             black_tests = []
112             logger.debug("Tempest blacklist file does not exist.")
113
114         for cases_line in cases_file:
115             for black_tests_line in black_tests:
116                 if black_tests_line in cases_line:
117                     break
118             else:
119                 result_file.write(str(cases_line) + '\n')
120         result_file.close()
121
122     def run_verifier_tests(self):
123         self.OPTION += (" --load-list {} --detailed"
124                         .format(conf_utils.TEMPEST_LIST))
125
126         cmd_line = "rally verify start " + self.OPTION
127         logger.info("Starting Tempest test suite: '%s'." % cmd_line)
128
129         header = ("Tempest environment:\n"
130                   "  SUT: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
131                   (CONST.__getattribute__('INSTALLER_TYPE'),
132                    CONST.__getattribute__('DEPLOY_SCENARIO'),
133                    CONST.__getattribute__('NODE_NAME'),
134                    time.strftime("%a %b %d %H:%M:%S %Z %Y")))
135
136         f_stdout = open(
137             os.path.join(conf_utils.TEMPEST_RESULTS_DIR, "tempest.log"), 'w+')
138         f_stderr = open(
139             os.path.join(conf_utils.TEMPEST_RESULTS_DIR,
140                          "tempest-error.log"), 'w+')
141         f_env = open(os.path.join(conf_utils.TEMPEST_RESULTS_DIR,
142                                   "environment.log"), 'w+')
143         f_env.write(header)
144
145         p = subprocess.Popen(
146             cmd_line, shell=True,
147             stdout=subprocess.PIPE,
148             stderr=f_stderr,
149             bufsize=1)
150
151         with p.stdout:
152             for line in iter(p.stdout.readline, b''):
153                 if re.search("\} tempest\.", line):
154                     logger.info(line.replace('\n', ''))
155                 elif re.search('Starting verification', line):
156                     logger.info(line.replace('\n', ''))
157                     first_pos = line.index("UUID=") + len("UUID=")
158                     last_pos = line.index(") for deployment")
159                     self.VERIFICATION_ID = line[first_pos:last_pos]
160                     logger.debug('Verification UUID: %s', self.VERIFICATION_ID)
161                 f_stdout.write(line)
162         p.wait()
163
164         f_stdout.close()
165         f_stderr.close()
166         f_env.close()
167
168     def parse_verifier_result(self):
169         if self.VERIFICATION_ID is None:
170             raise Exception('Verification UUID not found')
171
172         cmd_line = "rally verify show --uuid {}".format(self.VERIFICATION_ID)
173         logger.info("Showing result for a verification: '%s'." % cmd_line)
174         p = subprocess.Popen(cmd_line,
175                              shell=True,
176                              stdout=subprocess.PIPE,
177                              stderr=subprocess.STDOUT)
178         for line in p.stdout:
179             new_line = line.replace(' ', '').split('|')
180             if 'Tests' in new_line:
181                 break
182
183             logger.info(line)
184             if 'Testscount' in new_line:
185                 num_tests = new_line[2]
186             elif 'Success' in new_line:
187                 num_success = new_line[2]
188             elif 'Skipped' in new_line:
189                 num_skipped = new_line[2]
190             elif 'Failures' in new_line:
191                 num_failures = new_line[2]
192
193         try:
194             num_executed = int(num_tests) - int(num_skipped)
195             try:
196                 self.result = 100 * int(num_success) / int(num_executed)
197             except ZeroDivisionError:
198                 self.result = 0
199                 if int(num_tests) > 0:
200                     logger.info("All tests have been skipped")
201                 else:
202                     logger.error("No test has been executed")
203                     return
204
205             with open(os.path.join(conf_utils.TEMPEST_RESULTS_DIR,
206                                    "tempest.log"), 'r') as logfile:
207                 output = logfile.read()
208
209             success_testcases = []
210             for match in re.findall('.*\{0\} (.*?)[. ]*success ', output):
211                 success_testcases.append(match)
212             failed_testcases = []
213             for match in re.findall('.*\{0\} (.*?)[. ]*fail ', output):
214                 failed_testcases.append(match)
215             skipped_testcases = []
216             for match in re.findall('.*\{0\} (.*?)[. ]*skip:', output):
217                 skipped_testcases.append(match)
218
219             self.details = {"tests": int(num_tests),
220                             "failures": int(num_failures),
221                             "success": success_testcases,
222                             "errors": failed_testcases,
223                             "skipped": skipped_testcases}
224         except Exception:
225             self.result = 0
226
227         logger.info("Tempest %s success_rate is %s%%"
228                     % (self.case_name, self.result))
229
230     def run(self):
231
232         self.start_time = time.time()
233         try:
234             if not os.path.exists(conf_utils.TEMPEST_RESULTS_DIR):
235                 os.makedirs(conf_utils.TEMPEST_RESULTS_DIR)
236             resources = self.resources.create()
237             conf_utils.configure_tempest(
238                 self.DEPLOYMENT_DIR,
239                 image_id=resources.get("image_id"),
240                 flavor_id=resources.get("flavor_id"),
241                 mode=self.MODE)
242             self.generate_test_list(self.VERIFIER_REPO_DIR)
243             self.apply_tempest_blacklist()
244             self.run_verifier_tests()
245             self.parse_verifier_result()
246             res = testcase.TestCase.EX_OK
247         except Exception as e:
248             logger.error('Error with run: %s' % e)
249             res = testcase.TestCase.EX_RUN_ERROR
250         finally:
251             self.resources.cleanup()
252
253         self.stop_time = time.time()
254         return res
255
256     def create_snapshot(self):
257         """
258         Run the Tempest cleanup utility to initialize OS state.
259
260         :return: TestCase.EX_OK
261         """
262         logger.info("Initializing the saved state of the OpenStack deployment")
263
264         # Make sure that the verifier is configured
265         conf_utils.configure_verifier(self.DEPLOYMENT_DIR)
266
267         os_utils.init_tempest_cleanup(
268             self.DEPLOYMENT_DIR, 'tempest.conf',
269             os.path.join(conf_utils.TEMPEST_RESULTS_DIR,
270                          "tempest-cleanup-init.log")
271         )
272
273         return super(TempestCommon, self).create_snapshot()
274
275     def clean(self):
276         """
277         Run the Tempest cleanup utility to delete and destroy OS resources
278         created by Tempest.
279         """
280         logger.info("Initializing the saved state of the OpenStack deployment")
281
282         os_utils.init_tempest_cleanup(
283             self.DEPLOYMENT_DIR, 'tempest.conf',
284             os.path.join(conf_utils.REFSTACK_RESULTS_DIR,
285                          "tempest-cleanup.log")
286         )
287
288         return super(TempestCommon, self).clean()
289
290
291 class TempestSmokeSerial(TempestCommon):
292
293     def __init__(self, **kwargs):
294         if "case_name" not in kwargs:
295             kwargs["case_name"] = 'tempest_smoke_serial'
296         TempestCommon.__init__(self, **kwargs)
297         self.MODE = "smoke"
298         self.OPTION = "--concurrency 1"
299
300
301 class TempestSmokeParallel(TempestCommon):
302
303     def __init__(self, **kwargs):
304         if "case_name" not in kwargs:
305             kwargs["case_name"] = 'tempest_smoke_parallel'
306         TempestCommon.__init__(self, **kwargs)
307         self.MODE = "smoke"
308         self.OPTION = ""
309
310
311 class TempestFullParallel(TempestCommon):
312
313     def __init__(self, **kwargs):
314         if "case_name" not in kwargs:
315             kwargs["case_name"] = 'tempest_full_parallel'
316         TempestCommon.__init__(self, **kwargs)
317         self.MODE = "full"
318
319
320 class TempestCustom(TempestCommon):
321
322     def __init__(self, **kwargs):
323         if "case_name" not in kwargs:
324             kwargs["case_name"] = 'tempest_custom'
325         TempestCommon.__init__(self, **kwargs)
326         self.MODE = "custom"
327         self.OPTION = "--concurrency 1"
328
329
330 class TempestDefcore(TempestCommon):
331
332     def __init__(self, **kwargs):
333         if "case_name" not in kwargs:
334             kwargs["case_name"] = 'tempest_defcore'
335         TempestCommon.__init__(self, **kwargs)
336         self.MODE = "defcore"
337         self.OPTION = "--concurrency 1"
338
339
340 class TempestResourcesManager(object):
341
342     def __init__(self, **kwargs):
343         self.os_creds = None
344         if 'os_creds' in kwargs:
345             self.os_creds = kwargs['os_creds']
346         else:
347             self.os_creds = openstack_tests.get_credentials(
348                 os_env_file=CONST.__getattribute__('openstack_creds'))
349
350         self.creators = list()
351
352         if hasattr(CONST, 'snaps_images_cirros'):
353             self.cirros_image_config = CONST.__getattribute__(
354                 'snaps_images_cirros')
355         else:
356             self.cirros_image_config = None
357
358     def create(self, use_custom_images=False, use_custom_flavors=False,
359                create_project=False):
360         if create_project:
361             logger.debug("Creating project (tenant) for Tempest suite")
362             project_name = CONST.__getattribute__(
363                 'tempest_identity_tenant_name')
364             project_creator = deploy_utils.create_project(
365                 self.os_creds, ProjectSettings(
366                     name=project_name,
367                     description=CONST.__getattribute__(
368                         'tempest_identity_tenant_description')))
369             if (project_creator is None or
370                     project_creator.get_project() is None):
371                 raise Exception("Failed to create tenant")
372             project_id = project_creator.get_project().id
373             self.creators.append(project_creator)
374
375             logger.debug("Creating user for Tempest suite")
376             user_creator = deploy_utils.create_user(
377                 self.os_creds, UserSettings(
378                     name=CONST.__getattribute__('tempest_identity_user_name'),
379                     password=CONST.__getattribute__(
380                         'tempest_identity_user_password'),
381                     project_name=project_name))
382             if user_creator is None or user_creator.get_user() is None:
383                 raise Exception("Failed to create user")
384             user_id = user_creator.get_user().id
385             self.creators.append(user_creator)
386         else:
387             project_name = None
388             project_id = None
389             user_id = None
390
391         logger.debug("Creating private network for Tempest suite")
392         network_creator = deploy_utils.create_network(
393             self.os_creds, NetworkSettings(
394                 name=CONST.__getattribute__('tempest_private_net_name'),
395                 project_name=project_name,
396                 subnet_settings=[SubnetSettings(
397                     name=CONST.__getattribute__('tempest_private_subnet_name'),
398                     cidr=CONST.__getattribute__('tempest_private_subnet_cidr'))
399                 ]))
400         if network_creator is None or network_creator.get_network() is None:
401             raise Exception("Failed to create private network")
402         self.creators.append(network_creator)
403
404         image_id = None
405         image_id_alt = None
406         flavor_id = None
407         flavor_id_alt = None
408
409         if (CONST.__getattribute__('tempest_use_custom_images') or
410            use_custom_images):
411             logger.debug("Creating image for Tempest suite")
412             image_base_name = CONST.__getattribute__('openstack_image_name')
413             os_image_settings = openstack_tests.cirros_image_settings(
414                 image_base_name, public=True,
415                 image_metadata=self.cirros_image_config)
416             logger.debug("Creating image for Tempest suite")
417             image_creator = deploy_utils.create_image(
418                 self.os_creds, os_image_settings)
419             if image_creator is None:
420                 raise Exception('Failed to create image')
421             self.creators.append(image_creator)
422             image_id = image_creator.get_image().id
423
424         if use_custom_images:
425             logger.debug("Creating 2nd image for Tempest suite")
426             image_base_name_alt = CONST.__getattribute__(
427                 'openstack_image_name_alt')
428             os_image_settings_alt = openstack_tests.cirros_image_settings(
429                 image_base_name_alt, public=True,
430                 image_metadata=self.cirros_image_config)
431             logger.debug("Creating 2nd image for Tempest suite")
432             image_creator_alt = deploy_utils.create_image(
433                 self.os_creds, os_image_settings_alt)
434             if image_creator_alt is None:
435                 raise Exception('Failed to create image')
436             self.creators.append(image_creator_alt)
437             image_id_alt = image_creator_alt.get_image().id
438
439         if (CONST.__getattribute__('tempest_use_custom_flavors') or
440            use_custom_flavors):
441             logger.info("Creating flavor for Tempest suite")
442             scenario = ft_utils.get_scenario()
443             flavor_metadata = None
444             if 'ovs' in scenario or 'fdio' in scenario:
445                 flavor_metadata = create_flavor.MEM_PAGE_SIZE_LARGE
446             flavor_creator = OpenStackFlavor(
447                 self.os_creds, FlavorSettings(
448                     name=CONST.__getattribute__('openstack_flavor_name'),
449                     ram=CONST.__getattribute__('openstack_flavor_ram'),
450                     disk=CONST.__getattribute__('openstack_flavor_disk'),
451                     vcpus=CONST.__getattribute__('openstack_flavor_vcpus'),
452                     metadata=flavor_metadata))
453             flavor = flavor_creator.create()
454             if flavor is None:
455                 raise Exception('Failed to create flavor')
456             self.creators.append(flavor_creator)
457             flavor_id = flavor.id
458
459         if use_custom_flavors:
460             logger.info("Creating 2nd flavor for Tempest suite")
461             scenario = ft_utils.get_scenario()
462             flavor_metadata_alt = None
463             if 'ovs' in scenario or 'fdio' in scenario:
464                 flavor_metadata_alt = create_flavor.MEM_PAGE_SIZE_LARGE
465             flavor_creator_alt = OpenStackFlavor(
466                 self.os_creds, FlavorSettings(
467                     name=CONST.__getattribute__('openstack_flavor_name_alt'),
468                     ram=CONST.__getattribute__('openstack_flavor_ram'),
469                     disk=CONST.__getattribute__('openstack_flavor_disk'),
470                     vcpus=CONST.__getattribute__('openstack_flavor_vcpus'),
471                     metadata=flavor_metadata_alt))
472             flavor_alt = flavor_creator_alt.create()
473             if flavor_alt is None:
474                 raise Exception('Failed to create flavor')
475             self.creators.append(flavor_creator_alt)
476             flavor_id_alt = flavor_alt.id
477
478         print("RESOURCES CREATE: image_id: %s, image_id_alt: %s, "
479               "flavor_id: %s, flavor_id_alt: %s" % (
480                   image_id, image_id_alt, flavor_id, flavor_id_alt,))
481
482         result = {
483             'image_id': image_id,
484             'image_id_alt': image_id_alt,
485             'flavor_id': flavor_id,
486             'flavor_id_alt': flavor_id_alt
487         }
488
489         if create_project:
490             result['project_id'] = project_id
491             result['tenant_id'] = project_id  # for compatibility
492             result['user_id'] = user_id
493
494         return result
495
496     def cleanup(self):
497         """
498         Cleanup all OpenStack objects. Should be called on completion.
499         """
500         for creator in reversed(self.creators):
501             try:
502                 creator.clean()
503             except Exception as e:
504                 logger.error('Unexpected error cleaning - %s', e)