blob: 26a7c94e695d21f0771f80742223eb9cb81c398c [file] [log] [blame]
Pablo Montes Morenoeebea062017-02-27 12:33:10 +01001#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3
4##
tiernoec66e9a2017-05-17 15:28:10 +02005# Copyright 2017
Pablo Montes Morenoeebea062017-02-27 12:33:10 +01006# This file is part of openmano
7# All Rights Reserved.
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License. You may obtain
11# a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18# License for the specific language governing permissions and limitations
19# under the License.
20#
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010021##
22
23'''
24Module for testing openmano functionality. It uses openmanoclient.py for invoking openmano
25'''
tierno0e6fcaa2017-05-05 15:54:07 +020026__author__="Pablo Montes, Alfonso Tierno"
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010027__date__ ="$16-Feb-2017 17:08:16$"
tiernoec66e9a2017-05-17 15:28:10 +020028__version__="0.0.3"
tierno0e6fcaa2017-05-05 15:54:07 +020029version_date="May 2017"
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010030
31import logging
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010032import os
tiernoec66e9a2017-05-17 15:28:10 +020033from argparse import ArgumentParser
34import argcomplete
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010035import unittest
36import string
37import inspect
38import random
39import traceback
40import glob
41import yaml
42import sys
43import time
44
tiernoec66e9a2017-05-17 15:28:10 +020045global test_config # used for global variables with the test configuration
46test_config = {}
47
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010048
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +020049def check_instance_scenario_active(uuid):
tiernoec66e9a2017-05-17 15:28:10 +020050 instance = test_config["client"].get_instance(uuid=uuid)
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +020051
52 for net in instance['nets']:
53 status = net['status']
Pablo Montes Morenob12711f2017-04-06 11:54:34 +020054 if status != 'ACTIVE':
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +020055 return (False, status)
56
57 for vnf in instance['vnfs']:
58 for vm in vnf['vms']:
59 status = vm['status']
60 if status != 'ACTIVE':
61 return (False, status)
62
63 return (True, None)
64
tiernoec66e9a2017-05-17 15:28:10 +020065
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010066'''
67IMPORTANT NOTE
68All unittest classes for code based tests must have prefix 'test_' in order to be taken into account for tests
69'''
tiernoec66e9a2017-05-17 15:28:10 +020070class test_VIM_datacenter_tenant_operations(unittest.TestCase):
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010071 test_index = 1
72 tenant_name = None
73 test_text = None
74
75 @classmethod
76 def setUpClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +020077 logger.info("{}. {}".format(test_config["test_number"], cls.__name__))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010078
79 @classmethod
80 def tearDownClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +020081 test_config["test_number"] += 1
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010082
83 def tearDown(self):
84 exec_info = sys.exc_info()
85 if exec_info == (None, None, None):
86 logger.info(self.__class__.test_text+" -> TEST OK")
87 else:
88 logger.warning(self.__class__.test_text+" -> TEST NOK")
89 error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2])
90 msg = ""
91 for line in error_trace:
92 msg = msg + line
93 logger.critical("{}".format(msg))
94
95 def test_000_create_RO_tenant(self):
96 self.__class__.tenant_name = _get_random_string(20)
tiernoec66e9a2017-05-17 15:28:10 +020097 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +010098 inspect.currentframe().f_code.co_name)
99 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200100 tenant = test_config["client"].create_tenant(name=self.__class__.tenant_name,
101 description=self.__class__.tenant_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100102 logger.debug("{}".format(tenant))
103 self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.tenant_name)
104
105 def test_010_list_RO_tenant(self):
tiernoec66e9a2017-05-17 15:28:10 +0200106 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100107 inspect.currentframe().f_code.co_name)
108 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200109 tenant = test_config["client"].get_tenant(name=self.__class__.tenant_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100110 logger.debug("{}".format(tenant))
111 self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.tenant_name)
112
113 def test_020_delete_RO_tenant(self):
tiernoec66e9a2017-05-17 15:28:10 +0200114 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100115 inspect.currentframe().f_code.co_name)
116 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200117 tenant = test_config["client"].delete_tenant(name=self.__class__.tenant_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100118 logger.debug("{}".format(tenant))
119 assert('deleted' in tenant.get('result',""))
120
tiernoec66e9a2017-05-17 15:28:10 +0200121
122class test_VIM_datacenter_operations(unittest.TestCase):
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100123 test_index = 1
124 datacenter_name = None
125 test_text = None
126
127 @classmethod
128 def setUpClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200129 logger.info("{}. {}".format(test_config["test_number"], cls.__name__))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100130
131 @classmethod
132 def tearDownClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200133 test_config["test_number"] += 1
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100134
135 def tearDown(self):
136 exec_info = sys.exc_info()
137 if exec_info == (None, None, None):
138 logger.info(self.__class__.test_text+" -> TEST OK")
139 else:
140 logger.warning(self.__class__.test_text+" -> TEST NOK")
141 error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2])
142 msg = ""
143 for line in error_trace:
144 msg = msg + line
145 logger.critical("{}".format(msg))
146
147 def test_000_create_datacenter(self):
tiernoec66e9a2017-05-17 15:28:10 +0200148 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100149 inspect.currentframe().f_code.co_name)
150 self.__class__.datacenter_name = _get_random_string(20)
151 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200152 self.datacenter = test_config["client"].create_datacenter(name=self.__class__.datacenter_name,
153 vim_url="http://fakeurl/fake")
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100154 logger.debug("{}".format(self.datacenter))
155 self.assertEqual (self.datacenter.get('datacenter', {}).get('name',''), self.__class__.datacenter_name)
156
157 def test_010_list_datacenter(self):
tiernoec66e9a2017-05-17 15:28:10 +0200158 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100159 inspect.currentframe().f_code.co_name)
160
161 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200162 self.datacenter = test_config["client"].get_datacenter(all_tenants=True, name=self.__class__.datacenter_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100163 logger.debug("{}".format(self.datacenter))
164 self.assertEqual (self.datacenter.get('datacenter', {}).get('name', ''), self.__class__.datacenter_name)
165
166 def test_020_attach_datacenter(self):
tiernoec66e9a2017-05-17 15:28:10 +0200167 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100168 inspect.currentframe().f_code.co_name)
169
170 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200171 self.datacenter = test_config["client"].attach_datacenter(name=self.__class__.datacenter_name,
172 vim_tenant_name='fake')
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100173 logger.debug("{}".format(self.datacenter))
174 assert ('vim_tenants' in self.datacenter.get('datacenter', {}))
175
176 def test_030_list_attached_datacenter(self):
tiernoec66e9a2017-05-17 15:28:10 +0200177 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100178 inspect.currentframe().f_code.co_name)
179
180 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200181 self.datacenter = test_config["client"].get_datacenter(all_tenants=False, name=self.__class__.datacenter_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100182 logger.debug("{}".format(self.datacenter))
183 self.assertEqual (self.datacenter.get('datacenter', {}).get('name', ''), self.__class__.datacenter_name)
184
185 def test_040_detach_datacenter(self):
tiernoec66e9a2017-05-17 15:28:10 +0200186 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100187 inspect.currentframe().f_code.co_name)
188
189 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200190 self.datacenter = test_config["client"].detach_datacenter(name=self.__class__.datacenter_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100191 logger.debug("{}".format(self.datacenter))
192 assert ('detached' in self.datacenter.get('result', ""))
193
194 def test_050_delete_datacenter(self):
tiernoec66e9a2017-05-17 15:28:10 +0200195 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100196 inspect.currentframe().f_code.co_name)
197
198 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200199 self.datacenter = test_config["client"].delete_datacenter(name=self.__class__.datacenter_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100200 logger.debug("{}".format(self.datacenter))
201 assert('deleted' in self.datacenter.get('result',""))
202
tiernoec66e9a2017-05-17 15:28:10 +0200203
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100204class test_VIM_network_operations(unittest.TestCase):
205 test_index = 1
206 vim_network_name = None
207 test_text = None
208 vim_network_uuid = None
209
210 @classmethod
211 def setUpClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200212 logger.info("{}. {}".format(test_config["test_number"], cls.__name__))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100213
214 @classmethod
215 def tearDownClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200216 test_config["test_number"] += 1
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100217
218 def tearDown(self):
219 exec_info = sys.exc_info()
220 if exec_info == (None, None, None):
221 logger.info(self.__class__.test_text + " -> TEST OK")
222 else:
223 logger.warning(self.__class__.test_text + " -> TEST NOK")
224 error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2])
225 msg = ""
226 for line in error_trace:
227 msg = msg + line
228 logger.critical("{}".format(msg))
229
230 def test_000_create_VIM_network(self):
tiernoec66e9a2017-05-17 15:28:10 +0200231 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100232 inspect.currentframe().f_code.co_name)
233 self.__class__.vim_network_name = _get_random_string(20)
234 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200235 network = test_config["client"].vim_action("create", "networks", name=self.__class__.vim_network_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100236 logger.debug("{}".format(network))
237 self.__class__.vim_network_uuid = network["network"]["id"]
238 self.assertEqual(network.get('network', {}).get('name', ''), self.__class__.vim_network_name)
239
240 def test_010_list_VIM_networks(self):
tiernoec66e9a2017-05-17 15:28:10 +0200241 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100242 inspect.currentframe().f_code.co_name)
243 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200244 networks = test_config["client"].vim_action("list", "networks")
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100245 logger.debug("{}".format(networks))
246
247 def test_020_get_VIM_network_by_uuid(self):
tiernoec66e9a2017-05-17 15:28:10 +0200248 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100249 inspect.currentframe().f_code.co_name)
250
251 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200252 network = test_config["client"].vim_action("show", "networks", uuid=self.__class__.vim_network_uuid)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100253 logger.debug("{}".format(network))
254 self.assertEqual(network.get('network', {}).get('name', ''), self.__class__.vim_network_name)
255
256 def test_030_delete_VIM_network_by_uuid(self):
tiernoec66e9a2017-05-17 15:28:10 +0200257 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100258 inspect.currentframe().f_code.co_name)
259
260 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200261 network = test_config["client"].vim_action("delete", "networks", uuid=self.__class__.vim_network_uuid)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100262 logger.debug("{}".format(network))
263 assert ('deleted' in network.get('result', ""))
264
tiernoec66e9a2017-05-17 15:28:10 +0200265
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100266class test_VIM_image_operations(unittest.TestCase):
267 test_index = 1
268 test_text = None
269
270 @classmethod
271 def setUpClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200272 logger.info("{}. {}".format(test_config["test_number"], cls.__name__))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100273
274 @classmethod
275 def tearDownClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200276 test_config["test_number"] += 1
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100277
278 def tearDown(self):
279 exec_info = sys.exc_info()
280 if exec_info == (None, None, None):
281 logger.info(self.__class__.test_text + " -> TEST OK")
282 else:
283 logger.warning(self.__class__.test_text + " -> TEST NOK")
284 error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2])
285 msg = ""
286 for line in error_trace:
287 msg = msg + line
288 logger.critical("{}".format(msg))
289
290 def test_000_list_VIM_images(self):
tiernoec66e9a2017-05-17 15:28:10 +0200291 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100292 inspect.currentframe().f_code.co_name)
293 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200294 images = test_config["client"].vim_action("list", "images")
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100295 logger.debug("{}".format(images))
296
297'''
298The following is a non critical test that will fail most of the times.
299In case of OpenStack datacenter these tests will only success if RO has access to the admin endpoint
300This test will only be executed in case it is specifically requested by the user
301'''
302class test_VIM_tenant_operations(unittest.TestCase):
303 test_index = 1
304 vim_tenant_name = None
305 test_text = None
306 vim_tenant_uuid = None
307
308 @classmethod
309 def setUpClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200310 logger.info("{}. {}".format(test_config["test_number"], cls.__name__))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100311 logger.warning("In case of OpenStack datacenter these tests will only success "
312 "if RO has access to the admin endpoint")
313
314 @classmethod
315 def tearDownClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200316 test_config["test_number"] += 1
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100317
318 def tearDown(self):
319 exec_info = sys.exc_info()
320 if exec_info == (None, None, None):
321 logger.info(self.__class__.test_text + " -> TEST OK")
322 else:
323 logger.warning(self.__class__.test_text + " -> TEST NOK")
324 error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2])
325 msg = ""
326 for line in error_trace:
327 msg = msg + line
328 logger.critical("{}".format(msg))
329
330 def test_000_create_VIM_tenant(self):
tiernoec66e9a2017-05-17 15:28:10 +0200331 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100332 inspect.currentframe().f_code.co_name)
333 self.__class__.vim_tenant_name = _get_random_string(20)
334 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200335 tenant = test_config["client"].vim_action("create", "tenants", name=self.__class__.vim_tenant_name)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100336 logger.debug("{}".format(tenant))
337 self.__class__.vim_tenant_uuid = tenant["tenant"]["id"]
338 self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.vim_tenant_name)
339
340 def test_010_list_VIM_tenants(self):
tiernoec66e9a2017-05-17 15:28:10 +0200341 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100342 inspect.currentframe().f_code.co_name)
343 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200344 tenants = test_config["client"].vim_action("list", "tenants")
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100345 logger.debug("{}".format(tenants))
346
347 def test_020_get_VIM_tenant_by_uuid(self):
tiernoec66e9a2017-05-17 15:28:10 +0200348 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100349 inspect.currentframe().f_code.co_name)
350
351 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200352 tenant = test_config["client"].vim_action("show", "tenants", uuid=self.__class__.vim_tenant_uuid)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100353 logger.debug("{}".format(tenant))
354 self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.vim_tenant_name)
355
356 def test_030_delete_VIM_tenant_by_uuid(self):
tiernoec66e9a2017-05-17 15:28:10 +0200357 self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100358 inspect.currentframe().f_code.co_name)
359
360 self.__class__.test_index += 1
tiernoec66e9a2017-05-17 15:28:10 +0200361 tenant = test_config["client"].vim_action("delete", "tenants", uuid=self.__class__.vim_tenant_uuid)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100362 logger.debug("{}".format(tenant))
363 assert ('deleted' in tenant.get('result', ""))
364
365'''
366IMPORTANT NOTE
367The following unittest class does not have the 'test_' on purpose. This test is the one used for the
368scenario based tests.
369'''
370class descriptor_based_scenario_test(unittest.TestCase):
371 test_index = 0
372 test_text = None
373 scenario_test_path = None
374 scenario_uuid = None
375 instance_scenario_uuid = None
376 to_delete_list = []
377
378 @classmethod
379 def setUpClass(cls):
380 cls.test_index = 1
381 cls.to_delete_list = []
tiernoec66e9a2017-05-17 15:28:10 +0200382 cls.scenario_test_path = test_config["test_directory"] + '/' + test_config["test_folder"]
383 logger.info("{}. {} {}".format(test_config["test_number"], cls.__name__, test_config["test_folder"]))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100384
385 @classmethod
386 def tearDownClass(cls):
tiernoec66e9a2017-05-17 15:28:10 +0200387 test_config["test_number"] += 1
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100388
389 def tearDown(self):
390 exec_info = sys.exc_info()
391 if exec_info == (None, None, None):
392 logger.info(self.__class__.test_text + " -> TEST OK")
393 else:
394 logger.warning(self.__class__.test_text + " -> TEST NOK")
395 error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2])
396 msg = ""
397 for line in error_trace:
398 msg = msg + line
399 logger.critical("{}".format(msg))
400
401
402 def test_000_load_scenario(self):
tiernoec66e9a2017-05-17 15:28:10 +0200403 self.__class__.test_text = "{}.{}. TEST {} {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100404 inspect.currentframe().f_code.co_name,
tiernoec66e9a2017-05-17 15:28:10 +0200405 test_config["test_folder"])
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100406 self.__class__.test_index += 1
407 vnfd_files = glob.glob(self.__class__.scenario_test_path+'/vnfd_*.yaml')
408 scenario_file = glob.glob(self.__class__.scenario_test_path + '/scenario_*.yaml')
409 if len(vnfd_files) == 0 or len(scenario_file) > 1:
tiernoec66e9a2017-05-17 15:28:10 +0200410 raise Exception("Test '{}' not valid. It must contain an scenario file and at least one vnfd file'".format(
411 test_config["test_folder"]))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100412
413 #load all vnfd
414 for vnfd in vnfd_files:
415 with open(vnfd, 'r') as stream:
416 vnf_descriptor = yaml.load(stream)
417
418 vnfc_list = vnf_descriptor['vnf']['VNFC']
419 for vnfc in vnfc_list:
tiernoec66e9a2017-05-17 15:28:10 +0200420 vnfc['image name'] = test_config["image_name"]
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100421 devices = vnfc.get('devices',[])
422 for device in devices:
423 if device['type'] == 'disk' and 'image name' in device:
tiernoec66e9a2017-05-17 15:28:10 +0200424 device['image name'] = test_config["image_name"]
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100425
426 logger.debug("VNF descriptor: {}".format(vnf_descriptor))
tiernoec66e9a2017-05-17 15:28:10 +0200427 vnf = test_config["client"].create_vnf(descriptor=vnf_descriptor)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100428 logger.debug(vnf)
tiernoec66e9a2017-05-17 15:28:10 +0200429 self.__class__.to_delete_list.insert(0, {"item": "vnf", "function": test_config["client"].delete_vnf,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100430 "params": {"uuid": vnf['vnf']['uuid']}})
431
432 #load the scenario definition
433 with open(scenario_file[0], 'r') as stream:
434 scenario_descriptor = yaml.load(stream)
435 networks = scenario_descriptor['scenario']['networks']
tiernoec66e9a2017-05-17 15:28:10 +0200436 networks[test_config["mgmt_net"]] = networks.pop('mgmt')
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100437 logger.debug("Scenario descriptor: {}".format(scenario_descriptor))
tiernoec66e9a2017-05-17 15:28:10 +0200438 scenario = test_config["client"].create_scenario(descriptor=scenario_descriptor)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100439 logger.debug(scenario)
tiernoec66e9a2017-05-17 15:28:10 +0200440 self.__class__.to_delete_list.insert(0,{"item": "scenario", "function": test_config["client"].delete_scenario,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100441 "params":{"uuid": scenario['scenario']['uuid']} })
442 self.__class__.scenario_uuid = scenario['scenario']['uuid']
443
444 def test_010_instantiate_scenario(self):
tiernoec66e9a2017-05-17 15:28:10 +0200445 self.__class__.test_text = "{}.{}. TEST {} {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100446 inspect.currentframe().f_code.co_name,
tiernoec66e9a2017-05-17 15:28:10 +0200447 test_config["test_folder"])
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100448 self.__class__.test_index += 1
449
tiernoec66e9a2017-05-17 15:28:10 +0200450 instance = test_config["client"].create_instance(scenario_id=self.__class__.scenario_uuid,
451 name=self.__class__.test_text)
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +0200452 self.__class__.instance_scenario_uuid = instance['uuid']
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100453 logger.debug(instance)
tiernoec66e9a2017-05-17 15:28:10 +0200454 self.__class__.to_delete_list.insert(0, {"item": "instance", "function": test_config["client"].delete_instance,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100455 "params": {"uuid": instance['uuid']}})
456
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +0200457 def test_020_check_deployent(self):
tiernoec66e9a2017-05-17 15:28:10 +0200458 self.__class__.test_text = "{}.{}. TEST {} {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +0200459 inspect.currentframe().f_code.co_name,
tiernoec66e9a2017-05-17 15:28:10 +0200460 test_config["test_folder"])
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +0200461 self.__class__.test_index += 1
462
tiernoec66e9a2017-05-17 15:28:10 +0200463 if test_config["manual"]:
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200464 raw_input('Scenario has been deployed. Perform manual check and press any key to resume')
465 return
466
tiernoec66e9a2017-05-17 15:28:10 +0200467 keep_waiting = test_config["timeout"]
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +0200468 instance_active = False
tierno0e6fcaa2017-05-05 15:54:07 +0200469 while True:
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +0200470 result = check_instance_scenario_active(self.__class__.instance_scenario_uuid)
471 if result[0]:
472 break
473 elif 'ERROR' in result[1]:
474 msg = 'Got error while waiting for the instance to get active: '+result[1]
475 logging.error(msg)
476 raise Exception(msg)
477
tierno0e6fcaa2017-05-05 15:54:07 +0200478 if keep_waiting >= 5:
479 time.sleep(5)
480 keep_waiting -= 5
481 elif keep_waiting > 0:
482 time.sleep(keep_waiting)
483 keep_waiting = 0
484 else:
485 msg = 'Timeout reached while waiting instance scenario to get active'
486 logging.error(msg)
487 raise Exception(msg)
Pablo Montes Moreno2fe24fa2017-03-27 12:56:13 +0200488
489 def test_030_clean_deployment(self):
tiernoec66e9a2017-05-17 15:28:10 +0200490 self.__class__.test_text = "{}.{}. TEST {} {}".format(test_config["test_number"], self.__class__.test_index,
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100491 inspect.currentframe().f_code.co_name,
tiernoec66e9a2017-05-17 15:28:10 +0200492 test_config["test_folder"])
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100493 self.__class__.test_index += 1
494 #At the moment if you delete an scenario right after creating it, in openstack datacenters
495 #sometimes scenario ports get orphaned. This sleep is just a dirty workaround
496 time.sleep(5)
497 for item in self.__class__.to_delete_list:
498 response = item["function"](**item["params"])
499 logger.debug(response)
500
tiernoec66e9a2017-05-17 15:28:10 +0200501
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100502def _get_random_string(maxLength):
503 '''generates a string with random characters string.letters and string.digits
504 with a random length up to maxLength characters. If maxLength is <15 it will be changed automatically to 15
505 '''
506 prefix = 'testing_'
507 min_string = 15
508 minLength = min_string - len(prefix)
509 if maxLength < min_string: maxLength = min_string
510 maxLength -= len(prefix)
511 length = random.randint(minLength,maxLength)
512 return 'testing_'+"".join([random.choice(string.letters+string.digits) for i in xrange(length)])
513
tiernoec66e9a2017-05-17 15:28:10 +0200514
515def test_vimconnector(args):
516 global test_config
tierno0e6fcaa2017-05-05 15:54:07 +0200517 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/osm_ro")
tiernoec66e9a2017-05-17 15:28:10 +0200518 if args.vimtype == "vmware":
519 import vimconn_vmware as vim
520 elif args.vimtype == "aws":
521 import vimconn_aws as vim
522 elif args.vimtype == "openstack":
523 import vimconn_openstack as vim
524 elif args.vimtype == "openvim":
525 import vimconn_openvim as vim
526 else:
527 logger.critical("vimtype '{}' not supported".format(args.vimtype))
528 sys.exit(1)
529 executed = 0
530 failed = 0
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100531 clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100532 # If only want to obtain a tests list print it and exit
tiernoec66e9a2017-05-17 15:28:10 +0200533 if args.list_tests:
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100534 tests_names = []
535 for cls in clsmembers:
tiernoec66e9a2017-05-17 15:28:10 +0200536 if cls[0].startswith('test_vimconnector'):
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100537 tests_names.append(cls[0])
538
tiernoec66e9a2017-05-17 15:28:10 +0200539 msg = "The 'vim' set tests are:\n\t" + ', '.join(sorted(tests_names))
540 print(msg)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100541 logger.info(msg)
542 sys.exit(0)
543
tierno0e6fcaa2017-05-05 15:54:07 +0200544 # Create the list of tests to be run
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100545 code_based_tests = []
tiernoec66e9a2017-05-17 15:28:10 +0200546 if args.tests:
547 for test in args.tests:
548 for t in test.split(','):
549 matches_code_based_tests = [item for item in clsmembers if item[0] == t]
550 if len(matches_code_based_tests) > 0:
551 code_based_tests.append(matches_code_based_tests[0][1])
552 else:
553 logger.critical("Test '{}' is not among the possible ones".format(t))
554 sys.exit(1)
555 if not code_based_tests:
tierno0e6fcaa2017-05-05 15:54:07 +0200556 # include all tests
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100557 for cls in clsmembers:
tierno0e6fcaa2017-05-05 15:54:07 +0200558 # We exclude 'test_VIM_tenant_operations' unless it is specifically requested by the user
tiernoec66e9a2017-05-17 15:28:10 +0200559 if cls[0].startswith('test_vimconnector'):
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100560 code_based_tests.append(cls[1])
561
tiernoec66e9a2017-05-17 15:28:10 +0200562 logger.debug("tests to be executed: {}".format(code_based_tests))
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100563
564 # TextTestRunner stream is set to /dev/null in order to avoid the method to directly print the result of tests.
565 # This is handled in the tests using logging.
566 stream = open('/dev/null', 'w')
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100567
tierno0e6fcaa2017-05-05 15:54:07 +0200568 # Run code based tests
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100569 basic_tests_suite = unittest.TestSuite()
570 for test in code_based_tests:
571 basic_tests_suite.addTest(unittest.makeSuite(test))
tierno0e6fcaa2017-05-05 15:54:07 +0200572 result = unittest.TextTestRunner(stream=stream, failfast=failfast).run(basic_tests_suite)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100573 executed += result.testsRun
574 failed += len(result.failures) + len(result.errors)
tierno0e6fcaa2017-05-05 15:54:07 +0200575 if failfast and failed:
576 sys.exit(1)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100577 if len(result.failures) > 0:
578 logger.debug("failures : {}".format(result.failures))
579 if len(result.errors) > 0:
580 logger.debug("errors : {}".format(result.errors))
tiernoec66e9a2017-05-17 15:28:10 +0200581 return executed, failed
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100582
tiernoec66e9a2017-05-17 15:28:10 +0200583
584def test_vim(args):
585 global test_config
586 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/osm_ro")
587 import openmanoclient
588 executed = 0
589 failed = 0
590 test_config["client"] = openmanoclient.openmanoclient(
591 endpoint_url=args.endpoint_url,
592 tenant_name=args.tenant_name,
593 datacenter_name=args.datacenter,
594 debug=args.debug, logger=test_config["logger_name"])
595 clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass)
596 # If only want to obtain a tests list print it and exit
597 if args.list_tests:
598 tests_names = []
599 for cls in clsmembers:
600 if cls[0].startswith('test_VIM'):
601 tests_names.append(cls[0])
602
603 msg = "The 'vim' set tests are:\n\t" + ', '.join(sorted(tests_names)) +\
604 "\nNOTE: The test test_VIM_tenant_operations will fail in case the used datacenter is type OpenStack " \
605 "unless RO has access to the admin endpoint. Therefore this test is excluded by default"
606 print(msg)
607 logger.info(msg)
608 sys.exit(0)
609
610 # Create the list of tests to be run
611 code_based_tests = []
612 if args.tests:
613 for test in args.tests:
614 for t in test.split(','):
615 matches_code_based_tests = [item for item in clsmembers if item[0] == t]
616 if len(matches_code_based_tests) > 0:
617 code_based_tests.append(matches_code_based_tests[0][1])
618 else:
619 logger.critical("Test '{}' is not among the possible ones".format(t))
620 sys.exit(1)
621 if not code_based_tests:
622 # include all tests
623 for cls in clsmembers:
624 # We exclude 'test_VIM_tenant_operations' unless it is specifically requested by the user
625 if cls[0].startswith('test_VIM') and cls[0] != 'test_VIM_tenant_operations':
626 code_based_tests.append(cls[1])
627
628 logger.debug("tests to be executed: {}".format(code_based_tests))
629
630 # TextTestRunner stream is set to /dev/null in order to avoid the method to directly print the result of tests.
631 # This is handled in the tests using logging.
632 stream = open('/dev/null', 'w')
633
634 # Run code based tests
635 basic_tests_suite = unittest.TestSuite()
636 for test in code_based_tests:
637 basic_tests_suite.addTest(unittest.makeSuite(test))
638 result = unittest.TextTestRunner(stream=stream, failfast=failfast).run(basic_tests_suite)
639 executed += result.testsRun
640 failed += len(result.failures) + len(result.errors)
641 if failfast and failed:
642 sys.exit(1)
643 if len(result.failures) > 0:
644 logger.debug("failures : {}".format(result.failures))
645 if len(result.errors) > 0:
646 logger.debug("errors : {}".format(result.errors))
647 return executed, failed
648
649
650def test_deploy(args):
651 global test_config
652 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/osm_ro")
653 import openmanoclient
654 executed = 0
655 failed = 0
656 test_config["test_directory"] = os.path.dirname(__file__) + "/RO_tests"
657 test_config["image_name"] = args.image_name
658 test_config["mgmt_net"] = args.mgmt_net
659 test_config["manual"] = args.manual
660 test_directory_content = os.listdir(test_config["test_directory"])
661 # If only want to obtain a tests list print it and exit
662 if args.list_tests:
663 msg = "he 'deploy' set tests are:\n\t" + ', '.join(sorted(test_directory_content))
664 print(msg)
665 logger.info(msg)
666 sys.exit(0)
667
668 descriptor_based_tests = []
669 # Create the list of tests to be run
670 code_based_tests = []
671 if args.tests:
672 for test in args.tests:
673 for t in test.split(','):
674 if t in test_directory_content:
675 descriptor_based_tests.append(t)
676 else:
677 logger.critical("Test '{}' is not among the possible ones".format(t))
678 sys.exit(1)
679 if not descriptor_based_tests:
680 # include all tests
681 descriptor_based_tests = test_directory_content
682
683 logger.debug("tests to be executed: {}".format(code_based_tests))
684
685 # import openmanoclient from relative path
686 test_config["client"] = openmanoclient.openmanoclient(
687 endpoint_url=args.endpoint_url,
688 tenant_name=args.tenant_name,
689 datacenter_name=args.datacenter,
690 debug=args.debug, logger=test_config["logger_name"])
691
692 # TextTestRunner stream is set to /dev/null in order to avoid the method to directly print the result of tests.
693 # This is handled in the tests using logging.
694 stream = open('/dev/null', 'w')
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100695 # This scenario based tests are defined as directories inside the directory defined in 'test_directory'
696 for test in descriptor_based_tests:
tiernoec66e9a2017-05-17 15:28:10 +0200697 test_config["test_folder"] = test
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100698 test_suite = unittest.TestSuite()
699 test_suite.addTest(unittest.makeSuite(descriptor_based_scenario_test))
tierno0e6fcaa2017-05-05 15:54:07 +0200700 result = unittest.TextTestRunner(stream=stream, failfast=False).run(test_suite)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100701 executed += result.testsRun
702 failed += len(result.failures) + len(result.errors)
tierno0e6fcaa2017-05-05 15:54:07 +0200703 if failfast and failed:
704 sys.exit(1)
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100705 if len(result.failures) > 0:
706 logger.debug("failures : {}".format(result.failures))
707 if len(result.errors) > 0:
708 logger.debug("errors : {}".format(result.errors))
709
tiernoec66e9a2017-05-17 15:28:10 +0200710 return executed, failed
711
712if __name__=="__main__":
713
714 parser = ArgumentParser(description='Test RO module')
715 parser.add_argument('-v','--version', action='version', help="Show current version",
716 version='%(prog)s version ' + __version__ + ' ' + version_date)
717
718 # Common parameters
719 parent_parser = ArgumentParser(add_help=False)
720 parent_parser.add_argument('--failfast', help='Stop when a test fails rather than execute all tests',
721 dest='failfast', action="store_true", default=False)
722 parent_parser.add_argument('--failed', help='Set logs to show only failed tests. --debug disables this option',
723 dest='failed', action="store_true", default=False)
724 default_logger_file = os.path.dirname(__file__)+'/'+os.path.splitext(os.path.basename(__file__))[0]+'.log'
725 parent_parser.add_argument('--list-tests', help='List all available tests', dest='list_tests', action="store_true",
726 default=False)
727 parent_parser.add_argument('--logger_file', dest='logger_file', default=default_logger_file,
728 help='Set the logger file. By default '+default_logger_file)
729 parent_parser.add_argument("-t", '--tenant', dest='tenant_name', default="osm",
730 help="Set the openmano tenant to use for the test. By default 'osm'")
731 parent_parser.add_argument('--debug', help='Set logs to debug level', dest='debug', action="store_true")
732 parent_parser.add_argument('--timeout', help='Specify the instantiation timeout in seconds. By default 300',
733 dest='timeout', type=int, default=300)
734 parent_parser.add_argument('--test', '--tests', help='Specify the tests to run', dest='tests', action="append")
735
736 subparsers = parser.add_subparsers(help='test sets')
737
738 # Deployment test set
739 # -------------------
740 deploy_parser = subparsers.add_parser('deploy', parents=[parent_parser],
741 help="test deployment using descriptors at RO_test folder ")
742 deploy_parser.set_defaults(func=test_deploy)
743
744 # Mandatory arguments
745 mandatory_arguments = deploy_parser.add_argument_group('mandatory arguments')
746 mandatory_arguments.add_argument('-d', '--datacenter', required=True, help='Set the datacenter to test')
747 mandatory_arguments.add_argument("-i", '--image-name', required=True, dest="image_name",
748 help='Image name available at datacenter used for the tests')
749 mandatory_arguments.add_argument("-n", '--mgmt-net-name', required=True, dest='mgmt_net',
750 help='Set the vim management network to use for tests')
751
752 # Optional arguments
753 deploy_parser.add_argument('-m', '--manual-check', dest='manual', action="store_true", default=False,
754 help='Pause execution once deployed to allow manual checking of the '
755 'deployed instance scenario')
756 deploy_parser.add_argument('-u', '--url', dest='endpoint_url', default='http://localhost:9090/openmano',
757 help="Set the openmano server url. By default 'http://localhost:9090/openmano'")
758
759 # Vimconn test set
760 # -------------------
761 vimconn_parser = subparsers.add_parser('vimconn', parents=[parent_parser], help="test vimconnector plugin")
762 vimconn_parser.set_defaults(func=test_vimconnector)
763 # Mandatory arguments
764 mandatory_arguments = vimconn_parser.add_argument_group('mandatory arguments')
765 mandatory_arguments.add_argument('--vimtype', choices=['vmware', 'aws', 'openstack', 'openvim'], required=True,
766 help='Set the vimconnector type to test')
767 # TODO add mandatory arguments for vimconn test
768 # mandatory_arguments.add_argument('-c', '--config', dest='config_param', required=True, help='<HELP>')
769
770 # Optional arguments
771 # TODO add optional arguments for vimconn tests
772 # vimconn_parser.add_argument("-i", '--image-name', dest='image_name', help='<HELP>'))
773
774 # Datacenter test set
775 # -------------------
776 vimconn_parser = subparsers.add_parser('vim', parents=[parent_parser], help="test vim")
777 vimconn_parser.set_defaults(func=test_vim)
778
779 # Mandatory arguments
780 mandatory_arguments = vimconn_parser.add_argument_group('mandatory arguments')
781 mandatory_arguments.add_argument('-d', '--datacenter', required=True, help='Set the datacenter to test')
782
783 # Optional arguments
784 vimconn_parser.add_argument('-u', '--url', dest='endpoint_url', default='http://localhost:9090/openmano',
785 help="Set the openmano server url. By default 'http://localhost:9090/openmano'")
786
787 argcomplete.autocomplete(parser)
788 args = parser.parse_args()
789 # print str(args)
790 test_config = {}
791
792 # default logger level is INFO. Options --debug and --failed override this, being --debug prioritary
793 logger_level = 'INFO'
794 if args.debug:
795 logger_level = 'DEBUG'
796 elif args.failed:
797 logger_level = 'WARNING'
798 logger_name = os.path.basename(__file__)
799 test_config["logger_name"] = logger_name
800 logger = logging.getLogger(logger_name)
801 logger.setLevel(logger_level)
802 failfast = args.failfast
803
804 # Configure a logging handler to store in a logging file
805 if args.logger_file:
806 fileHandler = logging.FileHandler(args.logger_file)
807 formatter_fileHandler = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')
808 fileHandler.setFormatter(formatter_fileHandler)
809 logger.addHandler(fileHandler)
810
811 # Configure a handler to print to stdout
812 consoleHandler = logging.StreamHandler(sys.stdout)
813 formatter_consoleHandler = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')
814 consoleHandler.setFormatter(formatter_consoleHandler)
815 logger.addHandler(consoleHandler)
816
817 logger.debug('Program started with the following arguments: ' + str(args))
818
819 # set test config parameters
820 test_config["timeout"] = args.timeout
821 test_config["test_number"] = 1
822
823 executed, failed = args.func(args)
824
tierno0e6fcaa2017-05-05 15:54:07 +0200825 # Log summary
Pablo Montes Morenoeebea062017-02-27 12:33:10 +0100826 logger.warning("Total number of tests: {}; Total number of failures/errors: {}".format(executed, failed))
tierno0e6fcaa2017-05-05 15:54:07 +0200827 sys.exit(1 if failed else 0)