From: Aline Manera <alinefm(a)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(a)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(a)linux.vnet.ibm.com>
+# Aline Manera <alinefm(a)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(a)linux.vnet.ibm.com>
+# Aline Manera <alinefm(a)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