Drop netifaces dependency

The netifaces library is no longer maintained, which is why we
need to drop this dependency.

There is only one place in which netifaces is being used, a
trivial function that retrives the mac address for a given ip
address.

Thankfully, we already have a cloudbase-init "get_adapter_addresses"
function that uses ctypes to call GetAdaptersAddresses, which
happens to be the same Windows function used by netifaces.

Worth mentioning that netifaces is the only compilable cloudbase-init
dependency that does not provide a wheel package.

Fixes: https://github.com/cloudbase/cloudbase-init/issues/140
Change-Id: Ie52ff722cbf42da7b9bfa9f9942adc1996ce5dd8
This commit is contained in:
Lucian Petrut 2024-06-03 13:33:06 +00:00
parent dc0367425a
commit e17488c239
6 changed files with 58 additions and 34 deletions

View File

@ -91,6 +91,9 @@ class BaseOSUtils(object):
def get_network_adapter_name_by_mac_address(self, mac_address):
raise NotImplementedError()
def get_mac_address_by_local_ip(self, ip_addr):
raise NotImplementedError()
def set_network_adapter_mtu(self, name, mtu):
raise NotImplementedError()

View File

@ -819,6 +819,13 @@ class WindowsUtils(base.BaseOSUtils):
return iface_index_list[0]["friendly_name"]
def get_mac_address_by_local_ip(self, ip_addr):
for iface in network.get_adapter_addresses():
addrs = iface.get('unicast_addresses', [])
for addr, family in addrs:
if ip_addr and addr and ip_addr.lower() == addr.lower():
return iface['mac_address'].lower()
@retry_decorator.retry_decorator(
max_retry_count=3, exceptions=exception.ItemNotFoundException)
def set_network_adapter_mtu(self, name, mtu):

View File

@ -2153,6 +2153,44 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase):
self._test_get_network_adapter_name_by_mac_address(
multiple_adapters_found=True)
@mock.patch("cloudbaseinit.utils.windows.network."
"get_adapter_addresses")
def test_get_mac_address_by_local_ip(self, mock_get_adapter_addresses):
fake_addresses = [
{
"friendly_name": "mgmt",
"mac_address": "24:6E:96:E0:FE:76",
"interface_type": 6,
"unicast_addresses": [("fe80::499d:b2f9:48c0:c88e%20", 23),
("10.11.12.13", 2)],
},
{
"friendly_name": "Loopback Pseudo-Interface 1",
"mac_address": "",
"interface_type": 24,
"unicast_addresses": [("::1", 23), ("127.0.0.1", 2)],
},
]
mock_get_adapter_addresses.return_value = fake_addresses
self.assertEqual(
"24:6e:96:e0:fe:76",
self._winutils.get_mac_address_by_local_ip("10.11.12.13"))
self.assertEqual(
"24:6e:96:e0:fe:76",
self._winutils.get_mac_address_by_local_ip(
"fe80::499d:b2f9:48c0:c88e%20"))
self.assertEqual(
"24:6e:96:e0:fe:76",
self._winutils.get_mac_address_by_local_ip(
"FE80::499D:B2F9:48C0:C88E%20"))
self.assertEqual(
"",
self._winutils.get_mac_address_by_local_ip("127.0.0.1"))
self.assertIsNone(
self._winutils.get_mac_address_by_local_ip("10.10.10.10"))
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'.execute_process')
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import netifaces
import socket
import struct
import unittest
@ -108,35 +107,19 @@ class DHCPUtilsTests(unittest.TestCase):
self._test_parse_dhcp_reply(message_type=3, id_reply=111,
equals_cookie=False)
@mock.patch('netifaces.ifaddresses')
@mock.patch('netifaces.interfaces')
def test_get_mac_address_by_local_ip(self, mock_interfaces,
mock_ifaddresses):
fake_addresses = {}
fake_addresses[netifaces.AF_INET] = [{'addr': 'fake address'}]
fake_addresses[netifaces.AF_LINK] = [{'addr': 'fake mac'}]
mock_interfaces.return_value = ['fake interface']
mock_ifaddresses.return_value = fake_addresses
response = dhcp._get_mac_address_by_local_ip('fake address')
mock_interfaces.assert_called_once_with()
mock_ifaddresses.assert_called_once_with('fake interface')
self.assertEqual(fake_addresses[netifaces.AF_LINK][0]['addr'],
response)
@mock.patch('random.randint')
@mock.patch('socket.socket')
@mock.patch('cloudbaseinit.utils.dhcp._get_mac_address_by_local_ip')
@mock.patch('cloudbaseinit.utils.dhcp._get_dhcp_request_data')
@mock.patch('cloudbaseinit.utils.dhcp._parse_dhcp_reply')
def test_get_dhcp_options(self, mock_parse_dhcp_reply,
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils')
def test_get_dhcp_options(self, mock_get_os_utils,
mock_parse_dhcp_reply,
mock_get_dhcp_request_data,
mock_get_mac_address_by_local_ip, mock_socket,
mock_randint):
mock_socket, mock_randint):
mock_randint.return_value = 'fake int'
mock_socket().getsockname.return_value = ['fake local ip']
mock_get_mac_address_by_local_ip = (
mock_get_os_utils.return_value.get_mac_address_by_local_ip)
mock_get_mac_address_by_local_ip.return_value = 'fake mac'
mock_get_dhcp_request_data.return_value = 'fake data'
mock_parse_dhcp_reply.return_value = (True, 'fake replied options')

View File

@ -13,7 +13,6 @@
# under the License.
import datetime
import netifaces
import random
import socket
import struct
@ -21,6 +20,7 @@ import time
from oslo_log import log as oslo_logging
from cloudbaseinit.osutils import factory as osutils_factory
from cloudbaseinit.utils import network
_DHCP_COOKIE = b'\x63\x82\x53\x63'
@ -96,14 +96,6 @@ def _parse_dhcp_reply(data, id_req):
return True, options
def _get_mac_address_by_local_ip(ip_addr):
for iface in netifaces.interfaces():
addrs = netifaces.ifaddresses(iface)
for addr in addrs.get(netifaces.AF_INET, []):
if addr['addr'] == ip_addr:
return addrs[netifaces.AF_LINK][0]['addr']
def _bind_dhcp_client_socket(s, max_bind_attempts, bind_retry_interval):
bind_attempts = 1
while True:
@ -138,7 +130,9 @@ def get_dhcp_options(dhcp_host=None, requested_options=[], timeout=5.0,
s.settimeout(timeout)
local_ip_addr = network.get_local_ip(dhcp_host)
mac_address = _get_mac_address_by_local_ip(local_ip_addr)
osutils = osutils_factory.get_os_utils()
mac_address = osutils.get_mac_address_by_local_ip(local_ip_addr)
data = _get_dhcp_request_data(id_req, mac_address, requested_options,
vendor_id)

View File

@ -6,7 +6,6 @@ oslo.config
oslo.log
Babel>=1.3
oauthlib
netifaces
PyYAML
requests
untangle==1.2.1