Allow to execute commands from inside a network namespace

Change-Id: I6aa499824bca146b6d2bf834b6565679cb423c4a
This commit is contained in:
Federico Ressi 2019-10-31 20:18:56 +01:00
parent 909dc7cf9b
commit cc63ebb518
5 changed files with 45 additions and 15 deletions

View File

@ -67,7 +67,7 @@ def list_ip_addresses(ip_version=None, scope=None, **execute_params):
def list_network_namespaces(**execute_params): def list_network_namespaces(**execute_params):
output = execute_ip(['-o', 'netns', 'list'], **execute_params) output = execute_ip(['-o', 'netns', 'list'], **execute_params)
namespaces = list() namespaces = tobiko.Selection()
if output: if output:
for line in output.splitlines(): for line in output.splitlines():
fields = line.strip().split() fields = line.strip().split()
@ -76,15 +76,10 @@ def list_network_namespaces(**execute_params):
return namespaces return namespaces
def execute_ip(ifconfig_args, network_namespace=None, sudo=None, def execute_ip(ifconfig_args, **execute_params):
**execute_params):
command = ['/sbin/ip'] + ifconfig_args command = ['/sbin/ip'] + ifconfig_args
if network_namespace:
if sudo is None:
sudo = True
command = ['/sbin/ip', 'netns', 'exec', network_namespace] + command
result = sh.execute(command, stdin=False, stdout=True, stderr=True, result = sh.execute(command, stdin=False, stdout=True, stderr=True,
expect_exit_status=None, sudo=sudo, **execute_params) expect_exit_status=None, **execute_params)
if result.exit_status: if result.exit_status:
raise IpError(error=result.stderr, exit_status=result.exit_status) raise IpError(error=result.stderr, exit_status=result.exit_status)
return result.stdout return result.stdout

View File

@ -54,11 +54,12 @@ def local_execute(command, environment=None, timeout=None, shell=None,
def local_process(command, environment=None, current_dir=None, timeout=None, def local_process(command, environment=None, current_dir=None, timeout=None,
shell=None, stdin=None, stdout=None, stderr=True, sudo=None): shell=None, stdin=None, stdout=None, stderr=True, sudo=None,
network_namespace=None):
return LocalShellProcessFixture( return LocalShellProcessFixture(
command=command, environment=environment, current_dir=current_dir, command=command, environment=environment, current_dir=current_dir,
timeout=timeout, shell=shell, stdin=stdin, stdout=stdout, timeout=timeout, shell=shell, stdin=stdin, stdout=stdout,
stderr=stderr, sudo=sudo) stderr=stderr, sudo=sudo, network_namespace=network_namespace)
class LocalExecutePathFixture(_path.ExecutePathFixture): class LocalExecutePathFixture(_path.ExecutePathFixture):

View File

@ -79,6 +79,7 @@ class ShellProcessParameters(Parameters):
buffer_size = io.DEFAULT_BUFFER_SIZE buffer_size = io.DEFAULT_BUFFER_SIZE
poll_interval = 1. poll_interval = 1.
sudo = None sudo = None
network_namespace = None
class ShellProcessFixture(tobiko.SharedFixture): class ShellProcessFixture(tobiko.SharedFixture):
@ -121,6 +122,9 @@ class ShellProcessFixture(tobiko.SharedFixture):
def setup_command(self): def setup_command(self):
command = _command.shell_command(self.parameters.command) command = _command.shell_command(self.parameters.command)
network_namespace = self.parameters.network_namespace
sudo = self.parameters.sudo
shell = self.parameters.shell shell = self.parameters.shell
if shell: if shell:
if shell is True: if shell is True:
@ -130,7 +134,12 @@ class ShellProcessFixture(tobiko.SharedFixture):
command = shell + [str(command)] command = shell + [str(command)]
else: else:
command = _command.shell_command(command) command = _command.shell_command(command)
sudo = self.parameters.sudo
if network_namespace:
if sudo is None:
sudo = True
command = network_namespace_command(network_namespace, command)
if sudo: if sudo:
if sudo is True: if sudo is True:
sudo = default_sudo_command() sudo = default_sudo_command()
@ -495,3 +504,8 @@ def default_sudo_command():
from tobiko import config from tobiko import config
CONF = config.CONF CONF = config.CONF
return _command.shell_command(CONF.tobiko.shell.sudo) return _command.shell_command(CONF.tobiko.shell.sudo)
def network_namespace_command(network_namespace, command):
return _command.shell_command(['/sbin/ip', 'netns', 'exec',
network_namespace]) + command

View File

@ -48,19 +48,20 @@ def ssh_execute(ssh_client, command, environment=None, timeout=None,
def ssh_process(command, environment=None, current_dir=None, timeout=None, def ssh_process(command, environment=None, current_dir=None, timeout=None,
shell=None, stdin=None, stdout=None, stderr=None, shell=None, stdin=None, stdout=None, stderr=None,
ssh_client=None, sudo=None): ssh_client=None, sudo=None, network_namespace=None):
if ssh_client is None: if ssh_client is None:
ssh_client = ssh.ssh_proxy_client() ssh_client = ssh.ssh_proxy_client()
if ssh_client: if ssh_client:
return SSHShellProcessFixture( return SSHShellProcessFixture(
command=command, environment=environment, current_dir=current_dir, command=command, environment=environment, current_dir=current_dir,
timeout=timeout, shell=shell, stdin=stdin, stdout=stdout, timeout=timeout, shell=shell, stdin=stdin, stdout=stdout,
stderr=stderr, ssh_client=ssh_client, sudo=sudo) stderr=stderr, ssh_client=ssh_client, sudo=sudo,
network_namespace=network_namespace)
else: else:
return _local.local_process( return _local.local_process(
command=command, environment=environment, current_dir=current_dir, command=command, environment=environment, current_dir=current_dir,
timeout=timeout, shell=shell, stdin=stdin, stdout=stdout, timeout=timeout, shell=shell, stdin=stdin, stdout=stdout,
stderr=stderr, sudo=sudo) stderr=stderr, sudo=sudo, network_namespace=network_namespace)
class SSHShellProcessParameters(_process.ShellProcessParameters): class SSHShellProcessParameters(_process.ShellProcessParameters):

View File

@ -20,9 +20,10 @@ import six
import testtools import testtools
import tobiko import tobiko
from tobiko.openstack import stacks
from tobiko.openstack import topology
from tobiko.shell import ip from tobiko.shell import ip
from tobiko.shell import ssh from tobiko.shell import ssh
from tobiko.openstack import stacks
class IpTest(testtools.TestCase): class IpTest(testtools.TestCase):
@ -55,6 +56,7 @@ class IpTest(testtools.TestCase):
elif scope == 'global': elif scope == 'global':
self.assertNotIn(netaddr.IPAddress('127.0.0.1'), ips) self.assertNotIn(netaddr.IPAddress('127.0.0.1'), ips)
self.assertNotIn(netaddr.IPAddress('::1'), ips) self.assertNotIn(netaddr.IPAddress('::1'), ips)
return ips
def test_list_ip_addresses_with_host_scope(self, **execute_params): def test_list_ip_addresses_with_host_scope(self, **execute_params):
self.test_list_ip_addresses(scope='host', **execute_params) self.test_list_ip_addresses(scope='host', **execute_params)
@ -98,6 +100,23 @@ class IpTest(testtools.TestCase):
self, **execute_params): self, **execute_params):
self.test_list_ip_addresses(scope='global', **execute_params) self.test_list_ip_addresses(scope='global', **execute_params)
def test_list_ip_addresses_with_namespace(self):
for node in topology.list_openstack_nodes():
network_namespaces = ip.list_network_namespaces(
ssh_client=node.ssh_client)
if network_namespaces:
network_namespace = network_namespaces.first
ssh_client = node.ssh_client
break
namespace_ips = ip.list_ip_addresses(
ssh_client=ssh_client, scope='global',
network_namespace=network_namespace)
self.assertNotEqual([], namespace_ips)
host_ips = ip.list_ip_addresses(ssh_client=ssh_client, scope='global')
self.assertNotEqual(host_ips, namespace_ips)
def test_list_namespaces(self, **execute_params): def test_list_namespaces(self, **execute_params):
namespaces = ip.list_network_namespaces(**execute_params) namespaces = ip.list_network_namespaces(**execute_params)
self.assertIsInstance(namespaces, list) self.assertIsInstance(namespaces, list)