Sanitize profile column of ml2_port_bindings table in the DB

With the introduction of port-resource-request-groups extension,
format of binding-profile.allocation has changed. Since the DB,
may contain port bindings that were created before the introduction
of the new format, it's necessary to perform upgrade check and
sanitize those rows that are still using an older format.

Partial-Bug: #1922237
See-Also: https://review.opendev.org/785236
Change-Id: I95e9e1bc553ac499d75c9280e45dfea61d135279
This commit is contained in:
Przemyslaw Szczerbik 2021-09-24 13:39:15 +02:00
parent 8db15cb2f3
commit d699a955cd
6 changed files with 187 additions and 0 deletions

37
neutron/api/converters.py Normal file
View File

@ -0,0 +1,37 @@
# Copyright (c) 2021 Ericsson Software Technology
#
# 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 uuid
from neutron_lib.placement import utils as pl_utils
# TODO(przszc): Delete when https://review.opendev.org/813650 is released
def convert_to_sanitized_binding_profile_allocation(allocation, port_id,
min_bw_rules):
"""Return binding-profile.allocation in the new format
:param allocation: binding-profile.allocation attribute containting a
string with RP UUID
:param port_id: ID of the port that is being sanitized
:param min_bw_rules: A list of minimum bandwidth rules associated with the
port.
:return: A dict with allocation in {'<group_uuid>': '<rp_uuid>'} format.
"""
if isinstance(allocation, dict):
return allocation
group_id = str(
pl_utils.resource_request_group_uuid(uuid.UUID(port_id), min_bw_rules))
return {group_id: allocation}

View File

@ -0,0 +1,87 @@
# Copyright (c) 2021 Ericsson Software Technology
#
# 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 neutron_lib import context
from neutron_lib.db import api as db_api
from oslo_config import cfg
from oslo_db import options as db_options
from oslo_log import log as logging
from neutron.api import converters as n_converters
from neutron.objects import ports as port_obj
from neutron.objects.qos import binding as qos_binding_obj
from neutron.objects.qos import rule as qos_rule_obj
LOG = logging.getLogger(__name__)
def setup_conf():
conf = cfg.CONF
db_group, neutron_db_opts = db_options.list_opts()[0]
cfg.CONF.register_cli_opts(neutron_db_opts, db_group)
conf()
def main():
"""Main method for sanitizing "ml2_port_bindings.profile" column.
This script will sanitize "ml2_port_bindings.profile" columns existing in
the database. In Yoga release the format of this column has changed from:
{'allocation': '<rp_uuid>'}
to:
{'allocation': {'<group_uuid>': '<rp_uuid>'}}
where group_uuid is generated based on port_id and ID of QoS rules
belonging to that group.
"""
setup_conf()
admin_ctx = context.get_admin_context()
with db_api.CONTEXT_WRITER.using(admin_ctx):
for port_binding in port_obj.PortBinding.get_objects(admin_ctx):
# NOTE(przszc): Before minimum packet rate rule was introduced,
# binding-profile.allocation attribute could contain only a single
# RP UUID, responsible for providing minimum bandwidth resources.
# Because of that, whenever we find allocation attribute that still
# uses old format, we can safely assume that we need to generate
# minimum bandwidth group UUID.
allocation = port_binding.profile.get('allocation')
if (not allocation or isinstance(allocation, dict)):
continue
qos_port_binding = qos_binding_obj.QosPolicyPortBinding.get_object(
admin_ctx, port_id=port_binding.port_id)
if not qos_port_binding:
LOG.error(
'Failed to sanitize binding-profile.allocation attribute '
'%s for port %s: Did not find associated QoS policy.',
allocation, port_binding.port_id)
continue
min_bw_rules = qos_rule_obj.QosMinimumBandwidthRule.get_objects(
admin_ctx, qos_policy_id=qos_port_binding.policy_id)
if not min_bw_rules:
LOG.error(
'Failed to sanitize binding-profile.allocation attribute '
'%s for port %s: Associated QoS policy %s has no minimum '
'bandwidth rules.', allocation, port_binding.port_id,
qos_port_binding.policy_id)
continue
port_binding.profile = {'allocation':
n_converters.convert_to_sanitized_binding_profile_allocation(
allocation, port_binding.port_id, min_bw_rules)}
LOG.info('Port %s updated, New binding-profile.allocation format: '
'%s', port_binding.port_id, port_binding.profile)
port_binding.update()

View File

@ -29,6 +29,7 @@ from neutron.db.models import agent as agent_model
from neutron.db.models.plugins.ml2 import vlanallocation
from neutron.db.models import segment
from neutron.db import models_v2
from neutron.objects import ports as port_obj
OVN_ALEMBIC_TABLE_NAME = "ovn_alembic_version"
@ -106,6 +107,12 @@ def get_duplicate_network_segment_count():
return query.count()
def port_binding_profiles():
ctx = context.get_admin_context()
return [port_binding.profile
for port_binding in port_obj.PortBinding.get_objects(ctx)]
class CoreChecks(base.BaseChecks):
def get_checks(self):
@ -127,6 +134,8 @@ class CoreChecks(base.BaseChecks):
self.port_mac_address_sanity),
(_('NetworkSegments unique constraint check'),
self.networksegments_unique_constraint_check),
(_('Port Binding profile sanity check'),
self.port_binding_profile_sanity),
]
@staticmethod
@ -365,3 +374,31 @@ class CoreChecks(base.BaseChecks):
upgradecheck.Code.SUCCESS,
_("No networksegments sharing the same network_id, network_type "
"and physical_network found."))
@staticmethod
def port_binding_profile_sanity(checker):
"""Checks that "ml2_port_bindings.profile" uses the new format
All allocation information should be stored in the following format:
{'allocation': {'<group_uuid>': '<rp_uuid>'}}.
"""
if not cfg.CONF.database.connection:
return upgradecheck.Result(
upgradecheck.Code.WARNING,
_("Database connection string is not set. Check for "
"ml2_port_bindings.profile sanity can't be done."))
for profile in port_binding_profiles():
allocation = profile.get('allocation')
if (allocation and not isinstance(allocation, dict)):
return upgradecheck.Result(
upgradecheck.Code.FAILURE,
_("ml2_port_bindings.profile rows are not correctly "
"formated in the database. The script "
"neutron-sanitize-port-binding-profile-allocation "
"should be executed"))
return upgradecheck.Result(
upgradecheck.Code.SUCCESS,
_("All ml2_port_bindings.profile rows are correctly formated in "
"the database."))

View File

@ -211,3 +211,19 @@ class TestChecks(base.BaseTestCase):
result = checks.CoreChecks.\
networksegments_unique_constraint_check(mock.ANY)
self.assertEqual(returned_code, result.code)
def test_port_binding_profile_sanity(self):
new_format = {"allocation":
{"397aec7a-1f69-11ec-9f1a-7b14e597e275":
"41d7391e-1f69-11ec-a899-8f9d6d950f8d"}}
old_format = {"allocation": "41d7391e-1f69-11ec-a899-8f9d6d950f8d"}
cases = (([new_format], Code.SUCCESS),
([old_format], Code.FAILURE))
with mock.patch.object(
checks, 'port_binding_profiles') \
as mock_port_binding_profiles:
for profile, returned_code in cases:
mock_port_binding_profiles.return_value = profile
result = checks.CoreChecks.port_binding_profile_sanity(
mock.ANY)
self.assertEqual(returned_code, result.code)

View File

@ -0,0 +1,9 @@
---
features:
- |
Added a check to verify if all rows of ``ml2_port_bindings`` table in the
DB are using the new format for ``profile`` column. This check is part of
upgrade check, that can be executed with ``neutron-status upgrade check``
command. If some rows are using obsolete format, they can be sanitized
with a script that can be executed with
``neutron-sanitize-port-binding-profile-allocation`` command.

View File

@ -61,6 +61,7 @@ console_scripts =
neutron-ovn-metadata-agent = neutron.cmd.eventlet.agents.ovn_metadata:main
neutron-ovn-migration-mtu = neutron.cmd.ovn.migration_mtu:main
neutron-ovn-db-sync-util = neutron.cmd.ovn.neutron_ovn_db_sync_util:main
neutron-sanitize-port-binding-profile-allocation = neutron.cmd.sanitize_port_binding_profile_allocation:main
neutron-sanitize-port-mac-addresses = neutron.cmd.sanitize_port_mac_addresses:main
ml2ovn-trace = neutron.cmd.ovn.ml2ovn_trace:main
neutron.core_plugins =