diff --git a/hpe3parclient/ssh.py b/hpe3parclient/ssh.py index 5ce91878..7408eab1 100644 --- a/hpe3parclient/ssh.py +++ b/hpe3parclient/ssh.py @@ -208,52 +208,54 @@ def strip_input_from_output(cmd, output): in the output so that it knows what it is stripping (or else it raises an exception). """ - - # Keep output lines after the 'exit'. - # 'exit' is the last of the stdin. + # Search for the prompt. It may or may not be the first line, because + # on fast connections we get a "dump" of stdin with all the commands + # we've sent, but we don't on slow connections. for i, line in enumerate(output): - if line == 'exit': + prompt_pct = line.find('% setclienv csvtable 1') + if prompt_pct >= 0: + prompt = line[:prompt_pct + 1] + ' ' output = output[i + 1:] break else: - reason = "Did not find 'exit' in output." - HPE3PARSSHClient.raise_stripper_error(reason, output) - - if not output: - reason = "Did not find any output after 'exit'." - HPE3PARSSHClient.raise_stripper_error(reason, output) - - # The next line is prompt plus setclienv command. - # Use this to get the prompt string. - prompt_pct = output[0].find('% setclienv csvtable 1') - if prompt_pct < 0: reason = "Did not find '% setclienv csvtable 1' in output." HPE3PARSSHClient.raise_stripper_error(reason, output) - prompt = output[0][0:prompt_pct + 1] - del output[0] - # Next find the prompt plus the command. + # Some systems return additional empty prompts, others return an + # extra \r after commands. In both cases this prevents us from finding + # the output delimiters, and makes us return useless data to caller, so + # fix them. + output = [line.rstrip('\r\n') for line in output if line != prompt] + + # Output starts right after the command we sent. # It might be broken into multiple lines, so loop and # append until we find the whole prompt plus command. command_string = ' '.join(cmd) if re.match('|'.join(tpd_commands), command_string): escp_command_string = command_string.replace('"', '\\"') command_string = "Tpd::rtpd " + '"' + escp_command_string + '"' - seek = ' '.join((prompt, command_string)) + seek = prompt + command_string found = '' for i, line in enumerate(output): - found = ''.join((found, line.rstrip('\r\n'))) + found = found + line if found == seek: - # Found the whole thing. Use the rest as output now. - output = output[i + 1:] + # Output is right after this line, drop everything before it + del output[:i + 1] break else: HPE3PARSSHClient._logger.debug("Command: %s" % command_string) reason = "Did not find match for command in output" HPE3PARSSHClient.raise_stripper_error(reason, output) - # Always strip the last 2 - return output[:len(output) - 2] + # Output stops right before exit command is executed + try: + exit_index = output.index(prompt + 'exit') + del output[exit_index:] + except ValueError: + reason = "Did not find 'exit' in output." + HPE3PARSSHClient.raise_stripper_error(reason, output) + + return output def run(self, cmd, multi_line_stripper=False): """Runs a CLI command over SSH, without doing any result parsing.""" diff --git a/test/test_HPE3ParClient_FilePersona_Mock.py b/test/test_HPE3ParClient_FilePersona_Mock.py index e241a793..eec7f5e6 100644 --- a/test/test_HPE3ParClient_FilePersona_Mock.py +++ b/test/test_HPE3ParClient_FilePersona_Mock.py @@ -258,17 +258,17 @@ def test_strip_input_from_output(self): 'CSIM-EOS08_1611165 cli% createvfs -fpg marktestfpg -wait ' '127.0.0.2 255.255.255.\r', '0 UT5_VFS_150651\r', - 'VFS UT5_VFS_150651 already exists within FPG marktestfpg\r', + 'VFS UT5_VFS_150651 already exists within FPG marktestfpg', 'CSIM-EOS08_1611165 cli% exit\r', '' ] expected = [ - 'VFS UT5_VFS_150651 already exists within FPG marktestfpg\r'] + 'VFS UT5_VFS_150651 already exists within FPG marktestfpg'] actual = ssh.HPE3PARSSHClient.strip_input_from_output(cmd, out) self.assertEqual(expected, actual) - def test_strip_input_from_output_no_exit(self): + def test_strip_input_from_output_no_stdin(self): cmd = [ 'createvfs', '-fpg', @@ -279,10 +279,6 @@ def test_strip_input_from_output_no_exit(self): 'UT5_VFS_150651' ] out = [ - 'setclienv csvtable 1', - 'createvfs -fpg marktestfpg -wait 127.0.0.2 255.255.255.0 ' - 'UT5_VFS_150651', - 'XXXt', # Don't match 'CSIM-EOS08_1611165 cli% setclienv csvtable 1\r', 'CSIM-EOS08_1611165 cli% createvfs -fpg marktestfpg -wait ' '127.0.0.2 255.255.255.\r', @@ -291,9 +287,11 @@ def test_strip_input_from_output_no_exit(self): 'CSIM-EOS08_1611165 cli% exit\r', '' ] - self.assertRaises(exceptions.SSHException, - ssh.HPE3PARSSHClient.strip_input_from_output, - cmd, out) + expected = [ + 'VFS UT5_VFS_150651 already exists within FPG marktestfpg'] + + actual = ssh.HPE3PARSSHClient.strip_input_from_output(cmd, out) + self.assertEqual(expected, actual) def test_strip_input_from_output_no_setclienv(self): cmd = [ diff --git a/test/test_HPE3ParClient_MockSSH.py b/test/test_HPE3ParClient_MockSSH.py index 6f122a14..71a244d3 100644 --- a/test/test_HPE3ParClient_MockSSH.py +++ b/test/test_HPE3ParClient_MockSSH.py @@ -210,6 +210,14 @@ def test_sanitize_cert(self): self.assertEqual(expected, out) def test_strip_input_from_output(self): + def gen_output(*args): + result = [] + for arg in args: + if isinstance(arg, list): + result.extend(arg) + else: + result.append(arg) + return result cmd = ['foo', '-v'] # nothing after exit output = ['exit'] @@ -224,30 +232,68 @@ def test_strip_input_from_output(self): cmd, output) # no setclienv csv - output = [cmd, 'exit', 'out'] + output = cmd + ['exit', 'out'] self.assertRaises(exceptions.SSHException, ssh.HPE3PARSSHClient.strip_input_from_output, cmd, output) # command not in output after exit - output = [cmd, 'exit', 'PROMPT% setclienv csvtable 1'] + output = gen_output('setclienv csvtable 1', + cmd, + 'exit', + 'PROMPT% setclienv csvtable 1') + self.assertRaises(exceptions.SSHException, + ssh.HPE3PARSSHClient.strip_input_from_output, + cmd, + output) + # Missing exit prompt because output was cut + output = gen_output('setclienv csvtable 1', + cmd, + 'exit', + 'PROMPT% setclienv csvtable 1', + 'PROMPT% foo -v', + 'out1', + 'out2') self.assertRaises(exceptions.SSHException, ssh.HPE3PARSSHClient.strip_input_from_output, cmd, output) # success - output = [cmd, - 'setclienv csvtable 1', - 'exit', - 'PROMPT% setclienv csvtable 1', - 'PROMPT% foo -v', - 'out1', - 'out2', - 'out3', - '------', - 'totals'] + expected = ['out1', 'out2', 'out3', '------', 'totals'] + output = gen_output('setclienv csvtable 1', + cmd, + 'exit', + 'PROMPT% setclienv csvtable 1', + 'PROMPT% foo -v', + 'out1', + 'out2', + 'out3', + '------', + 'totals', + 'PROMPT% exit') + result = ssh.HPE3PARSSHClient.strip_input_from_output(cmd, output) + self.assertEqual(expected, result) + + # succeed even on slow connection that's missing the stdin dump + output = output[4:] + result = ssh.HPE3PARSSHClient.strip_input_from_output(cmd, output) + self.assertEqual(expected, result) + + # succeed with empty prompts and extra \r on commands + output = gen_output('setclienv csvtable 1', + cmd, + 'exit', + 'PROMPT% setclienv csvtable 1\r', + 'PROMPT% ', + 'PROMPT% foo -v\r', + 'out1', + 'out2', + 'out3', + '------', + 'totals', + 'PROMPT% exit\r') result = ssh.HPE3PARSSHClient.strip_input_from_output(cmd, output) - self.assertEqual(['out1', 'out2', 'out3'], result) + self.assertEqual(expected, result) @mock.patch('hpe3parclient.client.ssh.HPE3PARSSHClient', spec=True) def test_verify_get_port(self, mock_ssh_client):