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:
parent
6cdee81c36
commit
4c2c983618
@ -1 +1 @@
|
||||
2f9e956e7532
|
||||
3894bccad37f
|
||||
|
@ -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)
|
||||
)
|
@ -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
|
||||
|
65
neutron/extensions/timestamp_core.py
Normal file
65
neutron/extensions/timestamp_core.py
Normal 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 {}
|
@ -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
|
||||
|
0
neutron/services/timestamp/__init__.py
Normal file
0
neutron/services/timestamp/__init__.py
Normal file
75
neutron/services/timestamp/timestamp_db.py
Normal file
75
neutron/services/timestamp/timestamp_db.py
Normal 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
|
39
neutron/services/timestamp/timestamp_plugin.py
Normal file
39
neutron/services/timestamp/timestamp_plugin.py
Normal 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"
|
178
neutron/tests/api/test_timestamp.py
Normal file
178
neutron/tests/api/test_timestamp.py
Normal 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'])
|
@ -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.
|
@ -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 =
|
||||
|
Loading…
Reference in New Issue
Block a user