Add timestamp for neutron core resources

Currently, neutron core resources (like net, subnet, port and subnetpool)
do not save time-stamps upon their creation and updation. This
information can be critical for debugging purposes.

This patch introduces a new extension called "timestamp" extending existing
the neutron core resources to allow their creation and modification times
to be record. Now this patch add this resource schema and the functions which
listen db events to add timestamp fields.

APIImpact
DocImpact: Neutron core resources now contain 'timestamp' fields like
           'created_at' and 'updated_at'

Change-Id: I24114b464403435d9c1e1e123d2bc2f37c8fc6ea
Partially-Implements: blueprint add-port-timestamp
This commit is contained in:
ZhaoBo 2016-02-18 13:28:58 +08:00 committed by Armando Migliaccio
parent 6cdee81c36
commit 4c2c983618
11 changed files with 404 additions and 2 deletions

View File

@ -1 +1 @@
2f9e956e7532
3894bccad37f

View File

@ -0,0 +1,37 @@
# Copyright 2015 HuaWei Technologies.
#
# 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_timestamp_to_base_resources
Revision ID: 3894bccad37f
Revises: 2f9e956e7532
Create Date: 2016-03-01 04:19:58.852612
"""
# revision identifiers, used by Alembic.
revision = '3894bccad37f'
down_revision = '2f9e956e7532'
from alembic import op
import sqlalchemy as sa
def upgrade():
for column_name in ['created_at', 'updated_at']:
op.add_column(
'standardattributes',
sa.Column(column_name, sa.DateTime(), nullable=True)
)

View File

@ -79,7 +79,7 @@ class NeutronBaseV2(NeutronBase):
BASEV2 = declarative.declarative_base(cls=NeutronBaseV2)
class StandardAttribute(BASEV2):
class StandardAttribute(BASEV2, models.TimestampMixin):
"""Common table to associate all Neutron API resources.
By having Neutron objects related to this table, we can associate new

View File

@ -0,0 +1,65 @@
# Copyright 2015 HuaWei Technologies.
#
# 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.api import extensions
# Attribute Map
CREATED = 'created_at'
UPDATED = 'updated_at'
TIMESTAMP_BODY = {
CREATED: {'allow_post': False, 'allow_put': False,
'is_visible': True, 'default': None
},
UPDATED: {'allow_post': False, 'allow_put': False,
'is_visible': True, 'default': None
},
}
EXTENDED_ATTRIBUTES_2_0 = {
'networks': TIMESTAMP_BODY,
'subnets': TIMESTAMP_BODY,
'ports': TIMESTAMP_BODY,
'subnetpools': TIMESTAMP_BODY,
}
class Timestamp_core(extensions.ExtensionDescriptor):
"""Extension class supporting timestamp.
This class is used by neutron's extension framework for adding timestamp
to neutron core resources.
"""
@classmethod
def get_name(cls):
return "Time Stamp Fields addition for core resources"
@classmethod
def get_alias(cls):
return "timestamp_core"
@classmethod
def get_description(cls):
return ("This extension can be used for recording "
"create/update timestamps for core resources "
"like port/subnet/network/subnetpools.")
@classmethod
def get_updated(cls):
return "2016-03-01T10:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

View File

@ -42,6 +42,7 @@ EXT_TO_SERVICE_MAPPING = {
DEFAULT_SERVICE_PLUGINS = {
'auto_allocate': 'auto-allocated-topology',
'tag': 'tag',
'timestamp_core': 'timestamp_core',
}
# Service operation status constants

View File

View File

@ -0,0 +1,75 @@
# Copyright 2015 HuaWei Technologies.
#
# 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_log import log
from oslo_utils import timeutils
from sqlalchemy import event
from sqlalchemy import exc as sql_exc
from sqlalchemy.orm import session as se
from neutron._i18n import _LW
from neutron.db import model_base
LOG = log.getLogger(__name__)
class TimeStamp_db_mixin(object):
"""Mixin class to add Time Stamp methods."""
ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
def update_timestamp(self, session, context, instances):
objs_list = session.new.union(session.dirty)
while objs_list:
obj = objs_list.pop()
if hasattr(obj, 'standard_attr') and obj.standard_attr_id:
obj.standard_attr.updated_at = timeutils.utcnow()
def register_db_events(self):
event.listen(model_base.StandardAttribute, 'before_insert',
self._add_timestamp)
event.listen(se.Session, 'before_flush', self.update_timestamp)
def unregister_db_events(self):
self._unregister_db_event(model_base.StandardAttribute,
'before_insert', self._add_timestamp)
self._unregister_db_event(se.Session, 'before_flush',
self.update_timestamp)
def _unregister_db_event(self, listen_obj, listened_event, listen_hander):
try:
event.remove(listen_obj, listened_event, listen_hander)
except sql_exc.InvalidRequestError:
LOG.warning(_LW("No sqlalchemy event for resource %s found"),
listen_obj)
def _format_timestamp(self, resource_db, result):
result['created_at'] = (resource_db.standard_attr.created_at.
strftime(self.ISO8601_TIME_FORMAT))
result['updated_at'] = (resource_db.standard_attr.updated_at.
strftime(self.ISO8601_TIME_FORMAT))
def extend_resource_dict_timestamp(self, plugin_obj,
resource_res, resource_db):
if (resource_db and resource_db.standard_attr.created_at and
resource_db.standard_attr.updated_at):
self._format_timestamp(resource_db, resource_res)
def _add_timestamp(self, mapper, _conn, target):
if not target.created_at and not target.updated_at:
time = timeutils.utcnow()
for field in ['created_at', 'updated_at']:
setattr(target, field, time)
return target

View File

@ -0,0 +1,39 @@
# Copyright 2015 HuaWei Technologies.
#
# 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.api.v2 import attributes
from neutron.db import db_base_plugin_v2
from neutron.services import service_base
from neutron.services.timestamp import timestamp_db as ts_db
class TimeStampPlugin(service_base.ServicePluginBase,
ts_db.TimeStamp_db_mixin):
"""Implements Neutron Timestamp Service plugin."""
supported_extension_aliases = ['timestamp_core']
def __init__(self):
super(TimeStampPlugin, self).__init__()
self.register_db_events()
for resources in [attributes.NETWORKS, attributes.PORTS,
attributes.SUBNETS, attributes.SUBNETPOOLS]:
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
resources, [self.extend_resource_dict_timestamp])
def get_plugin_type(self):
return 'timestamp_core'
def get_plugin_description(self):
return "Neutron core resources timestamp addition support"

View File

@ -0,0 +1,178 @@
# 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 copy
from tempest.lib.common.utils import data_utils
from tempest import test
from neutron.tests.api import base
class TestTimeStamp(base.BaseAdminNetworkTest):
## attributes for subnetpool
min_prefixlen = '28'
max_prefixlen = '31'
_ip_version = 4
subnet_cidr = '10.11.12.0/31'
new_prefix = '10.11.15.0/24'
larger_prefix = '10.11.0.0/16'
@classmethod
def skip_checks(cls):
super(TestTimeStamp, cls).skip_checks()
if not test.is_extension_enabled('timestamp_core', 'network'):
raise cls.skipException("timestamp_core extension not enabled")
@classmethod
def resource_setup(cls):
super(TestTimeStamp, cls).resource_setup()
prefixes = ['10.11.12.0/24']
cls._subnetpool_data = {'min_prefixlen': '29', 'prefixes': prefixes}
def _create_subnetpool(self, is_admin=False, **kwargs):
name = data_utils.rand_name('subnetpool-')
subnetpool_data = copy.deepcopy(self._subnetpool_data)
for key in subnetpool_data.keys():
kwargs[key] = subnetpool_data[key]
return self.create_subnetpool(name=name, is_admin=is_admin, **kwargs)
@test.idempotent_id('462be770-b310-4df9-9c42-773217e4c8b1')
def test_create_network_with_timestamp(self):
network = self.create_network()
# Verifies body contains timestamp fields
self.assertIsNotNone(network['created_at'])
self.assertIsNotNone(network['updated_at'])
@test.idempotent_id('4db5417a-e11c-474d-a361-af00ebef57c5')
def test_update_network_with_timestamp(self):
network = self.create_network()
origin_updated_at = network['updated_at']
update_body = {'name': network['name'] + 'new'}
body = self.admin_client.update_network(network['id'],
**update_body)
updated_network = body['network']
new_updated_at = updated_network['updated_at']
self.assertEqual(network['created_at'], updated_network['created_at'])
# Verify that origin_updated_at is not same with new_updated_at
self.assertIsNot(origin_updated_at, new_updated_at)
@test.idempotent_id('2ac50ab2-7ebd-4e27-b3ce-a9e399faaea2')
def test_show_networks_attribute_with_timestamp(self):
network = self.create_network()
body = self.client.show_network(network['id'])
show_net = body['network']
# verify the timestamp from creation and showed is same
self.assertEqual(network['created_at'],
show_net['created_at'])
self.assertEqual(network['updated_at'],
show_net['updated_at'])
@test.idempotent_id('8ee55186-454f-4b97-9f9f-eb2772ee891c')
def test_create_subnet_with_timestamp(self):
network = self.create_network()
subnet = self.create_subnet(network)
# Verifies body contains timestamp fields
self.assertIsNotNone(subnet['created_at'])
self.assertIsNotNone(subnet['updated_at'])
@test.idempotent_id('a490215a-6f4c-4af9-9a4c-57c41f1c4fa1')
def test_update_subnet_with_timestamp(self):
network = self.create_network()
subnet = self.create_subnet(network)
origin_updated_at = subnet['updated_at']
update_body = {'name': subnet['name'] + 'new'}
body = self.admin_client.update_subnet(subnet['id'],
**update_body)
updated_subnet = body['subnet']
new_updated_at = updated_subnet['updated_at']
self.assertEqual(subnet['created_at'], updated_subnet['created_at'])
# Verify that origin_updated_at is not same with new_updated_at
self.assertIsNot(origin_updated_at, new_updated_at)
@test.idempotent_id('1836a086-e7cf-4141-bf57-0cfe79e8051e')
def test_show_subnet_attribute_with_timestamp(self):
network = self.create_network()
subnet = self.create_subnet(network)
body = self.client.show_subnet(subnet['id'])
show_subnet = body['subnet']
# verify the timestamp from creation and showed is same
self.assertEqual(subnet['created_at'],
show_subnet['created_at'])
self.assertEqual(subnet['updated_at'],
show_subnet['updated_at'])
@test.idempotent_id('e2450a7b-d84f-4600-a093-45e78597bbac')
def test_create_port_with_timestamp(self):
network = self.create_network()
port = self.create_port(network)
# Verifies body contains timestamp fields
self.assertIsNotNone(port['created_at'])
self.assertIsNotNone(port['updated_at'])
@test.idempotent_id('4241e0d3-54b4-46ce-a9a7-093fc764161b')
def test_update_port_with_timestamp(self):
network = self.create_network()
port = self.create_port(network)
origin_updated_at = port['updated_at']
update_body = {'name': port['name'] + 'new'}
body = self.admin_client.update_port(port['id'],
**update_body)
updated_port = body['port']
new_updated_at = updated_port['updated_at']
self.assertEqual(port['created_at'], updated_port['created_at'])
# Verify that origin_updated_at is not same with new_updated_at
self.assertIsNot(origin_updated_at, new_updated_at)
@test.idempotent_id('584c6723-40b6-4f26-81dd-f508f9d9fb51')
def test_show_port_attribute_with_timestamp(self):
network = self.create_network()
port = self.create_port(network)
body = self.client.show_port(port['id'])
show_port = body['port']
# verify the timestamp from creation and showed is same
self.assertEqual(port['created_at'],
show_port['created_at'])
self.assertEqual(port['updated_at'],
show_port['updated_at'])
@test.idempotent_id('87a8b196-4b90-44f0-b7f3-d2057d7d658e')
def test_create_subnetpool_with_timestamp(self):
sp = self._create_subnetpool()
# Verifies body contains timestamp fields
self.assertIsNotNone(sp['created_at'])
self.assertIsNotNone(sp['updated_at'])
@test.idempotent_id('d48c7578-c3d2-4f9b-a7a1-be2008c770a0')
def test_update_subnetpool_with_timestamp(self):
sp = self._create_subnetpool()
origin_updated_at = sp['updated_at']
update_body = {'name': sp['name'] + 'new',
'min_prefixlen': self.min_prefixlen,
'max_prefixlen': self.max_prefixlen}
body = self.client.update_subnetpool(sp['id'], **update_body)
updated_sp = body['subnetpool']
new_updated_at = updated_sp['updated_at']
self.assertEqual(sp['created_at'], updated_sp['created_at'])
# Verify that origin_updated_at is not same with new_updated_at
self.assertIsNot(origin_updated_at, new_updated_at)
@test.idempotent_id('1d3970e6-bcf7-46cd-b7d7-0807759c73b4')
def test_show_subnetpool_attribute_with_timestamp(self):
sp = self._create_subnetpool()
body = self.client.show_subnetpool(sp['id'])
show_sp = body['subnetpool']
# verify the timestamp from creation and showed is same
self.assertEqual(sp['created_at'], show_sp['created_at'])
self.assertEqual(sp['updated_at'], show_sp['updated_at'])

View File

@ -0,0 +1,6 @@
---
prelude: >
Timestamp fields are now added to neutron core resources.
features:
- Add timestamp fields 'created_at', 'updated_at' into neutron core
resources like network, subnet, port and subnetpool.

View File

@ -82,6 +82,7 @@ neutron.service_plugins =
flavors = neutron.services.flavors.flavors_plugin:FlavorsPlugin
auto_allocate = neutron.services.auto_allocate.plugin:Plugin
network_ip_availability = neutron.services.network_ip_availability.plugin:NetworkIPAvailabilityPlugin
timestamp_core = neutron.services.timestamp.timestamp_plugin:TimeStampPlugin
neutron.qos.notification_drivers =
message_queue = neutron.services.qos.notification_drivers.message_queue:RpcQosServiceNotificationDriver
neutron.ml2.type_drivers =