mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +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)
 |