"""
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
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")),
]
# 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):
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
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)
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
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.
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'),
# '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'
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
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"))
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):
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
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)
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
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)
"""
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)
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])
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: "
# 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.
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)
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):
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":
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')]}
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
--- /dev/null
+# 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)