Add netns support check
Change-Id: I03d0a6bebaeddddcbcf49c376ff1c9d03ff1bdec Fixes: rhbz#966560
This commit is contained in:
@@ -143,3 +143,20 @@ def validate_puppet_logfile(logfile):
|
|||||||
logging.error("Error during remote puppet apply of " + manifestfile)
|
logging.error("Error during remote puppet apply of " + manifestfile)
|
||||||
logging.error(data)
|
logging.error(data)
|
||||||
raise PackStackError(message)
|
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
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
Plugin responsible for setting OpenStack global options
|
Plugin responsible for setting OpenStack global options
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import uuid
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
from packstack.installer import validators
|
from packstack.installer import exceptions
|
||||||
from packstack.installer import utils
|
from packstack.installer import utils
|
||||||
from packstack.installer.exceptions import ParamValidationError
|
from packstack.installer import validators
|
||||||
|
|
||||||
from packstack.modules.ospluginutils import gethostlist,\
|
from packstack.modules.ospluginutils import gethostlist,\
|
||||||
getManifestTemplate, \
|
getManifestTemplate, \
|
||||||
appendManifestFile
|
appendManifestFile
|
||||||
@@ -144,7 +144,7 @@ def initConfig(controllerObject):
|
|||||||
|
|
||||||
def initSequences(controller):
|
def initSequences(controller):
|
||||||
osclientsteps = [
|
osclientsteps = [
|
||||||
{'title': 'Adding pre install manifest entries', 'functions':[createmanifest]}
|
{'title': 'Adding pre install manifest entries', 'functions':[createmanifest]},
|
||||||
]
|
]
|
||||||
controller.addSequence("Running pre install scripts", [], [], osclientsteps)
|
controller.addSequence("Running pre install scripts", [], [], osclientsteps)
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ from packstack.installer import utils
|
|||||||
from packstack.installer import basedefs, output_messages
|
from packstack.installer import basedefs, output_messages
|
||||||
from packstack.installer.exceptions import ScriptRuntimeError
|
from packstack.installer.exceptions import ScriptRuntimeError
|
||||||
|
|
||||||
from packstack.modules.ospluginutils import gethostlist,\
|
from packstack.modules.ospluginutils import (gethostlist,
|
||||||
manifestfiles,\
|
manifestfiles,
|
||||||
validate_puppet_logfile
|
scan_puppet_logfile,
|
||||||
|
validate_puppet_logfile)
|
||||||
|
|
||||||
# Controller object will be initialized from main flow
|
# Controller object will be initialized from main flow
|
||||||
controller = None
|
controller = None
|
||||||
@@ -123,6 +124,7 @@ def copyPuppetModules(config):
|
|||||||
|
|
||||||
|
|
||||||
def waitforpuppet(currently_running):
|
def waitforpuppet(currently_running):
|
||||||
|
global controller
|
||||||
log_len = 0
|
log_len = 0
|
||||||
twirl = ["-","\\","|","/"]
|
twirl = ["-","\\","|","/"]
|
||||||
while currently_running:
|
while currently_running:
|
||||||
@@ -159,6 +161,9 @@ def waitforpuppet(currently_running):
|
|||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# check log file for relevant notices
|
||||||
|
controller.MESSAGES.extend(scan_puppet_logfile(log))
|
||||||
|
|
||||||
# check the log file for errors
|
# check the log file for errors
|
||||||
validate_puppet_logfile(log)
|
validate_puppet_logfile(log)
|
||||||
sys.stdout.write(("\r%s : " % log_file).ljust(basedefs.SPACE_LEN))
|
sys.stdout.write(("\r%s : " % log_file).ljust(basedefs.SPACE_LEN))
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import datetime
|
|||||||
import platform
|
import platform
|
||||||
|
|
||||||
from packstack.installer import basedefs
|
from packstack.installer import basedefs
|
||||||
|
from packstack.installer import exceptions
|
||||||
from packstack.installer import utils
|
from packstack.installer import utils
|
||||||
from packstack.installer import validators
|
from packstack.installer import validators
|
||||||
from packstack.installer.exceptions import InstallError
|
|
||||||
|
|
||||||
|
|
||||||
from packstack.modules.ospluginutils import gethostlist
|
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])
|
cmd.extend(['--password', password])
|
||||||
mask.append(password)
|
mask.append(password)
|
||||||
else:
|
else:
|
||||||
raise InstallError('Either RHN Satellite activation key or '
|
raise exceptions.InstallError('Either RHN Satellite activation '
|
||||||
'username/password must be provided.')
|
'key or username/password must '
|
||||||
|
'be provided.')
|
||||||
|
|
||||||
if cacert:
|
if cacert:
|
||||||
# use and if required download given certificate
|
# use and if required download given certificate
|
||||||
@@ -374,8 +375,6 @@ def initSequences(controller):
|
|||||||
|
|
||||||
|
|
||||||
def serverprep(config):
|
def serverprep(config):
|
||||||
config = controller.CONF
|
|
||||||
|
|
||||||
rh_username = None
|
rh_username = None
|
||||||
sat_url = None
|
sat_url = None
|
||||||
if is_rhel():
|
if is_rhel():
|
||||||
@@ -399,6 +398,7 @@ def serverprep(config):
|
|||||||
'proxy_user': sat_proxy_user.strip(),
|
'proxy_user': sat_proxy_user.strip(),
|
||||||
'proxy_pass': sat_proxy_pass.strip(),
|
'proxy_pass': sat_proxy_pass.strip(),
|
||||||
'flags': sat_flags}
|
'flags': sat_flags}
|
||||||
|
|
||||||
for hostname in gethostlist(config):
|
for hostname in gethostlist(config):
|
||||||
if '/' in hostname:
|
if '/' in hostname:
|
||||||
hostname = hostname.split('/')[0]
|
hostname = hostname.split('/')[0]
|
||||||
|
|||||||
5
packstack/puppet/modules/packstack/lib/facter/has_rdo.rb
Normal file
5
packstack/puppet/modules/packstack/lib/facter/has_rdo.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Facter.add(:has_rdo) do
|
||||||
|
setcode do
|
||||||
|
system "ls /etc/yum.repos.d/rdo-release.repo"
|
||||||
|
end
|
||||||
|
end
|
||||||
193
packstack/puppet/modules/packstack/lib/facter/netns.py
Normal file
193
packstack/puppet/modules/packstack/lib/facter/netns.py
Normal 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()
|
||||||
@@ -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
|
||||||
41
packstack/puppet/modules/packstack/manifests/netns.pp
Normal file
41
packstack/puppet/modules/packstack/manifests/netns.pp
Normal 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"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class CommandLineTestCase(PackstackTestCaseMixin, TestCase):
|
|||||||
# ospluginutils.validate_puppet_logfile with a mock function
|
# ospluginutils.validate_puppet_logfile with a mock function
|
||||||
orig_validate_logfile = ospluginutils.validate_puppet_logfile
|
orig_validate_logfile = ospluginutils.validate_puppet_logfile
|
||||||
ospluginutils.validate_puppet_logfile = lambda a: None
|
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
|
# 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
|
# the actual error that should be reported, so we replace it to
|
||||||
|
|||||||
Reference in New Issue
Block a user