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.common import exception
from heat.engine import clients from heat.engine import clients
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
from heat.engine import properties
from heat.engine import scheduler from heat.engine import scheduler
if clients.neutronclient is not None: if clients.neutronclient is not None:
@ -93,6 +94,16 @@ class RouterInterface(neutron.NeutronResource):
'Description': _('The port id, either ' 'Description': _('The port id, either '
'subnet_id or port_id should be specified.')}} '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): def validate(self):
''' '''
Validate any of the provided params Validate any of the provided params
@ -157,6 +168,12 @@ class RouterGateway(neutron.NeutronResource):
resource.properties.get('network_id') == resource.properties.get('network_id') ==
self.properties.get('network_id')): self.properties.get('network_id')):
deps += (self, resource) 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): def handle_create(self):
router_id = self.properties.get('router_id') router_id = self.properties.get('router_id')
@ -181,6 +198,50 @@ class RouterGateway(neutron.NeutronResource):
raise ex 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(): def resource_mapping():
if clients.neutronclient is None: if clients.neutronclient is None:
return {} return {}
@ -189,4 +250,5 @@ def resource_mapping():
'OS::Neutron::Router': Router, 'OS::Neutron::Router': Router,
'OS::Neutron::RouterInterface': RouterInterface, 'OS::Neutron::RouterInterface': RouterInterface,
'OS::Neutron::RouterGateway': RouterGateway, '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 = ''' neutron_floating_template = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
@ -770,6 +800,10 @@ class NeutronRouterTest(HeatTestCase):
self.m.StubOutWithMock(neutronclient.Client, 'remove_interface_router') self.m.StubOutWithMock(neutronclient.Client, 'remove_interface_router')
self.m.StubOutWithMock(neutronclient.Client, 'add_gateway_router') self.m.StubOutWithMock(neutronclient.Client, 'add_gateway_router')
self.m.StubOutWithMock(neutronclient.Client, 'remove_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, self.m.StubOutWithMock(router.neutronV20,
'find_resourceid_by_name_or_id') 'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
@ -1083,6 +1117,103 @@ class NeutronRouterTest(HeatTestCase):
scheduler.TaskRunner(rsrc.delete)() scheduler.TaskRunner(rsrc.delete)()
self.m.VerifyAll() 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') @skipIf(neutronclient is None, 'neutronclient unavailable')
class NeutronFloatingIPTest(HeatTestCase): class NeutronFloatingIPTest(HeatTestCase):