4545qga_channel_state_connected = 'connected'
4646qga_channel_state_disconnected = 'disconnected'
4747
48+ encodings = ['utf-8' , 'GB2312' , 'ISO-8859-1' ]
49+
50+
51+ def decode_with_fallback (encoded_bytes ):
52+ for encoding in encodings :
53+ try :
54+ return encoded_bytes .decode (encoding ).encode ('utf-8' )
55+ except UnicodeDecodeError :
56+ continue
57+ raise UnicodeDecodeError ("Unable to decode bytes using provided encodings" )
58+
4859
4960def get_qga_channel_state (vm_dom ):
5061 xml_tree = ET .fromstring (vm_dom .XMLDesc ())
@@ -61,9 +72,11 @@ def is_qga_connected(vm_dom):
6172 except :
6273 return False
6374
75+
6476# windows zs-tools command wait 120s
6577zs_tools_wait_retry = 120
6678
79+
6780class QgaException (Exception ):
6881 """ The base exception class for all exceptions this agent raises."""
6982
@@ -85,6 +98,8 @@ class VmQga(object):
8598 VM_OS_LINUX_SUSE_D = "sled"
8699 VM_OS_LINUX_ORACLE = "ol"
87100 VM_OS_LINUX_REDHAT = "rhel"
101+ VM_OS_LINUX_DEBIAN = "debian"
102+ VM_OS_LINUX_FEDORA = "fedora"
88103 VM_OS_WINDOWS = "mswindows"
89104
90105 ZS_TOOLS_PATN_WIN = "C:\Program Files\GuestTools\zs-tools\zs-tools.exe"
@@ -202,9 +217,9 @@ def guest_exec_bash(self, cmd, output=True, wait=qga_exec_wait_interval, retry=q
202217 exit_code = ret .get ('exitcode' )
203218 ret_data = None
204219 if 'out-data' in ret :
205- ret_data = ret ['out-data' ]
220+ ret_data = decode_with_fallback ( ret ['out-data' ])
206221 elif 'err-data' in ret :
207- ret_data = ret ['err-data' ]
222+ ret_data = decode_with_fallback ( ret ['err-data' ])
208223
209224 return exit_code , ret_data
210225
@@ -247,9 +262,9 @@ def guest_exec_python(self, file, params=None, output=True, wait=qga_exec_wait_i
247262 exit_code = ret .get ('exitcode' )
248263 ret_data = None
249264 if 'out-data' in ret :
250- ret_data = ret ['out-data' ]
265+ ret_data = decode_with_fallback ( ret ['out-data' ])
251266 elif 'err-data' in ret :
252- ret_data = ret ['err-data' ]
267+ ret_data = decode_with_fallback ( ret ['err-data' ])
253268
254269 return exit_code , ret_data
255270
@@ -264,9 +279,10 @@ def guest_exec_zs_tools(self, operate, config, output=True, wait=qga_exec_wait_i
264279 raise Exception ('qga exec zs-tools unknow operate {} for vm {}' .format (operate , self .vm_uuid ))
265280
266281 if ret and "pid" in ret :
267- pid = ret ["pid" ]
282+ pid = ret ["pid" ]
268283 else :
269- raise Exception ('qga exec zs-tools operate {} config {} failed for vm {}' .format (operate , config , self .vm_uuid ))
284+ raise Exception (
285+ 'qga exec zs-tools operate {} config {} failed for vm {}' .format (operate , config , self .vm_uuid ))
270286
271287 ret = None
272288 for i in range (retry ):
@@ -276,17 +292,52 @@ def guest_exec_zs_tools(self, operate, config, output=True, wait=qga_exec_wait_i
276292 break
277293
278294 if not ret or not ret .get ('exited' ):
279- raise Exception ('qga exec zs-tools operate {} config {} timeout for vm {}' .format (operate , config , self .vm_uuid ))
295+ raise Exception (
296+ 'qga exec zs-tools operate {} config {} timeout for vm {}' .format (operate , config , self .vm_uuid ))
280297
281298 exit_code = ret .get ('exitcode' )
282299 ret_data = None
283300 if 'out-data' in ret :
284- ret_data = ret ['out-data' ]. decode ( 'utf-8' ). encode ( 'utf-8' )
301+ ret_data = decode_with_fallback ( ret ['out-data' ])
285302 elif 'err-data' in ret :
286- ret_data = ret ['err-data' ]. decode ( 'utf-8' ). encode ( 'utf-8' )
303+ ret_data = decode_with_fallback ( ret ['err-data' ])
287304
288305 return exit_code , ret_data .replace ('\r \n ' , '' )
289306
307+ def guest_exec_wmic (self , cmd , output = True , wait = qga_exec_wait_interval , retry = qga_exec_wait_retry ):
308+ cmd_parts = cmd .split ('|' )
309+ cmd = "{}" .format (" " .join ([part for part in cmd_parts ]))
310+
311+ ret = self .guest_exec (
312+ {"path" : "wmic" , "arg" : cmd .split (" " ), "capture-output" : output })
313+ if ret and "pid" in ret :
314+ pid = ret ["pid" ]
315+ else :
316+ raise Exception ('qga exec cmd {} failed for vm {}' .format (cmd , self .vm_uuid ))
317+
318+ if not output :
319+ logger .debug ("run qga wmic: {} failed, no output" .format (cmd ))
320+ return 0 , None
321+
322+ ret = None
323+ for i in range (retry ):
324+ time .sleep (wait )
325+ ret = self .guest_exec_status (pid )
326+ if ret ['exited' ]:
327+ break
328+
329+ if not ret or not ret .get ('exited' ):
330+ raise Exception ('qga exec cmd {} timeout for vm {}' .format (cmd , self .vm_uuid ))
331+
332+ exit_code = ret .get ('exitcode' )
333+ ret_data = None
334+ if 'out-data' in ret :
335+ ret_data = decode_with_fallback (ret ['out-data' ])
336+ elif 'err-data' in ret :
337+ ret_data = decode_with_fallback (ret ['err-data' ])
338+
339+ return exit_code , ret_data
340+
290341 def guest_exec_powershell (self , cmd , output = True , wait = qga_exec_wait_interval , retry = qga_exec_wait_retry ):
291342 cmd_parts = cmd .split ('|' )
292343 cmd = "& '{}'" .format ("' '" .join ([part for part in cmd_parts ]))
@@ -315,9 +366,9 @@ def guest_exec_powershell(self, cmd, output=True, wait=qga_exec_wait_interval, r
315366 exit_code = ret .get ('exitcode' )
316367 ret_data = None
317368 if 'out-data' in ret :
318- ret_data = ret ['out-data' ]. decode ( "GB2312" )
369+ ret_data = decode_with_fallback ( ret ['out-data' ])
319370 elif 'err-data' in ret :
320- ret_data = ret ['err-data' ]. decode ( "GB2312" )
371+ ret_data = decode_with_fallback ( ret ['err-data' ])
321372
322373 return exit_code , ret_data
323374
@@ -438,18 +489,25 @@ def guest_get_os_id_like(self):
438489 def guest_get_os_info (self ):
439490 ret = self .guest_exec_bash_no_exitcode ('cat /etc/os-release' )
440491 if not ret :
441- raise Exception ('get os info failed' )
442-
443- lines = [line for line in ret .split ('\n ' ) if line != "" ]
444- config = {}
445- for line in lines :
446- if line .startswith ('#' ):
447- continue
448-
449- info = line .split ('=' )
450- if len (info ) != 2 :
451- continue
452- config [info [0 ].strip ()] = info [1 ].strip ().strip ('"' )
492+ # Parse /etc/redhat-release for CentOS/RHEL 6
493+ ret = self .guest_exec_bash_no_exitcode ('cat /etc/redhat-release' )
494+ if not ret :
495+ raise Exception ('get os info failed' )
496+ parts = ret .split ()
497+ if len (parts ) >= 3 and parts [1 ] == 'release' :
498+ config = {'ID' : parts [0 ].lower (), 'VERSION_ID' : parts [2 ]}
499+ else :
500+ # Parse /etc/os-release
501+ lines = [line for line in ret .split ('\n ' ) if line != "" ]
502+ config = {}
503+ for line in lines :
504+ if line .startswith ('#' ):
505+ continue
506+
507+ info = line .split ('=' )
508+ if len (info ) != 2 :
509+ continue
510+ config [info [0 ].strip ()] = info [1 ].strip ().strip ('"' )
453511
454512 vm_os = config .get ('ID' )
455513 version = config .get ('VERSION_ID' )
0 commit comments