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
This commit is contained in:
Kevin Benton 2013-08-22 20:17:00 -07:00
parent 09dad2f1fe
commit f3215f8d90
4 changed files with 615 additions and 550 deletions

View File

@ -68,6 +68,7 @@ from neutron.db import l3_db
from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.extensions import l3 from neutron.extensions import l3
from neutron.extensions import portbindings from neutron.extensions import portbindings
from neutron.openstack.common import excutils
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
from neutron.openstack.common import rpc from neutron.openstack.common import rpc
from neutron.plugins.bigswitch.db import porttracker_db from neutron.plugins.bigswitch.db import porttracker_db
@ -302,6 +303,10 @@ class ServerPool(object):
'server': (active_server.server, 'server': (active_server.server,
active_server.port), active_server.port),
'response': ret[3]}) '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 active_server.failed = True
# All servers failed, reset server list and try again next time # 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)}) s.port) for s in self.servers)})
return (0, None, None, None) return (0, None, None, None)
def get(self, resource, data='', headers=None, ignore_codes=[]): def rest_action(self, action, resource, data='', errstr='%s',
return self.rest_call('GET', resource, data, headers, ignore_codes) 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 put(self, resource, data, headers=None, ignore_codes=[]): def rest_create_router(self, tenant_id, router):
return self.rest_call('PUT', resource, data, headers, ignore_codes) resource = ROUTER_RESOURCE_PATH % tenant_id
data = {"router": router}
errstr = _("Unable to create remote router: %s")
self.rest_action('POST', resource, data, errstr)
def post(self, resource, data, headers=None, ignore_codes=[]): def rest_update_router(self, tenant_id, router, router_id):
return self.rest_call('POST', resource, data, headers, ignore_codes) 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 delete(self, resource, data='', headers=None, ignore_codes=[]): def rest_delete_router(self, tenant_id, router_id):
return self.rest_call('DELETE', resource, data, headers, ignore_codes) 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): class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
@ -413,32 +507,20 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
self._warn_on_state_status(network['network']) self._warn_on_state_status(network['network'])
# Validate args with context.session.begin(subtransactions=True):
tenant_id = self._get_tenant_id_for_create(context, network["network"]) # 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 # create network in DB
new_net = super(NeutronRestProxyV2, self).create_network(context, new_net = super(NeutronRestProxyV2, self).create_network(context,
network) network)
self._process_l3_create(context, new_net, network['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 # create network on the network controller
try: self.servers.rest_create_network(tenant_id, mapped_network)
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
# return created network # return created network
return new_net return new_net
@ -472,25 +554,12 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
session = context.session session = context.session
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
orig_net = super(NeutronRestProxyV2, self).get_network(context, new_net = super(NeutronRestProxyV2, self).update_network(
net_id) context, net_id, network)
new_net = super(NeutronRestProxyV2, self).update_network(context,
net_id,
network)
self._process_l3_update(context, new_net, network['network']) self._process_l3_update(context, new_net, network['network'])
# update network on network controller # update network on network controller
try: self._send_update_network(new_net, context)
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
return new_net return new_net
def delete_network(self, context, net_id): def delete_network(self, context, net_id):
@ -520,20 +589,11 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
if not only_auto_del: if not only_auto_del:
raise exceptions.NetworkInUse(net_id=net_id) raise exceptions.NetworkInUse(net_id=net_id)
with context.session.begin(subtransactions=True):
# 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])
ret_val = super(NeutronRestProxyV2, self).delete_network(context, ret_val = super(NeutronRestProxyV2, self).delete_network(context,
net_id) net_id)
self.servers.rest_delete_network(tenant_id, net_id)
return ret_val 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): def create_port(self, context, port):
"""Create a port, which is a connection point of a device """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")) LOG.debug(_("NeutronRestProxyV2: create_port() called"))
# Update DB # Update DB in new session so exceptions rollback changes
port["port"]["admin_state_up"] = False with context.session.begin(subtransactions=True):
dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) port["port"]["admin_state_up"] = False
new_port = super(NeutronRestProxyV2, self).create_port(context, port) dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
if (portbindings.HOST_ID in port['port'] new_port = super(NeutronRestProxyV2, self).create_port(context,
and 'id' in new_port): port)
porttracker_db.put_port_hostid(context, new_port['id'], if (portbindings.HOST_ID in port['port']
port['port'][portbindings.HOST_ID]) and 'id' in new_port):
self._process_port_create_extra_dhcp_opts(context, new_port, dhcp_opts) host_id = port['port'][portbindings.HOST_ID]
new_port = self._extend_port_dict_binding(context, new_port) porttracker_db.put_port_hostid(context, new_port['id'],
net = super(NeutronRestProxyV2, host_id)
self).get_network(context, new_port["network_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 self.add_meta_server_route:
if new_port['device_owner'] == 'network:dhcp': if new_port['device_owner'] == 'network:dhcp':
destination = METADATA_SERVER_IP + '/32' destination = METADATA_SERVER_IP + '/32'
self._add_host_route(context, destination, new_port) self._add_host_route(context, destination, new_port)
# create on networl ctrl # create on network ctrl
try:
resource = PORT_RESOURCE_PATH % (net["tenant_id"], net["id"])
mapped_port = self._map_state_and_status(new_port) mapped_port = self._map_state_and_status(new_port)
data = { self.servers.rest_create_port(net, mapped_port)
"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
# 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 # Set port state up and return that port
port_update = {"port": {"admin_state_up": True}} port_update = {"port": {"admin_state_up": True}}
new_port = super(NeutronRestProxyV2, self).update_port(context, new_port = super(NeutronRestProxyV2, self).update_port(context,
@ -660,44 +718,45 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
# Validate Args # Validate Args
orig_port = super(NeutronRestProxyV2, self).get_port(context, port_id) orig_port = super(NeutronRestProxyV2, self).get_port(context, 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 DB # update on networl ctrl
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)
mapped_port = self._map_state_and_status(new_port) mapped_port = self._map_state_and_status(new_port)
data = {"port": mapped_port} self.servers.rest_update_port(orig_port["tenant_id"],
ret = self.servers.put(resource, data) orig_port["network_id"],
if not self.servers.action_success(ret): mapped_port, port_id)
raise RemoteRestError(ret[2])
if new_port.get("device_id") != orig_port.get("device_id"): if (new_port.get("device_id") != orig_port.get("device_id") and
if orig_port.get("device_id"): orig_port.get("device_id")):
self._unplug_interface(context, orig_port["tenant_id"], try:
orig_port["network_id"], self.servers.rest_unplug_interface(orig_port["tenant_id"],
orig_port["id"]) orig_port["network_id"],
orig_port["id"])
device_id = new_port.get("device_id") device_id = new_port.get("device_id")
if device_id: if device_id:
self._plug_interface(context, new_port["tenant_id"], self.rest_plug_interface(new_port["tenant_id"],
new_port["network_id"], new_port["network_id"],
new_port["id"], device_id) new_port, device_id)
except RemoteRestError as e: except RemoteRestError:
LOG.error(_("NeutronRestProxyV2: Unable to create remote port: " with excutils.save_and_reraise_exception():
"%s"), e.message) port_update = {"port": {"status": "ERROR"}}
# reset port to original state super(NeutronRestProxyV2, self).update_port(
super(NeutronRestProxyV2, self).update_port(context, port_id, context,
orig_port) new_port["id"],
raise port_update
)
# return new_port # return new_port
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. # and l3-router. If so, we should prevent deletion.
if l3_port_check: if l3_port_check:
self.prevent_l3_port_deletion(context, port_id) 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):
def _delete_port(self, context, port_id):
# Delete from DB
port = super(NeutronRestProxyV2, self).get_port(context, port_id) port = super(NeutronRestProxyV2, self).get_port(context, port_id)
tenant_id = port['tenant_id'] tenant_id = port['tenant_id']
net_id = port['network_id']
if tenant_id == '': if tenant_id == '':
net = super(NeutronRestProxyV2, net = super(NeutronRestProxyV2, self).get_network(context, net_id)
self).get_network(context, port['network_id'])
tenant_id = net['tenant_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)
# delete from network ctrl. Remote error on delete is ignored def _delete_port(self, context, port_id):
try: port = super(NeutronRestProxyV2, self).get_port(context, port_id)
resource = PORTS_PATH % (tenant_id, port["network_id"], tenant_id = port['tenant_id']
port_id) net_id = port['network_id']
ret = self.servers.delete(resource) if tenant_id == '':
if not self.servers.action_success(ret): net = super(NeutronRestProxyV2, self).get_network(context, net_id)
raise RemoteRestError(ret[2]) tenant_id = net['tenant_id']
# Delete from DB
if port.get("device_id"): ret_val = super(NeutronRestProxyV2,
self._unplug_interface(context, tenant_id, self)._delete_port(context, port_id)
port["network_id"], port["id"]) self.servers.rest_delete_port(tenant_id, net_id, port_id)
ret_val = super(NeutronRestProxyV2, self)._delete_port(context, return ret_val
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
def create_subnet(self, context, subnet): def create_subnet(self, context, subnet):
LOG.debug(_("NeutronRestProxyV2: create_subnet() called")) LOG.debug(_("NeutronRestProxyV2: create_subnet() called"))
self._warn_on_state_status(subnet['subnet']) self._warn_on_state_status(subnet['subnet'])
# create subnet in DB with context.session.begin(subtransactions=True):
new_subnet = super(NeutronRestProxyV2, self).create_subnet(context, # create subnet in DB
subnet) new_subnet = super(NeutronRestProxyV2,
net_id = new_subnet['network_id'] self).create_subnet(context, subnet)
orig_net = super(NeutronRestProxyV2, self).get_network(context, net_id = new_subnet['network_id']
net_id) orig_net = super(NeutronRestProxyV2,
# update network on network controller self).get_network(context, net_id)
try: # update network on network controller
self._send_update_network(orig_net) self._send_update_network(orig_net)
except RemoteRestError:
# rollback creation of subnet
super(NeutronRestProxyV2, self).delete_subnet(context,
subnet['id'])
raise
return new_subnet return new_subnet
def update_subnet(self, context, id, 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']) self._warn_on_state_status(subnet['subnet'])
orig_subnet = super(NeutronRestProxyV2, self)._get_subnet(context, id) with context.session.begin(subtransactions=True):
# update subnet in DB
# update subnet in DB new_subnet = super(NeutronRestProxyV2,
new_subnet = super(NeutronRestProxyV2, self).update_subnet(context, id, self).update_subnet(context, id, subnet)
subnet) net_id = new_subnet['network_id']
net_id = new_subnet['network_id'] orig_net = super(NeutronRestProxyV2,
orig_net = super(NeutronRestProxyV2, self).get_network(context, self).get_network(context, net_id)
net_id) # update network on network controller
# update network on network controller
try:
self._send_update_network(orig_net) self._send_update_network(orig_net)
except RemoteRestError: return new_subnet
# rollback updation of subnet
super(NeutronRestProxyV2, self).update_subnet(context, id,
orig_subnet)
raise
return new_subnet
def delete_subnet(self, context, id): def delete_subnet(self, context, id):
LOG.debug(_("NeutronRestProxyV2: delete_subnet() called")) LOG.debug(_("NeutronRestProxyV2: delete_subnet() called"))
orig_subnet = super(NeutronRestProxyV2, self).get_subnet(context, id) orig_subnet = super(NeutronRestProxyV2, self).get_subnet(context, id)
net_id = orig_subnet['network_id'] net_id = orig_subnet['network_id']
# delete subnet in DB with context.session.begin(subtransactions=True):
super(NeutronRestProxyV2, self).delete_subnet(context, id) # delete subnet in DB
orig_net = super(NeutronRestProxyV2, self).get_network(context, super(NeutronRestProxyV2, self).delete_subnet(context, id)
net_id) orig_net = super(NeutronRestProxyV2, self).get_network(context,
# update network on network controller net_id)
try: # update network on network controller - exception will rollback
self._send_update_network(orig_net) self._send_update_network(orig_net)
except RemoteRestError:
# TODO(Sumit): rollback deletion of subnet
raise
def _get_tenant_default_router_rules(self, tenant): def _get_tenant_default_router_rules(self, tenant):
rules = cfg.CONF.ROUTER.tenant_default_router_rule 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) rules = self._get_tenant_default_router_rules(tenant_id)
router['router']['router_rules'] = rules router['router']['router_rules'] = rules
# create router in DB with context.session.begin(subtransactions=True):
new_router = super(NeutronRestProxyV2, self).create_router(context, # create router in DB
router) new_router = super(NeutronRestProxyV2, self).create_router(context,
router)
# create router on the network controller
try:
resource = ROUTER_RESOURCE_PATH % tenant_id
mapped_router = self._map_state_and_status(new_router) mapped_router = self._map_state_and_status(new_router)
data = { self.servers.rest_create_router(tenant_id, mapped_router)
"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 created router
return new_router return new_router
def update_router(self, context, router_id, 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, orig_router = super(NeutronRestProxyV2, self).get_router(context,
router_id) router_id)
tenant_id = orig_router["tenant_id"] tenant_id = orig_router["tenant_id"]
new_router = super(NeutronRestProxyV2, self).update_router(context, with context.session.begin(subtransactions=True):
router_id, new_router = super(NeutronRestProxyV2,
router) self).update_router(context, router_id, router)
router = self._map_state_and_status(new_router)
# update router on network controller # update router on network controller
try: self.servers.rest_update_router(tenant_id, router, router_id)
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
# return updated router # return updated router
return new_router return new_router
def delete_router(self, context, router_id): def delete_router(self, context, router_id):
LOG.debug(_("NeutronRestProxyV2: delete_router() called")) LOG.debug(_("NeutronRestProxyV2: delete_router() called"))
@ -985,20 +947,12 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
filters=device_filter) filters=device_filter)
if ports: if ports:
raise l3.RouterInUse(router_id=router_id) 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 # delete from network ctrl
try: self.servers.rest_delete_router(tenant_id, router_id)
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)
return ret_val 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): 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) router = self._get_router(context, router_id)
tenant_id = router['tenant_id'] tenant_id = router['tenant_id']
# create interface in DB with context.session.begin(subtransactions=True):
new_interface_info = super(NeutronRestProxyV2, # create interface in DB
self).add_router_interface(context, new_intf_info = super(NeutronRestProxyV2,
router_id, self).add_router_interface(context,
interface_info) router_id,
port = self._get_port(context, new_interface_info['port_id']) interface_info)
net_id = port['network_id'] port = self._get_port(context, new_intf_info['port_id'])
subnet_id = new_interface_info['subnet_id'] net_id = port['network_id']
# we will use the port's network id as interface's id subnet_id = new_intf_info['subnet_id']
interface_id = net_id # we will use the port's network id as interface's id
intf_details = self._get_router_intf_details(context, interface_id = net_id
interface_id, intf_details = self._get_router_intf_details(context,
subnet_id) interface_id,
subnet_id)
# create interface on the network controller # create interface on the network controller
try: self.servers.rest_add_router_interface(tenant_id, router_id,
resource = ROUTER_INTF_OP_PATH % (tenant_id, router_id) intf_details)
data = {"interface": intf_details} return new_intf_info
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
def remove_router_interface(self, context, router_id, interface_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" msg = "Either subnet_id or port_id must be specified"
raise exceptions.BadRequest(resource='router', msg=msg) raise exceptions.BadRequest(resource='router', msg=msg)
# remove router in DB with context.session.begin(subtransactions=True):
del_intf_info = super(NeutronRestProxyV2, # remove router in DB
self).remove_router_interface(context, del_ret = super(NeutronRestProxyV2,
router_id, self).remove_router_interface(context,
interface_info) router_id,
interface_info)
# create router on the network controller # create router on the network controller
try: self.servers.rest_remove_router_interface(tenant_id, router_id,
resource = ROUTER_INTF_PATH % (tenant_id, router_id, interface_id) interface_id)
ret = self.servers.delete(resource) return del_ret
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
def create_floatingip(self, context, floatingip): def create_floatingip(self, context, floatingip):
LOG.debug(_("NeutronRestProxyV2: create_floatingip() called")) LOG.debug(_("NeutronRestProxyV2: create_floatingip() called"))
# create floatingip in DB with context.session.begin(subtransactions=True):
new_fl_ip = super(NeutronRestProxyV2, # create floatingip in DB
self).create_floatingip(context, floatingip) new_fl_ip = super(NeutronRestProxyV2,
self).create_floatingip(context, floatingip)
net_id = new_fl_ip['floating_network_id'] net_id = new_fl_ip['floating_network_id']
orig_net = super(NeutronRestProxyV2, self).get_network(context, orig_net = super(NeutronRestProxyV2, self).get_network(context,
net_id) net_id)
# create floatingip on the network controller # create floatingip on the network controller
try: try:
self._send_update_network(orig_net) self._send_update_network(orig_net)
except RemoteRestError as e: except RemoteRestError as e:
LOG.error(_("NeutronRestProxyV2: Unable to create remote " with excutils.save_and_reraise_exception():
"floatin IP: %s"), e.message) LOG.error(
super(NeutronRestProxyV2, self).delete_floatingip(context, _("NeutronRestProxyV2: Unable to create remote "
floatingip) "floating IP: %s"), e)
raise # return created floating IP
return new_fl_ip
# return created floating IP
return new_fl_ip
def update_floatingip(self, context, id, floatingip): def update_floatingip(self, context, id, floatingip):
LOG.debug(_("NeutronRestProxyV2: update_floatingip() called")) LOG.debug(_("NeutronRestProxyV2: update_floatingip() called"))
orig_fl_ip = super(NeutronRestProxyV2, self).get_floatingip(context, with context.session.begin(subtransactions=True):
id) # update floatingip in DB
new_fl_ip = super(NeutronRestProxyV2,
self).update_floatingip(context, id, floatingip)
# update floatingip in DB net_id = new_fl_ip['floating_network_id']
new_fl_ip = super(NeutronRestProxyV2, orig_net = super(NeutronRestProxyV2, self).get_network(context,
self).update_floatingip(context, id, floatingip) net_id)
# update network on network controller
net_id = new_fl_ip['floating_network_id']
orig_net = super(NeutronRestProxyV2, self).get_network(context,
net_id)
# update network on network controller
try:
self._send_update_network(orig_net) self._send_update_network(orig_net)
except RemoteRestError: return new_fl_ip
# rollback updation of subnet
super(NeutronRestProxyV2, self).update_floatingip(context, id,
orig_fl_ip)
raise
return new_fl_ip
def delete_floatingip(self, context, id): def delete_floatingip(self, context, id):
LOG.debug(_("NeutronRestProxyV2: delete_floatingip() called")) LOG.debug(_("NeutronRestProxyV2: delete_floatingip() called"))
orig_fl_ip = super(NeutronRestProxyV2, self).get_floatingip(context, orig_fl_ip = super(NeutronRestProxyV2, self).get_floatingip(context,
id) id)
# delete floating IP in DB with context.session.begin(subtransactions=True):
net_id = orig_fl_ip['floating_network_id'] # delete floating IP in DB
super(NeutronRestProxyV2, self).delete_floatingip(context, id) net_id = orig_fl_ip['floating_network_id']
super(NeutronRestProxyV2, self).delete_floatingip(context, id)
orig_net = super(NeutronRestProxyV2, self).get_network(context, orig_net = super(NeutronRestProxyV2, self).get_network(context,
net_id) net_id)
# update network on network controller # update network on network controller
try:
self._send_update_network(orig_net) self._send_update_network(orig_net)
except RemoteRestError:
# TODO(Sumit): rollback deletion of floating IP
raise
def _send_all_data(self): def _send_all_data(self):
"""Pushes all data to network ctrl (networks/ports, ports/attachments). """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) routers.append(mapped_router)
try: resource = '/topology'
resource = '/topology' data = {
data = { 'networks': networks,
'networks': networks, 'routers': routers,
'routers': routers, }
} errstr = _("Unable to update remote topology: %s")
ret = self.servers.put(resource, data) return self.servers.rest_action('PUT', resource, data, errstr)
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
def _add_host_route(self, context, destination, port): def _add_host_route(self, context, destination, port):
subnet = {} subnet = {}
@ -1232,22 +1148,26 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
LOG.debug("destination:%s nexthop:%s" % (destination, LOG.debug("destination:%s nexthop:%s" % (destination,
nexthop)) nexthop))
def _get_network_with_floatingips(self, network): def _get_network_with_floatingips(self, network, context=None):
admin_context = qcontext.get_admin_context() if context is None:
context = qcontext.get_admin_context()
net_id = network['id'] net_id = network['id']
net_filter = {'floating_network_id': [net_id]} net_filter = {'floating_network_id': [net_id]}
fl_ips = super(NeutronRestProxyV2, fl_ips = super(NeutronRestProxyV2,
self).get_floatingips(admin_context, self).get_floatingips(context,
filters=net_filter) or [] filters=net_filter) or []
network['floatingips'] = fl_ips network['floatingips'] = fl_ips
return network return network
def _get_all_subnets_json_for_network(self, net_id): def _get_all_subnets_json_for_network(self, net_id, context=None):
admin_context = qcontext.get_admin_context() if context is None:
subnets = self._get_subnets_by_network(admin_context, context = qcontext.get_admin_context()
net_id) # 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 = [] subnets_details = []
if subnets: if subnets:
for subnet in subnets: for subnet in subnets:
@ -1257,10 +1177,13 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
return subnets_details return subnets_details
def _get_mapped_network_with_subnets(self, network): def _get_mapped_network_with_subnets(self, network, context=None):
admin_context = qcontext.get_admin_context() # 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) 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 network['subnets'] = subnets
for subnet in (subnets or []): for subnet in (subnets or []):
if subnet['gateway_ip']: if subnet['gateway_ip']:
@ -1269,30 +1192,20 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
break break
else: else:
network['gateway'] = '' network['gateway'] = ''
network[l3.EXTERNAL] = self._network_is_external(context,
network[l3.EXTERNAL] = self._network_is_external(admin_context,
network['id']) network['id'])
return network return network
def _send_update_network(self, network): def _send_update_network(self, network, context=None):
net_id = network['id'] net_id = network['id']
tenant_id = network['tenant_id'] tenant_id = network['tenant_id']
# update network on network controller # update network on network controller
try: mapped_network = self._get_mapped_network_with_subnets(network,
resource = NETWORKS_PATH % (tenant_id, net_id) context)
mapped_network = self._get_mapped_network_with_subnets(network) net_fl_ips = self._get_network_with_floatingips(mapped_network,
net_fl_ips = self._get_network_with_floatingips(mapped_network) context)
data = { self.servers.rest_update_network(tenant_id, net_id, net_fl_ips)
"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
def _map_state_and_status(self, resource): def _map_state_and_status(self, resource):
resource = copy.copy(resource) resource = copy.copy(resource)

View File

@ -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, <kevin.benton@bigswitch.com>
#
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

View File

@ -22,79 +22,18 @@ from oslo.config import cfg
import webob.exc import webob.exc
import neutron.common.test_lib as test_lib import neutron.common.test_lib as test_lib
from neutron import context
from neutron.extensions import portbindings from neutron.extensions import portbindings
from neutron.manager import NeutronManager 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 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 import neutron.tests.unit.test_db_plugin as test_plugin
RESTPROXY_PKG_PATH = 'neutron.plugins.bigswitch.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): class BigSwitchProxyPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
_plugin_name = ('%s.NeutronRestProxyV2' % RESTPROXY_PKG_PATH) _plugin_name = ('%s.NeutronRestProxyV2' % RESTPROXY_PKG_PATH)
@ -105,7 +44,7 @@ class BigSwitchProxyPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
'restproxy.ini.test')] 'restproxy.ini.test')]
self.httpPatch = patch('httplib.HTTPConnection', create=True, self.httpPatch = patch('httplib.HTTPConnection', create=True,
new=HTTPConnectionMock) new=fake_server.HTTPConnectionMock)
self.addCleanup(self.httpPatch.stop) self.addCleanup(self.httpPatch.stop)
self.httpPatch.start() self.httpPatch.start()
super(BigSwitchProxyPluginV2TestCase, super(BigSwitchProxyPluginV2TestCase,
@ -138,6 +77,89 @@ class TestBigSwitchProxyPortsV2(test_plugin.TestPortsV2,
VIF_TYPE = portbindings.VIF_TYPE_OVS VIF_TYPE = portbindings.VIF_TYPE_OVS
HAS_PORT_FILTER = False 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, class TestBigSwitchProxyPortsV2IVS(test_plugin.TestPortsV2,
BigSwitchProxyPluginV2TestCase, BigSwitchProxyPluginV2TestCase,
@ -213,7 +235,45 @@ class TestBigSwitchVIFOverride(test_plugin.TestPortsV2,
class TestBigSwitchProxyNetworksV2(test_plugin.TestNetworksV2, class TestBigSwitchProxyNetworksV2(test_plugin.TestNetworksV2,
BigSwitchProxyPluginV2TestCase): 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, class TestBigSwitchProxySubnetsV2(test_plugin.TestSubnetsV2,

View File

@ -26,11 +26,14 @@ from oslo.config import cfg
from webob import exc from webob import exc
from neutron.common.test_lib import test_config from neutron.common.test_lib import test_config
from neutron import context
from neutron.extensions import l3 from neutron.extensions import l3
from neutron.manager import NeutronManager from neutron.manager import NeutronManager
from neutron.openstack.common.notifier import api as notifier_api from neutron.openstack.common.notifier import api as notifier_api
from neutron.openstack.common.notifier import test_notifier from neutron.openstack.common.notifier import test_notifier
from neutron.plugins.bigswitch.extensions import routerrule 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_extension_extradhcpopts as test_extradhcp
from neutron.tests.unit import test_l3_plugin from neutron.tests.unit import test_l3_plugin
@ -54,51 +57,6 @@ def new_L3_setUp(self):
origSetUp = test_l3_plugin.L3NatDBTestCase.setUp 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): class RouterRulesTestExtensionManager(object):
def get_resources(self): def get_resources(self):
@ -117,7 +75,7 @@ class DHCPOptsTestCase(test_extradhcp.TestExtraDhcpOpt):
def setUp(self, plugin=None): def setUp(self, plugin=None):
self.httpPatch = patch('httplib.HTTPConnection', create=True, self.httpPatch = patch('httplib.HTTPConnection', create=True,
new=HTTPConnectionMock) new=fake_server.HTTPConnectionMock)
self.httpPatch.start() self.httpPatch.start()
self.addCleanup(self.httpPatch.stop) self.addCleanup(self.httpPatch.stop)
p_path = 'neutron.plugins.bigswitch.plugin.NeutronRestProxyV2' p_path = 'neutron.plugins.bigswitch.plugin.NeutronRestProxyV2'
@ -128,7 +86,7 @@ class RouterDBTestCase(test_l3_plugin.L3NatDBTestCase):
def setUp(self): def setUp(self):
self.httpPatch = patch('httplib.HTTPConnection', create=True, self.httpPatch = patch('httplib.HTTPConnection', create=True,
new=HTTPConnectionMock) new=fake_server.HTTPConnectionMock)
self.httpPatch.start() self.httpPatch.start()
test_l3_plugin.L3NatDBTestCase.setUp = new_L3_setUp test_l3_plugin.L3NatDBTestCase.setUp = new_L3_setUp
super(RouterDBTestCase, self).setUp() super(RouterDBTestCase, self).setUp()
@ -498,6 +456,44 @@ class RouterDBTestCase(test_l3_plugin.L3NatDBTestCase):
{'router': {'router_rules': rules}}, {'router': {'router_rules': rules}},
expected_code=exc.HTTPBadRequest.code) 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): def _strip_rule_ids(rules):
cleaned = [] cleaned = []