e05537583f
Change-Id: Ic0e9ea6aa84c30996b29dce3bfa06b443e9adcc4
976 lines
38 KiB
Python
976 lines
38 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright (C) 2012 Midokura Japan K.K.
|
|
# Copyright (C) 2013 Midokura PTE LTD
|
|
# 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: Tomoe Sugihara, Midokura Japan KK
|
|
# @author: Ryu Ishimoto, Midokura Japan KK
|
|
|
|
|
|
from webob import exc as w_exc
|
|
|
|
from neutron.common import exceptions as q_exc
|
|
from neutron.openstack.common import log as logging
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
PREFIX = 'OS_SG_'
|
|
NAME_IDENTIFIABLE_PREFIX_LEN = len(PREFIX) + 36 # 36 = length of uuid
|
|
OS_FLOATING_IP_RULE_KEY = 'OS_FLOATING_IP'
|
|
OS_ROUTER_IN_CHAIN_NAME_FORMAT = 'OS_ROUTER_IN_%s'
|
|
OS_ROUTER_OUT_CHAIN_NAME_FORMAT = 'OS_ROUTER_OUT_%s'
|
|
OS_SG_KEY = 'os_sg_rule_id'
|
|
OS_TENANT_ROUTER_RULE_KEY = 'OS_TENANT_ROUTER_RULE'
|
|
SNAT_RULE = 'SNAT'
|
|
SNAT_RULE_PROPERTY = {OS_TENANT_ROUTER_RULE_KEY: SNAT_RULE}
|
|
SUFFIX_IN = '_IN'
|
|
SUFFIX_OUT = '_OUT'
|
|
|
|
|
|
def sg_label(sg_id, sg_name):
|
|
"""Construct the security group ID used as chain identifier in MidoNet."""
|
|
return PREFIX + str(sg_id) + '_' + sg_name
|
|
|
|
|
|
def sg_rule_properties(os_sg_rule_id):
|
|
return {OS_SG_KEY: str(os_sg_rule_id)}
|
|
|
|
port_group_name = sg_label
|
|
|
|
|
|
def chain_names(sg_id, sg_name):
|
|
"""Get inbound and outbound chain names."""
|
|
prefix = sg_label(sg_id, sg_name)
|
|
in_chain_name = prefix + SUFFIX_IN
|
|
out_chain_name = prefix + SUFFIX_OUT
|
|
return {'in': in_chain_name, 'out': out_chain_name}
|
|
|
|
|
|
def router_chain_names(router_id):
|
|
in_name = OS_ROUTER_IN_CHAIN_NAME_FORMAT % router_id
|
|
out_name = OS_ROUTER_OUT_CHAIN_NAME_FORMAT % router_id
|
|
return {'in': in_name, 'out': out_name}
|
|
|
|
|
|
def handle_api_error(fn):
|
|
def wrapped(*args, **kwargs):
|
|
try:
|
|
return fn(*args, **kwargs)
|
|
except w_exc.HTTPException as ex:
|
|
raise MidonetApiException(msg=ex)
|
|
return wrapped
|
|
|
|
|
|
class MidonetResourceNotFound(q_exc.NotFound):
|
|
message = _('MidoNet %(resource_type)s %(id)s could not be found')
|
|
|
|
|
|
class MidonetApiException(q_exc.NeutronException):
|
|
message = _("MidoNet API error: %(msg)s")
|
|
|
|
|
|
class MidoClient:
|
|
|
|
def __init__(self, mido_api):
|
|
self.mido_api = mido_api
|
|
|
|
@handle_api_error
|
|
def create_bridge(self, tenant_id, name):
|
|
"""Create a new bridge
|
|
|
|
:param tenant_id: id of tenant creating the bridge
|
|
:param name: name of the bridge
|
|
:returns: newly created bridge
|
|
"""
|
|
LOG.debug(_("MidoClient.create_bridge called: "
|
|
"tenant_id=%(tenant_id)s, name=%(name)s"),
|
|
{'tenant_id': tenant_id, 'name': name})
|
|
return self.mido_api.add_bridge().name(name).tenant_id(
|
|
tenant_id).create()
|
|
|
|
@handle_api_error
|
|
def delete_bridge(self, id):
|
|
"""Delete a bridge
|
|
|
|
:param id: id of the bridge
|
|
"""
|
|
LOG.debug(_("MidoClient.delete_bridge called: id=%(id)s"), {'id': id})
|
|
return self.mido_api.delete_bridge(id)
|
|
|
|
@handle_api_error
|
|
def get_bridge(self, id):
|
|
"""Get a bridge
|
|
|
|
:param id: id of the bridge
|
|
:returns: requested bridge. None if bridge does not exist.
|
|
"""
|
|
LOG.debug(_("MidoClient.get_bridge called: id=%s"), id)
|
|
try:
|
|
return self.mido_api.get_bridge(id)
|
|
except w_exc.HTTPNotFound:
|
|
raise MidonetResourceNotFound(resource_type='Bridge', id=id)
|
|
|
|
@handle_api_error
|
|
def update_bridge(self, id, name):
|
|
"""Update a bridge of the given id with the new name
|
|
|
|
:param id: id of the bridge
|
|
:param name: name of the bridge to set to
|
|
:returns: bridge object
|
|
"""
|
|
LOG.debug(_("MidoClient.update_bridge called: "
|
|
"id=%(id)s, name=%(name)s"), {'id': id, 'name': name})
|
|
try:
|
|
return self.mido_api.get_bridge(id).name(name).update()
|
|
except w_exc.HTTPNotFound:
|
|
raise MidonetResourceNotFound(resource_type='Bridge', id=id)
|
|
|
|
@handle_api_error
|
|
def create_dhcp(self, bridge, gateway_ip, net_addr, net_len):
|
|
"""Create a new DHCP entry
|
|
|
|
:param bridge: bridge object to add dhcp to
|
|
:param gateway_ip: IP address of gateway
|
|
:param net_addr: network IP address
|
|
:param net_len: network IP address length
|
|
:returns: newly created dhcp
|
|
"""
|
|
LOG.debug(_("MidoClient.create_dhcp called: bridge=%s(bridge)s, "
|
|
"net_addr=%(net_addr)s, net_len=%(net_len)s, "
|
|
"gateway_ip=%(gateway_ip)s"),
|
|
{'bridge': bridge, 'net_addr': net_addr, 'net_len': net_len,
|
|
'gateway_ip': gateway_ip})
|
|
return bridge.add_dhcp_subnet().default_gateway(
|
|
gateway_ip).subnet_prefix(net_addr).subnet_length(
|
|
net_len).create()
|
|
|
|
@handle_api_error
|
|
def create_dhcp_hosts(self, bridge, ip, mac):
|
|
"""Create DHCP host entries
|
|
|
|
:param bridge: bridge of the DHCP
|
|
:param ip: IP address
|
|
:param mac: MAC address
|
|
"""
|
|
LOG.debug(_("MidoClient.create_dhcp_hosts called: bridge=%s(bridge), "
|
|
"ip=%(ip)s, mac=%(mac)s"), {'bridge': bridge, 'ip': ip,
|
|
'mac': mac})
|
|
dhcp_subnets = bridge.get_dhcp_subnets()
|
|
if dhcp_subnets:
|
|
# Add the host to the first subnet as we currently support one
|
|
# subnet per network.
|
|
dhcp_subnets[0].add_dhcp_host().ip_addr(ip).mac_addr(mac).create()
|
|
|
|
@handle_api_error
|
|
def delete_dhcp_hosts(self, bridge_id, ip, mac):
|
|
"""Delete DHCP host entries
|
|
|
|
:param bridge_id: id of the bridge of the DHCP
|
|
:param ip: IP address
|
|
:param mac: MAC address
|
|
"""
|
|
LOG.debug(_("MidoClient.delete_dhcp_hosts called: "
|
|
"bridge_id=%s(bridge_id), ip=%(ip)s, mac=%(mac)s"),
|
|
{'bridge_id': bridge_id, 'ip': ip, 'mac': mac})
|
|
bridge = self.get_bridge(bridge_id)
|
|
dhcp_subnets = bridge.get_dhcp_subnets()
|
|
if dhcp_subnets:
|
|
for dh in dhcp_subnets[0].get_dhcp_hosts():
|
|
if dh.get_mac_addr() == mac and dh.get_ip_addr() == ip:
|
|
dh.delete()
|
|
|
|
@handle_api_error
|
|
def delete_dhcp(self, bridge):
|
|
"""Delete a DHCP entry
|
|
|
|
:param bridge: bridge to remove DHCP from
|
|
"""
|
|
LOG.debug(_("MidoClient.delete_dhcp called: bridge=%s(bridge), "),
|
|
{'bridge': bridge})
|
|
dhcp = bridge.get_dhcp_subnets()
|
|
if not dhcp:
|
|
raise MidonetApiException(msg="Tried to delete non-existent DHCP")
|
|
dhcp[0].delete()
|
|
|
|
@handle_api_error
|
|
def delete_port(self, id):
|
|
"""Delete a port
|
|
|
|
:param id: id of the port
|
|
"""
|
|
LOG.debug(_("MidoClient.delete_port called: id=%(id)s"), {'id': id})
|
|
self.mido_api.delete_port(id)
|
|
|
|
@handle_api_error
|
|
def get_port(self, id):
|
|
"""Get a port
|
|
|
|
:param id: id of the port
|
|
:returns: requested port. None if it does not exist
|
|
"""
|
|
LOG.debug(_("MidoClient.get_port called: id=%(id)s"), {'id': id})
|
|
try:
|
|
return self.mido_api.get_port(id)
|
|
except w_exc.HTTPNotFound:
|
|
raise MidonetResourceNotFound(resource_type='Port', id=id)
|
|
|
|
@handle_api_error
|
|
def create_exterior_bridge_port(self, bridge):
|
|
"""Create a new exterior bridge port
|
|
|
|
:param bridge: bridge object to add port to
|
|
:returns: newly created port
|
|
"""
|
|
LOG.debug(_("MidoClient.create_exterior_bridge_port called: "
|
|
"bridge=%(bridge)s"), {'bridge': bridge})
|
|
return bridge.add_exterior_port().create()
|
|
|
|
@handle_api_error
|
|
def create_interior_bridge_port(self, bridge):
|
|
"""Create a new interior bridge port
|
|
|
|
:param bridge: bridge object to add port to
|
|
:returns: newly created port
|
|
"""
|
|
LOG.debug(_("MidoClient.create_interior_bridge_port called: "
|
|
"bridge=%(bridge)s"), {'bridge': bridge})
|
|
return bridge.add_interior_port().create()
|
|
|
|
@handle_api_error
|
|
def create_router(self, tenant_id, name):
|
|
"""Create a new router
|
|
|
|
:param tenant_id: id of tenant creating the router
|
|
:param name: name of the router
|
|
:returns: newly created router
|
|
"""
|
|
LOG.debug(_("MidoClient.create_router called: "
|
|
"tenant_id=%(tenant_id)s, name=%(name)s"),
|
|
{'tenant_id': tenant_id, 'name': name})
|
|
return self.mido_api.add_router().name(name).tenant_id(
|
|
tenant_id).create()
|
|
|
|
@handle_api_error
|
|
def create_tenant_router(self, tenant_id, name, metadata_router):
|
|
"""Create a new tenant router
|
|
|
|
:param tenant_id: id of tenant creating the router
|
|
:param name: name of the router
|
|
:param metadata_router: metadata router
|
|
:returns: newly created router
|
|
"""
|
|
LOG.debug(_("MidoClient.create_tenant_router called: "
|
|
"tenant_id=%(tenant_id)s, name=%(name)s,"
|
|
" metadata_router=%(metadata_router)s"),
|
|
{'tenant_id': tenant_id, 'name': name,
|
|
'metadata_router': metadata_router})
|
|
router = self.create_router(tenant_id, name)
|
|
self.link_router_to_metadata_router(router, metadata_router)
|
|
return router
|
|
|
|
@handle_api_error
|
|
def delete_tenant_router(self, id, metadata_router):
|
|
"""Delete a tenant router
|
|
|
|
:param id: id of router
|
|
:param metadata_router: metadata router
|
|
"""
|
|
LOG.debug(_("MidoClient.delete_tenant_router called: "
|
|
"id=%(id)s, metadata_router=%(metadata_router)s"),
|
|
{'id': id, 'metadata_router': metadata_router})
|
|
self.unlink_router_from_metadata_router(id, metadata_router)
|
|
self.destroy_router_chains(id)
|
|
|
|
# delete the router
|
|
self.delete_router(id)
|
|
|
|
@handle_api_error
|
|
def delete_router(self, id):
|
|
"""Delete a router
|
|
|
|
:param id: id of the router
|
|
"""
|
|
LOG.debug(_("MidoClient.delete_router called: id=%(id)s"), {'id': id})
|
|
return self.mido_api.delete_router(id)
|
|
|
|
@handle_api_error
|
|
def get_router(self, id):
|
|
"""Get a router with the given id
|
|
|
|
:param id: id of the router
|
|
:returns: requested router object. None if it does not exist.
|
|
"""
|
|
LOG.debug(_("MidoClient.get_router called: id=%(id)s"), {'id': id})
|
|
try:
|
|
return self.mido_api.get_router(id)
|
|
except w_exc.HTTPNotFound:
|
|
raise MidonetResourceNotFound(resource_type='Router', id=id)
|
|
|
|
@handle_api_error
|
|
def update_router(self, id, name):
|
|
"""Update a router of the given id with the new name
|
|
|
|
:param id: id of the router
|
|
:param name: name of the router to set to
|
|
:returns: router object
|
|
"""
|
|
LOG.debug(_("MidoClient.update_router called: "
|
|
"id=%(id)s, name=%(name)s"), {'id': id, 'name': name})
|
|
try:
|
|
return self.mido_api.get_router(id).name(name).update()
|
|
except w_exc.HTTPNotFound:
|
|
raise MidonetResourceNotFound(resource_type='Router', id=id)
|
|
|
|
@handle_api_error
|
|
def link_bridge_port_to_router(self, port_id, router_id, gateway_ip,
|
|
net_addr, net_len, metadata_router):
|
|
"""Link a tenant bridge port to the router
|
|
|
|
:param port_id: port ID
|
|
:param router_id: router id to link to
|
|
:param gateway_ip: IP address of gateway
|
|
:param net_addr: network IP address
|
|
:param net_len: network IP address length
|
|
:param metadata_router: metadata router instance
|
|
"""
|
|
LOG.debug(_("MidoClient.link_bridge_port_to_router called: "
|
|
"port_id=%(port_id)s, router_id=%(router_id)s, "
|
|
"gateway_ip=%(gateway_ip)s net_addr=%(net_addr)s, "
|
|
"net_len=%(net_len)s, "
|
|
"metadata_router=%(metadata_router)s"),
|
|
{'port_id': port_id, 'router_id': router_id,
|
|
'gateway_ip': gateway_ip, 'net_addr': net_addr,
|
|
'net_len': net_len, 'metadata_router': metadata_router})
|
|
router = self.get_router(router_id)
|
|
|
|
# create an interior port on the router
|
|
in_port = router.add_interior_port()
|
|
router_port = in_port.port_address(gateway_ip).network_address(
|
|
net_addr).network_length(net_len).create()
|
|
|
|
br_port = self.get_port(port_id)
|
|
router_port.link(br_port.get_id())
|
|
|
|
# add a route for the subnet in the provider router
|
|
router.add_route().type('Normal').src_network_addr(
|
|
'0.0.0.0').src_network_length(0).dst_network_addr(
|
|
net_addr).dst_network_length(net_len).weight(
|
|
100).next_hop_port(router_port.get_id()).create()
|
|
|
|
# add a route for the subnet in metadata router; forward
|
|
# packets destined to the subnet to the tenant router
|
|
for pp in metadata_router.get_peer_ports():
|
|
if pp.get_device_id() == router.get_id():
|
|
mdr_port_id = pp.get_peer_id()
|
|
break
|
|
else:
|
|
raise Exception(
|
|
_("Couldn't find a md router port for the router=%r"), router)
|
|
|
|
metadata_router.add_route().type('Normal').src_network_addr(
|
|
'0.0.0.0').src_network_length(0).dst_network_addr(
|
|
net_addr).dst_network_length(net_len).weight(
|
|
100).next_hop_port(mdr_port_id).create()
|
|
|
|
@handle_api_error
|
|
def unlink_bridge_port_from_router(self, port_id, net_addr, net_len,
|
|
metadata_router):
|
|
"""Unlink a tenant bridge port from the router
|
|
|
|
:param bridge_id: bridge ID
|
|
:param net_addr: network IP address
|
|
:param net_len: network IP address length
|
|
:param metadata_router: metadata router instance
|
|
"""
|
|
LOG.debug(_("MidoClient.unlink_bridge_port_from_router called: "
|
|
"port_id=%(port_id)s, net_addr=%(net_addr)s, "
|
|
"net_len=%(net_len)s, "
|
|
"metadata_router=%(metadata_router)s"),
|
|
{'port_id': port_id, 'net_addr': net_addr,
|
|
'net_len': net_len, 'metadata_router': metadata_router})
|
|
port = self.get_port(port_id)
|
|
port.unlink()
|
|
self.delete_port(port.get_peer_id())
|
|
self.delete_port(port.get_id())
|
|
|
|
# delete the route for the subnet in the metadata router
|
|
for r in metadata_router.get_routes():
|
|
if (r.get_dst_network_addr() == net_addr and
|
|
r.get_dst_network_length() == net_len):
|
|
LOG.debug(_('Deleting route=%r ...'), r)
|
|
self.mido_api.delete_route(r.get_id())
|
|
break
|
|
|
|
@handle_api_error
|
|
def link_bridge_to_provider_router(self, bridge, provider_router,
|
|
gateway_ip, net_addr, net_len):
|
|
"""Link a tenant bridge to the provider router
|
|
|
|
:param bridge: tenant bridge
|
|
:param provider_router: provider router to link to
|
|
:param gateway_ip: IP address of gateway
|
|
:param net_addr: network IP address
|
|
:param net_len: network IP address length
|
|
"""
|
|
LOG.debug(_("MidoClient.link_bridge_to_provider_router called: "
|
|
"bridge=%(bridge)s, provider_router=%(provider_router)s, "
|
|
"gateway_ip=%(gateway_ip)s, net_addr=%(net_addr)s, "
|
|
"net_len=%(net_len)s"),
|
|
{'bridge': bridge, 'provider_router': provider_router,
|
|
'gateway_ip': gateway_ip, 'net_addr': net_addr,
|
|
'net_len': net_len})
|
|
# create an interior port on the provider router
|
|
in_port = provider_router.add_interior_port()
|
|
pr_port = in_port.port_address(gateway_ip).network_address(
|
|
net_addr).network_length(net_len).create()
|
|
|
|
# create an interior bridge port, then link it to the router.
|
|
br_port = bridge.add_interior_port().create()
|
|
pr_port.link(br_port.get_id())
|
|
|
|
# add a route for the subnet in the provider router
|
|
provider_router.add_route().type('Normal').src_network_addr(
|
|
'0.0.0.0').src_network_length(0).dst_network_addr(
|
|
net_addr).dst_network_length(net_len).weight(
|
|
100).next_hop_port(pr_port.get_id()).create()
|
|
|
|
@handle_api_error
|
|
def unlink_bridge_from_provider_router(self, bridge, provider_router):
|
|
"""Unlink a tenant bridge from the provider router
|
|
|
|
:param bridge: tenant bridge
|
|
:param provider_router: provider router to link to
|
|
"""
|
|
LOG.debug(_("MidoClient.unlink_bridge_from_provider_router called: "
|
|
"bridge=%(bridge)s, provider_router=%(provider_router)s"),
|
|
{'bridge': bridge, 'provider_router': provider_router})
|
|
# Delete routes and unlink the router and the bridge.
|
|
routes = provider_router.get_routes()
|
|
|
|
bridge_ports_to_delete = [
|
|
p for p in provider_router.get_peer_ports()
|
|
if p.get_device_id() == bridge.get_id()]
|
|
|
|
for p in bridge.get_peer_ports():
|
|
if p.get_device_id() == provider_router.get_id():
|
|
# delete the routes going to the bridge
|
|
for r in routes:
|
|
if r.get_next_hop_port() == p.get_id():
|
|
self.mido_api.delete_route(r.get_id())
|
|
p.unlink()
|
|
self.mido_api.delete_port(p.get_id())
|
|
|
|
# delete bridge port
|
|
for port in bridge_ports_to_delete:
|
|
self.mido_api.delete_port(port.get_id())
|
|
|
|
@handle_api_error
|
|
def set_router_external_gateway(self, id, provider_router, snat_ip):
|
|
"""Set router external gateway
|
|
|
|
:param ID: ID of the tenant router
|
|
:param provider_router: provider router
|
|
:param snat_ip: SNAT IP address
|
|
"""
|
|
LOG.debug(_("MidoClient.set_router_external_gateway called: "
|
|
"id=%(id)s, provider_router=%(provider_router)s, "
|
|
"snat_ip=%s(snat_ip)s)"),
|
|
{'id': id, 'provider_router': provider_router,
|
|
'snat_ip': snat_ip})
|
|
tenant_router = self.get_router(id)
|
|
|
|
# Create a interior port in the provider router
|
|
in_port = provider_router.add_interior_port()
|
|
pr_port = in_port.network_address(
|
|
'169.254.255.0').network_length(30).port_address(
|
|
'169.254.255.1').create()
|
|
|
|
# Create a port in the tenant router
|
|
tr_port = tenant_router.add_interior_port().network_address(
|
|
'169.254.255.0').network_length(30).port_address(
|
|
'169.254.255.2').create()
|
|
|
|
# Link them
|
|
pr_port.link(tr_port.get_id())
|
|
|
|
# Add a route for snat_ip to bring it down to tenant
|
|
provider_router.add_route().type(
|
|
'Normal').src_network_addr('0.0.0.0').src_network_length(
|
|
0).dst_network_addr(snat_ip).dst_network_length(
|
|
32).weight(100).next_hop_port(
|
|
pr_port.get_id()).create()
|
|
|
|
# Add default route to uplink in the tenant router
|
|
tenant_router.add_route().type('Normal').src_network_addr(
|
|
'0.0.0.0').src_network_length(0).dst_network_addr(
|
|
'0.0.0.0').dst_network_length(0).weight(
|
|
100).next_hop_port(tr_port.get_id()).create()
|
|
|
|
# ADD SNAT(masquerade) rules
|
|
chains = self.get_router_chains(
|
|
tenant_router.get_tenant_id(), tenant_router.get_id())
|
|
|
|
chains['in'].add_rule().nw_dst_address(snat_ip).nw_dst_length(
|
|
32).type('rev_snat').flow_action('accept').in_ports(
|
|
[tr_port.get_id()]).properties(
|
|
SNAT_RULE_PROPERTY).position(1).create()
|
|
|
|
nat_targets = []
|
|
nat_targets.append(
|
|
{'addressFrom': snat_ip, 'addressTo': snat_ip,
|
|
'portFrom': 1, 'portTo': 65535})
|
|
|
|
chains['out'].add_rule().type('snat').flow_action(
|
|
'accept').nat_targets(nat_targets).out_ports(
|
|
[tr_port.get_id()]).properties(
|
|
SNAT_RULE_PROPERTY).position(1).create()
|
|
|
|
@handle_api_error
|
|
def clear_router_external_gateway(self, id):
|
|
"""Clear router external gateway
|
|
|
|
:param ID: ID of the tenant router
|
|
"""
|
|
LOG.debug(_("MidoClient.clear_router_external_gateway called: "
|
|
"id=%(id)s"), {'id': id})
|
|
tenant_router = self.get_router(id)
|
|
|
|
# delete the port that is connected to provider router
|
|
for p in tenant_router.get_ports():
|
|
if p.get_port_address() == '169.254.255.2':
|
|
peer_port_id = p.get_peer_id()
|
|
p.unlink()
|
|
self.mido_api.delete_port(peer_port_id)
|
|
self.mido_api.delete_port(p.get_id())
|
|
|
|
# delete default route
|
|
for r in tenant_router.get_routes():
|
|
if (r.get_dst_network_addr() == '0.0.0.0' and
|
|
r.get_dst_network_length() == 0):
|
|
self.mido_api.delete_route(r.get_id())
|
|
|
|
# delete SNAT(masquerade) rules
|
|
chains = self.get_router_chains(
|
|
tenant_router.get_tenant_id(),
|
|
tenant_router.get_id())
|
|
|
|
for r in chains['in'].get_rules():
|
|
if OS_TENANT_ROUTER_RULE_KEY in r.get_properties():
|
|
if r.get_properties()[
|
|
OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE:
|
|
self.mido_api.delete_rule(r.get_id())
|
|
|
|
for r in chains['out'].get_rules():
|
|
if OS_TENANT_ROUTER_RULE_KEY in r.get_properties():
|
|
if r.get_properties()[
|
|
OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE:
|
|
self.mido_api.delete_rule(r.get_id())
|
|
|
|
@handle_api_error
|
|
def get_router_chains(self, tenant_id, router_id):
|
|
"""Get router chains.
|
|
|
|
Returns a dictionary that has in/out chain resources key'ed with 'in'
|
|
and 'out' respectively, given the tenant_id and the router_id passed
|
|
in in the arguments.
|
|
"""
|
|
LOG.debug(_("MidoClient.get_router_chains called: "
|
|
"tenant_id=%(tenant_id)s router_id=%(router_id)s"),
|
|
{'tenant_id': tenant_id, 'router_id': router_id})
|
|
|
|
chain_names = router_chain_names(router_id)
|
|
chains = {}
|
|
for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
|
|
if c.get_name() == chain_names['in']:
|
|
chains['in'] = c
|
|
elif c.get_name() == chain_names['out']:
|
|
chains['out'] = c
|
|
return chains
|
|
|
|
@handle_api_error
|
|
def create_router_chains(self, router):
|
|
"""Create chains for a new router.
|
|
|
|
Creates chains for the router and returns the same dictionary as
|
|
get_router_chains() returns.
|
|
|
|
:param router: router to set chains for
|
|
"""
|
|
LOG.debug(_("MidoClient.create_router_chains called: "
|
|
"router=%(router)s"), {'router': router})
|
|
chains = {}
|
|
router_id = router.get_id()
|
|
tenant_id = router.get_tenant_id()
|
|
chain_names = router_chain_names(router_id)
|
|
chains['in'] = self.mido_api.add_chain().tenant_id(tenant_id).name(
|
|
chain_names['in']).create()
|
|
|
|
chains['out'] = self.mido_api.add_chain().tenant_id(tenant_id).name(
|
|
chain_names['out']).create()
|
|
|
|
# set chains to in/out filters
|
|
router.inbound_filter_id(
|
|
chains['in'].get_id()).outbound_filter_id(
|
|
chains['out'].get_id()).update()
|
|
return chains
|
|
|
|
@handle_api_error
|
|
def destroy_router_chains(self, id):
|
|
"""Deletes chains of a router.
|
|
|
|
:param id: router ID to delete chains of
|
|
"""
|
|
LOG.debug(_("MidoClient.destroy_router_chains called: "
|
|
"id=%(id)s"), {'id': id})
|
|
# delete corresponding chains
|
|
router = self.get_router(id)
|
|
chains = self.get_router_chains(router.get_tenant_id(), id)
|
|
self.mido_api.delete_chain(chains['in'].get_id())
|
|
self.mido_api.delete_chain(chains['out'].get_id())
|
|
|
|
@handle_api_error
|
|
def link_router_to_metadata_router(self, router, metadata_router):
|
|
"""Link a router to the metadata router
|
|
|
|
:param router: router to link
|
|
:param metadata_router: metadata router
|
|
"""
|
|
LOG.debug(_("MidoClient.link_router_to_metadata_router called: "
|
|
"router=%(router)s, metadata_router=%(metadata_router)s"),
|
|
{'router': router, 'metadata_router': metadata_router})
|
|
# link to metadata router
|
|
in_port = metadata_router.add_interior_port()
|
|
mdr_port = in_port.network_address('169.254.255.0').network_length(
|
|
30).port_address('169.254.255.1').create()
|
|
|
|
tr_port = router.add_interior_port().network_address(
|
|
'169.254.255.0').network_length(30).port_address(
|
|
'169.254.255.2').create()
|
|
mdr_port.link(tr_port.get_id())
|
|
|
|
# forward metadata traffic to metadata router
|
|
router.add_route().type('Normal').src_network_addr(
|
|
'0.0.0.0').src_network_length(0).dst_network_addr(
|
|
'169.254.169.254').dst_network_length(32).weight(
|
|
100).next_hop_port(tr_port.get_id()).create()
|
|
|
|
@handle_api_error
|
|
def unlink_router_from_metadata_router(self, id, metadata_router):
|
|
"""Unlink a router from the metadata router
|
|
|
|
:param id: ID of router
|
|
:param metadata_router: metadata router
|
|
"""
|
|
LOG.debug(_("MidoClient.unlink_router_from_metadata_router called: "
|
|
"id=%(id)s, metadata_router=%(metadata_router)s"),
|
|
{'id': id, 'metadata_router': metadata_router})
|
|
# unlink from metadata router and delete the interior ports
|
|
# that connect metadata router and this router.
|
|
for pp in metadata_router.get_peer_ports():
|
|
if pp.get_device_id() == id:
|
|
mdr_port = self.get_port(pp.get_peer_id())
|
|
pp.unlink()
|
|
self.mido_api.delete_port(pp.get_id())
|
|
self.mido_api.delete_port(mdr_port.get_id())
|
|
|
|
@handle_api_error
|
|
def setup_floating_ip(self, router_id, provider_router, floating_ip,
|
|
fixed_ip, identifier):
|
|
"""Setup MidoNet for floating IP
|
|
|
|
:param router_id: router_id
|
|
:param provider_router: provider router
|
|
:param floating_ip: floating IP address
|
|
:param fixed_ip: fixed IP address
|
|
:param identifier: identifier to use to map to MidoNet
|
|
"""
|
|
LOG.debug(_("MidoClient.setup_floating_ip called: "
|
|
"router_id=%(router_id)s, "
|
|
"provider_router=%(provider_router)s"
|
|
"floating_ip=%(floating_ip)s, fixed_ip=%(fixed_ip)s"
|
|
"identifier=%(identifier)s"),
|
|
{'router_id': router_id, 'provider_router': provider_router,
|
|
'floating_ip': floating_ip, 'fixed_ip': fixed_ip,
|
|
'identifier': identifier})
|
|
# unlink from metadata router and delete the interior ports
|
|
router = self.mido_api.get_router(router_id)
|
|
# find the provider router port that is connected to the tenant
|
|
# of the floating ip
|
|
for p in router.get_peer_ports():
|
|
if p.get_device_id() == provider_router.get_id():
|
|
pr_port = p
|
|
|
|
# get the tenant router port id connected to provider router
|
|
tr_port_id = pr_port.get_peer_id()
|
|
|
|
# add a route for the floating ip to bring it to the tenant
|
|
provider_router.add_route().type(
|
|
'Normal').src_network_addr('0.0.0.0').src_network_length(
|
|
0).dst_network_addr(
|
|
floating_ip).dst_network_length(
|
|
32).weight(100).next_hop_port(
|
|
pr_port.get_id()).create()
|
|
|
|
chains = self.get_router_chains(router.get_tenant_id(), router_id)
|
|
|
|
# add dnat/snat rule pair for the floating ip
|
|
nat_targets = []
|
|
nat_targets.append(
|
|
{'addressFrom': fixed_ip, 'addressTo': fixed_ip,
|
|
'portFrom': 0, 'portTo': 0})
|
|
|
|
floating_property = {OS_FLOATING_IP_RULE_KEY: identifier}
|
|
chains['in'].add_rule().nw_dst_address(
|
|
floating_ip).nw_dst_length(32).type(
|
|
'dnat').flow_action('accept').nat_targets(
|
|
nat_targets).in_ports([tr_port_id]).position(
|
|
1).properties(floating_property).create()
|
|
|
|
nat_targets = []
|
|
nat_targets.append(
|
|
{'addressFrom': floating_ip, 'addressTo': floating_ip,
|
|
'portFrom': 0, 'portTo': 0})
|
|
|
|
chains['out'].add_rule().nw_src_address(
|
|
fixed_ip).nw_src_length(32).type(
|
|
'snat').flow_action('accept').nat_targets(
|
|
nat_targets).out_ports(
|
|
[tr_port_id]).position(1).properties(
|
|
floating_property).create()
|
|
|
|
@handle_api_error
|
|
def clear_floating_ip(self, router_id, provider_router, floating_ip,
|
|
identifier):
|
|
"""Remove floating IP
|
|
|
|
:param router_id: router_id
|
|
:param provider_router: provider router
|
|
:param floating_ip: floating IP address
|
|
:param identifier: identifier to use to map to MidoNet
|
|
"""
|
|
LOG.debug(_("MidoClient.clear_floating_ip called: "
|
|
"router_id=%(router_id)s, "
|
|
"provider_router=%(provider_router)s"
|
|
"floating_ip=%(floating_ip)s, identifier=%(identifier)s"),
|
|
{'router_id': router_id, 'provider_router': provider_router,
|
|
'floating_ip': floating_ip, 'identifier': identifier})
|
|
router = self.mido_api.get_router(router_id)
|
|
|
|
# find the provider router port that is connected to the tenant
|
|
# delete the route for this floating ip
|
|
for r in provider_router.get_routes():
|
|
if (r.get_dst_network_addr() == floating_ip and
|
|
r.get_dst_network_length() == 32):
|
|
self.mido_api.delete_route(r.get_id())
|
|
|
|
# delete snat/dnat rule pair for this floating ip
|
|
chains = self.get_router_chains(router.get_tenant_id(), router_id)
|
|
|
|
for r in chains['in'].get_rules():
|
|
if OS_FLOATING_IP_RULE_KEY in r.get_properties():
|
|
if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == identifier:
|
|
LOG.debug(_('deleting rule=%r'), r)
|
|
self.mido_api.delete_rule(r.get_id())
|
|
break
|
|
|
|
for r in chains['out'].get_rules():
|
|
if OS_FLOATING_IP_RULE_KEY in r.get_properties():
|
|
if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == identifier:
|
|
LOG.debug(_('deleting rule=%r'), r)
|
|
self.mido_api.delete_rule(r.get_id())
|
|
break
|
|
|
|
@handle_api_error
|
|
def create_for_sg(self, tenant_id, sg_id, sg_name):
|
|
"""Create a new chain for security group.
|
|
|
|
Creating a security group creates a pair of chains in MidoNet, one for
|
|
inbound and the other for outbound.
|
|
"""
|
|
LOG.debug(_("MidoClient.create_for_sg called: "
|
|
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
|
|
"sg_name=%(sg_name)s "),
|
|
{'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
|
|
|
|
cnames = chain_names(sg_id, sg_name)
|
|
self.mido_api.add_chain().tenant_id(tenant_id).name(
|
|
cnames['in']).create()
|
|
self.mido_api.add_chain().tenant_id(tenant_id).name(
|
|
cnames['out']).create()
|
|
|
|
pg_name = port_group_name(sg_id, sg_name)
|
|
self.mido_api.add_port_group().tenant_id(tenant_id).name(
|
|
pg_name).create()
|
|
|
|
@handle_api_error
|
|
def delete_for_sg(self, tenant_id, sg_id, sg_name):
|
|
"""Delete a chain mapped to a security group.
|
|
|
|
Delete a SG means deleting all the chains (inbound and outbound)
|
|
associated with the SG in MidoNet.
|
|
"""
|
|
LOG.debug(_("MidoClient.delete_for_sg called: "
|
|
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
|
|
"sg_name=%(sg_name)s "),
|
|
{'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
|
|
|
|
cnames = chain_names(sg_id, sg_name)
|
|
chains = self.mido_api.get_chains({'tenant_id': tenant_id})
|
|
for c in chains:
|
|
if c.get_name() == cnames['in'] or c.get_name() == cnames['out']:
|
|
LOG.debug(_('MidoClient.delete_for_sg: deleting chain=%r'),
|
|
c.get_id())
|
|
self.mido_api.delete_chain(c.get_id())
|
|
|
|
pg_name = port_group_name(sg_id, sg_name)
|
|
pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id})
|
|
for pg in pgs:
|
|
if pg.get_name() == pg_name:
|
|
LOG.debug(_("MidoClient.delete_for_sg: deleting pg=%r"),
|
|
pg)
|
|
self.mido_api.delete_port_group(pg.get_id())
|
|
|
|
@handle_api_error
|
|
def get_sg_chains(self, tenant_id, sg_id):
|
|
"""Get a list of chains mapped to a security group."""
|
|
LOG.debug(_("MidoClient.get_sg_chains called: "
|
|
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s"),
|
|
{'tenant_id': tenant_id, 'sg_id': sg_id})
|
|
|
|
cnames = chain_names(sg_id, sg_name='')
|
|
chain_name_prefix_for_id = cnames['in'][:NAME_IDENTIFIABLE_PREFIX_LEN]
|
|
chains = {}
|
|
|
|
for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
|
|
if c.get_name().startswith(chain_name_prefix_for_id):
|
|
if c.get_name().endswith(SUFFIX_IN):
|
|
chains['in'] = c
|
|
if c.get_name().endswith(SUFFIX_OUT):
|
|
chains['out'] = c
|
|
assert 'in' in chains
|
|
assert 'out' in chains
|
|
return chains
|
|
|
|
@handle_api_error
|
|
def get_port_groups_for_sg(self, tenant_id, sg_id):
|
|
LOG.debug(_("MidoClient.get_port_groups_for_sg called: "
|
|
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s"),
|
|
{'tenant_id': tenant_id, 'sg_id': sg_id})
|
|
|
|
pg_name_prefix = port_group_name(
|
|
sg_id, sg_name='')[:NAME_IDENTIFIABLE_PREFIX_LEN]
|
|
port_groups = self.mido_api.get_port_groups({'tenant_id': tenant_id})
|
|
for pg in port_groups:
|
|
if pg.get_name().startswith(pg_name_prefix):
|
|
LOG.debug(_(
|
|
"MidoClient.get_port_groups_for_sg exiting: pg=%r"), pg)
|
|
return pg
|
|
return None
|
|
|
|
@handle_api_error
|
|
def create_for_sg_rule(self, rule):
|
|
LOG.debug(_("MidoClient.create_for_sg_rule called: rule=%r"), rule)
|
|
|
|
direction = rule['direction']
|
|
protocol = rule['protocol']
|
|
port_range_max = rule['port_range_max']
|
|
rule_id = rule['id']
|
|
security_group_id = rule['security_group_id']
|
|
remote_group_id = rule['remote_group_id']
|
|
remote_ip_prefix = rule['remote_ip_prefix'] # watch out. not validated
|
|
tenant_id = rule['tenant_id']
|
|
port_range_min = rule['port_range_min']
|
|
|
|
# construct a corresponding rule
|
|
tp_src_start = tp_src_end = None
|
|
tp_dst_start = tp_dst_end = None
|
|
nw_src_address = None
|
|
nw_src_length = None
|
|
port_group_id = None
|
|
|
|
# handle source
|
|
if remote_ip_prefix is not None:
|
|
nw_src_address, nw_src_length = remote_ip_prefix.split('/')
|
|
elif not remote_group_id is None: # security group as a srouce
|
|
source_pg = self.pg_manager.get_for_sg(tenant_id, remote_group_id)
|
|
port_group_id = source_pg.get_id()
|
|
else:
|
|
raise Exception(_("Don't know what to do with rule=%r"), rule)
|
|
|
|
# dst ports
|
|
tp_dst_start, tp_dst_end = port_range_min, port_range_max
|
|
|
|
# protocol
|
|
if protocol == 'tcp':
|
|
nw_proto = 6
|
|
elif protocol == 'udp':
|
|
nw_proto = 17
|
|
elif protocol == 'icmp':
|
|
nw_proto = 1
|
|
# extract type and code from reporposed fields
|
|
icmp_type = rule['from_port']
|
|
icmp_code = rule['to_port']
|
|
|
|
# translate -1(wildcard in OS) to midonet wildcard
|
|
if icmp_type == -1:
|
|
icmp_type = None
|
|
if icmp_code == -1:
|
|
icmp_code = None
|
|
|
|
# set data for midonet rule
|
|
tp_src_start = tp_src_end = icmp_type
|
|
tp_dst_start = tp_dst_end = icmp_code
|
|
|
|
chains = self.get_sg_chains(tenant_id, security_group_id)
|
|
chain = None
|
|
if direction == 'egress':
|
|
chain = chains['in']
|
|
elif direction == 'ingress':
|
|
chain = chains['out']
|
|
else:
|
|
raise Exception(_("Don't know what to do with rule=%r"), rule)
|
|
|
|
# create an accept rule
|
|
properties = sg_rule_properties(rule_id)
|
|
LOG.debug(_("MidoClient.create_for_sg_rule: adding accept rule "
|
|
"%(rule_id)s in portgroup %(port_group_id)s"),
|
|
{'rule_id': rule_id, 'port_group_id': port_group_id})
|
|
chain.add_rule().port_group(port_group_id).type('accept').nw_proto(
|
|
nw_proto).nw_src_address(nw_src_address).nw_src_length(
|
|
nw_src_length).tp_src_start(tp_src_start).tp_src_end(
|
|
tp_src_end).tp_dst_start(tp_dst_start).tp_dst_end(
|
|
tp_dst_end).properties(properties).create()
|
|
|
|
@handle_api_error
|
|
def delete_for_sg_rule(self, rule):
|
|
LOG.debug(_("MidoClient.delete_for_sg_rule called: rule=%r"), rule)
|
|
|
|
tenant_id = rule['tenant_id']
|
|
security_group_id = rule['security_group_id']
|
|
rule_id = rule['id']
|
|
|
|
properties = sg_rule_properties(rule_id)
|
|
# search for the chains to find the rule to delete
|
|
chains = self.get_sg_chains(tenant_id, security_group_id)
|
|
for c in chains['in'], chains['out']:
|
|
rules = c.get_rules()
|
|
for r in rules:
|
|
if r.get_properties() == properties:
|
|
LOG.debug(_("MidoClient.delete_for_sg_rule: deleting "
|
|
"rule %r"), r)
|
|
self.mido_api.delete_rule(r.get_id())
|