
From: Aline Manera <alinefm@br.ibm.com> The model implementation for network and its sub-resources were added to model_/networks.py Also add default network verification in Model() as it is not related to network resource Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- src/kimchi/model_/model.py | 42 +++++++ src/kimchi/model_/networks.py | 265 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 src/kimchi/model_/networks.py diff --git a/src/kimchi/model_/model.py b/src/kimchi/model_/model.py index 709e0bb..3603ec1 100644 --- a/src/kimchi/model_/model.py +++ b/src/kimchi/model_/model.py @@ -21,7 +21,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import inspect +import logging import os +import sys + +import cherrypy +import libvirt from kimchi.basemodel import BaseModel from kimchi.model_.libvirtconnection import LibvirtConnection @@ -35,6 +40,9 @@ class Model(BaseModel): self.conn = LibvirtConnection(libvirt_uri) kargs = {'objstore': self.objstore, 'conn': self.conn} + if 'qemu:///' in libvirt_uri: + self._default_network_check() + this = os.path.basename(__file__) this_mod = os.path.splitext(this)[0] @@ -51,3 +59,37 @@ class Model(BaseModel): models.append(instance(**kargs)) return super(Model, self).__init__(models) + + def _default_network_check(self): + conn = self.conn.get() + xml = """ + <network> + <name>default</name> + <forward mode='nat'/> + <bridge name='virbr0' stp='on' delay='0' /> + <ip address='192.168.122.1' netmask='255.255.255.0'> + <dhcp> + <range start='192.168.122.2' end='192.168.122.254' /> + </dhcp> + </ip> + </network> + """ + try: + net = conn.networkLookupByName("default") + except libvirt.libvirtError: + try: + net = conn.networkDefineXML(xml) + except libvirt.libvirtError, e: + cherrypy.log.error("Fatal: Cannot create default network " + "because of %s, exit kimchid" % e.message, + severity=logging.ERROR) + sys.exit(1) + + if net.isActive() == 0: + try: + net.create() + except libvirt.libvirtError, e: + cherrypy.log.error("Fatal: Cannot activate default network " + "because of %s, exit kimchid" % e.message, + severity=logging.ERROR) + sys.exit(1) diff --git a/src/kimchi/model_/networks.py b/src/kimchi/model_/networks.py new file mode 100644 index 0000000..b164141 --- /dev/null +++ b/src/kimchi/model_/networks.py @@ -0,0 +1,265 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# Aline Manera <alinefm@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 + +import ipaddr +import libvirt + +from kimchi import netinfo +from kimchi import network as knetwork +from kimchi import networkxml +from kimchi import xmlutils +from kimchi.exception import InvalidOperation, InvalidParameter +from kimchi.exception import MissingParameter, NotFoundError, OperationFailed + + +class NetworksModel(object): + def __init__(self, **kargs): + self.conn = kargs['conn'] + + def create(self, params): + conn = self.conn.get() + name = params['name'] + if name in self.get_list(): + raise InvalidOperation("Network %s already exists" % name) + + connection = params["connection"] + # set forward mode, isolated do not need forward + if connection != 'isolated': + params['forward'] = {'mode': 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': + self._set_network_bridge(params) + + xml = networkxml.to_network_xml(**params) + + try: + network = conn.networkDefineXML(xml) + network.setAutostart(True) + except libvirt.libvirtError as e: + raise OperationFailed(e.get_error_message()) + + return name + + def get_list(self): + conn = self.conn.get() + return sorted(conn.listNetworks() + conn.listDefinedNetworks()) + + 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(): + network = self._get_network(net_name) + xml = network.XMLDesc(0) + subnet = NetworkModel.get_network_from_xml(xml)['subnet'] + subnet and net_addrs.append(ipaddr.IPNetwork(subnet)) + netaddr = knetwork.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({'net': str(ip), + 'dhcp': {'range': {'start': dhcp_start, + 'end': dhcp_end}}}) + + def _set_network_bridge(self, params): + try: + iface = params['interface'] + if iface in self.get_all_networks_interfaces(): + raise InvalidParameter("interface '%s' already in use." % + iface) + except KeyError, e: + raise MissingParameter(e) + 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'])) + else: + raise InvalidParameter("the interface should be bare nic, " + "bonding or bridge device.") + + def get_all_networks_interfaces(self): + net_names = self.get_list() + interfaces = [] + for name in net_names: + conn = self.conn.get() + network = conn.networkLookupByName(name) + xml = network.XMLDesc(0) + net_dict = NetworkModel.get_network_from_xml(xml) + forward = net_dict['forward'] + (forward['mode'] == 'bridge' and forward['interface'] and + interfaces.append(forward['interface'][0]) is None or + interfaces.extend(forward['interface'] + forward['pf'])) + net_dict['bridge'] and interfaces.append(net_dict['bridge']) + return interfaces + + 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 + + +class NetworkModel(object): + def __init__(self, **kargs): + self.conn = kargs['conn'] + + def lookup(self, name): + network = self._get_network(name) + xml = network.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 = knetwork.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_a_network(name), + 'autostart': network.autostart() == 1, + 'state': network.isActive() and "active" or "inactive"} + + def _get_vms_attach_to_a_network(self, network): + vms = [] + conn = self.conn.get() + for dom in conn.listAllDomains(0): + networks = self._vm_get_networks(dom) + if network in networks: + vms.append(dom.name()) + return vms + + def _vm_get_networks(self, dom): + xml = dom.XMLDesc(0) + xpath = "/domain/devices/interface[@type='network']/source/@network" + return xmlutils.xpath_get_text(xml, xpath) + + def activate(self, name): + network = self._get_network(name) + network.create() + + def deactivate(self, name): + network = self._get_network(name) + network.destroy() + + def delete(self, name): + network = self._get_network(name) + if network.isActive(): + raise InvalidOperation( + "Unable to delete the active network %s" % name) + self._remove_vlan_tagged_bridge(network) + network.undefine() + + def _get_network(self, name): + conn = self.conn.get() + try: + return conn.networkLookupByName(name) + except libvirt.libvirtError as e: + raise NotFoundError("Network '%s' not found: %s" % + (name, e.get_error_message())) + + @staticmethod + def get_network_from_xml(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 _remove_vlan_tagged_bridge(self, network): + try: + bridge = network.bridgeName() + except libvirt.libvirtError: + pass + else: + if bridge.startswith('kimchi-'): + conn = self.conn.get() + iface = conn.interfaceLookupByName(bridge) + if iface.isActive(): + iface.destroy() + iface.undefine() -- 1.7.10.4