vmware-nsx/neutron/plugins/midonet/midonet_lib.py
Joe Mills b07d7454e8 Add host routes and dns nameservers to Midonet DHCP
In the Midonet plugin, the host routes and dns nameserver information
was not being passed down to the midonet client API. This fix addresses
this by passing down the correct information.

Change-Id: I142ad4ceccdcf8b0e13db55fa54513f82995efc5
Closes-Bug: #1229655
2013-09-27 07:33:17 +00:00

658 lines
26 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
# @author: Rossella Sblendido, Midokura Japan KK
from midonetclient import exc
from webob import exc as w_exc
from neutron.common import exceptions as n_exc
from neutron.openstack.common import log as logging
from neutron.plugins.midonet.common import net_util
LOG = logging.getLogger(__name__)
def handle_api_error(fn):
"""Wrapper for methods that throws custom exceptions."""
def wrapped(*args, **kwargs):
try:
return fn(*args, **kwargs)
except (w_exc.HTTPException,
exc.MidoApiConnectionError) as ex:
raise MidonetApiException(msg=ex)
return wrapped
class MidonetResourceNotFound(n_exc.NotFound):
message = _('MidoNet %(resource_type)s %(id)s could not be found')
class MidonetApiException(n_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, cidr, host_rts=None,
dns_servers=None):
"""Create a new DHCP entry
:param bridge: bridge object to add dhcp to
:param gateway_ip: IP address of gateway
:param cidr: subnet represented as x.x.x.x/y
:param host_rts: list of routes set in the host
:param dns_servers: list of dns servers
:returns: newly created dhcp
"""
LOG.debug(_("MidoClient.create_dhcp called: bridge=%(bridge)s, "
"cidr=%(cidr)s, gateway_ip=%(gateway_ip)s, "
"host_rts=%(host_rts)s, dns_servers=%(dns_servers)s"),
{'bridge': bridge, 'cidr': cidr, 'gateway_ip': gateway_ip,
'host_rts': host_rts, 'dns_servers': dns_servers})
self.mido_api.add_bridge_dhcp(bridge, gateway_ip, cidr,
host_rts=host_rts,
dns_nservers=dns_servers)
@handle_api_error
def add_dhcp_host(self, bridge, cidr, ip, mac):
"""Add DHCP host entry
:param bridge: bridge the DHCP is configured for
:param cidr: subnet represented as x.x.x.x/y
:param ip: IP address
:param mac: MAC address
"""
LOG.debug(_("MidoClient.add_dhcp_host called: bridge=%(bridge)s, "
"cidr=%(cidr)s, ip=%(ip)s, mac=%(mac)s"),
{'bridge': bridge, 'cidr': cidr, 'ip': ip, 'mac': mac})
subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
if subnet is None:
raise MidonetApiException(msg=_("Tried to add to"
"non-existent DHCP"))
subnet.add_dhcp_host().ip_addr(ip).mac_addr(mac).create()
@handle_api_error
def remove_dhcp_host(self, bridge, cidr, ip, mac):
"""Remove DHCP host entry
:param bridge: bridge the DHCP is configured for
:param cidr: subnet represented as x.x.x.x/y
:param ip: IP address
:param mac: MAC address
"""
LOG.debug(_("MidoClient.remove_dhcp_host called: bridge=%(bridge)s, "
"cidr=%(cidr)s, ip=%(ip)s, mac=%(mac)s"),
{'bridge': bridge, 'cidr': cidr, 'ip': ip, 'mac': mac})
subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
if subnet is None:
LOG.warn(_("Tried to delete mapping from non-existent subnet"))
return
for dh in subnet.get_dhcp_hosts():
if dh.get_mac_addr() == mac and dh.get_ip_addr() == ip:
LOG.debug(_("MidoClient.remove_dhcp_host: Deleting %(dh)r"),
{"dh": dh})
dh.delete()
@handle_api_error
def delete_dhcp_host(self, bridge_id, cidr, ip, mac):
"""Delete DHCP host entry
:param bridge_id: id of the bridge of the DHCP
:param cidr: subnet represented as x.x.x.x/y
:param ip: IP address
:param mac: MAC address
"""
LOG.debug(_("MidoClient.delete_dhcp_host called: "
"bridge_id=%(bridge_id)s, cidr=%(cidr)s, ip=%(ip)s, "
"mac=%(mac)s"), {'bridge_id': bridge_id,
'cidr': cidr,
'ip': ip, 'mac': mac})
bridge = self.get_bridge(bridge_id)
self.remove_dhcp_host(bridge, net_util.subnet_str(cidr), ip, mac)
@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=%(bridge)s, "),
{'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_chains=False):
"""Delete a port
:param id: id of the port
"""
LOG.debug(_("MidoClient.delete_port called: id=%(id)s, "
"delete_chains=%(delete_chains)s"),
{'id': id, 'delete_chains': delete_chains})
if delete_chains:
self.delete_port_chains(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 add_bridge_port(self, bridge):
"""Add a port on a bridge
:param bridge: Bridge to add a new port to
:returns: newly created port
"""
LOG.debug(_("MidoClient.add_bridge_port called: "
"bridge=%(bridge)s"), {'bridge': bridge})
return self.mido_api.add_bridge_port(bridge)
@handle_api_error
def add_router_port(self, router, port_address=None,
network_address=None, network_length=None):
"""Add a new port to an existing router."""
return self.mido_api.add_router_port(router,
port_address=port_address,
network_address=network_address,
network_length=network_length)
@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 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 delete_route(self, id):
return self.mido_api.delete_route(id)
@handle_api_error
def add_dhcp_route_option(self, bridge, cidr, gw_ip, dst_ip):
"""Add Option121 route to subnet
:param bridge: Bridge to add the option route to
:param cidr: subnet represented as x.x.x.x/y
:param gw_ip: IP address of the next hop
:param dst_ip: IP address of the destination, in x.x.x.x/y format
"""
LOG.debug(_("MidoClient.add_dhcp_route_option called: "
"bridge=%(bridge)s, cidr=%(cidr)s, gw_ip=%(gw_ip)s"
"dst_ip=%(dst_ip)s"),
{"bridge": bridge, "cidr": cidr, "gw_ip": gw_ip,
"dst_ip": dst_ip})
subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
if subnet is None:
raise MidonetApiException(
msg=_("Tried to access non-existent DHCP"))
prefix, length = dst_ip.split("/")
routes = [{'destinationPrefix': prefix, 'destinationLength': length,
'gatewayAddr': gw_ip}]
cur_routes = subnet.get_opt121_routes()
if cur_routes:
routes = routes + cur_routes
subnet.opt121_routes(routes).update()
@handle_api_error
def link(self, port, peer_id):
"""Link a port to a given peerId."""
self.mido_api.link(port, peer_id)
@handle_api_error
def delete_port_routes(self, routes, port_id):
"""Remove routes whose next hop port is the given port ID."""
for route in routes:
if route.get_next_hop_port() == port_id:
self.mido_api.delete_route(route.get_id())
@handle_api_error
def get_router_routes(self, router_id):
"""Get all routes for the given router."""
return self.mido_api.get_router_routes(router_id)
@handle_api_error
def unlink(self, port):
"""Unlink a port
:param port: port object
"""
LOG.debug(_("MidoClient.unlink called: port=%(port)s"),
{'port': port})
if port.get_peer_id():
self.mido_api.unlink(port)
else:
LOG.warn(_("Attempted to unlink a port that was not linked. %s"),
port.get_id())
@handle_api_error
def remove_rules_by_property(self, tenant_id, chain_name, key, value):
"""Remove all the rules that match the provided key and value."""
LOG.debug(_("MidoClient.remove_rules_by_property called: "
"tenant_id=%(tenant_id)s, chain_name=%(chain_name)s"
"key=%(key)s, value=%(value)s"),
{'tenant_id': tenant_id, 'chain_name': chain_name,
'key': key, 'value': value})
chain = self.get_chain_by_name(tenant_id, chain_name)
if chain is None:
raise MidonetResourceNotFound(resource_type='Chain',
id=chain_name)
for r in chain.get_rules():
if key in r.get_properties():
if r.get_properties()[key] == value:
self.mido_api.delete_rule(r.get_id())
@handle_api_error
def add_router_chains(self, router, inbound_chain_name,
outbound_chain_name):
"""Create chains for a new router.
Creates inbound and outbound chains for the router with the given
names, and the new chains are set on the router.
:param router: router to set chains for
:param inbound_chain_name: Name of the inbound chain
:param outbound_chain_name: Name of the outbound chain
"""
LOG.debug(_("MidoClient.create_router_chains called: "
"router=%(router)s, inbound_chain_name=%(in_chain)s, "
"outbound_chain_name=%(out_chain)s"),
{"router": router, "in_chain": inbound_chain_name,
"out_chain": outbound_chain_name})
tenant_id = router.get_tenant_id()
inbound_chain = self.mido_api.add_chain().tenant_id(tenant_id).name(
inbound_chain_name,).create()
outbound_chain = self.mido_api.add_chain().tenant_id(tenant_id).name(
outbound_chain_name).create()
# set chains to in/out filters
router.inbound_filter_id(inbound_chain.get_id()).outbound_filter_id(
outbound_chain.get_id()).update()
return inbound_chain, outbound_chain
@handle_api_error
def delete_router_chains(self, id):
"""Deletes chains of a router.
:param id: router ID to delete chains of
"""
LOG.debug(_("MidoClient.delete_router_chains called: "
"id=%(id)s"), {'id': id})
router = self.get_router(id)
if (router.get_inbound_filter_id()):
self.mido_api.delete_chain(router.get_inbound_filter_id())
if (router.get_outbound_filter_id()):
self.mido_api.delete_chain(router.get_outbound_filter_id())
@handle_api_error
def delete_port_chains(self, id):
"""Deletes chains of a port.
:param id: port ID to delete chains of
"""
LOG.debug(_("MidoClient.delete_port_chains called: "
"id=%(id)s"), {'id': id})
port = self.get_port(id)
if (port.get_inbound_filter_id()):
self.mido_api.delete_chain(port.get_inbound_filter_id())
if (port.get_outbound_filter_id()):
self.mido_api.delete_chain(port.get_outbound_filter_id())
@handle_api_error
def get_link_port(self, router, peer_router_id):
"""Setup a route on the router to the next hop router."""
LOG.debug(_("MidoClient.get_link_port called: "
"router=%(router)s, peer_router_id=%(peer_router_id)s"),
{'router': router, 'peer_router_id': peer_router_id})
# Find the port linked between the two routers
link_port = None
for p in router.get_peer_ports():
if p.get_device_id() == peer_router_id:
link_port = p
break
return link_port
@handle_api_error
def add_router_route(self, router, type='Normal',
src_network_addr=None, src_network_length=None,
dst_network_addr=None, dst_network_length=None,
next_hop_port=None, next_hop_gateway=None,
weight=100):
"""Setup a route on the router."""
return self.mido_api.add_router_route(
router, type=type, src_network_addr=src_network_addr,
src_network_length=src_network_length,
dst_network_addr=dst_network_addr,
dst_network_length=dst_network_length,
next_hop_port=next_hop_port, next_hop_gateway=next_hop_gateway,
weight=weight)
@handle_api_error
def add_static_nat(self, tenant_id, chain_name, from_ip, to_ip, port_id,
nat_type='dnat', **kwargs):
"""Add a static NAT entry
:param tenant_id: owner fo the chain to add a NAT to
:param chain_name: name of the chain to add a NAT to
:param from_ip: IP to translate from
:param from_ip: IP to translate from
:param to_ip: IP to translate to
:param port_id: port to match on
:param nat_type: 'dnat' or 'snat'
"""
LOG.debug(_("MidoClient.add_static_nat called: "
"tenant_id=%(tenant_id)s, chain_name=%(chain_name)s, "
"from_ip=%(from_ip)s, to_ip=%(to_ip)s, "
"port_id=%(port_id)s, nat_type=%(nat_type)s"),
{'tenant_id': tenant_id, 'chain_name': chain_name,
'from_ip': from_ip, 'to_ip': to_ip,
'portid': port_id, 'nat_type': nat_type})
if nat_type not in ['dnat', 'snat']:
raise ValueError(_("Invalid NAT type passed in %s") % nat_type)
chain = self.get_chain_by_name(tenant_id, chain_name)
nat_targets = []
nat_targets.append(
{'addressFrom': to_ip, 'addressTo': to_ip,
'portFrom': 0, 'portTo': 0})
rule = chain.add_rule().type(nat_type).flow_action('accept').position(
1).nat_targets(nat_targets).properties(kwargs)
if nat_type == 'dnat':
rule = rule.nw_dst_address(from_ip).nw_dst_length(32).in_ports(
[port_id])
else:
rule = rule.nw_src_address(from_ip).nw_src_length(32).out_ports(
[port_id])
return rule.create()
@handle_api_error
def add_dynamic_snat(self, tenant_id, pre_chain_name, post_chain_name,
snat_ip, port_id, **kwargs):
"""Add SNAT masquerading rule
MidoNet requires two rules on the router, one to do NAT to a range of
ports, and another to retrieve back the original IP in the return
flow.
"""
pre_chain = self.get_chain_by_name(tenant_id, pre_chain_name)
post_chain = self.get_chain_by_name(tenant_id, post_chain_name)
pre_chain.add_rule().nw_dst_address(snat_ip).nw_dst_length(
32).type('rev_snat').flow_action('accept').in_ports(
[port_id]).properties(kwargs).position(1).create()
nat_targets = []
nat_targets.append(
{'addressFrom': snat_ip, 'addressTo': snat_ip,
'portFrom': 1, 'portTo': 65535})
post_chain.add_rule().type('snat').flow_action(
'accept').nat_targets(nat_targets).out_ports(
[port_id]).properties(kwargs).position(1).create()
@handle_api_error
def remove_static_route(self, router, ip):
"""Remove static route for the IP
:param router: next hop router to remove the routes to
:param ip: IP address of the route to remove
"""
LOG.debug(_("MidoClient.remote_static_route called: "
"router=%(router)s, ip=%(ip)s"),
{'router': router, 'ip': ip})
for r in router.get_routes():
if (r.get_dst_network_addr() == ip and
r.get_dst_network_length() == 32):
self.mido_api.delete_route(r.get_id())
@handle_api_error
def update_port_chains(self, port, inbound_chain_id, outbound_chain_id):
"""Bind inbound and outbound chains to the port."""
LOG.debug(_("MidoClient.update_port_chains called: port=%(port)s"
"inbound_chain_id=%(inbound_chain_id)s, "
"outbound_chain_id=%(outbound_chain_id)s"),
{"port": port, "inbound_chain_id": inbound_chain_id,
"outbound_chain_id": outbound_chain_id})
port.inbound_filter_id(inbound_chain_id).outbound_filter_id(
outbound_chain_id).update()
@handle_api_error
def create_chain(self, tenant_id, name):
"""Create a new chain."""
LOG.debug(_("MidoClient.create_chain called: tenant_id=%(tenant_id)s "
" name=%(name)s"), {"tenant_id": tenant_id, "name": name})
return self.mido_api.add_chain().tenant_id(tenant_id).name(
name).create()
@handle_api_error
def delete_chain(self, id):
"""Delete chain matching the ID."""
LOG.debug(_("MidoClient.delete_chain called: id=%(id)s"), {"id": id})
self.mido_api.delete_chain(id)
@handle_api_error
def delete_chains_by_names(self, tenant_id, names):
"""Delete chains matching the names given for a tenant."""
LOG.debug(_("MidoClient.delete_chains_by_names called: "
"tenant_id=%(tenant_id)s names=%(names)s "),
{"tenant_id": tenant_id, "names": names})
chains = self.mido_api.get_chains({'tenant_id': tenant_id})
for c in chains:
if c.get_name() in names:
self.mido_api.delete_chain(c.get_id())
@handle_api_error
def get_chain_by_name(self, tenant_id, name):
"""Get the chain by its name."""
LOG.debug(_("MidoClient.get_chain_by_name called: "
"tenant_id=%(tenant_id)s name=%(name)s "),
{"tenant_id": tenant_id, "name": name})
for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
if c.get_name() == name:
return c
return None
@handle_api_error
def get_port_group_by_name(self, tenant_id, name):
"""Get the port group by name."""
LOG.debug(_("MidoClient.get_port_group_by_name called: "
"tenant_id=%(tenant_id)s name=%(name)s "),
{"tenant_id": tenant_id, "name": name})
for p in self.mido_api.get_port_groups({'tenant_id': tenant_id}):
if p.get_name() == name:
return p
return None
@handle_api_error
def create_port_group(self, tenant_id, name):
"""Create a port group
Create a new port group for a given name and ID.
"""
LOG.debug(_("MidoClient.create_port_group called: "
"tenant_id=%(tenant_id)s name=%(name)s"),
{"tenant_id": tenant_id, "name": name})
return self.mido_api.add_port_group().tenant_id(tenant_id).name(
name).create()
@handle_api_error
def delete_port_group_by_name(self, tenant_id, name):
"""Delete port group matching the name given for a tenant."""
LOG.debug(_("MidoClient.delete_port_group_by_name called: "
"tenant_id=%(tenant_id)s name=%(name)s "),
{"tenant_id": tenant_id, "name": name})
pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id})
for pg in pgs:
if pg.get_name() == name:
LOG.debug(_("Deleting pg %(id)s"), {"id": pg.get_id()})
self.mido_api.delete_port_group(pg.get_id())
@handle_api_error
def add_port_to_port_group_by_name(self, tenant_id, name, port_id):
"""Add a port to a port group with the given name."""
LOG.debug(_("MidoClient.add_port_to_port_group_by_name called: "
"tenant_id=%(tenant_id)s name=%(name)s "
"port_id=%(port_id)s"),
{"tenant_id": tenant_id, "name": name, "port_id": port_id})
pg = self.get_port_group_by_name(tenant_id, name)
if pg is None:
raise MidonetResourceNotFound(resource_type='PortGroup', id=name)
pg = pg.add_port_group_port().port_id(port_id).create()
return pg
@handle_api_error
def remove_port_from_port_groups(self, port_id):
"""Remove a port binding from all the port groups."""
LOG.debug(_("MidoClient.remove_port_from_port_groups called: "
"port_id=%(port_id)s"), {"port_id": port_id})
port = self.get_port(port_id)
for pg in port.get_port_groups():
pg.delete()
@handle_api_error
def add_chain_rule(self, chain, action='accept', **kwargs):
"""Create a new accept chain rule."""
self.mido_api.add_chain_rule(chain, action, **kwargs)