Add netns support check

Change-Id: I03d0a6bebaeddddcbcf49c376ff1c9d03ff1bdec
Fixes: rhbz#966560
This commit is contained in:
Martin Magr 2013-05-24 13:21:02 +02:00
parent 77b481a44c
commit 1cd4ec913d
12 changed files with 320 additions and 13 deletions

View File

@ -143,3 +143,20 @@ def validate_puppet_logfile(logfile):
logging.error("Error during remote puppet apply of " + manifestfile)
logging.error(data)
raise PackStackError(message)
def scan_puppet_logfile(logfile):
"""
Returns list of packstack_info/packstack_warn notices parsed from
given puppet log file.
"""
output = []
notice = re.compile(r"notice: .*Notify\[packstack_info\]"
"\/message: defined \'message\' as "
"\'(?P<message>.*)\'")
with open(logfile) as content:
for line in content:
match = notice.search(line)
if match:
output.append(match.group('message'))
return output

View File

@ -2,13 +2,13 @@
Plugin responsible for setting OpenStack global options
"""
import uuid
import logging
import os
import uuid
from packstack.installer import validators
from packstack.installer import exceptions
from packstack.installer import utils
from packstack.installer.exceptions import ParamValidationError
from packstack.installer import validators
from packstack.modules.ospluginutils import gethostlist,\
getManifestTemplate, \
appendManifestFile
@ -144,7 +144,7 @@ def initConfig(controllerObject):
def initSequences(controller):
osclientsteps = [
{'title': 'Adding pre install manifest entries', 'functions':[createmanifest]}
{'title': 'Adding pre install manifest entries', 'functions':[createmanifest]},
]
controller.addSequence("Running pre install scripts", [], [], osclientsteps)

View File

@ -11,9 +11,10 @@ from packstack.installer import utils
from packstack.installer import basedefs, output_messages
from packstack.installer.exceptions import ScriptRuntimeError
from packstack.modules.ospluginutils import gethostlist,\
manifestfiles,\
validate_puppet_logfile
from packstack.modules.ospluginutils import (gethostlist,
manifestfiles,
scan_puppet_logfile,
validate_puppet_logfile)
# Controller object will be initialized from main flow
controller = None
@ -123,6 +124,7 @@ def copyPuppetModules(config):
def waitforpuppet(currently_running):
global controller
log_len = 0
twirl = ["-","\\","|","/"]
while currently_running:
@ -159,6 +161,9 @@ def waitforpuppet(currently_running):
time.sleep(3)
continue
# check log file for relevant notices
controller.MESSAGES.extend(scan_puppet_logfile(log))
# check the log file for errors
validate_puppet_logfile(log)
sys.stdout.write(("\r%s : " % log_file).ljust(basedefs.SPACE_LEN))

View File

@ -9,9 +9,9 @@ import datetime
import platform
from packstack.installer import basedefs
from packstack.installer import exceptions
from packstack.installer import utils
from packstack.installer import validators
from packstack.installer.exceptions import InstallError
from packstack.modules.ospluginutils import gethostlist
@ -303,8 +303,9 @@ def run_rhn_reg(host, server_url, username=None, password=None,
cmd.extend(['--password', password])
mask.append(password)
else:
raise InstallError('Either RHN Satellite activation key or '
'username/password must be provided.')
raise exceptions.InstallError('Either RHN Satellite activation '
'key or username/password must '
'be provided.')
if cacert:
# use and if required download given certificate
@ -374,8 +375,6 @@ def initSequences(controller):
def serverprep(config):
config = controller.CONF
rh_username = None
sat_url = None
if is_rhel():
@ -399,6 +398,7 @@ def serverprep(config):
'proxy_user': sat_proxy_user.strip(),
'proxy_pass': sat_proxy_pass.strip(),
'flags': sat_flags}
for hostname in gethostlist(config):
if '/' in hostname:
hostname = hostname.split('/')[0]

View File

@ -0,0 +1,5 @@
Facter.add(:has_rdo) do
setcode do
system "ls /etc/yum.repos.d/rdo-release.repo"
end
end

View File

@ -0,0 +1,193 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import contextlib
import inspect
import os
import random
import subprocess
import sys
import tempfile
import time
import uuid
import unittest
def execute(cmd_string, check_error=True, return_code=0, input=None,
block=True, error_msg='Error executing cmd'):
print cmd_string
cmd = cmd_string.split(' ')
proc = subprocess.Popen(cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if input:
proc.communicate(input=input)
elif block:
proc.wait()
if (check_error and
proc.returncode is not None and
proc.returncode != return_code):
msg = """
%(error_msg)s
Command: %(cmd)s
Exit Code: %(code)s
""".strip() % dict(cmd=' '.join(cmd),
code=proc.returncode,
error_msg=error_msg)
if input:
msg += "\n Stdin: %s" % input
if not proc.stdout.closed:
msg += "\n Stdout: %s" % proc.stdout.read()
if not proc.stderr.closed:
msg += "\n Stderr: %s" % proc.stderr.read()
raise Exception(msg)
return proc
def e(cmd, prefix='ip netns exec ', sudo=False, **kwargs):
frame_locals = inspect.getargvalues(sys._getframe(1))[3]
if sudo:
prefix = 'sudo ' + prefix
return execute(prefix + cmd % frame_locals, **kwargs)
def rand_name(name='test'):
return '%s-%s' % (name, str(random.randint(1, 0x7fffffff)))
@contextlib.contextmanager
def add_namespace():
name = rand_name('testns')
try:
e('ip netns add %(name)s', prefix='')
e('%(name)s ip link set lo up')
yield name
finally:
e('ip netns delete %(name)s', prefix='')
@contextlib.contextmanager
def add_namespaces():
with add_namespace() as ns1:
with add_namespace() as ns2:
yield ns1, ns2
def add_veth_pair(ns1, ns2, veth1, veth2, address1, address2):
e('ip link add %(veth1)s netns %(ns1)s type veth '
'peer name %(veth2)s netns %(ns2)s', prefix='')
e('%(ns1)s ip link show %(veth1)s')
e('%(ns2)s ip link show %(veth2)s')
e('%(ns1)s ip -4 addr add %(address1)s/24 brd 255.255.255.0 '
'scope global dev %(veth1)s')
e('%(ns2)s ip -4 addr add %(address2)s/24 brd 255.255.255.0 '
'scope global dev %(veth2)s')
e('%(ns1)s ip link set %(veth1)s up')
e('%(ns2)s ip link set %(veth2)s up')
class TestNetns(unittest.TestCase):
def test_quantum_netns_cmds(self):
"""Exercise the netns functionality required by quantum.
- Check that a veth pair can be configured to transit traffic
between 2 namespaces
- Check that iptables filtering can be configured
- Check that iptables routing can be configured
"""
# Naming scheme [resource][id]_[namespace id]
veth1_1 = 'veth1_1'
veth1_2 = 'veth1_2'
address1_1 = '192.168.0.1'
address1_2 = '192.168.0.2'
with add_namespaces() as (ns1, ns2):
# Check that inter-namespace connectivity can be established
add_veth_pair(ns1, ns2, veth1_1, veth1_2, address1_1, address1_2)
e('%(ns1)s ip link list')
e('%(ns1)s ip link show %(veth1_1)s')
e('%(ns1)s arping -A -U -I %(veth1_1)s '
'-c 1 %(address1_1)s')
e('%(ns2)s route add default gw %(address1_1)s')
e('%(ns2)s ping -c 1 -w 1 %(address1_1)s')
# Check that iptables filtering and save/restore can be performed
saved_iptables_state = e('%(ns1)s iptables-save').stdout.read()
e('%(ns1)s iptables -A INPUT -p icmp --icmp-type 8 -j DROP')
e('%(ns2)s ping -c 1 -w 1 %(address1_1)s', return_code=1)
e('%(ns1)s iptables-restore', input=saved_iptables_state)
e('%(ns2)s ping -c 1 -w 1 %(address1_1)s')
# Create another namespace (ns3) that is connected to ns1
# via a different subnet, so that traffic between ns3 and
# ns2 will have to be routed by ns1:
#
# ns2 <- 192.168.0.0/24 -> ns1 <- 192.168.1.0/24 -> ns3
#
with add_namespace() as ns3:
veth2_1 = 'veth2_1'
veth2_3 = 'veth2_3'
address2_1 = '192.168.1.1'
address2_3 = '192.168.1.2'
add_veth_pair(ns1, ns3, veth2_1, veth2_3,
address2_1, address2_3)
e('%(ns1)s sysctl -w net.ipv4.ip_forward=1')
e('%(ns1)s iptables -t nat -A POSTROUTING -o %(veth2_1)s -j '
'MASQUERADE')
e('%(ns1)s iptables -A FORWARD -i %(veth2_1)s -o %(veth1_1)s '
'-m state --state RELATED,ESTABLISHED -j ACCEPT')
e('%(ns1)s iptables -A FORWARD -i %(veth1_1)s -o %(veth2_1)s '
'-j ACCEPT')
e('%(ns2)s ping -c 1 -w 1 %(address2_3)s')
# Check that links can be torn down
e('%(ns1)s ip -4 addr del %(address1_1)s/24 '
'dev %(veth1_1)s')
e('%(ns1)s ip link delete %(veth1_1)s')
def test_domain_socket_access(self):
"""Check that a domain socket can be accessed regardless of namespace.
Quantum extends nova' metadata service - which identifies VM's
by their ip addresses - to configurations with overlapping
ips. Support is provided by:
- a proxy in each namespace (quantum-ns-metadata-proxy)
- the proxy can uniquely identify a given VM by its ip
address in the context of the router or network of the
namespace.
- a metadata agent (quantum-metadata-agent) that forwards
requests from the namespace proxies to nova's metadata
service.
Communication between the proxies and the agent is over a unix
domain socket. It is necessary that access to a domain socket
not be restricted by namespace, or such communication will not
be possible.
"""
sock_filename = os.path.join(tempfile.gettempdir(),
'testsock-%s' % str(uuid.uuid4()))
server = None
try:
# Create a server in the root namespace attached to a domain socket
server = e('nc -lU %(sock_filename)s', sudo=False, prefix='',
block=False)
# Attempt to connect to the domain socket from within a namespace
with add_namespace() as ns:
e('%(ns)s nc -U %(sock_filename)s', input='magic',
error_msg='Unable to communicate between namespaces via '
'domain sockets.')
finally:
if server:
server.kill()
if os.path.exists(sock_filename):
os.unlink(sock_filename)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,16 @@
Facter.add(:netns_support) do
setcode do
oldout = $stdout.clone
olderr = $stderr.clone
$stdout.reopen("/dev/null", "w")
$stderr.reopen("/dev/null", "w")
script_path = File.join(File.dirname(__FILE__), 'netns.py')
passed = system "python #{script_path}"
$stdout.reopen(oldout)
$stderr.reopen(olderr)
passed
end
end

View File

@ -0,0 +1,41 @@
#
# Checks if netns is supported and if not updates kernel, iputils
# and iproute
class packstack::netns (
$rdo_repo_url = 'http://repos.fedorapeople.org/repos/openstack/openstack-grizzly/',
$rdo_rpm_nvr = 'rdo-release-grizzly-3.noarch',
$warning = "Kernel package with netns support has been installed."
)
{
if $::netns_support != "true" {
exec { "netns_dependecy_install":
path => "/usr/bin/",
command => "yum update -y kernel iputils iproute"
}
if $::has_rdo != "true" {
stage { 'prepare':
before => Stage['main'],
}
stage { 'cleanup':
require => Stage['main'],
}
class {'packstack::netns::prepare':
rdo_rpm_url => "${rdo_repo_url}${rdo_rpm_nvr}.rpm",
stage => prepare,
}
class {'packstack::netns::cleanup':
rdo_rpm_nvr => $rdo_rpm_nvr,
stage => cleanup,
}
}
notify { "packstack_info":
message => $warning,
require => Exec["netns_dependecy_install"],
}
}
}

View File

@ -0,0 +1,9 @@
class packstack::netns::cleanup (
$rdo_rpm_nvr = 'rdo-release-grizzly-3.noarch'
) {
exec { "rdo_repo_uninstall":
path => "/usr/bin/",
command => "yum remove -y $rdo_rpm_nvr",
}
}

View File

@ -0,0 +1,9 @@
class packstack::netns::prepare (
$rdo_rpm_url = 'http://repos.fedorapeople.org/repos/openstack/openstack-grizzly/rdo-release-grizzly-3.noarch'
){
exec { "rdo_repo_install":
path => "/usr/bin/",
command => "yum localinstall -y $rdo_rpm_url",
}
}

View File

@ -0,0 +1,11 @@
$warning = "Kernel package with netns support has been installed on host $::ipaddress. Please note that with this action you are loosing Red Hat support for this host. Because of the kernel update host mentioned above requires reboot."
$required_value = 'y'
if $required_value == '%(CONFIG_QUANTUM_INSTALL)s' {
class { 'packstack::netns':
warning => $warning
}
}

View File

@ -57,6 +57,7 @@ class CommandLineTestCase(PackstackTestCaseMixin, TestCase):
# ospluginutils.validate_puppet_logfile with a mock function
orig_validate_logfile = ospluginutils.validate_puppet_logfile
ospluginutils.validate_puppet_logfile = lambda a: None
ospluginutils.scan_puppet_logfile = lambda a: []
# If there is a error in a plugin sys.exit() gets called, this masks
# the actual error that should be reported, so we replace it to