From c761a08473b8f56a8d80ac9d773fbbb88ed6502e Mon Sep 17 00:00:00 2001 From: sindhudevale Date: Fri, 26 Aug 2016 19:49:50 +0000 Subject: [PATCH] OVO for L3HARouter This patch introduces and integrates OVO for L3 HA Router. Co-Authored-By: Nguyen Phuong An Co-Authored-By: Vu Cong Tuan Change-Id: I3463921dec415dd073503ab9470588193d08ce87 Partially-Implements: blueprint adopt-oslo-versioned-objects-for-db --- neutron/common/constants.py | 2 +- neutron/db/l3_hamode_db.py | 78 +++++++---------- neutron/objects/common_types.py | 4 + neutron/objects/l3_hamode.py | 83 +++++++++++++++++++ neutron/scheduler/l3_agent_scheduler.py | 2 +- neutron/tests/tools.py | 4 + neutron/tests/unit/db/test_l3_hamode_db.py | 18 ++-- neutron/tests/unit/objects/test_base.py | 1 + neutron/tests/unit/objects/test_l3_hamode.py | 74 +++++++++++++++++ neutron/tests/unit/objects/test_objects.py | 3 + .../unit/plugins/ml2/drivers/l2pop/test_db.py | 13 ++- .../unit/scheduler/test_l3_agent_scheduler.py | 10 +-- 12 files changed, 220 insertions(+), 72 deletions(-) create mode 100644 neutron/objects/l3_hamode.py create mode 100644 neutron/tests/unit/objects/test_l3_hamode.py diff --git a/neutron/common/constants.py b/neutron/common/constants.py index 4ad780d0261..0bc56c2c10d 100644 --- a/neutron/common/constants.py +++ b/neutron/common/constants.py @@ -36,7 +36,7 @@ HA_SUBNET_NAME = 'HA subnet tenant %s' HA_PORT_NAME = 'HA port tenant %s' HA_ROUTER_STATE_ACTIVE = 'active' HA_ROUTER_STATE_STANDBY = 'standby' - +VALID_HA_STATES = (HA_ROUTER_STATE_ACTIVE, HA_ROUTER_STATE_STANDBY) PAGINATION_INFINITE = 'infinite' SORT_DIRECTION_ASC = 'asc' diff --git a/neutron/db/l3_hamode_db.py b/neutron/db/l3_hamode_db.py index 983bb12987d..76489f5bc2c 100644 --- a/neutron/db/l3_hamode_db.py +++ b/neutron/db/l3_hamode_db.py @@ -25,6 +25,7 @@ from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import exceptions as n_exc +from neutron_lib.objects import exceptions as obj_base from oslo_config import cfg from oslo_db import exception as db_exc from oslo_log import helpers as log_helpers @@ -43,10 +44,11 @@ from neutron.db import api as db_api from neutron.db.availability_zone import router as router_az_db from neutron.db import l3_dvr_db from neutron.db.l3_dvr_db import is_distributed_router -from neutron.db.models import agent as agent_model from neutron.db.models import l3ha as l3ha_model from neutron.extensions import l3 from neutron.extensions import l3_ext_ha_mode as l3_ha +from neutron.objects import base +from neutron.objects import l3_hamode from neutron.objects import router as l3_obj from neutron.plugins.common import utils as p_utils @@ -110,19 +112,16 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, return inst def get_ha_network(self, context, tenant_id): - return (context.session.query(l3ha_model.L3HARouterNetwork). - filter(l3ha_model.L3HARouterNetwork.tenant_id == tenant_id). - first()) + pager = base.Pager(limit=1) + results = l3_hamode.L3HARouterNetwork.get_objects( + context, _pager=pager, project_id=tenant_id) + return results.pop() if results else None def _get_allocated_vr_id(self, context, network_id): - with context.session.begin(subtransactions=True): - query = (context.session.query( - l3ha_model.L3HARouterVRIdAllocation). - filter(l3ha_model.L3HARouterVRIdAllocation.network_id == - network_id)) - - allocated_vr_ids = set(a.vr_id for a in query) - set([0]) + vr_id_objs = l3_hamode.L3HARouterVRIdAllocation.get_objects( + context, network_id=network_id) + allocated_vr_ids = set(a.vr_id for a in vr_id_objs) - set([0]) return allocated_vr_ids @db_api.retry_if_session_inactive() @@ -152,11 +151,11 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, if not available_vr_ids: raise l3_ha.NoVRIDAvailable(router_id=router_id) - allocation = l3ha_model.L3HARouterVRIdAllocation() - allocation.network_id = network_id - allocation.vr_id = available_vr_ids.pop() + allocation = l3_hamode.L3HARouterVRIdAllocation( + context, network_id=network_id, + vr_id=available_vr_ids.pop()) + allocation.create() - context.session.add(allocation) router_db.extra_attributes.ha_vr_id = allocation.vr_id LOG.debug( "Router %(router_id)s has been allocated a ha_vr_id " @@ -165,7 +164,7 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, return allocation.vr_id - except db_exc.DBDuplicateEntry: + except obj_base.NeutronDbObjectDuplicateEntry: LOG.info("Attempt %(count)s to allocate a VRID in the " "network %(network)s for the router %(router)s", {'count': count, 'network': network_id, @@ -177,10 +176,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, @db_api.retry_if_session_inactive() def _delete_vr_id_allocation(self, context, ha_network, vr_id): - with context.session.begin(subtransactions=True): - context.session.query( - l3ha_model.L3HARouterVRIdAllocation).filter_by( - network_id=ha_network.network_id, vr_id=vr_id).delete() + l3_hamode.L3HARouterVRIdAllocation.delete_objects( + context, network_id=ha_network.network_id, vr_id=vr_id) def _create_ha_subnet(self, context, network_id, tenant_id): args = {'network_id': network_id, @@ -195,21 +192,18 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, def _create_ha_network_tenant_binding(self, context, tenant_id, network_id): - with context.session.begin(): - ha_network = l3ha_model.L3HARouterNetwork( - tenant_id=tenant_id, network_id=network_id) - context.session.add(ha_network) + ha_network = l3_hamode.L3HARouterNetwork( + context, project_id=tenant_id, network_id=network_id) + ha_network.create() # we need to check if someone else just inserted at exactly the # same time as us because there is no constrain in L3HARouterNetwork # that prevents multiple networks per tenant - with context.session.begin(subtransactions=True): - items = (context.session.query(l3ha_model.L3HARouterNetwork). - filter_by(tenant_id=tenant_id).all()) - if len(items) > 1: - # we need to throw an error so our network is deleted - # and the process is started over where the existing - # network will be selected. - raise db_exc.DBDuplicateEntry(columns=['tenant_id']) + if l3_hamode.L3HARouterNetwork.count( + context, project_id=tenant_id) > 1: + # we need to throw an error so our network is deleted + # and the process is started over where the existing + # network will be selected. + raise db_exc.DBDuplicateEntry(columns=['tenant_id']) return ha_network def _add_ha_network_settings(self, network): @@ -545,24 +539,14 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, def get_ha_router_port_bindings(self, context, router_ids, host=None): if not router_ids: return [] - query = context.session.query(l3ha_model.L3HARouterAgentPortBinding) - - if host: - query = query.join(agent_model.Agent).filter( - agent_model.Agent.host == host) - - query = query.filter( - l3ha_model.L3HARouterAgentPortBinding.router_id.in_(router_ids)) - - return query.all() + return ( + l3_hamode.L3HARouterAgentPortBinding.get_l3ha_filter_host_router( + context, router_ids, host)) @staticmethod def _check_router_agent_ha_binding(context, router_id, agent_id): - query = context.session.query(l3ha_model.L3HARouterAgentPortBinding) - query = query.filter( - l3ha_model.L3HARouterAgentPortBinding.router_id == router_id, - l3ha_model.L3HARouterAgentPortBinding.l3_agent_id == agent_id) - return query.first() is not None + return l3_hamode.L3HARouterAgentPortBinding.objects_exist( + context, router_id=router_id, l3_agent_id=agent_id) def _get_bindings_and_update_router_state_for_dead_agents(self, context, router_id): diff --git a/neutron/objects/common_types.py b/neutron/objects/common_types.py index a7f4c0a8b7b..7baba4cabb7 100644 --- a/neutron/objects/common_types.py +++ b/neutron/objects/common_types.py @@ -29,6 +29,10 @@ from neutron.common import utils from neutron.plugins.common import constants as plugin_constants +class HARouterEnumField(obj_fields.AutoTypedField): + AUTO_TYPE = obj_fields.Enum(valid_values=constants.VALID_HA_STATES) + + class IPV6ModeEnumField(obj_fields.AutoTypedField): AUTO_TYPE = obj_fields.Enum(valid_values=lib_constants.IPV6_MODES) diff --git a/neutron/objects/l3_hamode.py b/neutron/objects/l3_hamode.py new file mode 100644 index 00000000000..e50aa4cbde4 --- /dev/null +++ b/neutron/objects/l3_hamode.py @@ -0,0 +1,83 @@ +# Copyright (c) 2016 Intel Corporation. +# +# 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.common import constants +from neutron.db.models import agent as agent_model +from neutron.db.models import l3ha +from neutron.objects import base +from neutron.objects import common_types + + +@obj_base.VersionedObjectRegistry.register +class L3HARouterAgentPortBinding(base.NeutronDbObject): + # Version 1.0: Initial version + VERSION = '1.0' + + db_model = l3ha.L3HARouterAgentPortBinding + + fields = { + 'port_id': common_types.UUIDField(), + 'router_id': common_types.UUIDField(), + 'l3_agent_id': common_types.UUIDField(nullable=True), + 'state': common_types.HARouterEnumField( + default=constants.HA_ROUTER_STATE_STANDBY), + } + + primary_keys = ['port_id'] + fields_no_update = ['router_id', 'port_id', 'l3_agent_id'] + + @classmethod + def get_l3ha_filter_host_router(cls, context, router_ids, host): + query = context.session.query(l3ha.L3HARouterAgentPortBinding) + + if host: + query = query.join(agent_model.Agent).filter( + agent_model.Agent.host == host) + + query = query.filter( + l3ha.L3HARouterAgentPortBinding.router_id.in_(router_ids)) + return query.all() + + +@obj_base.VersionedObjectRegistry.register +class L3HARouterNetwork(base.NeutronDbObject): + # Version 1.0: Initial version + VERSION = '1.0' + + db_model = l3ha.L3HARouterNetwork + + fields = { + 'network_id': common_types.UUIDField(), + 'project_id': obj_fields.StringField(), + } + + primary_keys = ['network_id', 'project_id'] + + +@obj_base.VersionedObjectRegistry.register +class L3HARouterVRIdAllocation(base.NeutronDbObject): + # Version 1.0: Initial version + VERSION = '1.0' + + db_model = l3ha.L3HARouterVRIdAllocation + + fields = { + 'network_id': common_types.UUIDField(), + 'vr_id': obj_fields.IntegerField() + } + + primary_keys = ['network_id', 'vr_id'] diff --git a/neutron/scheduler/l3_agent_scheduler.py b/neutron/scheduler/l3_agent_scheduler.py index d30a2c03ea2..3ea81b7c418 100644 --- a/neutron/scheduler/l3_agent_scheduler.py +++ b/neutron/scheduler/l3_agent_scheduler.py @@ -268,7 +268,7 @@ class L3Scheduler(object): def _add_port_from_net_and_ensure_vr_id(self, plugin, ctxt, router_db, tenant_id, ha_net): plugin._ensure_vr_id(ctxt, router_db, ha_net) - return plugin.add_ha_port(ctxt, router_db.id, ha_net.network.id, + return plugin.add_ha_port(ctxt, router_db.id, ha_net.network_id, tenant_id) def create_ha_port_and_bind(self, plugin, context, router_id, diff --git a/neutron/tests/tools.py b/neutron/tests/tools.py index d3b1a4bf55d..9fdbdc2f1f9 100644 --- a/neutron/tests/tools.py +++ b/neutron/tests/tools.py @@ -289,6 +289,10 @@ def get_random_flow_direction(): return random.choice(n_const.VALID_DIRECTIONS) +def get_random_ha_states(): + return random.choice(n_const.VALID_HA_STATES) + + def get_random_ether_type(): return random.choice(n_const.VALID_ETHERTYPES) diff --git a/neutron/tests/unit/db/test_l3_hamode_db.py b/neutron/tests/unit/db/test_l3_hamode_db.py index 38c42ae3a1f..d568f617b36 100644 --- a/neutron/tests/unit/db/test_l3_hamode_db.py +++ b/neutron/tests/unit/db/test_l3_hamode_db.py @@ -23,6 +23,7 @@ from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import context from neutron_lib import exceptions as n_exc +from neutron_lib.objects import exceptions from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from oslo_config import cfg @@ -43,6 +44,7 @@ from neutron.db.models import l3ha as l3ha_model from neutron.extensions import external_net from neutron.extensions import l3 from neutron.extensions import l3_ext_ha_mode +from neutron.objects import l3_hamode from neutron.scheduler import l3_agent_scheduler from neutron.services.revisions import revision_plugin from neutron.tests.common import helpers @@ -169,10 +171,8 @@ class L3HATestCase(L3HATestFramework): def test_get_l3_bindings_hosting_router_with_ha_states_not_scheduled(self): router = self._create_router(ha=False) # Check that there no L3 agents scheduled for this router - res = self.admin_ctx.session.query( - l3ha_model.L3HARouterAgentPortBinding).filter( - l3ha_model.L3HARouterAgentPortBinding.router_id == router['id'] - ).all() + res = l3_hamode.L3HARouterAgentPortBinding.get_objects( + self.admin_ctx, router_id=router['id']) self.assertEqual([], [r.agent for r in res]) bindings = self.plugin.get_l3_bindings_hosting_router_with_ha_states( self.admin_ctx, router['id']) @@ -589,7 +589,7 @@ class L3HATestCase(L3HATestFramework): def test_create_ha_network_binding_failure_rolls_back_network(self): networks_before = self.core_plugin.get_networks(self.admin_ctx) - with mock.patch.object(l3ha_model, 'L3HARouterNetwork', + with mock.patch.object(l3_hamode, 'L3HARouterNetwork', side_effect=ValueError): self.assertRaises(ValueError, self.plugin._create_ha_network, self.admin_ctx, _uuid()) @@ -688,7 +688,8 @@ class L3HATestCase(L3HATestFramework): router['tenant_id']) self.plugin._create_ha_network_tenant_binding( self.admin_ctx, 't1', network['network_id']) - with testtools.ExpectedException(db_exc.DBDuplicateEntry): + with testtools.ExpectedException( + exceptions.NeutronDbObjectDuplicateEntry): self.plugin._create_ha_network_tenant_binding( self.admin_ctx, 't1', network['network_id']) @@ -947,9 +948,8 @@ class L3HATestCase(L3HATestFramework): router1 = self._create_router() states = {router1['id']: 'active'} with mock.patch.object(self.plugin, 'get_ha_router_port_bindings'): - (self.admin_ctx.session.query( - l3ha_model.L3HARouterAgentPortBinding). - filter_by(router_id=router1['id']).delete()) + (l3_hamode.L3HARouterAgentPortBinding.delete_objects( + self.admin_ctx, router_id=router1['id'])) self.plugin.update_routers_states( self.admin_ctx, states, self.agent1['host']) diff --git a/neutron/tests/unit/objects/test_base.py b/neutron/tests/unit/objects/test_base.py index ddb0ff71769..6d172d5bc58 100644 --- a/neutron/tests/unit/objects/test_base.py +++ b/neutron/tests/unit/objects/test_base.py @@ -452,6 +452,7 @@ FIELD_TYPE_VALUE_GENERATOR_MAP = { common_types.EtherTypeEnumField: tools.get_random_ether_type, common_types.FloatingIPStatusEnumField: tools.get_random_floatingip_status, common_types.FlowDirectionEnumField: tools.get_random_flow_direction, + common_types.HARouterEnumField: tools.get_random_ha_states, common_types.IpamAllocationStatusEnumField: tools.get_random_ipam_status, common_types.IPNetworkField: tools.get_random_ip_network, common_types.IPNetworkPrefixLenField: tools.get_random_prefixlen, diff --git a/neutron/tests/unit/objects/test_l3_hamode.py b/neutron/tests/unit/objects/test_l3_hamode.py new file mode 100644 index 00000000000..57bcfd92eeb --- /dev/null +++ b/neutron/tests/unit/objects/test_l3_hamode.py @@ -0,0 +1,74 @@ +# Copyright (c) 2016 Intel Corporation. +# +# 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.objects import l3_hamode +from neutron.tests.unit.objects import test_base as base +from neutron.tests.unit import testlib_api + + +class L3HARouterAgentPortBindingIfaceObjectTestCase( + base.BaseObjectIfaceTestCase): + + _test_class = l3_hamode.L3HARouterAgentPortBinding + + +class L3HARouterAgentPortBindingDbObjectTestCase(base.BaseDbObjectTestCase, + testlib_api.SqlTestCase): + + _test_class = l3_hamode.L3HARouterAgentPortBinding + + def setUp(self): + super(L3HARouterAgentPortBindingDbObjectTestCase, + self).setUp() + _network_id = self._create_test_network_id() + + def get_port(): + return self._create_test_port_id(network_id=_network_id) + + self.update_obj_fields({'port_id': get_port, + 'router_id': self._create_test_router_id, + 'l3_agent_id': self._create_test_agent_id}) + + +class L3HARouterNetworkIfaceObjectTestCase(base.BaseObjectIfaceTestCase): + + _test_class = l3_hamode.L3HARouterNetwork + + +class L3HARouterNetworkDbObjectTestCase(base.BaseDbObjectTestCase, + testlib_api.SqlTestCase): + + _test_class = l3_hamode.L3HARouterNetwork + + def setUp(self): + super(L3HARouterNetworkDbObjectTestCase, self).setUp() + network = self._create_test_network() + self.update_obj_fields({'network_id': network.id}) + + +class L3HARouterVRIdAllocationIfaceObjectTestCase( + base.BaseObjectIfaceTestCase): + + _test_class = l3_hamode.L3HARouterVRIdAllocation + + +class L3HARouterVRIdAllocationDbObjectTestCase(base.BaseDbObjectTestCase, + testlib_api.SqlTestCase): + + _test_class = l3_hamode.L3HARouterVRIdAllocation + + def setUp(self): + super(L3HARouterVRIdAllocationDbObjectTestCase, self).setUp() + network = self._create_test_network() + self.update_obj_fields({'network_id': network.id}) diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 4c1b4cc0126..e4e3c451c37 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -50,6 +50,9 @@ object_data = { 'IpamAllocation': '1.0-ace65431abd0a7be84cc4a5f32d034a3', 'IpamAllocationPool': '1.0-c4fa1460ed1b176022ede7af7d1510d5', 'IpamSubnet': '1.0-713de401682a70f34891e13af645fa08', + 'L3HARouterAgentPortBinding': '1.0-d1d7ee13f35d56d7e225def980612ee5', + 'L3HARouterNetwork': '1.0-87acea732853f699580179a94d2baf91', + 'L3HARouterVRIdAllocation': '1.0-37502aebdbeadc4f9e3bd5e9da714ab9', 'MeteringLabel': '1.0-cc4b620a3425222447cbe459f62de533', 'MeteringLabelRule': '1.0-b5c5717e7bab8d1af1623156012a5842', 'Log': '1.0-6391351c0f34ed34375a19202f361d24', diff --git a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_db.py b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_db.py index 2209770a33c..7ac15af0cc0 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_db.py +++ b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_db.py @@ -20,8 +20,8 @@ from oslo_utils import uuidutils from neutron.common import constants as n_const from neutron.db.models import l3 as l3_models -from neutron.db.models import l3ha as l3ha_model from neutron.db import models_v2 +from neutron.objects import l3_hamode from neutron.objects import network as network_obj from neutron.objects import router as l3_objs from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db @@ -128,13 +128,10 @@ class TestL2PopulationDBTestCase(testlib_api.SqlTestCase): if network_id == TEST_HA_NETWORK_ID: agent = self.get_l3_agent_by_host(host) - haport_bindings_cls = l3ha_model.L3HARouterAgentPortBinding - habinding_kwarg = {'port_id': port_id, - 'router_id': device_id, - 'l3_agent_id': agent['id'], - 'state': kwargs.get('host_state', - n_const.HA_ROUTER_STATE_ACTIVE)} - self.ctx.session.add(haport_bindings_cls(**habinding_kwarg)) + l3_hamode.L3HARouterAgentPortBinding( + self.ctx, port_id=port_id, router_id=device_id, + l3_agent_id=agent['id'], state=kwargs.get( + 'host_state', n_const.HA_ROUTER_STATE_ACTIVE)).create() def test_get_distributed_active_network_ports(self): self._setup_port_binding( diff --git a/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py b/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py index 56abd752d90..f5c2dff4b3e 100644 --- a/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py +++ b/neutron/tests/unit/scheduler/test_l3_agent_scheduler.py @@ -39,10 +39,10 @@ from neutron.db import l3_hamode_db from neutron.db import l3_hascheduler_db from neutron.db.models import agent as agent_model from neutron.db.models import l3agent as rb_model -from neutron.db.models import l3ha as l3ha_model from neutron.extensions import l3 from neutron.extensions import l3agentscheduler as l3agent from neutron import manager +from neutron.objects import l3_hamode from neutron.objects import l3agent as rb_obj from neutron.scheduler import l3_agent_scheduler from neutron.tests import base @@ -1615,11 +1615,9 @@ class L3AgentSchedulerDbMixinTestCase(L3HATestCaseMixin): agent = agents.pop() self.plugin.remove_router_from_l3_agent( self.adminContext, agent.id, router['id']) - session = self.adminContext.session - db = l3ha_model.L3HARouterAgentPortBinding - results = session.query(db).filter_by( - router_id=router['id']) - results = [binding.l3_agent_id for binding in results.all()] + objs = l3_hamode.L3HARouterAgentPortBinding.get_objects( + self.adminContext, router_id=router['id']) + results = [binding.l3_agent_id for binding in objs] self.assertNotIn(agent.id, results) def test_add_ha_interface_to_l3_agent(self):