2017-12-12 15:10:51 +03:00
#!@PYTHON3@
2016-02-18 02:53:35 +03:00
# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright 2015-2016, Vratislav Podzimek <vpodzime@redhat.com>
import subprocess
import shlex
from fcntl import fcntl , F_GETFL , F_SETFL
2016-08-12 23:23:05 +03:00
import os
2016-02-18 02:53:35 +03:00
import traceback
import sys
2016-08-12 23:23:05 +03:00
import tempfile
import time
import select
import copy
try :
import simplejson as json
except ImportError :
import json
2016-02-18 02:53:35 +03:00
2016-08-29 22:51:26 +03:00
from lvmdbusd . cfg import LVM_CMD
2017-03-09 00:35:53 +03:00
from lvmdbusd . utils import log_debug , log_error , add_no_notify
2016-02-18 02:53:35 +03:00
SHELL_PROMPT = " lvm> "
def _quote_arg ( arg ) :
if len ( shlex . split ( arg ) ) > 1 :
return ' " %s " ' % arg
else :
return arg
class LVMShellProxy ( object ) :
2016-11-29 20:07:21 +03:00
2016-11-29 21:37:59 +03:00
@staticmethod
def _read ( stream ) :
tmp = stream . read ( )
if tmp :
return tmp . decode ( " utf-8 " )
return ' '
2016-11-29 20:07:21 +03:00
# Read until we get prompt back and a result
# @param: no_output Caller expects no output to report FD
# Returns stdout, report, stderr (report is JSON!)
2016-11-17 00:57:04 +03:00
def _read_until_prompt ( self , no_output = False ) :
2016-02-18 02:53:35 +03:00
stdout = " "
2016-08-12 23:23:05 +03:00
report = " "
stderr = " "
2016-11-17 00:57:04 +03:00
keep_reading = True
2016-11-29 20:07:21 +03:00
extra_passes = 3
report_json = { }
prev_report_len = 0
2016-02-18 02:53:35 +03:00
2016-08-12 23:23:05 +03:00
# Try reading from all FDs to prevent one from filling up and causing
2016-11-29 20:07:21 +03:00
# a hang. Keep reading until we get the prompt back and the report
# FD does not contain valid JSON
2016-11-17 00:57:04 +03:00
while keep_reading :
2016-02-18 02:53:35 +03:00
try :
2016-08-12 23:23:05 +03:00
rd_fd = [
self . lvm_shell . stdout . fileno ( ) ,
2016-11-29 21:37:59 +03:00
self . report_stream . fileno ( ) ,
2016-08-12 23:23:05 +03:00
self . lvm_shell . stderr . fileno ( ) ]
ready = select . select ( rd_fd , [ ] , [ ] , 2 )
for r in ready [ 0 ] :
if r == self . lvm_shell . stdout . fileno ( ) :
2016-11-29 21:37:59 +03:00
stdout + = LVMShellProxy . _read ( self . lvm_shell . stdout )
elif r == self . report_stream . fileno ( ) :
report + = LVMShellProxy . _read ( self . report_stream )
2016-08-12 23:23:05 +03:00
elif r == self . lvm_shell . stderr . fileno ( ) :
2016-11-29 21:37:59 +03:00
stderr + = LVMShellProxy . _read ( self . lvm_shell . stderr )
2016-08-12 23:23:05 +03:00
2016-10-07 21:45:30 +03:00
# Check to see if the lvm process died on us
if self . lvm_shell . poll ( ) :
raise Exception ( self . lvm_shell . returncode , " %s " % stderr )
2016-11-17 00:57:04 +03:00
if stdout . endswith ( SHELL_PROMPT ) :
if no_output :
keep_reading = False
else :
2016-11-29 20:07:21 +03:00
cur_report_len = len ( report )
if cur_report_len != 0 :
# Only bother to parse if we have more data
if prev_report_len != cur_report_len :
prev_report_len = cur_report_len
# Parse the JSON if it's good we are done,
# if not we will try to read some more.
try :
report_json = json . loads ( report )
keep_reading = False
except ValueError :
pass
if keep_reading :
2016-11-17 00:57:04 +03:00
extra_passes - = 1
if extra_passes < = 0 :
2016-11-29 20:07:21 +03:00
if len ( report ) :
raise ValueError ( " Invalid json: %s " %
report )
else :
raise ValueError (
" lvm returned no JSON output! " )
2016-11-17 00:57:04 +03:00
2016-08-12 23:23:05 +03:00
except IOError as ioe :
log_debug ( str ( ioe ) )
2016-02-18 02:53:35 +03:00
pass
2016-11-29 20:07:21 +03:00
return stdout , report_json , stderr
2016-02-18 02:53:35 +03:00
def _write_cmd ( self , cmd ) :
cmd_bytes = bytes ( cmd , " utf-8 " )
num_written = self . lvm_shell . stdin . write ( cmd_bytes )
assert ( num_written == len ( cmd_bytes ) )
self . lvm_shell . stdin . flush ( )
2016-11-29 21:37:59 +03:00
@staticmethod
def _make_non_block ( stream ) :
flags = fcntl ( stream , F_GETFL )
fcntl ( stream , F_SETFL , flags | os . O_NONBLOCK )
2016-08-12 23:23:05 +03:00
def __init__ ( self ) :
2016-02-18 02:53:35 +03:00
2016-08-12 23:23:05 +03:00
# Create a temp directory
tmp_dir = tempfile . mkdtemp ( prefix = " lvmdbus_ " )
tmp_file = " %s /lvmdbus_report " % ( tmp_dir )
2016-02-18 02:53:35 +03:00
2016-08-12 23:23:05 +03:00
try :
# Lets create fifo for the report output
os . mkfifo ( tmp_file , 0o600 )
except FileExistsError :
pass
2016-02-18 02:53:35 +03:00
2016-11-29 21:37:59 +03:00
# We have to open non-blocking as the other side isn't open until
# we actually fork the process.
self . report_fd = os . open ( tmp_file , os . O_NONBLOCK )
self . report_stream = os . fdopen ( self . report_fd , ' rb ' , 0 )
2016-08-12 23:23:05 +03:00
# Setup the environment for using our own socket for reporting
local_env = copy . deepcopy ( os . environ )
local_env [ " LVM_REPORT_FD " ] = " 32 "
local_env [ " LVM_COMMAND_PROFILE " ] = " lvmdbusd "
2016-02-18 02:53:35 +03:00
2016-10-11 17:36:47 +03:00
# Disable the abort logic if lvm logs too much, which easily happens
# when utilizing the lvm shell.
local_env [ " LVM_LOG_FILE_MAX_LINES " ] = " 0 "
2016-02-18 02:53:35 +03:00
# run the lvm shell
self . lvm_shell = subprocess . Popen (
2016-08-12 23:23:05 +03:00
[ LVM_CMD + " 32> %s " % tmp_file ] ,
stdin = subprocess . PIPE , stdout = subprocess . PIPE , env = local_env ,
stderr = subprocess . PIPE , close_fds = True , shell = True )
2016-10-07 21:46:59 +03:00
try :
2016-11-29 21:37:59 +03:00
LVMShellProxy . _make_non_block ( self . lvm_shell . stdout )
LVMShellProxy . _make_non_block ( self . lvm_shell . stderr )
2016-10-07 21:46:59 +03:00
# wait for the first prompt
2016-11-17 00:57:04 +03:00
errors = self . _read_until_prompt ( no_output = True ) [ 2 ]
2016-10-07 21:46:59 +03:00
if errors and len ( errors ) :
raise RuntimeError ( errors )
except :
raise
finally :
2016-11-29 21:37:59 +03:00
# These will get deleted when the FD count goes to zero so we
# can be sure to clean up correctly no matter how we finish
2016-10-07 21:46:59 +03:00
os . unlink ( tmp_file )
os . rmdir ( tmp_dir )
2016-08-12 23:23:05 +03:00
def get_error_msg ( self ) :
# We got an error, lets go fetch the error message
self . _write_cmd ( ' lastlog \n ' )
# read everything from the STDOUT to the next prompt
2016-11-29 20:07:21 +03:00
stdout , report_json , stderr = self . _read_until_prompt ( )
if ' log ' in report_json :
error_msg = " "
# Walk the entire log array and build an error string
for log_entry in report_json [ ' log ' ] :
if log_entry [ ' log_type ' ] == " error " :
if error_msg :
error_msg + = ' , ' + log_entry [ ' log_message ' ]
else :
error_msg = log_entry [ ' log_message ' ]
2016-08-12 23:23:05 +03:00
2016-11-29 20:07:21 +03:00
return error_msg
2016-08-12 23:23:05 +03:00
2016-11-29 20:07:21 +03:00
return ' No error reason provided! (missing " log " section) '
2016-02-18 02:53:35 +03:00
def call_lvm ( self , argv , debug = False ) :
2016-08-12 23:23:05 +03:00
rc = 1
error_msg = " "
2016-10-07 21:45:30 +03:00
if self . lvm_shell . poll ( ) :
raise Exception (
self . lvm_shell . returncode ,
" Underlying lvm shell process is not present! " )
2017-03-09 00:35:53 +03:00
argv = add_no_notify ( argv )
2016-02-18 02:53:35 +03:00
# create the command string
cmd = " " . join ( _quote_arg ( arg ) for arg in argv )
cmd + = " \n "
# run the command by writing it to the shell's STDIN
self . _write_cmd ( cmd )
# read everything from the STDOUT to the next prompt
2016-11-29 20:07:21 +03:00
stdout , report_json , stderr = self . _read_until_prompt ( )
2016-02-18 02:53:35 +03:00
2016-08-12 23:23:05 +03:00
# Parse the report to see what happened
2016-11-29 20:07:21 +03:00
if ' log ' in report_json :
2018-12-18 18:51:50 +03:00
ret_code = int ( report_json [ ' log ' ] [ - 1 : ] [ 0 ] [ ' log_ret_code ' ] )
# If we have an exported vg we get a log_ret_code == 5 when
# we do a 'fullreport'
if ( ret_code == 1 ) or ( ret_code == 5 and argv [ 0 ] == ' fullreport ' ) :
2016-11-29 20:07:21 +03:00
rc = 0
else :
error_msg = self . get_error_msg ( )
2016-02-18 02:53:35 +03:00
if debug or rc != 0 :
log_error ( ( ' CMD: %s ' % cmd ) )
log_error ( ( " EC = %d " % rc ) )
2016-08-12 23:23:05 +03:00
log_error ( ( " ERROR_MSG= \n %s \n " % error_msg ) )
2016-02-18 02:53:35 +03:00
2016-11-29 20:07:21 +03:00
return rc , report_json , error_msg
2016-02-18 02:53:35 +03:00
2016-08-25 02:29:35 +03:00
def exit_shell ( self ) :
try :
self . _write_cmd ( ' exit \n ' )
except Exception as e :
log_error ( str ( e ) )
2016-02-18 02:53:35 +03:00
def __del__ ( self ) :
2016-08-12 23:23:05 +03:00
try :
self . lvm_shell . terminate ( )
except :
pass
2016-02-18 02:53:35 +03:00
if __name__ == " __main__ " :
shell = LVMShellProxy ( )
in_line = " start "
try :
while in_line :
in_line = input ( " lvm> " )
if in_line :
2016-08-12 23:23:05 +03:00
start = time . time ( )
ret , out , err = shell . call_lvm ( in_line . split ( ) )
end = time . time ( )
print ( ( " RC: %d " % ret ) )
2016-10-07 21:45:30 +03:00
print ( ( " OUT: \n %s " % out ) )
2016-02-18 02:53:35 +03:00
print ( ( " ERR: \n %s " % err ) )
2016-08-12 23:23:05 +03:00
print ( " Command = %f seconds " % ( end - start ) )
2016-02-18 02:53:35 +03:00
except KeyboardInterrupt :
pass
except EOFError :
pass
except Exception :
traceback . print_exc ( file = sys . stdout )