From 54f3f1d60af880dfd6f4ad8d6ade73566405bc02 Mon Sep 17 00:00:00 2001 From: Ryo Miki Date: Tue, 10 Dec 2013 17:41:57 +0900 Subject: [PATCH] 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 --- heat/engine/resources/neutron/router.py | 62 +++++++++++ heat/tests/test_neutron.py | 131 ++++++++++++++++++++++++ 2 files changed, 193 insertions(+) diff --git a/heat/engine/resources/neutron/router.py b/heat/engine/resources/neutron/router.py index c59d28dcb7..6d23ebbda0 100644 --- a/heat/engine/resources/neutron/router.py +++ b/heat/engine/resources/neutron/router.py @@ -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, } diff --git a/heat/tests/test_neutron.py b/heat/tests/test_neutron.py index 8e0e0c8206..89bdb3b3ad 100644 --- a/heat/tests/test_neutron.py +++ b/heat/tests/test_neutron.py @@ -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):