[Kimchi-devel] [PATCH 10/13] refactor model: Create a separated model for interface and network resources

Aline Manera alinefm at linux.vnet.ibm.com
Fri Jan 17 02:24:46 UTC 2014


From: Aline Manera <alinefm at br.ibm.com>

To avoid duplicating code in model and mockmodel, the code to interface
resource was added to model_/interfaces.py.

And the code related to network resource was added to model_/networks.py and
the specific code for each backend (libvirt or mock) was added to
model_/libvirtbackend.py and model_/mockbackend.py

Signed-off-by: Aline Manera <alinefm at br.ibm.com>
---
 src/kimchi/model.py                 |    2 +-
 src/kimchi/model_/interfaces.py     |   48 ++++++++++++
 src/kimchi/model_/libvirtbackend.py |  139 +++++++++++++++++++++++++++++++++++
 src/kimchi/model_/mockbackend.py    |   31 ++++++++
 src/kimchi/model_/networks.py       |  115 +++++++++++++++++++++++++++++
 src/kimchi/networkxml.py            |    6 +-
 6 files changed, 337 insertions(+), 4 deletions(-)
 create mode 100644 src/kimchi/model_/interfaces.py
 create mode 100644 src/kimchi/model_/networks.py

diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index 027d6d5..7b0eafc 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -818,7 +818,7 @@ class Model(object):
             ip.ip = ip.ip + 1
         dhcp_start = str(ip.ip + ip.numhosts / 2)
         dhcp_end = str(ip.ip + ip.numhosts - 2)
-        params.update({'net': str(ip),
+        params.update({'subnet': str(ip),
                        'dhcp': {'range': {'start': dhcp_start,
                                 'end': dhcp_end}}})
 
diff --git a/src/kimchi/model_/interfaces.py b/src/kimchi/model_/interfaces.py
new file mode 100644
index 0000000..7b1d156
--- /dev/null
+++ b/src/kimchi/model_/interfaces.py
@@ -0,0 +1,48 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+#  Adam Litke <agl at linux.vnet.ibm.com>
+#  Aline Manera <alinefm at linux.vnet.ibm.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+from kimchi import netinfo
+
+class Interfaces(object):
+    def __init__(self, backend):
+        self.backend = backend
+
+    def get_list(self):
+        return list(set(netinfo.all_favored_interfaces()) -
+                    set(self.get_used_ifaces()))
+
+    def get_used_ifaces(self):
+        net_names = self.backend.get_networks()
+        interfaces = []
+        for name in net_names:
+            net_dict = self.backend.get_network_by_name(name)
+            net_dict['interface'] and interfaces.append(net_dict['interface'])
+
+        return interfaces
+
+class Interface(object):
+    def lookup(self, name):
+        try:
+            return netinfo.get_interface_info(name)
+        except ValueError, e:
+            raise NotFoundError(e)
diff --git a/src/kimchi/model_/libvirtbackend.py b/src/kimchi/model_/libvirtbackend.py
index ee263c6..630204b 100644
--- a/src/kimchi/model_/libvirtbackend.py
+++ b/src/kimchi/model_/libvirtbackend.py
@@ -24,6 +24,7 @@
 import cherrypy
 import fnmatch
 import glob
+import ipaddr
 import logging
 import os
 import platform
@@ -36,6 +37,9 @@ from cherrypy.process.plugins import BackgroundTask
 from collections import defaultdict
 
 from kimchi import config
+from kimchi import network
+from kimchi import netinfo
+from kimchi import networkxml
 from kimchi import xmlutils
 from kimchi.asynctask import AsyncTask
 from kimchi.featuretests import FeatureTests
@@ -491,3 +495,138 @@ class LibvirtBackend(object):
             volume.delete(0)
         except libvirt.libvirtError as e:
             raise OperationFailed(e.get_error_message())
+
+    def create_network(self, params):
+        connection = params["connection"]
+        # set forward mode, isolated do not need forward
+        if connection != 'isolated':
+            params['forward'] = {'mode': connection}
+
+        if connection == 'bridge':
+            if netinfo.is_bridge(iface):
+                params['bridge'] = iface
+            elif netinfo.is_bare_nic(iface) or netinfo.is_bonding(iface):
+                if params.get('vlan_id') is None:
+                    params['forward']['dev'] = iface
+                else:
+                    params['bridge'] = \
+                        self._create_vlan_tagged_bridge(str(iface),
+                                                        str(params['vlan_id']))
+
+        xml = networkxml.to_network_xml(**params)
+        try:
+            conn = self.conn.get()
+            network = conn.networkDefineXML(xml)
+            network.setAutostart(True)
+        except libvirt.libvirtError as e:
+            raise OperationFailed(e.get_error_message())
+
+    def _create_vlan_tagged_bridge(self, interface, vlan_id):
+        br_name = '-'.join(('kimchi', interface, vlan_id))
+        br_xml = networkxml.create_vlan_tagged_bridge_xml(br_name, interface,
+                                                          vlan_id)
+        conn = self.conn.get()
+        conn.changeBegin()
+        try:
+            vlan_tagged_br = conn.interfaceDefineXML(br_xml)
+            vlan_tagged_br.create()
+        except libvirt.libvirtError as e:
+            conn.changeRollback()
+            raise OperationFailed(e.message)
+        else:
+            conn.changeCommit()
+            return br_name
+
+    def get_networks(self):
+        conn = self.conn.get()
+        return conn.listNetworks() + conn.listDefinedNetworks()
+
+    def get_network_by_name(self, name):
+        conn = self.conn.get()
+        net = conn.networkLookupByName(name)
+        xml = net.XMLDesc(0)
+        net_dict = self._get_network_from_xml(xml)
+        subnet = net_dict['subnet']
+        dhcp = net_dict['dhcp']
+        forward = net_dict['forward']
+        interface = net_dict['bridge']
+
+        connection = forward['mode'] or "isolated"
+        # FIXME, if we want to support other forward mode well.
+        if connection == 'bridge':
+            # macvtap bridge
+            interface = interface or forward['interface'][0]
+            # exposing the network on linux bridge or macvtap interface
+            interface_subnet = network.get_dev_netaddr(interface)
+            subnet = subnet if subnet else interface_subnet
+
+        # libvirt use format 192.168.0.1/24, standard should be 192.168.0.0/24
+        # http://www.ovirt.org/File:Issue3.png
+        if subnet:
+            subnet = ipaddr.IPNetwork(subnet)
+            subnet = "%s/%s" % (subnet.network, subnet.prefixlen)
+
+        return {'connection': connection,
+                'interface': interface,
+                'subnet': subnet,
+                'dhcp': dhcp,
+                'vms': self._get_vms_attach_to_network(name),
+                'autostart': net.autostart() == 1,
+                'state':  net.isActive() and "active" or "inactive"}
+
+    def _get_network_from_xml(self, xml):
+        address = xmlutils.xpath_get_text(xml, "/network/ip/@address")
+        address = address and address[0] or ''
+        netmask = xmlutils.xpath_get_text(xml, "/network/ip/@netmask")
+        netmask = netmask and netmask[0] or ''
+        net = address and netmask and "/".join([address, netmask]) or ''
+
+        dhcp_start = xmlutils.xpath_get_text(xml,
+                                             "/network/ip/dhcp/range/@start")
+        dhcp_start = dhcp_start and dhcp_start[0] or ''
+        dhcp_end = xmlutils.xpath_get_text(xml, "/network/ip/dhcp/range/@end")
+        dhcp_end = dhcp_end and dhcp_end[0] or ''
+        dhcp = {'start': dhcp_start, 'end': dhcp_end}
+
+        forward_mode = xmlutils.xpath_get_text(xml, "/network/forward/@mode")
+        forward_mode = forward_mode and forward_mode[0] or ''
+        forward_if = xmlutils.xpath_get_text(xml,
+                                             "/network/forward/interface/@dev")
+        forward_pf = xmlutils.xpath_get_text(xml, "/network/forward/pf/@dev")
+        bridge = xmlutils.xpath_get_text(xml, "/network/bridge/@name")
+        bridge = bridge and bridge[0] or ''
+        return {'subnet': net, 'dhcp': dhcp, 'bridge': bridge,
+                'forward': {'mode': forward_mode, 'interface': forward_if,
+                            'pf': forward_pf}}
+
+    def _get_vms_attach_to_network(self, network):
+        return []
+
+    def activate_network(self, name):
+        conn = self.conn.get()
+        net = conn.networkLookupByName(name)
+        net.create()
+
+    def deactivate_network(self, name):
+        conn = self.conn.get()
+        net = conn.networkLookupByName(name)
+        net.destroy()
+
+    def delete_network(self, name):
+        conn = self.conn.get()
+        net = conn.networkLookupByName(name)
+        self._remove_vlan_tagged_bridge(net)
+        net.undefine()
+
+    def _remove_vlan_tagged_bridge(self, network):
+        try:
+            bridge = network.bridgeName()
+        except libvirt.libvirtError:
+            pass
+        else:
+            if self._is_vlan_tagged_bridge(bridge):
+                conn = self.conn.get()
+                iface = conn.interfaceLookupByName(bridge)
+                if iface.isActive():
+                    iface.destroy()
+                iface.undefine()
diff --git a/src/kimchi/model_/mockbackend.py b/src/kimchi/model_/mockbackend.py
index 1f93d36..4c0fdf8 100644
--- a/src/kimchi/model_/mockbackend.py
+++ b/src/kimchi/model_/mockbackend.py
@@ -30,11 +30,16 @@ from kimchi.asynctask import AsyncTask
 from kimchi.objectstore import ObjectStore
 
 class MockBackend(object):
+    _network_info = {'state': 'inactive', 'autostart': True, 'connection': '',
+                     'interface': '', 'subnet': '',
+                     'dhcp': {'start': '', 'stop':  ''}}
+
     def __init__(self, objstore_loc=None):
         self.objstore = ObjectStore(objstore_loc)
         self.next_taskid = 1
         self.host_stats = self._get_host_stats()
         self._storagepools = {}
+        self._networks = {}
 
     def _get_host_stats(self):
         memory_stats = {'total': 3934908416L,
@@ -150,6 +155,32 @@ class MockBackend(object):
     def delete_storagevolume(self, pool, name):
         del self._storagepools[pool]._volumes[name]
 
+    def create_network(self, params):
+        name = params['name']
+        info = copy.deepcopy(self._network_info)
+        info.update(params)
+        self._networks[name] = info
+
+    def get_networks(self):
+        return self._networks.keys()
+
+    def get_network_by_name(self, name):
+        info = self._networks[name]
+        info['vms'] = self._get_vms_attach_to_a_network(name)
+        return info
+
+    def _get_vms_attach_to_a_network(self, network):
+        return []
+
+    def activate_network(self, name):
+        self._networks[name]['state'] = 'active'
+
+    def deactivate_network(self, name):
+        self._networks[name]['state'] = 'inactive'
+
+    def delete_network(self, name):
+        del self._networks[name]
+
 class MockStoragePool(object):
     def __init__(self, name):
         self.name = name
diff --git a/src/kimchi/model_/networks.py b/src/kimchi/model_/networks.py
new file mode 100644
index 0000000..dfefa81
--- /dev/null
+++ b/src/kimchi/model_/networks.py
@@ -0,0 +1,115 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+#  Adam Litke <agl at linux.vnet.ibm.com>
+#  Aline Manera <alinefm at linux.vnet.ibm.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+from kimchi.model import interfaces
+
+class Networks(object):
+    def __init__(self, backend):
+        self.backend = backend
+        self.ifaces = interfaces.Interfaces(backend)
+
+    def create(self, params):
+        name = params['name']
+        if name in self.get_list():
+            raise InvalidOperation("Network %s already exists" % name)
+
+        connection = params['connection']
+        # set subnet, bridge network do not need subnet
+        if connection in ["nat", 'isolated']:
+            self._set_network_subnet(params)
+
+        # only bridge network need bridge(linux bridge) or interface(macvtap)
+        if connection == 'bridge':
+            iface = params.get('interface', None)
+            if iface is None:
+                raise MissingParameter("You need to specify interface to create"
+                                       " a bridged network.")
+
+            if iface in self.ifaces.get_used_ifaces():
+                raise InvalidParameter("interface '%s' already in use." % iface)
+
+            if not (netinfo.is_bridge(iface) or netinfo.is_bare_nic(iface) or
+                    netinfo.is_bonding(iface)):
+                raise InvalidParameter("The interface should be bare nic, bonding "
+                                       "or bridge device.")
+
+        self.backend.create_network(params)
+        return name
+
+    def get_list(self):
+        return sorted(self.backend.get_networks())
+
+    def _set_network_subnet(self, params):
+        netaddr = params.get('subnet', '')
+        net_addrs = []
+        # lookup a free network address for nat and isolated automatically
+        if not netaddr:
+            for net_name in self.get_list():
+                net = self.backend.get_network_by_name(net_name)
+                subnet = net['subnet']
+                subnet and net_addrs.append(ipaddr.IPNetwork(subnet))
+
+            netaddr = network.get_one_free_network(net_addrs)
+            if not netaddr:
+                raise OperationFailed("can not find a free IP address for "
+                                      "network '%s'" % params['name'])
+        try:
+            ip = ipaddr.IPNetwork(netaddr)
+        except ValueError as e:
+            raise InvalidParameter("%s" % e)
+
+        if ip.ip == ip.network:
+            ip.ip = ip.ip + 1
+
+        dhcp_start = str(ip.ip + ip.numhosts / 2)
+        dhcp_end = str(ip.ip + ip.numhosts - 2)
+
+        params.update({'subnet': str(ip),
+                       'dhcp': {'range': {'start': dhcp_start,
+                                'end': dhcp_end}}})
+
+class Network(Networks):
+    def _network_exist(self, name):
+        if name not in self.get_list():
+            raise NotFoundError("Network '%s' not found.")
+
+        return True
+
+    def lookup(self, name):
+        if self._network_exist(name):
+            return self.backend.get_network_by_name(name)
+
+    def activate(self, name):
+        if self._network_exist(name):
+            return self.backend.activate_network(name)
+
+    def deactivate(self, name):
+        if self._network_exist(name):
+            return self.backend.deactivate_network(name)
+
+    def delete(self, name):
+        if self.lookup(name)['state'] == 'active':
+            raise InvalidOperation("Unable to delete the active network %s" %
+                                    name)
+
+        return self.backend.delete_network(name)
diff --git a/src/kimchi/networkxml.py b/src/kimchi/networkxml.py
index 63cb210..212b0ff 100644
--- a/src/kimchi/networkxml.py
+++ b/src/kimchi/networkxml.py
@@ -59,8 +59,8 @@ def _get_ip_xml(**kwargs):
     </ip>
     """
     xml = ""
-    if 'net' in kwargs.keys():
-        net = ipaddr.IPNetwork(kwargs['net'])
+    if 'subnet' in kwargs.keys():
+        net = ipaddr.IPNetwork(kwargs['subnet'])
         address = str(net.ip)
         netmask = str(net.netmask)
         dhcp_params = kwargs.get('dhcp', {})
@@ -96,7 +96,7 @@ def to_network_xml(**kwargs):
     params = {'name': kwargs['name']}
     # None means is Isolated network, {} means default mode nat
     forward = kwargs.get('forward', {"mode": None})
-    ip = {'net': kwargs['net']} if 'net' in kwargs else {}
+    ip = {'subnet': kwargs['subnet']} if 'subnet' in kwargs else {}
     ip['dhcp'] = kwargs.get('dhcp', {})
     bridge = kwargs.get('bridge')
     params = {'name': kwargs['name'],
-- 
1.7.10.4




More information about the Kimchi-devel mailing list