Initial ifcfg implementation for interfaces/routes
Ifcfg formatted persistence for interfaces and routes.
This commit is contained in:
@@ -17,3 +17,17 @@ import pbr.version
|
|||||||
|
|
||||||
__version__ = pbr.version.VersionInfo(
|
__version__ = pbr.version.VersionInfo(
|
||||||
'os_net_config').version_string()
|
'os_net_config').version_string()
|
||||||
|
|
||||||
|
|
||||||
|
class NotImplemented(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkConfig(object):
|
||||||
|
"""Configure network interfaces using the ifcfg format."""
|
||||||
|
|
||||||
|
def addInterface(self, interface):
|
||||||
|
raise NotImplemented("addInterface is not implemented.")
|
||||||
|
|
||||||
|
def addRoutes(self, interface_name, routes=[]):
|
||||||
|
raise NotImplemented("addRoutes is not implemented.")
|
||||||
|
|||||||
93
os_net_config/impl_ifcfg.py
Normal file
93
os_net_config/impl_ifcfg.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
def ifcfg_config_path(name):
|
||||||
|
return "/etc/sysconfig/network-scripts/ifcfg-%s" % name
|
||||||
|
|
||||||
|
|
||||||
|
def route_config_path(name):
|
||||||
|
return "/etc/sysconfig/network-scripts/route-%s" % name
|
||||||
|
|
||||||
|
|
||||||
|
def writeConfig(filename, data):
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
f.write(str(data))
|
||||||
|
|
||||||
|
|
||||||
|
def get_config(filename):
|
||||||
|
with open(filename, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def diff(filename, data):
|
||||||
|
return get_config(filename) == data
|
||||||
|
|
||||||
|
|
||||||
|
class IfcfgNetwork(object):
|
||||||
|
"""Configure network interfaces using the ifcfg format."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.interfaces = {}
|
||||||
|
self.routes = {}
|
||||||
|
|
||||||
|
def addInterface(self, interface):
|
||||||
|
data = "DEVICE=%s\n" % interface.name
|
||||||
|
data += "ONBOOT=yes\n"
|
||||||
|
data += "HOTPLUG=no\n"
|
||||||
|
if interface.type == 'ovs':
|
||||||
|
data += "DEVICETYPE=ovs\n"
|
||||||
|
if interface.bridge:
|
||||||
|
data += "TYPE=OVSPort\n"
|
||||||
|
data += "OVS_BRIDGE=%s\n" % interface.bridge
|
||||||
|
data += "BOOTPROTO=none\n"
|
||||||
|
if interface.mtu != 1500:
|
||||||
|
data += "MTU=%i\n" % interface.mtu
|
||||||
|
if interface.use_dhcp:
|
||||||
|
data += "BOOTPROTO=dhcp\n"
|
||||||
|
if interface.use_dhcpv6 or interface.v6_addresses():
|
||||||
|
data += "IPV6INIT=yes\n"
|
||||||
|
if interface.mtu != 1500:
|
||||||
|
data += "IPV6_MTU=%i\n" % interface.mtu
|
||||||
|
if interface.use_dhcpv6:
|
||||||
|
data += "DHCPV6C=yes\n"
|
||||||
|
elif interface.addresses:
|
||||||
|
#TODO(dprince): support multiple addresses for each type
|
||||||
|
v4_addresses = interface.v4_addresses()
|
||||||
|
if v4_addresses:
|
||||||
|
first_v4 = v4_addresses[0]
|
||||||
|
data += "BOOTPROTO=static\n"
|
||||||
|
data += "IPADDR=%s\n" % first_v4.ip
|
||||||
|
data += "NETMASK=%s\n" % first_v4.netmask
|
||||||
|
|
||||||
|
v6_addresses = interface.v6_addresses()
|
||||||
|
if v6_addresses:
|
||||||
|
first_v6 = v6_addresses[0]
|
||||||
|
data += "IPV6_AUTOCONF=no\n"
|
||||||
|
data += "IPV6ADDR=%s\n" % first_v6.ip
|
||||||
|
|
||||||
|
self.interfaces[interface.name] = data
|
||||||
|
|
||||||
|
def addRoutes(self, interface_name, routes=[]):
|
||||||
|
data = ""
|
||||||
|
first_line = ""
|
||||||
|
for route in routes:
|
||||||
|
if route.default:
|
||||||
|
first_line = "default %s dev %s\n" % (route.next_hop,
|
||||||
|
interface_name)
|
||||||
|
else:
|
||||||
|
data += "%s via %s dev %s" % (route.ip_netmask,
|
||||||
|
route.next_hop,
|
||||||
|
interface_name)
|
||||||
|
self.routes[interface_name] == first_line + data
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
# a copy of the License at
|
# a copy of the License at
|
||||||
@@ -20,9 +22,10 @@ class NetworkObjectException(Exception):
|
|||||||
class Route(object):
|
class Route(object):
|
||||||
"""Base class for network routes."""
|
"""Base class for network routes."""
|
||||||
|
|
||||||
def __init__(self, netmask_cidr, gateway):
|
def __init__(self, next_hop, ip_netmask="", default=False):
|
||||||
self.netmask_cidr = netmask_cidr
|
self.next_hop = next_hop
|
||||||
self.gateway = gateway
|
self.ip_netmask = ip_netmask
|
||||||
|
self.default = default
|
||||||
|
|
||||||
|
|
||||||
class Address(object):
|
class Address(object):
|
||||||
@@ -41,10 +44,11 @@ class Interface(object):
|
|||||||
"""Base class for network interfaces."""
|
"""Base class for network interfaces."""
|
||||||
|
|
||||||
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
|
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
|
||||||
mtu=1500):
|
routes=[], mtu=1500):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.mtu = mtu
|
self.mtu = mtu
|
||||||
self.use_dhcp = use_dhcp
|
self.use_dhcp = use_dhcp
|
||||||
|
self.use_dhcpv6 = use_dhcpv6
|
||||||
self.addresses = addresses
|
self.addresses = addresses
|
||||||
self.bridge = None
|
self.bridge = None
|
||||||
self.type = None
|
self.type = None
|
||||||
|
|||||||
82
os_net_config/tests/test_impl_ifcfg.py
Normal file
82
os_net_config/tests/test_impl_ifcfg.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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 tempfile
|
||||||
|
|
||||||
|
from os_net_config import impl_ifcfg
|
||||||
|
from os_net_config import objects
|
||||||
|
from os_net_config.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
_BASE_IFCFG = """DEVICE=foo
|
||||||
|
ONBOOT=yes
|
||||||
|
HOTPLUG=no
|
||||||
|
"""
|
||||||
|
|
||||||
|
_V4_IFCFG = _BASE_IFCFG + """BOOTPROTO=static
|
||||||
|
IPADDR=192.168.1.1
|
||||||
|
NETMASK=255.255.255.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
_V6_IFCFG = _BASE_IFCFG + """IPV6INIT=yes
|
||||||
|
IPV6_AUTOCONF=no
|
||||||
|
IPV6ADDR=2001:abc:a::
|
||||||
|
"""
|
||||||
|
|
||||||
|
_OVS_IFCFG = _BASE_IFCFG + "DEVICETYPE=ovs\n"
|
||||||
|
|
||||||
|
|
||||||
|
_OVS_BRIDGE_IFCFG = _BASE_IFCFG + "DEVICETYPE=ovs\n"
|
||||||
|
|
||||||
|
|
||||||
|
class TestIfcfgNetwork(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestIfcfgNetwork, self).setUp()
|
||||||
|
self.temp_config_file = tempfile.NamedTemporaryFile()
|
||||||
|
|
||||||
|
def test_config_path(name):
|
||||||
|
return self.temp_config_file.name
|
||||||
|
self.stubs.Set(impl_ifcfg, 'ifcfg_config_path', test_config_path)
|
||||||
|
self.provider = impl_ifcfg.IfcfgNetwork()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.temp_config_file.close()
|
||||||
|
super(TestIfcfgNetwork, self).tearDown()
|
||||||
|
|
||||||
|
def get_interface_config(self):
|
||||||
|
return self.provider.interfaces['foo']
|
||||||
|
|
||||||
|
def test_add_base_interface(self):
|
||||||
|
interface = objects.Interface('foo')
|
||||||
|
self.provider.addInterface(interface)
|
||||||
|
self.assertEqual(_BASE_IFCFG, self.get_interface_config())
|
||||||
|
|
||||||
|
def test_add_ovs_interface(self):
|
||||||
|
interface = objects.Interface('foo')
|
||||||
|
interface.type = 'ovs'
|
||||||
|
self.provider.addInterface(interface)
|
||||||
|
self.assertEqual(_OVS_IFCFG, self.get_interface_config())
|
||||||
|
|
||||||
|
def test_add_interface_with_v4(self):
|
||||||
|
v4_addr = objects.Address('192.168.1.1/24')
|
||||||
|
interface = objects.Interface('foo', addresses=[v4_addr])
|
||||||
|
self.provider.addInterface(interface)
|
||||||
|
self.assertEqual(_V4_IFCFG, self.get_interface_config())
|
||||||
|
|
||||||
|
def test_add_interface_with_v6(self):
|
||||||
|
v6_addr = objects.Address('2001:abc:a::/64')
|
||||||
|
interface = objects.Interface('foo', addresses=[v6_addr])
|
||||||
|
self.provider.addInterface(interface)
|
||||||
|
self.assertEqual(_V6_IFCFG, self.get_interface_config())
|
||||||
41
os_net_config/tests/test_objects.py
Normal file
41
os_net_config/tests/test_objects.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from os_net_config import objects
|
||||||
|
from os_net_config.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestAddress(base.TestCase):
|
||||||
|
|
||||||
|
def test_ipv4_address(self):
|
||||||
|
address = objects.Address('192.168.1.1/24')
|
||||||
|
self.assertEqual("192.168.1.1", address.ip)
|
||||||
|
self.assertEqual("255.255.255.0", address.netmask)
|
||||||
|
self.assertEqual(4, address.version)
|
||||||
|
|
||||||
|
def test_ipv6_address(self):
|
||||||
|
address = objects.Address('2001:abc:a::/64')
|
||||||
|
self.assertEqual("2001:abc:a::", address.ip)
|
||||||
|
self.assertEqual("ffff:ffff:ffff:ffff::", address.netmask)
|
||||||
|
self.assertEqual(6, address.version)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInterface(base.TestCase):
|
||||||
|
|
||||||
|
def test_interface_addresses(self):
|
||||||
|
v4_addr = objects.Address('192.168.1.1/24')
|
||||||
|
v6_addr = objects.Address('2001:abc:a::/64')
|
||||||
|
interface = objects.Interface('foo', addresses=[v4_addr, v6_addr])
|
||||||
|
self.assertEquals("192.168.1.1", interface.v4_addresses()[0].ip)
|
||||||
|
self.assertEquals("2001:abc:a::", interface.v6_addresses()[0].ip)
|
||||||
Reference in New Issue
Block a user