Merge "Backport Neutron port data plane status"
authorTim Rozet <trozet@redhat.com>
Thu, 20 Jul 2017 19:53:57 +0000 (19:53 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Thu, 20 Jul 2017 19:53:57 +0000 (19:53 +0000)
build/opnfv-environment.yaml
build/overcloud-full.sh
build/patches/neutron_lib_dps.patch [new file with mode: 0644]
build/patches/neutron_openstackclient_dps.patch [new file with mode: 0644]
build/patches/neutron_openstacksdk_dps.patch [new file with mode: 0644]
build/patches/neutron_server_dps.patch [new file with mode: 0644]

index a861312..79b9542 100644 (file)
@@ -11,6 +11,7 @@ parameter_defaults:
   NeutronEnableForceMetadata: true
   NeutronEnableDHCPMetadata: true
   NeutronEnableIsolatedMetadata: true
+  NeutronPluginExtensions: 'qos,port_security,data_plane_status'
   # NeutronVPPAgentPhysnets:
   # NovaSchedulerDefaultFilters:
   # # Kernel arguments, this value will be set to kernel arguments specified
@@ -33,6 +34,13 @@ parameter_defaults:
       nova-os_compute_api:servers:show:host_status:
         key: 'os_compute_api:servers:show:host_status'
         value: 'rule:admin_or_owner'
+    neutron::policy::policies:
+      neutron-admin_or_data_plane_int:
+        key: 'admin_or_data_plane_int'
+        value: 'rule:context_is_admin or role:data_plane_integrator'
+      neutron-update_port:data_plane_status:
+        key: 'update_port:data_plane_status'
+        value: 'rule:admin_or_data_plane_int'
     nova::api::default_floating_pool: 'external'
     # VPP routing node, used for odl-fdio only.
     # value updated via lib/overcloud-deploy-functions.sh
index 5f50c1d..cb1b94b 100755 (executable)
@@ -45,6 +45,7 @@ qemu-img resize overcloud-full_build.qcow2 +900MB
 
 # expand file system to max disk size
 # installing forked apex-puppet-tripleo
+# upload neutron port data plane status
 LIBGUESTFS_BACKEND=direct virt-customize \
     --run-command "xfs_growfs /dev/sda" \
     --upload ${BUILD_DIR}/apex-puppet-tripleo.tar.gz:/etc/puppet/modules \
@@ -62,6 +63,22 @@ LIBGUESTFS_BACKEND=direct virt-customize \
     --upload ${BUILD_DIR}/vsperf.tar.gz:/var/opt \
     --run-command "cd /var/opt && tar xzf vsperf.tar.gz" \
     --run-command "sed -i -E 's/timeout=[0-9]+/timeout=60/g' /usr/share/openstack-puppet/modules/rabbitmq/lib/puppet/provider/rabbitmqctl.rb" \
+    --install patch \
+    --upload ${BUILD_ROOT}/patches/neutron_lib_dps.patch:/usr/lib/python2.7/site-packages/ \
+    --upload ${BUILD_ROOT}/patches/neutron_server_dps.patch:/usr/lib/python2.7/site-packages/ \
+    --upload ${BUILD_ROOT}/patches/neutron_openstacksdk_dps.patch:/usr/lib/python2.7/site-packages/ \
+    --upload ${BUILD_ROOT}/patches/neutron_openstackclient_dps.patch:/usr/lib/python2.7/site-packages/ \
+    -a overcloud-full_build.qcow2
+
+# apply neutron port data plane status patches
+# https://specs.openstack.org/openstack/neutron-specs/specs/backlog/ocata/port-data-plane-status.html
+# Requirement from Doctor project
+# TODO(cgoncalves): code merged in Pike dev cycle. drop from >= OpenStack Pike / > OPNFV Euphrates
+LIBGUESTFS_BACKEND=direct virt-customize \
+    --run-command "cd /usr/lib/python2.7/site-packages/ && patch -p1 < neutron_lib_dps.patch " \
+    --run-command "cd /usr/lib/python2.7/site-packages/ && patch -p1 < neutron_server_dps.patch" \
+    --run-command "cd /usr/lib/python2.7/site-packages/ && patch -p1 < neutron_openstacksdk_dps.patch" \
+    --run-command "cd /usr/lib/python2.7/site-packages/ && patch -p1 < neutron_openstackclient_dps.patch" \
     -a overcloud-full_build.qcow2
 
 # Arch dependent on x86
diff --git a/build/patches/neutron_lib_dps.patch b/build/patches/neutron_lib_dps.patch
new file mode 100644 (file)
index 0000000..fa353ba
--- /dev/null
@@ -0,0 +1,116 @@
+From ee74cb2a5ccdc13e8bf137d7387f01c6b202c150 Mon Sep 17 00:00:00 2001
+From: Carlos Goncalves <carlos.goncalves@neclab.eu>
+Date: Tue, 24 Jan 2017 21:52:27 +0000
+Subject: [PATCH] API definition and reference for data plane status extension
+
+Related-Bug: #1598081
+Related-Bug: #1575146
+
+Partial-Implements: blueprint port-data-plane-status
+
+Change-Id: I04eef902b3310f799b1ce7ea44ed7cf77c74da04
+---
+ neutron_lib/api/definitions/base.py              |  1 +
+ neutron_lib/api/definitions/data_plane_status.py | 78 ++++++++++++++++++++++++
+ 2 files changed, 79 insertions(+)
+ create mode 100644 neutron_lib/api/definitions/data_plane_status.py
+
+diff --git a/neutron_lib/api/definitions/base.py b/neutron_lib/api/definitions/base.py
+index 6fbcbfa..38f183c 100644
+--- a/neutron_lib/api/definitions/base.py
++++ b/neutron_lib/api/definitions/base.py
+@@ -44,6 +44,7 @@ KNOWN_EXTENSIONS = (
+     'auto-allocated-topology',
+     'availability_zone',
+     'binding',
++    'data-plane-status',
+     'default-subnetpools',
+     'dhcp_agent_scheduler',
+     'dns-integration',
+diff --git a/neutron_lib/api/definitions/data_plane_status.py b/neutron_lib/api/definitions/data_plane_status.py
+new file mode 100644
+index 0000000..5bcbf60
+--- /dev/null
++++ b/neutron_lib/api/definitions/data_plane_status.py
+@@ -0,0 +1,78 @@
++# Copyright (c) 2017 NEC Corporation.  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 import constants
++
++
++VALID_VALUES = [None, constants.ACTIVE, constants.DOWN]
++
++# The alias of the extension.
++ALIAS = 'data-plane-status'
++
++# Whether or not this extension is simply signaling behavior to the user
++# or it actively modifies the attribute map.
++IS_SHIM_EXTENSION = False
++
++# Whether the extension is marking the adoption of standardattr model for
++# legacy resources, or introducing new standardattr attributes. False or
++# None if the standardattr model is adopted since the introduction of
++# resource extension.
++# If this is True, the alias for the extension should be prefixed with
++# 'standard-attr-'.
++IS_STANDARD_ATTR_EXTENSION = False
++
++# The name of the extension.
++NAME = 'Port data plane status extension'
++
++# The description of the extension.
++DESCRIPTION = "Expose status of underlying data plane"
++
++# A timestamp of when the extension was introduced.
++UPDATED_TIMESTAMP = "2017-01-24T10:00:00-00:00"
++
++# The name of the resource introduced or being extended.
++RESOURCE_NAME = 'port'
++
++# The plural for the resource introduced or being extended.
++COLLECTION_NAME = 'ports'
++
++# The specific resources and/or attributes for the extension (optional).
++DATA_PLANE_STATUS = 'data_plane_status'
++
++# The resource attribute map for the extension.
++RESOURCE_ATTRIBUTE_MAP = {
++    COLLECTION_NAME: {
++        DATA_PLANE_STATUS: {'allow_post': False, 'allow_put': True,
++                            'default': constants.ATTR_NOT_SPECIFIED,
++                            'validate': {'type:values': VALID_VALUES},
++                            'is_visible': True,
++                            'enforce_policy': True, }
++    },
++}
++
++# The subresource attribute map for the extension.
++SUB_RESOURCE_ATTRIBUTE_MAP = {
++}
++
++# The action map.
++ACTION_MAP = {
++}
++
++# The list of required extensions.
++REQUIRED_EXTENSIONS = [
++]
++
++# The list of optional extensions.
++OPTIONAL_EXTENSIONS = [
++]
+-- 
+2.12.3
+
diff --git a/build/patches/neutron_openstackclient_dps.patch b/build/patches/neutron_openstackclient_dps.patch
new file mode 100644 (file)
index 0000000..6c743f9
--- /dev/null
@@ -0,0 +1,65 @@
+From 1ae904a4912494b3d0ac87f22aaf958129744548 Mon Sep 17 00:00:00 2001
+From: Carlos Goncalves <carlos.goncalves@neclab.eu>
+Date: Wed, 18 Jan 2017 11:16:39 +0000
+Subject: [PATCH] Add 'data_plane_status' option to Port classes
+
+Adds 'data_plane_status' option to SetPort and UnsetPort classes.
+
+Closes-Bug: #1684989
+Change-Id: I26e23b551afb8c37e6babdea1655efb7c5c6873b
+---
+ openstackclient/network/v2/port.py | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
+index 42291bf2..1409a194 100644
+--- a/openstackclient/network/v2/port.py
++++ b/openstackclient/network/v2/port.py
+@@ -632,6 +632,14 @@
+                    "(Specify both --allowed-address and --no-allowed-address"
+                    "to overwrite the current allowed-address pairs)")
+         )
++        parser.add_argument(
++            '--data-plane-status',
++            metavar='<status>',
++            choices=['ACTIVE', 'DOWN'],
++            help=_("Set data plane status of this port (ACTIVE | DOWN). "
++                   "Unset it to None with the 'port unset' command "
++                   "(requires data plane status extension)")
++        )
+         return parser
+
+     def take_action(self, parsed_args):
+@@ -684,6 +692,9 @@
+         elif parsed_args.no_allowed_address_pair:
+             attrs['allowed_address_pairs'] = []
+
++        if parsed_args.data_plane_status:
++            attrs['data_plane_status'] = parsed_args.data_plane_status
++
+         client.update_port(obj, **attrs)
+
+
+@@ -756,6 +767,11 @@
+                    "[,mac-address=<mac-address>] (repeat option to set "
+                    "multiple allowed-address pairs)")
+         )
++        parser.add_argument(
++            '--data-plane-status',
++            action='store_true',
++            help=_("Clear existing information of data plane status")
++        )
+
+         return parser
+
+@@ -805,6 +821,8 @@
+                 msg = _("Port does not contain allowed-address-pair %s") % addr
+                 raise exceptions.CommandError(msg)
+             attrs['allowed_address_pairs'] = tmp_addr_pairs
++        if parsed_args.data_plane_status:
++            attrs['data_plane_status'] = None
+
+         if attrs:
+             client.update_port(obj, **attrs)
+-- 
+2.12.3
diff --git a/build/patches/neutron_openstacksdk_dps.patch b/build/patches/neutron_openstacksdk_dps.patch
new file mode 100644 (file)
index 0000000..4fd6d94
--- /dev/null
@@ -0,0 +1,29 @@
+From a98d3ada2a4d51bd5fbd676fe3306871ad8228eb Mon Sep 17 00:00:00 2001
+From: Carlos Goncalves <carlos.goncalves@neclab.eu>
+Date: Thu, 20 Apr 2017 17:52:43 +0000
+Subject: [PATCH] Add data plane status support to Network Port obj
+
+Added 'data_plane_status' parameter to Port class.
+
+Partial-Bug: #1684989
+Change-Id: I716ee25d1e7e4f81319f66b7f7457db243b4ffe3
+---
+ openstack/network/v2/port.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/openstack/network/v2/port.py b/openstack/network/v2/port.py
+index 6234ea45..e98de374 100644
+--- a/openstack/network/v2/port.py
++++ b/openstack/network/v2/port.py
+@@ -67,6 +67,8 @@ class Port(resource.Resource):
+     binding_vnic_type = resource.Body('binding:vnic_type')
+     #: Timestamp when the port was created.
+     created_at = resource.Body('created_at')
++    #: Underlying data plane status of this port.
++    data_plane_status = resource.Body('data_plane_status')
+     #: The port description.
+     description = resource.Body('description')
+     #: Device ID of this port.
+-- 
+2.12.3
+
diff --git a/build/patches/neutron_server_dps.patch b/build/patches/neutron_server_dps.patch
new file mode 100644 (file)
index 0000000..6136aec
--- /dev/null
@@ -0,0 +1,393 @@
+From 89de63de05e296af583032cb17a3d76b4b4d6a40 Mon Sep 17 00:00:00 2001
+From: Carlos Goncalves <carlos.goncalves@neclab.eu>
+Date: Mon, 23 Jan 2017 19:53:04 +0000
+Subject: [PATCH] Port data plane status extension implementation
+
+Implements the port data plane status extension. Third parties
+can report via Neutron API issues in the underlying data plane
+affecting connectivity from/to Neutron ports.
+
+Supported statuses:
+  - None: no status being reported; default value
+  - ACTIVE: all is up and running
+  - DOWN: no traffic can flow from/to the Neutron port
+
+Setting attribute available to admin or any user with specific role
+(default role: data_plane_integrator).
+
+ML2 extension driver loaded on request via configuration:
+
+  [ml2]
+  extension_drivers = data_plane_status
+
+Related-Bug: #1598081
+Related-Bug: #1575146
+
+DocImpact: users can get status of the underlying port data plane;
+attribute writable by admin users and users granted the
+'data-plane-integrator' role.
+APIImpact: port now has data_plane_status attr, set on port update
+
+Implements: blueprint port-data-plane-status
+
+Depends-On: I04eef902b3310f799b1ce7ea44ed7cf77c74da04
+Change-Id: Ic9e1e3ed9e3d4b88a4292114f4cb4192ac4b3502
+---
+ neutron/db/data_plane_status_db.py                 | 48 ++++++++++++++++++++++
+ .../alembic_migrations/versions/EXPAND_HEAD        |  2 +-
+ .../804a3c76314c_add_data_plane_status_to_port.py  | 39 ++++++++++++++++++
+ neutron/db/models/data_plane_status.py             | 34 +++++++++++++++
+ neutron/extensions/data_plane_status.py            | 47 +++++++++++++++++++++
+ .../objects/port/extensions/data_plane_status.py   | 37 +++++++++++++++++
+ neutron/objects/ports.py                           | 14 ++++++-
+ .../plugins/ml2/extensions/data_plane_status.py    | 41 ++++++++++++++++++
+ 8 files changed, 260 insertions(+), 2 deletions(-)
+ create mode 100644 neutron/db/data_plane_status_db.py
+ create mode 100644 neutron/db/migration/alembic_migrations/versions/pike/expand/804a3c76314c_add_data_plane_status_to_port.py
+ create mode 100644 neutron/db/models/data_plane_status.py
+ create mode 100644 neutron/extensions/data_plane_status.py
+ create mode 100644 neutron/objects/port/extensions/data_plane_status.py
+ create mode 100644 neutron/plugins/ml2/extensions/data_plane_status.py
+
+diff --git a/neutron/db/data_plane_status_db.py b/neutron/db/data_plane_status_db.py
+new file mode 100644
+index 000000000..4e5c23aef
+--- /dev/null
++++ b/neutron/db/data_plane_status_db.py
+@@ -0,0 +1,48 @@
++# Copyright (c) 2017 NEC Corporation.  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 data_plane_status as dps_lib
++
++from neutron.objects.port.extensions import data_plane_status as dps_obj
++
++
++class DataPlaneStatusMixin(object):
++    """Mixin class to add data plane status to a port"""
++
++    def _process_create_port_data_plane_status(self, context, data, res):
++        obj = dps_obj.PortDataPlaneStatus(context, port_id=res['id'],
++            data_plane_status=data[dps_lib.DATA_PLANE_STATUS])
++        obj.create()
++        res[dps_lib.DATA_PLANE_STATUS] = data[dps_lib.DATA_PLANE_STATUS]
++
++    def _process_update_port_data_plane_status(self, context, data,
++                                               res):
++        if dps_lib.DATA_PLANE_STATUS not in data:
++            return
++
++        obj = dps_obj.PortDataPlaneStatus.get_object(context,
++                                                     port_id=res['id'])
++        if obj:
++            obj.data_plane_status = data[dps_lib.DATA_PLANE_STATUS]
++            obj.update()
++            res[dps_lib.DATA_PLANE_STATUS] = data[dps_lib.DATA_PLANE_STATUS]
++        else:
++            self._process_create_port_data_plane_status(context, data, res)
++
++    def _extend_port_data_plane_status(self, port_res, port_db):
++        port_res[dps_lib.DATA_PLANE_STATUS] = None
++
++        if port_db.get(dps_lib.DATA_PLANE_STATUS):
++            port_res[dps_lib.DATA_PLANE_STATUS] = (
++                        port_db[dps_lib.DATA_PLANE_STATUS].data_plane_status)
+diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD
+index 1c625bc83..8c1796ba3 100644
+--- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD
++++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD
+@@ -1 +1 @@
+-a9c43481023c
++804a3c76314c
+diff --git a/neutron/db/migration/alembic_migrations/versions/pike/expand/804a3c76314c_add_data_plane_status_to_port.py b/neutron/db/migration/alembic_migrations/versions/pike/expand/804a3c76314c_add_data_plane_status_to_port.py
+new file mode 100644
+index 000000000..bd4d1472b
+--- /dev/null
++++ b/neutron/db/migration/alembic_migrations/versions/pike/expand/804a3c76314c_add_data_plane_status_to_port.py
+@@ -0,0 +1,39 @@
++# Copyright 2017 OpenStack Foundation
++#
++#    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.
++#
++
++"""Add data_plane_status to Port
++
++Revision ID: 804a3c76314c
++Revises: a9c43481023c
++Create Date: 2017-01-17 13:51:45.737987
++
++"""
++
++# revision identifiers, used by Alembic.
++revision = '804a3c76314c'
++down_revision = 'a9c43481023c'
++
++from alembic import op
++import sqlalchemy as sa
++
++
++def upgrade():
++    op.create_table('portdataplanestatuses',
++                    sa.Column('port_id', sa.String(36),
++                              sa.ForeignKey('ports.id',
++                                            ondelete="CASCADE"),
++                              primary_key=True, index=True),
++                    sa.Column('data_plane_status', sa.String(length=16),
++                              nullable=True))
+diff --git a/neutron/db/models/data_plane_status.py b/neutron/db/models/data_plane_status.py
+new file mode 100644
+index 000000000..ada10af55
+--- /dev/null
++++ b/neutron/db/models/data_plane_status.py
+@@ -0,0 +1,34 @@
++# Copyright (c) 2017 NEC Corporation.  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.db import model_base
++import sqlalchemy as sa
++from sqlalchemy import orm
++
++from neutron.db import models_v2
++
++
++class PortDataPlaneStatus(model_base.BASEV2):
++    __tablename__ = 'portdataplanestatuses'
++
++    port_id = sa.Column(sa.String(36),
++                        sa.ForeignKey('ports.id', ondelete="CASCADE"),
++                        primary_key=True, index=True)
++    data_plane_status = sa.Column(sa.String(16), nullable=True)
++    port = orm.relationship(
++        models_v2.Port, load_on_pending=True,
++        backref=orm.backref("data_plane_status",
++                            lazy='joined', uselist=False,
++                            cascade='delete'))
++    revises_on_change = ('port', )
+diff --git a/neutron/extensions/data_plane_status.py b/neutron/extensions/data_plane_status.py
+new file mode 100644
+index 000000000..8e225e670
+--- /dev/null
++++ b/neutron/extensions/data_plane_status.py
+@@ -0,0 +1,47 @@
++# Copyright (c) 2017 NEC Corporation.  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 data_plane_status
++from neutron_lib.api import extensions
++
++
++class Data_plane_status(extensions.ExtensionDescriptor):
++
++    @classmethod
++    def get_name(cls):
++        return data_plane_status.NAME
++
++    @classmethod
++    def get_alias(cls):
++        return data_plane_status.ALIAS
++
++    @classmethod
++    def get_description(cls):
++        return data_plane_status.DESCRIPTION
++
++    @classmethod
++    def get_updated(cls):
++        return data_plane_status.UPDATED_TIMESTAMP
++
++    def get_required_extensions(self):
++        return data_plane_status.REQUIRED_EXTENSIONS or []
++
++    def get_optional_extensions(self):
++        return data_plane_status.OPTIONAL_EXTENSIONS or []
++
++    def get_extended_resources(self, version):
++        if version == "2.0":
++            return data_plane_status.RESOURCE_ATTRIBUTE_MAP
++        else:
++            return {}
+diff --git a/neutron/objects/port/extensions/data_plane_status.py b/neutron/objects/port/extensions/data_plane_status.py
+new file mode 100644
+index 000000000..bd5858123
+--- /dev/null
++++ b/neutron/objects/port/extensions/data_plane_status.py
+@@ -0,0 +1,37 @@
++# Copyright (c) 2017 NEC Corporation.  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_versionedobjects import base as obj_base
++from oslo_versionedobjects import fields as obj_fields
++
++from neutron.db.models import data_plane_status as db_models
++from neutron.objects import base
++from neutron.objects import common_types
++
++
++@obj_base.VersionedObjectRegistry.register
++class PortDataPlaneStatus(base.NeutronDbObject):
++    # Version 1.0: Initial version
++    VERSION = "1.0"
++
++    db_model = db_models.PortDataPlaneStatus
++
++    primary_keys = ['port_id']
++
++    fields = {
++        'port_id': common_types.UUIDField(),
++        'data_plane_status': obj_fields.StringField(),
++    }
++
++    foreign_keys = {'Port': {'port_id': 'id'}}
+diff --git a/neutron/objects/ports.py b/neutron/objects/ports.py
+index bbddb4dde..dd83db147 100644
+--- a/neutron/objects/ports.py
++++ b/neutron/objects/ports.py
+@@ -13,6 +13,7 @@
+ #    under the License.
+ import netaddr
++from oslo_utils import versionutils
+ from oslo_versionedobjects import base as obj_base
+ from oslo_versionedobjects import fields as obj_fields
+@@ -206,7 +207,8 @@ class PortDNS(base.NeutronDbObject):
+ @obj_base.VersionedObjectRegistry.register
+ class Port(base.NeutronDbObject):
+     # Version 1.0: Initial version
+-    VERSION = '1.0'
++    # Version 1.1: Add data_plane_status field
++    VERSION = '1.1'
+     db_model = models_v2.Port
+@@ -227,6 +229,9 @@ class Port(base.NeutronDbObject):
+         'binding': obj_fields.ObjectField(
+             'PortBinding', nullable=True
+         ),
++        'data_plane_status': obj_fields.ObjectField(
++            'PortDataPlaneStatus', nullable=True
++        ),
+         'dhcp_options': obj_fields.ListOfObjectsField(
+             'ExtraDhcpOpt', nullable=True
+         ),
+@@ -260,6 +265,7 @@ class Port(base.NeutronDbObject):
+         'allowed_address_pairs',
+         'binding',
+         'binding_levels',
++        'data_plane_status',
+         'dhcp_options',
+         'distributed_binding',
+         'dns',
+@@ -374,3 +380,9 @@ class Port(base.NeutronDbObject):
+         else:
+             self.qos_policy_id = None
+         self.obj_reset_changes(['qos_policy_id'])
++
++    def obj_make_compatible(self, primitive, target_version):
++        _target_version = versionutils.convert_version_to_tuple(target_version)
++
++        if _target_version < (1, 1):
++            primitive.pop('data_plane_status')
+diff --git a/neutron/plugins/ml2/extensions/data_plane_status.py b/neutron/plugins/ml2/extensions/data_plane_status.py
+new file mode 100644
+index 000000000..850dafab6
+--- /dev/null
++++ b/neutron/plugins/ml2/extensions/data_plane_status.py
+@@ -0,0 +1,41 @@
++# Copyright (c) 2017 NEC Corporation.  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 data_plane_status as dps_lib
++from oslo_log import log as logging
++
++from neutron.db import data_plane_status_db as dps_db
++from neutron.plugins.ml2 import driver_api as api
++
++LOG = logging.getLogger(__name__)
++
++
++class DataPlaneStatusExtensionDriver(api.ExtensionDriver,
++                                     dps_db.DataPlaneStatusMixin):
++    _supported_extension_alias = 'data-plane-status'
++
++    def initialize(self):
++        LOG.info("DataPlaneStatusExtensionDriver initialization complete")
++
++    @property
++    def extension_alias(self):
++        return self._supported_extension_alias
++
++    def process_update_port(self, plugin_context, data, result):
++        if dps_lib.DATA_PLANE_STATUS in data:
++            self._process_update_port_data_plane_status(plugin_context,
++                                                        data, result)
++
++    def extend_port_dict(self, session, db_data, result):
++        self._extend_port_data_plane_status(result, db_data)
+-- 
+2.12.3
+