Merge "[OVN] Add revision number methods"
This commit is contained in:
commit
7ba82ebcdf
166
neutron/db/ovn_revision_numbers_db.py
Normal file
166
neutron/db/ovn_revision_numbers_db.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# Copyright 2017 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.db import api as db_api
|
||||||
|
from neutron_lib import exceptions as n_exc
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
from sqlalchemy.orm import exc
|
||||||
|
|
||||||
|
from neutron.db.models import l3 # noqa
|
||||||
|
from neutron.db.models import ovn as ovn_models
|
||||||
|
from neutron.db.models import securitygroup # noqa
|
||||||
|
from neutron.db import models_v2 # noqa
|
||||||
|
from neutron.db import standard_attr
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
STD_ATTR_MAP = standard_attr.get_standard_attr_resource_model_map()
|
||||||
|
|
||||||
|
# NOTE(ralonsoh): to be moved to neutron-lib
|
||||||
|
TYPE_NETWORKS = 'networks'
|
||||||
|
TYPE_PORTS = 'ports'
|
||||||
|
TYPE_SECURITY_GROUP_RULES = 'security_group_rules'
|
||||||
|
TYPE_ROUTERS = 'routers'
|
||||||
|
TYPE_ROUTER_PORTS = 'router_ports'
|
||||||
|
TYPE_SECURITY_GROUPS = 'security_groups'
|
||||||
|
TYPE_FLOATINGIPS = 'floatingips'
|
||||||
|
TYPE_SUBNETS = 'subnets'
|
||||||
|
TYPES_OVN = (TYPE_NETWORKS, TYPE_PORTS, TYPE_SECURITY_GROUP_RULES,
|
||||||
|
TYPE_ROUTERS, TYPE_ROUTER_PORTS, TYPE_SECURITY_GROUPS,
|
||||||
|
TYPE_FLOATINGIPS, TYPE_SUBNETS)
|
||||||
|
INITIAL_REV_NUM = -1
|
||||||
|
|
||||||
|
|
||||||
|
# 1:2 mapping for OVN, neutron router ports are simple ports, but
|
||||||
|
# for OVN we handle LSP & LRP objects
|
||||||
|
if STD_ATTR_MAP:
|
||||||
|
STD_ATTR_MAP[TYPE_ROUTER_PORTS] = STD_ATTR_MAP[TYPE_PORTS]
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(ralonsoh): to be moved to neutron-lib
|
||||||
|
class StandardAttributeIDNotFound(n_exc.NeutronException):
|
||||||
|
message = 'Standard attribute ID not found for %(resource_uuid)s'
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(ralonsoh): to be moved to neutron-lib
|
||||||
|
class UnknownResourceType(n_exc.NeutronException):
|
||||||
|
message = 'Uknown resource type: %(resource_type)s'
|
||||||
|
|
||||||
|
|
||||||
|
def get_revision_number(resource, resource_type):
|
||||||
|
"""Get the resource's revision number based on its type."""
|
||||||
|
if resource_type in TYPES_OVN:
|
||||||
|
return resource['revision_number']
|
||||||
|
raise UnknownResourceType(resource_type=resource_type)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_standard_attr_id(context, resource_uuid, resource_type):
|
||||||
|
try:
|
||||||
|
row = context.session.query(STD_ATTR_MAP[resource_type]).filter_by(
|
||||||
|
id=resource_uuid).one()
|
||||||
|
return row.standard_attr_id
|
||||||
|
except exc.NoResultFound:
|
||||||
|
raise StandardAttributeIDNotFound(resource_uuid=resource_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@db_api.retry_if_session_inactive()
|
||||||
|
def create_initial_revision(context, resource_uuid, resource_type,
|
||||||
|
revision_number=INITIAL_REV_NUM,
|
||||||
|
may_exist=False):
|
||||||
|
LOG.debug('create_initial_revision uuid=%s, type=%s, rev=%s',
|
||||||
|
resource_uuid, resource_type, revision_number)
|
||||||
|
db_func = context.session.merge if may_exist else context.session.add
|
||||||
|
with db_api.CONTEXT_WRITER.using(context):
|
||||||
|
std_attr_id = _get_standard_attr_id(
|
||||||
|
context, resource_uuid, resource_type)
|
||||||
|
row = ovn_models.OVNRevisionNumbers(
|
||||||
|
resource_uuid=resource_uuid, resource_type=resource_type,
|
||||||
|
standard_attr_id=std_attr_id, revision_number=revision_number)
|
||||||
|
db_func(row)
|
||||||
|
context.session.flush()
|
||||||
|
|
||||||
|
|
||||||
|
@db_api.retry_if_session_inactive()
|
||||||
|
def delete_revision(context, resource_uuid, resource_type):
|
||||||
|
LOG.debug('delete_revision(%s)', resource_uuid)
|
||||||
|
with db_api.CONTEXT_WRITER.using(context):
|
||||||
|
row = context.session.query(ovn_models.OVNRevisionNumbers).filter_by(
|
||||||
|
resource_uuid=resource_uuid,
|
||||||
|
resource_type=resource_type).one_or_none()
|
||||||
|
if row:
|
||||||
|
context.session.delete(row)
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_revision_row_exist(context, resource, resource_type):
|
||||||
|
"""Ensure the revision row exists.
|
||||||
|
|
||||||
|
Ensure the revision row exist before we try to bump its revision
|
||||||
|
number. This method is part of the migration plan to deal with
|
||||||
|
resources that have been created prior to the database sync work
|
||||||
|
getting merged.
|
||||||
|
"""
|
||||||
|
# TODO(lucasagomes): As the docstring says, this method was created to
|
||||||
|
# deal with objects that already existed before the sync work. I believe
|
||||||
|
# that we can remove this method after few development cycles. Or,
|
||||||
|
# if we decide to make a migration script as well.
|
||||||
|
with db_api.CONTEXT_WRITER.using(context):
|
||||||
|
if not context.session.query(ovn_models.OVNRevisionNumbers).filter_by(
|
||||||
|
resource_uuid=resource['id'],
|
||||||
|
resource_type=resource_type).one_or_none():
|
||||||
|
LOG.warning(
|
||||||
|
'No revision row found for %(res_uuid)s (type: '
|
||||||
|
'%(res_type)s) when bumping the revision number. '
|
||||||
|
'Creating one.', {'res_uuid': resource['id'],
|
||||||
|
'res_type': resource_type})
|
||||||
|
create_initial_revision(context, resource['id'], resource_type)
|
||||||
|
|
||||||
|
|
||||||
|
@db_api.retry_if_session_inactive()
|
||||||
|
def get_revision_row(context, resource_uuid):
|
||||||
|
try:
|
||||||
|
with db_api.CONTEXT_READER.using(context):
|
||||||
|
return context.session.query(
|
||||||
|
ovn_models.OVNRevisionNumbers).filter_by(
|
||||||
|
resource_uuid=resource_uuid).one()
|
||||||
|
except exc.NoResultFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@db_api.retry_if_session_inactive()
|
||||||
|
def bump_revision(context, resource, resource_type):
|
||||||
|
revision_number = get_revision_number(resource, resource_type)
|
||||||
|
with db_api.CONTEXT_WRITER.using(context):
|
||||||
|
_ensure_revision_row_exist(context, resource, resource_type)
|
||||||
|
std_attr_id = _get_standard_attr_id(
|
||||||
|
context, resource['id'], resource_type)
|
||||||
|
row = context.session.merge(ovn_models.OVNRevisionNumbers(
|
||||||
|
standard_attr_id=std_attr_id, resource_uuid=resource['id'],
|
||||||
|
resource_type=resource_type))
|
||||||
|
if revision_number < row.revision_number:
|
||||||
|
LOG.debug(
|
||||||
|
'Skip bumping the revision number for %(res_uuid)s (type: '
|
||||||
|
'%(res_type)s) to %(rev_num)d. A higher version is already '
|
||||||
|
'registered in the database (%(new_rev)d)',
|
||||||
|
{'res_type': resource_type, 'res_uuid': resource['id'],
|
||||||
|
'rev_num': revision_number, 'new_rev': row.revision_number})
|
||||||
|
return
|
||||||
|
row.revision_number = revision_number
|
||||||
|
context.session.merge(row)
|
||||||
|
LOG.info('Successfully bumped revision number for resource '
|
||||||
|
'%(res_uuid)s (type: %(res_type)s) to %(rev_num)d',
|
||||||
|
{'res_uuid': resource['id'], 'res_type': resource_type,
|
||||||
|
'rev_num': revision_number})
|
95
neutron/tests/unit/db/test_ovn_revision_numbers_db.py
Normal file
95
neutron/tests/unit/db/test_ovn_revision_numbers_db.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Copyright 2019 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 mock
|
||||||
|
|
||||||
|
from neutron_lib import context
|
||||||
|
from neutron_lib.db import api as db_api
|
||||||
|
from oslo_db import exception as db_exc
|
||||||
|
|
||||||
|
from neutron.db.models import ovn as ovn_models
|
||||||
|
from neutron.db import ovn_revision_numbers_db as ovn_rn_db
|
||||||
|
from neutron.tests.unit.db import test_db_base_plugin_v2
|
||||||
|
|
||||||
|
|
||||||
|
class TestRevisionNumber(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestRevisionNumber, self).setUp()
|
||||||
|
self.ctx = context.get_admin_context()
|
||||||
|
self.addCleanup(self._delete_objs)
|
||||||
|
res = self._create_network(fmt=self.fmt, name='net',
|
||||||
|
admin_state_up=True)
|
||||||
|
self.net = self.deserialize(self.fmt, res)['network']
|
||||||
|
|
||||||
|
def _delete_objs(self):
|
||||||
|
with db_api.CONTEXT_WRITER.using(self.ctx):
|
||||||
|
self.ctx.session.query(
|
||||||
|
ovn_models.OVNRevisionNumbers).delete()
|
||||||
|
|
||||||
|
def _create_initial_revision(self, resource_uuid, resource_type,
|
||||||
|
revision_number=ovn_rn_db.INITIAL_REV_NUM,
|
||||||
|
may_exist=False):
|
||||||
|
with self.ctx.session.begin(subtransactions=True):
|
||||||
|
ovn_rn_db.create_initial_revision(
|
||||||
|
self.ctx, resource_uuid, resource_type,
|
||||||
|
revision_number=revision_number, may_exist=may_exist)
|
||||||
|
|
||||||
|
def test_bump_revision(self):
|
||||||
|
self._create_initial_revision(self.net['id'], ovn_rn_db.TYPE_NETWORKS)
|
||||||
|
self.net['revision_number'] = 123
|
||||||
|
ovn_rn_db.bump_revision(self.ctx, self.net,
|
||||||
|
ovn_rn_db.TYPE_NETWORKS)
|
||||||
|
row = ovn_rn_db.get_revision_row(self.ctx, self.net['id'])
|
||||||
|
self.assertEqual(123, row.revision_number)
|
||||||
|
|
||||||
|
def test_bump_older_revision(self):
|
||||||
|
self._create_initial_revision(self.net['id'], ovn_rn_db.TYPE_NETWORKS,
|
||||||
|
revision_number=124)
|
||||||
|
self.net['revision_number'] = 1
|
||||||
|
ovn_rn_db.bump_revision(self.ctx, self.net,
|
||||||
|
ovn_rn_db.TYPE_NETWORKS)
|
||||||
|
row = ovn_rn_db.get_revision_row(self.ctx, self.net['id'])
|
||||||
|
self.assertEqual(124, row.revision_number)
|
||||||
|
|
||||||
|
@mock.patch.object(ovn_rn_db.LOG, 'warning')
|
||||||
|
def test_bump_revision_row_not_found(self, mock_log):
|
||||||
|
self.net['revision_number'] = 123
|
||||||
|
ovn_rn_db.bump_revision(self.ctx, self.net, ovn_rn_db.TYPE_NETWORKS)
|
||||||
|
# Assert the revision number wasn't bumped
|
||||||
|
row = ovn_rn_db.get_revision_row(self.ctx, self.net['id'])
|
||||||
|
self.assertEqual(123, row.revision_number)
|
||||||
|
self.assertIn('No revision row found for', mock_log.call_args[0][0])
|
||||||
|
|
||||||
|
def test_delete_revision(self):
|
||||||
|
self._create_initial_revision(self.net['id'], ovn_rn_db.TYPE_NETWORKS)
|
||||||
|
ovn_rn_db.delete_revision(self.ctx, self.net['id'],
|
||||||
|
ovn_rn_db.TYPE_NETWORKS)
|
||||||
|
row = ovn_rn_db.get_revision_row(self.ctx, self.net['id'])
|
||||||
|
self.assertIsNone(row)
|
||||||
|
|
||||||
|
def test_create_initial_revision_may_exist_duplicated_entry(self):
|
||||||
|
args = (self.net['id'], ovn_rn_db.TYPE_NETWORKS)
|
||||||
|
self._create_initial_revision(*args)
|
||||||
|
|
||||||
|
# Assert DBDuplicateEntry is raised when may_exist is False (default)
|
||||||
|
self.assertRaises(db_exc.DBDuplicateEntry,
|
||||||
|
self._create_initial_revision, *args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._create_initial_revision(*args, may_exist=True)
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
self.fail("create_initial_revision shouldn't raise "
|
||||||
|
"DBDuplicateEntry when may_exist is True")
|
Loading…
Reference in New Issue
Block a user