Merge "Fix report generation"
[yardstick.git] / yardstick / tests / unit / benchmark / core / test_task.py
1 ##############################################################################
2 # Copyright (c) 2015 Huawei Technologies Co.,Ltd and others.
3 #
4 # All rights reserved. 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 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 import copy
11 import io
12 import os
13 import sys
14
15 import mock
16 import six
17 import unittest
18 import uuid
19
20 from yardstick.benchmark.contexts import dummy
21 from yardstick.benchmark.core import task
22 from yardstick.common import constants as consts
23 from yardstick.common import exceptions
24 from yardstick.common import task_template
25 from yardstick.common import utils
26
27
28 class TaskTestCase(unittest.TestCase):
29
30     @mock.patch.object(task, 'Context')
31     def test_parse_nodes_with_context_same_context(self, mock_context):
32         scenario_cfg = {
33             "nodes": {
34                 "host": "node1.LF",
35                 "target": "node2.LF"
36             }
37         }
38         server_info = {
39             "ip": "10.20.0.3",
40             "user": "root",
41             "key_filename": "/root/.ssh/id_rsa"
42         }
43         mock_context.get_server.return_value = server_info
44
45         context_cfg = task.parse_nodes_with_context(scenario_cfg)
46
47         self.assertEqual(context_cfg["host"], server_info)
48         self.assertEqual(context_cfg["target"], server_info)
49
50     def test_set_dispatchers(self):
51         t = task.Task()
52         output_config = {"DEFAULT": {"dispatcher": "file, http"}}
53         t._set_dispatchers(output_config)
54         self.assertEqual(output_config, output_config)
55
56     @mock.patch.object(task, 'DispatcherBase')
57     def test__do_output(self, mock_dispatcher):
58         t = task.Task()
59         output_config = {"DEFAULT": {"dispatcher": "file, http"}}
60
61         dispatcher1 = mock.MagicMock()
62         dispatcher1.__dispatcher_type__ = 'file'
63
64         dispatcher2 = mock.MagicMock()
65         dispatcher2.__dispatcher_type__ = 'http'
66
67         mock_dispatcher.get = mock.MagicMock(return_value=[dispatcher1,
68                                                            dispatcher2])
69         self.assertIsNone(t._do_output(output_config, {}))
70
71     @mock.patch.object(task, 'Context')
72     def test_parse_networks_from_nodes(self, mock_context):
73         nodes = {
74             'node1': {
75                 'interfaces': {
76                     'mgmt': {
77                         'network_name': 'mgmt',
78                     },
79                     'xe0': {
80                         'network_name': 'uplink_0',
81                     },
82                     'xe1': {
83                         'network_name': 'downlink_0',
84                     },
85                 },
86             },
87             'node2': {
88                 'interfaces': {
89                     'mgmt': {
90                         'network_name': 'mgmt',
91                     },
92                     'uplink_0': {
93                         'network_name': 'uplink_0',
94                     },
95                     'downlink_0': {
96                         'network_name': 'downlink_0',
97                     },
98                 },
99             },
100         }
101
102         mock_context.get_network.side_effect = iter([
103             None,
104             {
105                 'name': 'mgmt',
106                 'network_type': 'flat',
107             },
108             {},
109             {
110                 'name': 'uplink_0',
111                 'subnet_cidr': '10.20.0.0/16',
112             },
113             {
114                 'name': 'downlink_0',
115                 'segmentation_id': '1001',
116             },
117             {
118                 'name': 'uplink_1',
119             },
120         ])
121
122         # one for each interface
123         expected_get_network_calls = 6
124         expected = {
125             'mgmt': {'name': 'mgmt', 'network_type': 'flat'},
126             'uplink_0': {'name': 'uplink_0', 'subnet_cidr': '10.20.0.0/16'},
127             'uplink_1': {'name': 'uplink_1'},
128             'downlink_0': {'name': 'downlink_0', 'segmentation_id': '1001'},
129         }
130
131         networks = task.get_networks_from_nodes(nodes)
132         self.assertEqual(mock_context.get_network.call_count, expected_get_network_calls)
133         self.assertDictEqual(networks, expected)
134
135     @mock.patch.object(task, 'Context')
136     @mock.patch.object(task, 'base_runner')
137     def test_run(self, mock_base_runner, *args):
138         scenario = {
139             'host': 'athena.demo',
140             'target': 'ares.demo',
141             'runner': {
142                 'duration': 60,
143                 'interval': 1,
144                 'type': 'Duration'
145             },
146             'type': 'Ping'
147         }
148
149         t = task.Task()
150         runner = mock.Mock()
151         runner.join.return_value = 0
152         runner.get_output.return_value = {}
153         runner.get_result.return_value = []
154         mock_base_runner.Runner.get.return_value = runner
155         t._run([scenario], False, "yardstick.out")
156         self.assertTrue(runner.run.called)
157
158     @mock.patch.object(os, 'environ')
159     def test_check_precondition(self, mock_os_environ):
160         cfg = {
161             'precondition': {
162                 'installer_type': 'compass',
163                 'deploy_scenarios': 'os-nosdn',
164                 'pod_name': 'huawei-pod1'
165             }
166         }
167
168         t = task.TaskParser('/opt')
169         mock_os_environ.get.side_effect = ['compass',
170                                            'os-nosdn',
171                                            'huawei-pod1']
172         result = t._check_precondition(cfg)
173         self.assertTrue(result)
174
175     def test_parse_suite_no_constraint_no_args(self):
176         SAMPLE_SCENARIO_PATH = "no_constraint_no_args_scenario_sample.yaml"
177         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
178         with mock.patch.object(os, 'environ',
179                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
180             task_files, task_args, task_args_fnames = t.parse_suite()
181
182         self.assertEqual(task_files[0], self.change_to_abspath(
183                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
184         self.assertEqual(task_files[1], self.change_to_abspath(
185                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
186         self.assertIsNone(task_args[0])
187         self.assertIsNone(task_args[1])
188         self.assertIsNone(task_args_fnames[0])
189         self.assertIsNone(task_args_fnames[1])
190
191     def test_parse_suite_no_constraint_with_args(self):
192         SAMPLE_SCENARIO_PATH = "no_constraint_with_args_scenario_sample.yaml"
193         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
194         with mock.patch.object(os, 'environ',
195                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
196             task_files, task_args, task_args_fnames = t.parse_suite()
197
198         self.assertEqual(task_files[0], self.change_to_abspath(
199                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
200         self.assertEqual(task_files[1], self.change_to_abspath(
201                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
202         self.assertIsNone(task_args[0])
203         self.assertEqual(task_args[1],
204                          '{"host": "node1.LF","target": "node2.LF"}')
205         self.assertIsNone(task_args_fnames[0])
206         self.assertIsNone(task_args_fnames[1])
207
208     def test_parse_suite_with_constraint_no_args(self):
209         SAMPLE_SCENARIO_PATH = "with_constraint_no_args_scenario_sample.yaml"
210         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
211         with mock.patch.object(os, 'environ',
212                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
213             task_files, task_args, task_args_fnames = t.parse_suite()
214         self.assertEqual(task_files[0], self.change_to_abspath(
215                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
216         self.assertEqual(task_files[1], self.change_to_abspath(
217                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
218         self.assertIsNone(task_args[0])
219         self.assertIsNone(task_args[1])
220         self.assertIsNone(task_args_fnames[0])
221         self.assertIsNone(task_args_fnames[1])
222
223     def test_parse_suite_with_constraint_with_args(self):
224         SAMPLE_SCENARIO_PATH = "with_constraint_with_args_scenario_sample.yaml"
225         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
226         with mock.patch('os.environ',
227                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
228             task_files, task_args, task_args_fnames = t.parse_suite()
229
230         self.assertEqual(task_files[0], self.change_to_abspath(
231                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
232         self.assertEqual(task_files[1], self.change_to_abspath(
233                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
234         self.assertIsNone(task_args[0])
235         self.assertEqual(task_args[1],
236                          '{"host": "node1.LF","target": "node2.LF"}')
237         self.assertIsNone(task_args_fnames[0])
238         self.assertIsNone(task_args_fnames[1])
239
240     def test_parse_options(self):
241         options = {
242             'openstack': {
243                 'EXTERNAL_NETWORK': '$network'
244             },
245             'nodes': ['node1', '$node'],
246             'host': '$host'
247         }
248
249         t = task.Task()
250         t.outputs = {
251             'network': 'ext-net',
252             'node': 'node2',
253             'host': 'server.yardstick'
254         }
255
256         expected_result = {
257             'openstack': {
258                 'EXTERNAL_NETWORK': 'ext-net'
259             },
260             'nodes': ['node1', 'node2'],
261             'host': 'server.yardstick'
262         }
263
264         actual_result = t._parse_options(options)
265         self.assertEqual(expected_result, actual_result)
266
267     def test_parse_options_no_teardown(self):
268         options = {
269             'openstack': {
270                 'EXTERNAL_NETWORK': '$network'
271             },
272             'nodes': ['node1', '$node'],
273             'host': '$host',
274             'contexts' : {'name': "my-context",
275                           'no_teardown': True}
276         }
277
278         t = task.Task()
279         t.outputs = {
280             'network': 'ext-net',
281             'node': 'node2',
282             'host': 'server.yardstick'
283         }
284
285         expected_result = {
286             'openstack': {
287                 'EXTERNAL_NETWORK': 'ext-net'
288             },
289             'nodes': ['node1', 'node2'],
290             'host': 'server.yardstick',
291             'contexts': {'name': 'my-context',
292                          'no_teardown': True,
293                         }
294         }
295
296         actual_result = t._parse_options(options)
297         self.assertEqual(expected_result, actual_result)
298
299     @mock.patch('six.moves.builtins.open', side_effect=mock.mock_open())
300     @mock.patch.object(task, 'utils')
301     @mock.patch('logging.root')
302     def test_set_log(self, mock_logging_root, *args):
303         task_obj = task.Task()
304         task_obj.task_id = 'task_id'
305         task_obj._set_log()
306         mock_logging_root.addHandler.assert_called()
307
308     def _get_file_abspath(self, filename):
309         curr_path = os.path.dirname(os.path.abspath(__file__))
310         file_path = os.path.join(curr_path, filename)
311         return file_path
312
313     def change_to_abspath(self, filepath):
314         return os.path.join(consts.YARDSTICK_ROOT_PATH, filepath)
315
316
317 class TaskParserTestCase(unittest.TestCase):
318
319     TASK = """
320 {% set value1 = value1 or 'var1' %}
321 {% set value2 = value2 or 'var2' %}
322 key1: {{ value1 }}
323 key2:
324     - {{ value2 }}"""
325
326     TASK_RENDERED_1 = u"""
327
328
329 key1: var1
330 key2:
331     - var2"""
332
333     TASK_RENDERED_2 = u"""
334
335
336 key1: var3
337 key2:
338     - var4"""
339
340     def setUp(self):
341         self.parser = task.TaskParser('fake/path')
342         self.scenario = {
343             'host': 'athena.demo',
344             'target': 'kratos.demo',
345             'targets': [
346                 'ares.demo', 'mars.demo'
347                 ],
348             'options': {
349                 'server_name': {
350                     'host': 'jupiter.demo',
351                     'target': 'saturn.demo',
352                     },
353                 },
354             'nodes': {
355                 'tg__0': 'tg_0.demo',
356                 'vnf__0': 'vnf_0.demo',
357                 }
358             }
359
360     def test__change_node_names(self):
361
362         ctx_attrs = {
363             'name': 'demo',
364             'task_id': '1234567890',
365             'servers': [
366                 'athena', 'kratos',
367                 'ares', 'mars',
368                 'jupiter', 'saturn',
369                 'tg_0', 'vnf_0'
370                 ]
371             }
372
373         my_context = dummy.DummyContext()
374         my_context.init(ctx_attrs)
375
376         expected_scenario = {
377             'host': 'athena.demo-12345678',
378             'target': 'kratos.demo-12345678',
379             'targets': [
380                 'ares.demo-12345678', 'mars.demo-12345678'
381                 ],
382             'options': {
383                 'server_name': {
384                     'host': 'jupiter.demo-12345678',
385                     'target': 'saturn.demo-12345678',
386                     },
387                 },
388             'nodes': {
389                 'tg__0': 'tg_0.demo-12345678',
390                 'vnf__0': 'vnf_0.demo-12345678',
391                 }
392             }
393
394         scenario = copy.deepcopy(self.scenario)
395
396         self.parser._change_node_names(scenario, [my_context])
397         self.assertEqual(scenario, expected_scenario)
398
399     def test__change_node_names_context_not_found(self):
400         scenario = copy.deepcopy(self.scenario)
401         self.assertRaises(exceptions.ScenarioConfigContextNameNotFound,
402                           self.parser._change_node_names,
403                           scenario, [])
404
405     def test__change_node_names_context_name_unchanged(self):
406         ctx_attrs = {
407             'name': 'demo',
408             'task_id': '1234567890',
409             'flags': {
410                 'no_setup': True,
411                 'no_teardown': True
412                 }
413             }
414
415         my_context = dummy.DummyContext()
416         my_context.init(ctx_attrs)
417
418         scenario = copy.deepcopy(self.scenario)
419         expected_scenario = copy.deepcopy(self.scenario)
420
421         self.parser._change_node_names(scenario, [my_context])
422         self.assertEqual(scenario, expected_scenario)
423
424     def test__parse_tasks(self):
425         task_obj = task.Task()
426         _uuid = uuid.uuid4()
427         task_obj.task_id = _uuid
428         task_files = ['/directory/task_file_name.yml']
429         mock_parser = mock.Mock()
430         mock_parser.parse_task.return_value = {'rendered': 'File content'}
431         mock_args = mock.Mock()
432         mock_args.render_only = False
433
434         tasks = task_obj._parse_tasks(mock_parser, task_files, mock_args,
435                                       ['arg1'], ['file_arg1'])
436         self.assertEqual(
437             [{'rendered': 'File content', 'case_name': 'task_file_name'}],
438             tasks)
439         mock_parser.parse_task.assert_called_once_with(
440             _uuid, 'arg1', 'file_arg1')
441
442     @mock.patch.object(sys, 'exit')
443     @mock.patch.object(utils, 'write_file')
444     @mock.patch.object(utils, 'makedirs')
445     def test__parse_tasks_render_only(self, mock_makedirs, mock_write_file,
446                                       mock_exit):
447         task_obj = task.Task()
448         _uuid = uuid.uuid4()
449         task_obj.task_id = _uuid
450         task_files = ['/directory/task_file_name.yml']
451         mock_parser = mock.Mock()
452         mock_parser.parse_task.return_value = {'rendered': 'File content'}
453         mock_args = mock.Mock()
454         mock_args.render_only = '/output_directory'
455
456         task_obj._parse_tasks(mock_parser, task_files, mock_args,
457                               ['arg1'], ['file_arg1'])
458         mock_makedirs.assert_called_once_with('/output_directory')
459         mock_write_file.assert_called_once_with(
460             '/output_directory/000-task_file_name.yml', 'File content')
461         mock_exit.assert_called_once_with(0)
462
463     def test__render_task_no_args(self):
464         task_parser = task.TaskParser('task_file')
465         task_str = io.StringIO(six.text_type(self.TASK))
466         with mock.patch.object(six.moves.builtins, 'open',
467                                return_value=task_str) as mock_open:
468             parsed, rendered = task_parser._render_task(None, None)
469
470         self.assertEqual(self.TASK_RENDERED_1, rendered)
471         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
472         mock_open.assert_called_once_with('task_file')
473
474     def test__render_task_arguments(self):
475         task_parser = task.TaskParser('task_file')
476         task_str = io.StringIO(six.text_type(self.TASK))
477         with mock.patch.object(six.moves.builtins, 'open',
478                                return_value=task_str) as mock_open:
479             parsed, rendered = task_parser._render_task('value1: "var1"', None)
480
481         self.assertEqual(self.TASK_RENDERED_1, rendered)
482         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
483         mock_open.assert_called_once_with('task_file')
484
485     def test__render_task_file_arguments(self):
486         task_parser = task.TaskParser('task_file')
487         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
488             mock_open.side_effect = (
489                 io.StringIO(six.text_type('value2: var4')),
490                 io.StringIO(six.text_type(self.TASK))
491             )
492             parsed, rendered = task_parser._render_task('value1: "var3"',
493                                                         'args_file')
494
495         self.assertEqual(self.TASK_RENDERED_2, rendered)
496         self.assertEqual({'key1': 'var3', 'key2': ['var4']}, parsed)
497         mock_open.assert_has_calls([mock.call('args_file'),
498                                     mock.call('task_file')])
499
500     def test__render_task_error_arguments(self):
501         with self.assertRaises(exceptions.TaskRenderArgumentError):
502             task.TaskParser('task_file')._render_task('value1="var3"', None)
503
504     def test__render_task_error_task_file(self):
505         task_parser = task.TaskParser('task_file')
506         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
507             mock_open.side_effect = (
508                 io.StringIO(six.text_type('value2: var4')),
509                 IOError()
510             )
511             with self.assertRaises(exceptions.TaskReadError):
512                 task_parser._render_task('value1: "var3"', 'args_file')
513
514         mock_open.assert_has_calls([mock.call('args_file'),
515                                     mock.call('task_file')])
516
517     def test__render_task_render_error(self):
518         task_parser = task.TaskParser('task_file')
519         with mock.patch.object(six.moves.builtins, 'open') as mock_open, \
520                 mock.patch.object(task_template.TaskTemplate, 'render',
521                                   side_effect=TypeError) as mock_render:
522             mock_open.side_effect = (
523                 io.StringIO(six.text_type('value2: var4')),
524                 io.StringIO(six.text_type(self.TASK))
525             )
526             with self.assertRaises(exceptions.TaskRenderError):
527                 task_parser._render_task('value1: "var3"', 'args_file')
528
529         mock_open.assert_has_calls([mock.call('args_file'),
530                                     mock.call('task_file')])
531         mock_render.assert_has_calls(
532             [mock.call(self.TASK, value1='var3', value2='var4')])