neutron/neutron/cmd/ipset_cleanup.py
Ihar Hrachyshka a521bf0393 Make use of -w argument for iptables calls
Upstream iptables added support for -w ('wait') argument to
iptables-restore. It makes the command grab a 'xlock' that guarantees
that no two iptables calls will mess a table if called in parallel.
[This somewhat resembles what we try to achieve with a file lock we
grab in iptables manager's _apply_synchronized.]

If two processes call to iptables-restore or iptables in parallel, the
second call risks failing, returning error code = 4, and also printing
the following error:

    Another app is currently holding the xtables lock. Perhaps you want
    to use the -w option?

If we call to iptables / iptables-restore with -w though, it will wait
for the xlock release before proceeding, and won't fail.

Though the feature was added in iptables/master only and is not part of
an official iptables release, it was already backported to RHEL 7.x
iptables package, and so we need to adopt to it. At the same time, we
can't expect any underlying platform to support the argument.

A solution here is to call iptables-restore with -w when a regular call
failed. Also, the patch adds -w to all iptables calls, in the iptables
manager as well as in ipset-cleanup.

Since we don't want to lock agent in case current xlock owner doesn't
release it in reasonable time, we limit the time we wait to ~1/3 of
report_interval, to give the agent some time to recover without
triggering expensive fullsync.

In the future, we may be able to get rid of our custom synchronization
lock that we use in iptables manager. But this will require all
supported platforms to get the feature in and will take some time.

Closes-Bug: #1712185
Change-Id: I94e54935df7c6caa2480eca19e851cb4882c0f8b
2017-08-31 20:28:53 +00:00

99 lines
3.2 KiB
Python

# Copyright (c) 2015 OpenStack Foundation.
# 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 oslo_config import cfg
from oslo_log import log as logging
from neutron.agent.linux import utils
from neutron.common import config
from neutron.conf.agent import cmd as command
LOG = logging.getLogger(__name__)
def setup_conf():
"""Setup the cfg for the clean up utility.
Use separate setup_conf for the utility because there are many options
from the main config that do not apply during clean-up.
"""
conf = cfg.CONF
command.register_cmd_opts(command.ip_opts, conf)
return conf
def remove_iptables_reference(ipset):
# Remove any iptables reference to this IPset
cmd = ['iptables-save'] if 'IPv4' in ipset else ['ip6tables-save']
iptables_save = utils.execute(cmd, run_as_root=True)
if ipset in iptables_save:
cmd = ['iptables'] if 'IPv4' in ipset else ['ip6tables']
cmd += ['-w', '10'] # wait for xlock release
LOG.info("Removing iptables rule for IPset: %s", ipset)
for rule in iptables_save.splitlines():
if '--match-set %s ' % ipset in rule and rule.startswith('-A'):
# change to delete
params = rule.split()
params[0] = '-D'
try:
utils.execute(cmd + params, run_as_root=True)
except Exception:
LOG.exception('Error, unable to remove iptables rule '
'for IPset: %s', ipset)
def destroy_ipset(conf, ipset):
# If there is an iptables reference and we don't remove it, the
# IPset removal will fail below
if conf.force:
remove_iptables_reference(ipset)
LOG.info("Destroying IPset: %s", ipset)
cmd = ['ipset', 'destroy', ipset]
try:
utils.execute(cmd, run_as_root=True)
except Exception:
LOG.exception('Error, unable to destroy IPset: %s', ipset)
def cleanup_ipsets(conf):
# Identify ipsets for destruction.
LOG.info("Destroying IPsets with prefix: %s", conf.prefix)
cmd = ['ipset', '-L', '-n']
ipsets = utils.execute(cmd, run_as_root=True)
for ipset in ipsets.split('\n'):
if conf.allsets or ipset.startswith(conf.prefix):
destroy_ipset(conf, ipset)
LOG.info("IPset cleanup completed successfully")
def main():
"""Main method for cleaning up IPsets.
The utility is designed to clean-up after the forced or unexpected
termination of Neutron agents.
The --allsets flag should only be used as part of the cleanup of a devstack
installation as it will blindly destroy all IPsets.
"""
conf = setup_conf()
conf()
config.setup_logging()
cleanup_ipsets(conf)