From 9928edb42da42af3463d4a989a0797be46198908 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Thu, 22 Aug 2013 20:17:00 -0700 Subject: [PATCH] Refactor BigSwitch error handling to use db rollbacks This patch adjusts most of the logic handling the calls to the controller in the BigSwitch/floodlight plugin to make use of the db rollbacks from sqlalchemy for free on exceptions. This eliminates several complex try-except blocks and makes maintaining db<->controller consistency easier. Fixes: bug #1215823 Change-Id: Ia636c40e744b3b1c543e891791bf492df4f675d2 --- neutron/plugins/bigswitch/plugin.py | 805 ++++++++---------- neutron/tests/unit/bigswitch/fake_server.py | 96 +++ .../unit/bigswitch/test_restproxy_plugin.py | 194 +++-- .../tests/unit/bigswitch/test_router_db.py | 90 +- 4 files changed, 625 insertions(+), 560 deletions(-) create mode 100644 neutron/tests/unit/bigswitch/fake_server.py diff --git a/neutron/plugins/bigswitch/plugin.py b/neutron/plugins/bigswitch/plugin.py index 78c02466a..3911f2fe5 100644 --- a/neutron/plugins/bigswitch/plugin.py +++ b/neutron/plugins/bigswitch/plugin.py @@ -68,6 +68,7 @@ from neutron.db import l3_db from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import l3 from neutron.extensions import portbindings +from neutron.openstack.common import excutils from neutron.openstack.common import log as logging from neutron.openstack.common import rpc from neutron.plugins.bigswitch.db import porttracker_db @@ -302,6 +303,10 @@ class ServerPool(object): 'server': (active_server.server, active_server.port), 'response': ret[3]}) + LOG.error(_("ServerProxy: Error details: status=%(status)d, " + "reason=%(reason)r, ret=%(ret)s, data=%(data)r"), + {'status': ret[0], 'reason': ret[1], 'ret': ret[2], + 'data': ret[3]}) active_server.failed = True # All servers failed, reset server list and try again next time @@ -312,17 +317,106 @@ class ServerPool(object): s.port) for s in self.servers)}) return (0, None, None, None) - def get(self, resource, data='', headers=None, ignore_codes=[]): - return self.rest_call('GET', resource, data, headers, ignore_codes) - - def put(self, resource, data, headers=None, ignore_codes=[]): - return self.rest_call('PUT', resource, data, headers, ignore_codes) - - def post(self, resource, data, headers=None, ignore_codes=[]): - return self.rest_call('POST', resource, data, headers, ignore_codes) - - def delete(self, resource, data='', headers=None, ignore_codes=[]): - return self.rest_call('DELETE', resource, data, headers, ignore_codes) + def rest_action(self, action, resource, data='', errstr='%s', + ignore_codes=[], headers=None): + """ + Wrapper for rest_call that verifies success and raises a + RemoteRestError on failure with a provided error string + By default, 404 errors on DELETE calls are ignored because + they already do not exist on the backend. + """ + if not ignore_codes and action == 'DELETE': + ignore_codes = [404] + resp = self.rest_call(action, resource, data, headers, ignore_codes) + if self.server_failure(resp, ignore_codes): + LOG.error(_("NeutronRestProxyV2: ") + errstr, resp[2]) + raise RemoteRestError(resp[2]) + if resp[0] in ignore_codes: + LOG.warning(_("NeutronRestProxyV2: Received and ignored error " + "code %(code)d on %(action)s action to resource " + "%(resource)s"), + {'code': resp[2], 'action': action, + 'resource': resource}) + return resp + + def rest_create_router(self, tenant_id, router): + resource = ROUTER_RESOURCE_PATH % tenant_id + data = {"router": router} + errstr = _("Unable to create remote router: %s") + self.rest_action('POST', resource, data, errstr) + + def rest_update_router(self, tenant_id, router, router_id): + resource = ROUTERS_PATH % (tenant_id, router_id) + data = {"router": router} + errstr = _("Unable to update remote router: %s") + self.rest_action('PUT', resource, data, errstr) + + def rest_delete_router(self, tenant_id, router_id): + resource = ROUTERS_PATH % (tenant_id, router_id) + errstr = _("Unable to delete remote router: %s") + self.rest_action('DELETE', resource, errstr=errstr) + + def rest_add_router_interface(self, tenant_id, router_id, intf_details): + resource = ROUTER_INTF_OP_PATH % (tenant_id, router_id) + data = {"interface": intf_details} + errstr = _("Unable to add router interface: %s") + self.rest_action('POST', resource, data, errstr) + + def rest_remove_router_interface(self, tenant_id, router_id, interface_id): + resource = ROUTER_INTF_PATH % (tenant_id, router_id, interface_id) + errstr = _("Unable to delete remote intf: %s") + self.rest_action('DELETE', resource, errstr=errstr) + + def rest_create_network(self, tenant_id, network): + resource = NET_RESOURCE_PATH % tenant_id + data = {"network": network} + errstr = _("Unable to create remote network: %s") + self.rest_action('POST', resource, data, errstr) + + def rest_update_network(self, tenant_id, net_id, network): + resource = NETWORKS_PATH % (tenant_id, net_id) + data = {"network": network} + errstr = _("Unable to update remote network: %s") + self.rest_action('PUT', resource, data, errstr) + + def rest_delete_network(self, tenant_id, net_id): + resource = NETWORKS_PATH % (tenant_id, net_id) + errstr = _("Unable to update remote network: %s") + self.rest_action('DELETE', resource, errstr=errstr) + + def rest_create_port(self, net, port): + resource = PORT_RESOURCE_PATH % (net["tenant_id"], net["id"]) + data = {"port": port} + errstr = _("Unable to create remote port: %s") + self.rest_action('POST', resource, data, errstr) + + def rest_update_port(self, tenant_id, network_id, port, port_id): + resource = PORTS_PATH % (tenant_id, network_id, port_id) + data = {"port": port} + errstr = _("Unable to update remote port: %s") + self.rest_action('PUT', resource, data, errstr) + + def rest_delete_port(self, tenant_id, network_id, port_id): + resource = PORTS_PATH % (tenant_id, network_id, port_id) + errstr = _("Unable to delete remote port: %s") + self.rest_action('DELETE', resource, errstr=errstr) + + def rest_plug_interface(self, tenant_id, net_id, port, + remote_interface_id): + if port["mac_address"] is not None: + resource = ATTACHMENT_PATH % (tenant_id, net_id, port["id"]) + data = {"attachment": + {"id": remote_interface_id, + "mac": port["mac_address"], + } + } + errstr = _("Unable to plug in interface: %s") + self.rest_action('PUT', resource, data, errstr) + + def rest_unplug_interface(self, tenant_id, net_id, port_id): + resource = ATTACHMENT_PATH % (tenant_id, net_id, port_id) + errstr = _("Unable to unplug interface: %s") + self.rest_action('DELETE', resource, errstr=errstr) class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin): @@ -413,32 +507,20 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, self._warn_on_state_status(network['network']) - # Validate args - tenant_id = self._get_tenant_id_for_create(context, network["network"]) + with context.session.begin(subtransactions=True): + # Validate args + tenant_id = self._get_tenant_id_for_create(context, + network["network"]) - session = context.session - with session.begin(subtransactions=True): # create network in DB new_net = super(NeutronRestProxyV2, self).create_network(context, network) self._process_l3_create(context, new_net, network['network']) + mapped_network = self._get_mapped_network_with_subnets(new_net, + context) - # create network on the network controller - try: - resource = NET_RESOURCE_PATH % tenant_id - mapped_network = self._get_mapped_network_with_subnets(new_net) - data = { - "network": mapped_network - } - ret = self.servers.post(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2:Unable to create remote " - "network: %s"), e.message) - super(NeutronRestProxyV2, self).delete_network(context, - new_net['id']) - raise + # create network on the network controller + self.servers.rest_create_network(tenant_id, mapped_network) # return created network return new_net @@ -472,25 +554,12 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, session = context.session with session.begin(subtransactions=True): - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - new_net = super(NeutronRestProxyV2, self).update_network(context, - net_id, - network) + new_net = super(NeutronRestProxyV2, self).update_network( + context, net_id, network) self._process_l3_update(context, new_net, network['network']) - # update network on network controller - try: - self._send_update_network(new_net) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to update remote " - "network: %s"), e.message) - # reset network to original state - super(NeutronRestProxyV2, self).update_network(context, id, - orig_net) - raise - - # return updated network + # update network on network controller + self._send_update_network(new_net, context) return new_net def delete_network(self, context, net_id): @@ -520,20 +589,11 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, if not only_auto_del: raise exceptions.NetworkInUse(net_id=net_id) - - # delete from network ctrl. Remote error on delete is ignored - try: - resource = NETWORKS_PATH % (tenant_id, net_id) - ret = self.servers.delete(resource) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) + with context.session.begin(subtransactions=True): ret_val = super(NeutronRestProxyV2, self).delete_network(context, net_id) + self.servers.rest_delete_network(tenant_id, net_id) return ret_val - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to update remote " - "network: %s"), e.message) - raise def create_port(self, context, port): """Create a port, which is a connection point of a device @@ -563,48 +623,46 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, """ LOG.debug(_("NeutronRestProxyV2: create_port() called")) - # Update DB - port["port"]["admin_state_up"] = False - dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) - new_port = super(NeutronRestProxyV2, self).create_port(context, port) - if (portbindings.HOST_ID in port['port'] - and 'id' in new_port): - porttracker_db.put_port_hostid(context, new_port['id'], - port['port'][portbindings.HOST_ID]) - self._process_port_create_extra_dhcp_opts(context, new_port, dhcp_opts) - new_port = self._extend_port_dict_binding(context, new_port) - net = super(NeutronRestProxyV2, - self).get_network(context, new_port["network_id"]) - - if self.add_meta_server_route: - if new_port['device_owner'] == 'network:dhcp': - destination = METADATA_SERVER_IP + '/32' - self._add_host_route(context, destination, new_port) - - # create on networl ctrl - try: - resource = PORT_RESOURCE_PATH % (net["tenant_id"], net["id"]) - mapped_port = self._map_state_and_status(new_port) - data = { - "port": mapped_port - } - ret = self.servers.post(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - - # connect device to network, if present - device_id = port["port"].get("device_id") - if device_id: - self._plug_interface(context, - net["tenant_id"], net["id"], - new_port["id"], device_id) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to create remote port: " - "%s"), e.message) - super(NeutronRestProxyV2, self).delete_port(context, - new_port["id"]) - raise + # Update DB in new session so exceptions rollback changes + with context.session.begin(subtransactions=True): + port["port"]["admin_state_up"] = False + dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) + new_port = super(NeutronRestProxyV2, self).create_port(context, + port) + if (portbindings.HOST_ID in port['port'] + and 'id' in new_port): + host_id = port['port'][portbindings.HOST_ID] + porttracker_db.put_port_hostid(context, new_port['id'], + host_id) + self._process_port_create_extra_dhcp_opts(context, new_port, + dhcp_opts) + new_port = self._extend_port_dict_binding(context, new_port) + net = super(NeutronRestProxyV2, + self).get_network(context, new_port["network_id"]) + if self.add_meta_server_route: + if new_port['device_owner'] == 'network:dhcp': + destination = METADATA_SERVER_IP + '/32' + self._add_host_route(context, destination, new_port) + + # create on network ctrl + mapped_port = self._map_state_and_status(new_port) + self.servers.rest_create_port(net, mapped_port) + + # connect device to network, if present + device_id = port["port"].get("device_id") + if device_id: + try: + self.servers.rest_plug_interface(net["tenant_id"], net["id"], + new_port, device_id) + except RemoteRestError: + with excutils.save_and_reraise_exception(): + port_update = {"port": {"status": "ERROR"}} + super(NeutronRestProxyV2, self).update_port( + context, + new_port["id"], + port_update + ) # Set port state up and return that port port_update = {"port": {"admin_state_up": True}} new_port = super(NeutronRestProxyV2, self).update_port(context, @@ -660,44 +718,45 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, # Validate Args orig_port = super(NeutronRestProxyV2, self).get_port(context, port_id) - - # Update DB - new_port = super(NeutronRestProxyV2, self).update_port(context, - port_id, port) - self._update_extra_dhcp_opts_on_port(context, port_id, port, new_port) - if (portbindings.HOST_ID in port['port'] - and 'id' in new_port): - porttracker_db.put_port_hostid(context, new_port['id'], - port['port'][portbindings.HOST_ID]) - new_port = self._extend_port_dict_binding(context, new_port) - # update on networl ctrl - try: - resource = PORTS_PATH % (orig_port["tenant_id"], - orig_port["network_id"], port_id) + with context.session.begin(subtransactions=True): + # Update DB + new_port = super(NeutronRestProxyV2, + self).update_port(context, port_id, port) + self._update_extra_dhcp_opts_on_port(context, port_id, port, + new_port) + if (portbindings.HOST_ID in port['port'] + and 'id' in new_port): + host_id = port['port'][portbindings.HOST_ID] + porttracker_db.put_port_hostid(context, new_port['id'], + host_id) + new_port = self._extend_port_dict_binding(context, new_port) + + # update on networl ctrl mapped_port = self._map_state_and_status(new_port) - data = {"port": mapped_port} - ret = self.servers.put(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - - if new_port.get("device_id") != orig_port.get("device_id"): - if orig_port.get("device_id"): - self._unplug_interface(context, orig_port["tenant_id"], - orig_port["network_id"], - orig_port["id"]) + self.servers.rest_update_port(orig_port["tenant_id"], + orig_port["network_id"], + mapped_port, port_id) + + if (new_port.get("device_id") != orig_port.get("device_id") and + orig_port.get("device_id")): + try: + self.servers.rest_unplug_interface(orig_port["tenant_id"], + orig_port["network_id"], + orig_port["id"]) device_id = new_port.get("device_id") if device_id: - self._plug_interface(context, new_port["tenant_id"], - new_port["network_id"], - new_port["id"], device_id) - - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to create remote port: " - "%s"), e.message) - # reset port to original state - super(NeutronRestProxyV2, self).update_port(context, port_id, - orig_port) - raise + self.rest_plug_interface(new_port["tenant_id"], + new_port["network_id"], + new_port, device_id) + + except RemoteRestError: + with excutils.save_and_reraise_exception(): + port_update = {"port": {"status": "ERROR"}} + super(NeutronRestProxyV2, self).update_port( + context, + new_port["id"], + port_update + ) # return new_port return new_port @@ -719,115 +778,57 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, # and l3-router. If so, we should prevent deletion. if l3_port_check: self.prevent_l3_port_deletion(context, port_id) - self.disassociate_floatingips(context, port_id) + with context.session.begin(subtransactions=True): + self.disassociate_floatingips(context, port_id) + self._unplug_port(context, port_id) + # Separate transaction for delete in case unplug passes + # but delete fails on controller + with context.session.begin(subtransactions=True): + super(NeutronRestProxyV2, self).delete_port(context, port_id) - super(NeutronRestProxyV2, self).delete_port(context, port_id) + def _unplug_port(self, context, port_id): + port = super(NeutronRestProxyV2, self).get_port(context, port_id) + tenant_id = port['tenant_id'] + net_id = port['network_id'] + if tenant_id == '': + net = super(NeutronRestProxyV2, self).get_network(context, net_id) + tenant_id = net['tenant_id'] + if port.get("device_id"): + self.servers.rest_unplug_interface(tenant_id, net_id, port_id) + # Port should transition to error state now that it's unplugged + # but not yet deleted + port_update = {"port": {"status": "ERROR"}} + super(NeutronRestProxyV2, self).update_port(context, + port_id, + port_update) def _delete_port(self, context, port_id): - # Delete from DB port = super(NeutronRestProxyV2, self).get_port(context, port_id) tenant_id = port['tenant_id'] + net_id = port['network_id'] if tenant_id == '': - net = super(NeutronRestProxyV2, - self).get_network(context, port['network_id']) + net = super(NeutronRestProxyV2, self).get_network(context, net_id) tenant_id = net['tenant_id'] - - # delete from network ctrl. Remote error on delete is ignored - try: - resource = PORTS_PATH % (tenant_id, port["network_id"], - port_id) - ret = self.servers.delete(resource) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - - if port.get("device_id"): - self._unplug_interface(context, tenant_id, - port["network_id"], port["id"]) - ret_val = super(NeutronRestProxyV2, self)._delete_port(context, - port_id) - return ret_val - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to update remote port: " - "%s"), e.message) - raise - - def _plug_interface(self, context, tenant_id, net_id, port_id, - remote_interface_id): - """Plug remote interface to the network. - - Attaches a remote interface to the specified port on the specified - Virtual Network. - - :returns: None - - :raises: exceptions.NetworkNotFound - :raises: exceptions.PortNotFound - :raises: RemoteRestError - """ - LOG.debug(_("NeutronRestProxyV2: _plug_interface() called")) - - # update attachment on network controller - try: - port = super(NeutronRestProxyV2, self).get_port(context, port_id) - mac = port["mac_address"] - - if mac is not None: - resource = ATTACHMENT_PATH % (tenant_id, net_id, port_id) - data = {"attachment": - {"id": remote_interface_id, - "mac": mac, - } - } - ret = self.servers.put(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2:Unable to update remote network: " - "%s"), e.message) - raise - - def _unplug_interface(self, context, tenant_id, net_id, port_id): - """Detach interface from the network controller. - - Detaches a remote interface from the specified port on the network - controller. - - :returns: None - - :raises: RemoteRestError - """ - LOG.debug(_("NeutronRestProxyV2: _unplug_interface() called")) - - # delete from network ctrl. Remote error on delete is ignored - try: - resource = ATTACHMENT_PATH % (tenant_id, net_id, port_id) - ret = self.servers.delete(resource, ignore_codes=[404]) - if self.servers.server_failure(ret, ignore_codes=[404]): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to update remote port: " - "%s"), e.message) - raise + # Delete from DB + ret_val = super(NeutronRestProxyV2, + self)._delete_port(context, port_id) + self.servers.rest_delete_port(tenant_id, net_id, port_id) + return ret_val def create_subnet(self, context, subnet): LOG.debug(_("NeutronRestProxyV2: create_subnet() called")) self._warn_on_state_status(subnet['subnet']) - # create subnet in DB - new_subnet = super(NeutronRestProxyV2, self).create_subnet(context, - subnet) - net_id = new_subnet['network_id'] - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - # update network on network controller - try: + with context.session.begin(subtransactions=True): + # create subnet in DB + new_subnet = super(NeutronRestProxyV2, + self).create_subnet(context, subnet) + net_id = new_subnet['network_id'] + orig_net = super(NeutronRestProxyV2, + self).get_network(context, net_id) + # update network on network controller self._send_update_network(orig_net) - except RemoteRestError: - # rollback creation of subnet - super(NeutronRestProxyV2, self).delete_subnet(context, - subnet['id']) - raise return new_subnet def update_subnet(self, context, id, subnet): @@ -835,38 +836,28 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, self._warn_on_state_status(subnet['subnet']) - orig_subnet = super(NeutronRestProxyV2, self)._get_subnet(context, id) - - # update subnet in DB - new_subnet = super(NeutronRestProxyV2, self).update_subnet(context, id, - subnet) - net_id = new_subnet['network_id'] - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - # update network on network controller - try: + with context.session.begin(subtransactions=True): + # update subnet in DB + new_subnet = super(NeutronRestProxyV2, + self).update_subnet(context, id, subnet) + net_id = new_subnet['network_id'] + orig_net = super(NeutronRestProxyV2, + self).get_network(context, net_id) + # update network on network controller self._send_update_network(orig_net) - except RemoteRestError: - # rollback updation of subnet - super(NeutronRestProxyV2, self).update_subnet(context, id, - orig_subnet) - raise - return new_subnet + return new_subnet def delete_subnet(self, context, id): LOG.debug(_("NeutronRestProxyV2: delete_subnet() called")) orig_subnet = super(NeutronRestProxyV2, self).get_subnet(context, id) net_id = orig_subnet['network_id'] - # delete subnet in DB - super(NeutronRestProxyV2, self).delete_subnet(context, id) - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - # update network on network controller - try: + with context.session.begin(subtransactions=True): + # delete subnet in DB + super(NeutronRestProxyV2, self).delete_subnet(context, id) + orig_net = super(NeutronRestProxyV2, self).get_network(context, + net_id) + # update network on network controller - exception will rollback self._send_update_network(orig_net) - except RemoteRestError: - # TODO(Sumit): rollback deletion of subnet - raise def _get_tenant_default_router_rules(self, tenant): rules = cfg.CONF.ROUTER.tenant_default_router_rule @@ -905,29 +896,15 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, rules = self._get_tenant_default_router_rules(tenant_id) router['router']['router_rules'] = rules - # create router in DB - new_router = super(NeutronRestProxyV2, self).create_router(context, - router) - - # create router on the network controller - try: - resource = ROUTER_RESOURCE_PATH % tenant_id + with context.session.begin(subtransactions=True): + # create router in DB + new_router = super(NeutronRestProxyV2, self).create_router(context, + router) mapped_router = self._map_state_and_status(new_router) - data = { - "router": mapped_router - } - ret = self.servers.post(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to create remote router: " - "%s"), e.message) - super(NeutronRestProxyV2, self).delete_router(context, - new_router['id']) - raise - - # return created router - return new_router + self.servers.rest_create_router(tenant_id, mapped_router) + + # return created router + return new_router def update_router(self, context, router_id, router): @@ -938,31 +915,16 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, orig_router = super(NeutronRestProxyV2, self).get_router(context, router_id) tenant_id = orig_router["tenant_id"] - new_router = super(NeutronRestProxyV2, self).update_router(context, - router_id, - router) + with context.session.begin(subtransactions=True): + new_router = super(NeutronRestProxyV2, + self).update_router(context, router_id, router) + router = self._map_state_and_status(new_router) - # update router on network controller - try: - resource = ROUTERS_PATH % (tenant_id, router_id) - mapped_router = self._map_state_and_status(new_router) - data = { - "router": mapped_router - } - ret = self.servers.put(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to update remote router: " - "%s"), e.message) - # reset router to original state - super(NeutronRestProxyV2, self).update_router(context, - router_id, - orig_router) - raise + # update router on network controller + self.servers.rest_update_router(tenant_id, router, router_id) - # return updated router - return new_router + # return updated router + return new_router def delete_router(self, context, router_id): LOG.debug(_("NeutronRestProxyV2: delete_router() called")) @@ -985,20 +947,12 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, filters=device_filter) if ports: raise l3.RouterInUse(router_id=router_id) + ret_val = super(NeutronRestProxyV2, + self).delete_router(context, router_id) - # delete from network ctrl. Remote error on delete is ignored - try: - resource = ROUTERS_PATH % (tenant_id, router_id) - ret = self.servers.delete(resource) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - ret_val = super(NeutronRestProxyV2, self).delete_router(context, - router_id) + # delete from network ctrl + self.servers.rest_delete_router(tenant_id, router_id) return ret_val - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to delete remote router: " - "%s"), e.message) - raise def add_router_interface(self, context, router_id, interface_info): @@ -1008,36 +962,25 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, router = self._get_router(context, router_id) tenant_id = router['tenant_id'] - # create interface in DB - new_interface_info = super(NeutronRestProxyV2, - self).add_router_interface(context, - router_id, - interface_info) - port = self._get_port(context, new_interface_info['port_id']) - net_id = port['network_id'] - subnet_id = new_interface_info['subnet_id'] - # we will use the port's network id as interface's id - interface_id = net_id - intf_details = self._get_router_intf_details(context, - interface_id, - subnet_id) - - # create interface on the network controller - try: - resource = ROUTER_INTF_OP_PATH % (tenant_id, router_id) - data = {"interface": intf_details} - ret = self.servers.post(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to create interface: " - "%s"), e.message) - super(NeutronRestProxyV2, - self).remove_router_interface(context, router_id, - interface_info) - raise - - return new_interface_info + with context.session.begin(subtransactions=True): + # create interface in DB + new_intf_info = super(NeutronRestProxyV2, + self).add_router_interface(context, + router_id, + interface_info) + port = self._get_port(context, new_intf_info['port_id']) + net_id = port['network_id'] + subnet_id = new_intf_info['subnet_id'] + # we will use the port's network id as interface's id + interface_id = net_id + intf_details = self._get_router_intf_details(context, + interface_id, + subnet_id) + + # create interface on the network controller + self.servers.rest_add_router_interface(tenant_id, router_id, + intf_details) + return new_intf_info def remove_router_interface(self, context, router_id, interface_info): @@ -1061,89 +1004,69 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, msg = "Either subnet_id or port_id must be specified" raise exceptions.BadRequest(resource='router', msg=msg) - # remove router in DB - del_intf_info = super(NeutronRestProxyV2, - self).remove_router_interface(context, - router_id, - interface_info) + with context.session.begin(subtransactions=True): + # remove router in DB + del_ret = super(NeutronRestProxyV2, + self).remove_router_interface(context, + router_id, + interface_info) - # create router on the network controller - try: - resource = ROUTER_INTF_PATH % (tenant_id, router_id, interface_id) - ret = self.servers.delete(resource) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2:Unable to delete remote intf: " - "%s"), e.message) - raise - - # return new interface - return del_intf_info + # create router on the network controller + self.servers.rest_remove_router_interface(tenant_id, router_id, + interface_id) + return del_ret def create_floatingip(self, context, floatingip): LOG.debug(_("NeutronRestProxyV2: create_floatingip() called")) - # create floatingip in DB - new_fl_ip = super(NeutronRestProxyV2, - self).create_floatingip(context, floatingip) - - net_id = new_fl_ip['floating_network_id'] - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - # create floatingip on the network controller - try: - self._send_update_network(orig_net) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to create remote " - "floatin IP: %s"), e.message) - super(NeutronRestProxyV2, self).delete_floatingip(context, - floatingip) - raise + with context.session.begin(subtransactions=True): + # create floatingip in DB + new_fl_ip = super(NeutronRestProxyV2, + self).create_floatingip(context, floatingip) - # return created floating IP - return new_fl_ip + net_id = new_fl_ip['floating_network_id'] + orig_net = super(NeutronRestProxyV2, self).get_network(context, + net_id) + # create floatingip on the network controller + try: + self._send_update_network(orig_net) + except RemoteRestError as e: + with excutils.save_and_reraise_exception(): + LOG.error( + _("NeutronRestProxyV2: Unable to create remote " + "floating IP: %s"), e) + # return created floating IP + return new_fl_ip def update_floatingip(self, context, id, floatingip): LOG.debug(_("NeutronRestProxyV2: update_floatingip() called")) - orig_fl_ip = super(NeutronRestProxyV2, self).get_floatingip(context, - id) - - # update floatingip in DB - new_fl_ip = super(NeutronRestProxyV2, - self).update_floatingip(context, id, floatingip) + with context.session.begin(subtransactions=True): + # update floatingip in DB + new_fl_ip = super(NeutronRestProxyV2, + self).update_floatingip(context, id, floatingip) - net_id = new_fl_ip['floating_network_id'] - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - # update network on network controller - try: + net_id = new_fl_ip['floating_network_id'] + orig_net = super(NeutronRestProxyV2, self).get_network(context, + net_id) + # update network on network controller self._send_update_network(orig_net) - except RemoteRestError: - # rollback updation of subnet - super(NeutronRestProxyV2, self).update_floatingip(context, id, - orig_fl_ip) - raise - return new_fl_ip + return new_fl_ip def delete_floatingip(self, context, id): LOG.debug(_("NeutronRestProxyV2: delete_floatingip() called")) orig_fl_ip = super(NeutronRestProxyV2, self).get_floatingip(context, id) - # delete floating IP in DB - net_id = orig_fl_ip['floating_network_id'] - super(NeutronRestProxyV2, self).delete_floatingip(context, id) + with context.session.begin(subtransactions=True): + # delete floating IP in DB + net_id = orig_fl_ip['floating_network_id'] + super(NeutronRestProxyV2, self).delete_floatingip(context, id) - orig_net = super(NeutronRestProxyV2, self).get_network(context, - net_id) - # update network on network controller - try: + orig_net = super(NeutronRestProxyV2, self).get_network(context, + net_id) + # update network on network controller self._send_update_network(orig_net) - except RemoteRestError: - # TODO(Sumit): rollback deletion of floating IP - raise def _send_all_data(self): """Pushes all data to network ctrl (networks/ports, ports/attachments). @@ -1200,20 +1123,13 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, routers.append(mapped_router) - try: - resource = '/topology' - data = { - 'networks': networks, - 'routers': routers, - } - ret = self.servers.put(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - return ret - except RemoteRestError as e: - LOG.error(_('NeutronRestProxy: Unable to update remote ' - 'topology: %s'), e.message) - raise + resource = '/topology' + data = { + 'networks': networks, + 'routers': routers, + } + errstr = _("Unable to update remote topology: %s") + return self.servers.rest_action('PUT', resource, data, errstr) def _add_host_route(self, context, destination, port): subnet = {} @@ -1232,22 +1148,26 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, LOG.debug("destination:%s nexthop:%s" % (destination, nexthop)) - def _get_network_with_floatingips(self, network): - admin_context = qcontext.get_admin_context() + def _get_network_with_floatingips(self, network, context=None): + if context is None: + context = qcontext.get_admin_context() net_id = network['id'] net_filter = {'floating_network_id': [net_id]} fl_ips = super(NeutronRestProxyV2, - self).get_floatingips(admin_context, + self).get_floatingips(context, filters=net_filter) or [] network['floatingips'] = fl_ips return network - def _get_all_subnets_json_for_network(self, net_id): - admin_context = qcontext.get_admin_context() - subnets = self._get_subnets_by_network(admin_context, - net_id) + def _get_all_subnets_json_for_network(self, net_id, context=None): + if context is None: + context = qcontext.get_admin_context() + # start a sub-transaction to avoid breaking parent transactions + with context.session.begin(subtransactions=True): + subnets = self._get_subnets_by_network(context, + net_id) subnets_details = [] if subnets: for subnet in subnets: @@ -1257,10 +1177,13 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, return subnets_details - def _get_mapped_network_with_subnets(self, network): - admin_context = qcontext.get_admin_context() + def _get_mapped_network_with_subnets(self, network, context=None): + # if context is not provided, admin context is used + if context is None: + context = qcontext.get_admin_context() network = self._map_state_and_status(network) - subnets = self._get_all_subnets_json_for_network(network['id']) + subnets = self._get_all_subnets_json_for_network(network['id'], + context) network['subnets'] = subnets for subnet in (subnets or []): if subnet['gateway_ip']: @@ -1269,30 +1192,20 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, break else: network['gateway'] = '' - - network[l3.EXTERNAL] = self._network_is_external(admin_context, + network[l3.EXTERNAL] = self._network_is_external(context, network['id']) return network - def _send_update_network(self, network): + def _send_update_network(self, network, context=None): net_id = network['id'] tenant_id = network['tenant_id'] # update network on network controller - try: - resource = NETWORKS_PATH % (tenant_id, net_id) - mapped_network = self._get_mapped_network_with_subnets(network) - net_fl_ips = self._get_network_with_floatingips(mapped_network) - data = { - "network": net_fl_ips, - } - ret = self.servers.put(resource, data) - if not self.servers.action_success(ret): - raise RemoteRestError(ret[2]) - except RemoteRestError as e: - LOG.error(_("NeutronRestProxyV2: Unable to update remote " - "network: %s"), e.message) - raise + mapped_network = self._get_mapped_network_with_subnets(network, + context) + net_fl_ips = self._get_network_with_floatingips(mapped_network, + context) + self.servers.rest_update_network(tenant_id, net_id, net_fl_ips) def _map_state_and_status(self, resource): resource = copy.copy(resource) diff --git a/neutron/tests/unit/bigswitch/fake_server.py b/neutron/tests/unit/bigswitch/fake_server.py new file mode 100644 index 000000000..bc392c651 --- /dev/null +++ b/neutron/tests/unit/bigswitch/fake_server.py @@ -0,0 +1,96 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Big Switch Networks, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Kevin Benton, +# + + +class HTTPResponseMock(): + status = 200 + reason = 'OK' + + def __init__(self, sock, debuglevel=0, strict=0, method=None, + buffering=False): + pass + + def read(self): + return "{'status': '200 OK'}" + + +class HTTPResponseMock404(HTTPResponseMock): + status = 404 + reason = 'Not Found' + + def read(self): + return "{'status': '404 Not Found'}" + + +class HTTPResponseMock500(HTTPResponseMock): + status = 500 + reason = 'Internal Server Error' + + def __init__(self, sock, debuglevel=0, strict=0, method=None, + buffering=False, errmsg='500 Internal Server Error'): + self.errmsg = errmsg + + def read(self): + return "{'status': '%s'}" % self.errmsg + + +class HTTPConnectionMock(): + + def __init__(self, server, port, timeout): + self.response = None + self.broken = False + # Port 9000 is the broken server + if port == 9000: + self.broken = True + errmsg = "This server is broken, please try another" + self.response = HTTPResponseMock500(None, errmsg=errmsg) + + def request(self, action, uri, body, headers): + if self.broken and "ExceptOnBadServer" in uri: + raise Exception("Broken server got an unexpected request") + if self.response: + return + + # detachment may return 404 and plugin shouldn't die + if uri.endswith('attachment') and action == 'DELETE': + self.response = HTTPResponseMock404(None) + else: + self.response = HTTPResponseMock(None) + + # Port creations/updates must contain binding information + if ('port' in uri and 'attachment' not in uri + and 'binding' not in body and action in ('POST', 'PUT')): + errmsg = "Port binding info missing in port request '%s'" % body + self.response = HTTPResponseMock500(None, errmsg=errmsg) + return + + return + + def getresponse(self): + return self.response + + def close(self): + pass + + +class HTTPConnectionMock500(HTTPConnectionMock): + + def __init__(self, server, port, timeout): + self.response = HTTPResponseMock500(None) + self.broken = True diff --git a/neutron/tests/unit/bigswitch/test_restproxy_plugin.py b/neutron/tests/unit/bigswitch/test_restproxy_plugin.py index 474811472..34dd567d5 100644 --- a/neutron/tests/unit/bigswitch/test_restproxy_plugin.py +++ b/neutron/tests/unit/bigswitch/test_restproxy_plugin.py @@ -22,79 +22,18 @@ from oslo.config import cfg import webob.exc import neutron.common.test_lib as test_lib +from neutron import context from neutron.extensions import portbindings from neutron.manager import NeutronManager +from neutron.plugins.bigswitch.plugin import RemoteRestError from neutron.tests.unit import _test_extension_portbindings as test_bindings +from neutron.tests.unit.bigswitch import fake_server +from neutron.tests.unit import test_api_v2 import neutron.tests.unit.test_db_plugin as test_plugin - RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.plugin' -class HTTPResponseMock(): - status = 200 - reason = 'OK' - - def __init__(self, sock, debuglevel=0, strict=0, method=None, - buffering=False): - pass - - def read(self): - return "{'status': '200 OK'}" - - -class HTTPResponseMock404(): - status = 404 - reason = 'Not Found' - - def __init__(self, sock, debuglevel=0, strict=0, method=None, - buffering=False): - pass - - def read(self): - return "{'status': '404 Not Found'}" - - -class HTTPResponseMock500(): - status = 500 - reason = 'Internal Server Error' - - def __init__(self, sock, debuglevel=0, strict=0, method=None, - buffering=False): - pass - - def read(self): - return "{'status': '500 Internal Server Error'}" - - -class HTTPConnectionMock(): - - def __init__(self, server, port, timeout): - if port == 9000: - self.response = HTTPResponseMock500(None) - self.broken = True - else: - self.response = HTTPResponseMock(None) - self.broken = False - - def request(self, action, uri, body, headers): - if self.broken: - if "ExceptOnBadServer" in uri: - raise Exception("Broken server got an unexpected request") - return - if uri.endswith('attachment') and action == 'DELETE': - self.response = HTTPResponseMock404(None) - else: - self.response = HTTPResponseMock(None) - return - - def getresponse(self): - return self.response - - def close(self): - pass - - class BigSwitchProxyPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): _plugin_name = ('%s.NeutronRestProxyV2' % RESTPROXY_PKG_PATH) @@ -105,7 +44,7 @@ class BigSwitchProxyPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): 'restproxy.ini.test')] self.httpPatch = patch('httplib.HTTPConnection', create=True, - new=HTTPConnectionMock) + new=fake_server.HTTPConnectionMock) self.addCleanup(self.httpPatch.stop) self.httpPatch.start() super(BigSwitchProxyPluginV2TestCase, @@ -138,6 +77,89 @@ class TestBigSwitchProxyPortsV2(test_plugin.TestPortsV2, VIF_TYPE = portbindings.VIF_TYPE_OVS HAS_PORT_FILTER = False + def _get_ports(self, netid): + return self.deserialize('json', + self._list_ports('json', netid=netid))['ports'] + + def test_rollback_for_port_create(self): + with self.network(no_delete=True) as n: + self.httpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.httpPatch.start() + kwargs = {'device_id': 'somedevid', + 'tenant_id': n['network']['tenant_id']} + self._create_port('json', n['network']['id'], + expected_code= + webob.exc.HTTPInternalServerError.code, + **kwargs) + self.httpPatch.stop() + ports = self._get_ports(n['network']['id']) + #failure to create should result in no ports + self.assertEqual(0, len(ports)) + + def test_rollback_on_port_attach(self): + with self.network() as n: + plugin_obj = NeutronManager.get_plugin() + with patch.object(plugin_obj.servers, + 'rest_plug_interface') as mock_plug_interface: + mock_plug_interface.side_effect = RemoteRestError('fake error') + kwargs = {'device_id': 'somedevid', + 'tenant_id': n['network']['tenant_id']} + self._create_port('json', n['network']['id'], + expected_code= + webob.exc.HTTPInternalServerError.code, + **kwargs) + port = self._get_ports(n['network']['id'])[0] + # Attachment failure should leave created port in error state + self.assertEqual('ERROR', port['status']) + self._delete('ports', port['id']) + + def test_rollback_for_port_update(self): + with self.network() as n: + with self.port(network_id=n['network']['id']) as port: + port = self._get_ports(n['network']['id'])[0] + data = {'port': {'name': 'aNewName'}} + self.httpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.httpPatch.start() + self.new_update_request('ports', + data, + port['id']).get_response(self.api) + self.httpPatch.stop() + uport = self._get_ports(n['network']['id'])[0] + # name should have stayed the same + self.assertEqual(port['name'], uport['name']) + + def test_rollback_for_port_detach(self): + with self.network() as n: + with self.port(network_id=n['network']['id'], + device_id='somedevid') as port: + self.httpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.httpPatch.start() + self._delete('ports', port['port']['id'], + expected_code= + webob.exc.HTTPInternalServerError.code) + self.httpPatch.stop() + port = self._get_ports(n['network']['id'])[0] + self.assertEqual('ACTIVE', port['status']) + + def test_rollback_for_port_delete(self): + with self.network() as n: + with self.port(network_id=n['network']['id'], + device_id='somdevid') as port: + plugin_obj = NeutronManager.get_plugin() + with patch.object(plugin_obj.servers, + 'rest_delete_port' + ) as mock_plug_interface: + mock_plug_interface.side_effect = RemoteRestError( + 'fake error') + self._delete('ports', port['port']['id'], + expected_code= + webob.exc.HTTPInternalServerError.code) + port = self._get_ports(n['network']['id'])[0] + self.assertEqual('ERROR', port['status']) + class TestBigSwitchProxyPortsV2IVS(test_plugin.TestPortsV2, BigSwitchProxyPluginV2TestCase, @@ -213,7 +235,45 @@ class TestBigSwitchVIFOverride(test_plugin.TestPortsV2, class TestBigSwitchProxyNetworksV2(test_plugin.TestNetworksV2, BigSwitchProxyPluginV2TestCase): - pass + def _get_networks(self, tenant_id): + ctx = context.Context('', tenant_id) + return NeutronManager.get_plugin().get_networks(ctx) + + def test_rollback_on_network_create(self): + tid = test_api_v2._uuid() + kwargs = {'tenant_id': tid} + self.httpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.httpPatch.start() + self._create_network('json', 'netname', True, **kwargs) + self.httpPatch.stop() + self.assertFalse(self._get_networks(tid)) + + def test_rollback_on_network_update(self): + with self.network() as n: + data = {'network': {'name': 'aNewName'}} + self.httpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.httpPatch.start() + self.new_update_request('networks', data, + n['network']['id']).get_response(self.api) + self.httpPatch.stop() + updatedn = self._get_networks(n['network']['tenant_id'])[0] + # name should have stayed the same due to failure + self.assertEqual(n['network']['name'], updatedn['name']) + + def test_rollback_on_network_delete(self): + with self.network() as n: + self.httpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.httpPatch.start() + self._delete('networks', n['network']['id'], + expected_code=webob.exc.HTTPInternalServerError.code) + self.httpPatch.stop() + # network should still exist in db + self.assertEqual(n['network']['id'], + self._get_networks(n['network']['tenant_id'] + )[0]['id']) class TestBigSwitchProxySubnetsV2(test_plugin.TestSubnetsV2, diff --git a/neutron/tests/unit/bigswitch/test_router_db.py b/neutron/tests/unit/bigswitch/test_router_db.py index cc044bddb..33c19d3ab 100644 --- a/neutron/tests/unit/bigswitch/test_router_db.py +++ b/neutron/tests/unit/bigswitch/test_router_db.py @@ -26,11 +26,14 @@ from oslo.config import cfg from webob import exc from neutron.common.test_lib import test_config +from neutron import context from neutron.extensions import l3 from neutron.manager import NeutronManager from neutron.openstack.common.notifier import api as notifier_api from neutron.openstack.common.notifier import test_notifier from neutron.plugins.bigswitch.extensions import routerrule +from neutron.tests.unit.bigswitch import fake_server +from neutron.tests.unit import test_api_v2 from neutron.tests.unit import test_extension_extradhcpopts as test_extradhcp from neutron.tests.unit import test_l3_plugin @@ -54,51 +57,6 @@ def new_L3_setUp(self): origSetUp = test_l3_plugin.L3NatDBTestCase.setUp -class HTTPResponseMock(): - status = 200 - reason = 'OK' - - def __init__(self, sock, debuglevel=0, strict=0, method=None, - buffering=False): - pass - - def read(self): - return "{'status': '200 OK'}" - - -class HTTPResponseMock500(): - status = 500 - reason = 'Internal Server Error' - - def __init__(self, sock, debuglevel=0, strict=0, method=None, - buffering=False, errmsg='500 Internal Server Error'): - self.errmsg = errmsg - - def read(self): - return "{'status': '%s'}" % self.errmsg - - -class HTTPConnectionMock(): - - def __init__(self, server, port, timeout): - self.response = None - - def request(self, action, uri, body, headers): - self.response = HTTPResponseMock(None) - # Port creations/updates must contain binding information - if ('port' in uri and 'attachment' not in uri - and 'binding' not in body and action in ('POST', 'PUT')): - errmsg = "Port binding info missing in port request '%s'" % body - self.response = HTTPResponseMock500(None, errmsg=errmsg) - return - - def getresponse(self): - return self.response - - def close(self): - pass - - class RouterRulesTestExtensionManager(object): def get_resources(self): @@ -117,7 +75,7 @@ class DHCPOptsTestCase(test_extradhcp.TestExtraDhcpOpt): def setUp(self, plugin=None): self.httpPatch = patch('httplib.HTTPConnection', create=True, - new=HTTPConnectionMock) + new=fake_server.HTTPConnectionMock) self.httpPatch.start() self.addCleanup(self.httpPatch.stop) p_path = 'neutron.plugins.bigswitch.plugin.NeutronRestProxyV2' @@ -128,7 +86,7 @@ class RouterDBTestCase(test_l3_plugin.L3NatDBTestCase): def setUp(self): self.httpPatch = patch('httplib.HTTPConnection', create=True, - new=HTTPConnectionMock) + new=fake_server.HTTPConnectionMock) self.httpPatch.start() test_l3_plugin.L3NatDBTestCase.setUp = new_L3_setUp super(RouterDBTestCase, self).setUp() @@ -498,6 +456,44 @@ class RouterDBTestCase(test_l3_plugin.L3NatDBTestCase): {'router': {'router_rules': rules}}, expected_code=exc.HTTPBadRequest.code) + def test_rollback_on_router_create(self): + tid = test_api_v2._uuid() + self.errhttpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.errhttpPatch.start() + self._create_router('json', tid) + self.errhttpPatch.stop() + self.assertTrue(len(self._get_routers(tid)) == 0) + + def test_rollback_on_router_update(self): + with self.router() as r: + data = {'router': {'name': 'aNewName'}} + self.errhttpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.errhttpPatch.start() + self.new_update_request('routers', data, + r['router']['id']).get_response(self.api) + self.errhttpPatch.stop() + updatedr = self._get_routers(r['router']['tenant_id'])[0] + # name should have stayed the same due to failure + self.assertEqual(r['router']['name'], updatedr['name']) + + def test_rollback_on_router_delete(self): + with self.router() as r: + self.errhttpPatch = patch('httplib.HTTPConnection', create=True, + new=fake_server.HTTPConnectionMock500) + self.errhttpPatch.start() + self._delete('routers', r['router']['id'], + expected_code=exc.HTTPInternalServerError.code) + self.errhttpPatch.stop() + self.assertEqual(r['router']['id'], + self._get_routers(r['router']['tenant_id'] + )[0]['id']) + + def _get_routers(self, tenant_id): + ctx = context.Context('', tenant_id) + return self.plugin_obj.get_routers(ctx) + def _strip_rule_ids(rules): cleaned = [] -- 2.45.2