nameservers are obtained from nmcli

Before this patch, tobiko tried to obtain the list of nameservers
from a machine from its resolv.conf file.
With this patch, tobiko checks whether that machine has nmcli command
available and, if so, tries to obtain the DNS server with it too.

Change-Id: If83a8637dffa14dc712e605842e5d55713389dea
This commit is contained in:
Eduardo Olivares 2025-01-22 12:05:59 +01:00
parent 7b67ccba2e
commit 0a74f0e951
4 changed files with 81 additions and 3 deletions

View File

@ -24,6 +24,7 @@ from tobiko.shell.sh import _hostname
from tobiko.shell.sh import _io
from tobiko.shell.sh import _local
from tobiko.shell.sh import _nameservers
from tobiko.shell.sh import _nmcli
from tobiko.shell.sh import _path
from tobiko.shell.sh import _process
from tobiko.shell.sh import _ps
@ -135,3 +136,6 @@ get_file_size = _wc.get_file_size
CommandNotFound = _which.CommandNotFound
SkipOnCommandNotFound = _which.SkipOnCommandNotFound
find_command = _which.find_command
get_nm_connection_ids = _nmcli.get_nm_connection_ids
get_nm_connection_values = _nmcli.get_nm_connection_values

View File

@ -59,10 +59,23 @@ def list_nameservers(ssh_client: typing.Optional[ssh.SSHClientFixture] = None,
filenames = ['/etc/resolv.conf']
nameservers: tobiko.Selection[netaddr.IPAddress] = tobiko.Selection()
# obtain nameservers from the resolv.conf file
for filename in filenames:
nameservers.extend(parse_resolv_conf_file(ssh_client=ssh_client,
filename=filename,
**execute_params))
# obtain nameservers from nmcli, if available
try:
sh.find_command('nmcli', ssh_client=ssh_client)
except sh.CommandNotFound:
msg = 'nmcli command not available'
if ssh_client:
msg += f' on {ssh_client.host}'
LOG.debug(msg)
else:
nameservers.extend(parse_dns_nmcli(ssh_client=ssh_client))
if ip_version:
nameservers = nameservers.with_attributes(version=ip_version)
return nameservers
@ -71,8 +84,7 @@ def list_nameservers(ssh_client: typing.Optional[ssh.SSHClientFixture] = None,
def parse_resolv_conf_file(
filename: str,
ssh_client: typing.Optional[ssh.SSHClientFixture] = None,
**execute_params) -> \
typing.Generator[netaddr.IPAddress, None, None]:
**execute_params) -> typing.Generator[netaddr.IPAddress, None, None]:
lines: typing.List[str] = \
sh.execute(f"cat '{filename}'",
ssh_client=ssh_client,
@ -94,3 +106,18 @@ def parse_resolv_conf_file(
yield netaddr.IPAddress(nameserver)
except netaddr.AddrFormatError:
LOG.exception(f"Invalid nameserver address: {nameserver}")
def parse_dns_nmcli(
ssh_client: typing.Optional[ssh.SSHClientFixture] = None) -> \
typing.Generator[netaddr.IPAddress, None, None]:
connections = sh.get_nm_connection_ids(ssh_client=ssh_client)
for connection in connections:
nameservers = sh.get_nm_connection_values(connection,
'IP4.DNS,IP6.DNS',
ssh_client=ssh_client)
for nameserver in nameservers:
try:
yield netaddr.IPAddress(nameserver)
except netaddr.AddrFormatError:
LOG.exception(f"Invalid nameserver address: {nameserver}")

45
tobiko/shell/sh/_nmcli.py Normal file
View File

@ -0,0 +1,45 @@
# Copyright (c) 2025 Red Hat, Inc.
#
# All Rights Reserved.
#
# 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 __future__ import absolute_import
from oslo_log import log
from tobiko.shell.sh import _execute
from tobiko.shell import ssh
LOG = log.getLogger(__name__)
def get_nm_connection_ids(ssh_client: ssh.SSHClientType = None) -> list:
result = _execute.execute('nmcli -g UUID con',
ssh_client=ssh_client)
return result.stdout.splitlines()
def get_nm_connection_values(connection: str,
values: str,
ssh_client: ssh.SSHClientType = None) -> list:
result = _execute.execute(f'nmcli -g {values} con show "{connection}"',
ssh_client=ssh_client)
return_values = []
for line in result.stdout.splitlines():
if line:
for value in line.split('|'):
# nmcli adds escape char before ":" and we need to remove it
return_values.append(value.strip().replace('\\', ''))
return return_values

View File

@ -15,6 +15,8 @@
# under the License.
from __future__ import absolute_import
import typing
import tobiko
from tobiko.shell import sh
from tobiko.openstack import stacks
@ -27,7 +29,7 @@ class AdvancedServerStackTest(test_cirros.CirrosServerStackTest):
#: Stack of resources with a server attached to a floating IP
stack = tobiko.required_fixture(stacks.AdvancedServerStackFixture)
nameservers_filenames = ('/etc/resolv.conf',)
nameservers_filenames: typing.Optional[typing.Sequence[str]] = []
def test_python(self):
python_version = sh.execute(['python3', '--version'],