9e8e4e9f7dba39bd4b99e27799a4150182d15b93
[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         runner.run.assert_called_once()
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__change_node_names_options_empty(self):
425         ctx_attrs = {
426             'name': 'demo',
427             'task_id': '1234567890'
428         }
429
430         my_context = dummy.DummyContext()
431         my_context.init(ctx_attrs)
432         scenario = copy.deepcopy(self.scenario)
433         scenario['options'] = None
434
435         self.parser._change_node_names(scenario, [my_context])
436         self.assertIsNone(scenario['options'])
437
438     def test__change_node_names_options_server_name_empty(self):
439         ctx_attrs = {
440             'name': 'demo',
441             'task_id': '1234567890'
442         }
443
444         my_context = dummy.DummyContext()
445         my_context.init(ctx_attrs)
446         scenario = copy.deepcopy(self.scenario)
447         scenario['options']['server_name'] = None
448
449         self.parser._change_node_names(scenario, [my_context])
450         self.assertIsNone(scenario['options']['server_name'])
451
452     def test__parse_tasks(self):
453         task_obj = task.Task()
454         _uuid = uuid.uuid4()
455         task_obj.task_id = _uuid
456         task_files = ['/directory/task_file_name.yml']
457         mock_parser = mock.Mock()
458         mock_parser.parse_task.return_value = {'rendered': 'File content'}
459         mock_args = mock.Mock()
460         mock_args.render_only = False
461
462         tasks = task_obj._parse_tasks(mock_parser, task_files, mock_args,
463                                       ['arg1'], ['file_arg1'])
464         self.assertEqual(
465             [{'rendered': 'File content', 'case_name': 'task_file_name'}],
466             tasks)
467         mock_parser.parse_task.assert_called_once_with(
468             _uuid, 'arg1', 'file_arg1')
469
470     @mock.patch.object(sys, 'exit')
471     @mock.patch.object(utils, 'write_file')
472     @mock.patch.object(utils, 'makedirs')
473     def test__parse_tasks_render_only(self, mock_makedirs, mock_write_file,
474                                       mock_exit):
475         task_obj = task.Task()
476         _uuid = uuid.uuid4()
477         task_obj.task_id = _uuid
478         task_files = ['/directory/task_file_name.yml']
479         mock_parser = mock.Mock()
480         mock_parser.parse_task.return_value = {'rendered': 'File content'}
481         mock_args = mock.Mock()
482         mock_args.render_only = '/output_directory'
483
484         task_obj._parse_tasks(mock_parser, task_files, mock_args,
485                               ['arg1'], ['file_arg1'])
486         mock_makedirs.assert_called_once_with('/output_directory')
487         mock_write_file.assert_called_once_with(
488             '/output_directory/000-task_file_name.yml', 'File content')
489         mock_exit.assert_called_once_with(0)
490
491     def test__render_task_no_args(self):
492         task_parser = task.TaskParser('task_file')
493         task_str = io.StringIO(six.text_type(self.TASK))
494         with mock.patch.object(six.moves.builtins, 'open',
495                                return_value=task_str) as mock_open:
496             parsed, rendered = task_parser._render_task(None, None)
497
498         self.assertEqual(self.TASK_RENDERED_1, rendered)
499         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
500         mock_open.assert_called_once_with('task_file')
501
502     def test__render_task_arguments(self):
503         task_parser = task.TaskParser('task_file')
504         task_str = io.StringIO(six.text_type(self.TASK))
505         with mock.patch.object(six.moves.builtins, 'open',
506                                return_value=task_str) as mock_open:
507             parsed, rendered = task_parser._render_task('value1: "var1"', None)
508
509         self.assertEqual(self.TASK_RENDERED_1, rendered)
510         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
511         mock_open.assert_called_once_with('task_file')
512
513     def test__render_task_file_arguments(self):
514         task_parser = task.TaskParser('task_file')
515         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
516             mock_open.side_effect = (
517                 io.StringIO(six.text_type('value2: var4')),
518                 io.StringIO(six.text_type(self.TASK))
519             )
520             parsed, rendered = task_parser._render_task('value1: "var3"',
521                                                         'args_file')
522
523         self.assertEqual(self.TASK_RENDERED_2, rendered)
524         self.assertEqual({'key1': 'var3', 'key2': ['var4']}, parsed)
525         mock_open.assert_has_calls([mock.call('args_file'),
526                                     mock.call('task_file')])
527
528     def test__render_task_error_arguments(self):
529         with self.assertRaises(exceptions.TaskRenderArgumentError):
530             task.TaskParser('task_file')._render_task('value1="var3"', None)
531
532     def test__render_task_error_task_file(self):
533         task_parser = task.TaskParser('task_file')
534         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
535             mock_open.side_effect = (
536                 io.StringIO(six.text_type('value2: var4')),
537                 IOError()
538             )
539             with self.assertRaises(exceptions.TaskReadError):
540                 task_parser._render_task('value1: "var3"', 'args_file')
541
542         mock_open.assert_has_calls([mock.call('args_file'),
543                                     mock.call('task_file')])
544
545     def test__render_task_render_error(self):
546         task_parser = task.TaskParser('task_file')
547         with mock.patch.object(six.moves.builtins, 'open') as mock_open, \
548                 mock.patch.object(task_template.TaskTemplate, 'render',
549                                   side_effect=TypeError) as mock_render:
550             mock_open.side_effect = (
551                 io.StringIO(six.text_type('value2: var4')),
552                 io.StringIO(six.text_type(self.TASK))
553             )
554             with self.assertRaises(exceptions.TaskRenderError):
555                 task_parser._render_task('value1: "var3"', 'args_file')
556
557         mock_open.assert_has_calls([mock.call('args_file'),
558                                     mock.call('task_file')])
559         mock_render.assert_has_calls(
560             [mock.call(self.TASK, value1='var3', value2='var4')])