L3 API support for BigSwitch-FloodLight Plugin

In keeping with the philosophy  of the RESTProxy plugin, L3 extension calls
are processed (CRUD of logical resources) and the state changes are proxied
to a backend controller.

A configuration variable specific to the RESTProxy plugin is being added
to identify that particular Quantum server's ID.

blueprint quantum-floodlight-bigswitch-l3

Change-Id: I24be02cd836352497fe5b1e7622d138e0c816377
This commit is contained in:
Sumit Naiksatam 2013-02-05 20:25:46 -08:00
parent a7837e5f90
commit 6020c897ab
5 changed files with 942 additions and 126 deletions

View File

@ -28,13 +28,13 @@ reconnect_interval = 2
#
# The following parameters are supported:
# servers : <host:port>[,<host:port>]* (Error if not set)
# serverauth : <username:password> (default: no auth)
# serverssl : True | False (default: False)
# syncdata : True | False (default: False)
# servertimeout : 10 (default: 10 seconds)
# server_auth : <username:password> (default: no auth)
# server_ssl : True | False (default: False)
# sync_data : True | False (default: False)
# server_timeout : 10 (default: 10 seconds)
#
servers=localhost:8080
#serverauth=username:password
#serverssl=True
#syncdata=True
#servertimeout=10
#server_auth=username:password
#server_ssl=True
#sync_data=True
#server_timeout=10

View File

@ -45,18 +45,24 @@ on port-attach) on an additional PUT to do a bulk dump of all persistent data.
"""
import base64
import copy
import httplib
import json
import socket
from quantum.common import constants as const
from quantum.common import exceptions
from quantum.common import rpc as q_rpc
from quantum.common import topics
from quantum.common import utils
from quantum import context as qcontext
from quantum.db import api as db
from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base
from quantum.db import l3_db
from quantum.extensions import l3
from quantum.openstack.common import cfg
from quantum.openstack.common import lockutils
from quantum.openstack.common import log as logging
from quantum.openstack.common import rpc
from quantum.plugins.bigswitch.version import version_string_with_vcs
@ -69,16 +75,20 @@ restproxy_opts = [
cfg.StrOpt('servers', default='localhost:8800',
help=_("A comma separated list of servers and port numbers "
"to proxy request to.")),
cfg.StrOpt('serverauth', default='username:password',
help=_("Server authentication"),
secret=True),
cfg.BoolOpt('serverssl', default=False,
cfg.StrOpt('server_auth', default='username:password', secret=True,
help=_("Server authentication")),
cfg.BoolOpt('server_ssl', default=False,
help=_("Use SSL to connect")),
cfg.BoolOpt('syncdata', default=False,
cfg.BoolOpt('sync_data', default=False,
help=_("Sync data on connect")),
cfg.IntOpt('servertimeout', default=10,
cfg.IntOpt('server_timeout', default=10,
help=_("Maximum number of seconds to wait for proxy request "
"to connect and complete.")),
cfg.StrOpt('quantum_id', default='Quantum-' + utils.get_hostname(),
help=_("User defined identifier for this Quantum deployment")),
cfg.BoolOpt('add_meta_server_route', default=True,
help=_("Flag to decide if a route to the metadata server "
"should be injected into the VM")),
]
@ -88,13 +98,20 @@ cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
# The following are used to invoke the API on the external controller
NET_RESOURCE_PATH = "/tenants/%s/networks"
PORT_RESOURCE_PATH = "/tenants/%s/networks/%s/ports"
ROUTER_RESOURCE_PATH = "/tenants/%s/routers"
ROUTER_INTF_OP_PATH = "/tenants/%s/routers/%s/interfaces"
NETWORKS_PATH = "/tenants/%s/networks/%s"
PORTS_PATH = "/tenants/%s/networks/%s/ports/%s"
ATTACHMENT_PATH = "/tenants/%s/networks/%s/ports/%s/attachment"
ROUTERS_PATH = "/tenants/%s/routers/%s"
ROUTER_INTF_PATH = "/tenants/%s/routers/%s/interfaces/%s"
SUCCESS_CODES = range(200, 207)
FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503,
504, 505]
SYNTAX_ERROR_MESSAGE = 'Syntax error in server config file, aborting plugin'
BASE_URI = '/networkService/v1.1'
ORCHESTRATION_SERVICE_ID = 'Quantum v2.0'
METADATA_SERVER_IP = '169.254.169.254'
class RemoteRestError(exceptions.QuantumException):
@ -109,7 +126,8 @@ class RemoteRestError(exceptions.QuantumException):
class ServerProxy(object):
"""REST server proxy to a network controller."""
def __init__(self, server, port, ssl, auth, timeout, base_uri, name):
def __init__(self, server, port, ssl, auth, quantum_id, timeout,
base_uri, name):
self.server = server
self.port = port
self.ssl = ssl
@ -118,9 +136,11 @@ class ServerProxy(object):
self.name = name
self.success_codes = SUCCESS_CODES
self.auth = None
self.quantum_id = quantum_id
if auth:
self.auth = 'Basic ' + base64.encodestring(auth).strip()
@lockutils.synchronized('rest_call', 'bsn-', external=True)
def rest_call(self, action, resource, data, headers):
uri = self.base_uri + resource
body = json.dumps(data)
@ -129,6 +149,8 @@ class ServerProxy(object):
headers['Content-type'] = 'application/json'
headers['Accept'] = 'application/json'
headers['QuantumProxy-Agent'] = self.name
headers['Instance-ID'] = self.quantum_id
headers['Orchestration-Service-ID'] = ORCHESTRATION_SERVICE_ID
if self.auth:
headers['Authorization'] = self.auth
@ -180,20 +202,21 @@ class ServerProxy(object):
class ServerPool(object):
def __init__(self, servers, ssl, auth, timeout=10,
def __init__(self, servers, ssl, auth, quantum_id, timeout=10,
base_uri='/quantum/v1.0', name='QuantumRestProxy'):
self.base_uri = base_uri
self.timeout = timeout
self.name = name
self.auth = auth
self.ssl = ssl
self.quantum_id = quantum_id
self.servers = []
for server_port in servers:
self.servers.append(self.server_proxy_for(*server_port))
def server_proxy_for(self, server, port):
return ServerProxy(server, port, self.ssl, self.auth, self.timeout,
self.base_uri, self.name)
return ServerProxy(server, port, self.ssl, self.auth, self.quantum_id,
self.timeout, self.base_uri, self.name)
def server_failure(self, resp):
"""Define failure codes as required.
@ -254,7 +277,10 @@ class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
return q_rpc.PluginRpcDispatcher([self])
class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2,
l3_db.L3_NAT_db_mixin):
supported_extension_aliases = ["router"]
def __init__(self):
LOG.info(_('QuantumRestProxy: Starting plugin. Version=%s'),
@ -265,12 +291,14 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
# 'servers' is the list of network controller REST end-points
# (used in order specified till one suceeds, and it is sticky
# till next failure). Use 'serverauth' to encode api-key
# till next failure). Use 'server_auth' to encode api-key
servers = cfg.CONF.RESTPROXY.servers
serverauth = cfg.CONF.RESTPROXY.serverauth
serverssl = cfg.CONF.RESTPROXY.serverssl
syncdata = cfg.CONF.RESTPROXY.syncdata
timeout = cfg.CONF.RESTPROXY.servertimeout
server_auth = cfg.CONF.RESTPROXY.server_auth
server_ssl = cfg.CONF.RESTPROXY.server_ssl
sync_data = cfg.CONF.RESTPROXY.sync_data
timeout = cfg.CONF.RESTPROXY.server_timeout
quantum_id = cfg.CONF.RESTPROXY.quantum_id
self.add_meta_server_route = cfg.CONF.RESTPROXY.add_meta_server_route
# validate config
assert servers is not None, 'Servers not defined. Aborting plugin'
@ -279,8 +307,8 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
assert all(len(s) == 2 for s in servers), SYNTAX_ERROR_MESSAGE
# init network ctrl connections
self.servers = ServerPool(servers, serverssl, serverauth,
timeout)
self.servers = ServerPool(servers, server_ssl, server_auth, quantum_id,
timeout, BASE_URI)
# init dhcp support
self.topic = topics.PLUGIN
@ -291,7 +319,7 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
fanout=False)
# Consume from all consumers in a thread
self.conn.consume_in_thread()
if syncdata:
if sync_data:
self._send_all_data()
LOG.debug(_("QuantumRestProxyV2: initialization done"))
@ -320,26 +348,25 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
LOG.debug(_("QuantumRestProxyV2: create_network() called"))
self._warn_on_state_status(network['network'])
# Validate args
tenant_id = self._get_tenant_id_for_create(context, network["network"])
net_name = network["network"]["name"]
if network["network"]["admin_state_up"] is False:
LOG.warning(_("Network with admin_state_up=False are not yet "
"supported by this plugin. Ignoring setting for "
"network %s"), net_name)
# create in DB
new_net = super(QuantumRestProxyV2, self).create_network(context,
network)
session = context.session
with session.begin(subtransactions=True):
# create network in DB
new_net = super(QuantumRestProxyV2, self).create_network(context,
network)
self._process_l3_create(context, network['network'], new_net['id'])
self._extend_network_dict_l3(context, new_net)
# create on networl ctrl
# 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": {
"id": new_net["id"],
"name": new_net["name"],
}
"network": mapped_network
}
ret = self.servers.post(resource, data)
if not self.servers.action_success(ret):
@ -379,36 +406,28 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
LOG.debug(_("QuantumRestProxyV2.update_network() called"))
# Validate Args
if network["network"].get("admin_state_up"):
if network["network"]["admin_state_up"] is False:
LOG.warning(_("Network with admin_state_up=False are not yet "
"supported by this plugin. Ignoring setting for "
"network %s", net_name))
self._warn_on_state_status(network['network'])
# update DB
orig_net = super(QuantumRestProxyV2, self).get_network(context, net_id)
tenant_id = orig_net["tenant_id"]
new_net = super(QuantumRestProxyV2, self).update_network(
context, net_id, network)
session = context.session
with session.begin(subtransactions=True):
orig_net = super(QuantumRestProxyV2, self).get_network(context,
net_id)
new_net = super(QuantumRestProxyV2, self).update_network(context,
net_id,
network)
self._process_l3_update(context, network['network'], net_id)
self._extend_network_dict_l3(context, new_net)
# update network on network controller
if new_net["name"] != orig_net["name"]:
try:
resource = NETWORKS_PATH % (tenant_id, net_id)
data = {
"network": new_net,
}
ret = self.servers.put(resource, data)
if not self.servers.action_success(ret):
raise RemoteRestError(ret[2])
except RemoteRestError as e:
LOG.error(_("QuantumRestProxyV2: Unable to update remote "
"network: %s"), e.message)
# reset network to original state
super(QuantumRestProxyV2, self).update_network(
context, id, orig_net)
raise
try:
self._send_update_network(new_net)
except RemoteRestError as e:
LOG.error(_("QuantumRestProxyV2: Unable to update remote "
"network: %s"), e.message)
# reset network to original state
super(QuantumRestProxyV2, self).update_network(context, id,
orig_net)
raise
# return updated network
return new_net
@ -430,6 +449,17 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
orig_net = super(QuantumRestProxyV2, self).get_network(context, net_id)
tenant_id = orig_net["tenant_id"]
filter = {'network_id': [net_id]}
ports = self.get_ports(context, filters=filter)
# check if there are any tenant owned ports in-use
auto_delete_port_owners = db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS
only_auto_del = all(p['device_owner'] in auto_delete_port_owners
for p in ports)
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)
@ -442,6 +472,7 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
except RemoteRestError as e:
LOG.error(_("QuantumRestProxyV2: 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
@ -477,24 +508,28 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
net = super(QuantumRestProxyV2,
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": {
"id": new_port["id"],
"state": "ACTIVE",
}
"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
if port["port"].get("device_id"):
device_id = port["port"].get("device_id")
if device_id:
self._plug_interface(context,
net["tenant_id"], net["id"],
new_port["id"], new_port["id"] + "00")
new_port["id"], device_id)
except RemoteRestError as e:
LOG.error(_("QuantumRestProxyV2: Unable to create remote port: "
"%s"), e.message)
@ -536,6 +571,8 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
"""
LOG.debug(_("QuantumRestProxyV2: update_port() called"))
self._warn_on_state_status(port['port'])
# Validate Args
orig_port = super(QuantumRestProxyV2, self).get_port(context, port_id)
@ -547,7 +584,8 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
try:
resource = PORTS_PATH % (orig_port["tenant_id"],
orig_port["network_id"], port_id)
data = {"port": new_port, }
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])
@ -557,10 +595,11 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
self._unplug_interface(context, orig_port["tenant_id"],
orig_port["network_id"],
orig_port["id"])
if new_port.get("device_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"], new_port["id"] + "00")
new_port["id"], device_id)
except RemoteRestError as e:
LOG.error(_("QuantumRestProxyV2: Unable to create remote port: "
@ -573,7 +612,7 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
# return new_port
return new_port
def delete_port(self, context, port_id):
def delete_port(self, context, port_id, l3_port_check=True):
"""Delete a port.
:param context: quantum api request context
:param id: UUID representing the port to delete.
@ -586,6 +625,15 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
LOG.debug(_("QuantumRestProxyV2: delete_port() called"))
# if needed, check to see if this is a port owned by
# 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)
super(QuantumRestProxyV2, self).delete_port(context, port_id)
def _delete_port(self, context, port_id):
# Delete from DB
port = super(QuantumRestProxyV2, self).get_port(context, port_id)
@ -600,12 +648,13 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
if port.get("device_id"):
self._unplug_interface(context, port["tenant_id"],
port["network_id"], port["id"])
ret_val = super(QuantumRestProxyV2, self).delete_port(context,
port_id)
ret_val = super(QuantumRestProxyV2, self)._delete_port(context,
port_id)
return ret_val
except RemoteRestError as e:
LOG.error(_("QuantumRestProxyV2: Unable to update remote port: "
"%s"), e.message)
raise
def _plug_interface(self, context, tenant_id, net_id, port_id,
remote_interface_id):
@ -625,22 +674,6 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
port = super(QuantumRestProxyV2, self).get_port(context, port_id)
mac = port["mac_address"]
for ip in port["fixed_ips"]:
if ip.get("subnet_id") is not None:
subnet = super(QuantumRestProxyV2, self).get_subnet(
context, ip["subnet_id"])
gateway = subnet.get("gateway_ip")
if gateway is not None:
resource = NETWORKS_PATH % (tenant_id, net_id)
data = {"network":
{"id": net_id,
"gateway": gateway,
}
}
ret = self.servers.put(resource, data)
if not self.servers.action_success(ret):
raise RemoteRestError(ret[2])
if mac is not None:
resource = ATTACHMENT_PATH % (tenant_id, net_id, port_id)
data = {"attachment":
@ -676,31 +709,326 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
LOG.error(_("QuantumRestProxyV2: Unable to update remote port: "
"%s"), e.message)
def create_subnet(self, context, subnet):
LOG.debug(_("QuantumRestProxyV2: create_subnet() called"))
self._warn_on_state_status(subnet['subnet'])
# create subnet in DB
new_subnet = super(QuantumRestProxyV2, self).create_subnet(context,
subnet)
net_id = new_subnet['network_id']
orig_net = super(QuantumRestProxyV2, self).get_network(context,
net_id)
# update network on network controller
try:
self._send_update_network(orig_net)
except RemoteRestError as e:
# rollback creation of subnet
super(QuantumRestProxyV2, self).delete_subnet(context,
subnet['id'])
raise
return new_subnet
def update_subnet(self, context, id, subnet):
LOG.debug(_("QuantumRestProxyV2: update_subnet() called"))
self._warn_on_state_status(subnet['subnet'])
orig_subnet = super(QuantumRestProxyV2, self)._get_subnet(context, id)
# update subnet in DB
new_subnet = super(QuantumRestProxyV2, self).update_subnet(context, id,
subnet)
net_id = new_subnet['network_id']
orig_net = super(QuantumRestProxyV2, self).get_network(context,
net_id)
# update network on network controller
try:
self._send_update_network(orig_net)
except RemoteRestError as e:
# rollback updation of subnet
super(QuantumRestProxyV2, self).update_subnet(context, id,
orig_subnet)
raise
return new_subnet
def delete_subnet(self, context, id):
LOG.debug(_("QuantumRestProxyV2: delete_subnet() called"))
orig_subnet = super(QuantumRestProxyV2, self).get_subnet(context, id)
net_id = orig_subnet['network_id']
# delete subnet in DB
super(QuantumRestProxyV2, self).delete_subnet(context, id)
orig_net = super(QuantumRestProxyV2, self).get_network(context,
net_id)
# update network on network controller
try:
self._send_update_network(orig_net)
except RemoteRestError as e:
# TODO (Sumit): rollback deletion of subnet
raise
def create_router(self, context, router):
LOG.debug(_("QuantumRestProxyV2: create_router() called"))
self._warn_on_state_status(router['router'])
tenant_id = self._get_tenant_id_for_create(context, router["router"])
# create router in DB
new_router = super(QuantumRestProxyV2, 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)
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(_("QuantumRestProxyV2: Unable to create remote router: "
"%s"), e.message)
super(QuantumRestProxyV2, self).delete_router(context,
new_router['id'])
raise
# return created router
return new_router
def update_router(self, context, router_id, router):
LOG.debug(_("QuantumRestProxyV2.update_router() called"))
self._warn_on_state_status(router['router'])
orig_router = super(QuantumRestProxyV2, self).get_router(context,
router_id)
tenant_id = orig_router["tenant_id"]
new_router = super(QuantumRestProxyV2, self).update_router(context,
router_id,
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(_("QuantumRestProxyV2: Unable to update remote router: "
"%s"), e.message)
# reset router to original state
super(QuantumRestProxyV2, self).update_router(context,
router_id,
orig_router)
raise
# return updated router
return new_router
def delete_router(self, context, router_id):
LOG.debug(_("QuantumRestProxyV2: delete_router() called"))
with context.session.begin(subtransactions=True):
orig_router = self._get_router(context, router_id)
tenant_id = orig_router["tenant_id"]
# Ensure that the router is not used
router_filter = {'router_id': [router_id]}
fips = self.get_floatingips_count(context.elevated(),
filters=router_filter)
if fips:
raise l3.RouterInUse(router_id=router_id)
device_owner = l3_db.DEVICE_OWNER_ROUTER_INTF
device_filter = {'device_id': [router_id],
'device_owner': [device_owner]}
ports = self.get_ports_count(context.elevated(),
filters=device_filter)
if ports:
raise l3.RouterInUse(router_id=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(QuantumRestProxyV2, self).delete_router(context,
router_id)
return ret_val
except RemoteRestError as e:
LOG.error(_("QuantumRestProxyV2: Unable to delete remote router: "
"%s"), e.message)
raise
def add_router_interface(self, context, router_id, interface_info):
LOG.debug(_("QuantumRestProxyV2: add_router_interface() called"))
# Validate args
router = self._get_router(context, router_id)
tenant_id = router['tenant_id']
# create interface in DB
new_interface_info = super(QuantumRestProxyV2,
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(_("QuantumRestProxyV2: Unable to create interface: "
"%s"), e.message)
super(QuantumRestProxyV2,
self).remove_router_interface(context, router_id,
interface_info)
raise
return new_interface_info
def remove_router_interface(self, context, router_id, interface_info):
LOG.debug(_("QuantumRestProxyV2: remove_router_interface() called"))
# Validate args
router = self._get_router(context, router_id)
tenant_id = router['tenant_id']
# we will first get the interface identifier before deleting in the DB
if not interface_info:
msg = "Either subnet_id or port_id must be specified"
raise exceptions.BadRequest(resource='router', msg=msg)
if 'port_id' in interface_info:
port = self._get_port(context, interface_info['port_id'])
interface_id = port['network_id']
elif 'subnet_id' in interface_info:
subnet = self._get_subnet(context, interface_info['subnet_id'])
interface_id = subnet['network_id']
else:
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(QuantumRestProxyV2,
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(_("QuantumRestProxyV2:Unable to delete remote intf: "
"%s"), e.message)
raise
# return new interface
return del_intf_info
def create_floatingip(self, context, floatingip):
LOG.debug(_("QuantumRestProxyV2: create_floatingip() called"))
# create floatingip in DB
new_fl_ip = super(QuantumRestProxyV2,
self).create_floatingip(context, floatingip)
net_id = new_fl_ip['floating_network_id']
orig_net = super(QuantumRestProxyV2, 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(_("QuantumRestProxyV2: Unable to create remote "
"floatin IP: %s"), e.message)
super(QuantumRestProxyV2, self).delete_floatingip(context,
floatingip)
raise
# return created floating IP
return new_fl_ip
def update_floatingip(self, context, id, floatingip):
LOG.debug(_("QuantumRestProxyV2: update_floatingip() called"))
orig_fl_ip = super(QuantumRestProxyV2, self).get_floatingip(context,
id)
# update floatingip in DB
new_fl_ip = super(QuantumRestProxyV2,
self).update_floatingip(context, id, floatingip)
net_id = new_fl_ip['floating_network_id']
orig_net = super(QuantumRestProxyV2, self).get_network(context,
net_id)
# update network on network controller
try:
self._send_update_network(orig_net)
except RemoteRestError as e:
# rollback updation of subnet
super(QuantumRestProxyV2, self).update_floatingip(context, id,
orig_fl_ip)
raise
return new_fl_ip
def delete_floatingip(self, context, id):
LOG.debug(_("QuantumRestProxyV2: delete_floatingip() called"))
orig_fl_ip = super(QuantumRestProxyV2, self).get_floatingip(context,
id)
# delete floating IP in DB
net_id = orig_fl_ip['floating_network_id']
super(QuantumRestProxyV2, self).delete_floatingip(context, id)
orig_net = super(QuantumRestProxyV2, self).get_network(context,
net_id)
# update network on network controller
try:
self._send_update_network(orig_net)
except RemoteRestError as e:
# TODO(Sumit): rollback deletion of floating IP
raise
def _send_all_data(self):
"""Pushes all data to network ctrl (networks/ports, ports/attachments)
to give the controller an option to re-sync it's persistent store
with quantum's current view of that data.
"""
admin_context = qcontext.get_admin_context()
networks = {}
ports = {}
networks = []
routers = []
all_networks = super(QuantumRestProxyV2,
self).get_networks(admin_context) or []
for net in all_networks:
networks[net.get('id')] = {
'id': net.get('id'),
'name': net.get('name'),
'op-status': net.get('admin_state_up'),
}
subnets = net.get('subnets', [])
for subnet_id in subnets:
subnet = self.get_subnet(admin_context, subnet_id)
gateway_ip = subnet.get('gateway_ip')
if gateway_ip:
# FIX: For backward compatibility with wire protocol
networks[net.get('id')]['gateway'] = gateway_ip
mapped_network = self._get_mapped_network_with_subnets(net)
net_fl_ips = self._get_network_with_floatingips(mapped_network)
ports = []
net_filter = {'network_id': [net.get('id')]}
@ -708,28 +1036,166 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
self).get_ports(admin_context,
filters=net_filter) or []
for port in net_ports:
port_details = {
'id': port.get('id'),
'attachment': {
'id': port.get('id') + '00',
'mac': port.get('mac_address'),
},
'state': port.get('status'),
'op-status': port.get('admin_state_up'),
'mac': None
mapped_port = self._map_state_and_status(port)
mapped_port['attachment'] = {
'id': port.get('device_id'),
'mac': port.get('mac_address'),
}
ports.append(port_details)
networks[net.get('id')]['ports'] = ports
ports.append(mapped_port)
net_fl_ips['ports'] = ports
networks.append(net_fl_ips)
all_routers = super(QuantumRestProxyV2,
self).get_routers(admin_context) or []
for router in all_routers:
interfaces = []
mapped_router = self._map_state_and_status(router)
router_filter = {
'device_owner': ["network:router_interface"],
'device_id': [router.get('id')]
}
router_ports = super(QuantumRestProxyV2,
self).get_ports(admin_context,
filters=router_filter) or []
for port in router_ports:
net_id = port.get('network_id')
subnet_id = port['fixed_ips'][0]['subnet_id']
intf_details = self._get_router_intf_details(admin_context,
net_id,
subnet_id)
interfaces.append(intf_details)
mapped_router['interfaces'] = interfaces
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(_('QuantumRestProxy: Unable to update remote network: '
'%s'), e.message)
LOG.error(_('QuantumRestProxy: Unable to update remote '
'topology: %s'), e.message)
raise
def _add_host_route(self, context, destination, port):
subnet = {}
for fixed_ip in port['fixed_ips']:
subnet_id = fixed_ip['subnet_id']
nexthop = fixed_ip['ip_address']
subnet['host_routes'] = [{'destination': destination,
'nexthop': nexthop}]
self.update_subnet(context, subnet_id, {'subnet': subnet})
LOG.debug(_("Adding host route: "))
LOG.debug(_("destination:%s nexthop:%s"), (destination,
nexthop))
def _get_network_with_floatingips(self, network):
admin_context = qcontext.get_admin_context()
net_id = network['id']
net_filter = {'floating_network_id': [net_id]}
fl_ips = super(QuantumRestProxyV2,
self).get_floatingips(admin_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)
subnets_details = []
if subnets:
for subnet in subnets:
subnet_dict = self._make_subnet_dict(subnet)
mapped_subnet = self._map_state_and_status(subnet_dict)
subnets_details.append(mapped_subnet)
return subnets_details
def _get_mapped_network_with_subnets(self, network):
admin_context = qcontext.get_admin_context()
network = self._map_state_and_status(network)
subnets = self._get_all_subnets_json_for_network(network['id'])
network['subnets'] = subnets
for subnet in (subnets or []):
if subnet['gateway_ip']:
# FIX: For backward compatibility with wire protocol
network['gateway'] = subnet['gateway_ip']
break
else:
network['gateway'] = ''
network[l3.EXTERNAL] = self._network_is_external(admin_context,
network['id'])
return network
def _send_update_network(self, network):
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(_("QuantumRestProxyV2: Unable to update remote "
"network: %s"), e.message)
raise
def _map_state_and_status(self, resource):
resource = copy.copy(resource)
resource['state'] = ('UP' if resource.pop('admin_state_up',
True) else 'DOWN')
if 'status' in resource:
del resource['status']
return resource
def _warn_on_state_status(self, resource):
if resource.get('admin_state_up', True) is False:
LOG.warning(_("Setting admin_state_up=False is not supported"
" in this plugin version. Ignoring setting for "
"resource: %s"), resource)
if 'status' in resource:
if resource['status'] is not const.NET_STATUS_ACTIVE:
LOG.warning(_("Operational status is internally set by the"
" plugin. Ignoring setting status=%s."),
resource['status'])
def _get_router_intf_details(self, context, intf_id, subnet_id):
# we will use the network id as interface's id
net_id = intf_id
network = super(QuantumRestProxyV2, self).get_network(context,
net_id)
subnet = super(QuantumRestProxyV2, self).get_subnet(context,
subnet_id)
mapped_network = self._get_mapped_network_with_subnets(network)
mapped_subnet = self._map_state_and_status(subnet)
data = {
'id': intf_id,
"network": mapped_network,
"subnet": mapped_subnet
}
return data

View File

@ -0,0 +1,27 @@
# 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: Sumit Naiksatam, sumitnaiksatam@gmail.com
#
version_info = {'branch_nick': u'quantum/trunk',
'revision_id': u'1',
'revno': 0}
QUANTUMRESTPROXY_VERSION = ['2013', '1', None]
FINAL = False # This becomes true at Release Candidate time

View File

@ -23,16 +23,22 @@
# if vcsversion exists, use it. Else, use LOCALBRANCH:LOCALREVISION
try:
from bigswitch.vcsversion import version_info
from quantum.plugins.bigswitch.vcsversion import version_info
except ImportError:
version_info = {'branch_nick': u'LOCALBRANCH',
'revision_id': u'LOCALREVISION',
'revno': 0}
try:
from quantum.plugins.bigswitch.vcsversion import QUANTUMRESTPROXY_VERSION
except ImportError:
QUANTUMRESTPROXY_VERSION = ['2013', '1', None]
try:
from quantum.plugins.bigswitch.vcsversion import FINAL
except ImportError:
FINAL = False # This becomes true at Release Candidate time
QUANTUMRESTPROXY_VERSION = ['2012', '1', None]
YEAR, COUNT, REVISION = QUANTUMRESTPROXY_VERSION
FINAL = False # This becomes true at Release Candidate time
def canonical_version_string():

View File

@ -0,0 +1,317 @@
# 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.
#
# Adapted from quantum.tests.unit.test_l3_plugin
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com
#
import os
from mock import patch
from webob import exc
from quantum.common.test_lib import test_config
from quantum.extensions import l3
from quantum.manager import QuantumManager
from quantum.openstack.common import cfg
from quantum.openstack.common.notifier import api as notifier_api
from quantum.openstack.common.notifier import test_notifier
from quantum.tests.unit import test_l3_plugin
def new_L3_setUp(self):
test_config['plugin_name_v2'] = (
'quantum.plugins.bigswitch.plugin.QuantumRestProxyV2')
etc_path = os.path.join(os.path.dirname(__file__), 'etc')
rp_conf_file = os.path.join(etc_path, 'restproxy.ini.test')
test_config['config_files'] = [rp_conf_file]
cfg.CONF.set_default('allow_overlapping_ips', False)
ext_mgr = L3TestExtensionManager()
test_config['extension_manager'] = ext_mgr
super(test_l3_plugin.L3NatDBTestCase, self).setUp()
# Set to None to reload the drivers
notifier_api._drivers = None
cfg.CONF.set_override("notification_driver", [test_notifier.__name__])
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 HTTPConnectionMock():
def __init__(self, server, port, timeout):
pass
def request(self, action, uri, body, headers):
return
def getresponse(self):
return HTTPResponseMock(None)
def close(self):
pass
class L3TestExtensionManager(object):
def get_resources(self):
return l3.L3.get_resources()
def get_actions(self):
return []
def get_request_extensions(self):
return []
class RouterDBTestCase(test_l3_plugin.L3NatDBTestCase):
def setUp(self):
self.httpPatch = patch('httplib.HTTPConnection', create=True,
new=HTTPConnectionMock)
self.httpPatch.start()
test_l3_plugin.L3NatDBTestCase.setUp = new_L3_setUp
super(RouterDBTestCase, self).setUp()
self.plugin_obj = QuantumManager.get_plugin()
def tearDown(self):
self.httpPatch.stop()
super(RouterDBTestCase, self).tearDown()
del test_config['plugin_name_v2']
del test_config['config_files']
cfg.CONF.reset()
test_l3_plugin.L3NatDBTestCase.setUp = origSetUp
def test_router_remove_router_interface_wrong_subnet_returns_409(self):
with self.router() as r:
with self.subnet() as s:
with self.subnet(cidr='10.0.10.0/24') as s1:
with self.port(subnet=s1, no_delete=True) as p:
self._router_interface_action('add',
r['router']['id'],
None,
p['port']['id'])
self._router_interface_action('remove',
r['router']['id'],
s['subnet']['id'],
p['port']['id'],
exc.HTTPConflict.code)
#remove properly to clean-up
self._router_interface_action('remove',
r['router']['id'],
None,
p['port']['id'])
def test_router_remove_router_interface_wrong_port_returns_404(self):
with self.router() as r:
with self.subnet() as s:
with self.port(subnet=s, no_delete=True) as p:
self._router_interface_action('add',
r['router']['id'],
None,
p['port']['id'])
# create another port for testing failure case
res = self._create_port('json', p['port']['network_id'])
p2 = self.deserialize('json', res)
self._router_interface_action('remove',
r['router']['id'],
None,
p2['port']['id'],
exc.HTTPNotFound.code)
# remove correct interface to cleanup
self._router_interface_action('remove',
r['router']['id'],
None,
p['port']['id'])
# remove extra port created
self._delete('ports', p2['port']['id'])
def test_create_floatingip_no_ext_gateway_return_404(self):
with self.subnet(cidr='10.0.10.0/24') as public_sub:
self._set_net_external(public_sub['subnet']['network_id'])
with self.port() as private_port:
with self.router() as r:
res = self._create_floatingip(
'json',
public_sub['subnet']['network_id'],
port_id=private_port['port']['id'])
self.assertEqual(res.status_int, exc.HTTPNotFound.code)
def test_router_update_gateway(self):
with self.router() as r:
with self.subnet() as s1:
with self.subnet(cidr='10.0.10.0/24') as s2:
self._set_net_external(s1['subnet']['network_id'])
self._add_external_gateway_to_router(
r['router']['id'],
s1['subnet']['network_id'])
body = self._show('routers', r['router']['id'])
net_id = (body['router']
['external_gateway_info']['network_id'])
self.assertEquals(net_id, s1['subnet']['network_id'])
self._set_net_external(s2['subnet']['network_id'])
self._add_external_gateway_to_router(
r['router']['id'],
s2['subnet']['network_id'])
body = self._show('routers', r['router']['id'])
net_id = (body['router']
['external_gateway_info']['network_id'])
self.assertEquals(net_id, s2['subnet']['network_id'])
self._remove_external_gateway_from_router(
r['router']['id'],
s2['subnet']['network_id'])
def test_router_add_interface_overlapped_cidr(self):
self.skipTest("Plugin does not support")
def test_router_add_interface_overlapped_cidr_returns_400(self):
self.skipTest("Plugin does not support")
def test_list_nets_external(self):
self.skipTest("Plugin does not support")
def test_router_update_gateway_with_existed_floatingip(self):
with self.subnet(cidr='10.0.10.0/24') as subnet:
self._set_net_external(subnet['subnet']['network_id'])
with self.floatingip_with_assoc() as fip:
self._add_external_gateway_to_router(
fip['floatingip']['router_id'],
subnet['subnet']['network_id'],
expected_code=exc.HTTPConflict.code)
def test_router_remove_interface_wrong_subnet_returns_409(self):
with self.router() as r:
with self.subnet(cidr='10.0.10.0/24') as s:
with self.port(no_delete=True) as p:
self._router_interface_action('add',
r['router']['id'],
None,
p['port']['id'])
self._router_interface_action('remove',
r['router']['id'],
s['subnet']['id'],
p['port']['id'],
exc.HTTPConflict.code)
#remove properly to clean-up
self._router_interface_action('remove',
r['router']['id'],
None,
p['port']['id'])
def test_router_remove_interface_wrong_port_returns_404(self):
with self.router() as r:
with self.subnet(cidr='10.0.10.0/24') as s:
with self.port(no_delete=True) as p:
self._router_interface_action('add',
r['router']['id'],
None,
p['port']['id'])
# create another port for testing failure case
res = self._create_port('json', p['port']['network_id'])
p2 = self.deserialize('json', res)
self._router_interface_action('remove',
r['router']['id'],
None,
p2['port']['id'],
exc.HTTPNotFound.code)
# remove correct interface to cleanup
self._router_interface_action('remove',
r['router']['id'],
None,
p['port']['id'])
# remove extra port created
self._delete('ports', p2['port']['id'])
def test_send_data(self):
fmt = 'json'
plugin_obj = QuantumManager.get_plugin()
with self.router() as r:
r_id = r['router']['id']
with self.subnet(cidr='10.0.10.0/24') as s:
s_id = s['subnet']['id']
with self.router() as r1:
r1_id = r1['router']['id']
body = self._router_interface_action('add', r_id, s_id,
None)
self.assertTrue('port_id' in body)
r_port_id = body['port_id']
body = self._show('ports', r_port_id)
self.assertEquals(body['port']['device_id'], r_id)
with self.subnet(cidr='10.0.20.0/24') as s1:
s1_id = s1['subnet']['id']
body = self._router_interface_action('add', r1_id,
s1_id, None)
self.assertTrue('port_id' in body)
r1_port_id = body['port_id']
body = self._show('ports', r1_port_id)
self.assertEquals(body['port']['device_id'], r1_id)
with self.subnet(cidr='11.0.0.0/24') as public_sub:
public_net_id = public_sub['subnet']['network_id']
self._set_net_external(public_net_id)
with self.port() as prv_port:
prv_fixed_ip = prv_port['port']['fixed_ips'][0]
priv_sub_id = prv_fixed_ip['subnet_id']
self._add_external_gateway_to_router(
r_id, public_net_id)
self._router_interface_action('add', r_id,
priv_sub_id,
None)
priv_port_id = prv_port['port']['id']
res = self._create_floatingip(
fmt, public_net_id,
port_id=priv_port_id)
self.assertEqual(res.status_int,
exc.HTTPCreated.code)
floatingip = self.deserialize(fmt, res)
result = plugin_obj._send_all_data()
self.assertEquals(result[0], 200)
self._delete('floatingips',
floatingip['floatingip']['id'])
self._remove_external_gateway_from_router(
r_id, public_net_id)
self._router_interface_action('remove', r_id,
priv_sub_id,
None)
self._router_interface_action('remove', r_id, s_id,
None)
self._show('ports', r_port_id,
expected_code=exc.HTTPNotFound.code)
self._router_interface_action('remove', r1_id, s1_id,
None)
self._show('ports', r1_port_id,
expected_code=exc.HTTPNotFound.code)