3 # Copyright (c) 2016-2017 Intel Corporation
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
18 # Unittest for yardstick.benchmark.scenarios.networking.test_vnf_generic
20 from __future__ import absolute_import
27 from tests.unit import STL_MOCKS
28 from yardstick.benchmark.scenarios.networking.vnf_generic import \
29 SshManager, NetworkServiceTestCase, IncorrectConfig, \
31 from yardstick.network_services.collector.subscriber import Collector
32 from yardstick.network_services.vnf_generic.vnf.base import \
33 GenericTrafficGen, GenericVNF
36 COMPLETE_TREX_VNFD = {
37 'vnfd:vnfd-catalog': {
46 'tx_throughput_pc_linerate',
47 'rx_throughput_pc_linerate',
63 'description': 'TRex stateless traffic generator for RFC2544',
64 'id': 'TrexTrafficGen',
69 'vdu-id': 'trexgen-baremetal',
72 'short-name': 'trexgen',
73 'class-name': 'TrexTrafficGen',
76 'description': 'TRex stateless traffic generator for RFC2544',
77 'external-interface': [
80 'virtual-interface': {
81 'bandwidth': '10 Gbps',
83 'dst_mac': '00:01:02:03:04:05',
84 'local_ip': '1.1.1.2',
85 'local_mac': '00:01:02:03:05:05',
86 'type': 'PCI-PASSTHROUGH',
87 'netmask': "255.255.255.0",
89 'vpci': '0000:00:10.2',
91 'vnfd-connection-point-ref': 'xe0',
95 'virtual-interface': {
96 'bandwidth': '10 Gbps',
98 'dst_mac': '00:01:02:03:04:06',
99 'local_ip': '2.1.1.2',
100 'local_mac': '00:01:02:03:05:06',
101 'type': 'PCI-PASSTHROUGH',
102 'netmask': "255.255.255.0",
104 'vpci': '0000:00:10.1',
106 'vnfd-connection-point-ref': 'xe1',
109 'id': 'trexgen-baremetal',
110 'name': 'trexgen-baremetal',
119 28: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \
120 group default qlen 1000
121 link/ether 90:e2:ba:a7:6a:c8 brd ff:ff:ff:ff:ff:ff
122 inet 1.1.1.1/8 brd 1.255.255.255 scope global eth1
123 inet6 fe80::92e2:baff:fea7:6ac8/64 scope link
124 valid_lft forever preferred_lft forever
125 29: eth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \
126 group default qlen 1000
127 link/ether 90:e2:ba:a7:6a:c9 brd ff:ff:ff:ff:ff:ff
128 inet 2.1.1.1/8 brd 2.255.255.255 scope global eth5
129 inet6 fe80::92e2:baff:fea7:6ac9/64 scope link tentative
130 valid_lft forever preferred_lft forever
134 lrwxrwxrwx 1 root root 0 sie 10 14:16 eth1 -> \
135 ../../devices/pci0000:80/0000:80:02.2/0000:84:00.1/net/eth1
136 lrwxrwxrwx 1 root root 0 sie 3 10:37 eth2 -> \
137 ../../devices/pci0000:00/0000:00:01.1/0000:84:00.2/net/eth5
141 "schema": "isb:traffic_profile:0.1",
143 "description": "Fixed traffic profile to run UDP traffic",
145 "traffic_type": "FixedTraffic",
146 "frame_rate": 100, # pps
153 class TestNetworkServiceTestCase(unittest.TestCase):
157 'name': 'trafficgen_1.yardstick',
159 'role': 'TrafficGen',
164 'netmask': '255.255.255.0',
165 'local_ip': '152.16.100.20',
166 'local_mac': '00:00:00:00:00:01',
168 'vpci': '0000:07:00.0',
172 'netmask': '255.255.255.0',
173 'local_ip': '152.16.40.20',
174 'local_mac': '00:00:00:00:00:02',
176 'vpci': '0000:07:00.1',
183 'name': 'vnf.yardstick',
185 'host': '10.223.197.164',
191 'netmask': '255.255.255.0',
192 'local_ip': '152.16.100.19',
193 'local_mac': '00:00:00:00:00:03',
195 'vpci': '0000:07:00.0',
199 'netmask': '255.255.255.0',
200 'local_ip': '152.16.40.19',
201 'local_mac': '00:00:00:00:00:04',
203 'vpci': '0000:07:00.1',
209 'netmask': '255.255.255.0',
210 'gateway': '152.16.100.20',
211 'network': '152.16.100.20',
215 'netmask': '255.255.255.0',
216 'gateway': '152.16.40.20',
217 'network': '152.16.40.20',
224 'gateway': '0064:ff9b:0:0:0:0:9810:6414',
225 'network': '0064:ff9b:0:0:0:0:9810:6414',
230 'gateway': '0064:ff9b:0:0:0:0:9810:2814',
231 'network': '0064:ff9b:0:0:0:0:9810:2814',
240 'vnf__1': self.vnf__1,
253 'vnfd-connection-point-ref': [
255 'vnfd-connection-point-ref': 'xe0',
256 'member-vnf-index-ref': '1',
257 'vnfd-id-ref': 'trexgen'
260 'vnfd-connection-point-ref': 'xe0',
261 'member-vnf-index-ref': '2',
262 'vnfd-id-ref': 'trexgen'
267 'name': 'tg__1 to vnf__1 link 1'
271 'vnfd-connection-point-ref': [
273 'vnfd-connection-point-ref': 'xe1',
274 'member-vnf-index-ref': '1',
275 'vnfd-id-ref': 'trexgen'
278 'vnfd-connection-point-ref': 'xe1',
279 'member-vnf-index-ref': '2',
280 'vnfd-id-ref': 'trexgen'
285 'name': 'vnf__1 to tg__1 link 2'
289 'id': 'trex-tg-topology',
290 'short-name': 'trex-tg-topology',
291 'name': 'trex-tg-topology',
292 'description': 'trex-tg-topology',
293 'constituent-vnfd': [
295 'member-vnf-index': '1',
296 'VNF model': 'tg_trex_tpl.yaml',
297 'vnfd-id-ref': 'tg__1',
300 'member-vnf-index': '2',
301 'VNF model': 'tg_trex_tpl.yaml',
302 'vnfd-id-ref': 'vnf__1',
305 'vld': [self.vld0, self.vld1],
308 self.scenario_cfg = {
310 "topology": self._get_file_abspath("vpe_vnf_topology.yaml"),
311 'task_id': 'a70bdf4a-8e67-47a3-9dc1-273c14506eb7',
312 'tc': 'tc_ipv4_1Mflow_64B_packetsize',
313 'traffic_profile': 'ipv4_throughput_vpe.yaml',
317 'allowed_drop_rate': '0.8 - 1',
321 'framesize': {'64B': 100}
324 'object': 'NetworkServiceTestCase',
326 'output_filename': 'yardstick.out',
332 'flow': 'ipv4_1flow_Packets_vpe.yaml',
333 'imix': 'imix_voice.yaml'
336 'tg__2': 'trafficgen_2.yardstick',
337 'tg__1': 'trafficgen_1.yardstick',
338 'vnf__1': 'vnf.yardstick',
342 self.s = NetworkServiceTestCase(self.scenario_cfg, self.context_cfg)
344 def _get_file_abspath(self, filename):
345 curr_path = os.path.dirname(os.path.abspath(__file__))
346 file_path = os.path.join(curr_path, filename)
349 def test_ssh_manager(self):
350 with mock.patch("yardstick.ssh.SSH") as ssh:
351 ssh_mock = mock.Mock(autospec=ssh.SSH)
353 mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
354 ssh.from_node.return_value = ssh_mock
355 for node, node_dict in self.context_cfg["nodes"].items():
356 with SshManager(node_dict) as conn:
357 self.assertIsNotNone(conn)
359 def test___init__(self):
362 def test__get_ip_flow_range(self):
363 self.scenario_cfg["traffic_options"]["flow"] = \
364 self._get_file_abspath("ipv4_1flow_Packets_vpe.yaml")
365 result = '152.16.100.1-152.16.100.254'
366 self.assertEqual(result, self.s._get_ip_flow_range({"tg__1": 'xe0'}))
368 def test___get_traffic_flow(self):
369 self.scenario_cfg["traffic_options"]["flow"] = \
370 self._get_file_abspath("ipv4_1flow_Packets_vpe.yaml")
371 self.scenario_cfg["options"] = {}
372 self.scenario_cfg['options'] = {
384 'public_ip': ['1.1.1.1'],
387 result = {'flow': {'dst_ip0': '152.16.40.1-152.16.40.254',
388 'src_ip0': '152.16.100.1-152.16.100.254'}}
390 self.assertEqual(result, self.s._get_traffic_flow())
392 def test___get_traffic_flow_error(self):
393 self.scenario_cfg["traffic_options"]["flow"] = \
394 "ipv4_1flow_Packets_vpe.yaml1"
395 self.assertEqual({'flow': {}}, self.s._get_traffic_flow())
397 def test_get_vnf_imp(self):
398 vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0]['class-name']
399 with mock.patch.dict("sys.modules", STL_MOCKS):
400 self.assertIsNotNone(self.s.get_vnf_impl(vnfd))
402 with self.assertRaises(IncorrectConfig) as raised:
403 self.s.get_vnf_impl('NonExistentClass')
405 exc_str = str(raised.exception)
407 self.assertIn('No implementation', exc_str)
408 self.assertIn('found in', exc_str)
410 def test_load_vnf_models_invalid(self):
411 self.context_cfg["nodes"]['tg__1']['VNF model'] = \
412 self._get_file_abspath("tg_trex_tpl.yaml")
413 self.context_cfg["nodes"]['vnf__1']['VNF model'] = \
414 self._get_file_abspath("tg_trex_tpl.yaml")
416 vnf = mock.Mock(autospec=GenericVNF)
417 self.s.get_vnf_impl = mock.Mock(return_value=vnf)
419 self.assertIsNotNone(
420 self.s.load_vnf_models(self.scenario_cfg, self.context_cfg))
422 def test_map_topology_to_infrastructure(self):
423 with mock.patch("yardstick.ssh.SSH") as ssh:
424 ssh_mock = mock.Mock(autospec=ssh.SSH)
426 mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
427 ssh.from_node.return_value = ssh_mock
428 self.s.map_topology_to_infrastructure()
430 nodes = self.context_cfg["nodes"]
431 self.assertEqual("../../vnf_descriptors/tg_rfc2544_tpl.yaml", nodes['tg__1']['VNF model'])
432 self.assertEqual("../../vnf_descriptors/vpe_vnf.yaml", nodes['vnf__1']['VNF model'])
434 def test_map_topology_to_infrastructure_insufficient_nodes(self):
435 del self.context_cfg['nodes']['vnf__1']
436 with mock.patch("yardstick.ssh.SSH") as ssh:
437 ssh_mock = mock.Mock(autospec=ssh.SSH)
439 mock.Mock(return_value=(1, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
440 ssh.from_node.return_value = ssh_mock
442 with self.assertRaises(IncorrectConfig):
443 self.s.map_topology_to_infrastructure()
445 def test_map_topology_to_infrastructure_config_invalid(self):
446 cfg = dict(self.context_cfg)
447 del cfg['nodes']['vnf__1']['interfaces']['xe0']['local_mac']
448 with mock.patch("yardstick.ssh.SSH") as ssh:
449 ssh_mock = mock.Mock(autospec=ssh.SSH)
451 mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
452 ssh.from_node.return_value = ssh_mock
454 with self.assertRaises(IncorrectConfig):
455 self.s.map_topology_to_infrastructure()
457 def test__resolve_topology_invalid_config(self):
458 with mock.patch("yardstick.ssh.SSH") as ssh:
459 ssh_mock = mock.Mock(autospec=ssh.SSH)
461 mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
462 ssh.from_node.return_value = ssh_mock
464 # purge an important key from the data structure
465 for interface in self.tg__1['interfaces'].values():
466 del interface['local_mac']
469 "yardstick.benchmark.scenarios.networking.vnf_generic.LOG") as mock_log:
470 with self.assertRaises(IncorrectConfig) as raised:
471 self.s._resolve_topology()
473 self.assertIn('not found', str(raised.exception))
476 for index, interface in enumerate(self.tg__1['interfaces'].values()):
477 interface['local_mac'] = '00:00:00:00:00:{:2x}'.format(index)
479 # make a connection point ref with 3 points
480 self.s.topology["vld"][0]['vnfd-connection-point-ref'].append(
481 self.s.topology["vld"][0]['vnfd-connection-point-ref'][0])
484 "yardstick.benchmark.scenarios.networking.vnf_generic.LOG") as mock_log:
485 with self.assertRaises(IncorrectConfig) as raised:
486 self.s._resolve_topology()
488 self.assertIn('wrong endpoint count', str(raised.exception))
490 # make a connection point ref with 1 point
491 self.s.topology["vld"][0]['vnfd-connection-point-ref'] = \
492 self.s.topology["vld"][0]['vnfd-connection-point-ref'][:1]
495 "yardstick.benchmark.scenarios.networking.vnf_generic.LOG") as mock_log:
496 with self.assertRaises(IncorrectConfig) as raised:
497 self.s._resolve_topology()
499 self.assertIn('wrong endpoint count', str(raised.exception))
502 tgen = mock.Mock(autospec=GenericTrafficGen)
503 tgen.traffic_finished = True
504 verified_dict = {"verified": True}
505 tgen.verify_traffic = lambda x: verified_dict
506 tgen.name = "tgen__1"
507 vnf = mock.Mock(autospec=GenericVNF)
508 vnf.runs_traffic = False
509 self.s.vnfs = [tgen, vnf]
510 self.s.traffic_profile = mock.Mock()
511 self.s.collector = mock.Mock(autospec=Collector)
512 self.s.collector.get_kpi = \
513 mock.Mock(return_value={tgen.name: verified_dict})
516 self.assertDictEqual(result, {tgen.name: verified_dict})
518 def test_setup(self):
519 with mock.patch("yardstick.ssh.SSH") as ssh:
520 ssh_mock = mock.Mock(autospec=ssh.SSH)
522 mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
523 ssh.from_node.return_value = ssh_mock
525 tgen = mock.Mock(autospec=GenericTrafficGen)
526 tgen.traffic_finished = True
527 verified_dict = {"verified": True}
528 tgen.verify_traffic = lambda x: verified_dict
529 tgen.terminate = mock.Mock(return_value=True)
530 tgen.name = "tgen__1"
531 vnf = mock.Mock(autospec=GenericVNF)
532 vnf.runs_traffic = False
533 vnf.terminate = mock.Mock(return_value=True)
534 self.s.vnfs = [tgen, vnf]
535 self.s.traffic_profile = mock.Mock()
536 self.s.collector = mock.Mock(autospec=Collector)
537 self.s.collector.get_kpi = \
538 mock.Mock(return_value={tgen.name: verified_dict})
539 self.s.map_topology_to_infrastructure = mock.Mock(return_value=0)
540 self.s.load_vnf_models = mock.Mock(return_value=self.s.vnfs)
541 self.s._fill_traffic_profile = \
542 mock.Mock(return_value=TRAFFIC_PROFILE)
543 self.assertEqual(None, self.s.setup())
545 def test__get_traffic_profile(self):
546 self.scenario_cfg["traffic_profile"] = \
547 self._get_file_abspath("ipv4_throughput_vpe.yaml")
548 self.assertIsNotNone(self.s._get_traffic_profile())
550 def test__get_traffic_profile_exception(self):
551 with mock.patch.dict(self.scenario_cfg, {'traffic_profile': ''}):
552 with self.assertRaises(IOError):
553 self.s._get_traffic_profile()
555 def test___get_traffic_imix_exception(self):
556 with mock.patch.dict(self.scenario_cfg["traffic_options"], {'imix': ''}):
557 self.assertEqual({'imix': {'64B': 100}}, self.s._get_traffic_imix())
559 def test__fill_traffic_profile(self):
560 with mock.patch.dict("sys.modules", STL_MOCKS):
561 self.scenario_cfg["traffic_profile"] = \
562 self._get_file_abspath("ipv4_throughput_vpe.yaml")
563 self.scenario_cfg["traffic_options"]["flow"] = \
564 self._get_file_abspath("ipv4_1flow_Packets_vpe.yaml")
565 self.scenario_cfg["traffic_options"]["imix"] = \
566 self._get_file_abspath("imix_voice.yaml")
567 self.assertIsNotNone(self.s._fill_traffic_profile())
569 def test_teardown(self):
570 vnf = mock.Mock(autospec=GenericVNF)
572 mock.Mock(return_value=True)
574 self.s.traffic_profile = mock.Mock()
575 self.s.collector = mock.Mock(autospec=Collector)
576 self.s.collector.stop = \
577 mock.Mock(return_value=True)
578 self.assertIsNone(self.s.teardown())
582 'address': '0a:de:ad:be:ef:f5',
586 'interface_name': 'enp11s0',
588 'pci_bus_id': '0000:0b:00.0',
589 'subsystem_device': '0x1533',
590 'subsystem_vendor': '0x15d9',
594 'address': '0a:de:ad:be:ef:f4',
598 'interface_name': 'lan',
600 'pci_bus_id': '0000:00:19.0',
601 'subsystem_device': '0x153a',
602 'subsystem_vendor': '0x15d9',
607 SAMPLE_VM_NETDEVS = {
609 'address': 'fa:de:ad:be:ef:5b',
611 'driver': 'virtio_net',
613 'interface_name': 'eth1',
615 'pci_bus_id': '0000:00:04.0',
620 def test_parse_netdev_info(self):
622 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/ifindex:2
623 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/address:0a:de:ad:be:ef:f5
624 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/operstate:down
625 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/vendor:0x8086
626 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/device:0x1533
627 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/subsystem_vendor:0x15d9
628 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/subsystem_device:0x1533
629 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/driver:igb
630 /sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/pci_bus_id:0000:0b:00.0
631 /sys/devices/pci0000:00/0000:00:19.0/net/lan/ifindex:3
632 /sys/devices/pci0000:00/0000:00:19.0/net/lan/address:0a:de:ad:be:ef:f4
633 /sys/devices/pci0000:00/0000:00:19.0/net/lan/operstate:up
634 /sys/devices/pci0000:00/0000:00:19.0/net/lan/device/vendor:0x8086
635 /sys/devices/pci0000:00/0000:00:19.0/net/lan/device/device:0x153a
636 /sys/devices/pci0000:00/0000:00:19.0/net/lan/device/subsystem_vendor:0x15d9
637 /sys/devices/pci0000:00/0000:00:19.0/net/lan/device/subsystem_device:0x153a
638 /sys/devices/pci0000:00/0000:00:19.0/net/lan/driver:e1000e
639 /sys/devices/pci0000:00/0000:00:19.0/net/lan/pci_bus_id:0000:00:19.0
641 res = NetworkServiceTestCase.parse_netdev_info(output)
642 assert res == self.SAMPLE_NETDEVS
644 def test_parse_netdev_info_virtio(self):
646 /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/ifindex:3
647 /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/address:fa:de:ad:be:ef:5b
648 /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/operstate:down
649 /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/device/vendor:0x1af4
650 /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/device/device:0x0001
651 /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/driver:virtio_net
653 res = NetworkServiceTestCase.parse_netdev_info(output)
654 assert res == self.SAMPLE_VM_NETDEVS
656 def test_sort_dpdk_port_num(self):
657 netdevs = self.SAMPLE_NETDEVS.copy()
658 NetworkServiceTestCase._sort_dpdk_port_num(netdevs)
659 assert netdevs['lan']['dpdk_port_num'] == 0
660 assert netdevs['enp11s0']['dpdk_port_num'] == 1
662 def test_probe_missing_values(self):
663 netdevs = self.SAMPLE_NETDEVS.copy()
664 network = {'local_mac': '0a:de:ad:be:ef:f5'}
665 NetworkServiceTestCase._probe_missing_values(netdevs, network)
666 assert network['vpci'] == '0000:0b:00.0'
668 network = {'local_mac': '0a:de:ad:be:ef:f4'}
669 NetworkServiceTestCase._probe_missing_values(netdevs, network)
670 assert network['vpci'] == '0000:00:19.0'
672 def test_open_relative_path(self):
673 mock_open = mock.mock_open()
674 mock_open_result = mock_open()
675 mock_open_call_count = 1 # initial call to get result
678 'yardstick.benchmark.scenarios.networking.vnf_generic.open'
681 with mock.patch(module_name, mock_open, create=True):
682 self.assertEqual(open_relative_file('foo', 'bar'), mock_open_result)
684 mock_open_call_count += 1 # one more call expected
685 self.assertEqual(mock_open.call_count, mock_open_call_count)
686 self.assertIn('foo', mock_open.call_args_list[-1][0][0])
687 self.assertNotIn('bar', mock_open.call_args_list[-1][0][0])
689 def open_effect(*args, **kwargs):
690 if kwargs.get('name', args[0]) == os.path.join('bar', 'foo'):
691 return mock_open_result
692 raise IOError(errno.ENOENT, 'not found')
694 mock_open.side_effect = open_effect
695 self.assertEqual(open_relative_file('foo', 'bar'), mock_open_result)
697 mock_open_call_count += 2 # two more calls expected
698 self.assertEqual(mock_open.call_count, mock_open_call_count)
699 self.assertIn('foo', mock_open.call_args_list[-1][0][0])
700 self.assertIn('bar', mock_open.call_args_list[-1][0][0])
702 # test an IOError of type ENOENT
703 mock_open.side_effect = IOError(errno.ENOENT, 'not found')
704 with self.assertRaises(IOError):
705 # the second call still raises
706 open_relative_file('foo', 'bar')
708 mock_open_call_count += 2 # two more calls expected
709 self.assertEqual(mock_open.call_count, mock_open_call_count)
710 self.assertIn('foo', mock_open.call_args_list[-1][0][0])
711 self.assertIn('bar', mock_open.call_args_list[-1][0][0])
713 # test an IOError other than ENOENT
714 mock_open.side_effect = IOError(errno.EBUSY, 'busy')
715 with self.assertRaises(IOError):
716 open_relative_file('foo', 'bar')
718 mock_open_call_count += 1 # one more call expected
719 self.assertEqual(mock_open.call_count, mock_open_call_count)