From e2edd13f8c0dbe87f05cc2f7db3f89d7455327b2 Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 24 Nov 2023 16:09:51 +0000 Subject: [PATCH 1/2] Fix the 1st commit of #17: incompatible retcode of subprocess.getstatusoutput() Fix the 1st commit of #17 which replaced commands.getstatusoutput() with subprocess.getstatusoutput() without taking the change of the status code into account: Unfortunately, the Python3 developers broke the compatibility: The return code changes: python2 -c 'import commands ; print( commands.getstatusoutput("false"))' (256, '') python3 -c 'import subprocess; print(subprocess.getstatusoutput("false"))' (1, '') With commands.getstatusoutput(), you had to use this to get the actual exit code: status = os.WEXITSTATUS(status) These calls have to be removed because now they just shift away the error code: As shown at https://github.com/benjaminp/six/issues/207, the operation is just `status >> 8` Luckily, the status code is checked to against 0 at most places, so there is no change for these checks. There is only one location where a bit is checked. Fix this location too. Also, that commit did not take into account that subprocess.get*output do not exist in Python2, which goes against the directive by Andrew in PR #16 where he requires that we keep Python2 working: The current master branch works neither for Python2, nor Python3 - fix this breakage in the 2nd commit. Signed-off-by: Bernhard Kaindl --- XSConsoleDataUtils.py | 2 +- plugins-base/XSFeatureDRBackup.py | 1 - plugins-base/XSFeatureDRRestore.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/XSConsoleDataUtils.py b/XSConsoleDataUtils.py index 76d60b0..423a338 100644 --- a/XSConsoleDataUtils.py +++ b/XSConsoleDataUtils.py @@ -382,7 +382,7 @@ def HandleMountFailure(self, inStatus, inOutput): # Entered after self.Unmount has run if self.vdi['SR']['type'] != 'udev' or self.vdi['SR']['content_type'] != 'disk': # Take special action for USB devices only, i.e. don't reformat SCSI disks, etc. - if inStatus == 8192: # Return code for empty CD drive + if inStatus == 32: # 32 is the mount(8) return code for mount failure, assuming empty CD drive raise Exception(Lang("Drive is empty")) raise Exception(inOutput) diff --git a/plugins-base/XSFeatureDRBackup.py b/plugins-base/XSFeatureDRBackup.py index e9350c6..9450321 100644 --- a/plugins-base/XSFeatureDRBackup.py +++ b/plugins-base/XSFeatureDRBackup.py @@ -39,7 +39,6 @@ def DoAction(self, inSR): command = "%s/xe-backup-metadata -n -u %s" % (Config.Inst().HelperPath(), sr_uuid) status, output = subprocess.getstatusoutput(command) - status = os.WEXITSTATUS(status) initalize_vdi = "" if status == 3: initalize_vdi = "-c" diff --git a/plugins-base/XSFeatureDRRestore.py b/plugins-base/XSFeatureDRRestore.py index a8aedb6..afe984d 100644 --- a/plugins-base/XSFeatureDRRestore.py +++ b/plugins-base/XSFeatureDRRestore.py @@ -93,7 +93,6 @@ def HandleMethodChoice(self, inChoice, dryRun): Layout.Inst().TransientBanner(Lang("Restoring VM Metadata. This may take a few minutes...")) command = "%s/xe-restore-metadata -y %s -u %s -x %s -d %s -m %s" % (Config.Inst().HelperPath(), dry_flag, self.sr_uuid, self.vdi_uuid, self.chosen_date, chosen_mode) status, output = subprocess.getstatusoutput(command) - status = os.WEXITSTATUS(status) Layout.Inst().PopDialogue() if status == 0: Layout.Inst().PushDialogue(InfoDialogue(Lang("Metadata Restore Succeeded: ") + output)) From d436071bc749211dac374ea08ad9dceca04d536f Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 24 Nov 2023 16:52:09 +0000 Subject: [PATCH 2/2] Add py3-compatible wrapper for commands.getstatusoutput() Fix the code base to work at least with one Python version(Python2) After the previous commit updated the code base to work with the exit status returned by Python3 getstatusoutput(), add a compatible wrapper for commands.getstatusoutput() to have one Python version functional. Signed-off-by: Bernhard Kaindl --- XSConsoleData.py | 63 +++++++++++++++--------- XSConsoleDataUtils.py | 20 ++++---- plugins-base/XSFeatureDRBackup.py | 6 +-- plugins-base/XSFeatureDRRestore.py | 2 +- plugins-base/XSFeatureSRCreate.py | 6 +-- plugins-base/XSFeatureSaveBugReport.py | 4 +- plugins-base/XSFeatureUploadBugReport.py | 4 +- plugins-oem/XSFeatureOEMBackup.py | 4 +- plugins-oem/XSFeatureOEMRestore.py | 4 +- plugins-oem/XSFeatureUpdate.py | 4 +- 10 files changed, 60 insertions(+), 57 deletions(-) diff --git a/XSConsoleData.py b/XSConsoleData.py index 22925f7..5a96df0 100644 --- a/XSConsoleData.py +++ b/XSConsoleData.py @@ -26,6 +26,23 @@ from XSConsoleState import * from XSConsoleUtils import * +# PR #22: Add py3-compatible wrapper for commands.getstatusoutput() to keep the code base functional: +if sys.version_info >= (3, 0): + getoutput = subprocess.getoutput + getstatusoutput = subprocess.getstatusoutput + +else: + import commands + + getoutput = commands.getoutput + + def getstatusoutput(command): + """getstatusoutput with status = exit code, compatible with Python3 getstatusoutput()""" + + status, output = commands.getstatusoutput(command) + return os.WEXITSTATUS(status), output # https://github.com/benjaminp/six/issues/207 + + class DataMethod: def __init__(self, inSend, inName): self.send = inSend @@ -98,31 +115,31 @@ def Create(self): self.ReadTimezones() self.ReadKeymaps() - (status, output) = subprocess.getstatusoutput("dmidecode") + (status, output) = getstatusoutput("dmidecode") if status != 0: # Use test dmidecode file if there's no real output - (status, output) = subprocess.getstatusoutput("/bin/cat ./dmidecode.txt") + (status, output) = getstatusoutput("/bin/cat ./dmidecode.txt") if status == 0: self.ScanDmiDecode(output.split("\n")) - (status, output) = subprocess.getstatusoutput("/sbin/lspci -m") + (status, output) = getstatusoutput("/sbin/lspci -m") if status != 0: - (status, output) = subprocess.getstatusoutput("/usr/bin/lspci -m") + (status, output) = getstatusoutput("/usr/bin/lspci -m") if status == 0: self.ScanLspci(output.split("\n")) if os.path.isfile("/usr/bin/ipmitool"): - (status, output) = subprocess.getstatusoutput("/usr/bin/ipmitool mc info") + (status, output) = getstatusoutput("/usr/bin/ipmitool mc info") if status == 0: self.ScanIpmiMcInfo(output.split("\n")) - (status, output) = subprocess.getstatusoutput("/bin/cat /etc/xensource-inventory") + (status, output) = getstatusoutput("/bin/cat /etc/xensource-inventory") if status == 0: self.ScanInventory(output.split("\n")) - (status, output) = subprocess.getstatusoutput("/usr/bin/openssl x509 -in %s/xapi-ssl.pem -fingerprint -noout" % (Config.Inst().XCPConfigDir())) + (status, output) = getstatusoutput("/usr/bin/openssl x509 -in %s/xapi-ssl.pem -fingerprint -noout" % (Config.Inst().XCPConfigDir())) if status == 0: fp = output.split("=") if len(fp) >= 2: @@ -454,22 +471,22 @@ def LoggingDestinationSet(self, inDestination): self.session.xenapi.host.syslog_reconfigure(self.host.opaqueref()) def UpdateFromResolveConf(self): - (status, output) = subprocess.getstatusoutput("/usr/bin/grep -v \"^;\" /etc/resolv.conf") + (status, output) = getstatusoutput("/usr/bin/grep -v \"^;\" /etc/resolv.conf") if status == 0: self.ScanResolvConf(output.split("\n")) def UpdateFromSysconfig(self): - (status, output) = subprocess.getstatusoutput("/bin/cat /etc/sysconfig/network") + (status, output) = getstatusoutput("/bin/cat /etc/sysconfig/network") if status == 0: self.ScanSysconfigNetwork(output.split("\n")) def UpdateFromHostname(self): - (status, output) = subprocess.getstatusoutput("/bin/cat /etc/hostname") + (status, output) = getstatusoutput("/bin/cat /etc/hostname") if status == 0: self.ScanHostname(output.split("\n")) def UpdateFromNTPConf(self): - (status, output) = subprocess.getstatusoutput("/bin/cat /etc/chrony.conf") + (status, output) = getstatusoutput("/bin/cat /etc/chrony.conf") if status == 0: self.ScanNTPConf(output.split("\n")) @@ -477,7 +494,7 @@ def StringToBool(self, inString): return inString.lower().startswith('true') def RootLabel(self): - output = subprocess.getoutput('/bin/cat /proc/cmdline') + output = getoutput('/bin/cat /proc/cmdline') match = re.search(r'root=\s*LABEL\s*=\s*(\S+)', output) if match: retVal = match.group(1) @@ -675,7 +692,7 @@ def ScanIpmiMcInfo(self, inLines): self.data['bmc']['version'] = match.group(1) def ScanService(self, service): - (status, output) = subprocess.getstatusoutput("systemctl is-enabled %s" % (service,)) + (status, output) = getstatusoutput("systemctl is-enabled %s" % (service,)) self.data['chkconfig'][service] = status == 0 def ScanResolvConf(self, inLines): @@ -779,7 +796,7 @@ def TimezoneSet(self, inTimezone): cfg.write('/etc/sysconfig/clock') def CurrentTimeString(self): - return subprocess.getoutput('/bin/date -R') + return getoutput('/bin/date -R') def ReadKeymaps(self): self.data['keyboard'] = { @@ -811,7 +828,7 @@ def KeymapSet(self, inKeymap): keymapParam = ShellUtils.MakeSafeParam(inKeymap) # Load the keymap now - status, output = subprocess.getstatusoutput('/bin/loadkeys "'+keymapParam+'"') + status, output = getstatusoutput('/bin/loadkeys "'+keymapParam+'"') if status != 0: raise Exception(output) @@ -914,7 +931,7 @@ def ReconfigureManagement(self, inPIF, inMode, inIP, inNetmask, inGateway, in self.RequireSession() self.session.xenapi.PIF.reconfigure_ip(inPIF['opaqueref'], inMode, inIP, inNetmask, inGateway, FirstValue(inDNS, '')) self.session.xenapi.host.management_reconfigure(inPIF['opaqueref']) - status, output = subprocess.getstatusoutput('%s host-signal-networking-change' % (Config.Inst().XECLIPath())) + status, output = getstatusoutput('%s host-signal-networking-change' % (Config.Inst().XECLIPath())) if status != 0: raise Exception(output) finally: @@ -1090,32 +1107,32 @@ def StartXAPI(self): State.Inst().SaveIfRequired() def EnableService(self, service): - status, output = subprocess.getstatusoutput("systemctl enable %s" % (service,)) + status, output = getstatusoutput("systemctl enable %s" % (service,)) if status != 0: raise Exception(output) def DisableService(self, service): - status, output = subprocess.getstatusoutput("systemctl disable %s" % (service,)) + status, output = getstatusoutput("systemctl disable %s" % (service,)) if status != 0: raise Exception(output) def RestartService(self, service): - status, output = subprocess.getstatusoutput("systemctl restart %s" % (service,)) + status, output = getstatusoutput("systemctl restart %s" % (service,)) if status != 0: raise Exception(output) def StartService(self, service): - status, output = subprocess.getstatusoutput("systemctl start %s" % (service,)) + status, output = getstatusoutput("systemctl start %s" % (service,)) if status != 0: raise Exception(output) def StopService(self, service): - status, output = subprocess.getstatusoutput("systemctl stop %s" % (service,)) + status, output = getstatusoutput("systemctl stop %s" % (service,)) if status != 0: raise Exception(output) def NTPStatus(self): - status, output = subprocess.getstatusoutput("/usr/bin/ntpstat") + status, output = getstatusoutput("/usr/bin/ntpstat") return output def SetVerboseBoot(self, inVerbose): @@ -1124,7 +1141,7 @@ def SetVerboseBoot(self, inVerbose): else: name = 'quiet' - status, output = subprocess.getstatusoutput( + status, output = getstatusoutput( "(export TERM=xterm && /opt/xensource/libexec/set-boot " + name + ")") if status != 0: raise Exception(output) diff --git a/XSConsoleDataUtils.py b/XSConsoleDataUtils.py index 423a338..92d6aaf 100644 --- a/XSConsoleDataUtils.py +++ b/XSConsoleDataUtils.py @@ -80,7 +80,7 @@ def DeviceList(cls, inWritableOnly): @classmethod def SRDeviceList(self): retVal= [] - status, output = subprocess.getstatusoutput("/opt/xensource/libexec/list_local_disks") + status, output = getstatusoutput("/opt/xensource/libexec/list_local_disks") if status == 0: regExp = re.compile(r"\s*\(\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*\)") for line in output.split("\n"): @@ -168,18 +168,18 @@ def USBFormat(self, inVDI): if e.errno != errno.EINTR: # Loop if EINTR raise - status, output = subprocess.getstatusoutput('/bin/sync') + status, output = getstatusoutput('/bin/sync') if status != 0: raise Exception(output) # Format the new partition with VFAT - status, output = subprocess.getstatusoutput("/sbin/mkfs.vfat -n 'XenServer Backup' -F 32 '" +partitionName + "' 2>&1") + status, output = getstatusoutput("/sbin/mkfs.vfat -n 'XenServer Backup' -F 32 '" +partitionName + "' 2>&1") if status != 0: raise Exception(output) - status, output = subprocess.getstatusoutput('/bin/sync') + status, output = getstatusoutput('/bin/sync') if status != 0: raise Exception(output) @@ -237,7 +237,7 @@ def __init__(self, inVDI, inMode = None): FileUtils.AssertSafePath(self.mountDev) self.mountPoint = tempfile.mkdtemp(".xsconsole") - status, output = subprocess.getstatusoutput("/bin/mount -t auto -o " + self.mode + ' ' +self.mountDev+" "+self.mountPoint + " 2>&1") + status, output = getstatusoutput("/bin/mount -t auto -o " + self.mode + ' ' +self.mountDev+" "+self.mountPoint + " 2>&1") if status != 0: try: self.Unmount() @@ -276,7 +276,7 @@ def HandleMountFailure(self, inOutput): raise Exception(inOutput) realDevice = FileUtils.DeviceFromVDI(self.vdi) - status, output = subprocess.getstatusoutput("/sbin/fdisk -l '" +realDevice+"'") + status, output = getstatusoutput("/sbin/fdisk -l '" +realDevice+"'") if status != 0: raise Exception(output) @@ -313,7 +313,7 @@ def Scan(self, inRegExp = None, inNumToReturn = None): def Unmount(self): status = 0 if self.mountedVBD: - status, output = subprocess.getstatusoutput("/bin/umount '"+self.mountPoint + "' 2>&1") + status, output = getstatusoutput("/bin/umount '"+self.mountPoint + "' 2>&1") os.rmdir(self.mountPoint) self.mountedVBD = False if self.pluggedVBD: @@ -360,7 +360,7 @@ def __init__(self, inVDI, inMode = None): FileUtils.AssertSafePath(self.mountDev) self.mountPoint = tempfile.mkdtemp(".xsconsole") - status, output = subprocess.getstatusoutput("/bin/mount -t auto -o " + self.mode + ' ' +self.mountDev+" "+self.mountPoint + " 2>&1") + status, output = getstatusoutput("/bin/mount -t auto -o " + self.mode + ' ' +self.mountDev+" "+self.mountPoint + " 2>&1") if status != 0: try: self.Unmount() @@ -400,7 +400,7 @@ def HandleMountFailure(self, inStatus, inOutput): raise Exception(inOutput) realDevice = FileUtils.DeviceFromVDI(self.vdi) - status, output = subprocess.getstatusoutput("/sbin/fdisk -l '" +realDevice+"'") + status, output = getstatusoutput("/sbin/fdisk -l '" +realDevice+"'") if status != 0: raise Exception(output) @@ -437,7 +437,7 @@ def Scan(self, inRegExp = None, inNumToReturn = None): def Unmount(self): status = 0 if self.mountedVDI: - status, output = subprocess.getstatusoutput("/bin/umount '"+self.mountPoint + "' 2>&1") + status, output = getstatusoutput("/bin/umount '"+self.mountPoint + "' 2>&1") os.rmdir(self.mountPoint) self.mountedVDI = False XSLog('Unmounted '+self.mountPoint) diff --git a/plugins-base/XSFeatureDRBackup.py b/plugins-base/XSFeatureDRBackup.py index 9450321..55b2f2c 100644 --- a/plugins-base/XSFeatureDRBackup.py +++ b/plugins-base/XSFeatureDRBackup.py @@ -16,8 +16,6 @@ if __name__ == "__main__": raise Exception("This script is a plugin for xsconsole and cannot run independently") -import subprocess - from XSConsoleStandard import * class DRBackupDialogue(SRDialogue): @@ -38,7 +36,7 @@ def DoAction(self, inSR): sr_uuid = inSR['uuid'] command = "%s/xe-backup-metadata -n -u %s" % (Config.Inst().HelperPath(), sr_uuid) - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) initalize_vdi = "" if status == 3: initalize_vdi = "-c" @@ -47,7 +45,7 @@ def DoAction(self, inSR): Layout.Inst().TransientBanner(Lang("Backing up metadata... This may take several minutes.")) command = "%s/xe-backup-metadata %s -u %s" % (Config.Inst().HelperPath(), initalize_vdi, sr_uuid) - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) if status == 0: Layout.Inst().PushDialogue(InfoDialogue(Lang("Backup Successful"), output)) else: diff --git a/plugins-base/XSFeatureDRRestore.py b/plugins-base/XSFeatureDRRestore.py index afe984d..2fa6b8e 100644 --- a/plugins-base/XSFeatureDRRestore.py +++ b/plugins-base/XSFeatureDRRestore.py @@ -92,7 +92,7 @@ def HandleMethodChoice(self, inChoice, dryRun): dry_flag="" Layout.Inst().TransientBanner(Lang("Restoring VM Metadata. This may take a few minutes...")) command = "%s/xe-restore-metadata -y %s -u %s -x %s -d %s -m %s" % (Config.Inst().HelperPath(), dry_flag, self.sr_uuid, self.vdi_uuid, self.chosen_date, chosen_mode) - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) Layout.Inst().PopDialogue() if status == 0: Layout.Inst().PushDialogue(InfoDialogue(Lang("Metadata Restore Succeeded: ") + output)) diff --git a/plugins-base/XSFeatureSRCreate.py b/plugins-base/XSFeatureSRCreate.py index c29d258..f8c1a13 100644 --- a/plugins-base/XSFeatureSRCreate.py +++ b/plugins-base/XSFeatureSRCreate.py @@ -16,8 +16,6 @@ if __name__ == "__main__": raise Exception("This script is a plugin for xsconsole and cannot run independently") -import subprocess - from XSConsoleStandard import * import xml.dom.minidom @@ -1288,7 +1286,7 @@ def CommitNFS_ATTACH(self): 'user') def CommitNFS_ISO_ATTACH(self): - self.srParams['uuid'] = subprocess.getoutput('/usr/bin/uuidgen') + self.srParams['uuid'] = getoutput('/usr/bin/uuidgen') self.CommitAttach(self.srTypes['NFS_ISO'], { # device_config 'location':self.srParams['server']+':'+self.srParams['serverpath'], 'options':self.srParams['options'] @@ -1300,7 +1298,7 @@ def CommitNFS_ISO_ATTACH(self): ) def CommitCIFS_ISO_ATTACH(self): - self.srParams['uuid'] = subprocess.getoutput('/usr/bin/uuidgen') + self.srParams['uuid'] = getoutput('/usr/bin/uuidgen') deviceConfig = { 'location':'//'+self.srParams['server']+'/'+self.srParams['serverpath'], 'type':'cifs', diff --git a/plugins-base/XSFeatureSaveBugReport.py b/plugins-base/XSFeatureSaveBugReport.py index cc9e3dc..06a7c4c 100644 --- a/plugins-base/XSFeatureSaveBugReport.py +++ b/plugins-base/XSFeatureSaveBugReport.py @@ -16,8 +16,6 @@ if __name__ == "__main__": raise Exception("This script is a plugin for xsconsole and cannot run independently") -import subprocess - from XSConsoleStandard import * class SaveBugReportDialogue(FileDialogue): @@ -52,7 +50,7 @@ def DoAction(self): file = open(filename, "w") # xen-bugtool requires a value for $USER command = "( export USER=root && /usr/sbin/xen-bugtool --yestoall --silent --output=tar --outfd="+str(file.fileno()) + ' )' - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) file.close() if status != 0: diff --git a/plugins-base/XSFeatureUploadBugReport.py b/plugins-base/XSFeatureUploadBugReport.py index 6d3b8f5..d243f95 100644 --- a/plugins-base/XSFeatureUploadBugReport.py +++ b/plugins-base/XSFeatureUploadBugReport.py @@ -16,8 +16,6 @@ if __name__ == "__main__": raise Exception("This script is a plugin for xsconsole and cannot run independently") -import subprocess - from XSConsoleStandard import * class UploadBugReportDialogue(InputDialogue): @@ -48,7 +46,7 @@ def HandleCommit(self, inValues): if proxy != '': command += " http_proxy='"+proxy+"'" - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) if status != 0: XSLogError('Upload bugreport failed', output) # Error output can be verbose, so syslog only diff --git a/plugins-oem/XSFeatureOEMBackup.py b/plugins-oem/XSFeatureOEMBackup.py index 800a6ae..334181c 100644 --- a/plugins-oem/XSFeatureOEMBackup.py +++ b/plugins-oem/XSFeatureOEMBackup.py @@ -16,8 +16,6 @@ if __name__ == "__main__": raise Exception("This script is a plugin for xsconsole and cannot run independently") -import subprocess - from XSConsoleStandard import * class OEMBackupDialogue(FileDialogue): @@ -70,7 +68,7 @@ def DoCommit(self): filename = self.vdiMount.MountedPath(self.filename) FileUtils.AssertSafePath(filename) command = "/opt/xensource/bin/xe host-backup file-name='"+filename+"' host="+hostRef - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) if status != 0: raise Exception(output) diff --git a/plugins-oem/XSFeatureOEMRestore.py b/plugins-oem/XSFeatureOEMRestore.py index 68a44b6..39770d2 100644 --- a/plugins-oem/XSFeatureOEMRestore.py +++ b/plugins-oem/XSFeatureOEMRestore.py @@ -16,8 +16,6 @@ if __name__ == "__main__": raise Exception("This script is a plugin for xsconsole and cannot run independently") -import subprocess - from XSConsoleStandard import * class OEMRestoreDialogue(FileDialogue): @@ -60,7 +58,7 @@ def DoAction(self): filename = self.vdiMount.MountedPath(self.filename) FileUtils.AssertSafePath(filename) command = "/opt/xensource/bin/xe host-restore file-name='"+filename+"' host="+hostRef - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) if status != 0: raise Exception(output) diff --git a/plugins-oem/XSFeatureUpdate.py b/plugins-oem/XSFeatureUpdate.py index b820ea1..538c1f6 100644 --- a/plugins-oem/XSFeatureUpdate.py +++ b/plugins-oem/XSFeatureUpdate.py @@ -16,8 +16,6 @@ if __name__ == "__main__": raise Exception("This script is a plugin for xsconsole and cannot run independently") -import subprocess - from XSConsoleStandard import * class UpdateDialogue(FileDialogue): @@ -60,7 +58,7 @@ def DoAction(self): filename = self.vdiMount.MountedPath(self.filename) FileUtils.AssertSafePath(filename) command = "/opt/xensource/bin/xe update-upload file-name='"+filename+"' host-uuid="+hostRef - status, output = subprocess.getstatusoutput(command) + status, output = getstatusoutput(command) if status != 0: raise Exception(output)