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