From af110352f8f61f68c4758f2c8c0846f9691e6bf1 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 9 Jun 2014 16:38:34 -0400 Subject: Initial ifcfg implementation for interfaces/routes Ifcfg formatted persistence for interfaces and routes. --- os_net_config/__init__.py | 14 +++++ os_net_config/impl_ifcfg.py | 93 ++++++++++++++++++++++++++++++++++ os_net_config/objects.py | 12 +++-- os_net_config/tests/test_impl_ifcfg.py | 82 ++++++++++++++++++++++++++++++ os_net_config/tests/test_objects.py | 41 +++++++++++++++ 5 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 os_net_config/impl_ifcfg.py create mode 100644 os_net_config/tests/test_impl_ifcfg.py create mode 100644 os_net_config/tests/test_objects.py diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py index dbc60ab..bdbc69f 100644 --- a/os_net_config/__init__.py +++ b/os_net_config/__init__.py @@ -17,3 +17,17 @@ import pbr.version __version__ = pbr.version.VersionInfo( '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.") diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py new file mode 100644 index 0000000..c4fbde4 --- /dev/null +++ b/os_net_config/impl_ifcfg.py @@ -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 diff --git a/os_net_config/objects.py b/os_net_config/objects.py index 8c1fcd5..455e55f 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -1,3 +1,5 @@ +# -*- 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 @@ -20,9 +22,10 @@ class NetworkObjectException(Exception): class Route(object): """Base class for network routes.""" - def __init__(self, netmask_cidr, gateway): - self.netmask_cidr = netmask_cidr - self.gateway = gateway + def __init__(self, next_hop, ip_netmask="", default=False): + self.next_hop = next_hop + self.ip_netmask = ip_netmask + self.default = default class Address(object): @@ -41,10 +44,11 @@ class Interface(object): """Base class for network interfaces.""" def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[], - mtu=1500): + routes=[], mtu=1500): self.name = name self.mtu = mtu self.use_dhcp = use_dhcp + self.use_dhcpv6 = use_dhcpv6 self.addresses = addresses self.bridge = None self.type = None diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py new file mode 100644 index 0000000..7a2deec --- /dev/null +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -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()) diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py new file mode 100644 index 0000000..7ffd5b3 --- /dev/null +++ b/os_net_config/tests/test_objects.py @@ -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) -- cgit 1.2.3-korg