mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-11-04 00:02:37 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			868 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			868 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
# Licensed to the Apache Software Foundation (ASF) under one
 | 
						|
# or more contributor license agreements.  See the NOTICE file
 | 
						|
# distributed with this work for additional information
 | 
						|
# regarding copyright ownership.  The ASF licenses this file
 | 
						|
# to you 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 sys
 | 
						|
import syslog
 | 
						|
import os
 | 
						|
 | 
						|
from xml.dom.minidom import getDOMImplementation
 | 
						|
from xml.dom.minidom import parse as parseXML
 | 
						|
 | 
						|
the_root_prefix = ""
 | 
						|
def root_prefix():
 | 
						|
    """Returns a string to prefix to all file name references, which
 | 
						|
    is useful for testing."""
 | 
						|
    return the_root_prefix
 | 
						|
def set_root_prefix(prefix):
 | 
						|
    global the_root_prefix
 | 
						|
    the_root_prefix = prefix
 | 
						|
 | 
						|
log_destination = "syslog"
 | 
						|
def get_log_destination():
 | 
						|
    """Returns the current log destination.
 | 
						|
    'syslog' means "log to syslog".
 | 
						|
    'stderr' means "log to stderr"."""
 | 
						|
    return log_destination
 | 
						|
def set_log_destination(dest):
 | 
						|
    global log_destination
 | 
						|
    log_destination = dest
 | 
						|
 | 
						|
# Logging.
 | 
						|
 | 
						|
def log(s):
 | 
						|
    if get_log_destination() == 'syslog':
 | 
						|
        syslog.syslog(s)
 | 
						|
    else:
 | 
						|
        print >>sys.stderr, s
 | 
						|
 | 
						|
# Exceptions.
 | 
						|
 | 
						|
class Error(Exception):
 | 
						|
    def __init__(self, msg):
 | 
						|
        Exception.__init__(self)
 | 
						|
        self.msg = msg
 | 
						|
 | 
						|
# Run external utilities
 | 
						|
#
 | 
						|
 | 
						|
def run_command(command):
 | 
						|
    log("Running command: " + ' '.join(command))
 | 
						|
    rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
 | 
						|
    if rc != 0:
 | 
						|
        log("Command failed %d: " % rc + ' '.join(command))
 | 
						|
        return False
 | 
						|
    return True
 | 
						|
 | 
						|
#
 | 
						|
# Configuration File Handling.
 | 
						|
#
 | 
						|
 | 
						|
class ConfigurationFile(object):
 | 
						|
    """Write a file, tracking old and new versions.
 | 
						|
 | 
						|
    Supports writing a new version of a file and applying and
 | 
						|
    reverting those changes.
 | 
						|
    """
 | 
						|
 | 
						|
    __STATE = {"OPEN":"OPEN",
 | 
						|
               "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
 | 
						|
               "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
 | 
						|
 | 
						|
    def __init__(self, path):
 | 
						|
        dirname,basename = os.path.split(path)
 | 
						|
 | 
						|
        self.__state = self.__STATE['OPEN']
 | 
						|
        self.__children = []
 | 
						|
 | 
						|
        self.__path    = os.path.join(dirname, basename)
 | 
						|
        self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
 | 
						|
        self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
 | 
						|
 | 
						|
        self.__f = open(self.__newpath, "w")
 | 
						|
 | 
						|
    def attach_child(self, child):
 | 
						|
        self.__children.append(child)
 | 
						|
 | 
						|
    def path(self):
 | 
						|
        return self.__path
 | 
						|
 | 
						|
    def readlines(self):
 | 
						|
        try:
 | 
						|
            return open(self.path()).readlines()
 | 
						|
        except:
 | 
						|
            return ""
 | 
						|
 | 
						|
    def write(self, args):
 | 
						|
        if self.__state != self.__STATE['OPEN']:
 | 
						|
            raise Error("Attempt to write to file in state %s" % self.__state)
 | 
						|
        self.__f.write(args)
 | 
						|
 | 
						|
    def close(self):
 | 
						|
        if self.__state != self.__STATE['OPEN']:
 | 
						|
            raise Error("Attempt to close file in state %s" % self.__state)
 | 
						|
 | 
						|
        self.__f.close()
 | 
						|
        self.__state = self.__STATE['NOT-APPLIED']
 | 
						|
 | 
						|
    def changed(self):
 | 
						|
        if self.__state != self.__STATE['NOT-APPLIED']:
 | 
						|
            raise Error("Attempt to compare file in state %s" % self.__state)
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    def apply(self):
 | 
						|
        if self.__state != self.__STATE['NOT-APPLIED']:
 | 
						|
            raise Error("Attempt to apply configuration from state %s" % self.__state)
 | 
						|
 | 
						|
        for child in self.__children:
 | 
						|
            child.apply()
 | 
						|
 | 
						|
        log("Applying changes to %s configuration" % self.__path)
 | 
						|
 | 
						|
        # Remove previous backup.
 | 
						|
        if os.access(self.__oldpath, os.F_OK):
 | 
						|
            os.unlink(self.__oldpath)
 | 
						|
 | 
						|
        # Save current configuration.
 | 
						|
        if os.access(self.__path, os.F_OK):
 | 
						|
            os.link(self.__path, self.__oldpath)
 | 
						|
            os.unlink(self.__path)
 | 
						|
 | 
						|
        # Apply new configuration.
 | 
						|
        assert(os.path.exists(self.__newpath))
 | 
						|
        os.link(self.__newpath, self.__path)
 | 
						|
 | 
						|
        # Remove temporary file.
 | 
						|
        os.unlink(self.__newpath)
 | 
						|
 | 
						|
        self.__state = self.__STATE['APPLIED']
 | 
						|
 | 
						|
    def revert(self):
 | 
						|
        if self.__state != self.__STATE['APPLIED']:
 | 
						|
            raise Error("Attempt to revert configuration from state %s" % self.__state)
 | 
						|
 | 
						|
        for child in self.__children:
 | 
						|
            child.revert()
 | 
						|
 | 
						|
        log("Reverting changes to %s configuration" % self.__path)
 | 
						|
 | 
						|
        # Remove existing new configuration
 | 
						|
        if os.access(self.__newpath, os.F_OK):
 | 
						|
            os.unlink(self.__newpath)
 | 
						|
 | 
						|
        # Revert new configuration.
 | 
						|
        if os.access(self.__path, os.F_OK):
 | 
						|
            os.link(self.__path, self.__newpath)
 | 
						|
            os.unlink(self.__path)
 | 
						|
 | 
						|
        # Revert to old configuration.
 | 
						|
        if os.access(self.__oldpath, os.F_OK):
 | 
						|
            os.link(self.__oldpath, self.__path)
 | 
						|
            os.unlink(self.__oldpath)
 | 
						|
 | 
						|
        # Leave .*.xapi-new as an aid to debugging.
 | 
						|
 | 
						|
        self.__state = self.__STATE['REVERTED']
 | 
						|
 | 
						|
    def commit(self):
 | 
						|
        if self.__state != self.__STATE['APPLIED']:
 | 
						|
            raise Error("Attempt to commit configuration from state %s" % self.__state)
 | 
						|
 | 
						|
        for child in self.__children:
 | 
						|
            child.commit()
 | 
						|
 | 
						|
        log("Committing changes to %s configuration" % self.__path)
 | 
						|
 | 
						|
        if os.access(self.__oldpath, os.F_OK):
 | 
						|
            os.unlink(self.__oldpath)
 | 
						|
        if os.access(self.__newpath, os.F_OK):
 | 
						|
            os.unlink(self.__newpath)
 | 
						|
 | 
						|
        self.__state = self.__STATE['COMMITTED']
 | 
						|
 | 
						|
#
 | 
						|
# Helper functions for encoding/decoding database attributes to/from XML.
 | 
						|
#
 | 
						|
 | 
						|
def _str_to_xml(xml, parent, tag, val):
 | 
						|
    e = xml.createElement(tag)
 | 
						|
    parent.appendChild(e)
 | 
						|
    v = xml.createTextNode(val)
 | 
						|
    e.appendChild(v)
 | 
						|
def _str_from_xml(n):
 | 
						|
    def getText(nodelist):
 | 
						|
        rc = ""
 | 
						|
        for node in nodelist:
 | 
						|
            if node.nodeType == node.TEXT_NODE:
 | 
						|
                rc = rc + node.data
 | 
						|
        return rc
 | 
						|
    return getText(n.childNodes).strip()
 | 
						|
 | 
						|
def _bool_to_xml(xml, parent, tag, val):
 | 
						|
    if val:
 | 
						|
        _str_to_xml(xml, parent, tag, "True")
 | 
						|
    else:
 | 
						|
        _str_to_xml(xml, parent, tag, "False")
 | 
						|
def _bool_from_xml(n):
 | 
						|
    s = _str_from_xml(n)
 | 
						|
    if s == "True":
 | 
						|
        return True
 | 
						|
    elif s == "False":
 | 
						|
        return False
 | 
						|
    else:
 | 
						|
        raise Error("Unknown boolean value %s" % s)
 | 
						|
 | 
						|
def _strlist_to_xml(xml, parent, ltag, itag, val):
 | 
						|
    e = xml.createElement(ltag)
 | 
						|
    parent.appendChild(e)
 | 
						|
    for v in val:
 | 
						|
        c = xml.createElement(itag)
 | 
						|
        e.appendChild(c)
 | 
						|
        cv = xml.createTextNode(v)
 | 
						|
        c.appendChild(cv)
 | 
						|
def _strlist_from_xml(n, ltag, itag):
 | 
						|
    ret = []
 | 
						|
    for n in n.childNodes:
 | 
						|
        if n.nodeName == itag:
 | 
						|
            ret.append(_str_from_xml(n))
 | 
						|
    return ret
 | 
						|
 | 
						|
def _otherconfig_to_xml(xml, parent, val, attrs):
 | 
						|
    otherconfig = xml.createElement("other_config")
 | 
						|
    parent.appendChild(otherconfig)
 | 
						|
    for n,v in val.items():
 | 
						|
        if not n in attrs:
 | 
						|
            raise Error("Unknown other-config attribute: %s" % n)
 | 
						|
        _str_to_xml(xml, otherconfig, n, v)
 | 
						|
def _otherconfig_from_xml(n, attrs):
 | 
						|
    ret = {}
 | 
						|
    for n in n.childNodes:
 | 
						|
        if n.nodeName in attrs:
 | 
						|
            ret[n.nodeName] = _str_from_xml(n)
 | 
						|
    return ret
 | 
						|
 | 
						|
#
 | 
						|
# Definitions of the database objects (and their attributes) used by interface-reconfigure.
 | 
						|
#
 | 
						|
# Each object is defined by a dictionary mapping an attribute name in
 | 
						|
# the xapi database to a tuple containing two items:
 | 
						|
#  - a function which takes this attribute and encodes it as XML.
 | 
						|
#  - a function which takes XML and decocdes it into a value.
 | 
						|
#
 | 
						|
# other-config attributes are specified as a simple array of strings
 | 
						|
 | 
						|
_PIF_XML_TAG = "pif"
 | 
						|
_VLAN_XML_TAG = "vlan"
 | 
						|
_BOND_XML_TAG = "bond"
 | 
						|
_NETWORK_XML_TAG = "network"
 | 
						|
 | 
						|
_ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
 | 
						|
 | 
						|
_PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
 | 
						|
                        [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
 | 
						|
                        _ETHTOOL_OTHERCONFIG_ATTRS
 | 
						|
 | 
						|
_PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
 | 
						|
               'management': (_bool_to_xml,_bool_from_xml),
 | 
						|
               'network': (_str_to_xml,_str_from_xml),
 | 
						|
               'device': (_str_to_xml,_str_from_xml),
 | 
						|
               'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
 | 
						|
                                  lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
 | 
						|
               'bond_slave_of': (_str_to_xml,_str_from_xml),
 | 
						|
               'VLAN': (_str_to_xml,_str_from_xml),
 | 
						|
               'VLAN_master_of': (_str_to_xml,_str_from_xml),
 | 
						|
               'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
 | 
						|
                                 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
 | 
						|
               'ip_configuration_mode': (_str_to_xml,_str_from_xml),
 | 
						|
               'IP': (_str_to_xml,_str_from_xml),
 | 
						|
               'netmask': (_str_to_xml,_str_from_xml),
 | 
						|
               'gateway': (_str_to_xml,_str_from_xml),
 | 
						|
               'DNS': (_str_to_xml,_str_from_xml),
 | 
						|
               'MAC': (_str_to_xml,_str_from_xml),
 | 
						|
               'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
 | 
						|
                                lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
 | 
						|
 | 
						|
               # Special case: We write the current value
 | 
						|
               # PIF.currently-attached to the cache but since it will
 | 
						|
               # not be valid when we come to use the cache later
 | 
						|
               # (i.e. after a reboot) we always read it as False.
 | 
						|
               'currently_attached': (_bool_to_xml, lambda n: False),
 | 
						|
             }
 | 
						|
 | 
						|
_VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
 | 
						|
                'tagged_PIF': (_str_to_xml,_str_from_xml),
 | 
						|
                'untagged_PIF': (_str_to_xml,_str_from_xml),
 | 
						|
              }
 | 
						|
 | 
						|
_BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
 | 
						|
               'master': (_str_to_xml,_str_from_xml),
 | 
						|
               'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
 | 
						|
                          lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
 | 
						|
              }
 | 
						|
 | 
						|
_NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
 | 
						|
 | 
						|
_NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
 | 
						|
                   'bridge': (_str_to_xml,_str_from_xml),
 | 
						|
                   'MTU': (_str_to_xml,_str_from_xml),
 | 
						|
                   'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
 | 
						|
                            lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
 | 
						|
                   'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
 | 
						|
                                    lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
 | 
						|
                 }
 | 
						|
 | 
						|
#
 | 
						|
# Database Cache object
 | 
						|
#
 | 
						|
 | 
						|
_db = None
 | 
						|
 | 
						|
def db():
 | 
						|
    assert(_db is not None)
 | 
						|
    return _db
 | 
						|
 | 
						|
def db_init_from_cache(cache):
 | 
						|
    global _db
 | 
						|
    assert(_db is None)
 | 
						|
    _db = DatabaseCache(cache_file=cache)
 | 
						|
    
 | 
						|
def db_init_from_xenapi(session):
 | 
						|
    global _db 
 | 
						|
    assert(_db is None)
 | 
						|
    _db  = DatabaseCache(session_ref=session)
 | 
						|
    
 | 
						|
class DatabaseCache(object):
 | 
						|
    def __read_xensource_inventory(self):
 | 
						|
        filename = root_prefix() + "/etc/xensource-inventory"
 | 
						|
        f = open(filename, "r")
 | 
						|
        lines = [x.strip("\n") for x in f.readlines()]
 | 
						|
        f.close()
 | 
						|
 | 
						|
        defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
 | 
						|
        defs = [ (a, b.strip("'")) for (a,b) in defs ]
 | 
						|
 | 
						|
        return dict(defs)
 | 
						|
 | 
						|
    def __pif_on_host(self,pif):
 | 
						|
        return self.__pifs.has_key(pif)
 | 
						|
 | 
						|
    def __get_pif_records_from_xapi(self, session, host):
 | 
						|
        self.__pifs = {}
 | 
						|
        for (p,rec) in session.xenapi.PIF.get_all_records().items():
 | 
						|
            if rec['host'] != host:
 | 
						|
                continue
 | 
						|
            self.__pifs[p] = {}
 | 
						|
            for f in _PIF_ATTRS:
 | 
						|
                self.__pifs[p][f] = rec[f]
 | 
						|
            self.__pifs[p]['other_config'] = {}
 | 
						|
            for f in _PIF_OTHERCONFIG_ATTRS:
 | 
						|
                if not rec['other_config'].has_key(f): continue
 | 
						|
                self.__pifs[p]['other_config'][f] = rec['other_config'][f]
 | 
						|
 | 
						|
    def __get_vlan_records_from_xapi(self, session):
 | 
						|
        self.__vlans = {}
 | 
						|
        for (v,rec) in session.xenapi.VLAN.get_all_records().items():
 | 
						|
            if not self.__pif_on_host(rec['untagged_PIF']):
 | 
						|
                continue
 | 
						|
            self.__vlans[v] = {}
 | 
						|
            for f in _VLAN_ATTRS:
 | 
						|
                self.__vlans[v][f] = rec[f]
 | 
						|
 | 
						|
    def __get_bond_records_from_xapi(self, session):
 | 
						|
        self.__bonds = {}
 | 
						|
        for (b,rec) in session.xenapi.Bond.get_all_records().items():
 | 
						|
            if not self.__pif_on_host(rec['master']):
 | 
						|
                continue
 | 
						|
            self.__bonds[b] = {}
 | 
						|
            for f in _BOND_ATTRS:
 | 
						|
                self.__bonds[b][f] = rec[f]
 | 
						|
 | 
						|
    def __get_network_records_from_xapi(self, session):
 | 
						|
        self.__networks = {}
 | 
						|
        for (n,rec) in session.xenapi.network.get_all_records().items():
 | 
						|
            self.__networks[n] = {}
 | 
						|
            for f in _NETWORK_ATTRS:
 | 
						|
                if f == "PIFs":
 | 
						|
                    # drop PIFs on other hosts
 | 
						|
                    self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
 | 
						|
                elif f == "MTU" and f not in rec:
 | 
						|
                    # XenServer 5.5 network records did not have an
 | 
						|
                    # MTU field, so allow this to be missing.
 | 
						|
                    pass
 | 
						|
                else:
 | 
						|
                    self.__networks[n][f] = rec[f]
 | 
						|
            self.__networks[n]['other_config'] = {}
 | 
						|
            for f in _NETWORK_OTHERCONFIG_ATTRS:
 | 
						|
                if not rec['other_config'].has_key(f): continue
 | 
						|
                self.__networks[n]['other_config'][f] = rec['other_config'][f]
 | 
						|
 | 
						|
    def __to_xml(self, xml, parent, key, ref, rec, attrs):
 | 
						|
        """Encode a database object as XML"""
 | 
						|
        e = xml.createElement(key)
 | 
						|
        parent.appendChild(e)
 | 
						|
        if ref:
 | 
						|
            e.setAttribute('ref', ref)
 | 
						|
 | 
						|
        for n,v in rec.items():
 | 
						|
            if attrs.has_key(n):
 | 
						|
                h,_ = attrs[n]
 | 
						|
                h(xml, e, n, v)
 | 
						|
            else:
 | 
						|
                raise Error("Unknown attribute %s" % n)
 | 
						|
    def __from_xml(self, e, attrs):
 | 
						|
        """Decode a database object from XML"""
 | 
						|
        ref = e.attributes['ref'].value
 | 
						|
        rec = {}
 | 
						|
        for n in e.childNodes:
 | 
						|
            if n.nodeName in attrs:
 | 
						|
                _,h = attrs[n.nodeName]
 | 
						|
                rec[n.nodeName] = h(n)
 | 
						|
        return (ref,rec)
 | 
						|
 | 
						|
    def __init__(self, session_ref=None, cache_file=None):
 | 
						|
        if session_ref and cache_file:
 | 
						|
            raise Error("can't specify session reference and cache file")
 | 
						|
        if cache_file == None:
 | 
						|
            import XenAPI
 | 
						|
            session = XenAPI.xapi_local()
 | 
						|
 | 
						|
            if not session_ref:
 | 
						|
                log("No session ref given on command line, logging in.")
 | 
						|
                session.xenapi.login_with_password("root", "")
 | 
						|
            else:
 | 
						|
                session._session = session_ref
 | 
						|
 | 
						|
            try:
 | 
						|
 | 
						|
                inventory = self.__read_xensource_inventory()
 | 
						|
                assert(inventory.has_key('INSTALLATION_UUID'))
 | 
						|
                log("host uuid is %s" % inventory['INSTALLATION_UUID'])
 | 
						|
 | 
						|
                host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
 | 
						|
 | 
						|
                self.__get_pif_records_from_xapi(session, host)
 | 
						|
 | 
						|
                self.__get_vlan_records_from_xapi(session)
 | 
						|
                self.__get_bond_records_from_xapi(session)
 | 
						|
                self.__get_network_records_from_xapi(session)
 | 
						|
            finally:
 | 
						|
                if not session_ref:
 | 
						|
                    session.xenapi.session.logout()
 | 
						|
        else:
 | 
						|
            log("Loading xapi database cache from %s" % cache_file)
 | 
						|
 | 
						|
            xml = parseXML(root_prefix() + cache_file)
 | 
						|
 | 
						|
            self.__pifs = {}
 | 
						|
            self.__bonds = {}
 | 
						|
            self.__vlans = {}
 | 
						|
            self.__networks = {}
 | 
						|
 | 
						|
            assert(len(xml.childNodes) == 1)
 | 
						|
            toplevel = xml.childNodes[0]
 | 
						|
 | 
						|
            assert(toplevel.nodeName == "xenserver-network-configuration")
 | 
						|
 | 
						|
            for n in toplevel.childNodes:
 | 
						|
                if n.nodeName == "#text":
 | 
						|
                    pass
 | 
						|
                elif n.nodeName == _PIF_XML_TAG:
 | 
						|
                    (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
 | 
						|
                    self.__pifs[ref] = rec
 | 
						|
                elif n.nodeName == _BOND_XML_TAG:
 | 
						|
                    (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
 | 
						|
                    self.__bonds[ref] = rec
 | 
						|
                elif n.nodeName == _VLAN_XML_TAG:
 | 
						|
                    (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
 | 
						|
                    self.__vlans[ref] = rec
 | 
						|
                elif n.nodeName == _NETWORK_XML_TAG:
 | 
						|
                    (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
 | 
						|
                    self.__networks[ref] = rec
 | 
						|
                else:
 | 
						|
                    raise Error("Unknown XML element %s" % n.nodeName)
 | 
						|
 | 
						|
    def save(self, cache_file):
 | 
						|
 | 
						|
        xml = getDOMImplementation().createDocument(
 | 
						|
            None, "xenserver-network-configuration", None)
 | 
						|
        for (ref,rec) in self.__pifs.items():
 | 
						|
            self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
 | 
						|
        for (ref,rec) in self.__bonds.items():
 | 
						|
            self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
 | 
						|
        for (ref,rec) in self.__vlans.items():
 | 
						|
            self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
 | 
						|
        for (ref,rec) in self.__networks.items():
 | 
						|
            self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
 | 
						|
                          _NETWORK_ATTRS)
 | 
						|
 | 
						|
        f = open(cache_file, 'w')
 | 
						|
        f.write(xml.toprettyxml())
 | 
						|
        f.close()
 | 
						|
 | 
						|
    def get_pif_by_uuid(self, uuid):
 | 
						|
        pifs = map(lambda (ref,rec): ref,
 | 
						|
                  filter(lambda (ref,rec): uuid == rec['uuid'],
 | 
						|
                         self.__pifs.items()))
 | 
						|
        if len(pifs) == 0:
 | 
						|
            raise Error("Unknown PIF \"%s\"" % uuid)
 | 
						|
        elif len(pifs) > 1:
 | 
						|
            raise Error("Non-unique PIF \"%s\"" % uuid)
 | 
						|
 | 
						|
        return pifs[0]
 | 
						|
 | 
						|
    def get_pifs_by_device(self, device):
 | 
						|
        return map(lambda (ref,rec): ref,
 | 
						|
                   filter(lambda (ref,rec): rec['device'] == device,
 | 
						|
                          self.__pifs.items()))
 | 
						|
 | 
						|
    def get_pif_by_bridge(self, bridge):
 | 
						|
        networks = map(lambda (ref,rec): ref,
 | 
						|
                       filter(lambda (ref,rec): rec['bridge'] == bridge,
 | 
						|
                              self.__networks.items()))
 | 
						|
        if len(networks) == 0:
 | 
						|
            raise Error("No matching network \"%s\"" % bridge)
 | 
						|
 | 
						|
        answer = None
 | 
						|
        for network in networks:
 | 
						|
            nwrec = self.get_network_record(network)
 | 
						|
            for pif in nwrec['PIFs']:
 | 
						|
                pifrec = self.get_pif_record(pif)
 | 
						|
                if answer:
 | 
						|
                    raise Error("Multiple PIFs on host for network %s" % (bridge))
 | 
						|
                answer = pif
 | 
						|
        if not answer:
 | 
						|
            raise Error("No PIF on host for network %s" % (bridge))
 | 
						|
        return answer
 | 
						|
 | 
						|
    def get_pif_record(self, pif):
 | 
						|
        if self.__pifs.has_key(pif):
 | 
						|
            return self.__pifs[pif]
 | 
						|
        raise Error("Unknown PIF \"%s\"" % pif)
 | 
						|
    def get_all_pifs(self):
 | 
						|
        return self.__pifs
 | 
						|
    def pif_exists(self, pif):
 | 
						|
        return self.__pifs.has_key(pif)
 | 
						|
 | 
						|
    def get_management_pif(self):
 | 
						|
        """ Returns the management pif on host
 | 
						|
        """
 | 
						|
        all = self.get_all_pifs()
 | 
						|
        for pif in all:
 | 
						|
            pifrec = self.get_pif_record(pif)
 | 
						|
            if pifrec['management']: return pif
 | 
						|
        return None
 | 
						|
 | 
						|
    def get_network_record(self, network):
 | 
						|
        if self.__networks.has_key(network):
 | 
						|
            return self.__networks[network]
 | 
						|
        raise Error("Unknown network \"%s\"" % network)
 | 
						|
 | 
						|
    def get_bond_record(self, bond):
 | 
						|
        if self.__bonds.has_key(bond):
 | 
						|
            return self.__bonds[bond]
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
    def get_vlan_record(self, vlan):
 | 
						|
        if self.__vlans.has_key(vlan):
 | 
						|
            return self.__vlans[vlan]
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
#
 | 
						|
#
 | 
						|
#
 | 
						|
 | 
						|
def ethtool_settings(oc):
 | 
						|
    settings = []
 | 
						|
    if oc.has_key('ethtool-speed'):
 | 
						|
        val = oc['ethtool-speed']
 | 
						|
        if val in ["10", "100", "1000"]:
 | 
						|
            settings += ['speed', val]
 | 
						|
        else:
 | 
						|
            log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
 | 
						|
    if oc.has_key('ethtool-duplex'):
 | 
						|
        val = oc['ethtool-duplex']
 | 
						|
        if val in ["10", "100", "1000"]:
 | 
						|
            settings += ['duplex', 'val']
 | 
						|
        else:
 | 
						|
            log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
 | 
						|
    if oc.has_key('ethtool-autoneg'):
 | 
						|
        val = oc['ethtool-autoneg']
 | 
						|
        if val in ["true", "on"]:
 | 
						|
            settings += ['autoneg', 'on']
 | 
						|
        elif val in ["false", "off"]:
 | 
						|
            settings += ['autoneg', 'off']
 | 
						|
        else:
 | 
						|
            log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
 | 
						|
    offload = []
 | 
						|
    for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
 | 
						|
        if oc.has_key("ethtool-" + opt):
 | 
						|
            val = oc["ethtool-" + opt]
 | 
						|
            if val in ["true", "on"]:
 | 
						|
                offload += [opt, 'on']
 | 
						|
            elif val in ["false", "off"]:
 | 
						|
                offload += [opt, 'off']
 | 
						|
            else:
 | 
						|
                log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
 | 
						|
    return settings,offload
 | 
						|
 | 
						|
# By default the MTU is taken from the Network.MTU setting for VIF,
 | 
						|
# PIF and Bridge. However it is possible to override this by using
 | 
						|
# {VIF,PIF,Network}.other-config:mtu.
 | 
						|
#
 | 
						|
# type parameter is a string describing the object that the oc parameter
 | 
						|
# is from. e.g. "PIF", "Network" 
 | 
						|
def mtu_setting(nw, type, oc):
 | 
						|
    mtu = None
 | 
						|
 | 
						|
    nwrec = db().get_network_record(nw)
 | 
						|
    if nwrec.has_key('MTU'):
 | 
						|
        mtu = nwrec['MTU']
 | 
						|
    else:
 | 
						|
        mtu = "1500"
 | 
						|
        
 | 
						|
    if oc.has_key('mtu'):
 | 
						|
        log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
 | 
						|
            (nwrec['bridge'], type, mtu))
 | 
						|
        mtu = oc['mtu']
 | 
						|
 | 
						|
    if mtu is not None:
 | 
						|
        try:
 | 
						|
            int(mtu)      # Check that the value is an integer
 | 
						|
            return mtu
 | 
						|
        except ValueError, x:
 | 
						|
            log("Invalid value for mtu = %s" % mtu)
 | 
						|
 | 
						|
    return None
 | 
						|
 | 
						|
#
 | 
						|
# IP Network Devices -- network devices with IP configuration
 | 
						|
#
 | 
						|
def pif_ipdev_name(pif):
 | 
						|
    """Return the ipdev name associated with pif"""
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
    nwrec = db().get_network_record(pifrec['network'])
 | 
						|
 | 
						|
    if nwrec['bridge']:
 | 
						|
        # TODO: sanity check that nwrec['bridgeless'] != 'true'
 | 
						|
        return nwrec['bridge']
 | 
						|
    else:
 | 
						|
        # TODO: sanity check that nwrec['bridgeless'] == 'true'
 | 
						|
        return pif_netdev_name(pif)
 | 
						|
 | 
						|
#
 | 
						|
# Bare Network Devices -- network devices without IP configuration
 | 
						|
#
 | 
						|
 | 
						|
def netdev_exists(netdev):
 | 
						|
    return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
 | 
						|
 | 
						|
def pif_netdev_name(pif):
 | 
						|
    """Get the netdev name for a PIF."""
 | 
						|
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
 | 
						|
    if pif_is_vlan(pif):
 | 
						|
        return "%(device)s.%(VLAN)s" % pifrec
 | 
						|
    else:
 | 
						|
        return pifrec['device']
 | 
						|
 | 
						|
#
 | 
						|
# Bridges
 | 
						|
#
 | 
						|
 | 
						|
def pif_is_bridged(pif):
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
    nwrec = db().get_network_record(pifrec['network'])
 | 
						|
 | 
						|
    if nwrec['bridge']:
 | 
						|
        # TODO: sanity check that nwrec['bridgeless'] != 'true'
 | 
						|
        return True
 | 
						|
    else:
 | 
						|
        # TODO: sanity check that nwrec['bridgeless'] == 'true'
 | 
						|
        return False
 | 
						|
 | 
						|
def pif_bridge_name(pif):
 | 
						|
    """Return the bridge name of a pif.
 | 
						|
 | 
						|
    PIF must be a bridged PIF."""
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
 | 
						|
    nwrec = db().get_network_record(pifrec['network'])
 | 
						|
 | 
						|
    if nwrec['bridge']:
 | 
						|
        return nwrec['bridge']
 | 
						|
    else:
 | 
						|
        raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
 | 
						|
 | 
						|
#
 | 
						|
# Bonded PIFs
 | 
						|
#
 | 
						|
def pif_is_bond(pif):
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
 | 
						|
    return len(pifrec['bond_master_of']) > 0
 | 
						|
 | 
						|
def pif_get_bond_masters(pif):
 | 
						|
    """Returns a list of PIFs which are bond masters of this PIF"""
 | 
						|
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
 | 
						|
    bso = pifrec['bond_slave_of']
 | 
						|
 | 
						|
    # bond-slave-of is currently a single reference but in principle a
 | 
						|
    # PIF could be a member of several bonds which are not
 | 
						|
    # concurrently attached. Be robust to this possibility.
 | 
						|
    if not bso or bso == "OpaqueRef:NULL":
 | 
						|
        bso = []
 | 
						|
    elif not type(bso) == list:
 | 
						|
        bso = [bso]
 | 
						|
 | 
						|
    bondrecs = [db().get_bond_record(bond) for bond in bso]
 | 
						|
    bondrecs = [rec for rec in bondrecs if rec]
 | 
						|
 | 
						|
    return [bond['master'] for bond in bondrecs]
 | 
						|
 | 
						|
def pif_get_bond_slaves(pif):
 | 
						|
    """Returns a list of PIFs which make up the given bonded pif."""
 | 
						|
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
 | 
						|
    bmo = pifrec['bond_master_of']
 | 
						|
    if len(bmo) > 1:
 | 
						|
        raise Error("Bond-master-of contains too many elements")
 | 
						|
 | 
						|
    if len(bmo) == 0:
 | 
						|
        return []
 | 
						|
 | 
						|
    bondrec = db().get_bond_record(bmo[0])
 | 
						|
    if not bondrec:
 | 
						|
        raise Error("No bond record for bond master PIF")
 | 
						|
 | 
						|
    return bondrec['slaves']
 | 
						|
 | 
						|
#
 | 
						|
# VLAN PIFs
 | 
						|
#
 | 
						|
 | 
						|
def pif_is_vlan(pif):
 | 
						|
    return db().get_pif_record(pif)['VLAN'] != '-1'
 | 
						|
 | 
						|
def pif_get_vlan_slave(pif):
 | 
						|
    """Find the PIF which is the VLAN slave of pif.
 | 
						|
 | 
						|
Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
 | 
						|
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
 | 
						|
    vlan = pifrec['VLAN_master_of']
 | 
						|
    if not vlan or vlan == "OpaqueRef:NULL":
 | 
						|
        raise Error("PIF is not a VLAN master")
 | 
						|
 | 
						|
    vlanrec = db().get_vlan_record(vlan)
 | 
						|
    if not vlanrec:
 | 
						|
        raise Error("No VLAN record found for PIF")
 | 
						|
 | 
						|
    return vlanrec['tagged_PIF']
 | 
						|
 | 
						|
def pif_get_vlan_masters(pif):
 | 
						|
    """Returns a list of PIFs which are VLANs on top of the given pif."""
 | 
						|
 | 
						|
    pifrec = db().get_pif_record(pif)
 | 
						|
    vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
 | 
						|
    return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
 | 
						|
 | 
						|
#
 | 
						|
# Datapath base class
 | 
						|
#
 | 
						|
 | 
						|
class Datapath(object):
 | 
						|
    """Object encapsulating the actions necessary to (de)configure the
 | 
						|
       datapath for a given PIF. Does not include configuration of the
 | 
						|
       IP address on the ipdev.
 | 
						|
    """
 | 
						|
    
 | 
						|
    def __init__(self, pif):
 | 
						|
        self._pif = pif
 | 
						|
 | 
						|
    def configure_ipdev(self, cfg):
 | 
						|
        """Write ifcfg TYPE field for an IPdev, plus any type specific
 | 
						|
           fields to cfg
 | 
						|
        """
 | 
						|
        raise NotImplementedError        
 | 
						|
 | 
						|
    def preconfigure(self, parent):
 | 
						|
        """Prepare datapath configuration for PIF, but do not actually
 | 
						|
           apply any changes.
 | 
						|
 | 
						|
           Any configuration files should be attached to parent.
 | 
						|
        """
 | 
						|
        raise NotImplementedError
 | 
						|
    
 | 
						|
    def bring_down_existing(self):
 | 
						|
        """Tear down any existing network device configuration which
 | 
						|
           needs to be undone in order to bring this PIF up.
 | 
						|
        """
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
    def configure(self):
 | 
						|
        """Apply the configuration prepared in the preconfigure stage.
 | 
						|
 | 
						|
           Should assume any configuration files changed attached in
 | 
						|
           the preconfigure stage are applied and bring up the
 | 
						|
           necesary devices to provide the datapath for the
 | 
						|
           PIF.
 | 
						|
 | 
						|
           Should not bring up the IPdev.
 | 
						|
        """
 | 
						|
        raise NotImplementedError
 | 
						|
    
 | 
						|
    def post(self):
 | 
						|
        """Called after the IPdev has been brought up.
 | 
						|
 | 
						|
           Should do any final setup, including reinstating any
 | 
						|
           devices which were taken down in the bring_down_existing
 | 
						|
           hook.
 | 
						|
        """
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
    def bring_down(self):
 | 
						|
        """Tear down and deconfigure the datapath. Should assume the
 | 
						|
           IPdev has already been brought down.
 | 
						|
        """
 | 
						|
        raise NotImplementedError
 | 
						|
        
 | 
						|
def DatapathFactory(pif):
 | 
						|
    # XXX Need a datapath object for bridgeless PIFs
 | 
						|
 | 
						|
    try:
 | 
						|
        network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
 | 
						|
        network_backend = network_conf.readline().strip()
 | 
						|
        network_conf.close()                
 | 
						|
    except Exception, e:
 | 
						|
        raise Error("failed to determine network backend:" + e)
 | 
						|
    
 | 
						|
    if network_backend == "bridge":
 | 
						|
        from InterfaceReconfigureBridge import DatapathBridge
 | 
						|
        return DatapathBridge(pif)
 | 
						|
    elif network_backend in ["openvswitch", "vswitch"]:
 | 
						|
        from InterfaceReconfigureVswitch import DatapathVswitch
 | 
						|
        return DatapathVswitch(pif)
 | 
						|
    else:
 | 
						|
        raise Error("unknown network backend %s" % network_backend)
 |