
There are times when it is desirable to set the MTU of an "interface" to 1500 -- when the external network and the storage network are different VLANs on the same physical interface, for example. Change-Id: Ic5ea6ad05118fd57c26c898a460c19969de4109d Closes-Bug: #1539821
524 lines
20 KiB
Python
524 lines
20 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2014 Red Hat, Inc.
|
|
#
|
|
# 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 logging
|
|
import netaddr
|
|
from oslo_utils import strutils
|
|
|
|
from os_net_config import utils
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_NUMBERED_NICS = None
|
|
|
|
|
|
class InvalidConfigException(ValueError):
|
|
pass
|
|
|
|
|
|
def object_from_json(json):
|
|
obj_type = json.get("type")
|
|
if obj_type == "interface":
|
|
return Interface.from_json(json)
|
|
elif obj_type == "vlan":
|
|
return Vlan.from_json(json)
|
|
elif obj_type == "ovs_bridge":
|
|
return OvsBridge.from_json(json)
|
|
elif obj_type == "ovs_bond":
|
|
return OvsBond.from_json(json)
|
|
elif obj_type == "linux_bond":
|
|
return LinuxBond.from_json(json)
|
|
elif obj_type == "linux_bridge":
|
|
return LinuxBridge.from_json(json)
|
|
|
|
|
|
def _get_required_field(json, name, object_name):
|
|
field = json.get(name)
|
|
if not field:
|
|
msg = '%s JSON objects require \'%s\' to be configured.' \
|
|
% (object_name, name)
|
|
raise InvalidConfigException(msg)
|
|
return field
|
|
|
|
|
|
def _numbered_nics(nic_mapping=None):
|
|
mapping = nic_mapping or {}
|
|
global _NUMBERED_NICS
|
|
if _NUMBERED_NICS:
|
|
return _NUMBERED_NICS
|
|
_NUMBERED_NICS = {}
|
|
count = 0
|
|
active_nics = utils.ordered_active_nics()
|
|
for nic in active_nics:
|
|
count += 1
|
|
nic_alias = "nic%i" % count
|
|
nic_mapped = mapping.get(nic_alias, nic)
|
|
|
|
# The mapping is either invalid, or specifies a mac
|
|
if nic_mapped not in active_nics:
|
|
for active in active_nics:
|
|
try:
|
|
active_mac = utils.interface_mac(active)
|
|
except IOError:
|
|
continue
|
|
if nic_mapped == active_mac:
|
|
logger.debug("%s matches device %s" % (nic_mapped, active))
|
|
nic_mapped = active
|
|
break
|
|
else:
|
|
# The mapping can't specify a non-active or non-existent nic
|
|
logger.warning('interface %s is not in an active nic (%s)'
|
|
% (nic_mapped, ', '.join(active_nics)))
|
|
continue
|
|
|
|
# Duplicate mappings are not allowed
|
|
if nic_mapped in _NUMBERED_NICS.values():
|
|
msg = ('interface %s already mapped, '
|
|
'check mapping file for duplicates'
|
|
% nic_mapped)
|
|
raise InvalidConfigException(msg)
|
|
|
|
_NUMBERED_NICS[nic_alias] = nic_mapped
|
|
logger.info("%s mapped to: %s" % (nic_alias, nic_mapped))
|
|
return _NUMBERED_NICS
|
|
|
|
|
|
class Route(object):
|
|
"""Base class for network routes."""
|
|
|
|
def __init__(self, next_hop, ip_netmask="", default=False):
|
|
self.next_hop = next_hop
|
|
self.ip_netmask = ip_netmask
|
|
self.default = default
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
next_hop = _get_required_field(json, 'next_hop', 'Route')
|
|
ip_netmask = json.get('ip_netmask', "")
|
|
default = strutils.bool_from_string(str(json.get('default', False)))
|
|
return Route(next_hop, ip_netmask, default)
|
|
|
|
|
|
class Address(object):
|
|
"""Base class for network addresses."""
|
|
|
|
def __init__(self, ip_netmask):
|
|
self.ip_netmask = ip_netmask
|
|
ip_nw = netaddr.IPNetwork(self.ip_netmask)
|
|
self.ip = str(ip_nw.ip)
|
|
self.netmask = str(ip_nw.netmask)
|
|
self.prefixlen = ip_nw.prefixlen
|
|
self.version = ip_nw.version
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
ip_netmask = _get_required_field(json, 'ip_netmask', 'Address')
|
|
return Address(ip_netmask)
|
|
|
|
|
|
class _BaseOpts(object):
|
|
"""Base abstraction for logical port options."""
|
|
|
|
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
|
|
routes=None, mtu=None, primary=False, nic_mapping=None,
|
|
persist_mapping=False, defroute=True, dhclient_args=None,
|
|
dns_servers=None):
|
|
addresses = addresses or []
|
|
routes = routes or []
|
|
dns_servers = dns_servers or []
|
|
numbered_nic_names = _numbered_nics(nic_mapping)
|
|
self.hwaddr = None
|
|
self.hwname = None
|
|
self.renamed = False
|
|
if name in numbered_nic_names:
|
|
if persist_mapping:
|
|
self.name = name
|
|
self.hwname = numbered_nic_names[name]
|
|
self.hwaddr = utils.interface_mac(self.hwname)
|
|
self.renamed = True
|
|
else:
|
|
self.name = numbered_nic_names[name]
|
|
else:
|
|
self.name = name
|
|
|
|
self.mtu = mtu
|
|
self.use_dhcp = use_dhcp
|
|
self.use_dhcpv6 = use_dhcpv6
|
|
self.addresses = addresses
|
|
self.routes = routes
|
|
self.primary = primary
|
|
self.defroute = defroute
|
|
self.dhclient_args = dhclient_args
|
|
self.dns_servers = dns_servers
|
|
self.bridge_name = None # internal
|
|
self.linux_bridge_name = None # internal
|
|
self.ovs_port = False # internal
|
|
self.primary_interface_name = None # internal
|
|
|
|
def v4_addresses(self):
|
|
v4_addresses = []
|
|
for addr in self.addresses:
|
|
if addr.version == 4:
|
|
v4_addresses.append(addr)
|
|
|
|
return v4_addresses
|
|
|
|
def v6_addresses(self):
|
|
v6_addresses = []
|
|
for addr in self.addresses:
|
|
if addr.version == 6:
|
|
v6_addresses.append(addr)
|
|
|
|
return v6_addresses
|
|
|
|
@staticmethod
|
|
def base_opts_from_json(json, include_primary=True):
|
|
use_dhcp = strutils.bool_from_string(str(json.get('use_dhcp', False)))
|
|
use_dhcpv6 = strutils.bool_from_string(str(json.get('use_dhcpv6',
|
|
False)))
|
|
defroute = strutils.bool_from_string(str(json.get('defroute',
|
|
True)))
|
|
mtu = json.get('mtu', None)
|
|
dhclient_args = json.get('dhclient_args')
|
|
dns_servers = json.get('dns_servers')
|
|
primary = strutils.bool_from_string(str(json.get('primary', False)))
|
|
addresses = []
|
|
routes = []
|
|
|
|
# addresses
|
|
addresses_json = json.get('addresses')
|
|
if addresses_json:
|
|
if isinstance(addresses_json, list):
|
|
for address in addresses_json:
|
|
addresses.append(Address.from_json(address))
|
|
else:
|
|
msg = 'Addresses must be a list.'
|
|
raise InvalidConfigException(msg)
|
|
|
|
# routes
|
|
routes_json = json.get('routes')
|
|
if routes_json:
|
|
if isinstance(routes_json, list):
|
|
for route in routes_json:
|
|
routes.append(Route.from_json(route))
|
|
else:
|
|
msg = 'Routes must be a list.'
|
|
raise InvalidConfigException(msg)
|
|
|
|
nic_mapping = json.get('nic_mapping')
|
|
persist_mapping = json.get('persist_mapping')
|
|
|
|
if include_primary:
|
|
return (use_dhcp, use_dhcpv6, addresses, routes, mtu, primary,
|
|
nic_mapping, persist_mapping, defroute, dhclient_args,
|
|
dns_servers)
|
|
else:
|
|
return (use_dhcp, use_dhcpv6, addresses, routes, mtu,
|
|
nic_mapping, persist_mapping, defroute, dhclient_args,
|
|
dns_servers)
|
|
|
|
|
|
class Interface(_BaseOpts):
|
|
"""Base class for network interfaces."""
|
|
|
|
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
|
|
routes=None, mtu=None, primary=False, nic_mapping=None,
|
|
persist_mapping=False, defroute=True, dhclient_args=None,
|
|
dns_servers=None):
|
|
addresses = addresses or []
|
|
routes = routes or []
|
|
dns_servers = dns_servers or []
|
|
super(Interface, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
|
|
routes, mtu, primary, nic_mapping,
|
|
persist_mapping, defroute,
|
|
dhclient_args, dns_servers)
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
name = _get_required_field(json, 'name', 'Interface')
|
|
opts = _BaseOpts.base_opts_from_json(json)
|
|
return Interface(name, *opts)
|
|
|
|
|
|
class Vlan(_BaseOpts):
|
|
"""Base class for VLANs.
|
|
|
|
NOTE: the name parameter must be formated w/ vlan<num> where <num>
|
|
matches the vlan ID being used. Example: vlan5
|
|
"""
|
|
|
|
def __init__(self, device, vlan_id, use_dhcp=False, use_dhcpv6=False,
|
|
addresses=None, routes=None, mtu=None, primary=False,
|
|
nic_mapping=None, persist_mapping=False, defroute=True,
|
|
dhclient_args=None, dns_servers=None):
|
|
addresses = addresses or []
|
|
routes = routes or []
|
|
dns_servers = dns_servers or []
|
|
name = 'vlan%i' % vlan_id
|
|
super(Vlan, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
|
|
routes, mtu, primary, nic_mapping,
|
|
persist_mapping, defroute, dhclient_args,
|
|
dns_servers)
|
|
self.vlan_id = int(vlan_id)
|
|
|
|
numbered_nic_names = _numbered_nics(nic_mapping)
|
|
if device in numbered_nic_names:
|
|
self.device = numbered_nic_names[device]
|
|
else:
|
|
self.device = device
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
# A vlan on an OVS bridge won't require a device (OVS Int Port)
|
|
device = json.get('device')
|
|
vlan_id = _get_required_field(json, 'vlan_id', 'Vlan')
|
|
opts = _BaseOpts.base_opts_from_json(json)
|
|
return Vlan(device, vlan_id, *opts)
|
|
|
|
|
|
class OvsBridge(_BaseOpts):
|
|
"""Base class for OVS bridges."""
|
|
|
|
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
|
|
routes=None, mtu=None, members=None, ovs_options=None,
|
|
ovs_extra=None, nic_mapping=None, persist_mapping=False,
|
|
defroute=True, dhclient_args=None, dns_servers=None):
|
|
addresses = addresses or []
|
|
routes = routes or []
|
|
members = members or []
|
|
ovs_extra = ovs_extra or []
|
|
dns_servers = dns_servers or []
|
|
super(OvsBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
|
|
routes, mtu, False, nic_mapping,
|
|
persist_mapping, defroute,
|
|
dhclient_args, dns_servers)
|
|
self.members = members
|
|
self.ovs_options = ovs_options
|
|
self.ovs_extra = ovs_extra
|
|
for member in self.members:
|
|
member.bridge_name = name
|
|
member.ovs_port = True
|
|
if member.primary:
|
|
if self.primary_interface_name:
|
|
msg = 'Only one primary interface allowed per bridge.'
|
|
raise InvalidConfigException(msg)
|
|
if member.primary_interface_name:
|
|
self.primary_interface_name = member.primary_interface_name
|
|
else:
|
|
self.primary_interface_name = member.name
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
name = _get_required_field(json, 'name', 'OvsBridge')
|
|
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
|
|
persist_mapping, defroute,
|
|
dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json(
|
|
json, include_primary=False)
|
|
ovs_options = json.get('ovs_options')
|
|
ovs_extra = json.get('ovs_extra', [])
|
|
members = []
|
|
|
|
# members
|
|
members_json = json.get('members')
|
|
if members_json:
|
|
if isinstance(members_json, list):
|
|
for member in members_json:
|
|
members.append(object_from_json(member))
|
|
else:
|
|
msg = 'Members must be a list.'
|
|
raise InvalidConfigException(msg)
|
|
|
|
return OvsBridge(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
|
|
addresses=addresses, routes=routes, mtu=mtu,
|
|
members=members, ovs_options=ovs_options,
|
|
ovs_extra=ovs_extra, nic_mapping=nic_mapping,
|
|
persist_mapping=persist_mapping, defroute=defroute,
|
|
dhclient_args=dhclient_args, dns_servers=dns_servers)
|
|
|
|
|
|
class LinuxBridge(_BaseOpts):
|
|
"""Base class for Linux bridges."""
|
|
|
|
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
|
|
routes=None, mtu=None, members=None, nic_mapping=None,
|
|
persist_mapping=False, defroute=True, dhclient_args=None,
|
|
dns_servers=None):
|
|
addresses = addresses or []
|
|
routes = routes or []
|
|
members = members or []
|
|
dns_servers = dns_servers or []
|
|
super(LinuxBridge, self).__init__(name, use_dhcp, use_dhcpv6,
|
|
addresses, routes, mtu, False,
|
|
nic_mapping, persist_mapping,
|
|
defroute, dhclient_args, dns_servers)
|
|
self.members = members
|
|
for member in self.members:
|
|
member.linux_bridge_name = name
|
|
member.ovs_port = False
|
|
if member.primary:
|
|
if self.primary_interface_name:
|
|
msg = 'Only one primary interface allowed per bridge.'
|
|
raise InvalidConfigException(msg)
|
|
if member.primary_interface_name:
|
|
self.primary_interface_name = member.primary_interface_name
|
|
else:
|
|
self.primary_interface_name = member.name
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
name = _get_required_field(json, 'name', 'LinuxBridge')
|
|
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
|
|
persist_mapping, defroute, dhclient_args,
|
|
dns_servers) = _BaseOpts.base_opts_from_json(
|
|
json, include_primary=False)
|
|
members = []
|
|
|
|
# members
|
|
members_json = json.get('members')
|
|
if members_json:
|
|
if isinstance(members_json, list):
|
|
for member in members_json:
|
|
members.append(object_from_json(member))
|
|
else:
|
|
msg = 'Members must be a list.'
|
|
raise InvalidConfigException(msg)
|
|
|
|
return LinuxBridge(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
|
|
addresses=addresses, routes=routes, mtu=mtu,
|
|
members=members, nic_mapping=nic_mapping,
|
|
persist_mapping=persist_mapping, defroute=defroute,
|
|
dhclient_args=dhclient_args,
|
|
dns_servers=dns_servers)
|
|
|
|
|
|
class LinuxBond(_BaseOpts):
|
|
"""Base class for Linux bonds."""
|
|
|
|
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
|
|
routes=None, mtu=None, primary=False, members=None,
|
|
bonding_options=None, nic_mapping=None, persist_mapping=False,
|
|
defroute=True, dhclient_args=None, dns_servers=None):
|
|
addresses = addresses or []
|
|
routes = routes or []
|
|
members = members or []
|
|
dns_servers = dns_servers or []
|
|
super(LinuxBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
|
|
routes, mtu, primary, nic_mapping,
|
|
persist_mapping, defroute,
|
|
dhclient_args, dns_servers)
|
|
self.members = members
|
|
self.bonding_options = bonding_options
|
|
for member in self.members:
|
|
if member.primary:
|
|
if self.primary_interface_name:
|
|
msg = 'Only one primary interface allowed per bond.'
|
|
raise InvalidConfigException(msg)
|
|
if member.primary_interface_name:
|
|
self.primary_interface_name = member.primary_interface_name
|
|
else:
|
|
self.primary_interface_name = member.name
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
name = _get_required_field(json, 'name', 'LinuxBond')
|
|
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
|
|
persist_mapping, defroute, dhclient_args,
|
|
dns_servers) = _BaseOpts.base_opts_from_json(
|
|
json, include_primary=False)
|
|
bonding_options = json.get('bonding_options')
|
|
members = []
|
|
|
|
# members
|
|
members_json = json.get('members')
|
|
if members_json:
|
|
if isinstance(members_json, list):
|
|
for member in members_json:
|
|
members.append(object_from_json(member))
|
|
else:
|
|
msg = 'Members must be a list.'
|
|
raise InvalidConfigException(msg)
|
|
|
|
return LinuxBond(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
|
|
addresses=addresses, routes=routes, mtu=mtu,
|
|
members=members, bonding_options=bonding_options,
|
|
nic_mapping=nic_mapping,
|
|
persist_mapping=persist_mapping, defroute=defroute,
|
|
dhclient_args=dhclient_args, dns_servers=dns_servers)
|
|
|
|
|
|
class OvsBond(_BaseOpts):
|
|
"""Base class for OVS bonds."""
|
|
|
|
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
|
|
routes=None, mtu=None, primary=False, members=None,
|
|
ovs_options=None, ovs_extra=None, nic_mapping=None,
|
|
persist_mapping=False, defroute=True, dhclient_args=None,
|
|
dns_servers=None):
|
|
addresses = addresses or []
|
|
routes = routes or []
|
|
members = members or []
|
|
ovs_extra = ovs_extra or []
|
|
dns_servers = dns_servers or []
|
|
super(OvsBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
|
|
routes, mtu, primary, nic_mapping,
|
|
persist_mapping, defroute, dhclient_args,
|
|
dns_servers)
|
|
self.members = members
|
|
self.ovs_options = ovs_options
|
|
self.ovs_extra = ovs_extra
|
|
for member in self.members:
|
|
if member.primary:
|
|
if self.primary_interface_name:
|
|
msg = 'Only one primary interface allowed per bond.'
|
|
raise InvalidConfigException(msg)
|
|
if member.primary_interface_name:
|
|
self.primary_interface_name = member.primary_interface_name
|
|
else:
|
|
self.primary_interface_name = member.name
|
|
if not self.primary_interface_name:
|
|
bond_members = list(self.members)
|
|
bond_members.sort(key=lambda x: x.name)
|
|
self.primary_interface_name = bond_members[0].name
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
name = _get_required_field(json, 'name', 'OvsBond')
|
|
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
|
|
persist_mapping, defroute, dhclient_args,
|
|
dns_servers) = _BaseOpts.base_opts_from_json(
|
|
json, include_primary=False)
|
|
ovs_options = json.get('ovs_options')
|
|
ovs_extra = json.get('ovs_extra', [])
|
|
members = []
|
|
|
|
# members
|
|
members_json = json.get('members')
|
|
if members_json:
|
|
if isinstance(members_json, list):
|
|
for member in members_json:
|
|
members.append(object_from_json(member))
|
|
else:
|
|
msg = 'Members must be a list.'
|
|
raise InvalidConfigException(msg)
|
|
|
|
return OvsBond(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
|
|
addresses=addresses, routes=routes, mtu=mtu,
|
|
members=members, ovs_options=ovs_options,
|
|
ovs_extra=ovs_extra, nic_mapping=nic_mapping,
|
|
persist_mapping=persist_mapping, defroute=defroute,
|
|
dhclient_args=dhclient_args, dns_servers=dns_servers)
|