Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migrate node network list functionality into esisdk #5

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added esi/lib/__init__.py
Empty file.
114 changes: 114 additions & 0 deletions esi/lib/networks.py
Original file line number Diff line number Diff line change
@@ -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
140 changes: 140 additions & 0 deletions esi/lib/nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@

# 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 []
},
...
]
}
"""
ajamias marked this conversation as resolved.
Show resolved Hide resolved

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': {
'parent': None,
'trunk': [],
'floating': None
},
'floating_ip': None,
'port_forwardings': [],
})

if network_info != []:
data.append({
'node': baremetal_node,
'network_info': network_info
})

return 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.
Loading
Loading