diff --git a/heat/engine/resources/neutron/net.py b/heat/engine/resources/neutron/net.py index 9643964a4..f7ea3fb0b 100644 --- a/heat/engine/resources/neutron/net.py +++ b/heat/engine/resources/neutron/net.py @@ -116,10 +116,55 @@ class Net(neutron.NeutronResource): raise ex +class NetDHCPAgent(neutron.NeutronResource): + properties_schema = { + 'network_id': { + 'Type': 'String', + 'Required': True, + 'Description': _('The ID of the network you want to be scheduled ' + 'by the dhcp_agent. Note that the default policy ' + 'setting in Neutron restricts usage of this ' + 'property to administrative users only.'), + }, + 'dhcp_agent_id': { + 'Type': 'String', + 'Required': True, + 'Description': _('The ID of the dhcp-agent to schedule ' + 'the network. Note that the default policy ' + 'setting in Neutron restricts usage of this ' + 'property to administrative users only.'), + } + } + + def handle_create(self): + network_id = self.properties['network_id'] + dhcp_agent_id = self.properties['dhcp_agent_id'] + self.neutron().add_network_to_dhcp_agent( + dhcp_agent_id, {'network_id': network_id}) + self.resource_id_set('%(net)s:%(agt)s' % + {'net': network_id, 'agt': dhcp_agent_id}) + + def handle_delete(self): + if not self.resource_id: + return + client = self.neutron() + network_id, dhcp_agent_id = self.resource_id.split(':') + try: + client.remove_network_from_dhcp_agent( + dhcp_agent_id, network_id) + except neutron_exp.NeutronClientException as ex: + # assume 2 patterns about status_code following: + # 404: the network or agent is already gone + # 409: the network isn't scheduled by the dhcp_agent + if ex.status_code not in (404, 409): + raise ex + + def resource_mapping(): if clients.neutronclient is None: return {} return { 'OS::Neutron::Net': Net, + 'OS::Neutron::NetDHCPAgent': NetDHCPAgent, } diff --git a/heat/tests/test_neutron.py b/heat/tests/test_neutron.py index 8155edfeb..8e0e0c820 100644 --- a/heat/tests/test_neutron.py +++ b/heat/tests/test_neutron.py @@ -108,6 +108,22 @@ neutron_template = ''' } ''' +neutron_dhcp_agent_template = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "Template to test Neutron resources", + "Resources" : { + "network_dhcp_agent": { + "Type": "OS::Neutron::NetDHCPAgent", + "Properties": { + "network_id": "66a426ef-8b77-4e25-8098-b3a7c0964b93", + "dhcp_agent_id": "9f0df05b-4846-4d3d-971e-a2e1a06b1622" + } + } + } +} +''' + neutron_floating_template = ''' { "AWSTemplateFormatVersion" : "2010-09-09", @@ -257,6 +273,10 @@ class NeutronNetTest(HeatTestCase): self.m.StubOutWithMock(neutronclient.Client, 'delete_network') self.m.StubOutWithMock(neutronclient.Client, 'show_network') self.m.StubOutWithMock(neutronclient.Client, 'update_network') + self.m.StubOutWithMock(neutronclient.Client, + 'add_network_to_dhcp_agent') + self.m.StubOutWithMock(neutronclient.Client, + 'remove_network_from_dhcp_agent') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') utils.setup_dummy_db() @@ -399,6 +419,92 @@ class NeutronNetTest(HeatTestCase): scheduler.TaskRunner(rsrc.delete)() self.m.VerifyAll() + def test_net_dhcp_agent(self): + clients.OpenStackClients.keystone().AndReturn( + fakes.FakeKeystoneClient()) + + neutronclient.Client.add_network_to_dhcp_agent( + u'9f0df05b-4846-4d3d-971e-a2e1a06b1622', + {'network_id': u'66a426ef-8b77-4e25-8098-b3a7c0964b93'} + ).AndReturn(None) + + neutronclient.Client.remove_network_from_dhcp_agent( + u'9f0df05b-4846-4d3d-971e-a2e1a06b1622', + u'66a426ef-8b77-4e25-8098-b3a7c0964b93' + ).AndReturn(None) + + neutronclient.Client.remove_network_from_dhcp_agent( + u'9f0df05b-4846-4d3d-971e-a2e1a06b1622', + u'66a426ef-8b77-4e25-8098-b3a7c0964b93' + ).AndRaise(qe.NeutronClientException(status_code=404)) + + self.m.ReplayAll() + t = template_format.parse(neutron_dhcp_agent_template) + stack = utils.parse_stack(t) + rsrc = net.NetDHCPAgent('test_net_dhcp_agent', + t['Resources']['network_dhcp_agent'], stack) + 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_net_dhcp_agent_create_failed(self): + clients.OpenStackClients.keystone().AndReturn( + fakes.FakeKeystoneClient()) + + neutronclient.Client.add_network_to_dhcp_agent( + u'9f0df05b-4846-4d3d-971e-a2e1a06b1622', + {'network_id': u'66a426ef-8b77-4e25-8098-b3a7c0964b93'} + ).AndRaise(qe.NeutronClientException(status_code=500)) + + self.m.ReplayAll() + t = template_format.parse(neutron_dhcp_agent_template) + stack = utils.parse_stack(t) + rsrc = net.NetDHCPAgent('test_net_dhcp_agent', + t['Resources']['network_dhcp_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_net_dhcp_agent_delete_failed(self): + clients.OpenStackClients.keystone().AndReturn( + fakes.FakeKeystoneClient()) + + neutronclient.Client.add_network_to_dhcp_agent( + u'9f0df05b-4846-4d3d-971e-a2e1a06b1622', + {'network_id': u'66a426ef-8b77-4e25-8098-b3a7c0964b93'} + ).AndReturn(None) + + neutronclient.Client.remove_network_from_dhcp_agent( + u'9f0df05b-4846-4d3d-971e-a2e1a06b1622', + u'66a426ef-8b77-4e25-8098-b3a7c0964b93' + ).AndRaise(qe.NeutronClientException(status_code=500)) + + self.m.ReplayAll() + t = template_format.parse(neutron_dhcp_agent_template) + stack = utils.parse_stack(t) + rsrc = net.NetDHCPAgent('test_net_dhcp_agent', + t['Resources']['network_dhcp_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 NeutronSubnetTest(HeatTestCase):