from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import model_base
-from neutron.db import models_v2
from neutron.extensions import l3agentscheduler
cfg.CONF.register_opts(L3_AGENTS_SCHEDULER_OPTS)
-class RouterL3AgentBinding(model_base.BASEV2, models_v2.HasId):
+class RouterL3AgentBinding(model_base.BASEV2):
"""Represents binding between neutron routers and L3 agents."""
router_id = sa.Column(sa.String(36),
- sa.ForeignKey("routers.id", ondelete='CASCADE'))
+ sa.ForeignKey("routers.id", ondelete='CASCADE'),
+ primary_key=True)
l3_agent = orm.relation(agents_db.Agent)
l3_agent_id = sa.Column(sa.String(36),
- sa.ForeignKey("agents.id",
- ondelete='CASCADE'))
+ sa.ForeignKey("agents.id", ondelete='CASCADE'),
+ primary_key=True)
class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
--- /dev/null
+# Copyright 2014 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 constraint for routerid
+
+Revision ID: 31d7f831a591
+Revises: 37f322991f59
+Create Date: 2014-02-26 06:47:16.494393
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '31d7f831a591'
+down_revision = '37f322991f59'
+
+from alembic import op
+import sqlalchemy as sa
+
+TABLE_NAME = 'routerl3agentbindings'
+PK_NAME = 'pk_routerl3agentbindings'
+
+fk_names = {'postgresql':
+ {'router_id':
+ 'routerl3agentbindings_router_id_fkey',
+ 'l3_agent_id':
+ 'routerl3agentbindings_l3_agent_id_fkey'},
+ 'mysql':
+ {'router_id':
+ 'routerl3agentbindings_ibfk_2',
+ 'l3_agent_id':
+ 'routerl3agentbindings_ibfk_1'}}
+
+
+def upgrade(active_plugins=None, options=None):
+ # In order to sanitize the data during migration,
+ # the current records in the table need to be verified
+ # and all the duplicate records which violate the PK
+ # constraint need to be removed.
+ context = op.get_context()
+ if context.bind.dialect.name == 'postgresql':
+ op.execute('DELETE FROM %(table)s WHERE id in ('
+ 'SELECT %(table)s.id FROM %(table)s LEFT OUTER JOIN '
+ '(SELECT MIN(id) as id, router_id, l3_agent_id '
+ ' FROM %(table)s GROUP BY router_id, l3_agent_id) AS temp '
+ 'ON %(table)s.id = temp.id WHERE temp.id is NULL);'
+ % {'table': TABLE_NAME})
+ else:
+ op.execute('DELETE %(table)s FROM %(table)s LEFT OUTER JOIN '
+ '(SELECT MIN(id) as id, router_id, l3_agent_id '
+ ' FROM %(table)s GROUP BY router_id, l3_agent_id) AS temp '
+ 'ON %(table)s.id = temp.id WHERE temp.id is NULL;'
+ % {'table': TABLE_NAME})
+
+ op.drop_column(TABLE_NAME, 'id')
+
+ op.create_primary_key(
+ name=PK_NAME,
+ table_name=TABLE_NAME,
+ cols=['router_id', 'l3_agent_id']
+ )
+
+
+def downgrade(active_plugins=None, options=None):
+
+ context = op.get_context()
+ dialect = context.bind.dialect.name
+
+ # Drop the existed foreign key constraints
+ # In order to perform primary key changes
+ op.drop_constraint(
+ name=fk_names[dialect]['l3_agent_id'],
+ table_name=TABLE_NAME,
+ type_='foreignkey'
+ )
+ op.drop_constraint(
+ name=fk_names[dialect]['router_id'],
+ table_name=TABLE_NAME,
+ type_='foreignkey'
+ )
+
+ op.drop_constraint(
+ name=PK_NAME,
+ table_name=TABLE_NAME,
+ type_='primary'
+ )
+
+ op.add_column(
+ TABLE_NAME,
+ sa.Column('id', sa.String(32))
+ )
+
+ # Restore the foreign key constraints
+ op.create_foreign_key(
+ name=fk_names[dialect]['router_id'],
+ source=TABLE_NAME,
+ referent='routers',
+ local_cols=['router_id'],
+ remote_cols=['id'],
+ ondelete='CASCADE'
+ )
+
+ op.create_foreign_key(
+ name=fk_names[dialect]['l3_agent_id'],
+ source=TABLE_NAME,
+ referent='agents',
+ local_cols=['l3_agent_id'],
+ remote_cols=['id'],
+ ondelete='CASCADE'
+ )
+
+ op.create_primary_key(
+ name=PK_NAME,
+ table_name=TABLE_NAME,
+ cols=['id']
+ )
-37f322991f59
\ No newline at end of file
+31d7f831a591
import abc
import random
+from oslo.db import exception as db_exc
import six
from sqlalchemy.orm import exc
from sqlalchemy import sql
def bind_router(self, context, router_id, chosen_agent):
"""Bind the router to the l3 agent which has been chosen."""
- with context.session.begin(subtransactions=True):
- binding = l3_agentschedulers_db.RouterL3AgentBinding()
- binding.l3_agent = chosen_agent
- binding.router_id = router_id
- context.session.add(binding)
- LOG.debug(_('Router %(router_id)s is scheduled to '
- 'L3 agent %(agent_id)s'),
- {'router_id': router_id,
- 'agent_id': chosen_agent.id})
+ try:
+ with context.session.begin(subtransactions=True):
+ binding = l3_agentschedulers_db.RouterL3AgentBinding()
+ binding.l3_agent = chosen_agent
+ binding.router_id = router_id
+ context.session.add(binding)
+ except db_exc.DBDuplicateEntry:
+ LOG.debug('Router %(router_id)s has already been scheduled '
+ 'to L3 agent %(agent_id)s.',
+ {'agent_id': chosen_agent.id,
+ 'router_id': router_id})
+ return
+
+ LOG.debug('Router %(router_id)s is scheduled to L3 agent '
+ '%(agent_id)s', {'router_id': router_id,
+ 'agent_id': chosen_agent.id})
class ChanceScheduler(L3Scheduler):
from neutron.extensions import l3 as ext_l3
from neutron import manager
from neutron.openstack.common import timeutils
+from neutron.scheduler import l3_agent_scheduler
from neutron.tests.unit import test_db_plugin
from neutron.tests.unit import test_l3_plugin
agent_db = self.plugin.get_agents_db(self.adminContext,
filters={'host': [HOST]})
self.agent_id1 = agent_db[0].id
+ self.agent1 = agent_db[0]
callback.report_state(self.adminContext,
agent_state={'agent_state': SECOND_L3_AGENT},
router['router']['id'], subnet['subnet']['network_id'])
self._delete('routers', router['router']['id'])
+ def _test_schedule_bind_router(self, agent, router):
+ ctx = self.adminContext
+ session = ctx.session
+ db = l3_agentschedulers_db.RouterL3AgentBinding
+ scheduler = l3_agent_scheduler.ChanceScheduler()
+
+ rid = router['router']['id']
+ scheduler.bind_router(ctx, rid, agent)
+ results = (session.query(db).filter_by(router_id=rid).all())
+ self.assertTrue(len(results) > 0)
+ self.assertIn(agent.id, [bind.l3_agent_id for bind in results])
+
+ def test_bind_new_router(self):
+ router = self._make_router(self.fmt,
+ tenant_id=str(uuid.uuid4()),
+ name='r1')
+ with mock.patch.object(l3_agent_scheduler.LOG, 'debug') as flog:
+ self._test_schedule_bind_router(self.agent1, router)
+ self.assertEqual(1, flog.call_count)
+ args, kwargs = flog.call_args
+ self.assertIn('is scheduled', args[0])
+
+ def test_bind_existing_router(self):
+ router = self._make_router(self.fmt,
+ tenant_id=str(uuid.uuid4()),
+ name='r2')
+ self._test_schedule_bind_router(self.agent1, router)
+ with mock.patch.object(l3_agent_scheduler.LOG, 'debug') as flog:
+ self._test_schedule_bind_router(self.agent1, router)
+ self.assertEqual(1, flog.call_count)
+ args, kwargs = flog.call_args
+ self.assertIn('has already been scheduled', args[0])
+
class L3AgentChanceSchedulerTestCase(L3SchedulerTestCase):