From 1a62d37eb4b0a187c717511a75bd6c20f1fe46a7 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Wed, 28 Nov 2007 16:50:46 +0000 Subject: [PATCH] Netmask, gateway, OEM file fixes --- Makefile | 1 + XSConsoleAuth.py | 2 +- XSConsoleConfig.py | 4 +- XSConsoleData.py | 83 ++++++++++++++------ XSConsoleDataUtils.py | 162 ++++++++++++++++++++++++++++++++++++++ XSConsoleDialogues.py | 7 +- XSConsoleRootDialogue.py | 19 +++-- XSConsoleState.py | 4 +- XSConsoleUtils.py | 163 +-------------------------------------- 9 files changed, 245 insertions(+), 200 deletions(-) create mode 100644 XSConsoleDataUtils.py diff --git a/Makefile b/Makefile index c538bc9..9f3534e 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ SCRIPTS += XSConsoleBases.py SCRIPTS += XSConsoleConfig.py SCRIPTS += XSConsoleCurses.py SCRIPTS += XSConsoleData.py +SCRIPTS += XSConsoleDataUtils.py SCRIPTS += XSConsoleDialoguePane.py SCRIPTS += XSConsoleDialogues.py SCRIPTS += XSConsoleFields.py diff --git a/XSConsoleAuth.py b/XSConsoleAuth.py index 6963433..68dc49d 100755 --- a/XSConsoleAuth.py +++ b/XSConsoleAuth.py @@ -1,5 +1,5 @@ -import os, time +import os, re, time from XSConsoleBases import * from XSConsoleLang import * diff --git a/XSConsoleConfig.py b/XSConsoleConfig.py index 209f226..a4469f2 100644 --- a/XSConsoleConfig.py +++ b/XSConsoleConfig.py @@ -1,5 +1,5 @@ -import os +import os, sys class Config: instance = None @@ -46,6 +46,6 @@ def FTPServer(self): return self.ftpserver # Import a more specific configuration if available -if os.path.isfile('XSConsoleConfigOEM.py'): +if os.path.isfile(sys.path[0]+'/XSConsoleConfigOEM.py'): import XSConsoleConfigOEM diff --git a/XSConsoleData.py b/XSConsoleData.py index 5227d37..f4d2939 100755 --- a/XSConsoleData.py +++ b/XSConsoleData.py @@ -1,11 +1,13 @@ -# import socket, fcntl, struct, os + import XenAPI + import commands, re, sys, tempfile from pprint import pprint from XSConsoleAuth import * from XSConsoleLang import * from XSConsoleState import * +from XSConsoleUtils import * class DataMethod: def __init__(self, inSend, inName): @@ -464,31 +466,66 @@ def Ping(self, inDest): (status, output) = commands.getstatusoutput(command) return (status == 0, output) - def ManagementGateway(self): - retVal = None + def ManagementIP(self, inDefault = None): + retVal = inDefault + + retVal = self.host.address(retVal) + + return retVal + + def ManagementNetmask(self, inDefault = None): + retVal = inDefault # FIXME: Address should come from API, but not available at present. For DHCP this is just a guess at the gateway address - if len(self.derived.managementpifs()) == 0: - # No management i/f configured - pass - else: - for pif in self.derived.managementpifs(): - if pif['ip_configuration_mode'].lower().startswith('static'): - # For static IP the API address is correct - retVal = pif['gateway'] - elif pif['ip_configuration_mode'].lower().startswith('dhcp'): - # For DHCP, find the gateway address by parsing the output from the 'route' command - if 'bridge' in pif['network']: - device = pif['network']['bridge'] - else: - device = pif['device'] - routeRE = r'([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+UG\s+\d+\s+\d+\s+\d+\s+'+device + for pif in self.derived.managementpifs([]): + if pif['ip_configuration_mode'].lower().startswith('static'): + # For static IP the API address is correct + retVal = pif['netmask'] + elif pif['ip_configuration_mode'].lower().startswith('dhcp'): + # For DHCP, find the gateway address by parsing the output from the 'route' command + if 'bridge' in pif['network']: + device = pif['network']['bridge'] + else: + device = pif['device'] + + device = ShellUtils.MakeSafeParam(device) + + ipre = r'[0-9a-f.:]+' + ifRE = re.compile(r'\s*inet\s+addr\s*:'+ipre+'\s+bcast\s*:\s*'+ipre+r'\s+mask\s*:\s*('+ipre+r')\s*$', + re.IGNORECASE) + + ifconfig = commands.getoutput("/sbin/ifconfig '"+device+"'").split("\n") + for line in ifconfig: + match = ifRE.match(line) + if match: + retVal = match.group(1) + break + + return retVal + + def ManagementGateway(self, inDefault = None): + retVal = inDefault - routes = commands.getoutput("/sbin/route -n").split("\n") - for line in routes: - m = re.match(routeRE, line) - if m: - retVal = m.group(2) + # FIXME: Address should come from API, but not available at present. For DHCP this is just a guess at the gateway address + for pif in self.derived.managementpifs([]): + if pif['ip_configuration_mode'].lower().startswith('static'): + # For static IP the API address is correct + retVal = pif['gateway'] + elif pif['ip_configuration_mode'].lower().startswith('dhcp'): + # For DHCP, find the gateway address by parsing the output from the 'route' command + if 'bridge' in pif['network']: + device = pif['network']['bridge'] + else: + device = pif['device'] + routeRE = re.compile(r'([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+UG\s+\d+\s+\d+\s+\d+\s+'+device, + re.IGNORECASE) + + routes = commands.getoutput("/sbin/route -n").split("\n") + for line in routes: + match = routeRE.match(line) + if match: + retVal = match.group(2) + break return retVal diff --git a/XSConsoleDataUtils.py b/XSConsoleDataUtils.py new file mode 100644 index 0000000..71263d5 --- /dev/null +++ b/XSConsoleDataUtils.py @@ -0,0 +1,162 @@ + +import os, re, tempfile + +from XSConsoleBases import * +from XSConsoleData import * +from XSConsoleLang import * + +class FileUtils: + @classmethod + def PatchDeviceList(cls): + retVal = [] + + for pbd in Data.Inst().host.PBDs([]): + sr = pbd.get('SR', {}) + for vdi in sr.get('VDIs', []): + nameLabel = vdi.get('name_label', Lang('Unknown')) + if re.match(r'(SCSI|USB)', nameLabel): # Skip if not USB or SCSI + match = True + while match: + match = re.match(r'(.*):0$', nameLabel) + if match: + # Remove multiple trailing :0 + nameLabel = match.group(1) + nameDesc = vdi.get('name_description', Lang('Unknown device')) + match = re.match(r'(.*)\srev\b', nameDesc) + if match: + # Remove revision information + nameDesc = match.group(1) + + deviceSize = int(vdi.get('physical_utilisation', 0)) + if deviceSize < 0: + deviceSize = int(vdi.get('virtual_size', 0)) + + nameSize = cls.SizeString(deviceSize) + + name = "%-50s%10.10s%10.10s" % (nameDesc[:50], nameLabel[:10], nameSize[:10]) + retVal.append(Struct(name = name, vdi = vdi)) + + retVal.sort(lambda x, y : cmp(x.vdi['name_label'], y.vdi['name_label'])) + + return retVal + + @classmethod + def AssertSafePath(cls, inPath): + if not re.match(r'[-A-Za-z0-9/._~]*$', inPath): + raise Exception("Invalid characters in path '"+inPath+"'") + + @classmethod + def SizeString(cls, inSizeOrFilename, inDefault = None): + try: + if isinstance(inSizeOrFilename, str): + fileSize = os.path.getsize(inSizeOrFilename) + else: + fileSize = inSizeOrFilename + + # Using these values gives the expected values for USB sticks + if fileSize >= 1000000000: # 1GB + if fileSize < 10000000000: # 10GB + retVal = ('%.1f' % (int(fileSize / 100000000) / 10.0)) + Lang('GB') # e.g. 2.3GB + else: + retVal = str(int(fileSize / 1000000000))+Lang('GB') + elif fileSize >= 2000000: + retVal = str(int(fileSize / 1000000))+Lang('MB') + elif fileSize >= 2000: + retVal = str(int(fileSize / 1000))+Lang('KB') + else: + retVal = str(int(fileSize)) + + except Exception, e: + retVal = FirstValue(inDefault, '') + + return retVal + +class MountVDI: + def __init__(self, inVDI, inMode = None): + self.mountPoint = None + self.vbd = None + self.mode = FirstValue(inMode, 'ro') + + # Keep records of whether we created and plugged the VBD, for undoing it later + self.createdVBD = False + self.pluggedVBD = False + self.mountedVBD = False + data = Data.Inst() + data.Update() # Get current device list + + try: + vbdFound = None + allowedVBDs = data.derived.dom0_vm.allowed_VBD_devices([]) + for vbd in inVDI.get('VBDs', []): + if vbd['userdevice'] in allowedVBDs: + # Already mounted in userspace, so reuse + vbdFound = vbd + break + + if vbdFound is not None: + self.vbd = vbdFound + else: + deviceNum = data.derived.dom0_vm.allowed_VBD_devices([])[-1] # Highest allowed device number + self.vbd = data.CreateVBD(data.derived.dom0_vm(), inVDI, deviceNum, self.mode) + self.createdVBD = True + + if not self.vbd['currently_attached']: + self.vbd = data.PlugVBD(self.vbd) + self.pluggedVBD = True + + self.mountDev = '/dev/'+self.vbd['device'] + FileUtils.AssertSafePath(self.mountDev) + self.mountPoint = tempfile.mktemp(".xsconsole") + if not os.path.isdir(self.mountPoint): + os.mkdir(self.mountPoint, 0700) + + status, output = commands.getstatusoutput("/bin/mount -t auto -o " + self.mode + ' ' +self.mountDev+" "+self.mountPoint + " 2>&1") + if status != 0: + raise Exception(output) + + self.mountedVBD = True + + except Exception, e: + try: + self.Unmount() + except Exception: + pass # Report the original exception, not this one + raise e + + def Scan(self, inRegExp = None, inNumToReturn = None): + retVal = [] + numToReturn = FirstValue(inNumToReturn, 10) + regExp = re.compile(FirstValue(inRegExp, r'.*')) + for root, dirs, files in os.walk(self.mountPoint): + if len(retVal) >= numToReturn: + break + for filename in files: + if regExp.match(filename): + retVal.append(os.path.join(root, filename)[len(self.mountPoint)+1:]) + if len(retVal) >= numToReturn: + break + + return retVal + + def Unmount(self): + status = 0 + if self.mountedVBD: + status, output = commands.getstatusoutput("/bin/umount "+self.mountPoint + " 2>&1") + os.rmdir(self.mountPoint) + self.mountedVBD = False + if self.pluggedVBD: + self.vbd = Data.Inst().UnplugVBD(self.vbd) + self.pluggedVBD = False + if self.createdVBD: + Data.Inst().DestroyVBD(self.vbd) + self.createdVBD = False + if status != 0: + raise Exception(output) + + def MountedPath(self, inLeafname): + return self.mountPoint + '/' + inLeafname + + def SizeString(self, inFilename, inDefault = None): + return FileUtils.SizeString(self.MountedPath(inFilename), inDefault) + + diff --git a/XSConsoleDialogues.py b/XSConsoleDialogues.py index 496291e..5ecb511 100755 --- a/XSConsoleDialogues.py +++ b/XSConsoleDialogues.py @@ -3,6 +3,7 @@ from XSConsoleBases import * from XSConsoleCurses import * from XSConsoleData import * +from XSConsoleDataUtils import * from XSConsoleDialoguePane import * from XSConsoleFields import * from XSConsoleLang import * @@ -676,9 +677,9 @@ def HandleCommit(self, inValues): self.layout.Refresh() self.layout.DoUpdate() - hostRef = ShellUtils.AssertSafeParam(Data.Inst().host.uuid('')) - destURL = ShellUtils.AssertSafeParam(inValues['destination']) - proxy = ShellUtils.AssertSafeParam(inValues['proxy']) + hostRef = ShellUtils.MakeSafeParam(Data.Inst().host.uuid('')) + destURL = ShellUtils.MakeSafeParam(inValues['destination']) + proxy = ShellUtils.MakeSafeParam(inValues['proxy']) command = "/opt/xensource/bin/xe host-bugreport-upload host='"+hostRef+"' url='"+destURL+"'" if proxy != '': diff --git a/XSConsoleRootDialogue.py b/XSConsoleRootDialogue.py index 1441f9f..3c0af30 100644 --- a/XSConsoleRootDialogue.py +++ b/XSConsoleRootDialogue.py @@ -1,4 +1,6 @@ +import re + from XSConsoleAuth import * from XSConsoleBases import * from XSConsoleConfig import * @@ -36,11 +38,9 @@ def UpdateFieldsSTATUS(self, inPane): if len(data.derived.managementpifs([])) == 0: inPane.AddTextField(Lang("")) else: - for pif in data.derived.managementpifs(): - inPane.AddStatusField(Lang('IP address', 16), data.host.address()) # FIXME: should come from pif - if pif['ip_configuration_mode'].lower().startswith('static'): - inPane.AddStatusField(Lang('Netmask', 16), pif['netmask']) - inPane.AddStatusField(Lang('Gateway', 16), pif['gateway']) + inPane.AddStatusField(Lang('IP address', 16), data.ManagementIP('')) + inPane.AddStatusField(Lang('Netmask', 16), data.ManagementNetmask('')) + inPane.AddStatusField(Lang('Gateway', 16), data.ManagementGateway('')) inPane.NewLine() def UpdateFieldsPROPERTIES(self, inPane): @@ -240,12 +240,11 @@ def UpdateFieldsSELECTNIC(self, inPane): for pif in data.derived.managementpifs([]): inPane.AddStatusField(Lang('Device', 16), pif['device']) inPane.AddStatusField(Lang('MAC Address', 16), pif['MAC']) - inPane.AddStatusField(Lang('Assigned IP', 16), data.host.address()) # FIXME: should come from pif inPane.AddStatusField(Lang('DHCP/Static IP', 16), pif['ip_configuration_mode']) - if pif['ip_configuration_mode'].lower().startswith('static'): - # inPane.AddStatusField(Lang('IP Address', 16), pif['IP']) - inPane.AddStatusField(Lang('Netmask', 16), pif['netmask']) - inPane.AddStatusField(Lang('Gateway', 16), pif['gateway']) + + inPane.AddStatusField(Lang('IP address', 16), data.ManagementIP('')) + inPane.AddStatusField(Lang('Netmask', 16), data.ManagementNetmask('')) + inPane.AddStatusField(Lang('Gateway', 16), data.ManagementGateway('')) inPane.NewLine() inPane.AddTitleField(Lang("NIC Vendor")) diff --git a/XSConsoleState.py b/XSConsoleState.py index 14133ba..19ad24c 100644 --- a/XSConsoleState.py +++ b/XSConsoleState.py @@ -1,5 +1,5 @@ -import copy, os, pickle +import re, os, pickle from XSConsoleBases import * from XSConsoleLang import * @@ -8,7 +8,7 @@ class State: instance = None savePath = '/etc/xsconsole' saveLeafname = 'state.txt' - thisVersion = 1 + thisVersion = 2 def __init__(self): self.version = self.thisVersion diff --git a/XSConsoleUtils.py b/XSConsoleUtils.py index 03ec609..7634bbd 100644 --- a/XSConsoleUtils.py +++ b/XSConsoleUtils.py @@ -1,167 +1,12 @@ -import tempfile +import re -from XSConsoleBases import * -from XSConsoleData import * -from XSConsoleLang import * +# Utils that need to access Data must go in DataUtils, +# and XSConsoleData can't use anything in here class ShellUtils: @classmethod - def AssertSafeParam(cls, inParam): + def MakeSafeParam(cls, inParam): if not re.match(r'[-A-Za-z0-9/._~:]*$', inParam): raise Exception("Invalid characters in parameter '"+inParam+"'") return inParam - -class FileUtils: - @classmethod - def PatchDeviceList(cls): - retVal = [] - - for pbd in Data.Inst().host.PBDs([]): - sr = pbd.get('SR', {}) - for vdi in sr.get('VDIs', []): - nameLabel = vdi.get('name_label', Lang('Unknown')) - if re.match(r'(SCSI|USB)', nameLabel): # Skip if not USB or SCSI - match = True - while match: - match = re.match(r'(.*):0$', nameLabel) - if match: - # Remove multiple trailing :0 - nameLabel = match.group(1) - nameDesc = vdi.get('name_description', Lang('Unknown device')) - match = re.match(r'(.*)\srev\b', nameDesc) - if match: - # Remove revision information - nameDesc = match.group(1) - - deviceSize = int(vdi.get('physical_utilisation', 0)) - if deviceSize < 0: - deviceSize = int(vdi.get('virtual_size', 0)) - - nameSize = cls.SizeString(deviceSize) - - name = "%-50s%10.10s%10.10s" % (nameDesc[:50], nameLabel[:10], nameSize[:10]) - retVal.append(Struct(name = name, vdi = vdi)) - - retVal.sort(lambda x, y : cmp(x.vdi['name_label'], y.vdi['name_label'])) - - return retVal - - @classmethod - def AssertSafePath(cls, inPath): - if not re.match(r'[-A-Za-z0-9/._~]*$', inPath): - raise Exception("Invalid characters in path '"+inPath+"'") - - @classmethod - def SizeString(cls, inSizeOrFilename, inDefault = None): - try: - if isinstance(inSizeOrFilename, str): - fileSize = os.path.getsize(inSizeOrFilename) - else: - fileSize = inSizeOrFilename - - # Using these values gives the expected values for USB sticks - if fileSize >= 1000000000: # 1GB - if fileSize < 10000000000: # 10GB - retVal = ('%.1f' % (int(fileSize / 100000000) / 10.0)) + Lang('GB') # e.g. 2.3GB - else: - retVal = str(int(fileSize / 1000000000))+Lang('GB') - elif fileSize >= 2000000: - retVal = str(int(fileSize / 1000000))+Lang('MB') - elif fileSize >= 2000: - retVal = str(int(fileSize / 1000))+Lang('KB') - else: - retVal = str(int(fileSize)) - - except Exception, e: - retVal = FirstValue(inDefault, '') - - return retVal - -class MountVDI: - def __init__(self, inVDI, inMode = None): - self.mountPoint = None - self.vbd = None - self.mode = FirstValue(inMode, 'ro') - - # Keep records of whether we created and plugged the VBD, for undoing it later - self.createdVBD = False - self.pluggedVBD = False - self.mountedVBD = False - data = Data.Inst() - data.Update() # Get current device list - - try: - vbdFound = None - allowedVBDs = data.derived.dom0_vm.allowed_VBD_devices([]) - for vbd in inVDI.get('VBDs', []): - if vbd['userdevice'] in allowedVBDs: - # Already mounted in userspace, so reuse - vbdFound = vbd - break - - if vbdFound is not None: - self.vbd = vbdFound - else: - deviceNum = data.derived.dom0_vm.allowed_VBD_devices([])[-1] # Highest allowed device number - self.vbd = data.CreateVBD(data.derived.dom0_vm(), inVDI, deviceNum, self.mode) - self.createdVBD = True - - if not self.vbd['currently_attached']: - self.vbd = data.PlugVBD(self.vbd) - self.pluggedVBD = True - - self.mountDev = '/dev/'+self.vbd['device'] - FileUtils.AssertSafePath(self.mountDev) - self.mountPoint = tempfile.mktemp(".xsconsole") - if not os.path.isdir(self.mountPoint): - os.mkdir(self.mountPoint, 0700) - - status, output = commands.getstatusoutput("/bin/mount -t auto -o " + self.mode + ' ' +self.mountDev+" "+self.mountPoint + " 2>&1") - if status != 0: - raise Exception(output) - - self.mountedVBD = True - - except Exception, e: - try: - self.Unmount() - except Exception: - pass # Report the original exception, not this one - raise e - - def Scan(self, inRegExp = None, inNumToReturn = None): - retVal = [] - numToReturn = FirstValue(inNumToReturn, 10) - regExp = re.compile(FirstValue(inRegExp, r'.*')) - for root, dirs, files in os.walk(self.mountPoint): - if len(retVal) >= numToReturn: - break - for filename in files: - if regExp.match(filename): - retVal.append(os.path.join(root, filename)[len(self.mountPoint)+1:]) - if len(retVal) >= numToReturn: - break - - return retVal - - def Unmount(self): - status = 0 - if self.mountedVBD: - status, output = commands.getstatusoutput("/bin/umount "+self.mountPoint + " 2>&1") - os.rmdir(self.mountPoint) - self.mountedVBD = False - if self.pluggedVBD: - self.vbd = Data.Inst().UnplugVBD(self.vbd) - self.pluggedVBD = False - if self.createdVBD: - Data.Inst().DestroyVBD(self.vbd) - self.createdVBD = False - if status != 0: - raise Exception(output) - - def MountedPath(self, inLeafname): - return self.mountPoint + '/' + inLeafname - - def SizeString(self, inFilename, inDefault = None): - return FileUtils.SizeString(self.MountedPath(inFilename), inDefault)