neutron/neutron/tests/functional/cmd/process_spawn.py

184 lines
5.9 KiB
Python

# Copyright 2016 Red Hat, Inc.
#
# 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.
import os
import random
import signal
import socket
import sys
import time
from neutron_lib import constants as n_const
from oslo_config import cfg
from neutron.agent.linux import daemon
UNIX_FAMILY = 'UNIX'
OPTS = [
cfg.IntOpt('num_children',
short='n',
default=0,
help='Number of children to spawn',
required=False),
cfg.StrOpt('family',
short='f',
default=n_const.IPv4,
choices=[n_const.IPv4, n_const.IPv6, UNIX_FAMILY],
help='Listen socket family (%(v4)s, %(v6)s or %(unix)s)' %
{
'v4': n_const.IPv4,
'v6': n_const.IPv6,
'unix': UNIX_FAMILY
},
required=False),
cfg.StrOpt('proto',
short='p',
default=n_const.PROTO_NAME_TCP,
choices=[n_const.PROTO_NAME_TCP, n_const.PROTO_NAME_UDP],
help='Protocol (%(tcp)s or %(udp)s)' %
{
'tcp': n_const.PROTO_NAME_TCP,
'udp': n_const.PROTO_NAME_UDP
},
required=False),
cfg.BoolOpt('parent_listen',
short='pl',
default=True,
help='Parent process must listen too',
required=False),
cfg.BoolOpt('ignore_sigterm',
short='i',
default=False,
help='Ignore SIGTERM',
required=False)
]
class ProcessSpawn(daemon.Daemon):
"""This class is part of the functional test of the netns_cleanup module.
It allows spawning processes that listen on random ports either on
tcp(6), udp(6) or unix sockets. Also it allows handling or ignoring
SIGTERM to check whether the cleanup works as expected by getting rid
of the spawned processes.
"""
MAX_BIND_RETRIES = 64
DCT_FAMILY = {
n_const.IPv4: socket.AF_INET,
n_const.IPv6: socket.AF_INET6,
UNIX_FAMILY: socket.AF_UNIX
}
DCT_PROTO = {
n_const.PROTO_NAME_TCP: socket.SOCK_STREAM,
n_const.PROTO_NAME_UDP: socket.SOCK_DGRAM,
}
def __init__(self, pidfile=None,
family=n_const.IPv4,
proto=n_const.PROTO_NAME_TCP,
ignore_sigterm=False, num_children=0,
parent_must_listen=True):
self.family = family
self.proto = proto
self.ignore_sigterm = ignore_sigterm
self.num_children = num_children
self.listen_socket = None
self.parent_must_listen = parent_must_listen
self.child_pids = []
super(ProcessSpawn, self).__init__(pidfile)
def start_listening(self):
socket_family = self.DCT_FAMILY[self.family]
socket_type = self.DCT_PROTO[self.proto]
self.listen_socket = socket.socket(socket_family, socket_type)
# Set a different seed per process to increase randomness
random.seed(os.getpid())
# Try to listen in a random port which is not currently in use
retries = 0
while retries < ProcessSpawn.MAX_BIND_RETRIES:
# NOTE(dalvarez): not finding a free port on a freshly created
# namespace is very unlikely but if problems show up, retries can
# be increased to avoid tests failing
try:
if self.family == UNIX_FAMILY:
self.listen_socket.bind('')
else:
# Pick a non privileged port
port = random.randint(1024, 65535)
self.listen_socket.bind(('', port))
except socket.error:
retries += 1
else:
if n_const.PROTO_NAME_TCP in self.proto:
self.listen_socket.listen(0)
break
def do_sleep(self):
while True:
time.sleep(10)
def run(self):
# Spawn as many children as requested
children = []
while len(children) != self.num_children:
child_pid = os.fork()
if child_pid == 0:
# Listen and do nothing else
self.start_listening()
self.do_sleep()
return
children.append(child_pid)
# Install a SIGTERM handler if requested
handler = (
signal.SIG_IGN if self.ignore_sigterm else self.sigterm_handler)
signal.signal(signal.SIGTERM, handler)
self.child_pids = children
if self.parent_must_listen:
self.start_listening()
self.do_sleep()
def sigterm_handler(self, signum, frame):
if self.listen_socket:
self.listen_socket.close()
for child in self.child_pids:
try:
os.kill(child, signal.SIGTERM)
except OSError:
pass
sys.exit(0)
def main():
cfg.CONF.register_cli_opts(OPTS)
cfg.CONF(project='neutron', default_config_files=[])
proc_spawn = ProcessSpawn(num_children=cfg.CONF.num_children,
family=cfg.CONF.family,
proto=cfg.CONF.proto,
parent_must_listen=cfg.CONF.parent_listen,
ignore_sigterm=cfg.CONF.ignore_sigterm)
proc_spawn.start()
if __name__ == "__main__":
main()