Skip to content

Commit 3d307ef

Browse files
committed
Script to remove duplicated port bindings
A new script to remove the duplicated port bindings was added. This script will list all ``ml2_port_bindings`` records in the database, finding those ones with the same port ID. Then the script removes those ones with status=INACTIVE. This script is useful to remove those leftovers that remain in the database after a failed live migration. "dry_run" mode is possible if selected in "[cli_script] dry_run" boolean config option. The duplicated port bindings are printed in the shell but not deleted. Related-Bug: #1979072 Conflicts: neutron/conf/common.py Change-Id: I0de5fbb70eb852f82bd311616557985d1ce89bbf (cherry picked from commit c5b76a8)
1 parent a06dab2 commit 3d307ef

File tree

8 files changed

+138
-0
lines changed

8 files changed

+138
-0
lines changed

doc/source/contributor/internals/live_migration.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,27 @@ after migration finished. During this time window, the instance might not be
172172
reachable via the network. This should be solved with bug
173173
https://bugs.launchpad.net/nova/+bug/1605016
174174

175+
Error recovery
176+
--------------
177+
178+
If the Live Migration fails, Nova will revert the operation. That implies
179+
deleting any object created in the database or in the destination compute
180+
node. However, in some cases have been reported the presence of `duplicated
181+
port bindings per port <https://bugs.launchpad.net/neutron/+bug/1979072>`_.
182+
In this state, the port cannot be migrated until the inactive port binding
183+
(the failed destination host port binding) has been deleted.
184+
185+
To this end, the script ``neutron-remove-duplicated-port-bindings`` has been
186+
created. This script finds all duplicated port binding (that means, all port
187+
bindings that point to the same port) and deletes the inactive one.
188+
189+
.. note::
190+
191+
This script cannot be executed while a Live Migration or a cross cell Cold
192+
Migration. The script will delete the inactive port binding and will break
193+
the process.
194+
195+
175196
Flow Diagram
176197
------------
177198

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Copyright (c) 2022 Red Hat, Inc.
2+
# All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
from neutron_lib import constants
17+
from neutron_lib import context
18+
from neutron_lib.db import api as db_api
19+
from oslo_config import cfg
20+
from oslo_db import options as db_options
21+
from oslo_log import log as logging
22+
23+
from neutron.common import config as common_config
24+
from neutron.objects import ports as ports_obj
25+
26+
27+
LOG = logging.getLogger(__name__)
28+
29+
30+
def setup_conf(conf):
31+
common_config.register_common_config_options()
32+
db_group, neutron_db_opts = db_options.list_opts()[0]
33+
conf.register_cli_opts(neutron_db_opts, db_group)
34+
conf()
35+
36+
37+
def main():
38+
"""Main method for removing the duplicated port binding registers.
39+
40+
This script finds all ``PortBinding`` registers with the same ``port_id``.
41+
That happens during the live-migration process. Once finished, the inactive
42+
port binding register is deleted. However, it could happen that during the
43+
live-migration, an error occurs and this deletion is not executed. The
44+
related port cannot be migrated anymore.
45+
46+
This script should not be executed during a live migration process. It will
47+
remove the inactive port binding and will break the migration.
48+
"""
49+
conf = cfg.CONF
50+
setup_conf(conf)
51+
_dry_run = conf.cli_script.dry_run
52+
admin_ctx = context.get_admin_context()
53+
with db_api.CONTEXT_WRITER.using(admin_ctx):
54+
dup_pbindings = ports_obj.PortBinding.get_duplicated_port_bindings(
55+
admin_ctx)
56+
57+
# Clean duplicated port bindings that are INACTIVE (if not in dry-run).
58+
if not _dry_run:
59+
for pbinding in dup_pbindings:
60+
ports_obj.PortBinding.delete_objects(
61+
admin_ctx, status=constants.INACTIVE,
62+
port_id=pbinding.port_id)
63+
64+
if dup_pbindings:
65+
port_ids = [pbinding.port_id for pbinding in dup_pbindings]
66+
action = 'can be' if _dry_run else 'have been'
67+
LOG.info('The following duplicated PortBinding registers with '
68+
'status=INACTIVE %s removed, port_ids: %s',
69+
action, port_ids)
70+
else:
71+
LOG.info('No duplicated PortBinding registers has been found.')

neutron/common/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
common_config.IRONIC_CONF_SECTION)
8282
common_config.register_ironic_opts()
8383

84+
# Register the CLI script configuration options.
85+
common_config.register_cli_script_opts()
86+
8487

8588
def init(args, default_config_files=None, **kwargs):
8689
cfg.CONF(args=args, project='neutron',

neutron/conf/common.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,16 @@ def register_placement_opts(cfg=cfg.CONF):
238238

239239
def register_ironic_opts(cfg=cfg.CONF):
240240
cfg.register_opts(ironic_opts, group=IRONIC_CONF_SECTION)
241+
242+
243+
CLI_SCRIPT_SECTION = 'cli_script'
244+
245+
cli_script_options = [
246+
cfg.BoolOpt('dry_run', default=False,
247+
help=_('Dry-run execution of the CLI script. No change will '
248+
'be performed on the system.')),
249+
]
250+
251+
252+
def register_cli_script_opts(cfg=cfg.CONF):
253+
cfg.register_opts(cli_script_options, group=CLI_SCRIPT_SECTION)

neutron/objects/ports.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from oslo_log import log as logging
2121
from oslo_utils import versionutils
2222
from oslo_versionedobjects import fields as obj_fields
23+
import sqlalchemy
2324
from sqlalchemy import and_
2425

2526
from neutron.common import _constants
@@ -102,6 +103,13 @@ def get_port_id_and_host(cls, context, vif_type, vnic_type, status):
102103
cls.db_model.status == status))
103104
return query.all()
104105

106+
@classmethod
107+
@db_api.CONTEXT_READER
108+
def get_duplicated_port_bindings(cls, context):
109+
return context.session.query(
110+
cls.db_model).group_by(
111+
cls.db_model.port_id).having(sqlalchemy.func.count() > 1).all()
112+
105113

106114
@base.NeutronObjectRegistry.register
107115
class DistributedPortBinding(PortBindingBase):

neutron/tests/unit/objects/test_ports.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ class PortBindingDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
5454
BasePortBindingDbObjectTestCase):
5555
_test_class = ports.PortBinding
5656

57+
def test_get_duplicated_port_bindings(self):
58+
port_id = self._create_test_port_id()
59+
self.update_obj_fields({'port_id': port_id},
60+
objs=[self.objs[0], self.objs[1]])
61+
for i in range(3):
62+
_obj = self._make_object(self.obj_fields[i])
63+
_obj.create()
64+
dup_pb = ports.PortBinding.get_duplicated_port_bindings(self.context)
65+
self.assertEqual(1, len(dup_pb))
66+
self.assertEqual(port_id, dup_pb[0].port_id)
67+
5768

5869
class DistributedPortBindingIfaceObjTestCase(
5970
obj_test_base.BaseObjectIfaceTestCase):
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
features:
3+
- |
4+
A new script to remove the duplicated port bindings was added. This script
5+
will list all ``ml2_port_bindings`` records in the database, finding those
6+
ones with the same port ID. Then the script removes those ones with
7+
status=INACTIVE. This script is useful to remove those leftovers that
8+
remain in the database after a failed live migration. It is important to
9+
remark that this script should not be executed during any live migration
10+
process.

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ console_scripts =
6464
neutron-ovn-db-sync-util = neutron.cmd.ovn.neutron_ovn_db_sync_util:main
6565
neutron-sanitize-port-binding-profile-allocation = neutron.cmd.sanitize_port_binding_profile_allocation:main
6666
neutron-sanitize-port-mac-addresses = neutron.cmd.sanitize_port_mac_addresses:main
67+
neutron-remove-duplicated-port-bindings = neutron.cmd.remove_duplicated_port_bindings:main
6768
ml2ovn-trace = neutron.cmd.ovn.ml2ovn_trace:main
6869
neutron.core_plugins =
6970
ml2 = neutron.plugins.ml2.plugin:Ml2Plugin

0 commit comments

Comments
 (0)