-class Error(Exception):
- def __init__(self, msg):
- Exception.__init__(self)
- self.msg = msg
-
-#
-# 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, fname, path="/etc/sysconfig/network-scripts"):
-
- self.__state = self.__STATE['OPEN']
- self.__fname = fname
- self.__children = []
-
- if debug_mode():
- dirname = output_directory
- else:
- dirname = path
-
- self.__path = os.path.join(dirname, fname)
- self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
- self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
- self.__unlink = False
-
- 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 unlink(self):
- if self.__state != self.__STATE['OPEN']:
- raise Error("Attempt to unlink file in state %s" % self.__state)
- self.__unlink = True
- self.__f.close()
- self.__state = self.__STATE['NOT-APPLIED']
-
- 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.__fname)
-
- # 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))
- if not self.__unlink:
- os.link(self.__newpath, self.__path)
- else:
- pass # implicit unlink of original file
-
- # 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.__fname)
-
- # 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.__fname)
-
- 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),
- '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)),
- }
-
-class DatabaseCache(object):
- def __read_xensource_inventory(self):
- filename = "/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 in session.xenapi.VLAN.get_all():
- rec = session.xenapi.VLAN.get_record(v)
- 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 in session.xenapi.Bond.get_all():
- rec = session.xenapi.Bond.get_record(b)
- 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 in session.xenapi.network.get_all():
- rec = session.xenapi.network.get_record(n)
- 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)]
- 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:
- 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(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\" (get_pif_record)" % 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_all_networks(self):
- return self.__networks
-
- 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
-