Skip to content

Commit

Permalink
migrate node network list functionality into esisdk
Browse files Browse the repository at this point in the history
  • Loading branch information
ajamias committed Jun 26, 2024
1 parent 8e56bd3 commit 4527f84
Show file tree
Hide file tree
Showing 8 changed files with 709 additions and 0 deletions.
Empty file added esi/lib/__init__.py
Empty file.
108 changes: 108 additions & 0 deletions esi/lib/node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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 import utils


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 node: Name or UUID of the node
:param network: The name or ID of a network
"""

if filter_node:
machine = connection.baremetal.find_node(filter_node, ignore_missing=False)
ports = connection.list_nics_for_machine(uuid=machine.id)

node_name = machine.name
else:
ports = None
nodes = None

with concurrent.futures.ThreadPoolExecutor() as executor:
f1 = executor.submit(connection.list_nics)
f2 = executor.submit(connection.list_machines)
ports = f1.result()
nodes = f2.result()

if filter_network:
filter_network = connection.network.find_network(filter_network, ignore_missing=False)

# base network information
with concurrent.futures.ThreadPoolExecutor() as executor:
f1 = executor.submit(connection.network.ips)
f2 = executor.submit(connection.list_networks)
f3 = executor.submit(
utils.get_ports, connection, filter_network)
floating_ips = list(f1.result())
networks = f2.result()
networks_dict = {n.id: n for n in networks}
neutron_ports = f3.result()

# update floating IP list to include port forwarding information
for fip 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 fip.port_id:
pfws = list(connection.network.port_forwardings(fip))
if len(pfws):
fip.port_id = pfws[0].internal_port_id
pfw_ports = ["%s:%s" % (pfw.internal_port,
pfw.external_port)
for pfw in pfws]
fip.floating_ip_address = "%s (%s)" % (
fip.floating_ip_address, ','.join(pfw_ports))

data = []
for port in ports:
if not filter_node:
node_name = next((node for node in nodes
if node.id == port.node_id), None).name

neutron_port_id = port.internal_info.get('tenant_vif_port_id')
neutron_port = None

if neutron_port_id:
neutron_port = next((np for np in neutron_ports
if np.id == neutron_port_id), None)

if neutron_port is not None:
network_id = neutron_port.network_id

if not filter_network or filter_network.id == network_id:
network_names, _, fixed_ips \
= utils.get_full_network_info_from_port(
neutron_port, connection, networks_dict)
floating_ip_addresses, floating_network_names \
= utils.get_floating_ip(neutron_port_id,
floating_ips,
networks_dict)
data.append([node_name, port.address,
neutron_port.name,
"\n".join(network_names),
"\n".join(fixed_ips),
"\n".join(floating_network_names)
if floating_network_names else None,
"\n".join(floating_ip_addresses)
if floating_ip_addresses else None]
)
elif not filter_network:
data.append([node_name, port.address,
None, None, None, None, None])

return ["Node", "MAC Address", "Port", "Network", "Fixed IP",
"Floating Network", "Floating IP"], data
32 changes: 32 additions & 0 deletions esi/tests/unit/base.py
Original file line number Diff line number Diff line change
@@ -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()
Empty file added esi/tests/unit/lib/__init__.py
Empty file.
235 changes: 235 additions & 0 deletions esi/tests/unit/lib/test_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# 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.

from esi.lib import node
from esi.tests.unit import base
from esi.tests.unit import utils


class TestList(base.TestCommand):

def setUp(self):
super(TestList, self).setUp()
self.cmd = node.network_list

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.network = utils.create_mock_object({
"id": "network_uuid",
"name": "test_network"
})
self.neutron_port1 = utils.create_mock_object({
"id": "neutron_port_uuid_1",
"network_id": "network_uuid",
"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",
"name": "neutron_port_2",
"fixed_ips": [{"ip_address": "2.2.2.2"}],
"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"
})

def mock_node_get(node, ignore_missing=True):
if node == "11111111-2222-3333-4444-aaaaaaaaaaaa" or node == "node1":
return self.node1
elif node == "11111111-2222-3333-4444-bbbbbbbbbbbb" or node == "node2":
return self.node2
return None
self.connection.baremetal.find_node.side_effect = mock_node_get

def mock_neutron_port_get(port):
if port == "neutron_port_uuid_1" or port == "neutron_port_1":
return self.neutron_port1
elif port == "neutron_port_uuid_2" or port == "neutron_port_2":
return self.neutron_port2
return None
self.connection.get_port.\
side_effect = mock_neutron_port_get

def mock_neutron_port_forwardings(fip):
if fip.id == "floating_ip_uuid_1":
return [self.pfw1, self.pfw2]
return []
self.connection.network.port_forwardings.\
side_effect = mock_neutron_port_forwardings

def mock_find_network(network, ignore_missing=True):
return self.network
self.connection.network.find_network.\
side_effect = mock_find_network

self.connection.list_ports.\
return_value = [self.neutron_port1, self.neutron_port2]
self.connection.list_networks.\
return_value = [self.network, self.floating_network]
self.connection.list_machines.\
return_value = [self.node1, self.node2]
self.connection.network.ips.\
return_value = [self.floating_ip, self.floating_ip_pfw]

def test_network_list(self):
self.connection.list_nics.\
return_value = [self.port1, self.port2, self.port3]

filter_node = None
filter_network = None

results = self.cmd(self.connection, filter_node, filter_network)
expected = (
["Node", "MAC Address", "Port", "Network", "Fixed IP",
"Floating Network", "Floating IP"],
[['node1', 'aa:aa:aa:aa:aa:aa', 'neutron_port_1', 'test_network',
'1.1.1.1', 'floating_network', '9.9.9.9 (22:22,23:23)'],
['node2', 'bb:bb:bb:bb:bb:bb', None, None, None, None, None],
['node2', 'cc:cc:cc:cc:cc:cc', 'neutron_port_2', 'test_network',
'2.2.2.2', 'floating_network', '8.8.8.8']]
)
self.assertEqual(expected, results)
self.connection.list_nics.assert_called_once()

def test_network_list_neutron_port_does_not_exist(self):
self.connection.list_nics.\
return_value = [self.port4]

filter_node = None
filter_network = None

results = self.cmd(self.connection, filter_node, filter_network)
expected = (
["Node", "MAC Address", "Port", "Network", "Fixed IP",
"Floating Network", "Floating IP"],
[["node2", "dd:dd:dd:dd:dd:dd", None, None, None, None, None]]
)
self.assertEqual(expected, results)
self.connection.list_nics.assert_called_once()

def test_network_list_node_filter(self):
self.connection.list_nics_for_machine.\
return_value = [self.port2, self.port3]

filter_node = '11111111-2222-3333-4444-bbbbbbbbbbbb'
filter_network = None

results = self.cmd(self.connection, filter_node, filter_network)
expected = (
["Node", "MAC Address", "Port", "Network", "Fixed IP",
"Floating Network", "Floating IP"],
[['node2', 'bb:bb:bb:bb:bb:bb', None, None, None, None, None],
['node2', 'cc:cc:cc:cc:cc:cc', 'neutron_port_2', 'test_network',
'2.2.2.2', 'floating_network', '8.8.8.8']]
)
self.assertEqual(expected, results)
self.connection.list_nics_for_machine.\
assert_called_once_with(
uuid='11111111-2222-3333-4444-bbbbbbbbbbbb')

def test_network_list_network_filter(self):
self.connection.list_nics.\
return_value = [self.port1, self.port2, self.port3]

filter_node = None
filter_network = 'network'

results = self.cmd(self.connection, filter_node, filter_network)
expected = (
["Node", "MAC Address", "Port", "Network", "Fixed IP",
"Floating Network", "Floating IP"],
[["node1", "aa:aa:aa:aa:aa:aa",
"neutron_port_1", "test_network", "1.1.1.1",
"floating_network", "9.9.9.9 (22:22,23:23)"],
["node2", "cc:cc:cc:cc:cc:cc",
"neutron_port_2", "test_network", "2.2.2.2",
"floating_network", "8.8.8.8"]]
)
self.assertEqual(expected, results)
self.connection.list_nics.\
assert_called_once()

def test_network_list_node_network_filter(self):
self.connection.list_nics_for_machine.\
return_value = [self.port2, self.port3]

filter_node = 'node2'
filter_network = 'network'

results = self.cmd(self.connection, filter_node, filter_network)
expected = (
["Node", "MAC Address", "Port", "Network", "Fixed IP",
"Floating Network", "Floating IP"],
[["node2", "cc:cc:cc:cc:cc:cc",
"neutron_port_2", "test_network", "2.2.2.2",
"floating_network", "8.8.8.8"]]
)
self.assertEqual(expected, results)
self.connection.list_nics_for_machine.\
assert_called_once_with(
uuid='11111111-2222-3333-4444-bbbbbbbbbbbb')
Loading

0 comments on commit 4527f84

Please sign in to comment.