Merge "Add OVN_Northbound API LR, LRP, and LB commands"

This commit is contained in:
Jenkins 2017-07-13 19:50:29 +00:00 committed by Gerrit Code Review
commit 6417850d89
9 changed files with 1587 additions and 15 deletions

View File

@ -70,6 +70,9 @@ class Backend(object):
raise
def _lookup(self, table, record):
if record == "":
raise TypeError("Cannot look up record by empty string")
t = self.tables[table]
try:
if isinstance(record, uuid.UUID):

View File

@ -17,4 +17,13 @@ DEFAULT_OVNNB_CONNECTION = 'tcp:127.0.0.1:6641'
DEFAULT_TIMEOUT = 5
DEVICE_NAME_MAX_LEN = 14
ACL_PRIORITY_MAX = 32767
NAT_SNAT = 'snat'
NAT_DNAT = 'dnat'
NAT_BOTH = 'dnat_and_snat'
NAT_TYPES = (NAT_SNAT, NAT_DNAT, NAT_BOTH)
PROTO_TCP = 'tcp'
PROTO_UDP = 'udp'

View File

@ -15,6 +15,7 @@ import abc
import six
from ovsdbapp import api
from ovsdbapp import constants as const
@six.add_metaclass(abc.ABCMeta)
@ -290,6 +291,304 @@ class API(api.API):
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lr_add(self, router=None, may_exist=False, **columns):
"""Create a logical router named `router`
:param router: The optional name or uuid of the router
:type router: string or uuid.UUID
:param may_exist: If True, don't fail if the router already exists
:type may_exist: boolean
:param **columns: Additional columns to directly set on the router
:returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def lr_del(self, router, if_exists=False):
"""Delete 'router' and all its ports
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:param if_exists: If True, don't fail if the router doesn't exist
:type if_exists: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lr_list(self):
"""Get the UUIDs of all logical routers
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def lrp_add(self, router, port, mac, networks, peer=None, may_exist=False,
**columns):
"""Add logical port 'port' on 'router'
:param router: The name or uuid of the router to attach the port
:type router: string or uuid.UUID
:param mac: The MAC address of the port
:type mac: string
:param networks: One or more IP address/netmask to assign to the port
:type networks: list of strings
:param peer: Optional logical router port connected to this one
:param may_exist: If True, don't fail if the port already exists
:type may_exist: boolean
:param **columns: Additional column values to directly set on the port
:returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def lrp_del(self, port, router=None, if_exists=None):
"""Delete 'port' from its attached router
:param port: The name or uuid of the port
:type port: string or uuid.UUID
:param router: Only delete router if attached to `router`
:type router: string or uuiwhd.UUID
:param if_exists: If True, don't fail if the port doesn't exist
:type if_exists: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lrp_list(self, router):
"""Get the UUIDs of all ports on 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def lrp_set_enabled(self, port, is_enabled):
"""Set administrative state of 'port'
:param port: The name or uuid of the port
:type port: string or uuid.UUID
:param is_enabled: True for enabled, False for disabled
:type is_enabled: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lrp_get_enabled(self, port):
"""Get administrative state of 'port'
:param port: The name or uuid of the port
:type port: string or uuid.UUID
:returns:
"""
@abc.abstractmethod
def lr_route_add(self, router, prefix, nexthop, port=None,
policy='dst-ip', may_exist=False):
"""Add a route to 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:param prefix: an IPv4/6 prefix for this route, e.g. 192.168.1.0/24
:type prefix: type string
:parm nexthop: The gateway to use for this route, which should be
the IP address of one of `router`'s logical router
ports or the IP address of a logical port
:type nexthop: string
:param port: If specified, packets that match this route will be
sent out this port. Otherwise OVN infers the output
port based on nexthop.
:type port: string
:param policy: the policy used to make routing decisions
:type policy: string, 'dst-ip' or 'src-ip'
:param may_exist: If True, don't fail if the route already exists
:type may_exist: boolean
returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def lr_route_del(self, router, prefix=None, if_exists=False):
"""Remove routes from 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:param prefix: an IPv4/6 prefix to match, e.g. 192.168.1.0/24
:type prefix: type string
:param if_exists: If True, don't fail if the port doesn't exist
:type if_exists: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lr_route_list(self, router):
"""Get the UUIDs of static logical routes from 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def lr_nat_add(self, router, nat_type, external_ip, logical_ip,
logical_port=None, external_mac=None, may_exist=False):
"""Add a NAT to 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:param nat_type: The type of NAT to be done
:type nat_type: NAT_SNAT, NAT_DNAT, or NAT_BOTH
:param external_ip: Externally visible Ipv4 address
:type external_ip: string
:param logical_ip: The logical IPv4 network or address with which
`external_ip` is NATted
:type logical_ip: string
:param logical_port: The name of an existing logical switch port where
the logical_ip resides
:type logical_port: string
:param external_mac: ARP replies for the external_ip return the value
of `external_mac`. Packets transmitted with
source IP address equal to `external_ip` will be
sent using `external_mac`.
:type external_mac: string
:param may_exist: If True, don't fail if the route already exists
and if `logical_port` and `external_mac` are
specified, they will be updated
:type may_exist: boolean
:returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def lr_nat_del(self, router, nat_type=None, match_ip=None, if_exists=None):
"""Remove NATs from 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:param nat_type: The type of NAT to match
:type nat_type: NAT_SNAT, NAT_DNAT, or NAT_BOTH
:param match_ip: The IPv4 address to match on. If
`nat_type` is specified and is NAT_SNAT, the IP
should be the logical ip, otherwise the IP should
be the external IP.
:type match_ip: string
:param if_exists: If True, don't fail if the port doesn't exist
:type if_exists: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lr_nat_list(self, router):
"""Get the NATs on 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def lb_add(self, vip, ips, protocol=const.PROTO_TCP, may_exist=False):
"""Create a load-balancer or add a VIP to an existing load balancer
:param lb: The name or uuid of the load-balancer
:type lb: string or uuid.UUID
:param vip: A virtual IP in the format IP[:PORT]
:type vip: string
:param ips: A list of ips in the form IP[:PORT]
:type ips: string
:param protocol: The IP protocol for load balancing
:type protocol: PROTO_TCP or PROTO_UDP
:param may_exist: If True, don't fail if a LB w/ `vip` exists, and
instead, replace the vips on the LB
:type may_exist: boolean
:returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def lb_del(self, lb, vip=None, if_exists=False):
"""Remove a load balancer or just the VIP from a load balancer
:param lb: The name or uuid of a load balancer
:type lb: string or uuid.UUID
:param vip: The VIP on the load balancer to match
:type: string
:param if_exists: If True, don't fail if the port doesn't exist
:type if_exists: boolean
"""
@abc.abstractmethod
def lb_list(self):
"""Get the UUIDs of all load balanacers"""
@abc.abstractmethod
def lr_lb_add(self, router, lb, may_exist=False):
"""Add a load-balancer to 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:param lb: The name or uuid of the load balancer
:type lb: string or uuid.UUID
:param may_exist: If True, don't fail if lb already assigned to lr
:type may_exist: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lr_lb_del(self, router, lb=None, if_exists=False):
"""Remove load-balancers from 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:param lb: The name or uuid of the load balancer to remove. None
to remove all load balancers from the router
:type lb: string or uuid.UUID
:type if_exists: If True, don't fail if the switch doesn't exist
:type if_exists: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def lr_lb_list(self, router):
"""Get UUIDs of load-balancers on 'router'
:param router: The name or uuid of the router
:type router: string or uuid.UUID
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def ls_lb_add(self, switch, lb, may_exist=False):
"""Add a load-balancer to 'switch'
:param switch: The name or uuid of the switch
:type switch: string or uuid.UUID
:param lb: The name or uuid of the load balancer
:type lb: string or uuid.UUID
:param may_exist: If True, don't fail if lb already assigned to lr
:type may_exist: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def ls_lb_del(self, switch, lb=None, if_exists=False):
"""Remove load-balancers from 'switch'
:param switch: The name or uuid of the switch
:type switch: string or uuid.UUID
:param lb: The name or uuid of the load balancer to remove. None
to remove all load balancers from the switch
:type lb: string or uuid.UUID
:type if_exists: If True, don't fail if the switch doesn't exist
:type if_exists: boolean
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def ls_lb_list(self, switch):
"""Get UUIDs of load-balancers on 'switch'
:param switch: The name or uuid of the switch
:type switch: string or uuid.UUID
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def dhcp_options_add(self, cidr, **external_ids):
"""Create a DHCP options row with CIDR

View File

@ -17,6 +17,7 @@ from ovsdbapp.backend import ovs_idl
from ovsdbapp.backend.ovs_idl import command as cmd
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp import constants as const
from ovsdbapp import utils
class AddCommand(cmd.BaseCommand):
@ -226,33 +227,43 @@ class LspAddCommand(AddCommand):
self.result = lsp.uuid
class LspDelCommand(cmd.BaseCommand):
def __init__(self, api, port, switch=None, if_exists=False):
super(LspDelCommand, self).__init__(api)
class PortDelCommand(cmd.BaseCommand):
def __init__(self, api, table, port, parent_table, parent=None,
if_exists=False):
super(PortDelCommand, self).__init__(api)
self.table = table
self.port = port
self.switch = switch
self.parent_table = parent_table
self.parent = parent
self.if_exists = if_exists
def run_idl(self, txn):
try:
lsp = self.api.lookup('Logical_Switch_Port', self.port)
row = self.api.lookup(self.table, self.port)
except idlutils.RowNotFound:
if self.if_exists:
return
raise RuntimeError("%s does not exist" % self.port)
# We need to delete the port from its switch
if self.switch:
sw = self.api.lookup('Logical_Switch', self.switch)
# We need to delete the port from its parent
if self.parent:
parent = self.api.lookup(self.parent_table, self.parent)
else:
sw = next(iter(
s for s in self.api.tables['Logical_Switch'].rows.values()
if lsp in s.ports), None)
if not (sw and lsp in sw.ports):
parent = next(iter(
p for p in self.api.tables[self.parent_table].rows.values()
if row in p.ports), None)
if not (parent and row in parent.ports):
raise RuntimeError("%s does not exist in %s" % (
self.port, self.switch))
sw.delvalue('ports', lsp)
lsp.delete()
self.port, self.parent))
parent.delvalue('ports', row)
row.delete()
class LspDelCommand(PortDelCommand):
def __init__(self, api, port, switch=None, if_exists=False):
super(LspDelCommand, self).__init__(
api, 'Logical_Switch_Port', port, 'Logical_Switch', switch,
if_exists)
class LspListCommand(cmd.BaseCommand):
@ -487,3 +498,500 @@ class DhcpOptionsGetOptionsCommand(cmd.BaseCommand):
def run_idl(self, txn):
dhcpopt = self.api.lookup('DHCP_Options', self.dhcpopt_uuid)
self.result = dhcpopt.options
class LrAddCommand(cmd.BaseCommand):
def __init__(self, api, router=None, may_exist=False, **columns):
super(LrAddCommand, self).__init__(api)
self.router = router
self.may_exist = may_exist
self.columns = columns
def run_idl(self, txn):
if self.router:
try:
lr = self.api.lookup('Logical_Router', self.router)
if self.may_exist:
self.result = ovs_idl.RowView(lr)
return
except idlutils.RowNotFound:
pass
lr = txn.insert(self.api.tables['Logical_Router'])
lr.name = self.router if self.router else ""
for col, value in self.columns.items():
setattr(lr, col, value)
self.result = lr.uuid
def post_commit(self, txn):
real_uuid = txn.get_insert_uuid(self.result)
if real_uuid:
row = self.api.tables['Logical_Router'].rows[real_uuid]
self.result = ovs_idl.RowView(row)
class LrDelCommand(cmd.BaseCommand):
def __init__(self, api, router, if_exists=False):
super(LrDelCommand, self).__init__(api)
self.router = router
self.if_exists = if_exists
def run_idl(self, txn):
try:
lr = self.api.lookup('Logical_Router', self.router)
lr.delete()
except idlutils.RowNotFound:
if self.if_exists:
return
msg = "Logical Router %s does not exist" % self.router
raise RuntimeError(msg)
class LrListCommand(cmd.BaseCommand):
def run_idl(self, txn):
self.result = [ovs_idl.RowView(r) for
r in self.api.tables['Logical_Router'].rows.values()]
class LrpAddCommand(cmd.BaseCommand):
def __init__(self, api, router, port, mac, networks,
peer=None, may_exist=False, **columns):
self.mac = str(netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded))
self.networks = [str(netaddr.IPNetwork(net)) for net in networks]
self.router = router
self.port = port
self.peer = peer
self.may_exist = may_exist
self.columns = columns
super(LrpAddCommand, self).__init__(api)
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
try:
lrp = self.api.lookup('Logical_Router_Port', self.port)
if self.may_exist:
msg = None
if lrp not in lr.ports:
msg = "Port %s exists, but is not in router %s" % (
self.port, self.router)
elif netaddr.EUI(lrp.mac) != netaddr.EUI(self.mac):
msg = "Port %s exists with different mac" % (self.port)
elif set(self.networks) != set(lrp.networks):
msg = "Port %s exists with different networks" % (
self.port)
elif (not self.peer) != (not lrp.peer) or (
self.peer != lrp.peer):
msg = "Port %s exists with different peer" % (self.port)
if msg:
raise RuntimeError(msg)
self.result = ovs_idl.RowView(lrp)
return
except idlutils.RowNotFound:
pass
lrp = txn.insert(self.api.tables['Logical_Router_Port'])
# This is what ovn-nbctl does, though the lookup is by uuid or name
lrp.name = self.port
lrp.mac = self.mac
lrp.networks = self.networks
if self.peer:
lrp.peer = self.peer
lr.addvalue('ports', lrp)
for col, value in self.columns.items():
setattr(lrp, col, value)
self.result = lrp.uuid
def post_commit(self, txn):
real_uuid = txn.get_insert_uuid(self.result)
if real_uuid:
row = self.api.tables['Logical_Router_Port'].rows[real_uuid]
self.result = ovs_idl.RowView(row)
class LrpDelCommand(PortDelCommand):
def __init__(self, api, port, router=None, if_exists=False):
super(LrpDelCommand, self).__init__(
api, 'Logical_Router_Port', port, 'Logical_Router', router,
if_exists)
class LrpListCommand(cmd.BaseCommand):
def __init__(self, api, router):
super(LrpListCommand, self).__init__(api)
self.router = router
def run_idl(self, txn):
router = self.api.lookup('Logical_Router', self.router)
self.result = [ovs_idl.RowView(r) for r in router.ports]
class LrpSetEnabledCommand(cmd.BaseCommand):
def __init__(self, api, port, is_enabled):
super(LrpSetEnabledCommand, self).__init__(api)
self.port = port
self.is_enabled = is_enabled
def run_idl(self, txn):
lrp = self.api.lookup('Logical_Router_Port', self.port)
lrp.enabled = self.is_enabled
class LrpGetEnabledCommand(cmd.BaseCommand):
def __init__(self, api, port):
super(LrpGetEnabledCommand, self).__init__(api)
self.port = port
def run_idl(self, txn):
lrp = self.api.lookup('Logical_Router_Port', self.port)
# enabled is optional, but if not disabled then enabled
self.result = next(iter(lrp.enabled), True)
class LrRouteAddCommand(cmd.BaseCommand):
def __init__(self, api, router, prefix, nexthop, port=None,
policy='dst-ip', may_exist=False):
prefix = str(netaddr.IPNetwork(prefix))
nexthop = str(netaddr.IPAddress(nexthop))
super(LrRouteAddCommand, self).__init__(api)
self.router = router
self.prefix = prefix
self.nexthop = nexthop
self.port = port
self.policy = policy
self.may_exist = may_exist
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
for route in lr.static_routes:
if self.prefix == route.ip_prefix:
if not self.may_exist:
msg = "Route %s already exists on router %s" % (
self.prefix, self.router)
raise RuntimeError(msg)
route.nexthop = self.nexthop
route.policy = self.policy
if self.port:
route.port = self.port
self.result = ovs_idl.RowView(route)
return
route = txn.insert(self.api.tables['Logical_Router_Static_Route'])
route.ip_prefix = self.prefix
route.nexthop = self.nexthop
route.policy = self.policy
if self.port:
route.port = self.port
lr.addvalue('static_routes', route)
self.result = route.uuid
def post_commit(self, txn):
real_uuid = txn.get_insert_uuid(self.result)
if real_uuid:
table = self.api.tables['Logical_Router_Static_Route']
row = table.rows[real_uuid]
self.result = ovs_idl.RowView(row)
class LrRouteDelCommand(cmd.BaseCommand):
def __init__(self, api, router, prefix=None, if_exists=False):
if prefix is not None:
prefix = str(netaddr.IPNetwork(prefix))
super(LrRouteDelCommand, self).__init__(api)
self.router = router
self.prefix = prefix
self.if_exists = if_exists
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
if not self.prefix:
lr.static_routes = []
return
for route in lr.static_routes:
if self.prefix == route.ip_prefix:
lr.delvalue('static_routes', route)
# There should only be one possible match
return
if not self.if_exists:
msg = "Route for %s in router %s does not exist" % (
self.prefix, self.router)
raise RuntimeError(msg)
class LrRouteListCommand(cmd.BaseCommand):
def __init__(self, api, router):
super(LrRouteListCommand, self).__init__(api)
self.router = router
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
self.result = [ovs_idl.RowView(r) for r in lr.static_routes]
class LrNatAddCommand(cmd.BaseCommand):
def __init__(self, api, router, nat_type, external_ip, logical_ip,
logical_port=None, external_mac=None, may_exist=False):
if nat_type not in const.NAT_TYPES:
raise TypeError("nat_type not in %s" % str(const.NAT_TYPES))
external_ip = str(netaddr.IPAddress(external_ip))
if nat_type == const.NAT_DNAT:
logical_ip = str(netaddr.IPAddress(logical_ip))
else:
net = netaddr.IPNetwork(logical_ip)
logical_ip = str(net.ip if net.prefixlen == 32 else net)
if (logical_port is None) != (external_mac is None):
msg = "logical_port and external_mac must be passed together"
raise TypeError(msg)
if logical_port and nat_type != const.NAT_BOTH:
msg = "logical_port/external_mac only valid for %s" % (
const.NAT_BOTH,)
raise TypeError(msg)
if external_mac:
external_mac = str(
netaddr.EUI(external_mac, dialect=netaddr.mac_unix_expanded))
super(LrNatAddCommand, self).__init__(api)
self.router = router
self.nat_type = nat_type
self.external_ip = external_ip
self.logical_ip = logical_ip
self.logical_port = logical_port or []
self.external_mac = external_mac or []
self.may_exist = may_exist
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
if self.logical_port:
lp = self.api.lookup('Logical_Switch_Port', self.logical_port)
for nat in lr.nat:
if ((self.nat_type, self.external_ip, self.logical_ip) ==
(nat.type, nat.external_ip, nat.logical_ip)):
if self.may_exist:
nat.logical_port = self.logical_port
nat.external_mac = self.external_mac
self.result = ovs_idl.RowView(nat)
return
raise RuntimeError("NAT already exists")
nat = txn.insert(self.api.tables['NAT'])
nat.type = self.nat_type
nat.external_ip = self.external_ip
nat.logical_ip = self.logical_ip
if self.logical_port:
# It seems kind of weird that ovn uses a name string instead of
# a ref to a LSP, especially when ovn-nbctl looks the value up by
# either name or uuid (and discards the result and store the name).
nat.logical_port = lp.name
nat.external_mac = self.external_mac
lr.addvalue('nat', nat)
self.result = nat.uuid
def post_commit(self, txn):
real_uuid = txn.get_insert_uuid(self.result)
if real_uuid:
row = self.api.tables['NAT'].rows[real_uuid]
self.result = ovs_idl.RowView(row)
class LrNatDelCommand(cmd.BaseCommand):
def __init__(self, api, router, nat_type=None, match_ip=None,
if_exists=False):
super(LrNatDelCommand, self).__init__(api)
self.conditions = []
if nat_type:
if nat_type not in const.NAT_TYPES:
raise TypeError("nat_type not in %s" % str(const.NAT_TYPES))
self.conditions += [('type', '=', nat_type)]
if match_ip:
match_ip = str(netaddr.IPAddress(match_ip))
self.col = ('logical_ip' if nat_type == const.NAT_SNAT
else 'external_ip')
self.conditions += [(self.col, '=', match_ip)]
elif match_ip:
raise TypeError("must specify nat_type with match_ip")
self.router = router
self.nat_type = nat_type
self.match_ip = match_ip
self.if_exists = if_exists
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
found = False
for nat in [r for r in lr.nat
if idlutils.row_match(r, self.conditions)]:
found = True
lr.delvalue('nat', nat)
nat.delete()
if self.match_ip:
break
if self.match_ip and not (found or self.if_exists):
raise idlutils.RowNotFound(table='NAT', col=self.col,
match=self.match_ip)
class LrNatListCommand(cmd.BaseCommand):
def __init__(self, api, router):
super(LrNatListCommand, self).__init__(api)
self.router = router
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
self.result = [ovs_idl.RowView(r) for r in lr.nat]
class LbAddCommand(cmd.BaseCommand):
def __init__(self, api, lb, vip, ips, protocol=const.PROTO_TCP,
may_exist=False, **columns):
super(LbAddCommand, self).__init__(api)
self.lb = lb
self.vip = utils.normalize_ip_port(vip)
self.ips = ",".join(utils.normalize_ip(ip) for ip in ips)
self.protocol = protocol
self.may_exist = may_exist
self.columns = columns
def run_idl(self, txn):
try:
lb = self.api.lookup('Load_Balancer', self.lb)
if lb.vips.get(self.vip):
if not self.may_exist:
raise RuntimeError("Load Balancer %s exists" % lb.name)
# Update load balancer vip
lb.setkey('vips', self.vip, self.ips)
lb.protocol = self.protocol
except idlutils.RowNotFound:
# New load balancer
lb = txn.insert(self.api.tables['Load_Balancer'])
lb.name = self.lb
lb.protocol = self.protocol
lb.vips = {self.vip: self.ips}
for col, val in self.columns.items():
setattr(lb, col, val)
self.result = lb.uuid
def post_commit(self, txn):
real_uuid = txn.get_insert_uuid(self.result) or self.result
row = self.api.tables['Load_Balancer'].rows[real_uuid]
self.result = ovs_idl.RowView(row)
class LbDelCommand(cmd.BaseCommand):
def __init__(self, api, lb, vip=None, if_exists=False):
super(LbDelCommand, self).__init__(api)
self.lb = lb
self.vip = utils.normalize_ip_port(vip) if vip else vip
self.if_exists = if_exists
def run_idl(self, txn):
try:
lb = self.api.lookup('Load_Balancer', self.lb)
if self.vip:
if self.vip in lb.vips:
if self.if_exists:
return
lb.delkey('vips', self.vip)
else:
lb.delete()
except idlutils.RowNotFound:
if not self.if_exists:
raise
class LbListCommand(cmd.BaseCommand):
def run_idl(self, txn):
self.result = [ovs_idl.RowView(r)
for r in self.api.tables['Load_Balancer'].rows.values()]
class LrLbAddCommand(cmd.BaseCommand):
def __init__(self, api, router, lb, may_exist=False):
super(LrLbAddCommand, self).__init__(api)
self.router = router
self.lb = lb
self.may_exist = may_exist
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
lb = self.api.lookup('Load_Balancer', self.lb)
if lb in lr.load_balancer:
if self.may_exist:
return
raise RuntimeError("LB %s already exist in router %s" % (
lb.uuid, lr.uuid))
lr.addvalue('load_balancer', lb)
class LrLbDelCommand(cmd.BaseCommand):
def __init__(self, api, router, lb=None, if_exists=False):
super(LrLbDelCommand, self).__init__(api)
self.router = router
self.lb = lb
self.if_exists = if_exists
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
if not self.lb:
lr.load_balancer = []
return
try:
lb = self.api.lookup('Load_Balancer', self.lb)
lr.delvalue('load_balancer', lb)
except idlutils.RowNotFound:
if self.if_exists:
return
raise
class LrLbListCommand(cmd.BaseCommand):
def __init__(self, api, router):
super(LrLbListCommand, self).__init__(api)
self.router = router
def run_idl(self, txn):
lr = self.api.lookup('Logical_Router', self.router)
self.result = [ovs_idl.RowView(r) for r in lr.load_balancer]
class LsLbAddCommand(cmd.BaseCommand):
def __init__(self, api, switch, lb, may_exist=False):
super(LsLbAddCommand, self).__init__(api)
self.switch = switch
self.lb = lb
self.may_exist = may_exist
def run_idl(self, txn):
ls = self.api.lookup('Logical_Switch', self.switch)
lb = self.api.lookup('Load_Balancer', self.lb)
if lb in ls.load_balancer:
if self.may_exist:
return
raise RuntimeError("LB %s alseady exist in switch %s" % (
lb.uuid, ls.uuid))
ls.addvalue('load_balancer', lb)
class LsLbDelCommand(cmd.BaseCommand):
def __init__(self, api, switch, lb=None, if_exists=False):
super(LsLbDelCommand, self).__init__(api)
self.switch = switch
self.lb = lb
self.if_exists = if_exists
def run_idl(self, txn):
ls = self.api.lookup('Logical_Switch', self.switch)
if not self.lb:
ls.load_balancer = []
return
try:
lb = self.api.lookup('Load_Balancer', self.lb)
ls.delvalue('load_balancer', lb)
except idlutils.RowNotFound:
if self.if_exists:
return
raise
class LsLbListCommand(cmd.BaseCommand):
def __init__(self, api, switch):
super(LsLbListCommand, self).__init__(api)
self.switch = switch
def run_idl(self, txn):
ls = self.api.lookup('Logical_Switch', self.switch)
self.result = [ovs_idl.RowView(r) for r in ls.load_balancer]

View File

@ -15,6 +15,7 @@ import logging
from ovsdbapp.backend import ovs_idl
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.backend.ovs_idl import transaction
from ovsdbapp import constants as const
from ovsdbapp import exceptions
from ovsdbapp.schema.ovn_northbound import api
from ovsdbapp.schema.ovn_northbound import commands as cmd
@ -27,6 +28,8 @@ class OvnNbApiIdlImpl(ovs_idl.Backend, api.API):
ovsdb_connection = None
lookup_table = {
'Logical_Switch': idlutils.RowLookup('Logical_Switch', 'name', None),
'Logical_Router': idlutils.RowLookup('Logical_Router', 'name', None),
'Load_Balancer': idlutils.RowLookup('Load_Balancer', 'name', None),
}
def __init__(self, connection):
@ -136,6 +139,85 @@ class OvnNbApiIdlImpl(ovs_idl.Backend, api.API):
def lsp_get_dhcpv4_options(self, port):
return cmd.LspGetDhcpV4OptionsCommand(self, port)
def lr_add(self, router=None, may_exist=False, **columns):
return cmd.LrAddCommand(self, router, may_exist, **columns)
def lr_del(self, router, if_exists=False):
return cmd.LrDelCommand(self, router, if_exists)
def lr_list(self):
return cmd.LrListCommand(self)
def lrp_add(self, router, port, mac, networks, peer=None, may_exist=False,
**columns):
return cmd.LrpAddCommand(self, router, port, mac, networks,
peer, may_exist, **columns)
def lrp_del(self, port, router=None, if_exists=False):
return cmd.LrpDelCommand(self, port, router, if_exists)
def lrp_list(self, router):
return cmd.LrpListCommand(self, router)
def lrp_set_enabled(self, port, is_enabled):
return cmd.LrpSetEnabledCommand(self, port, is_enabled)
def lrp_get_enabled(self, port):
return cmd.LrpGetEnabledCommand(self, port)
def lr_route_add(self, router, prefix, nexthop, port=None,
policy='dst-ip', may_exist=False):
return cmd.LrRouteAddCommand(self, router, prefix, nexthop, port,
policy, may_exist)
def lr_route_del(self, router, prefix=None, if_exists=False):
return cmd.LrRouteDelCommand(self, router, prefix, if_exists)
def lr_route_list(self, router):
return cmd.LrRouteListCommand(self, router)
def lr_nat_add(self, router, nat_type, external_ip, logical_ip,
logical_port=None, external_mac=None, may_exist=False):
return cmd.LrNatAddCommand(
self, router, nat_type, external_ip, logical_ip, logical_port,
external_mac, may_exist)
def lr_nat_del(self, router, nat_type=None, match_ip=None,
if_exists=False):
return cmd.LrNatDelCommand(self, router, nat_type, match_ip, if_exists)
def lr_nat_list(self, router):
return cmd.LrNatListCommand(self, router)
def lb_add(self, lb, vip, ips, protocol=const.PROTO_TCP, may_exist=False,
**columns):
return cmd.LbAddCommand(self, lb, vip, ips, protocol, may_exist,
**columns)
def lb_del(self, lb, vip=None, if_exists=False):
return cmd.LbDelCommand(self, lb, vip, if_exists)
def lb_list(self):
return cmd.LbListCommand(self)
def lr_lb_add(self, router, lb, may_exist=False):
return cmd.LrLbAddCommand(self, router, lb, may_exist)
def lr_lb_del(self, router, lb=None, if_exists=False):
return cmd.LrLbDelCommand(self, router, lb, if_exists)
def lr_lb_list(self, router):
return cmd.LrLbListCommand(self, router)
def ls_lb_add(self, switch, lb, may_exist=False):
return cmd.LsLbAddCommand(self, switch, lb, may_exist)
def ls_lb_del(self, switch, lb=None, if_exists=False):
return cmd.LsLbDelCommand(self, switch, lb, if_exists)
def ls_lb_list(self, switch):
return cmd.LsLbListCommand(self, switch)
def dhcp_options_add(self, cidr, **external_ids):
return cmd.DhcpOptionsAddCommand(self, cidr, **external_ids)

View File

@ -53,3 +53,9 @@ class LogicalRouterFixture(ImplIdlFixture):
api = impl_idl.OvnNbApiIdlImpl
create = 'lr_add'
delete = 'lr_del'
class LoadBalancerFixture(ImplIdlFixture):
api = impl_idl.OvnNbApiIdlImpl
create = 'lb_add'
delete = 'lb_del'

View File

@ -11,11 +11,15 @@
# under the License.
import netaddr
import testscenarios
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp import constants as const
from ovsdbapp.schema.ovn_northbound import impl_idl
from ovsdbapp.tests.functional import base
from ovsdbapp.tests.functional.schema.ovn_northbound import fixtures
from ovsdbapp.tests import utils
from ovsdbapp import utils as ovsdb_utils
class OvnNorthboundTest(base.FunctionalTestCase):
@ -423,3 +427,568 @@ class TestDhcpOptionsOps(OvnNorthboundTest):
dhcpopt.uuid, **options).execute(check_error=True)
cmd = self.api.dhcp_options_get_options(dhcpopt.uuid)
self.assertEqual(options, cmd.execute(check_error=True))
class TestLogicalRouterOps(OvnNorthboundTest):
def _lr_add(self, *args, **kwargs):
lr = self.useFixture(
fixtures.LogicalRouterFixture(*args, **kwargs)).obj
self.assertIn(lr.uuid, self.api.tables['Logical_Router'].rows)
return lr
def test_lr_add(self):
self._lr_add()
def test_lr_add_name(self):
name = utils.get_rand_device_name()
lr = self._lr_add(name)
self.assertEqual(name, lr.name)
def test_lr_add_columns(self):
external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'}
lr = self._lr_add(external_ids=external_ids)
self.assertEqual(external_ids, lr.external_ids)
def test_lr_del(self):
lr = self._lr_add()
self.api.lr_del(lr.uuid).execute(check_error=True)
self.assertNotIn(lr.uuid,
self.api.tables['Logical_Router'].rows.keys())
def test_lr_del_name(self):
lr = self._lr_add(utils.get_rand_device_name())
self.api.lr_del(lr.name).execute(check_error=True)
self.assertNotIn(lr.uuid,
self.api.tables['Logical_Router'].rows.keys())
def test_lr_list(self):
lrs = {self._lr_add() for _ in range(3)}
lr_set = set(self.api.lr_list().execute(check_error=True))
self.assertTrue(lrs.issubset(lr_set), "%s vs %s" % (lrs, lr_set))
def _lr_add_route(self, router=None, prefix=None, nexthop=None, port=None,
**kwargs):
lr = self._lr_add(router or utils.get_rand_device_name(),
may_exist=True)
prefix = prefix or '192.0.2.0/25'
nexthop = nexthop or '192.0.2.254'
sr = self.api.lr_route_add(lr.uuid, prefix, nexthop, port,
**kwargs).execute(check_error=True)
self.assertIn(sr, lr.static_routes)
self.assertEqual(prefix, sr.ip_prefix)
self.assertEqual(nexthop, sr.nexthop)
sr.router = lr
return sr
def test_lr_route_add(self):
self._lr_add_route()
def test_lr_route_add_invalid_prefix(self):
self.assertRaises(netaddr.AddrFormatError, self._lr_add_route,
prefix='192.168.1.1/40')
def test_lr_route_add_invalid_nexthop(self):
self.assertRaises(netaddr.AddrFormatError, self._lr_add_route,
nexthop='256.0.1.3')
def test_lr_route_add_exist(self):
router_name = utils.get_rand_device_name()
self._lr_add_route(router_name)
self.assertRaises(RuntimeError, self._lr_add_route, router=router_name)
def test_lr_route_add_may_exist(self):
router_name = utils.get_rand_device_name()
self._lr_add_route(router_name)
self._lr_add_route(router_name, may_exist=True)
def test_lr_route_del(self):
prefix = "192.0.2.0/25"
route = self._lr_add_route(prefix=prefix)
self.api.lr_route_del(route.router.uuid, prefix).execute(
check_error=True)
self.assertNotIn(route, route.router.static_routes)
def test_lr_route_del_all(self):
router = self._lr_add()
for p in range(3):
self._lr_add_route(router.uuid, prefix="192.0.%s.0/24" % p)
self.api.lr_route_del(router.uuid).execute(check_error=True)
self.assertEqual([], router.static_routes)
def test_lr_route_del_no_router(self):
cmd = self.api.lr_route_del("fake_router", '192.0.2.0/25')
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lr_route_del_no_exist(self):
lr = self._lr_add()
cmd = self.api.lr_route_del(lr.uuid, '192.0.2.0/25')
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lr_route_del_if_exist(self):
lr = self._lr_add()
self.api.lr_route_del(lr.uuid, '192.0.2.0/25', if_exists=True).execute(
check_error=True)
def test_lr_route_list(self):
lr = self._lr_add()
routes = {self._lr_add_route(lr.uuid, prefix="192.0.%s.0/25" % p)
for p in range(3)}
route_set = set(self.api.lr_route_list(lr.uuid).execute(
check_error=True))
self.assertTrue(routes.issubset(route_set))
def _lr_nat_add(self, *args, **kwargs):
lr = kwargs.pop('router', self._lr_add(utils.get_rand_device_name()))
nat = self.api.lr_nat_add(
lr.uuid, *args, **kwargs).execute(
check_error=True)
self.assertIn(nat, lr.nat)
nat.router = lr
return nat
def test_lr_nat_add_dnat(self):
ext, log = ('10.172.4.1', '192.0.2.1')
nat = self._lr_nat_add(const.NAT_DNAT, ext, log)
self.assertEqual(ext, nat.external_ip)
self.assertEqual(log, nat.logical_ip)
def test_lr_nat_add_snat(self):
ext, log = ('10.172.4.1', '192.0.2.0/24')
nat = self._lr_nat_add(const.NAT_SNAT, ext, log)
self.assertEqual(ext, nat.external_ip)
self.assertEqual(log, nat.logical_ip)
def test_lr_nat_add_port(self):
sw = self.useFixture(
fixtures.LogicalSwitchFixture()).obj
lsp = self.api.lsp_add(sw.uuid, utils.get_rand_device_name()).execute(
check_error=True)
lport, mac = (lsp.name, 'de:ad:be:ef:4d:ad')
nat = self._lr_nat_add(const.NAT_BOTH, '10.172.4.1', '192.0.2.1',
lport, mac)
self.assertIn(lport, nat.logical_port) # because optional
self.assertIn(mac, nat.external_mac)
def test_lr_nat_add_port_no_mac(self):
# yes, this and other TypeError tests are technically unit tests
self.assertRaises(TypeError, self.api.lr_nat_add, 'faker',
const.NAT_DNAT, '10.17.4.1', '192.0.2.1', 'fake')
def test_lr_nat_add_port_wrong_type(self):
for nat_type in (const.NAT_DNAT, const.NAT_SNAT):
self.assertRaises(
TypeError, self.api.lr_nat_add, 'faker', nat_type,
'10.17.4.1', '192.0.2.1', 'fake', 'de:ad:be:ef:4d:ad')
def test_lr_nat_add_exists(self):
args = (const.NAT_SNAT, '10.17.4.1', '192.0.2.0/24')
nat1 = self._lr_nat_add(*args)
cmd = self.api.lr_nat_add(nat1.router.uuid, *args)
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lr_nat_add_may_exist(self):
sw = self.useFixture(
fixtures.LogicalSwitchFixture()).obj
lsp = self.api.lsp_add(sw.uuid, utils.get_rand_device_name()).execute(
check_error=True)
args = (const.NAT_BOTH, '10.17.4.1', '192.0.2.1')
nat1 = self._lr_nat_add(*args)
lp, mac = (lsp.name, 'de:ad:be:ef:4d:ad')
nat2 = self.api.lr_nat_add(
nat1.router.uuid, *args, logical_port=lp,
external_mac=mac, may_exist=True).execute(check_error=True)
self.assertEqual(nat1, nat2)
self.assertIn(lp, nat2.logical_port) # because optional
self.assertIn(mac, nat2.external_mac)
def test_lr_nat_add_may_exist_remove_port(self):
sw = self.useFixture(
fixtures.LogicalSwitchFixture()).obj
lsp = self.api.lsp_add(sw.uuid, utils.get_rand_device_name()).execute(
check_error=True)
args = (const.NAT_BOTH, '10.17.4.1', '192.0.2.1')
lp, mac = (lsp.name, 'de:ad:be:ef:4d:ad')
nat1 = self._lr_nat_add(*args, logical_port=lp, external_mac=mac)
nat2 = self.api.lr_nat_add(
nat1.router.uuid, *args, may_exist=True).execute(check_error=True)
self.assertEqual(nat1, nat2)
self.assertEqual([], nat2.logical_port) # because optional
self.assertEqual([], nat2.external_mac)
def _three_nats(self):
lr = self._lr_add(utils.get_rand_device_name())
for n, nat_type in enumerate((const.NAT_DNAT, const.NAT_SNAT,
const.NAT_BOTH)):
nat_kwargs = {'router': lr, 'nat_type': nat_type,
'logical_ip': '10.17.4.%s' % (n + 1),
'external_ip': '192.0.2.%s' % (n + 1)}
self._lr_nat_add(**nat_kwargs)
return lr
def _lr_nat_del(self, *args, **kwargs):
lr = self._three_nats()
self.api.lr_nat_del(lr.name, *args, **kwargs).execute(check_error=True)
return lr
def test_lr_nat_del_all(self):
lr = self._lr_nat_del()
self.assertEqual([], lr.nat)
def test_lr_nat_del_type(self):
lr = self._lr_nat_del(nat_type=const.NAT_SNAT)
types = tuple(nat.type for nat in lr.nat)
self.assertNotIn(const.NAT_SNAT, types)
self.assertEqual(len(types), len(const.NAT_TYPES) - 1)
def test_lr_nat_del_specific_dnat(self):
lr = self._lr_nat_del(nat_type=const.NAT_DNAT, match_ip='192.0.2.1')
self.assertEqual(len(lr.nat), len(const.NAT_TYPES) - 1)
for nat in lr.nat:
self.assertNotEqual('192.0.2.1', nat.external_ip)
self.assertNotEqual(const.NAT_DNAT, nat.type)
def test_lr_nat_del_specific_snat(self):
lr = self._lr_nat_del(nat_type=const.NAT_SNAT, match_ip='10.17.4.2')
self.assertEqual(len(lr.nat), len(const.NAT_TYPES) - 1)
for nat in lr.nat:
self.assertNotEqual('10.17.4.2', nat.external_ip)
self.assertNotEqual(const.NAT_SNAT, nat.type)
def test_lr_nat_del_specific_both(self):
lr = self._lr_nat_del(nat_type=const.NAT_BOTH, match_ip='192.0.2.3')
self.assertEqual(len(lr.nat), len(const.NAT_TYPES) - 1)
for nat in lr.nat:
self.assertNotEqual('192.0.2.3', nat.external_ip)
self.assertNotEqual(const.NAT_BOTH, nat.type)
def test_lr_nat_del_specific_not_found(self):
self.assertRaises(idlutils.RowNotFound, self._lr_nat_del,
nat_type=const.NAT_BOTH, match_ip='10.17.4.2')
def test_lr_nat_del_specific_if_exists(self):
lr = self._lr_nat_del(nat_type=const.NAT_BOTH, match_ip='10.17.4.2',
if_exists=True)
self.assertEqual(len(lr.nat), len(const.NAT_TYPES))
def test_lr_nat_list(self):
lr = self._three_nats()
nats = self.api.lr_nat_list(lr.uuid).execute(check_error=True)
self.assertEqual(lr.nat, nats)
class TestLogicalRouterPortOps(OvnNorthboundTest):
def setUp(self):
super(TestLogicalRouterPortOps, self).setUp()
self.lr = self.useFixture(fixtures.LogicalRouterFixture()).obj
def _lrp_add(self, port, mac='de:ad:be:ef:4d:ad',
networks=None, *args, **kwargs):
if port is None:
port = utils.get_rand_device_name()
if networks is None:
networks = ['192.0.2.0/24']
lrp = self.api.lrp_add(self.lr.uuid, port, mac, networks,
*args, **kwargs).execute(check_error=True)
self.assertIn(lrp, self.lr.ports)
self.assertEqual(mac, lrp.mac)
self.assertEqual(set(networks), set(lrp.networks))
return lrp
def test_lrp_add(self):
self._lrp_add(None, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24'])
def test_lpr_add_peer(self):
lrp = self._lrp_add(None, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24'],
peer='fake_peer')
self.assertIn('fake_peer', lrp.peer)
def test_lpr_add_multiple_networks(self):
networks = ['192.0.2.0/24', '192.2.1.0/24']
self._lrp_add(None, 'de:ad:be:ef:4d:ad', networks)
def test_lrp_add_invalid_mac(self):
self.assertRaises(
netaddr.AddrFormatError,
self.api.lrp_add, "fake", "fake", "000:11:22:33:44:55",
['192.0.2.0/24'])
def test_lrp_add_invalid_network(self):
self.assertRaises(
netaddr.AddrFormatError,
self.api.lrp_add, "fake", "fake", "01:02:03:04:05:06",
['256.2.0.1/24'])
def test_lrp_add_exists(self):
name = utils.get_rand_device_name()
args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24'])
self._lrp_add(*args)
self.assertRaises(RuntimeError, self._lrp_add, *args)
def test_lrp_add_may_exist(self):
name = utils.get_rand_device_name()
args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24'])
self._lrp_add(*args)
self.assertRaises(RuntimeError, self._lrp_add, *args, may_exist=True)
def test_lrp_add_may_exist_different_router(self):
name = utils.get_rand_device_name()
args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24'])
lr2 = self.useFixture(fixtures.LogicalRouterFixture()).obj
self._lrp_add(*args)
cmd = self.api.lrp_add(lr2.uuid, *args, may_exist=True)
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lrp_add_may_exist_different_mac(self):
name = utils.get_rand_device_name()
args = {'port': name, 'mac': 'de:ad:be:ef:4d:ad',
'networks': ['192.0.2.0/24']}
self._lrp_add(**args)
args['mac'] = 'da:d4:de:ad:be:ef'
self.assertRaises(RuntimeError, self._lrp_add, may_exist=True, **args)
def test_lrp_add_may_exist_different_networks(self):
name = utils.get_rand_device_name()
args = (name, 'de:ad:be:ef:4d:ad')
self._lrp_add(*args, networks=['192.0.2.0/24'])
self.assertRaises(RuntimeError, self._lrp_add, *args,
networks=['192.2.1.0/24'], may_exist=True)
def test_lrp_add_may_exist_different_peer(self):
name = utils.get_rand_device_name()
args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24'])
self._lrp_add(*args)
self.assertRaises(RuntimeError, self._lrp_add, *args,
peer='fake', may_exist=True)
def test_lrp_add_columns(self):
options = {'myside': 'yourside'}
external_ids = {'myside': 'yourside'}
lrp = self._lrp_add(None, options=options, external_ids=external_ids)
self.assertEqual(options, lrp.options)
self.assertEqual(external_ids, lrp.external_ids)
def test_lrp_del_uuid(self):
lrp = self._lrp_add(None)
self.api.lrp_del(lrp.uuid).execute(check_error=True)
self.assertNotIn(lrp, self.lr.ports)
def test_lrp_del_name(self):
lrp = self._lrp_add(None)
self.api.lrp_del(lrp.name).execute(check_error=True)
self.assertNotIn(lrp, self.lr.ports)
def test_lrp_del_router(self):
lrp = self._lrp_add(None)
self.api.lrp_del(lrp.uuid, self.lr.uuid).execute(check_error=True)
self.assertNotIn(lrp, self.lr.ports)
def test_lrp_del_router_name(self):
lrp = self._lrp_add(None)
self.api.lrp_del(lrp.uuid,
self.lr.name).execute(check_error=True)
self.assertNotIn(lrp, self.lr.ports)
def test_lrp_del_wrong_router(self):
lrp = self._lrp_add(None)
sw_id = self.useFixture(fixtures.LogicalSwitchFixture()).obj
cmd = self.api.lrp_del(lrp.uuid, sw_id)
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lrp_del_router_no_exist(self):
lrp = self._lrp_add(None)
cmd = self.api.lrp_del(lrp.uuid, utils.get_rand_device_name())
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lrp_del_no_exist(self):
cmd = self.api.lrp_del("fake_port")
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lrp_del_if_exist(self):
self.api.lrp_del("fake_port", if_exists=True).execute(check_error=True)
def test_lrp_list(self):
ports = {self._lrp_add(None) for _ in range(3)}
port_set = set(self.api.lrp_list(self.lr.uuid).execute(
check_error=True))
self.assertTrue(ports.issubset(port_set))
def test_lrp_get_set_enabled(self):
lrp = self._lrp_add(None)
# default is True
self.assertTrue(self.api.lrp_get_enabled(lrp.name).execute(
check_error=True))
self.api.lrp_set_enabled(lrp.name, False).execute(check_error=True)
self.assertFalse(self.api.lrp_get_enabled(lrp.name).execute(
check_error=True))
self.api.lrp_set_enabled(lrp.name, True).execute(check_error=True)
self.assertTrue(self.api.lrp_get_enabled(lrp.name).execute(
check_error=True))
class TestLoadBalancerOps(OvnNorthboundTest):
def _lb_add(self, lb, vip, ips, protocol=const.PROTO_TCP, may_exist=False,
**columns):
lbal = self.useFixture(fixtures.LoadBalancerFixture(
lb, vip, ips, protocol, may_exist, **columns)).obj
self.assertEqual(lb, lbal.name)
norm_vip = ovsdb_utils.normalize_ip_port(vip)
self.assertIn(norm_vip, lbal.vips)
self.assertEqual(",".join(ovsdb_utils.normalize_ip(ip) for ip in ips),
lbal.vips[norm_vip])
self.assertIn(protocol, lbal.protocol) # because optional
return lbal
def test_lb_add(self):
vip = '192.0.2.1'
ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3']
self._lb_add(utils.get_rand_device_name(), vip, ips)
def test_lb_add_port(self):
vip = '192.0.2.1:80'
ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3']
self._lb_add(utils.get_rand_device_name(), vip, ips)
def test_lb_add_protocol(self):
vip = '192.0.2.1'
ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3']
self._lb_add(utils.get_rand_device_name(), vip, ips, const.PROTO_UDP)
def test_lb_add_new_vip(self):
name = utils.get_rand_device_name()
lb1 = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2'])
lb2 = self._lb_add(name, '192.0.2.2', ['10.1.0.1', '10.1.0.2'])
self.assertEqual(lb1, lb2)
self.assertEqual(2, len(lb1.vips))
def test_lb_add_exists(self):
name = utils.get_rand_device_name()
vip = '192.0.2.1'
ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3']
self._lb_add(name, vip, ips)
cmd = self.api.lb_add(name, vip, ips)
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_lb_add_may_exist(self):
name = utils.get_rand_device_name()
vip = '192.0.2.1'
ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3']
lb1 = self._lb_add(name, vip, ips)
ips += ['10.0.0.4']
lb2 = self.api.lb_add(name, vip, ips, may_exist=True).execute(
check_error=True)
self.assertEqual(lb1, lb2)
self.assertEqual(",".join(ips), lb1.vips[vip])
def test_lb_add_columns(self):
ext_ids = {'one': 'two'}
name = utils.get_rand_device_name()
lb = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2'],
external_ids=ext_ids)
self.assertEqual(ext_ids, lb.external_ids)
def test_lb_del(self):
name = utils.get_rand_device_name()
lb = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2']).uuid
self.api.lb_del(lb).execute(check_error=True)
self.assertNotIn(lb, self.api.tables['Load_Balancer'].rows)
def test_lb_del_vip(self):
name = utils.get_rand_device_name()
lb1 = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2'])
lb2 = self._lb_add(name, '192.0.2.2', ['10.1.0.1', '10.1.0.2'])
self.assertEqual(lb1, lb2)
self.api.lb_del(lb1.name, '192.0.2.1').execute(check_error=True)
self.assertNotIn('192.0.2.1', lb1.vips)
self.assertIn('192.0.2.2', lb1.vips)
def test_lb_del_no_exist(self):
cmd = self.api.lb_del(utils.get_rand_device_name())
self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True)
def test_lb_del_if_exists(self):
self.api.lb_del(utils.get_rand_device_name(), if_exists=True).execute(
check_error=True)
def test_lb_list(self):
lbs = {self._lb_add(utils.get_rand_device_name(), '192.0.2.1',
['10.0.0.1', '10.0.0.2']) for _ in range(3)}
lbset = self.api.lb_list().execute(check_error=True)
self.assertTrue(lbs.issubset(lbset))
class TestObLbOps(testscenarios.TestWithScenarios, OvnNorthboundTest):
scenarios = [
('LrLbOps', dict(fixture=fixtures.LogicalRouterFixture,
_add_fn='lr_lb_add', _del_fn='lr_lb_del',
_list_fn='lr_lb_list')),
('LsLbOps', dict(fixture=fixtures.LogicalSwitchFixture,
_add_fn='ls_lb_add', _del_fn='ls_lb_del',
_list_fn='ls_lb_list')),
]
def setUp(self):
super(TestObLbOps, self).setUp()
self.add_fn = getattr(self.api, self._add_fn)
self.del_fn = getattr(self.api, self._del_fn)
self.list_fn = getattr(self.api, self._list_fn)
# They must be in this order because the load balancer
# can't be deleted when there is a reference in the router
self.lb = self.useFixture(fixtures.LoadBalancerFixture(
utils.get_rand_device_name(), '192.0.2.1',
['10.0.0.1', '10.0.0.2'])).obj
self.lb2 = self.useFixture(fixtures.LoadBalancerFixture(
utils.get_rand_device_name(), '192.0.2.2',
['10.1.0.1', '10.1.0.2'])).obj
self.lr = self.useFixture(self.fixture(
utils.get_rand_device_name())).obj
def test_ob_lb_add(self):
self.add_fn(self.lr.name, self.lb.name).execute(
check_error=True)
self.assertIn(self.lb, self.lr.load_balancer)
def test_ob_lb_add_exists(self):
cmd = self.add_fn(self.lr.name, self.lb.name)
cmd.execute(check_error=True)
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_ob_lb_add_may_exist(self):
cmd = self.add_fn(self.lr.name, self.lb.name, may_exist=True)
lb1 = cmd.execute(check_error=True)
lb2 = cmd.execute(check_error=True)
self.assertEqual(lb1, lb2)
def test_ob_lb_del(self):
self.add_fn(self.lr.name, self.lb.name).execute(
check_error=True)
self.assertIn(self.lb, self.lr.load_balancer)
self.del_fn(self.lr.name).execute(check_error=True)
self.assertEqual(0, len(self.lr.load_balancer))
def test_ob_lb_del_lb(self):
self.add_fn(self.lr.name, self.lb.name).execute(
check_error=True)
self.add_fn(self.lr.name, self.lb2.name).execute(
check_error=True)
self.del_fn(self.lr.name, self.lb2.name).execute(
check_error=True)
self.assertNotIn(self.lb2, self.lr.load_balancer)
self.assertIn(self.lb, self.lr.load_balancer)
def test_ob_lb_del_no_exist(self):
cmd = self.del_fn(self.lr.name, 'fake')
self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True)
def test_ob_lb_del_if_exists(self):
self.del_fn(self.lr.name, 'fake', if_exists=True).execute(
check_error=True)
def test_ob_lb_list(self):
self.add_fn(self.lr.name, self.lb.name).execute(
check_error=True)
self.add_fn(self.lr.name, self.lb2.name).execute(
check_error=True)
rows = self.list_fn(self.lr.name).execute(check_error=True)
self.assertIn(self.lb, rows)
self.assertIn(self.lb2, rows)

View File

@ -0,0 +1,53 @@
# 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.
import netaddr
from ovsdbapp.tests import base
from ovsdbapp import utils
class TestUtils(base.TestCase):
def test_normalize_ip(self):
good = [
('4.4.4.4', '4.4.4.4'),
('10.0.0.0', '10.0.0.0'),
('123', '0.0.0.123'),
('2001:0db8:85a3:0000:0000:8a2e:0370:7334',
'2001:db8:85a3::8a2e:370:7334')
]
bad = ('256.1.3.2', 'bad', '192.168.1.1:80')
for before, after in good:
norm = utils.normalize_ip(before)
self.assertEqual(after, norm,
"%s does not match %s" % (after, norm))
for val in bad:
self.assertRaises(netaddr.AddrFormatError, utils.normalize_ip, val)
def test_normalize_ip_port(self):
good = [
('4.4.4.4:53', '4.4.4.4:53'),
('10.0.0.0:7', '10.0.0.0:7'),
('123:12', '0.0.0.123:12'),
('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80',
'[2001:db8:85a3::8a2e:370:7334]:80')
]
bad = ('1.2.3.4:0', '1.2.3.4:99000',
'2001:0db8:85a3:0000:0000:8a2e:0370:7334:80')
for before, after in good:
norm = utils.normalize_ip_port(before)
self.assertEqual(after, norm,
"%s does not match %s" % (after, norm))
for val in bad:
self.assertRaises(netaddr.AddrFormatError,
utils.normalize_ip_port, val)

43
ovsdbapp/utils.py Normal file
View File

@ -0,0 +1,43 @@
# 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.
import netaddr
# NOTE(twilson) Clearly these are silly, but they are good enough for now
# I'm happy for someone to replace them with better parsing
def normalize_ip(ip):
return str(netaddr.IPAddress(ip))
def normalize_ip_port(ipport):
try:
return normalize_ip(ipport)
except netaddr.AddrFormatError:
# maybe we have a port
if ipport[0] == '[':
# Should be an IPv6 w/ port
try:
ip, port = ipport[1:].split(']:')
except ValueError:
raise netaddr.AddrFormatError("Invalid Port")
ip = "[%s]" % normalize_ip(ip)
else:
try:
ip, port = ipport.split(':')
except ValueError:
raise netaddr.AddrFormatError("Invalid Port")
ip = normalize_ip(ip)
if int(port) <= 0 or int(port) > 65535:
raise netaddr.AddrFormatError("Invalid port")
return "%s:%s" % (ip, port)