diff --git a/esi/lib/__init__.py b/esi/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/esi/lib/networks.py b/esi/lib/networks.py new file mode 100644 index 0000000..92412fb --- /dev/null +++ b/esi/lib/networks.py @@ -0,0 +1,114 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import concurrent.futures + + +def get_ports(connection, filter_network=None): + if filter_network: + neutron_ports = connection.network.ports(network_id=filter_network.id) + else: + neutron_ports = connection.network.ports() + return neutron_ports + + +def network_and_port_list(connection, filter_network=None): + """Gets accessible networking information + + :param connection: An OpenStack connection + :type connection: :class:`~openstack.connection.Connection` + :param filter_network: The name or ID of a network + + :returns: A tuple of network ports, networks, floating ips, and port forwardings of the form: + ( + [openstack.network.v2.port.Port], + {openstack.network.v2.network.Network.id: openstack.network.v2.network.Network}, + {openstack.network.v2.port.Port.id: openstack.network.v2.floating_ip.FloatingIP}, + {openstack.network.v2.port.Port.id: openstack.network.v2.port_forwarding.PortForwarding} + ) + """ + + floating_ips_dict = {} + port_forwardings_dict = {} + + with concurrent.futures.ThreadPoolExecutor() as executor: + f1 = executor.submit(get_ports, connection, filter_network) + f2 = executor.submit(connection.network.networks) + f3 = executor.submit(connection.network.ips) + network_ports = list(f1.result()) + networks_dict = {network.id: network for network in list(f2.result())} + floating_ips = list(f3.result()) + + for floating_ip in floating_ips: + # no need to do this for floating IPs associated with a port, + # as port forwarding is irrelevant in such a case + if not floating_ip.port_id: + pfwds = list(connection.network.port_forwardings(floating_ip=floating_ip)) + if len(pfwds): + floating_ip.port_id = pfwds[0].internal_port_id + port_forwardings_dict[floating_ip.port_id] = pfwds + floating_ips_dict[floating_ip.port_id] = floating_ip + + return network_ports, networks_dict, floating_ips_dict, port_forwardings_dict + + +def get_networks_from_port(connection, port, networks_dict={}, floating_ips_dict={}): + """Gets associated network objects from a port object + + :param connection: An OpenStack connection + :type connection: :class:`~openstack.connection.Connection` + :param port: A network port + :type port: :class:`~openstack.network.v2.port.Port` + :param networks_dict: A dictionary mapping network IDs to network objects + :param floating_ips_dict: A dictionary mapping port IDs to floating IPs + + :returns: A tuple containing the parent network, trunk networks, trunk ports, + and the floating network associated with the given port + """ + + parent_network = None + trunk_networks = [] + trunk_ports = [] + + if port.network_id in networks_dict: + parent_network = networks_dict[port.network_id] + else: + parent_network = connection.network.get_network(network=port.network_id) + + if port.trunk_details: + with concurrent.futures.ThreadPoolExecutor() as executor: + subport_futures = [] + subport_infos = port.trunk_details['sub_ports'] + for subport_info in subport_infos: + subport_futures.append(executor.submit( + connection.network.get_port, + port=subport_info['port_id'] + )) + for subport_future in subport_futures: + subport = subport_future.result() + if subport.network_id in networks_dict: + trunk_network = networks_dict[subport.network_id] + else: + trunk_network = connection.network.get_network(subport.network_id) + trunk_ports.append(subport) + trunk_networks.append(trunk_network) + + floating_network_id = getattr(floating_ips_dict.get(port.id), + 'floating_network_id', None) + if floating_network_id is None: + floating_network = None + elif networks_dict.get(floating_network_id): + floating_network = networks_dict[floating_network_id] + else: + floating_network = connection.network.get_network(floating_network_id) + + return parent_network, trunk_networks, trunk_ports, floating_network diff --git a/esi/lib/nodes.py b/esi/lib/nodes.py new file mode 100644 index 0000000..f04cee1 --- /dev/null +++ b/esi/lib/nodes.py @@ -0,0 +1,136 @@ + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import concurrent.futures + +from esi.lib import networks + + +def node_and_port_list(connection, filter_node=None): + """Get lists baremetal nodes and ports + + :param connection: An OpenStack connection + :type connection: :class:`~openstack.connection.Connection` + :param filter_node: The name or ID of a node + + :returns: A tuple of lists of nodes and ports of the form: + ( + [openstack.baremetal.v1.node.Node], + [openstack.baremetal.v1.port.Port] + ) + """ + + nodes = None + ports = None + + if filter_node: + nodes = [connection.baremetal.find_node(name_or_id=filter_node, + ignore_missing=False)] + ports = connection.baremetal.ports(details=True, node_id=nodes[0].id) + else: + with concurrent.futures.ThreadPoolExecutor() as executor: + f1 = executor.submit(connection.baremetal.nodes) + f2 = executor.submit(connection.baremetal.ports, details=True) + nodes = list(f1.result()) + ports = list(f2.result()) + + return nodes, ports + + +def network_list(connection, filter_node=None, filter_network=None): + """List nodes and their network attributes + + :param connection: An OpenStack connection + :type connection: :class:`~openstack.connection.Connection` + :param filter_node: the name or ID of a node + :param filter_network: The name or ID of a network + + :returns: A list of dictionaries of the form: + { + 'node': openstack.baremetal.v1.node.Node, + 'network_info': [ + { + 'baremetal_port': openstack.baremetal.v1.port.Port, + 'network_port': [openstack.network.v2.port.Port] or [], + 'networks': { + 'parent': openstack.network.v2.network.Network or None, + 'trunk': [openstack.network.v2.network.Network] or [], + 'floating': openstack.network.v2.network.Network or None, + }, + 'floating_ip': openstack.network.v2.floating_ip.FloatingIP or None, + 'port_forwardings': [openstack.network.v2.port_forwarding.PortForwarding] or [] + }, + ... + ] + } + """ + + with concurrent.futures.ThreadPoolExecutor() as executor: + f1 = executor.submit(node_and_port_list, connection, filter_node) + if filter_network: + f3 = executor.submit(connection.network.find_network, + name_or_id=filter_network, + ignore_missing=False) + filter_network = f3.result() + f2 = executor.submit(networks.network_and_port_list, connection, filter_network) + baremetal_nodes, baremetal_ports = f1.result() + network_ports, networks_dict, floating_ips_dict, port_forwardings_dict = f2.result() + + data = [] + for baremetal_node in baremetal_nodes: + network_info = [] + node_ports = [bp for bp in baremetal_ports + if bp.node_id == baremetal_node.id] + + for baremetal_port in node_ports: + network_port = None + network_port_id = baremetal_port.internal_info.get('tenant_vif_port_id', None) + + if network_port_id: + network_port = next((np for np in network_ports + if np.id == network_port_id), None) + + if network_port is not None and (not filter_network or filter_network.id == network_port.network_id): + parent_network, trunk_networks, trunk_ports, floating_network \ + = networks.get_networks_from_port(connection, + network_port, + networks_dict, + floating_ips_dict) + + network_info.append({ + 'baremetal_port': baremetal_port, + 'network_ports': [network_port] + trunk_ports, + 'networks': { + 'parent': parent_network, + 'trunk': trunk_networks, + 'floating': floating_network + }, + 'floating_ip': floating_ips_dict.get(network_port.id, None), + 'port_forwardings': port_forwardings_dict.get(network_port.id, []), + }) + elif not filter_network: + network_info.append({ + 'baremetal_port': baremetal_port, + 'network_ports': [], + 'networks': {}, + 'floating_ip': None, + 'port_forwardings': [], + }) + + if network_info != []: + data.append({ + 'node': baremetal_node, + 'network_info': network_info + }) + + return data diff --git a/esi/tests/unit/base.py b/esi/tests/unit/base.py new file mode 100644 index 0000000..23a3a89 --- /dev/null +++ b/esi/tests/unit/base.py @@ -0,0 +1,32 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock +import testtools + + +class TestCase(testtools.TestCase): + """Base class for all unit tests""" + + def setUp(self): + super(TestCase, self).setUp() + + +class TestCommand(TestCase): + """Base class for all command unit tests""" + + def setUp(self): + super(TestCommand, self).setUp() + self.connection = mock.Mock() + self.connection.baremetal = mock.Mock() + self.connection.network = mock.Mock() diff --git a/esi/tests/unit/lib/__init__.py b/esi/tests/unit/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/esi/tests/unit/lib/test_networks.py b/esi/tests/unit/lib/test_networks.py new file mode 100644 index 0000000..621288f --- /dev/null +++ b/esi/tests/unit/lib/test_networks.py @@ -0,0 +1,391 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock +from unittest import TestCase + +from esi.lib import networks +from esi.tests.unit import utils as test_utils + + +class TestGetPorts(TestCase): + + def setUp(self): + super(TestGetPorts, self).setUp() + self.neutron_port1 = test_utils.create_mock_object({ + "id": "neutron_port_uuid_1", + "network_id": "network_uuid_1", + "name": "neutron_port_1", + "fixed_ips": [{"ip_address": "1.1.1.1"}], + "trunk_details": None + }) + self.neutron_port2 = test_utils.create_mock_object({ + "id": "neutron_port_uuid_2", + "network_id": "network_uuid_2", + "name": "neutron_port_2", + "fixed_ips": [{"ip_address": "2.2.2.2"}], + "trunk_details": None + }) + self.network1 = test_utils.create_mock_object({ + "id": "network_uuid_1", + "name": "test_network1" + }) + self.network2 = test_utils.create_mock_object({ + "id": "network_uuid_2", + "name": "test_network2" + }) + + self.connection = mock.Mock() + + def mock_ports(network_id=None): + if network_id == 'network_uuid_1': + return [self.neutron_port1] + elif network_id == 'network_uuid_2': + return [self.neutron_port2] + elif network_id: + return [] + return [self.neutron_port1, self.neutron_port2] + self.connection.network.ports.side_effect = mock_ports + + def test_get_ports(self): + actual = networks.get_ports(self.connection) + + expected = [ + self.neutron_port1, + self.neutron_port2, + ] + + self.assertEqual(actual, expected) + + def test_get_ports_network_filter(self): + + actual = networks.get_ports(self.connection, self.network1) + + expected = [ + self.neutron_port1 + ] + + self.assertEqual(actual, expected) + + +class TestNetworkAndPortList(TestCase): + + def setUp(self): + super(TestNetworkAndPortList, self).setUp() + self.network1 = test_utils.create_mock_object({ + "id": "network_uuid_1", + "name": "test_network_1" + }) + self.network2 = test_utils.create_mock_object({ + "id": "network_uuid_2", + "name": "test_network_2" + }) + self.neutron_port1 = test_utils.create_mock_object({ + "id": "neutron_port_uuid_1", + "network_id": "network_uuid_1", + "name": "neutron_port_1", + "fixed_ips": [{"ip_address": "1.1.1.1"}], + "trunk_details": None + }) + self.neutron_port2 = test_utils.create_mock_object({ + "id": "neutron_port_uuid_2", + "network_id": "network_uuid_2", + "name": "neutron_port_2", + "fixed_ips": [{"ip_address": "2.2.2.2"}], + "trunk_details": None + }) + self.floating_network = test_utils.create_mock_object({ + "id": "floating_network_id", + "name": "floating_network" + }) + self.floating_ip = test_utils.create_mock_object({ + "id": "floating_ip_uuid_2", + "floating_ip_address": "8.8.8.8", + "floating_network_id": "floating_network_id", + "port_id": "neutron_port_uuid_2" + }) + self.floating_ip_pfw = test_utils.create_mock_object({ + "id": "floating_ip_uuid_1", + "floating_ip_address": "9.9.9.9", + "floating_network_id": "floating_network_id", + "port_id": None + }) + self.pfw1 = test_utils.create_mock_object({ + "internal_port": 22, + "external_port": 22, + "internal_port_id": "neutron_port_uuid_1" + }) + self.pfw2 = test_utils.create_mock_object({ + "internal_port": 23, + "external_port": 23, + "internal_port_id": "neutron_port_uuid_1" + }) + + self.connection = mock.Mock() + + def mock_find_network(name_or_id=None, ignore_missing=True): + if name_or_id == 'test_network_1' or name_or_id == 'network_uuid_1': + return self.network1 + elif name_or_id == 'test_network_2' or name_or_id == 'network_uuid_2': + return self.network2 + elif name_or_id == 'floating_network' or name_or_id == 'floating_network_id': + return self.floating_network + return None + self.connection.network.find_network.side_effect = mock_find_network + + def mock_ports(network_id=None): + if network_id == 'network_uuid_1': + return [self.neutron_port1] + elif network_id == 'network_uuid_2': + return [self.neutron_port2] + elif network_id: + return [] + return [self.neutron_port1, self.neutron_port2] + self.connection.network.ports.side_effect = mock_ports + + def mock_port_forwardings(floating_ip=None): + if floating_ip.id == 'floating_ip_uuid_1': + return [self.pfw1, self.pfw2] + return [] + self.connection.network.port_forwardings.side_effect = mock_port_forwardings + + self.connection.network.networks.\ + return_value = [self.network1, self.network2, self.floating_network] + self.connection.network.ips.\ + return_value = [self.floating_ip, self.floating_ip_pfw] + + def test_network_and_port_list(self): + actual = networks.network_and_port_list(self.connection) + + expected = ( + [ + self.neutron_port1, + self.neutron_port2 + ], + { + 'network_uuid_1': self.network1, + 'network_uuid_2': self.network2, + 'floating_network_id': self.floating_network + }, + { + 'neutron_port_uuid_1': self.floating_ip_pfw, + 'neutron_port_uuid_2': self.floating_ip + }, + { + 'neutron_port_uuid_1': [self.pfw1, self.pfw2] + } + ) + + self.assertEqual(actual, expected) + + def test_network_and_port_list_network_filter(self): + actual = networks.network_and_port_list(self.connection, self.network1) + + expected = ( + [ + self.neutron_port1, + ], + { + 'network_uuid_1': self.network1, + 'network_uuid_2': self.network2, + 'floating_network_id': self.floating_network + }, + { + 'neutron_port_uuid_1': self.floating_ip_pfw, + 'neutron_port_uuid_2': self.floating_ip + }, + { + 'neutron_port_uuid_1': [self.pfw1, self.pfw2] + } + ) + + self.assertEqual(actual, expected) + + +class TestGetNetworksFromPort(TestCase): + + def setUp(self): + super(TestGetNetworksFromPort, self).setUp() + self.network1 = test_utils.create_mock_object({ + "id": "network_uuid_1", + "name": "test_network_1" + }) + self.network2 = test_utils.create_mock_object({ + "id": "network_uuid_2", + "name": "test_network_2" + }) + self.network3 = test_utils.create_mock_object({ + "id": "network_uuid_3", + "name": "test_network_3" + }) + self.neutron_port1 = test_utils.create_mock_object({ + "id": "neutron_port_uuid_1", + "network_id": "network_uuid_1", + "name": "neutron_port_1", + "fixed_ips": [{"ip_address": "1.1.1.1"}], + "trunk_details": None + }) + self.neutron_port2 = test_utils.create_mock_object({ + "id": "neutron_port_uuid_2", + "network_id": "network_uuid_2", + "name": "neutron_port_2", + "fixed_ips": [{"ip_address": "2.2.2.2"}], + "trunk_details": None + }) + self.neutron_port3 = test_utils.create_mock_object({ + "id": "neutron_port_uuid_3", + "network_id": "network_uuid_3", + "name": "neutron_port_3", + "fixed_ips": [{"ip_address": "3.3.3.3"}], + "trunk_details": { + 'sub_ports': [ + {'port_id': 'sub_port_uuid_1'}, + {'port_id': 'sub_port_uuid_2'}, + ] + } + }) + self.sub_port1 = test_utils.create_mock_object({ + "id": "sub_port_uuid_1", + "network_id": "network_uuid_1", + "name": "sub_port_1", + "fixed_ips": [{"ip_address": "4.4.4.4"}], + "trunk_details": None + }) + self.sub_port2 = test_utils.create_mock_object({ + "id": "sub_port_uuid_2", + "network_id": "network_uuid_2", + "name": "sub_port_2", + "fixed_ips": [{"ip_address": "5.5.5.5"}], + "trunk_details": None + }) + self.floating_network = test_utils.create_mock_object({ + "id": "floating_network_id", + "name": "floating_network" + }) + self.floating_ip = test_utils.create_mock_object({ + "id": "floating_ip_uuid_2", + "floating_ip_address": "8.8.8.8", + "floating_network_id": "floating_network_id", + "port_id": "neutron_port_uuid_2" + }) + self.floating_ip_pfw = test_utils.create_mock_object({ + "id": "floating_ip_uuid_1", + "floating_ip_address": "9.9.9.9", + "floating_network_id": "floating_network_id", + "port_id": None + }) + self.pfw1 = test_utils.create_mock_object({ + "internal_port": 22, + "external_port": 22, + "internal_port_id": "neutron_port_uuid_1" + }) + self.pfw2 = test_utils.create_mock_object({ + "internal_port": 23, + "external_port": 23, + "internal_port_id": "neutron_port_uuid_1" + }) + + self.connection = mock.Mock() + + def mock_get_network(network=None): + if network == self.network1 or network == 'network_uuid_1': + return self.network1 + elif network == self.network2 or network == 'network_uuid_2': + return self.network2 + elif network == self.network3 or network == 'network_uuid_3': + return self.network3 + elif network == self.floating_network or network == 'floating_network_id': + return self.floating_network + return None + self.connection.network.get_network.side_effect = mock_get_network + + def mock_get_port(port=None): + if port == self.neutron_port1 or port == 'neutron_port_uuid_1': + return self.neutron_port1 + elif port == self.neutron_port2 or port == 'neutron_port_uuid_2': + return self.neutron_port2 + elif port == self.neutron_port3 or port == 'neutron_port_uuid_3': + return self.neutron_port3 + elif port == self.sub_port1 or port == 'sub_port_uuid_1': + return self.sub_port1 + elif port == self.sub_port2 or port == 'sub_port_uuid_2': + return self.sub_port2 + return None + self.connection.network.get_port.side_effect = mock_get_port + + def test_get_networks_from_port_networks_dict(self): + networks_dict = { + 'network_uuid_1': self.network1, + 'network_uuid_2': self.network2, + 'network_uuid_3': self.network3, + 'floating_network_id': self.floating_network + } + + actual = networks.get_networks_from_port(self.connection, + self.neutron_port1, + networks_dict=networks_dict) + + expected = ( + self.network1, + [], + [], + None + ) + + self.assertEqual(actual, expected) + + def test_get_networks_from_port_ips_dict(self): + floating_ips_dict = { + 'neutron_port_uuid_1': self.floating_ip_pfw, + 'neutron_port_uuid_2': self.floating_ip + } + + actual = networks.get_networks_from_port(self.connection, + self.neutron_port2, + floating_ips_dict=floating_ips_dict) + + expected = ( + self.network2, + [], + [], + self.floating_network + ) + + self.assertEqual(actual, expected) + + def test_get_networks_from_port_trunk(self): + networks_dict = { + 'network_uuid_1': self.network1, + 'network_uuid_2': self.network2, + 'network_uuid_3': self.network3, + 'floating_network_id': self.floating_network + } + + floating_ips_dict = { + 'neutron_port_uuid_1': self.floating_ip_pfw, + 'neutron_port_uuid_2': self.floating_ip + } + + actual = networks.get_networks_from_port(self.connection, + self.neutron_port3, + networks_dict=networks_dict, + floating_ips_dict=floating_ips_dict) + + expected = ( + self.network3, + [self.network1, self.network2], + [self.sub_port1, self.sub_port2], + None + ) + + self.assertEqual(actual, expected) diff --git a/esi/tests/unit/lib/test_nodes.py b/esi/tests/unit/lib/test_nodes.py new file mode 100644 index 0000000..e72b015 --- /dev/null +++ b/esi/tests/unit/lib/test_nodes.py @@ -0,0 +1,497 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from unittest import TestCase + +from esi.lib import nodes + +from esi.tests.unit import utils + + +class TestNodeAndPortList(TestCase): + + def setUp(self): + super(TestNodeAndPortList, self).setUp() + self.maxDiff = None + + self.port1 = utils.create_mock_object({ + "uuid": "port_uuid_1", + "node_id": "11111111-2222-3333-4444-aaaaaaaaaaaa", + "address": "aa:aa:aa:aa:aa:aa", + "internal_info": {'tenant_vif_port_id': 'neutron_port_uuid_1'} + }) + self.port2 = utils.create_mock_object({ + "uuid": "port_uuid_2", + "node_id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "address": "bb:bb:bb:bb:bb:bb", + "internal_info": {} + }) + self.port3 = utils.create_mock_object({ + "uuid": "port_uuid_3", + "node_id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "address": "cc:cc:cc:cc:cc:cc", + "internal_info": {'tenant_vif_port_id': 'neutron_port_uuid_2'} + }) + self.port4 = utils.create_mock_object({ + "uuid": "port_uuid_4", + "node_id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "address": "dd:dd:dd:dd:dd:dd", + "internal_info": {'tenant_vif_port_id': 'neutron_port_uuid_4'} + }) + self.node1 = utils.create_mock_object({ + "id": "11111111-2222-3333-4444-aaaaaaaaaaaa", + "name": "node1" + }) + self.node2 = utils.create_mock_object({ + "id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "name": "node2" + }) + + self.connection = mock.Mock() + + def mock_find_node(name_or_id=None, ignore_missing=True): + if name_or_id == "11111111-2222-3333-4444-aaaaaaaaaaaa" or name_or_id == "node1": + return self.node1 + elif name_or_id == "11111111-2222-3333-4444-bbbbbbbbbbbb" or name_or_id == "node2": + return self.node2 + self.connection.baremetal.find_node.side_effect = mock_find_node + + def mock_baremetal_ports(details=False, node_id=None): + if node_id == '11111111-2222-3333-4444-aaaaaaaaaaaa': + return [self.port1] + if node_id == '11111111-2222-3333-4444-bbbbbbbbbbbb': + return [self.port2, self.port3, self.port4] + return [self.port1, self.port2, self.port3, self.port4] + self.connection.baremetal.ports.side_effect = mock_baremetal_ports + + def test_node_and_port_list(self): + self.connection.baremetal.nodes.\ + return_value = [self.node1, self.node2] + + actual = nodes.node_and_port_list(self.connection, filter_node=None) + expected = ( + [ + self.node1, + self.node2 + ], + [ + self.port1, + self.port2, + self.port3, + self.port4, + ] + ) + self.assertEqual(expected, actual) + + def test_node_and_port_list_node_id_filter(self): + actual = nodes.node_and_port_list(self.connection, filter_node=self.node1.id) + expected = ( + [ + self.node1, + ], + [ + self.port1, + ] + ) + self.assertEqual(actual, expected) + + def test_node_and_port_list_node_name_filter(self): + actual = nodes.node_and_port_list(self.connection, filter_node=self.node2.name) + expected = ( + [ + self.node2, + ], + [ + self.port2, + self.port3, + self.port4, + ] + ) + self.assertEqual(actual, expected) + + +class TestNetworkList(TestCase): + + def setUp(self): + super(TestNetworkList, self).setUp() + self.maxDiff = None + + self.port1 = utils.create_mock_object({ + "id": "port_uuid_1", + "node_id": "11111111-2222-3333-4444-aaaaaaaaaaaa", + "address": "aa:aa:aa:aa:aa:aa", + "internal_info": {'tenant_vif_port_id': 'neutron_port_uuid_1'} + }) + self.port2 = utils.create_mock_object({ + "id": "port_uuid_2", + "node_id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "address": "bb:bb:bb:bb:bb:bb", + "internal_info": {} + }) + self.port3 = utils.create_mock_object({ + "id": "port_uuid_3", + "node_id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "address": "cc:cc:cc:cc:cc:cc", + "internal_info": {'tenant_vif_port_id': 'neutron_port_uuid_2'} + }) + self.port4 = utils.create_mock_object({ + "id": "port_uuid_4", + "node_id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "address": "dd:dd:dd:dd:dd:dd", + "internal_info": {'tenant_vif_port_id': 'neutron_port_uuid_4'} + }) + self.port5 = utils.create_mock_object({ + "id": "port_uuid_5", + "node_id": "11111111-2222-3333-4444-aaaaaaaaaaaa", + "address": "ee:ee:ee:ee:ee:ee", + "internal_info": {'tenant_vif_port_id': 'neutron_port_uuid_3'} + }) + self.node1 = utils.create_mock_object({ + "id": "11111111-2222-3333-4444-aaaaaaaaaaaa", + "name": "node1" + }) + self.node2 = utils.create_mock_object({ + "id": "11111111-2222-3333-4444-bbbbbbbbbbbb", + "name": "node2" + }) + self.network1 = utils.create_mock_object({ + "id": "network_uuid_1", + "name": "test_network_1" + }) + self.network2 = utils.create_mock_object({ + "id": "network_uuid_2", + "name": "test_network_2" + }) + self.network3 = utils.create_mock_object({ + "id": "network_uuid_3", + "name": "test_network_3" + }) + self.neutron_port1 = utils.create_mock_object({ + "id": "neutron_port_uuid_1", + "network_id": "network_uuid_1", + "name": "neutron_port_1", + "fixed_ips": [{"ip_address": "1.1.1.1"}], + "trunk_details": None + }) + self.neutron_port2 = utils.create_mock_object({ + "id": "neutron_port_uuid_2", + "network_id": "network_uuid_2", + "name": "neutron_port_2", + "fixed_ips": [{"ip_address": "2.2.2.2"}], + "trunk_details": None + }) + self.neutron_port3 = utils.create_mock_object({ + "id": "neutron_port_uuid_3", + "network_id": "network_uuid_3", + "name": "neutron_port_3", + "fixed_ips": [{"ip_address": "3.3.3.3"}], + "trunk_details": { + 'sub_ports': [ + {'port_id': 'sub_port_uuid_1'}, + {'port_id': 'sub_port_uuid_2'}, + ] + } + }) + self.sub_port1 = utils.create_mock_object({ + "id": "sub_port_uuid_1", + "network_id": "network_uuid_1", + "name": "sub_port_1", + "fixed_ips": [{"ip_address": "4.4.4.4"}], + "trunk_details": None + }) + self.sub_port2 = utils.create_mock_object({ + "id": "sub_port_uuid_2", + "network_id": "network_uuid_2", + "name": "sub_port_2", + "fixed_ips": [{"ip_address": "5.5.5.5"}], + "trunk_details": None + }) + self.floating_network = utils.create_mock_object({ + "id": "floating_network_id", + "name": "floating_network" + }) + self.floating_ip = utils.create_mock_object({ + "id": "floating_ip_uuid_2", + "floating_ip_address": "8.8.8.8", + "floating_network_id": "floating_network_id", + "port_id": "neutron_port_uuid_2" + }) + self.floating_ip_pfw = utils.create_mock_object({ + "id": "floating_ip_uuid_1", + "floating_ip_address": "9.9.9.9", + "floating_network_id": "floating_network_id", + "port_id": None + }) + self.pfw1 = utils.create_mock_object({ + "internal_port": 22, + "external_port": 22, + "internal_port_id": "neutron_port_uuid_1" + }) + self.pfw2 = utils.create_mock_object({ + "internal_port": 23, + "external_port": 23, + "internal_port_id": "neutron_port_uuid_1" + }) + + self.connection = mock.Mock() + + def mock_find_node(name_or_id=None, ignore_missing=True): + if name_or_id == "11111111-2222-3333-4444-aaaaaaaaaaaa" or name_or_id == "node1": + return self.node1 + elif name_or_id == "11111111-2222-3333-4444-bbbbbbbbbbbb" or name_or_id == "node2": + return self.node2 + return None + self.connection.baremetal.find_node.side_effect = mock_find_node + + def mock_baremetal_ports(details=False, node_id=None): + if node_id == '11111111-2222-3333-4444-aaaaaaaaaaaa': + return [self.port1, self.port5] + if node_id == '11111111-2222-3333-4444-bbbbbbbbbbbb': + return [self.port2, self.port3, self.port4] + return [self.port1, self.port2, self.port3, self.port4, self.port5] + self.connection.baremetal.ports.side_effect = mock_baremetal_ports + + def mock_network_ports(network_id=None): + if network_id == 'network_uuid_1': + return [self.neutron_port1] + elif network_id == 'network_uuid_2': + return [self.neutron_port2] + if network_id == 'network_uuid_3': + return [self.neutron_port3] + elif network_id: + return [] + return [self.neutron_port1, self.neutron_port2, self.neutron_port3] + self.connection.network.ports.side_effect = mock_network_ports + + def mock_find_network(name_or_id=None, ignore_missing=True): + if name_or_id == 'test_network_1' or name_or_id == 'network_uuid_1': + return self.network1 + elif name_or_id == 'test_network_2' or name_or_id == 'network_uuid_2': + return self.network2 + elif name_or_id == 'test_network_3' or name_or_id == 'network_uuid_3': + return self.network3 + elif name_or_id == 'floating_network' or name_or_id == 'floating_network_id': + return self.floating_network + return None + self.connection.network.find_network.side_effect = mock_find_network + + def mock_port_forwardings(floating_ip=None): + if floating_ip.id == 'floating_ip_uuid_1': + return [self.pfw1, self.pfw2] + return [] + self.connection.network.port_forwardings.side_effect = mock_port_forwardings + + def mock_get_network(network=None): + if network == self.network1 or network == 'network_uuid_1': + return self.network1 + elif network == self.network2 or network == 'network_uuid_2': + return self.network2 + elif network == self.network3 or network == 'network_uuid_3': + return self.network3 + elif network == self.floating_network or network == 'floating_network_id': + return self.floating_network + return None + self.connection.network.get_network.side_effect = mock_get_network + + def mock_get_port(port=None): + if port == self.neutron_port1 or port == 'neutron_port_uuid_1': + return self.neutron_port1 + elif port == self.neutron_port2 or port == 'neutron_port_uuid_2': + return self.neutron_port2 + elif port == self.neutron_port3 or port == 'neutron_port_uuid_3': + return self.neutron_port3 + elif port == self.sub_port1 or port == 'sub_port_uuid_1': + return self.sub_port1 + elif port == self.sub_port2 or port == 'sub_port_uuid_2': + return self.sub_port2 + return None + self.connection.network.get_port.side_effect = mock_get_port + + self.connection.baremetal.nodes.\ + return_value = [self.node1, self.node2] + self.connection.network.networks.\ + return_value = [self.network1, self.network2, self.network3, self.floating_network] + self.connection.network.ips.\ + return_value = [self.floating_ip, self.floating_ip_pfw] + + def test_network_list(self): + actual = nodes.network_list(self.connection, + filter_node=None, + filter_network=None) + expected = [ + { + 'node': self.node1, + 'network_info': [ + { + 'baremetal_port': self.port1, + 'network_ports': [self.neutron_port1], + 'networks': { + 'parent': self.network1, + 'trunk': [], + 'floating': self.floating_network + }, + 'floating_ip': self.floating_ip_pfw, + 'port_forwardings': [self.pfw1, self.pfw2] + }, + { + 'baremetal_port': self.port5, + 'network_ports': [ + self.neutron_port3, + self.sub_port1, + self.sub_port2, + ], + 'networks': { + 'parent': self.network3, + 'trunk': [self.network1, self.network2], + 'floating': None, + }, + 'floating_ip': None, + 'port_forwardings': [] + } + ] + }, + { + 'node': self.node2, + 'network_info': [ + { + 'baremetal_port': self.port2, + 'network_ports': [], + 'networks': {}, + 'floating_ip': None, + 'port_forwardings': [], + }, + { + 'baremetal_port': self.port3, + 'network_ports': [self.neutron_port2], + 'networks': { + 'parent': self.network2, + 'trunk': [], + 'floating': self.floating_network + }, + 'floating_ip': self.floating_ip, + 'port_forwardings': [], + }, + { + 'baremetal_port': self.port4, + 'network_ports': [], + 'networks': {}, + 'floating_ip': None, + 'port_forwardings': [], + } + ] + } + ] + + self.assertEqual(actual, expected) + + def test_network_list_filter_node(self): + filter_node = 'node1' + actual = nodes.network_list(self.connection, + filter_node=filter_node, + filter_network=None) + + expected = [ + { + 'node': self.node1, + 'network_info': [ + { + 'baremetal_port': self.port1, + 'network_ports': [self.neutron_port1], + 'networks': { + 'parent': self.network1, + 'trunk': [], + 'floating': self.floating_network + }, + 'floating_ip': self.floating_ip_pfw, + 'port_forwardings': [self.pfw1, self.pfw2] + }, + { + 'baremetal_port': self.port5, + 'network_ports': [ + self.neutron_port3, + self.sub_port1, + self.sub_port2, + ], + 'networks': { + 'parent': self.network3, + 'trunk': [self.network1, self.network2], + 'floating': None, + }, + 'floating_ip': None, + 'port_forwardings': [] + } + ] + } + ] + + self.assertEqual(expected, actual) + + def test_network_list_filter_network(self): + filter_network = 'test_network_3' + actual = nodes.network_list(self.connection, + filter_node=None, + filter_network=filter_network) + + expected = [ + { + 'node': self.node1, + 'network_info': [ + { + 'baremetal_port': self.port5, + 'network_ports': [ + self.neutron_port3, + self.sub_port1, + self.sub_port2, + ], + 'networks': { + 'parent': self.network3, + 'trunk': [self.network1, self.network2], + 'floating': None, + }, + 'floating_ip': None, + 'port_forwardings': [] + } + ] + } + ] + + self.assertEqual(expected, actual) + + def test_network_list_filter_node_network(self): + filter_node = '11111111-2222-3333-4444-bbbbbbbbbbbb' + filter_network = 'network_uuid_2' + + actual = nodes.network_list(self.connection, + filter_node=filter_node, + filter_network=filter_network) + + expected = [ + { + 'node': self.node2, + 'network_info': [ + { + 'baremetal_port': self.port3, + 'network_ports': [self.neutron_port2], + 'networks': { + 'parent': self.network2, + 'trunk': [], + 'floating': self.floating_network + }, + 'floating_ip': self.floating_ip, + 'port_forwardings': [], + } + ] + } + ] + + self.assertEqual(expected, actual) diff --git a/esi/tests/unit/utils.py b/esi/tests/unit/utils.py new file mode 100644 index 0000000..d81c260 --- /dev/null +++ b/esi/tests/unit/utils.py @@ -0,0 +1,21 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock + + +def create_mock_object(mock_attributes): + mock_object = mock.Mock(spec=[]) + mock_object.configure_mock(**mock_attributes) + + return mock_object diff --git a/test-requirements.txt b/test-requirements.txt index ea97f63..bf65c3c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,6 +3,7 @@ # process, which may cause wedges in the gate later. hacking>=3.1.0,<4.0.0 # Apache-2.0 +mock>=3.0.0 # BSD coverage!=4.4,>=4.0 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD