Merge "Standalone XML machine type is not longer valid"
[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     @staticmethod
361     def _remove_context(context):
362         if context:
363             context._delete_context()
364
365     def test__change_node_names(self):
366
367         ctx_attrs = {
368             'name': 'demo',
369             'task_id': '1234567890',
370             'servers': [
371                 'athena', 'kratos',
372                 'ares', 'mars',
373                 'jupiter', 'saturn',
374                 'tg_0', 'vnf_0'
375                 ]
376             }
377
378         my_context = dummy.DummyContext()
379         self.addCleanup(self._remove_context, my_context)
380         my_context.init(ctx_attrs)
381
382         expected_scenario = {
383             'host': 'athena.demo-12345678',
384             'target': 'kratos.demo-12345678',
385             'targets': [
386                 'ares.demo-12345678', 'mars.demo-12345678'
387                 ],
388             'options': {
389                 'server_name': {
390                     'host': 'jupiter.demo-12345678',
391                     'target': 'saturn.demo-12345678',
392                     },
393                 },
394             'nodes': {
395                 'tg__0': 'tg_0.demo-12345678',
396                 'vnf__0': 'vnf_0.demo-12345678',
397                 }
398             }
399
400         scenario = copy.deepcopy(self.scenario)
401
402         self.parser._change_node_names(scenario, [my_context])
403         self.assertEqual(scenario, expected_scenario)
404
405     def test__change_node_names_context_not_found(self):
406         scenario = copy.deepcopy(self.scenario)
407         self.assertRaises(exceptions.ScenarioConfigContextNameNotFound,
408                           self.parser._change_node_names,
409                           scenario, [])
410
411     def test__change_node_names_context_name_unchanged(self):
412         ctx_attrs = {
413             'name': 'demo',
414             'task_id': '1234567890',
415             'flags': {
416                 'no_setup': True,
417                 'no_teardown': True
418                 }
419             }
420
421         my_context = dummy.DummyContext()
422         self.addCleanup(self._remove_context, my_context)
423         my_context.init(ctx_attrs)
424
425         scenario = copy.deepcopy(self.scenario)
426         expected_scenario = copy.deepcopy(self.scenario)
427
428         self.parser._change_node_names(scenario, [my_context])
429         self.assertEqual(scenario, expected_scenario)
430
431     def test__change_node_names_options_empty(self):
432         ctx_attrs = {
433             'name': 'demo',
434             'task_id': '1234567890'
435         }
436
437         my_context = dummy.DummyContext()
438         self.addCleanup(self._remove_context, my_context)
439         my_context.init(ctx_attrs)
440         scenario = copy.deepcopy(self.scenario)
441         scenario['options'] = None
442
443         self.parser._change_node_names(scenario, [my_context])
444         self.assertIsNone(scenario['options'])
445
446     def test__change_node_names_options_server_name_empty(self):
447         ctx_attrs = {
448             'name': 'demo',
449             'task_id': '1234567890'
450         }
451
452         my_context = dummy.DummyContext()
453         self.addCleanup(self._remove_context, my_context)
454         my_context.init(ctx_attrs)
455         scenario = copy.deepcopy(self.scenario)
456         scenario['options']['server_name'] = None
457
458         self.parser._change_node_names(scenario, [my_context])
459         self.assertIsNone(scenario['options']['server_name'])
460
461     def test__parse_tasks(self):
462         task_obj = task.Task()
463         _uuid = uuid.uuid4()
464         task_obj.task_id = _uuid
465         task_files = ['/directory/task_file_name.yml']
466         mock_parser = mock.Mock()
467         mock_parser.parse_task.return_value = {'rendered': 'File content'}
468         mock_args = mock.Mock()
469         mock_args.render_only = False
470
471         tasks = task_obj._parse_tasks(mock_parser, task_files, mock_args,
472                                       ['arg1'], ['file_arg1'])
473         self.assertEqual(
474             [{'rendered': 'File content', 'case_name': 'task_file_name'}],
475             tasks)
476         mock_parser.parse_task.assert_called_once_with(
477             _uuid, 'arg1', 'file_arg1')
478
479     @mock.patch.object(sys, 'exit')
480     @mock.patch.object(utils, 'write_file')
481     @mock.patch.object(utils, 'makedirs')
482     def test__parse_tasks_render_only(self, mock_makedirs, mock_write_file,
483                                       mock_exit):
484         task_obj = task.Task()
485         _uuid = uuid.uuid4()
486         task_obj.task_id = _uuid
487         task_files = ['/directory/task_file_name.yml']
488         mock_parser = mock.Mock()
489         mock_parser.parse_task.return_value = {'rendered': 'File content'}
490         mock_args = mock.Mock()
491         mock_args.render_only = '/output_directory'
492
493         task_obj._parse_tasks(mock_parser, task_files, mock_args,
494                               ['arg1'], ['file_arg1'])
495         mock_makedirs.assert_called_once_with('/output_directory')
496         mock_write_file.assert_called_once_with(
497             '/output_directory/000-task_file_name.yml', 'File content')
498         mock_exit.assert_called_once_with(0)
499
500     def test__render_task_no_args(self):
501         task_parser = task.TaskParser('task_file')
502         task_str = io.StringIO(six.text_type(self.TASK))
503         with mock.patch.object(six.moves.builtins, 'open',
504                                return_value=task_str) as mock_open:
505             parsed, rendered = task_parser._render_task(None, None)
506
507         self.assertEqual(self.TASK_RENDERED_1, rendered)
508         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
509         mock_open.assert_called_once_with('task_file')
510
511     def test__render_task_arguments(self):
512         task_parser = task.TaskParser('task_file')
513         task_str = io.StringIO(six.text_type(self.TASK))
514         with mock.patch.object(six.moves.builtins, 'open',
515                                return_value=task_str) as mock_open:
516             parsed, rendered = task_parser._render_task('value1: "var1"', None)
517
518         self.assertEqual(self.TASK_RENDERED_1, rendered)
519         self.assertEqual({'key1': 'var1', 'key2': ['var2']}, parsed)
520         mock_open.assert_called_once_with('task_file')
521
522     def test__render_task_file_arguments(self):
523         task_parser = task.TaskParser('task_file')
524         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
525             mock_open.side_effect = (
526                 io.StringIO(six.text_type('value2: var4')),
527                 io.StringIO(six.text_type(self.TASK))
528             )
529             parsed, rendered = task_parser._render_task('value1: "var3"',
530                                                         'args_file')
531
532         self.assertEqual(self.TASK_RENDERED_2, rendered)
533         self.assertEqual({'key1': 'var3', 'key2': ['var4']}, parsed)
534         mock_open.assert_has_calls([mock.call('args_file'),
535                                     mock.call('task_file')])
536
537     def test__render_task_error_arguments(self):
538         with self.assertRaises(exceptions.TaskRenderArgumentError):
539             task.TaskParser('task_file')._render_task('value1="var3"', None)
540
541     def test__render_task_error_task_file(self):
542         task_parser = task.TaskParser('task_file')
543         with mock.patch.object(six.moves.builtins, 'open') as mock_open:
544             mock_open.side_effect = (
545                 io.StringIO(six.text_type('value2: var4')),
546                 IOError()
547             )
548             with self.assertRaises(exceptions.TaskReadError):
549                 task_parser._render_task('value1: "var3"', 'args_file')
550
551         mock_open.assert_has_calls([mock.call('args_file'),
552                                     mock.call('task_file')])
553
554     def test__render_task_render_error(self):
555         task_parser = task.TaskParser('task_file')
556         with mock.patch.object(six.moves.builtins, 'open') as mock_open, \
557                 mock.patch.object(task_template.TaskTemplate, 'render',
558                                   side_effect=TypeError) as mock_render:
559             mock_open.side_effect = (
560                 io.StringIO(six.text_type('value2: var4')),
561                 io.StringIO(six.text_type(self.TASK))
562             )
563             with self.assertRaises(exceptions.TaskRenderError):
564                 task_parser._render_task('value1: "var3"', 'args_file')
565
566         mock_open.assert_has_calls([mock.call('args_file'),
567                                     mock.call('task_file')])
568         mock_render.assert_has_calls(
569             [mock.call(self.TASK, value1='var3', value2='var4')])