diff --git a/devstack/lib/uplink_status_propagation b/devstack/lib/uplink_status_propagation index 28a77a85547..f62394dfa58 100644 --- a/devstack/lib/uplink_status_propagation +++ b/devstack/lib/uplink_status_propagation @@ -1,3 +1,4 @@ function configure_uplink_status_propagation_extension { neutron_ml2_extension_driver_add "uplink_status_propagation" + neutron_ml2_extension_driver_add "uplink_status_propagation_updatable" } diff --git a/neutron/common/ovn/extensions.py b/neutron/common/ovn/extensions.py index 6ecd0582b19..62197648bb1 100644 --- a/neutron/common/ovn/extensions.py +++ b/neutron/common/ovn/extensions.py @@ -93,6 +93,7 @@ from neutron_lib.api.definitions import tag_creation from neutron_lib.api.definitions import tap_mirror from neutron_lib.api.definitions import trunk from neutron_lib.api.definitions import uplink_status_propagation +from neutron_lib.api.definitions import uplink_status_propagation_updatable from neutron_lib.api.definitions import vlantransparent from neutron_lib.api.definitions import vpn from neutron_lib.api.definitions import vpn_endpoint_groups @@ -209,5 +210,6 @@ ML2_SUPPORTED_API_EXTENSIONS = [ firewall_v2.ALIAS, firewall_v2_stdattrs.ALIAS, uplink_status_propagation.ALIAS, + uplink_status_propagation_updatable.ALIAS, tap_mirror.ALIAS, ] diff --git a/neutron/db/uplink_status_propagation_db.py b/neutron/db/uplink_status_propagation_db.py index 8e30bed7764..bca2edb174e 100644 --- a/neutron/db/uplink_status_propagation_db.py +++ b/neutron/db/uplink_status_propagation_db.py @@ -26,6 +26,17 @@ class UplinkStatusPropagationMixin: obj.create() res[usp.PROPAGATE_UPLINK_STATUS] = data[usp.PROPAGATE_UPLINK_STATUS] + def _process_update_port(self, context, data, res): + obj = usp_obj.PortUplinkStatusPropagation.get_object( + context, port_id=res['id']) + if obj: + obj.propagate_uplink_status = data[usp.PROPAGATE_UPLINK_STATUS] + obj.update() + res[usp.PROPAGATE_UPLINK_STATUS] = data[ + usp.PROPAGATE_UPLINK_STATUS] + else: + self._process_create_port(context, data, res) + @staticmethod def _extend_port_dict(port_res, port_db): # NOTE(ralonsoh): the default value is "True". Ports created before diff --git a/neutron/extensions/uplink_status_propagation_updatable.py b/neutron/extensions/uplink_status_propagation_updatable.py new file mode 100644 index 00000000000..70af901e4ea --- /dev/null +++ b/neutron/extensions/uplink_status_propagation_updatable.py @@ -0,0 +1,19 @@ +# 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.api.definitions import uplink_status_propagation_updatable \ + as apidef +from neutron_lib.api import extensions + + +class Uplink_status_propagation_updatable(extensions.APIExtensionDescriptor): + api_definition = apidef diff --git a/neutron/plugins/ml2/extensions/uplink_status_propagation_updatable.py b/neutron/plugins/ml2/extensions/uplink_status_propagation_updatable.py new file mode 100644 index 00000000000..0a9f9c23c5a --- /dev/null +++ b/neutron/plugins/ml2/extensions/uplink_status_propagation_updatable.py @@ -0,0 +1,42 @@ +# Copyright (c) 2024 Red Hat Inc. +# 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 neutron_lib.api.definitions import uplink_status_propagation_updatable \ + as uspu +from neutron_lib.plugins.ml2 import api +from oslo_log import log as logging + +from neutron.db import uplink_status_propagation_db as usp_db + + +LOG = logging.getLogger(__name__) + + +class UplinkStatusPropagationUpdatableExtensionDriver( + api.ExtensionDriver, usp_db.UplinkStatusPropagationMixin): + + _supported_extension_alias = uspu.ALIAS + + def initialize(self): + LOG.info('UplinkStatusPropagationUpdatableExtensionDriver ' + 'initialization complete') + + @property + def extension_alias(self): + return self._supported_extension_alias + + def process_update_port(self, context, data, result): + if uspu.PROPAGATE_UPLINK_STATUS in data: + self._process_update_port(context, data, result) diff --git a/neutron/tests/contrib/hooks/api_all_extensions b/neutron/tests/contrib/hooks/api_all_extensions index 7cbaae210e2..88beaa0dc13 100644 --- a/neutron/tests/contrib/hooks/api_all_extensions +++ b/neutron/tests/contrib/hooks/api_all_extensions @@ -84,3 +84,4 @@ NETWORK_API_EXTENSIONS+=",tag-ports-during-bulk-creation" NETWORK_API_EXTENSIONS+=",trunk" NETWORK_API_EXTENSIONS+=",trunk-details" NETWORK_API_EXTENSIONS+=",uplink-status-propagation" +NETWORK_API_EXTENSIONS+=",uplink-status-propagation-updatable" diff --git a/neutron/tests/unit/extensions/test_uplink_status_propagation_updatable.py b/neutron/tests/unit/extensions/test_uplink_status_propagation_updatable.py new file mode 100644 index 00000000000..0bbb6196fb0 --- /dev/null +++ b/neutron/tests/unit/extensions/test_uplink_status_propagation_updatable.py @@ -0,0 +1,97 @@ +# 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 ddt +from neutron_lib.api.definitions import uplink_status_propagation as usp +from neutron_lib.api.definitions import uplink_status_propagation_updatable \ + as apidef +from neutron_lib.db import api as db_api +from neutron_lib.db import resource_extend + +from neutron.db import db_base_plugin_v2 +from neutron.db import uplink_status_propagation_db as usp_db +from neutron.tests.common import test_db_base_plugin_v2 + + +class UplinkStatusPropagationUpdatableExtensionTestPlugin( + db_base_plugin_v2.NeutronDbPluginV2, + usp_db.UplinkStatusPropagationMixin): + """Test plugin to mixin the uplink status propagation extension. + """ + + supported_extension_aliases = [usp.ALIAS, apidef.ALIAS] + + # TODO(ralonsoh): update ``uplink_status_propagation_updatable`` with + # COLLECTION_NAME=neutron_lib.api.definitions.port.COLLECTION_NAME + @staticmethod + @resource_extend.extends([usp.COLLECTION_NAME]) + def _extend_network_project_default(port_res, port_db): + return usp_db.UplinkStatusPropagationMixin._extend_port_dict( + port_res, port_db) + + def create_port(self, context, port): + with db_api.CONTEXT_WRITER.using(context): + new_port = super().create_port(context, port) + # Update the propagate_uplink_status in the database + p = port['port'] + if 'propagate_uplink_status' not in p: + p['propagate_uplink_status'] = False + self._process_create_port(context, p, new_port) + return new_port + + def update_port(self, context, port_id, port, **kwargs): + with db_api.CONTEXT_WRITER.using(context): + new_port = super().update_port(context, port_id, port) + # Update the propagate_uplink_status in the database + p = port['port'] + if 'propagate_uplink_status' not in p: + p['propagate_uplink_status'] = False + self._process_update_port(context, p, new_port) + return new_port + + +@ddt.ddt +class UplinkStatusPropagationUpdatableExtensionTestCase( + test_db_base_plugin_v2.NeutronDbPluginV2TestCase): + """Test API extension ``uplink-status-propagation-updatable`` attributes. + """ + + def setUp(self, **kwargs): + plugin = ('neutron.tests.unit.extensions.test_uplink_status_' + 'propagation_updatable.' + 'UplinkStatusPropagationUpdatableExtensionTestPlugin') + super().setUp(plugin=plugin) + + @ddt.data(True, False) + def test_update_port_propagate_uplink_status( + self, propagate_uplink_status): + name = 'propagate_uplink_status' + keys = [('name', name), ('admin_state_up', True), + ('status', self.port_create_status), + (usp.PROPAGATE_UPLINK_STATUS, propagate_uplink_status)] + with self.port(name=name, + propagate_uplink_status=propagate_uplink_status + ) as port: + for k, v in keys: + self.assertEqual(v, port['port'][k]) + + # Update the port with the opposite ``propagate_uplink_status`` + # value and check it. + data = {'port': {usp.PROPAGATE_UPLINK_STATUS: + not propagate_uplink_status}} + req = self.new_update_request('ports', data, port['port']['id']) + req.get_response(self.api) + req = self.new_show_request('ports', port['port']['id']) + res = req.get_response(self.api) + port = self.deserialize(self.fmt, res)['port'] + self.assertEqual(not propagate_uplink_status, + port[usp.PROPAGATE_UPLINK_STATUS]) diff --git a/neutron/tests/unit/plugins/ml2/extensions/test_uplink_status_propagation_updatable.py b/neutron/tests/unit/plugins/ml2/extensions/test_uplink_status_propagation_updatable.py new file mode 100644 index 00000000000..ab0915da6b1 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/extensions/test_uplink_status_propagation_updatable.py @@ -0,0 +1,60 @@ +# Copyright (c) 2024 Red Hat Inc. +# 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. + +import ddt +from neutron_lib.api.definitions import uplink_status_propagation as usp_def +from neutron_lib.api.definitions import uplink_status_propagation_updatable \ + as uspu_def +from neutron_lib.plugins import directory +from oslo_config import cfg + +from neutron.tests.unit.plugins.ml2 import test_plugin + + +@ddt.ddt +class UplinkStatusPropagationUpdatableML2ExtDriverTestCase( + test_plugin.Ml2PluginV2TestCase): + + _extension_drivers = [usp_def.ALIAS.replace('-', '_'), + uspu_def.ALIAS.replace('-', '_')] + + def setUp(self): + cfg.CONF.set_override('extension_drivers', + self._extension_drivers, + group='ml2') + super().setUp() + self.plugin = directory.get_plugin() + + @ddt.data(True, False) + def test_port_update_propagate_uplink_status(self, _status): + with self.network() as n: + args = {'port': {'name': 'test', + 'network_id': n['network']['id'], + 'tenant_id': n['network']['id'], + 'device_id': '', + 'device_owner': '', + 'fixed_ips': '', + 'propagate_uplink_status': _status, + 'admin_state_up': True, + 'status': 'ACTIVE'}} + try: + port = self.plugin.create_port(self.context, args) + args = {'port': {'propagate_uplink_status': not _status}} + self.plugin.update_port(self.context, port['id'], args) + port = self.plugin.get_port(self.context, port['id']) + self.assertEqual(not _status, port['propagate_uplink_status']) + finally: + if port: + self.plugin.delete_port(self.context, port['id']) diff --git a/releasenotes/notes/add-propagate_uplink_status_updatable-to-port-383669f31c82809a.yaml b/releasenotes/notes/add-propagate_uplink_status_updatable-to-port-383669f31c82809a.yaml new file mode 100644 index 00000000000..0398333133d --- /dev/null +++ b/releasenotes/notes/add-propagate_uplink_status_updatable-to-port-383669f31c82809a.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added a new API extension ``uplink_status_propagation_updatable``. Now the + port attribute `propagate_uplink_status`` can be updated once the port is + created. The backend (ML2/SR-IOV) will receive the update and update the + VF state. diff --git a/setup.cfg b/setup.cfg index 242298d4ba3..66ae1cc9a57 100644 --- a/setup.cfg +++ b/setup.cfg @@ -124,6 +124,7 @@ neutron.ml2.extension_drivers = port_numa_affinity_policy = neutron.plugins.ml2.extensions.port_numa_affinity_policy:PortNumaAffinityPolicyExtensionDriver port_trusted = neutron.plugins.ml2.extensions.port_trusted:PortTrustedExtensionDriver uplink_status_propagation = neutron.plugins.ml2.extensions.uplink_status_propagation:UplinkStatusPropagationExtensionDriver + uplink_status_propagation_updatable = neutron.plugins.ml2.extensions.uplink_status_propagation_updatable:UplinkStatusPropagationUpdatableExtensionDriver tag_ports_during_bulk_creation = neutron.plugins.ml2.extensions.tag_ports_during_bulk_creation:TagPortsDuringBulkCreationExtensionDriver subnet_dns_publish_fixed_ip = neutron.plugins.ml2.extensions.subnet_dns_publish_fixed_ip:SubnetDNSPublishFixedIPExtensionDriver dns_domain_keywords = neutron.plugins.ml2.extensions.dns_domain_keywords:DnsDomainKeywordsExtensionDriver