Add OS::Neutron::RouterL3Agent for router.py

This resource supports part of "agent management and scheduler
extensions" in Neutron. It associates router with l3-agent.

see related-bug: #1245366

Closes-bug: #1259845
Change-Id: I781645e62beb4fe60e064d61ad298834e551142d
This commit is contained in:
Ryo Miki 2013-12-10 17:41:57 +09:00
parent 582cf8f047
commit 54f3f1d60a
2 changed files with 193 additions and 0 deletions

View File

@ -16,6 +16,7 @@
from heat.common import exception
from heat.engine import clients
from heat.engine.resources.neutron import neutron
from heat.engine import properties
from heat.engine import scheduler
if clients.neutronclient is not None:
@ -93,6 +94,16 @@ class RouterInterface(neutron.NeutronResource):
'Description': _('The port id, either '
'subnet_id or port_id should be specified.')}}
def add_dependencies(self, deps):
super(RouterInterface, self).add_dependencies(deps)
# depend on any RouterL3agents in this template with the same router_id
# as this router_id.
for resource in self.stack.itervalues():
if (resource.has_interface('OS::Neutron::RouterL3Agent') and
resource.properties.get('router_id') ==
self.properties.get('router_id')):
deps += (self, resource)
def validate(self):
'''
Validate any of the provided params
@ -157,6 +168,12 @@ class RouterGateway(neutron.NeutronResource):
resource.properties.get('network_id') ==
self.properties.get('network_id')):
deps += (self, resource)
# depend on any RouterL3agents in this template with the same
# router_id as this router_id.
elif (resource.has_interface('OS::Neutron::RouterL3Agent') and
resource.properties.get('router_id') ==
self.properties.get('router_id')):
deps += (self, resource)
def handle_create(self):
router_id = self.properties.get('router_id')
@ -181,6 +198,50 @@ class RouterGateway(neutron.NeutronResource):
raise ex
class RouterL3Agent(neutron.NeutronResource):
properties_schema = {
'router_id': properties.Schema(
properties.Schema.STRING,
description=_('The ID of the router you want to be scheduled by '
'the l3_agent. Note that the default policy setting '
'in Neutron restricts usage of this property to '
'administrative users only.'),
required=True
),
'l3_agent_id': properties.Schema(
properties.Schema.STRING,
description=_('The ID of the l3-agent to schedule the router. '
'Note that the default policy setting in Neutron '
'restricts usage of this property to administrative '
'users only.'),
required=True
)
}
def handle_create(self):
router_id = self.properties['router_id']
l3_agent_id = self.properties['l3_agent_id']
self.neutron().add_router_to_l3_agent(
l3_agent_id, {'router_id': router_id})
self.resource_id_set('%(rtr)s:%(agt)s' %
{'rtr': router_id, 'agt': l3_agent_id})
def handle_delete(self):
if not self.resource_id:
return
client = self.neutron()
router_id, l3_agent_id = self.resource_id.split(':')
try:
client.remove_router_from_l3_agent(
l3_agent_id, router_id)
except NeutronClientException as ex:
# assume 2 patterns about status_code following:
# 404: the router or agent is already gone
# 409: the router isn't scheduled by the l3_agent
if ex.status_code not in (404, 409):
raise ex
def resource_mapping():
if clients.neutronclient is None:
return {}
@ -189,4 +250,5 @@ def resource_mapping():
'OS::Neutron::Router': Router,
'OS::Neutron::RouterInterface': RouterInterface,
'OS::Neutron::RouterGateway': RouterGateway,
'OS::Neutron::RouterL3Agent': RouterL3Agent,
}

View File

@ -124,6 +124,36 @@ neutron_dhcp_agent_template = '''
}
'''
neutron_l3_agent_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test Neutron resources",
"Resources" : {
"router_l3_agent": {
"Type": "OS::Neutron::RouterL3Agent",
"Properties": {
"router_id": "2b0347ab-9e42-434f-8249-702eda4ce7a6",
"l3_agent_id": "5dab1619-9bb0-4e6f-9725-c5e2bfdec434"
}
},
"router_interface": {
"Type": "OS::Neutron::RouterInterface",
"Properties": {
"router_id": "2b0347ab-9e42-434f-8249-702eda4ce7a6",
"subnet_id": "10c69b87-6322-4e5f-9616-fb18ad6547b4"
}
},
"router_gateway": {
"Type": "OS::Neutron::RouterGateway",
"Properties": {
"router_id": "2b0347ab-9e42-434f-8249-702eda4ce7a6",
"network_id": "b3ae63e2-a17b-4a1e-823b-5a082c562725"
}
}
}
}
'''
neutron_floating_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
@ -770,6 +800,10 @@ class NeutronRouterTest(HeatTestCase):
self.m.StubOutWithMock(neutronclient.Client, 'remove_interface_router')
self.m.StubOutWithMock(neutronclient.Client, 'add_gateway_router')
self.m.StubOutWithMock(neutronclient.Client, 'remove_gateway_router')
self.m.StubOutWithMock(neutronclient.Client,
'add_router_to_l3_agent')
self.m.StubOutWithMock(neutronclient.Client,
'remove_router_from_l3_agent')
self.m.StubOutWithMock(router.neutronV20,
'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
@ -1083,6 +1117,103 @@ class NeutronRouterTest(HeatTestCase):
scheduler.TaskRunner(rsrc.delete)()
self.m.VerifyAll()
def test_router_l3_agent(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.add_router_to_l3_agent(
u'5dab1619-9bb0-4e6f-9725-c5e2bfdec434',
{'router_id': u'2b0347ab-9e42-434f-8249-702eda4ce7a6'}
).AndReturn(None)
neutronclient.Client.remove_router_from_l3_agent(
u'5dab1619-9bb0-4e6f-9725-c5e2bfdec434',
u'2b0347ab-9e42-434f-8249-702eda4ce7a6'
).AndReturn(None)
neutronclient.Client.remove_router_from_l3_agent(
u'5dab1619-9bb0-4e6f-9725-c5e2bfdec434',
u'2b0347ab-9e42-434f-8249-702eda4ce7a6'
).AndRaise(qe.NeutronClientException(status_code=404))
self.m.ReplayAll()
t = template_format.parse(neutron_l3_agent_template)
stack = utils.parse_stack(t)
rsrc = router.RouterL3Agent('test_router_l3_agent',
t['Resources']['router_l3_agent'], stack)
# assert the implicit dependency between the l3_agent and the
# interface, and the l3_agent and the gateway
deps = stack.dependencies[stack['router_l3_agent']]
self.assertIn(stack['router_gateway'], deps)
self.assertIn(stack['router_interface'], deps)
# assert the implicit dependency between the router and the interface
deps = stack.dependencies[stack['router_interface']]
self.assertIn(stack['router_gateway'], deps)
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again')
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
self.m.VerifyAll()
def test_router_l3_agent_create_failed(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.add_router_to_l3_agent(
u'5dab1619-9bb0-4e6f-9725-c5e2bfdec434',
{'router_id': u'2b0347ab-9e42-434f-8249-702eda4ce7a6'}
).AndRaise(qe.NeutronClientException(status_code=500))
self.m.ReplayAll()
t = template_format.parse(neutron_l3_agent_template)
stack = utils.parse_stack(t)
rsrc = router.RouterL3Agent('test_router_l3_agent',
t['Resources']['router_l3_agent'], stack)
error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.create))
self.assertEqual(
'NeutronClientException: An unknown exception occurred.',
str(error))
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
self.m.VerifyAll()
def test_router_l3_agent_delete_failed(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.add_router_to_l3_agent(
u'5dab1619-9bb0-4e6f-9725-c5e2bfdec434',
{'router_id': u'2b0347ab-9e42-434f-8249-702eda4ce7a6'}
).AndReturn(None)
neutronclient.Client.remove_router_from_l3_agent(
u'5dab1619-9bb0-4e6f-9725-c5e2bfdec434',
u'2b0347ab-9e42-434f-8249-702eda4ce7a6'
).AndRaise(qe.NeutronClientException(status_code=500))
self.m.ReplayAll()
t = template_format.parse(neutron_l3_agent_template)
stack = utils.parse_stack(t)
rsrc = router.RouterL3Agent('test_router_l3_agent',
t['Resources']['router_l3_agent'], stack)
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
error = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(rsrc.delete))
self.assertEqual(
'NeutronClientException: An unknown exception occurred.',
str(error))
self.assertEqual((rsrc.DELETE, rsrc.FAILED), rsrc.state)
self.m.VerifyAll()
@skipIf(neutronclient is None, 'neutronclient unavailable')
class NeutronFloatingIPTest(HeatTestCase):