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