Merge "Install dependencies: bare-metal, standalone"
[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(task, '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(task, '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(task, '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(os, 'environ')
160     def test_check_precondition(self, mock_os_environ):
161         cfg = {
162             'precondition': {
163                 'installer_type': 'compass',
164                 'deploy_scenarios': 'os-nosdn',
165                 'pod_name': 'huawei-pod1'
166             }
167         }
168
169         t = task.TaskParser('/opt')
170         mock_os_environ.get.side_effect = ['compass',
171                                            'os-nosdn',
172                                            'huawei-pod1']
173         result = t._check_precondition(cfg)
174         self.assertTrue(result)
175
176     def test_parse_suite_no_constraint_no_args(self):
177         SAMPLE_SCENARIO_PATH = "no_constraint_no_args_scenario_sample.yaml"
178         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
179         with mock.patch.object(os, 'environ',
180                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
181             task_files, task_args, task_args_fnames = t.parse_suite()
182
183         self.assertEqual(task_files[0], self.change_to_abspath(
184                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
185         self.assertEqual(task_files[1], self.change_to_abspath(
186                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
187         self.assertIsNone(task_args[0])
188         self.assertIsNone(task_args[1])
189         self.assertIsNone(task_args_fnames[0])
190         self.assertIsNone(task_args_fnames[1])
191
192     def test_parse_suite_no_constraint_with_args(self):
193         SAMPLE_SCENARIO_PATH = "no_constraint_with_args_scenario_sample.yaml"
194         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
195         with mock.patch.object(os, 'environ',
196                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
197             task_files, task_args, task_args_fnames = t.parse_suite()
198
199         self.assertEqual(task_files[0], self.change_to_abspath(
200                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
201         self.assertEqual(task_files[1], self.change_to_abspath(
202                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
203         self.assertIsNone(task_args[0])
204         self.assertEqual(task_args[1],
205                          '{"host": "node1.LF","target": "node2.LF"}')
206         self.assertIsNone(task_args_fnames[0])
207         self.assertIsNone(task_args_fnames[1])
208
209     def test_parse_suite_with_constraint_no_args(self):
210         SAMPLE_SCENARIO_PATH = "with_constraint_no_args_scenario_sample.yaml"
211         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
212         with mock.patch.object(os, 'environ',
213                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
214             task_files, task_args, task_args_fnames = t.parse_suite()
215         self.assertEqual(task_files[0], self.change_to_abspath(
216                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
217         self.assertEqual(task_files[1], self.change_to_abspath(
218                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
219         self.assertIsNone(task_args[0])
220         self.assertIsNone(task_args[1])
221         self.assertIsNone(task_args_fnames[0])
222         self.assertIsNone(task_args_fnames[1])
223
224     def test_parse_suite_with_constraint_with_args(self):
225         SAMPLE_SCENARIO_PATH = "with_constraint_with_args_scenario_sample.yaml"
226         t = task.TaskParser(self._get_file_abspath(SAMPLE_SCENARIO_PATH))
227         with mock.patch('os.environ',
228                         new={'NODE_NAME': 'huawei-pod1', 'INSTALLER_TYPE': 'compass'}):
229             task_files, task_args, task_args_fnames = t.parse_suite()
230
231         self.assertEqual(task_files[0], self.change_to_abspath(
232                          'tests/opnfv/test_cases/opnfv_yardstick_tc037.yaml'))
233         self.assertEqual(task_files[1], self.change_to_abspath(
234                          'tests/opnfv/test_cases/opnfv_yardstick_tc043.yaml'))
235         self.assertIsNone(task_args[0])
236         self.assertEqual(task_args[1],
237                          '{"host": "node1.LF","target": "node2.LF"}')
238         self.assertIsNone(task_args_fnames[0])
239         self.assertIsNone(task_args_fnames[1])
240
241     def test_parse_options(self):
242         options = {
243             'openstack': {
244                 'EXTERNAL_NETWORK': '$network'
245             },
246             'nodes': ['node1', '$node'],
247             'host': '$host'
248         }
249
250         t = task.Task()
251         t.outputs = {
252             'network': 'ext-net',
253             'node': 'node2',
254             'host': 'server.yardstick'
255         }
256
257         expected_result = {
258             'openstack': {
259                 'EXTERNAL_NETWORK': 'ext-net'
260             },
261             'nodes': ['node1', 'node2'],
262             'host': 'server.yardstick'
263         }
264
265         actual_result = t._parse_options(options)
266         self.assertEqual(expected_result, actual_result)
267
268     def test_parse_options_no_teardown(self):
269         options = {
270             'openstack': {
271                 'EXTERNAL_NETWORK': '$network'
272             },
273             'nodes': ['node1', '$node'],
274             'host': '$host',
275             'contexts' : {'name': "my-context",
276                           'no_teardown': True}
277         }
278
279         t = task.Task()
280         t.outputs = {
281             'network': 'ext-net',
282             'node': 'node2',
283             'host': 'server.yardstick'
284         }
285
286         expected_result = {
287             'openstack': {
288                 'EXTERNAL_NETWORK': 'ext-net'
289             },
290             'nodes': ['node1', 'node2'],
291             'host': 'server.yardstick',
292             'contexts': {'name': 'my-context',
293                          'no_teardown': True,
294                         }
295         }
296
297         actual_result = t._parse_options(options)
298         self.assertEqual(expected_result, actual_result)
299
300     @mock.patch('six.moves.builtins.open', side_effect=mock.mock_open())
301     @mock.patch.object(task, 'utils')
302     @mock.patch('logging.root')
303     def test_set_log(self, mock_logging_root, *args):
304         task_obj = task.Task()
305         task_obj.task_id = 'task_id'
306         task_obj._set_log()
307         mock_logging_root.addHandler.assert_called()
308
309     def _get_file_abspath(self, filename):
310         curr_path = os.path.dirname(os.path.abspath(__file__))
311         file_path = os.path.join(curr_path, filename)
312         return file_path
313
314     def change_to_abspath(self, filepath):
315         return os.path.join(consts.YARDSTICK_ROOT_PATH, filepath)
316
317
318 class TaskParserTestCase(unittest.TestCase):
319
320     TASK = """
321 {% set value1 = value1 or 'var1' %}
322 {% set value2 = value2 or 'var2' %}
323 key1: {{ value1 }}
324 key2:
325     - {{ value2 }}"""
326
327     TASK_RENDERED_1 = u"""
328
329
330 key1: var1
331 key2:
332     - var2"""
333
334     TASK_RENDERED_2 = u"""
335
336
337 key1: var3
338 key2:
339     - var4"""
340
341     def setUp(self):
342         self.parser = task.TaskParser('fake/path')
343         self.scenario = {
344             'host': 'athena.demo',
345             'target': 'kratos.demo',
346             'targets': [
347                 'ares.demo', 'mars.demo'
348                 ],
349             'options': {
350                 'server_name': {
351                     'host': 'jupiter.demo',
352                     'target': 'saturn.demo',
353                     },
354                 },
355             'nodes': {
356                 'tg__0': 'tg_0.demo',
357                 'vnf__0': 'vnf_0.demo',
358                 }
359             }
360
361     @staticmethod
362     def _remove_contexts():
363         for context in base.Context.list:
364             context._delete_context()
365         base.Context.list = []
366
367     def test__change_node_names(self):
368
369         ctx_attrs = {
370             'name': 'demo',
371             'task_id': '1234567890',
372             'servers': [
373                 'athena', 'kratos',
374                 'ares', 'mars',
375                 'jupiter', 'saturn',
376                 'tg_0', 'vnf_0'
377                 ]
378             }
379
380         my_context = dummy.DummyContext()
381         self.addCleanup(self._remove_contexts)
382         my_context.init(ctx_attrs)
383
384         expected_scenario = {
385             'host': 'athena.demo-12345678',
386             'target': 'kratos.demo-12345678',
387             'targets': [
388                 'ares.demo-12345678', 'mars.demo-12345678'
389                 ],
390             'options': {
391                 'server_name': {
392                     'host': 'jupiter.demo-12345678',
393                     'target': 'saturn.demo-12345678',
394                     },
395                 },
396             'nodes': {
397                 'tg__0': 'tg_0.demo-12345678',
398                 'vnf__0': 'vnf_0.demo-12345678',
399                 }
400             }
401
402         scenario = copy.deepcopy(self.scenario)
403
404         self.parser._change_node_names(scenario, [my_context])
405         self.assertEqual(scenario, expected_scenario)
406
407     def test__change_node_names_context_not_found(self):
408         scenario = copy.deepcopy(self.scenario)
409         self.assertRaises(exceptions.ScenarioConfigContextNameNotFound,
410                           self.parser._change_node_names,
411                           scenario, [])
412
413     def test__change_node_names_context_name_unchanged(self):
414         ctx_attrs = {
415             'name': 'demo',
416             'task_id': '1234567890',
417             'flags': {
418                 'no_setup': True,
419                 'no_teardown': True
420                 }
421             }
422
423         my_context = dummy.DummyContext()
424         self.addCleanup(self._remove_contexts)
425         my_context.init(ctx_attrs)
426
427         scenario = copy.deepcopy(self.scenario)
428         expected_scenario = copy.deepcopy(self.scenario)
429
430         self.parser._change_node_names(scenario, [my_context])
431         self.assertEqual(scenario, expected_scenario)
432
433     def test__change_node_names_options_empty(self):
434         ctx_attrs = {
435             'name': 'demo',
436             'task_id': '1234567890'
437         }
438
439         my_context = dummy.DummyContext()
440         self.addCleanup(self._remove_contexts)
441         my_context.init(ctx_attrs)
442         scenario = copy.deepcopy(self.scenario)
443         scenario['options'] = None
444
445         self.parser._change_node_names(scenario, [my_context])
446         self.assertIsNone(scenario['options'])
447
448     def test__change_node_names_options_server_name_empty(self):
449         ctx_attrs = {
450             'name': 'demo',
451             'task_id': '1234567890'
452         }
453
454         my_context = dummy.DummyContext()
455         self.addCleanup(self._remove_contexts)
456         my_context.init(ctx_attrs)
457         scenario = copy.deepcopy(self.scenario)
458         scenario['options']['server_name'] = None
459
460         self.parser._change_node_names(scenario, [my_context])
461         self.assertIsNone(scenario['options']['server_name'])
462
463     def test__parse_tasks(self):
464         task_obj = task.Task()
465         _uuid = uuid.uuid4()
466         task_obj.task_id = _uuid
467         task_files = ['/directory/task_file_name.yml']
468         mock_parser = mock.Mock()
469         mock_parser.parse_task.return_value = {'rendered': 'File content'}
470         mock_args = mock.Mock()
471         mock_args.render_only = False
472
473         tasks = task_obj._parse_tasks(mock_parser, task_files, mock_args,
474                                       ['arg1'], ['file_arg1'])
475         self.assertEqual(
476             [{'rendered': 'File content', 'case_name': 'task_file_name'}],
477             tasks)
478         mock_parser.parse_task.assert_called_once_with(
479             _uuid, 'arg1', 'file_arg1')
480
481     @mock.patch.object(sys, 'exit')
482     @mock.patch.object(utils, 'write_file')
483     @mock.patch.object(utils, 'makedirs')
484     def test__parse_tasks_render_only(self, mock_makedirs, mock_write_file,
485                                       mock_exit):
486         task_obj = task.Task()
487         _uuid = uuid.uuid4()
488         task_obj.task_id = _uuid
489         task_files = ['/directory/task_file_name.yml']
490         mock_parser = mock.Mock()
491         mock_parser.parse_task.return_value = {'rendered': 'File content'}
492         mock_args = mock.Mock()
493         mock_args.render_only = '/output_directory'
494
495         task_obj._parse_tasks(mock_parser, task_files, mock_args,
496                               ['arg1'], ['file_arg1'])
497         mock_makedirs.assert_called_once_with('/output_directory')
498         mock_write_file.assert_called_once_with(
499             '/output_directory/000-task_file_name.yml', 'File content')
500         mock_exit.assert_called_once_with(0)
501
502     def test__render_task_no_args(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(None, 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_arguments(self):
514         task_parser = task.TaskParser('task_file')
515         task_str = io.StringIO(six.text_type(self.TASK))
516         with mock.patch.object(six.moves.builtins, 'open',
517                                return_value=task_str) as mock_open:
518             parsed, rendered = task_parser._render_task('value1: "var1"', None)
519
520         self.assertEqual(self.TASK_RENDERED_1, rendered)
521         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
522         mock_open.assert_called_once_with('task_file')
523
524     def test__render_task_file_arguments(self):
525         task_parser = task.TaskParser('task_file')
526         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
527             mock_open.side_effect = (
528                 io.StringIO(six.text_type('value2: var4')),
529                 io.StringIO(six.text_type(self.TASK))
530             )
531             parsed, rendered = task_parser._render_task('value1: "var3"',
532                                                         'args_file')
533
534         self.assertEqual(self.TASK_RENDERED_2, rendered)
535         self.assertEqual({'key1': 'var3', 'key2': ['var4']}, parsed)
536         mock_open.assert_has_calls([mock.call('args_file'),
537                                     mock.call('task_file')])
538
539     def test__render_task_error_arguments(self):
540         with self.assertRaises(exceptions.TaskRenderArgumentError):
541             task.TaskParser('task_file')._render_task('value1="var3"', None)
542
543     def test__render_task_error_task_file(self):
544         task_parser = task.TaskParser('task_file')
545         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
546             mock_open.side_effect = (
547                 io.StringIO(six.text_type('value2: var4')),
548                 IOError()
549             )
550             with self.assertRaises(exceptions.TaskReadError):
551                 task_parser._render_task('value1: "var3"', 'args_file')
552
553         mock_open.assert_has_calls([mock.call('args_file'),
554                                     mock.call('task_file')])
555
556     def test__render_task_render_error(self):
557         task_parser = task.TaskParser('task_file')
558         with mock.patch.object(six.moves.builtins, 'open') as mock_open, \
559                 mock.patch.object(task_template.TaskTemplate, 'render',
560                                   side_effect=TypeError) as mock_render:
561             mock_open.side_effect = (
562                 io.StringIO(six.text_type('value2: var4')),
563                 io.StringIO(six.text_type(self.TASK))
564             )
565             with self.assertRaises(exceptions.TaskRenderError):
566                 task_parser._render_task('value1: "var3"', 'args_file')
567
568         mock_open.assert_has_calls([mock.call('args_file'),
569                                     mock.call('task_file')])
570         mock_render.assert_has_calls(
571             [mock.call(self.TASK, value1='var3', value2='var4')])