diff --git a/heat/engine/resources/neutron/net.py b/heat/engine/resources/neutron/net.py index 756da90b73..f04a584ca9 100644 --- a/heat/engine/resources/neutron/net.py +++ b/heat/engine/resources/neutron/net.py @@ -27,8 +27,10 @@ logger = logging.getLogger(__name__) class Net(neutron.NeutronResource): PROPERTIES = ( NAME, VALUE_SPECS, ADMIN_STATE_UP, TENANT_ID, SHARED, + DHCP_AGENT_IDS, ) = ( 'name', 'value_specs', 'admin_state_up', 'tenant_id', 'shared', + 'dhcp_agent_ids', ) properties_schema = { @@ -67,6 +69,13 @@ class Net(neutron.NeutronResource): default=False, update_allowed=True ), + DHCP_AGENT_IDS: properties.Schema( + properties.Schema.LIST, + _('The IDs 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.'), + update_allowed=True + ), } attributes_schema = { @@ -84,9 +93,15 @@ class Net(neutron.NeutronResource): props = self.prepare_properties( self.properties, self.physical_resource_name()) + + dhcp_agent_ids = props.pop(self.DHCP_AGENT_IDS, None) + net = self.neutron().create_network({'network': props})['network'] self.resource_id_set(net['id']) + if dhcp_agent_ids: + self._replace_dhcp_agents(dhcp_agent_ids) + def _show_resource(self): return self.neutron().show_network( self.resource_id)['network'] @@ -107,7 +122,16 @@ class Net(neutron.NeutronResource): def handle_update(self, json_snippet, tmpl_diff, prop_diff): props = self.prepare_update_properties(json_snippet) - self.neutron().update_network(self.resource_id, {'network': props}) + dhcp_agent_ids = props.pop(self.DHCP_AGENT_IDS, None) + + if self.DHCP_AGENT_IDS in prop_diff: + if dhcp_agent_ids is not None: + self._replace_dhcp_agents(dhcp_agent_ids) + del prop_diff[self.DHCP_AGENT_IDS] + + if len(prop_diff) > 0: + self.neutron().update_network( + self.resource_id, {'network': props}) def check_update_complete(self, *args): attributes = self._show_resource() @@ -119,59 +143,31 @@ class Net(neutron.NeutronResource): isinstance(ex, neutron_exp.NetworkNotFoundClient)): raise ex + def _replace_dhcp_agents(self, dhcp_agent_ids): + ret = self.neutron().list_dhcp_agent_hosting_networks( + self.resource_id) + old = set([agent['id'] for agent in ret['agents']]) + new = set(dhcp_agent_ids) -class NetDHCPAgent(neutron.NeutronResource): - PROPERTIES = ( - NETWORK_ID, DHCP_AGENT_ID, - ) = ( - 'network_id', 'dhcp_agent_id', - ) + for dhcp_agent_id in new - old: + try: + self.neutron().add_network_to_dhcp_agent( + dhcp_agent_id, {'network_id': self.resource_id}) + except neutron_exp.NeutronClientException as ex: + # if 409 is happened, the agent is already associated. + if ex.status_code != 409: + raise ex - properties_schema = { - NETWORK_ID: properties.Schema( - properties.Schema.STRING, - _('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.'), - required=True - ), - DHCP_AGENT_ID: properties.Schema( - properties.Schema.STRING, - _('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.'), - required=True - ), - } - - def handle_create(self): - network_id = self.properties[self.NETWORK_ID] - dhcp_agent_id = self.properties[self.DHCP_AGENT_ID] - try: - self.neutron().add_network_to_dhcp_agent( - dhcp_agent_id, {'network_id': network_id}) - except neutron_exp.NeutronClientException as ex: - # if 409 is happened, the agent is already associated. - if ex.status_code != 409: - raise ex - 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 + for dhcp_agent_id in old - new: + try: + self.neutron().remove_network_from_dhcp_agent( + dhcp_agent_id, self.resource_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 class NetworkConstraint(object): @@ -199,5 +195,4 @@ def resource_mapping(): return { 'OS::Neutron::Net': Net, - 'OS::Neutron::NetDHCPAgent': NetDHCPAgent, } diff --git a/heat/tests/test_neutron.py b/heat/tests/test_neutron.py index a0a2045da5..a758532f5b 100644 --- a/heat/tests/test_neutron.py +++ b/heat/tests/test_neutron.py @@ -46,7 +46,10 @@ neutron_template = ''' "Properties": { "name": "the_network", "tenant_id": "c1210485b2424d48804aad5d39c61b8f", - "shared": true + "shared": true, + "dhcp_agent_ids": [ + "28c25a04-3f73-45a7-a2b4-59e183943ddc" + ] } }, "unnamed_network": { @@ -133,22 +136,6 @@ provider_network_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_external_gateway_template = ''' { "AWSTemplateFormatVersion" : "2010-09-09", @@ -341,6 +328,8 @@ class NeutronNetTest(HeatTestCase): 'add_network_to_dhcp_agent') self.m.StubOutWithMock(neutronclient.Client, 'remove_network_from_dhcp_agent') + self.m.StubOutWithMock(neutronclient.Client, + 'list_dhcp_agent_hosting_networks') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') utils.setup_dummy_db() @@ -371,6 +360,15 @@ class NeutronNetTest(HeatTestCase): "id": "fc68ea2c-b60b-4b4f-bd82-94ec81110766" }}) + neutronclient.Client.list_dhcp_agent_hosting_networks( + 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' + ).AndReturn({"agents": []}) + + neutronclient.Client.add_network_to_dhcp_agent( + '28c25a04-3f73-45a7-a2b4-59e183943ddc', + {'network_id': u'fc68ea2c-b60b-4b4f-bd82-94ec81110766'} + ).AndReturn(None) + neutronclient.Client.show_network( 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' ).AndReturn({"network": { @@ -424,6 +422,41 @@ class NeutronNetTest(HeatTestCase): }}) # Update script + neutronclient.Client.list_dhcp_agent_hosting_networks( + 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' + ).AndReturn({ + "agents": [{ + "admin_state_up": True, + "agent_type": "DHCP agent", + "alive": True, + "binary": "neutron-dhcp-agent", + "configurations": { + "dhcp_driver": "DummyDriver", + "dhcp_lease_duration": 86400, + "networks": 0, + "ports": 0, + "subnets": 0, + "use_namespaces": True}, + "created_at": "2014-03-20 05:12:34", + "description": None, + "heartbeat_timestamp": "2014-03-20 05:12:34", + "host": "hostname", + "id": "28c25a04-3f73-45a7-a2b4-59e183943ddc", + "started_at": "2014-03-20 05:12:34", + "topic": "dhcp_agent" + }] + }) + + neutronclient.Client.add_network_to_dhcp_agent( + 'bb09cfcd-5277-473d-8336-d4ed8628ae68', + {'network_id': u'fc68ea2c-b60b-4b4f-bd82-94ec81110766'} + ).AndReturn(None) + + neutronclient.Client.remove_network_from_dhcp_agent( + '28c25a04-3f73-45a7-a2b4-59e183943ddc', + 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' + ).AndReturn(None) + neutronclient.Client.update_network( 'fc68ea2c-b60b-4b4f-bd82-94ec81110766', {'network': { @@ -472,134 +505,25 @@ class NeutronNetTest(HeatTestCase): "Properties": { "name": "mynet", "shared": True, - "admin_state_up": True + "admin_state_up": True, + "dhcp_agent_ids": [ + "bb09cfcd-5277-473d-8336-d4ed8628ae68" + ] } } - rsrc.handle_update(update_snippet, {}, {}) + prop_diff = { + "name": "mynet", + "dhcp_agent_ids": [ + "bb09cfcd-5277-473d-8336-d4ed8628ae68" + ] + } + rsrc.handle_update(update_snippet, {}, prop_diff) scheduler.TaskRunner(rsrc.delete)() rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again') 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_happens_409(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=409)) - - 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 NeutronProviderNetTest(HeatTestCase):