Add "render-only" option to "task" command
[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.assertEqual(None, 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     @mock.patch('six.moves.builtins.open', side_effect=mock.mock_open())
268     @mock.patch.object(task, 'utils')
269     @mock.patch('logging.root')
270     def test_set_log(self, mock_logging_root, *args):
271         task_obj = task.Task()
272         task_obj.task_id = 'task_id'
273         task_obj._set_log()
274         mock_logging_root.addHandler.assert_called()
275
276     def _get_file_abspath(self, filename):
277         curr_path = os.path.dirname(os.path.abspath(__file__))
278         file_path = os.path.join(curr_path, filename)
279         return file_path
280
281     def change_to_abspath(self, filepath):
282         return os.path.join(consts.YARDSTICK_ROOT_PATH, filepath)
283
284
285 class TaskParserTestCase(unittest.TestCase):
286
287     TASK = """
288 {% set value1 = value1 or 'var1' %}
289 {% set value2 = value2 or 'var2' %}
290 key1: {{ value1 }}
291 key2:
292     - {{ value2 }}"""
293
294     TASK_RENDERED_1 = u"""
295
296
297 key1: var1
298 key2:
299     - var2"""
300
301     TASK_RENDERED_2 = u"""
302
303
304 key1: var3
305 key2:
306     - var4"""
307
308     def setUp(self):
309         self.parser = task.TaskParser('fake/path')
310         self.scenario = {
311             'host': 'athena.demo',
312             'target': 'kratos.demo',
313             'targets': [
314                 'ares.demo', 'mars.demo'
315                 ],
316             'options': {
317                 'server_name': {
318                     'host': 'jupiter.demo',
319                     'target': 'saturn.demo',
320                     },
321                 },
322             'nodes': {
323                 'tg__0': 'tg_0.demo',
324                 'vnf__0': 'vnf_0.demo',
325                 }
326             }
327
328     def test__change_node_names(self):
329
330         ctx_attrs = {
331             'name': 'demo',
332             'task_id': '1234567890',
333             'servers': [
334                 'athena', 'kratos',
335                 'ares', 'mars',
336                 'jupiter', 'saturn',
337                 'tg_0', 'vnf_0'
338                 ]
339             }
340
341         my_context = dummy.DummyContext()
342         my_context.init(ctx_attrs)
343
344         expected_scenario = {
345             'host': 'athena.demo-12345678',
346             'target': 'kratos.demo-12345678',
347             'targets': [
348                 'ares.demo-12345678', 'mars.demo-12345678'
349                 ],
350             'options': {
351                 'server_name': {
352                     'host': 'jupiter.demo-12345678',
353                     'target': 'saturn.demo-12345678',
354                     },
355                 },
356             'nodes': {
357                 'tg__0': 'tg_0.demo-12345678',
358                 'vnf__0': 'vnf_0.demo-12345678',
359                 }
360             }
361
362         scenario = copy.deepcopy(self.scenario)
363
364         self.parser._change_node_names(scenario, [my_context])
365         self.assertEqual(scenario, expected_scenario)
366
367     def test__change_node_names_context_not_found(self):
368         scenario = copy.deepcopy(self.scenario)
369         self.assertRaises(exceptions.ScenarioConfigContextNameNotFound,
370                           self.parser._change_node_names,
371                           scenario, [])
372
373     def test__change_node_names_context_name_unchanged(self):
374         ctx_attrs = {
375             'name': 'demo',
376             'task_id': '1234567890',
377             'flags': {
378                 'no_setup': True,
379                 'no_teardown': True
380                 }
381             }
382
383         my_context = dummy.DummyContext()
384         my_context.init(ctx_attrs)
385
386         scenario = copy.deepcopy(self.scenario)
387         expected_scenario = copy.deepcopy(self.scenario)
388
389         self.parser._change_node_names(scenario, [my_context])
390         self.assertEqual(scenario, expected_scenario)
391
392     def test__parse_tasks(self):
393         task_obj = task.Task()
394         _uuid = uuid.uuid4()
395         task_obj.task_id = _uuid
396         task_files = ['/directory/task_file_name.yml']
397         mock_parser = mock.Mock()
398         mock_parser.parse_task.return_value = {'rendered': 'File content'}
399         mock_args = mock.Mock()
400         mock_args.render_only = False
401
402         tasks = task_obj._parse_tasks(mock_parser, task_files, mock_args,
403                                       ['arg1'], ['file_arg1'])
404         self.assertEqual(
405             [{'rendered': 'File content', 'case_name': 'task_file_name'}],
406             tasks)
407         mock_parser.parse_task.assert_called_once_with(
408             _uuid, 'arg1', 'file_arg1')
409
410     @mock.patch.object(sys, 'exit')
411     @mock.patch.object(utils, 'write_file')
412     @mock.patch.object(utils, 'makedirs')
413     def test__parse_tasks_render_only(self, mock_makedirs, mock_write_file,
414                                       mock_exit):
415         task_obj = task.Task()
416         _uuid = uuid.uuid4()
417         task_obj.task_id = _uuid
418         task_files = ['/directory/task_file_name.yml']
419         mock_parser = mock.Mock()
420         mock_parser.parse_task.return_value = {'rendered': 'File content'}
421         mock_args = mock.Mock()
422         mock_args.render_only = '/output_directory'
423
424         task_obj._parse_tasks(mock_parser, task_files, mock_args,
425                               ['arg1'], ['file_arg1'])
426         mock_makedirs.assert_called_once_with('/output_directory')
427         mock_write_file.assert_called_once_with(
428             '/output_directory/000-task_file_name.yml', 'File content')
429         mock_exit.assert_called_once_with(0)
430
431     def test__render_task_no_args(self):
432         task_parser = task.TaskParser('task_file')
433         task_str = io.StringIO(six.text_type(self.TASK))
434         with mock.patch.object(six.moves.builtins, 'open',
435                                return_value=task_str) as mock_open:
436             parsed, rendered = task_parser._render_task(None, None)
437
438         self.assertEqual(self.TASK_RENDERED_1, rendered)
439         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
440         mock_open.assert_called_once_with('task_file')
441
442     def test__render_task_arguments(self):
443         task_parser = task.TaskParser('task_file')
444         task_str = io.StringIO(six.text_type(self.TASK))
445         with mock.patch.object(six.moves.builtins, 'open',
446                                return_value=task_str) as mock_open:
447             parsed, rendered = task_parser._render_task('value1: "var1"', None)
448
449         self.assertEqual(self.TASK_RENDERED_1, rendered)
450         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
451         mock_open.assert_called_once_with('task_file')
452
453     def test__render_task_file_arguments(self):
454         task_parser = task.TaskParser('task_file')
455         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
456             mock_open.side_effect = (
457                 io.StringIO(six.text_type('value2: var4')),
458                 io.StringIO(six.text_type(self.TASK))
459             )
460             parsed, rendered = task_parser._render_task('value1: "var3"',
461                                                         'args_file')
462
463         self.assertEqual(self.TASK_RENDERED_2, rendered)
464         self.assertEqual({'key1': 'var3', 'key2': ['var4']}, parsed)
465         mock_open.assert_has_calls([mock.call('args_file'),
466                                     mock.call('task_file')])
467
468     def test__render_task_error_arguments(self):
469         with self.assertRaises(exceptions.TaskRenderArgumentError):
470             task.TaskParser('task_file')._render_task('value1="var3"', None)
471
472     def test__render_task_error_task_file(self):
473         task_parser = task.TaskParser('task_file')
474         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
475             mock_open.side_effect = (
476                 io.StringIO(six.text_type('value2: var4')),
477                 IOError()
478             )
479             with self.assertRaises(exceptions.TaskReadError):
480                 task_parser._render_task('value1: "var3"', 'args_file')
481
482         mock_open.assert_has_calls([mock.call('args_file'),
483                                     mock.call('task_file')])
484
485     def test__render_task_render_error(self):
486         task_parser = task.TaskParser('task_file')
487         with mock.patch.object(six.moves.builtins, 'open') as mock_open, \
488                 mock.patch.object(task_template.TaskTemplate, 'render',
489                                   side_effect=TypeError) as mock_render:
490             mock_open.side_effect = (
491                 io.StringIO(six.text_type('value2: var4')),
492                 io.StringIO(six.text_type(self.TASK))
493             )
494             with self.assertRaises(exceptions.TaskRenderError):
495                 task_parser._render_task('value1: "var3"', 'args_file')
496
497         mock_open.assert_has_calls([mock.call('args_file'),
498                                     mock.call('task_file')])
499         mock_render.assert_has_calls(
500             [mock.call(self.TASK, value1='var3', value2='var4')])