blob: c48493295993b5d8e231b108ebacac10918e93c4 [file] [log] [blame]
Neels Hofmeyr6562c082017-10-18 03:20:04 +02001#!/usr/bin/env python3
2#
3# (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
4# All rights reserved.
5#
6# Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21'''
22Run VTY commands or test transcripts against a given application. Commandline
23invocation exposes only direct command piping, the transcript verification code
24is exposed as commandline args by osmo_verify_transcript_vty.py.
25'''
26
27import re
28
29from osmopy.osmo_interact_common import *
30
31class InteractVty(Interact):
32
33 class VtyStep(Interact.StepBase):
34 expect_node = None # e.g. '(config-net)'
35 expect_prompt_char = None # '>' or '#'
36
37 def __init__(self, prompt):
38 super().__init__()
39 self.prompt = prompt
40
41 def verify_interact_state(self, interact_instance):
42 if interact_instance.update:
43 return
44 if interact_instance.this_node != self.expect_node:
45 raise Exception('Mismatch: expected VTY node %r in the prompt, got %r'
46 % (self.expect_node, interact_instance.this_node))
47 if interact_instance.this_prompt_char != self.expect_prompt_char:
48 raise Exception('Mismatch: expected VTY prompt character %r, got %r'
49 % (self.expect_prompt_char, interact_instance.this_prompt_char))
50
51 @staticmethod
52 def is_next_step(line, interact_instance):
53 m = interact_instance.re_prompt.match(line)
54 if not m:
55 return None
56 next_step = InteractVty.VtyStep(interact_instance.prompt)
57 next_step.expect_node = m.group(1)
58 next_step.expect_prompt_char = m.group(2)
59 next_step.command = m.group(3)
60 return next_step
61
62 def command_str(self, interact_instance=None):
63 if interact_instance is None:
64 node = self.expect_node
65 prompt_char = self.expect_prompt_char
66 else:
67 node = interact_instance.last_node
68 prompt_char = interact_instance.last_prompt_char
69 if node:
70 node = '(%s)' % node
71 node = node or ''
72 return '%s%s%s %s' % (self.prompt, node, prompt_char, self.command)
73
74 def __init__(self, prompt, port, host, verbose, update):
75 self.prompt = prompt
76 super().__init__(InteractVty.VtyStep, port, host, verbose, update)
77
78 def connect(self):
79 self.this_node = None
80 self.this_prompt_char = '>' # slight cheat for initial prompt char
81 self.last_node = None
82 self.last_prompt_char = None
83
84 super().connect()
85 # receive the first welcome message and discard
86 data = self.socket.recv(4096)
87 if not self.prompt:
88 b = data
89 b = b[b.rfind(b'\n') + 1:]
90 while b and (b[0] < ord('A') or b[0] > ord('z')):
91 b = b[1:]
92 prompt_str = b.decode('utf-8')
93 if '>' in prompt_str:
94 self.prompt = prompt_str[:prompt_str.find('>')]
95 if not self.prompt:
96 raise Exception('Could not find application name; needed to decode prompts.'
97 ' Initial data was: %r' % data)
98 self.re_prompt = re.compile('^%s(?:\(([\w-]*)\))?([#>]) (.*)$' % self.prompt)
99
100 def _command(self, command_str, timeout=10):
101 self.socket.send(command_str.encode())
102
103 waited_since = time.time()
104 received_lines = []
105 last_line = ''
106
107 while True:
108 new_data = self.socket.recv(4096).decode('utf-8')
109
110 last_line = "%s%s" % (last_line, new_data)
111
112 if last_line:
113 lines = last_line.splitlines()
114 received_lines.extend(lines[:-1])
115 last_line = lines[-1]
116
117 match = self.re_prompt.match(last_line)
118 if not match:
119 if time.time() - waited_since > timeout:
120 raise IOError("Failed to read data (did the app crash?)")
121 time.sleep(.1)
122 continue
123
124 self.last_node = self.this_node
125 self.last_prompt_char = self.this_prompt_char
126 self.this_node = match.group(1) or None
127 self.this_prompt_char = match.group(2)
128 break
129
130 # expecting to have received the command we sent as echo, remove it
131 clean_command_str = command_str.strip()
132 if clean_command_str.endswith('?'):
133 clean_command_str = clean_command_str[:-1]
134 if received_lines and received_lines[0] == clean_command_str:
135 received_lines = received_lines[1:]
136 return received_lines
137
138 def command(self, command_str, timeout=10):
139 command_str = command_str or '\r'
140 if command_str[-1] not in '?\r\t':
141 command_str = command_str + '\r'
142
143 received_lines = self._command(command_str, timeout)
144
145 if command_str[-1] == '?':
146 self._command('\x03', timeout)
147
148 return received_lines
149
150def parser_add_vty_args(parser):
151 parser.add_argument('-n', '--prompt-name', dest='prompt',
152 help="Name used in application's telnet VTY prompt."
153 " If omitted, will attempt to determine the name from"
154 " the initial VTY prompt.")
155 return parser
156
157if __name__ == '__main__':
158 parser = common_parser()
159 parser_add_vty_args(parser)
160 parser_add_run_args(parser)
161 parser.add_argument('-X', '--gen-xml-ref', dest='gen_xml', action='store_true',
162 help="Equivalent to '-c \"show online-help\" -O -',"
163 " can be used to generate the VTY reference file as"
164 " required by osmo-gsm-manuals.git.")
165 args = parser.parse_args()
166
167 if args.gen_xml:
168 if args.cmd_str:
169 raise Exception('It does not make sense to pass both --command and'
170 ' --gen-xml-ref.')
171 args.cmd_str = 'show online-help'
172
173 interact = InteractVty(args.prompt, args.port, args.host,
174 verbose=False, update=False)
175
176 main_run_commands(args.run_app_str, args.output_path, args.cmd_str,
177 args.cmd_files, interact)
178
179# vim: tabstop=4 shiftwidth=4 expandtab nocin ai