Merge "add network info to topology"
authorRex Lee <limingjiang@huawei.com>
Mon, 17 Jul 2017 01:15:07 +0000 (01:15 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Mon, 17 Jul 2017 01:15:07 +0000 (01:15 +0000)
15 files changed:
tests/unit/benchmark/contexts/test_heat.py
tests/unit/benchmark/contexts/test_node.py
tests/unit/benchmark/contexts/test_standalone.py
tests/unit/benchmark/core/test_task.py
tests/unit/benchmark/scenarios/networking/test_vnf_generic.py
tests/unit/orchestrator/test_heat.py
yardstick/benchmark/contexts/base.py
yardstick/benchmark/contexts/dummy.py
yardstick/benchmark/contexts/heat.py
yardstick/benchmark/contexts/model.py
yardstick/benchmark/contexts/node.py
yardstick/benchmark/contexts/standalone.py
yardstick/benchmark/core/task.py
yardstick/benchmark/scenarios/networking/vnf_generic.py
yardstick/orchestrator/heat.py

index 3dadd48..c739f33 100644 (file)
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import
 
+import ipaddress
 import logging
 import os
 import unittest
@@ -120,7 +121,8 @@ class HeatContextTestCase(unittest.TestCase):
         mock_template.add_router_interface.assert_called_with("bar-fool-network-router-if0", "bar-fool-network-router", "bar-fool-network-subnet")
 
     @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
-    def test_deploy(self, mock_template):
+    @mock.patch('yardstick.benchmark.contexts.heat.get_neutron_client')
+    def test_deploy(self, mock_neutron, mock_template):
 
         self.test_context.name = 'foo'
         self.test_context.template_file = '/bar/baz/some-heat-file'
@@ -133,6 +135,59 @@ class HeatContextTestCase(unittest.TestCase):
                                          self.test_context.heat_parameters)
         self.assertIsNotNone(self.test_context.stack)
 
+    def test_add_server_port(self):
+        network1 = mock.MagicMock()
+        network1.vld_id = 'vld111'
+        network2 = mock.MagicMock()
+        network2.vld_id = 'vld777'
+        self.test_context.name = 'foo'
+        self.test_context.stack = mock.MagicMock()
+        self.test_context.networks = {
+            'a': network1,
+            'c': network2,
+        }
+        self.test_context.stack.outputs = {
+            'b': '10.20.30.45',
+            'b-subnet_id': 1,
+            'foo-a-subnet-cidr': '10.20.0.0/15',
+            'foo-a-subnet-gateway_ip': '10.20.30.1',
+            'b-mac_address': '00:01',
+            'b-device_id': 'dev21',
+            'b-network_id': 'net789',
+            'd': '40.30.20.15',
+            'd-subnet_id': 2,
+            'foo-c-subnet-cidr': '40.30.0.0/18',
+            'foo-c-subnet-gateway_ip': '40.30.20.254',
+            'd-mac_address': '00:10',
+            'd-device_id': 'dev43',
+            'd-network_id': 'net987',
+        }
+        server = mock.MagicMock()
+        server.ports = OrderedDict([
+            ('a', {'stack_name': 'b'}),
+            ('c', {'stack_name': 'd'}),
+        ])
+
+        expected = {
+            "private_ip": '10.20.30.45',
+            "subnet_id": 1,
+            "subnet_cidr": '10.20.0.0/15',
+            "network": '10.20.0.0',
+            "netmask": '255.254.0.0',
+            "gateway_ip": '10.20.30.1',
+            "mac_address": '00:01',
+            "device_id": 'dev21',
+            "network_id": 'net789',
+            "network_name": 'a',
+            "local_mac": '00:01',
+            "local_ip": '10.20.30.45',
+            "vld_id": 'vld111',
+        }
+        self.test_context.add_server_port(server)
+        self.assertEqual(server.private_ip, '10.20.30.45')
+        self.assertEqual(len(server.interfaces), 2)
+        self.assertDictEqual(server.interfaces['a'], expected)
+
     @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
     def test_undeploy(self, mock_template):
 
@@ -155,3 +210,57 @@ class HeatContextTestCase(unittest.TestCase):
 
         self.assertEqual(result['ip'], '127.0.0.1')
         self.assertEqual(result['private_ip'], '10.0.0.1')
+
+    def test__get_network(self):
+        network1 = mock.MagicMock()
+        network1.name = 'net_1'
+        network1.vld_id = 'vld111'
+        network1.segmentation_id = 'seg54'
+        network1.network_type = 'type_a'
+        network1.physical_network = 'phys'
+
+        network2 = mock.MagicMock()
+        network2.name = 'net_2'
+        network2.vld_id = 'vld999'
+        network2.segmentation_id = 'seg45'
+        network2.network_type = 'type_b'
+        network2.physical_network = 'virt'
+
+        self.test_context.networks = {
+            'a': network1,
+            'b': network2,
+        }
+
+        attr_name = None
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {}
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {'vld_id': 'vld777'}
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = 'vld777'
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {'vld_id': 'vld999'}
+        expected = {
+            "name": 'net_2',
+            "vld_id": 'vld999',
+            "segmentation_id": 'seg45',
+            "network_type": 'type_b',
+            "physical_network": 'virt',
+        }
+        result = self.test_context._get_network(attr_name)
+        self.assertDictEqual(result, expected)
+
+        attr_name = 'a'
+        expected = {
+            "name": 'net_1',
+            "vld_id": 'vld111',
+            "segmentation_id": 'seg54',
+            "network_type": 'type_a',
+            "physical_network": 'phys',
+        }
+        result = self.test_context._get_network(attr_name)
+        self.assertDictEqual(result, expected)
index 4b35ca4..d5ce8c5 100644 (file)
@@ -208,6 +208,50 @@ class NodeContextTestCase(unittest.TestCase):
         obj._get_client(node_name_args)
         self.assertTrue(wait_mock.called)
 
+    def test__get_network(self):
+        network1 = {
+            'name': 'net_1',
+            'vld_id': 'vld111',
+            'segmentation_id': 'seg54',
+            'network_type': 'type_a',
+            'physical_network': 'phys',
+        }
+        network2 = {
+            'name': 'net_2',
+            'vld_id': 'vld999',
+        }
+        self.test_context.networks = {
+            'a': network1,
+            'b': network2,
+        }
+
+        attr_name = {}
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {'vld_id': 'vld777'}
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        self.assertIsNone(self.test_context._get_network(None))
+
+        attr_name = 'vld777'
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {'vld_id': 'vld999'}
+        expected = {
+            "name": 'net_2',
+            "vld_id": 'vld999',
+            "segmentation_id": None,
+            "network_type": None,
+            "physical_network": None,
+        }
+        result = self.test_context._get_network(attr_name)
+        self.assertDictEqual(result, expected)
+
+        attr_name = 'a'
+        expected = network1
+        result = self.test_context._get_network(attr_name)
+        self.assertDictEqual(result, expected)
+
 
 def main():
     unittest.main()
index 687ef73..a6fd776 100644 (file)
@@ -129,3 +129,48 @@ class StandaloneContextTestCase(unittest.TestCase):
         curr_path = os.path.dirname(os.path.abspath(__file__))
         file_path = os.path.join(curr_path, filename)
         return file_path
+
+    def test__get_network(self):
+        network1 = {
+            'name': 'net_1',
+            'vld_id': 'vld111',
+            'segmentation_id': 'seg54',
+            'network_type': 'type_a',
+            'physical_network': 'phys',
+        }
+        network2 = {
+            'name': 'net_2',
+            'vld_id': 'vld999',
+        }
+        self.test_context.networks = {
+            'a': network1,
+            'b': network2,
+        }
+
+        attr_name = None
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {}
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {'vld_id': 'vld777'}
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = 'vld777'
+        self.assertIsNone(self.test_context._get_network(attr_name))
+
+        attr_name = {'vld_id': 'vld999'}
+        expected = {
+            "name": 'net_2',
+            "vld_id": 'vld999',
+            "segmentation_id": None,
+            "network_type": None,
+            "physical_network": None,
+        }
+        result = self.test_context._get_network(attr_name)
+        self.assertDictEqual(result, expected)
+
+        attr_name = 'a'
+        expected = network1
+        result = self.test_context._get_network(attr_name)
+        self.assertDictEqual(result, expected)
index b64bb8e..8d6d963 100644 (file)
@@ -47,6 +47,73 @@ class TaskTestCase(unittest.TestCase):
         self.assertEqual(context_cfg["host"], server_info)
         self.assertEqual(context_cfg["target"], server_info)
 
+    @mock.patch('yardstick.benchmark.core.task.Context')
+    def test_parse_networks_from_nodes(self, mock_context):
+        nodes = {
+            'node1': {
+                'interfaces': {
+                    'eth0': {
+                        'name': 'mgmt',
+                    },
+                    'eth1': {
+                        'name': 'external',
+                        'vld_id': '23',
+                    },
+                    'eth10': {
+                        'name': 'internal',
+                        'vld_id': '55',
+                    },
+                },
+            },
+            'node2': {
+                'interfaces': {
+                    'eth4': {
+                        'name': 'mgmt',
+                    },
+                    'eth2': {
+                        'name': 'external',
+                        'vld_id': '32',
+                    },
+                    'eth11': {
+                        'name': 'internal',
+                        'vld_id': '55',
+                    },
+                },
+            },
+        }
+
+        mock_context.get_network.side_effect = iter([
+            None,
+            {
+                'name': 'a',
+                'network_type': 'private',
+            },
+            {},
+            {
+                'name': 'b',
+                'vld_id': 'y',
+                'subnet_cidr': '10.20.0.0/16',
+            },
+            {
+                'name': 'c',
+                'vld_id': 'x',
+            },
+            {
+                'name': 'd',
+                'vld_id': 'w',
+            },
+        ])
+
+        expected_get_network_calls = 4 # once for each vld_id in the nodes dict
+        expected = {
+            'a': {'name': 'a', 'network_type': 'private'},
+            'b': {'name': 'b', 'vld_id': 'y', 'subnet_cidr': '10.20.0.0/16'},
+        }
+
+        networks = task.get_networks_from_nodes(nodes)
+        self.assertEqual(mock_context.get_network.call_count, expected_get_network_calls)
+        self.assertDictEqual(networks, expected)
+
     @mock.patch('yardstick.benchmark.core.task.Context')
     @mock.patch('yardstick.benchmark.core.task.base_runner')
     def test_run(self, mock_base_runner, mock_ctx):
index 111e781..c9cd7fe 100644 (file)
@@ -91,68 +91,97 @@ STL_MOCKS = {
     'stl.trex_stl_lib.zmq': mock.MagicMock(),
 }
 
-COMPLETE_TREX_VNFD = \
-    {'vnfd:vnfd-catalog':
-     {'vnfd':
-      [{'benchmark':
-        {'kpi':
-         ['rx_throughput_fps',
-          'tx_throughput_fps',
-          'tx_throughput_mbps',
-          'rx_throughput_mbps',
-          'tx_throughput_pc_linerate',
-          'rx_throughput_pc_linerate',
-          'min_latency',
-          'max_latency',
-          'avg_latency']},
-        'connection-point': [{'name': 'xe0',
-                              'type': 'VPORT'},
-                             {'name': 'xe1',
-                              'type': 'VPORT'}],
-        'description': 'TRex stateless traffic generator for RFC2544',
-        'id': 'TrexTrafficGen',
-        'mgmt-interface': {'ip': '1.1.1.1',
-                           'password': 'berta',
-                           'user': 'berta',
-                           'vdu-id': 'trexgen-baremetal'},
-        'name': 'trexgen',
-        'short-name': 'trexgen',
-        'vdu': [{'description': 'TRex stateless traffic generator for RFC2544',
-                 'external-interface':
-                 [{'name': 'xe0',
-                   'virtual-interface': {'bandwidth': '10 Gbps',
-                                         'dst_ip': '1.1.1.1',
-                                         'dst_mac': '00:01:02:03:04:05',
-                                         'local_ip': '1.1.1.2',
-                                         'local_mac': '00:01:02:03:05:05',
-                                         'type': 'PCI-PASSTHROUGH',
-                                         'netmask': "255.255.255.0",
-                                         'driver': 'i40',
-                                         'vpci': '0000:00:10.2'},
-                   'vnfd-connection-point-ref': 'xe0'},
-                  {'name': 'xe1',
-                   'virtual-interface': {'bandwidth': '10 Gbps',
-                                         'dst_ip': '2.1.1.1',
-                                         'dst_mac': '00:01:02:03:04:06',
-                                         'local_ip': '2.1.1.2',
-                                         'local_mac': '00:01:02:03:05:06',
-                                         'type': 'PCI-PASSTHROUGH',
-                                         'netmask': "255.255.255.0",
-                                         'driver': 'i40',
-                                         'vpci': '0000:00:10.1'},
-                   'vnfd-connection-point-ref': 'xe1'}],
-                 'id': 'trexgen-baremetal',
-                 'name': 'trexgen-baremetal'}]}]}}
+COMPLETE_TREX_VNFD = {
+    'vnfd:vnfd-catalog': {
+        'vnfd': [
+            {
+                'benchmark': {
+                    'kpi': [
+                        'rx_throughput_fps',
+                        'tx_throughput_fps',
+                        'tx_throughput_mbps',
+                        'rx_throughput_mbps',
+                        'tx_throughput_pc_linerate',
+                        'rx_throughput_pc_linerate',
+                        'min_latency',
+                        'max_latency',
+                        'avg_latency',
+                    ],
+                },
+                'connection-point': [
+                    {
+                        'name': 'xe0',
+                        'type': 'VPORT',
+                    },
+                    {
+                        'name': 'xe1',
+                        'type': 'VPORT',
+                    },
+                ],
+                'description': 'TRex stateless traffic generator for RFC2544',
+                'id': 'TrexTrafficGen',
+                'mgmt-interface': {
+                    'ip': '1.1.1.1',
+                    'password': 'berta',
+                    'user': 'berta',
+                    'vdu-id': 'trexgen-baremetal',
+                },
+                'name': 'trexgen',
+                'short-name': 'trexgen',
+                'class-name': 'TrexTrafficGen',
+                'vdu': [
+                    {
+                        'description': 'TRex stateless traffic generator for RFC2544',
+                        'external-interface': [
+                            {
+                                'name': 'xe0',
+                                'virtual-interface': {
+                                    'bandwidth': '10 Gbps',
+                                    'dst_ip': '1.1.1.1',
+                                    'dst_mac': '00:01:02:03:04:05',
+                                    'local_ip': '1.1.1.2',
+                                    'local_mac': '00:01:02:03:05:05',
+                                    'type': 'PCI-PASSTHROUGH',
+                                    'netmask': "255.255.255.0",
+                                    'driver': 'i40',
+                                    'vpci': '0000:00:10.2',
+                                },
+                                'vnfd-connection-point-ref': 'xe0',
+                            },
+                            {
+                                'name': 'xe1',
+                                'virtual-interface': {
+                                    'bandwidth': '10 Gbps',
+                                    'dst_ip': '2.1.1.1',
+                                    'dst_mac': '00:01:02:03:04:06',
+                                    'local_ip': '2.1.1.2',
+                                    'local_mac': '00:01:02:03:05:06',
+                                    'type': 'PCI-PASSTHROUGH',
+                                    'netmask': "255.255.255.0",
+                                    'driver': 'i40',
+                                    'vpci': '0000:00:10.1',
+                                },
+                                'vnfd-connection-point-ref': 'xe1',
+                            },
+                        ],
+                        'id': 'trexgen-baremetal',
+                        'name': 'trexgen-baremetal',
+                    },
+                ],
+            },
+        ],
+    },
+}
 
 IP_ADDR_SHOW = """
-28: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP """
-"""group default qlen 1000
+28: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \
+group default qlen 1000
     link/ether 90:e2:ba:a7:6a:c8 brd ff:ff:ff:ff:ff:ff
     inet 1.1.1.1/8 brd 1.255.255.255 scope global eth1
     inet6 fe80::92e2:baff:fea7:6ac8/64 scope link
        valid_lft forever preferred_lft forever
-29: eth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP """
-"""group default qlen 1000
+29: eth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \
+group default qlen 1000
     link/ether 90:e2:ba:a7:6a:c9 brd ff:ff:ff:ff:ff:ff
     inet 2.1.1.1/8 brd 2.255.255.255 scope global eth5
     inet6 fe80::92e2:baff:fea7:6ac9/64 scope link tentative
@@ -160,10 +189,10 @@ IP_ADDR_SHOW = """
 """
 
 SYS_CLASS_NET = """
-lrwxrwxrwx 1 root root 0 sie 10 14:16 eth1 -> """
-"""../../devices/pci0000:80/0000:80:02.2/0000:84:00.1/net/eth1
-lrwxrwxrwx 1 root root 0 sie  3 10:37 eth2 -> """
-"""../../devices/pci0000:00/0000:00:01.1/0000:84:00.2/net/eth5
+lrwxrwxrwx 1 root root 0 sie 10 14:16 eth1 -> \
+../../devices/pci0000:80/0000:80:02.2/0000:84:00.1/net/eth1
+lrwxrwxrwx 1 root root 0 sie  3 10:37 eth2 -> \
+../../devices/pci0000:00/0000:00:01.1/0000:84:00.2/net/eth5
 """
 
 TRAFFIC_PROFILE = {
@@ -174,137 +203,195 @@ TRAFFIC_PROFILE = {
         "traffic_type": "FixedTraffic",
         "frame_rate": 100,  # pps
         "flow_number": 10,
-        "frame_size": 64}}
+        "frame_size": 64,
+    },
+}
 
 
 class TestNetworkServiceTestCase(unittest.TestCase):
     def setUp(self):
-        self.context_cfg = \
-            {'nodes':
-             {'trexgen__1': {'role': 'TrafficGen',
-                             'name': 'trafficgen_1.yardstick',
-                             'ip': '10.10.10.11',
-                             'interfaces':
-                             {'xe0':
-                              {'netmask': '255.255.255.0',
-                               'local_ip': '152.16.100.20',
-                               'local_mac': '00:00:00:00:00:01',
-                               'driver': 'i40e',
-                               'vpci': '0000:07:00.0',
-                               'dpdk_port_num': 0},
-                              'xe1':
-                              {'netmask': '255.255.255.0',
-                               'local_ip': '152.16.40.20',
-                               'local_mac': '00:00:00:00:00:02',
-                               'driver': 'i40e',
-                               'vpci': '0000:07:00.1',
-                               'dpdk_port_num': 1}},
-                             'password': 'r00t',
-                             'user': 'root'},
-              'trexvnf__1': {'name': 'vnf.yardstick',
-                             'ip': '10.10.10.12',
-                             'interfaces':
-                             {'xe0':
-                              {'netmask': '255.255.255.0',
-                               'local_ip': '152.16.100.19',
-                               'local_mac': '00:00:00:00:00:03',
-                               'driver': 'i40e',
-                               'vpci': '0000:07:00.0',
-                               'dpdk_port_num': 0},
-                              'xe1': {'netmask': '255.255.255.0',
-                                      'local_ip': '152.16.40.19',
-                                      'local_mac': '00:00:00:00:00:04',
-                                      'driver': 'i40e',
-                                      'vpci': '0000:07:00.1',
-                                      'dpdk_port_num': 1}},
-                             'routing_table': [{'netmask': '255.255.255.0',
-                                                'gateway': '152.16.100.20',
-                                                'network': '152.16.100.20',
-                                                'if': 'xe0'},
-                                               {'netmask': '255.255.255.0',
-                                                'gateway': '152.16.40.20',
-                                                'network': '152.16.40.20',
-                                                'if': 'xe1'}],
-                             'host': '10.223.197.164',
-                             'role': 'vnf',
-                             'user': 'root',
-                             'nd_route_tbl':
-                             [{'netmask': '112',
-                               'gateway': '0064:ff9b:0:0:0:0:9810:6414',
-                               'network': '0064:ff9b:0:0:0:0:9810:6414',
-                               'if': 'xe0'},
-                              {'netmask': '112',
-                               'gateway': '0064:ff9b:0:0:0:0:9810:2814',
-                               'network': '0064:ff9b:0:0:0:0:9810:2814',
-                               'if': 'xe1'}],
-                             'password': 'r00t'}}}
+        self.trexgen__1 = {
+            'name': 'trafficgen_1.yardstick',
+            'ip': '10.10.10.11',
+            'role': 'TrafficGen',
+            'user': 'root',
+            'password': 'r00t',
+            'interfaces': {
+                'xe0': {
+                    'netmask': '255.255.255.0',
+                    'local_ip': '152.16.100.20',
+                    'local_mac': '00:00:00:00:00:01',
+                    'driver': 'i40e',
+                    'vpci': '0000:07:00.0',
+                    'dpdk_port_num': 0,
+                },
+                'xe1': {
+                    'netmask': '255.255.255.0',
+                    'local_ip': '152.16.40.20',
+                    'local_mac': '00:00:00:00:00:02',
+                    'driver': 'i40e',
+                    'vpci': '0000:07:00.1',
+                    'dpdk_port_num': 1,
+                },
+            },
+        }
+
+        self.trexvnf__1 = {
+            'name': 'vnf.yardstick',
+            'ip': '10.10.10.12',
+            'host': '10.223.197.164',
+            'role': 'vnf',
+            'user': 'root',
+            'password': 'r00t',
+            'interfaces': {
+                'xe0': {
+                    'netmask': '255.255.255.0',
+                    'local_ip': '152.16.100.19',
+                    'local_mac': '00:00:00:00:00:03',
+                    'driver': 'i40e',
+                    'vpci': '0000:07:00.0',
+                    'dpdk_port_num': 0,
+                },
+                'xe1': {
+                    'netmask': '255.255.255.0',
+                    'local_ip': '152.16.40.19',
+                    'local_mac': '00:00:00:00:00:04',
+                    'driver': 'i40e',
+                    'vpci': '0000:07:00.1',
+                    'dpdk_port_num': 1,
+                },
+            },
+            'routing_table': [
+                {
+                    'netmask': '255.255.255.0',
+                    'gateway': '152.16.100.20',
+                    'network': '152.16.100.20',
+                    'if': 'xe0',
+                },
+                {
+                    'netmask': '255.255.255.0',
+                    'gateway': '152.16.40.20',
+                    'network': '152.16.40.20',
+                    'if': 'xe1',
+                },
+            ],
+            'nd_route_tbl': [
+                {
+                    'netmask': '112',
+                    'gateway': '0064:ff9b:0:0:0:0:9810:6414',
+                    'network': '0064:ff9b:0:0:0:0:9810:6414',
+                    'if': 'xe0',
+                },
+                {
+                    'netmask': '112',
+                    'gateway': '0064:ff9b:0:0:0:0:9810:2814',
+                    'network': '0064:ff9b:0:0:0:0:9810:2814',
+                    'if': 'xe1',
+                },
+            ],
+        }
+
+        self.context_cfg = {
+            'nodes': {
+                'trexgen__1': self.trexgen__1,
+                'trexvnf__1': self.trexvnf__1,
+            },
+            'networks': {
+                'private': {
+                    'vld_id': 'private',
+                },
+                'public': {
+                    'vld_id': 'public',
+                },
+            },
+        }
+
+        self.vld0 = {
+            'vnfd-connection-point-ref': [
+                {
+                    'vnfd-connection-point-ref': 'xe0',
+                    'member-vnf-index-ref': '1',
+                    'vnfd-id-ref': 'trexgen'
+                },
+                {
+                    'vnfd-connection-point-ref': 'xe0',
+                    'member-vnf-index-ref': '2',
+                    'vnfd-id-ref': 'trexgen'
+                }
+            ],
+            'type': 'ELAN',
+            'id': 'private',
+            'name': 'trexgen__1 to trexvnf__1 link 1'
+        }
+
+        self.vld1 = {
+            'vnfd-connection-point-ref': [
+                {
+                    'vnfd-connection-point-ref': 'xe1',
+                    'member-vnf-index-ref': '1',
+                    'vnfd-id-ref': 'trexgen'
+                },
+                {
+                    'vnfd-connection-point-ref': 'xe1',
+                    'member-vnf-index-ref': '2',
+                    'vnfd-id-ref': 'trexgen'
+                }
+            ],
+            'type': 'ELAN',
+            'id': 'public',
+            'name': 'trexvnf__1 to trexgen__1 link 2'
+        }
 
         self.topology = {
+            'id': 'trex-tg-topology',
             'short-name': 'trex-tg-topology',
-            'constituent-vnfd':
-                [{'member-vnf-index': '1',
-                  'VNF model': 'tg_trex_tpl.yaml',
-                  'vnfd-id-ref': 'trexgen__1'},
-                 {'member-vnf-index': '2',
-                  'VNF model': 'tg_trex_tpl.yaml',
-                  'vnfd-id-ref': 'trexvnf__1'}],
-            'description': 'trex-tg-topology',
             'name': 'trex-tg-topology',
-            'vld': [
+            'description': 'trex-tg-topology',
+            'constituent-vnfd': [
                 {
-                    'vnfd-connection-point-ref': [
-                        {
-                            'vnfd-connection-point-ref': 'xe0',
-                            'member-vnf-index-ref': '1',
-                            'vnfd-id-ref': 'trexgen'
-                        },
-                        {
-                            'vnfd-connection-point-ref': 'xe0',
-                            'member-vnf-index-ref': '2',
-                            'vnfd-id-ref': 'trexgen'
-                        }
-                    ],
-                    'type': 'ELAN',
-                    'id': 'private',
-                    'name': 'trexgen__1 to trexvnf__1 link 1'
+                    'member-vnf-index': '1',
+                    'VNF model': 'tg_trex_tpl.yaml',
+                    'vnfd-id-ref': 'trexgen__1',
                 },
                 {
-                    'vnfd-connection-point-ref': [
-                        {
-                            'vnfd-connection-point-ref': 'xe1',
-                            'member-vnf-index-ref': '1',
-                            'vnfd-id-ref': 'trexgen'
-                        },
-                        {
-                            'vnfd-connection-point-ref': 'xe1',
-                            'member-vnf-index-ref': '2',
-                            'vnfd-id-ref': 'trexgen'
-                        }
-                    ],
-                    'type': 'ELAN',
-                    'id': 'public',
-                    'name': 'trexvnf__1 to trexgen__1 link 2'
-                }],
-            'id': 'trex-tg-topology',
+                    'member-vnf-index': '2',
+                    'VNF model': 'tg_trex_tpl.yaml',
+                    'vnfd-id-ref': 'trexvnf__1',
+                },
+            ],
+            'vld': [self.vld0, self.vld1],
         }
 
         self.scenario_cfg = {
             'task_path': "",
-            'tc_options': {'rfc2544': {'allowed_drop_rate': '0.8 - 1'}},
+            "topology": self._get_file_abspath("vpe_vnf_topology.yaml"),
             'task_id': 'a70bdf4a-8e67-47a3-9dc1-273c14506eb7',
             'tc': 'tc_ipv4_1Mflow_64B_packetsize',
-            'runner': {'object': 'NetworkServiceTestCase',
-                       'interval': 35,
-                       'output_filename': 'yardstick.out',
-                       'runner_id': 74476,
-                       'duration': 400, 'type': 'Duration'},
             'traffic_profile': 'ipv4_throughput_vpe.yaml',
-            'traffic_options': {'flow': 'ipv4_1flow_Packets_vpe.yaml',
-                                'imix': 'imix_voice.yaml'}, 'type': 'ISB',
-            'nodes': {'tg__2': 'trafficgen_2.yardstick',
-                      'tg__1': 'trafficgen_1.yardstick',
-                      'vnf__1': 'vnf.yardstick'},
-            "topology": self._get_file_abspath("vpe_vnf_topology.yaml")}
+            'type': 'ISB',
+            'tc_options': {
+                'rfc2544': {
+                    'allowed_drop_rate': '0.8 - 1',
+                },
+            },
+            'runner': {
+                'object': 'NetworkServiceTestCase',
+                'interval': 35,
+                'output_filename': 'yardstick.out',
+                'runner_id': 74476,
+                'duration': 400,
+                'type': 'Duration',
+            },
+            'traffic_options': {
+                'flow': 'ipv4_1flow_Packets_vpe.yaml',
+                'imix': 'imix_voice.yaml'
+            },
+            'nodes': {
+                'tg__2': 'trafficgen_2.yardstick',
+                'tg__1': 'trafficgen_1.yardstick',
+                'vnf__1': 'vnf.yardstick',
+            },
+        }
 
         self.s = NetworkServiceTestCase(self.scenario_cfg, self.context_cfg)
 
@@ -339,10 +426,18 @@ class TestNetworkServiceTestCase(unittest.TestCase):
         self.assertEqual({}, self.s._get_traffic_flow(self.scenario_cfg))
 
     def test_get_vnf_imp(self):
-        vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0]['class-name']
         with mock.patch.dict("sys.modules", STL_MOCKS):
             self.assertIsNotNone(self.s.get_vnf_impl(vnfd))
 
+            with self.assertRaises(IncorrectConfig) as raised:
+                self.s.get_vnf_impl('NonExistentClass')
+
+            exc_str = str(raised.exception)
+            print(exc_str)
+            self.assertIn('No implementation', exc_str)
+            self.assertIn('found in', exc_str)
+
     def test_load_vnf_models_invalid(self):
         self.context_cfg["nodes"]['trexgen__1']['VNF model'] = \
             self._get_file_abspath("tg_trex_tpl.yaml")
@@ -363,10 +458,10 @@ class TestNetworkServiceTestCase(unittest.TestCase):
             ssh.from_node.return_value = ssh_mock
             self.s.map_topology_to_infrastructure(self.context_cfg,
                                                   self.topology)
-        self.assertEqual("tg_trex_tpl.yaml",
-                         self.context_cfg["nodes"]['trexgen__1']['VNF model'])
-        self.assertEqual("tg_trex_tpl.yaml",
-                         self.context_cfg["nodes"]['trexvnf__1']['VNF model'])
+
+        nodes = self.context_cfg["nodes"]
+        self.assertEqual("tg_trex_tpl.yaml", nodes['trexgen__1']['VNF model'])
+        self.assertEqual("tg_trex_tpl.yaml", nodes['trexvnf__1']['VNF model'])
 
     def test_map_topology_to_infrastructure_insufficient_nodes(self):
         del self.context_cfg['nodes']['trexvnf__1']
@@ -376,9 +471,8 @@ class TestNetworkServiceTestCase(unittest.TestCase):
                 mock.Mock(return_value=(1, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
             ssh.from_node.return_value = ssh_mock
 
-            self.assertRaises(IncorrectSetup,
-                              self.s.map_topology_to_infrastructure,
-                              self.context_cfg, self.topology)
+            with self.assertRaises(IncorrectSetup):
+                self.s.map_topology_to_infrastructure(self.context_cfg, self.topology)
 
     def test_map_topology_to_infrastructure_config_invalid(self):
         cfg = dict(self.context_cfg)
@@ -389,9 +483,8 @@ class TestNetworkServiceTestCase(unittest.TestCase):
                 mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
             ssh.from_node.return_value = ssh_mock
 
-            self.assertRaises(IncorrectConfig,
-                              self.s.map_topology_to_infrastructure,
-                              self.context_cfg, self.topology)
+            with self.assertRaises(IncorrectConfig):
+                self.s.map_topology_to_infrastructure(self.context_cfg, self.topology)
 
     def test__resolve_topology_invalid_config(self):
         with mock.patch("yardstick.ssh.SSH") as ssh:
@@ -400,14 +493,32 @@ class TestNetworkServiceTestCase(unittest.TestCase):
                 mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, ""))
             ssh.from_node.return_value = ssh_mock
 
-            del self.context_cfg['nodes']
-            self.assertRaises(IncorrectConfig, self.s._resolve_topology,
-                              self.context_cfg, self.topology)
+            # purge an important key from the data structure
+            for interface in self.trexgen__1['interfaces'].values():
+                del interface['local_mac']
+
+            with self.assertRaises(IncorrectConfig) as raised:
+                self.s._resolve_topology(self.context_cfg, self.topology)
+
+            self.assertIn('not found', str(raised.exception))
+
+            # make a connection point ref with 3 points
+            self.vld0['vnfd-connection-point-ref'].append(
+                self.vld0['vnfd-connection-point-ref'][0])
+
+            with self.assertRaises(IncorrectConfig) as raised:
+                self.s._resolve_topology(self.context_cfg, self.topology)
+
+            self.assertIn('wrong number of endpoints', str(raised.exception))
+
+            # make a connection point ref with 1 point
+            self.vld0['vnfd-connection-point-ref'] = \
+                self.vld0['vnfd-connection-point-ref'][:1]
+
+            with self.assertRaises(IncorrectConfig) as raised:
+                self.s._resolve_topology(self.context_cfg, self.topology)
 
-            self.topology['vld'][0]['vnfd-connection-point-ref'].append(
-                self.topology['vld'][0]['vnfd-connection-point-ref'])
-            self.assertRaises(IncorrectConfig, self.s._resolve_topology,
-                              self.context_cfg, self.topology)
+            self.assertIn('wrong number of endpoints', str(raised.exception))
 
     def test_run(self):
         tgen = mock.Mock(autospec=GenericTrafficGen)
@@ -462,8 +573,8 @@ class TestNetworkServiceTestCase(unittest.TestCase):
     def test__get_traffic_profile_exception(self):
         cfg = dict(self.scenario_cfg)
         cfg["traffic_profile"] = ""
-        self.assertRaises(IOError, self.s._get_traffic_profile, cfg,
-                          self.context_cfg)
+        with self.assertRaises(IOError):
+            self.s._get_traffic_profile(cfg, self.context_cfg)
 
     def test___get_traffic_imix_exception(self):
         cfg = dict(self.scenario_cfg)
index 3b38733..3dc8ad7 100644 (file)
@@ -11,6 +11,7 @@
 
 # Unittest for yardstick.benchmark.orchestrator.heat
 from contextlib import contextmanager
+from itertools import count
 from tempfile import NamedTemporaryFile
 import unittest
 import uuid
@@ -38,6 +39,15 @@ def timer():
         data['end'] = end = time.time()
         data['delta'] = end - start
 
+
+def index_value_iter(index, index_value, base_value=None):
+    for current_index in count():
+        if current_index == index:
+            yield index_value
+        else:
+            yield base_value
+
+
 def get_error_message(error):
     try:
         # py2
@@ -249,7 +259,7 @@ class HeatTemplateTestCase(unittest.TestCase):
     @mock_patch_target_module('op_utils')
     @mock_patch_target_module('heatclient.client.Client')
     def test_create(self, mock_heat_client_class, mock_op_utils):
-        self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2
+        self.template.HEAT_WAIT_LOOP_INTERVAL = 0.2
         mock_heat_client = mock_heat_client_class()
 
         # populate attributes of the constructed mock
@@ -270,12 +280,11 @@ class HeatTemplateTestCase(unittest.TestCase):
         expected_op_utils_usage = 0
 
         with mock.patch.object(self.template, 'status') as mock_status:
-            # no block
-            with timer() as time_data:
-                self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack)
+            self.template.name = 'no block test'
+            mock_status.return_value = None
 
-            # ensure runtime is much less than one interval
-            self.assertLess(time_data['delta'], interval * 0.2)
+            # no block
+            self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack)
 
             # ensure op_utils was used
             expected_op_utils_usage += 1
@@ -296,12 +305,10 @@ class HeatTemplateTestCase(unittest.TestCase):
             self.assertEqual(self.template.outputs, {})
 
             # block with immediate complete
-            mock_status.return_value = u'CREATE_COMPLETE'
-            with timer() as time_data:
-                self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
+            self.template.name = 'block, immediate complete test'
 
-            # ensure runtime is less than one interval
-            self.assertLess(time_data['delta'], interval * 0.2)
+            mock_status.return_value = self.template.HEAT_CREATE_COMPLETE_STATUS
+            self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
 
             # ensure existing instance was re-used and op_utils was not used
             expected_create_calls += 1
@@ -319,14 +326,12 @@ class HeatTemplateTestCase(unittest.TestCase):
             self.template.outputs = None
 
             # block with delayed complete
-            mock_status.side_effect = iter([None, None, u'CREATE_COMPLETE'])
-            with timer() as time_data:
-                self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
+            self.template.name = 'block, delayed complete test'
 
-            # ensure runtime is approximately two intervals
-            expected_time_low = interval * 1.8
-            expected_time_high = interval * 2.2
-            self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high)
+            success_index = 2
+            mock_status.side_effect = index_value_iter(success_index,
+                                                       self.template.HEAT_CREATE_COMPLETE_STATUS)
+            self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
 
             # ensure existing instance was re-used and op_utils was not used
             expected_create_calls += 1
@@ -334,7 +339,7 @@ class HeatTemplateTestCase(unittest.TestCase):
             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
 
             # ensure status was checked three more times
-            expected_status_calls += 3
+            expected_status_calls += 1 + success_index
             self.assertEqual(mock_status.call_count, expected_status_calls)
 
 
@@ -348,7 +353,8 @@ class HeatStackTestCase(unittest.TestCase):
         # call once and then call again if uuid is not none
         self.assertGreater(delete_mock.call_count, 1)
 
-    def test_delete_all_calls_delete(self):
+    @mock.patch('yardstick.orchestrator.heat.op_utils')
+    def test_delete_all_calls_delete(self, mock_op):
         stack = heat.HeatStack('test')
         stack.uuid = 1
         with mock.patch.object(stack, "delete") as delete_mock:
index 0be2eee..e362c6a 100644 (file)
@@ -23,7 +23,7 @@ class Context(object):
 
     @abc.abstractmethod
     def init(self, attrs):
-        "Initiate context."
+        """Initiate context."""
 
     @staticmethod
     def get_cls(context_type):
@@ -56,20 +56,34 @@ class Context(object):
         """get server info by name from context
         """
 
+    @abc.abstractmethod
+    def _get_network(self, attr_name):
+        """get network info by name from context
+        """
+
     @staticmethod
     def get_server(attr_name):
         """lookup server info by name from context
         attr_name: either a name for a server created by yardstick or a dict
         with attribute name mapping when using external heat templates
         """
-        server = None
-        for context in Context.list:
-            server = context._get_server(attr_name)
-            if server is not None:
-                break
-
-        if server is None:
+        servers = (context._get_server(attr_name) for context in Context.list)
+        try:
+            return next(s for s in servers if s)
+        except StopIteration:
             raise ValueError("context not found for server '%r'" %
                              attr_name)
 
-        return server
+    @staticmethod
+    def get_network(attr_name):
+        """lookup server info by name from context
+        attr_name: either a name for a server created by yardstick or a dict
+        with attribute name mapping when using external heat templates
+        """
+
+        networks = (context._get_network(attr_name) for context in Context.list)
+        try:
+            return next(n for n in networks if n)
+        except StopIteration:
+            raise ValueError("context not found for server '%r'" %
+                             attr_name)
index c658d32..8ae4b65 100644 (file)
@@ -37,3 +37,6 @@ class DummyContext(Context):
 
     def _get_server(self, attr_name):
         return None
+
+    def _get_network(self, attr_name):
+        return None
index fed8fc3..0a94dd9 100644 (file)
@@ -25,6 +25,7 @@ from yardstick.benchmark.contexts.model import Network
 from yardstick.benchmark.contexts.model import PlacementGroup, ServerGroup
 from yardstick.benchmark.contexts.model import Server
 from yardstick.benchmark.contexts.model import update_scheduler_hints
+from yardstick.common.openstack_utils import get_neutron_client
 from yardstick.orchestrator.heat import HeatTemplate, get_short_key_uuid
 from yardstick.common.constants import YARDSTICK_ROOT_PATH
 
@@ -54,9 +55,11 @@ class HeatContext(Context):
         self._user = None
         self.template_file = None
         self.heat_parameters = None
+        self.neutron_client = None
         # generate an uuid to identify yardstick_key
         # the first 8 digits of the uuid will be used
         self.key_uuid = uuid.uuid4()
+        self.heat_timeout = None
         self.key_filename = ''.join(
             [YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-',
              get_short_key_uuid(self.key_uuid)])
@@ -65,15 +68,16 @@ class HeatContext(Context):
     def assign_external_network(self, networks):
         sorted_networks = sorted(networks.items())
         external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext")
-        have_external_network = [(name, net)
-                                 for name, net in sorted_networks if
-                                 net.get("external_network")]
-        # no external net defined, assign it to first network usig os.environ
+
+        have_external_network = any(net.get("external_network") for net in networks.values())
         if sorted_networks and not have_external_network:
+            # no external net defined, assign it to first network using os.environ
             sorted_networks[0][1]["external_network"] = external_network
-        return sorted_networks
 
-    def init(self, attrs):     # pragma: no cover
+        self.networks = OrderedDict((name, Network(name, self, attrs))
+                                    for name, attrs in sorted_networks)
+
+    def init(self, attrs):
         """initializes itself from the supplied arguments"""
         self.name = attrs["name"]
 
@@ -103,11 +107,7 @@ class HeatContext(Context):
 
         # we have to do this first, because we are injecting external_network
         # into the dict
-        sorted_networks = self.assign_external_network(attrs["networks"])
-
-        self.networks = OrderedDict(
-            (name, Network(name, self, netattrs)) for name, netattrs in
-            sorted_networks)
+        self.assign_external_network(attrs["networks"])
 
         for name, serverattrs in sorted(attrs["servers"].items()):
             server = Server(name, self, serverattrs)
@@ -120,7 +120,6 @@ class HeatContext(Context):
         with open(self.key_filename + ".pub", "w") as pubkey_file:
             pubkey_file.write(
                 "%s %s\n" % (rsa_key.get_name(), rsa_key.get_base64()))
-        del rsa_key
 
     @property
     def image(self):
@@ -194,7 +193,7 @@ class HeatContext(Context):
             scheduler_hints = {}
             for pg in server.placement_groups:
                 update_scheduler_hints(scheduler_hints, added_servers, pg)
-            # workround for openstack nova bug, check JIRA: YARDSTICK-200
+            # workaround for openstack nova bug, check JIRA: YARDSTICK-200
             # for details
             if len(availability_servers) == 2:
                 if not scheduler_hints["different_host"]:
@@ -250,6 +249,20 @@ class HeatContext(Context):
                                        list(self.networks.values()),
                                        scheduler_hints)
 
+    def get_neutron_info(self):
+        if not self.neutron_client:
+            self.neutron_client = get_neutron_client()
+
+        networks = self.neutron_client.list_networks()
+        for network in self.networks.values():
+            for neutron_net in networks['networks']:
+                if neutron_net['name'] == network.stack_name:
+                    network.segmentation_id = neutron_net.get('provider:segmentation_id')
+                    # we already have physical_network
+                    # network.physical_network = neutron_net.get('provider:physical_network')
+                    network.network_type = neutron_net.get('provider:network_type')
+                    network.neutron_info = neutron_net
+
     def deploy(self):
         """deploys template into a stack using cloud"""
         print("Deploying context '%s'" % self.name)
@@ -267,20 +280,16 @@ class HeatContext(Context):
             raise SystemExit("\nStack create interrupted")
         except:
             LOG.exception("stack failed")
+            # let the other failures happen, we want stack trace
             raise
-        # let the other failures happend, we want stack trace
+
+        # TODO: use Neutron to get segementation-id
+        self.get_neutron_info()
 
         # copy some vital stack output into server objects
         for server in self.servers:
             if server.ports:
-                # TODO(hafe) can only handle one internal network for now
-                port = next(iter(server.ports.values()))
-                server.private_ip = self.stack.outputs[port["stack_name"]]
-                server.interfaces = {}
-                for network_name, port in server.ports.items():
-                    self.make_interface_dict(network_name, port['stack_name'],
-                                             server,
-                                             self.stack.outputs)
+                self.add_server_port(server)
 
             if server.floating_ip:
                 server.public_ip = \
@@ -288,24 +297,36 @@ class HeatContext(Context):
 
         print("Context '%s' deployed" % self.name)
 
-    def make_interface_dict(self, network_name, stack_name, server, outputs):
-        server.interfaces[network_name] = {
-            "private_ip": outputs[stack_name],
+    def add_server_port(self, server):
+        # TODO(hafe) can only handle one internal network for now
+        port = next(iter(server.ports.values()))
+        server.private_ip = self.stack.outputs[port["stack_name"]]
+        server.interfaces = {}
+        for network_name, port in server.ports.items():
+            server.interfaces[network_name] = self.make_interface_dict(
+                network_name, port['stack_name'], self.stack.outputs)
+
+    def make_interface_dict(self, network_name, stack_name, outputs):
+        private_ip = outputs[stack_name]
+        mac_addr = outputs[stack_name + "-mac_address"]
+        subnet_cidr_key = "-".join([self.name, network_name, 'subnet', 'cidr'])
+        gateway_key = "-".join([self.name, network_name, 'subnet', 'gateway_ip'])
+        subnet_cidr = outputs[subnet_cidr_key]
+        subnet_ip = ipaddress.ip_network(subnet_cidr)
+        return {
+            "private_ip": private_ip,
             "subnet_id": outputs[stack_name + "-subnet_id"],
-            "subnet_cidr": outputs[
-                "{}-{}-subnet-cidr".format(self.name, network_name)],
-            "netmask": str(ipaddress.ip_network(
-                outputs["{}-{}-subnet-cidr".format(self.name,
-                                                   network_name)]).netmask),
-            "gateway_ip": outputs[
-                "{}-{}-subnet-gateway_ip".format(self.name, network_name)],
-            "mac_address": outputs[stack_name + "-mac_address"],
+            "subnet_cidr": subnet_cidr,
+            "network": str(subnet_ip.network_address),
+            "netmask": str(subnet_ip.netmask),
+            "gateway_ip": outputs[gateway_key],
+            "mac_address": mac_addr,
             "device_id": outputs[stack_name + "-device_id"],
             "network_id": outputs[stack_name + "-network_id"],
             "network_name": network_name,
             # to match vnf_generic
-            "local_mac": outputs[stack_name + "-mac_address"],
-            "local_ip": outputs[stack_name],
+            "local_mac": mac_addr,
+            "local_ip": private_ip,
             "vld_id": self.networks[network_name].vld_id,
         }
 
@@ -326,6 +347,19 @@ class HeatContext(Context):
 
         super(HeatContext, self).undeploy()
 
+    @staticmethod
+    def generate_routing_table(server):
+        routes = [
+            {
+                "network": intf["network"],
+                "netmask": intf["netmask"],
+                "if": name,
+                "gateway": intf["gateway_ip"],
+            }
+            for name, intf in server.interfaces.items()
+        ]
+        return routes
+
     def _get_server(self, attr_name):
         """lookup server info by name from context
         attr_name: either a name for a server created by yardstick or a dict
@@ -335,7 +369,10 @@ class HeatContext(Context):
             'yardstick.resources',
             'files/yardstick_key-' + get_short_key_uuid(self.key_uuid))
 
-        if isinstance(attr_name, collections.Mapping):
+        if not isinstance(attr_name, collections.Mapping):
+            server = self._server_map.get(attr_name, None)
+
+        else:
             cname = attr_name["name"].split(".")[1]
             if cname != self.name:
                 return None
@@ -352,10 +389,6 @@ class HeatContext(Context):
             server = Server(attr_name["name"].split(".")[0], self, {})
             server.public_ip = public_ip
             server.private_ip = private_ip
-        else:
-            if attr_name not in self._server_map:
-                return None
-            server = self._server_map[attr_name]
 
         if server is None:
             return None
@@ -365,9 +398,37 @@ class HeatContext(Context):
             "key_filename": key_filename,
             "private_ip": server.private_ip,
             "interfaces": server.interfaces,
+            "routing_table": self.generate_routing_table(server),
+            # empty IPv6 routing table
+            "nd_route_tbl": [],
         }
         # Target server may only have private_ip
         if server.public_ip:
             result["ip"] = server.public_ip
 
         return result
+
+    def _get_network(self, attr_name):
+        if not isinstance(attr_name, collections.Mapping):
+            network = self.networks.get(attr_name, None)
+
+        else:
+            # Don't generalize too much  Just support vld_id
+            vld_id = attr_name.get('vld_id')
+            if vld_id is None:
+                return None
+
+            network = next((n for n in self.networks.values() if
+                           getattr(n, "vld_id", None) == vld_id), None)
+
+        if network is None:
+            return None
+
+        result = {
+            "name": network.name,
+            "vld_id": network.vld_id,
+            "segmentation_id": network.segmentation_id,
+            "network_type": network.network_type,
+            "physical_network": network.physical_network,
+        }
+        return result
index 5077a97..06538d8 100644 (file)
@@ -106,13 +106,14 @@ class Network(Object):
         self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
         self.router = None
         self.physical_network = attrs.get('physical_network', 'physnet1')
-        self.provider = attrs.get('provider', None)
-        self.segmentation_id = attrs.get('segmentation_id', None)
+        self.provider = attrs.get('provider')
+        self.segmentation_id = attrs.get('segmentation_id')
+        self.network_type = attrs.get('network_type')
 
         if "external_network" in attrs:
             self.router = Router("router", self.name,
                                  context, attrs["external_network"])
-        self.vld_id = attrs.get("vld_id", "")
+        self.vld_id = attrs.get("vld_id")
 
         Network.list.append(self)
 
index baa1cf5..b3f0aca 100644 (file)
@@ -33,6 +33,7 @@ class NodeContext(Context):
         self.name = None
         self.file_path = None
         self.nodes = []
+        self.networks = {}
         self.controllers = []
         self.computes = []
         self.baremetals = []
@@ -77,6 +78,9 @@ class NodeContext(Context):
         self.env = attrs.get('env', {})
         LOG.debug("Env: %r", self.env)
 
+        # add optional static network definition
+        self.networks.update(cfg.get("networks", {}))
+
     def deploy(self):
         config_type = self.env.get('type', '')
         if config_type == 'ansible':
@@ -141,6 +145,32 @@ class NodeContext(Context):
         node["name"] = attr_name
         return node
 
+    def _get_network(self, attr_name):
+        if not isinstance(attr_name, collections.Mapping):
+            network = self.networks.get(attr_name)
+
+        else:
+            # Don't generalize too much  Just support vld_id
+            vld_id = attr_name.get('vld_id')
+            if vld_id is None:
+                return None
+
+            network = next((n for n in self.networks.values() if
+                           n.get("vld_id") == vld_id), None)
+
+        if network is None:
+            return None
+
+        result = {
+            # name is required
+            "name": network["name"],
+            "vld_id": network.get("vld_id"),
+            "segmentation_id": network.get("segmentation_id"),
+            "network_type": network.get("network_type"),
+            "physical_network": network.get("physical_network"),
+        }
+        return result
+
     def _execute_script(self, node_name, info):
         if node_name == 'local':
             self._execute_local_script(info)
index 78eaac7..8614f0c 100644 (file)
@@ -36,6 +36,7 @@ class StandaloneContext(Context):
         self.name = None
         self.file_path = None
         self.nodes = []
+        self.networks = {}
         self.nfvi_node = []
         super(StandaloneContext, self).__init__()
 
@@ -66,8 +67,11 @@ class StandaloneContext(Context):
         self.nodes.extend(cfg["nodes"])
         self.nfvi_node.extend([node for node in cfg["nodes"]
                                if node["role"] == "nfvi_node"])
+        # add optional static network definition
+        self.networks.update(cfg.get("networks", {}))
         LOG.debug("Nodes: %r", self.nodes)
         LOG.debug("NFVi Node: %r", self.nfvi_node)
+        LOG.debug("Networks: %r", self.networks)
 
     def deploy(self):
         """don't need to deploy"""
@@ -114,3 +118,31 @@ class StandaloneContext(Context):
 
         node["name"] = attr_name
         return node
+
+    def _get_network(self, attr_name):
+        if not isinstance(attr_name, collections.Mapping):
+            network = self.networks.get(attr_name)
+
+        else:
+            # Don't generalize too much  Just support vld_id
+            vld_id = attr_name.get('vld_id')
+            if vld_id is None:
+                return None
+            try:
+                network = next(n for n in self.networks.values() if
+                               n.get("vld_id") == vld_id)
+            except StopIteration:
+                return None
+
+        if network is None:
+            return None
+
+        result = {
+            # name is required
+            "name": network["name"],
+            "vld_id": network.get("vld_id"),
+            "segmentation_id": network.get("segmentation_id"),
+            "network_type": network.get("network_type"),
+            "physical_network": network.get("physical_network"),
+        }
+        return result
index 0e85e63..b53d644 100644 (file)
@@ -322,6 +322,8 @@ class Task(object):     # pragma: no cover
 
         if "nodes" in scenario_cfg:
             context_cfg["nodes"] = parse_nodes_with_context(scenario_cfg)
+            context_cfg["networks"] = get_networks_from_nodes(
+                context_cfg["nodes"])
         runner = base_runner.Runner.get(runner_cfg)
 
         print("Starting runner of type '%s'" % runner_cfg["type"])
@@ -518,7 +520,7 @@ class TaskParser(object):       # pragma: no cover
                                                                cfg_schema))
 
     def _check_precondition(self, cfg):
-        """Check if the envrionment meet the preconditon"""
+        """Check if the environment meet the precondition"""
 
         if "precondition" in cfg:
             precondition = cfg["precondition"]
@@ -573,14 +575,26 @@ def _is_background_scenario(scenario):
 
 
 def parse_nodes_with_context(scenario_cfg):
-    """paras the 'nodes' fields in scenario """
+    """parse the 'nodes' fields in scenario """
     nodes = scenario_cfg["nodes"]
-
-    nodes_cfg = {}
-    for nodename in nodes:
-        nodes_cfg[nodename] = Context.get_server(nodes[nodename])
-
-    return nodes_cfg
+    return {nodename: Context.get_server(node) for nodename, node in nodes.items()}
+
+
+def get_networks_from_nodes(nodes):
+    """parse the 'nodes' fields in scenario """
+    networks = {}
+    for node in nodes.values():
+        if not node:
+            continue
+        for interface in node['interfaces'].values():
+            vld_id = interface.get('vld_id')
+            # mgmt network doesn't have vld_id
+            if not vld_id:
+                continue
+            network = Context.get_network({"vld_id": vld_id})
+            if network:
+                networks[network['name']] = network
+    return networks
 
 
 def runner_join(runner):
index 594edea..9607e30 100644 (file)
@@ -164,38 +164,60 @@ class NetworkServiceTestCase(base.Scenario):
                      for vnfd in topology["constituent-vnfd"]
                      if vnf_id == vnfd["member-vnf-index"]), None)
 
+    @staticmethod
+    def get_vld_networks(networks):
+        return {n['vld_id']: n for n in networks.values()}
+
     def _resolve_topology(self, context_cfg, topology):
         for vld in topology["vld"]:
-            if len(vld["vnfd-connection-point-ref"]) > 2:
+            try:
+                node_0, node_1 = vld["vnfd-connection-point-ref"]
+            except (TypeError, ValueError):
                 raise IncorrectConfig("Topology file corrupted, "
-                                      "too many endpoint for connection")
-
-            node_0, node_1 = vld["vnfd-connection-point-ref"]
+                                      "wrong number of endpoints for connection")
 
-            node0 = self._find_vnf_name_from_id(topology,
-                                                node_0["member-vnf-index-ref"])
-            node1 = self._find_vnf_name_from_id(topology,
-                                                node_1["member-vnf-index-ref"])
+            node_0_name = self._find_vnf_name_from_id(topology,
+                                                      node_0["member-vnf-index-ref"])
+            node_1_name = self._find_vnf_name_from_id(topology,
+                                                      node_1["member-vnf-index-ref"])
 
-            if0 = node_0["vnfd-connection-point-ref"]
-            if1 = node_1["vnfd-connection-point-ref"]
+            node_0_ifname = node_0["vnfd-connection-point-ref"]
+            node_1_ifname = node_1["vnfd-connection-point-ref"]
 
+            node_0_if = context_cfg["nodes"][node_0_name]["interfaces"][node_0_ifname]
+            node_1_if = context_cfg["nodes"][node_1_name]["interfaces"][node_1_ifname]
             try:
-                nodes = context_cfg["nodes"]
-                nodes[node0]["interfaces"][if0]["vld_id"] = vld["id"]
-                nodes[node1]["interfaces"][if1]["vld_id"] = vld["id"]
-
-                nodes[node0]["interfaces"][if0]["dst_mac"] = \
-                    nodes[node1]["interfaces"][if1]["local_mac"]
-                nodes[node0]["interfaces"][if0]["dst_ip"] = \
-                    nodes[node1]["interfaces"][if1]["local_ip"]
-
-                nodes[node1]["interfaces"][if1]["dst_mac"] = \
-                    nodes[node0]["interfaces"][if0]["local_mac"]
-                nodes[node1]["interfaces"][if1]["dst_ip"] = \
-                    nodes[node0]["interfaces"][if0]["local_ip"]
+                vld_networks = self.get_vld_networks(context_cfg["networks"])
+
+                node_0_if["vld_id"] = vld["id"]
+                node_1_if["vld_id"] = vld["id"]
+
+                # set peer name
+                node_0_if["peer_name"] = node_1_name
+                node_1_if["peer_name"] = node_0_name
+
+                # set peer interface name
+                node_0_if["peer_ifname"] = node_1_ifname
+                node_1_if["peer_ifname"] = node_0_ifname
+
+                # just load the whole network dict
+                node_0_if["network"] = vld_networks.get(vld["id"], {})
+                node_1_if["network"] = vld_networks.get(vld["id"], {})
+
+                node_0_if["dst_mac"] = node_1_if["local_mac"]
+                node_0_if["dst_ip"] = node_1_if["local_ip"]
+
+                node_1_if["dst_mac"] = node_0_if["local_mac"]
+                node_1_if["dst_ip"] = node_0_if["local_ip"]
+
+                # add peer interface dict, but remove circular link
+                # TODO: don't waste memory
+                node_0_copy = node_0_if.copy()
+                node_1_copy = node_1_if.copy()
+                node_0_if["peer_intf"] = node_1_copy
+                node_1_if["peer_intf"] = node_0_copy
             except KeyError:
-                raise IncorrectConfig("Required interface not found,"
+                raise IncorrectConfig("Required interface not found, "
                                       "topology file corrupted")
 
     @classmethod
@@ -308,21 +330,36 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \
         return dict(network_devices)
 
     @classmethod
-    def get_vnf_impl(cls, vnf_model):
+    def get_vnf_impl(cls, vnf_model_id):
         """ Find the implementing class from vnf_model["vnf"]["name"] field
 
-        :param vnf_model: dictionary containing a parsed vnfd
+        :param vnf_model_id: parsed vnfd model ID field
         :return: subclass of GenericVNF
         """
         import_modules_from_package(
             "yardstick.network_services.vnf_generic.vnf")
-        expected_name = vnf_model['id']
-        impl = (c for c in itersubclasses(GenericVNF)
-                if c.__name__ == expected_name)
+        expected_name = vnf_model_id
+        classes_found = []
+
+        def impl():
+            for name, class_ in ((c.__name__, c) for c in itersubclasses(GenericVNF)):
+                if name == expected_name:
+                    yield class_
+                classes_found.append(name)
+
         try:
-            return next(impl)
+            return next(impl())
         except StopIteration:
-            raise IncorrectConfig("No implementation for %s", expected_name)
+            pass
+
+        raise IncorrectConfig("No implementation for %s found in %s" %
+                              (expected_name, classes_found))
+
+    @staticmethod
+    def update_interfaces_from_node(vnfd, node):
+        for intf in vnfd["vdu"][0]["external-interface"]:
+            node_intf = node['interfaces'][intf['name']]
+            intf['virtual-interface'].update(node_intf)
 
     def load_vnf_models(self, scenario_cfg, context_cfg):
         """ Create VNF objects based on YAML descriptors
@@ -339,8 +376,11 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \
                                     scenario_cfg['task_path']) as stream:
                 vnf_model = stream.read()
             vnfd = vnfdgen.generate_vnfd(vnf_model, node)
-            vnf_impl = self.get_vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0])
-            vnf_instance = vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0])
+            # TODO: here add extra context_cfg["nodes"] regardless of template
+            vnfd = vnfd["vnfd:vnfd-catalog"]["vnfd"][0]
+            self.update_interfaces_from_node(vnfd, node)
+            vnf_impl = self.get_vnf_impl(vnfd['id'])
+            vnf_instance = vnf_impl(vnfd)
             vnf_instance.name = node_name
             vnfs.append(vnf_instance)
 
index 7958b1c..2a907d1 100644 (file)
@@ -534,6 +534,7 @@ name (i.e. %s).\
         }
 
     HEAT_WAIT_LOOP_INTERVAL = 2
+    HEAT_CREATE_COMPLETE_STATUS = u'CREATE_COMPLETE'
 
     def create(self, block=True, timeout=3600):
         """
@@ -558,10 +559,13 @@ name (i.e. %s).\
 
         if not block:
             self.outputs = stack.outputs = {}
+            end_time = time.time()
+            log.info("Created stack '%s' in %.3e secs",
+                     self.name, end_time - start_time)
             return stack
 
         time_limit = start_time + timeout
-        for status in iter(self.status, u'CREATE_COMPLETE'):
+        for status in iter(self.status, self.HEAT_CREATE_COMPLETE_STATUS):
             log.debug("stack state %s", status)
             if status == u'CREATE_FAILED':
                 stack_status_reason = heat_client.stacks.get(self.uuid).stack_status_reason
@@ -574,7 +578,7 @@ name (i.e. %s).\
 
         end_time = time.time()
         outputs = heat_client.stacks.get(self.uuid).outputs
-        log.info("Created stack '%s' in %d secs",
+        log.info("Created stack '%s' in %.3e secs",
                  self.name, end_time - start_time)
 
         # keep outputs as unicode