2013-06-25 06:17:59 +00:00
|
|
|
# Copyright (C) 2012, 2013 Holger Hans Peter Freyther
|
2013-04-04 15:31:13 +00:00
|
|
|
# Copyright (C) 2013 Katerina Barone-Adesi
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#
|
|
|
|
# VTY helper code for OpenBSC
|
|
|
|
#
|
2013-06-25 06:17:59 +00:00
|
|
|
import re
|
2013-04-04 15:31:13 +00:00
|
|
|
import socket
|
2017-02-24 19:49:21 +00:00
|
|
|
import sys, subprocess
|
2017-02-26 23:58:19 +00:00
|
|
|
import os
|
2017-02-27 00:03:44 +00:00
|
|
|
import time
|
2013-04-04 15:31:13 +00:00
|
|
|
|
2013-04-05 15:06:30 +00:00
|
|
|
"""VTYInteract: interact with an osmocom vty
|
|
|
|
|
|
|
|
Specify a VTY to connect to, and run commands on it.
|
|
|
|
Connections will be reestablished as necessary.
|
|
|
|
Methods: __init__, command, enabled_command, verify, w_verify"""
|
|
|
|
|
2017-02-26 23:58:19 +00:00
|
|
|
debug_tcp_sockets = (os.getenv('OSMOPY_DEBUG_TCP_SOCKETS', '0') != '0')
|
2017-02-24 19:49:21 +00:00
|
|
|
|
|
|
|
def cmd(what):
|
2017-12-15 11:15:39 +00:00
|
|
|
print('\n> %s' % what)
|
2017-02-24 19:49:21 +00:00
|
|
|
sys.stdout.flush()
|
|
|
|
subprocess.call(what, shell=True)
|
|
|
|
sys.stdout.flush()
|
|
|
|
sys.stderr.flush()
|
2017-12-15 11:15:39 +00:00
|
|
|
print('')
|
2017-02-24 19:49:21 +00:00
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
def print_used_tcp_sockets():
|
2017-02-27 00:11:13 +00:00
|
|
|
global debug_tcp_sockets
|
2017-02-24 19:49:21 +00:00
|
|
|
if not debug_tcp_sockets:
|
|
|
|
return
|
2017-09-13 07:42:15 +00:00
|
|
|
cmd('ls -l /proc/' + str(os.getpid()) + '/fd');
|
2017-02-24 19:49:21 +00:00
|
|
|
cmd('ss -tn');
|
|
|
|
cmd('ss -tln');
|
|
|
|
cmd('ps xua | grep osmo');
|
2013-04-04 15:31:13 +00:00
|
|
|
|
|
|
|
class VTYInteract(object):
|
2013-04-05 15:06:30 +00:00
|
|
|
"""__init__(self, name, host, port):
|
|
|
|
|
|
|
|
name is the name the vty prints for commands, ie OpenBSC
|
|
|
|
host is the hostname to connect to
|
|
|
|
port is the port to connect on"""
|
2017-02-24 19:49:21 +00:00
|
|
|
|
|
|
|
all_sockets = []
|
|
|
|
|
2013-04-04 15:31:13 +00:00
|
|
|
def __init__(self, name, host, port):
|
2017-02-24 19:49:21 +00:00
|
|
|
print_used_tcp_sockets()
|
|
|
|
|
2013-04-04 15:31:13 +00:00
|
|
|
self.name = name
|
|
|
|
self.host = host
|
|
|
|
self.port = port
|
|
|
|
|
|
|
|
self.socket = None
|
2013-08-30 16:28:05 +00:00
|
|
|
self.norm_end = re.compile('\r\n%s(?:\(([\w-]*)\))?> $' % self.name)
|
|
|
|
self.priv_end = re.compile('\r\n%s(?:\(([\w-]*)\))?# $' % self.name)
|
|
|
|
self.last_node = ''
|
2013-04-04 15:31:13 +00:00
|
|
|
|
2017-02-27 00:31:02 +00:00
|
|
|
def _connect_socket(self):
|
|
|
|
if self.socket is not None:
|
|
|
|
return
|
2017-02-27 00:03:44 +00:00
|
|
|
retries = 30
|
|
|
|
took = 0
|
|
|
|
while True:
|
|
|
|
took += 1
|
|
|
|
try:
|
|
|
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
self.socket.setblocking(1)
|
|
|
|
self.socket.connect((self.host, self.port))
|
|
|
|
except IOError:
|
|
|
|
retries -= 1
|
|
|
|
if retries <= 0:
|
|
|
|
raise
|
|
|
|
# possibly the binary hasn't launched yet
|
|
|
|
if debug_tcp_sockets:
|
2017-12-15 11:15:39 +00:00
|
|
|
print("Connecting socket failed, retrying...")
|
2017-02-27 00:03:44 +00:00
|
|
|
time.sleep(.1)
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
|
2017-02-27 00:31:02 +00:00
|
|
|
if debug_tcp_sockets:
|
|
|
|
VTYInteract.all_sockets.append(self.socket)
|
2017-12-15 11:15:39 +00:00
|
|
|
print("Socket: in %d tries, connected to %s:%d %r (%d sockets open)" % (
|
2017-02-27 00:03:44 +00:00
|
|
|
took, self.host, self.port, self.socket,
|
2017-12-15 11:15:39 +00:00
|
|
|
len(VTYInteract.all_sockets)))
|
2017-02-27 00:31:02 +00:00
|
|
|
self.socket.recv(4096)
|
|
|
|
|
2013-04-04 15:31:13 +00:00
|
|
|
def _close_socket(self):
|
2017-02-27 00:11:13 +00:00
|
|
|
global debug_tcp_sockets
|
2017-02-27 00:12:14 +00:00
|
|
|
if self.socket is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
if debug_tcp_sockets:
|
2017-02-27 00:17:22 +00:00
|
|
|
try:
|
|
|
|
VTYInteract.all_sockets.remove(self.socket)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
2017-12-15 11:15:39 +00:00
|
|
|
print("Socket: closing %s:%d %r (%d sockets open)" % (
|
2017-02-27 00:12:14 +00:00
|
|
|
self.host, self.port, self.socket,
|
2017-12-15 11:15:39 +00:00
|
|
|
len(VTYInteract.all_sockets)))
|
2017-02-27 00:12:14 +00:00
|
|
|
self.socket.close()
|
|
|
|
self.socket = None
|
2013-04-04 15:31:13 +00:00
|
|
|
|
|
|
|
def _is_end(self, text, ends):
|
2013-06-25 06:17:59 +00:00
|
|
|
"""
|
|
|
|
>>> vty = VTYInteract('OsmoNAT', 'localhost', 9999)
|
|
|
|
>>> end = [vty.norm_end, vty.priv_end]
|
|
|
|
|
|
|
|
Simple test
|
|
|
|
>>> text1 = 'abc\\r\\nOsmoNAT> '
|
|
|
|
>>> vty._is_end(text1, end)
|
|
|
|
11
|
|
|
|
|
|
|
|
Simple test with the enabled node
|
|
|
|
>>> text2 = 'abc\\r\\nOsmoNAT# '
|
|
|
|
>>> vty._is_end(text2, end)
|
|
|
|
11
|
|
|
|
|
|
|
|
Now the more complicated one
|
|
|
|
>>> text3 = 'abc\\r\\nOsmoNAT(config)# '
|
|
|
|
>>> vty._is_end(text3, end)
|
|
|
|
19
|
|
|
|
|
|
|
|
Now the more complicated one
|
|
|
|
>>> text4 = 'abc\\r\\nOsmoNAT(config-nat)# '
|
|
|
|
>>> vty._is_end(text4, end)
|
|
|
|
23
|
|
|
|
|
|
|
|
Now the more complicated one
|
|
|
|
>>> text5 = 'abc\\r\\nmoo'
|
|
|
|
>>> vty._is_end(text5, end)
|
|
|
|
0
|
2013-08-30 16:28:05 +00:00
|
|
|
|
|
|
|
Check for node name extraction
|
|
|
|
>>> text6 = 'abc\\r\\nOsmoNAT(config-nat)# '
|
|
|
|
>>> vty._is_end(text6, end)
|
|
|
|
23
|
|
|
|
>>> vty.node()
|
|
|
|
'config-nat'
|
|
|
|
|
|
|
|
Check for empty node name extraction
|
|
|
|
>>> text7 = 'abc\\r\\nOsmoNAT# '
|
|
|
|
>>> vty._is_end(text7, end)
|
|
|
|
11
|
|
|
|
>>> vty.node() is None
|
|
|
|
True
|
|
|
|
|
2013-06-25 06:17:59 +00:00
|
|
|
"""
|
2013-08-30 16:28:05 +00:00
|
|
|
self.last_node = None
|
2013-04-04 15:31:13 +00:00
|
|
|
for end in ends:
|
2013-06-25 06:17:59 +00:00
|
|
|
match = end.search(text)
|
|
|
|
if match:
|
2013-08-30 16:28:05 +00:00
|
|
|
self.last_node = match.group(1)
|
2013-06-25 06:17:59 +00:00
|
|
|
return match.end() - match.start()
|
|
|
|
return 0
|
2013-04-04 15:31:13 +00:00
|
|
|
|
|
|
|
def _common_command(self, request, close=False, ends=None):
|
2017-02-27 00:11:13 +00:00
|
|
|
global debug_tcp_sockets
|
2013-04-04 15:31:13 +00:00
|
|
|
if not ends:
|
|
|
|
ends = [self.norm_end, self.priv_end]
|
2017-02-27 00:31:02 +00:00
|
|
|
|
|
|
|
self._connect_socket()
|
2013-04-04 15:31:13 +00:00
|
|
|
|
|
|
|
# Now send the command
|
Drop python2 support / make it work with python3
Re-apply reverted commit Iabda95073faa2191fd117e9637e0858c589e9d9e
("Drop python2 support"), but with additional changes to make the
scripts actually work with python3 and to make it build without python2.
I have verified, that the contrib/jenkins.sh scripts of all Osmocom
repositories (with their python3 patches on top) are working with this
patch and that all Osmocom repositories with the python3 patches build
in OBS (tested in own namespace).
New fixes:
* osmopy/obscvty.py: verify: fix compare
Comparing maps in python3 does not work the same as in python2. Convert
them to lists first, so the compare works as intended again.
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestvty.py", line 57, in test_history
assert(self.vty.w_verify(test_str, [t1]))
AssertionError
* osmopy/obscvty.py: use enc/dec with send/recv
Fix error:
self.socket.send("%s\r" % request)
TypeError: a bytes-like object is required, not 'str'
* scripts/osmotestconfig.py: use encode() before writing to file
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestconfig.py", line 91, in copy_config
tmpfile.write(open(config).read())
File "/usr/lib/python3.5/tempfile.py", line 622, in func_wrapper
return func(*args, **kwargs)
TypeError: a bytes-like object is required, not 'str'
* debian/control: add --buildsystem=pybuild. Otherwise "--with python3"
is ignored and the build fails if python2 is not installed, with:
Can't exec "pyversions": No such file or directory at /usr/[...]/python_distutils.pm line 120.
Related: OS#2819
Change-Id: I3ffc3519bf6c22536a49dad7a966188ddad351a7
2019-12-09 13:41:14 +00:00
|
|
|
self.socket.send(("%s\r" % request).encode())
|
2013-04-04 15:31:13 +00:00
|
|
|
res = ""
|
|
|
|
end = ""
|
|
|
|
|
|
|
|
# Unfortunately, timeout and recv don't always play nicely
|
|
|
|
while True:
|
Drop python2 support / make it work with python3
Re-apply reverted commit Iabda95073faa2191fd117e9637e0858c589e9d9e
("Drop python2 support"), but with additional changes to make the
scripts actually work with python3 and to make it build without python2.
I have verified, that the contrib/jenkins.sh scripts of all Osmocom
repositories (with their python3 patches on top) are working with this
patch and that all Osmocom repositories with the python3 patches build
in OBS (tested in own namespace).
New fixes:
* osmopy/obscvty.py: verify: fix compare
Comparing maps in python3 does not work the same as in python2. Convert
them to lists first, so the compare works as intended again.
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestvty.py", line 57, in test_history
assert(self.vty.w_verify(test_str, [t1]))
AssertionError
* osmopy/obscvty.py: use enc/dec with send/recv
Fix error:
self.socket.send("%s\r" % request)
TypeError: a bytes-like object is required, not 'str'
* scripts/osmotestconfig.py: use encode() before writing to file
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestconfig.py", line 91, in copy_config
tmpfile.write(open(config).read())
File "/usr/lib/python3.5/tempfile.py", line 622, in func_wrapper
return func(*args, **kwargs)
TypeError: a bytes-like object is required, not 'str'
* debian/control: add --buildsystem=pybuild. Otherwise "--with python3"
is ignored and the build fails if python2 is not installed, with:
Can't exec "pyversions": No such file or directory at /usr/[...]/python_distutils.pm line 120.
Related: OS#2819
Change-Id: I3ffc3519bf6c22536a49dad7a966188ddad351a7
2019-12-09 13:41:14 +00:00
|
|
|
data = self.socket.recv(4096).decode()
|
2013-04-04 15:31:13 +00:00
|
|
|
res = "%s%s" % (res, data)
|
|
|
|
if not res: # yes, this is ugly
|
|
|
|
raise IOError("Failed to read data (did the app crash?)")
|
|
|
|
end = self._is_end(res, ends)
|
2013-06-25 06:17:59 +00:00
|
|
|
if end > 0:
|
2013-04-04 15:31:13 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
if close:
|
|
|
|
self._close_socket()
|
2013-06-25 06:17:59 +00:00
|
|
|
return res[len(request) + 2: -end]
|
2013-04-04 15:31:13 +00:00
|
|
|
|
2015-05-30 14:07:53 +00:00
|
|
|
"""A generator function yielding lines separated by delim.
|
|
|
|
Behaves similar to a file readlines() method.
|
|
|
|
|
|
|
|
Example of use:
|
|
|
|
for line in vty.readlines():
|
|
|
|
print line
|
|
|
|
"""
|
|
|
|
def readlines(self, recv_buffer=4096, delim='\n'):
|
|
|
|
buffer = ''
|
|
|
|
data = True
|
|
|
|
while data:
|
Drop python2 support / make it work with python3
Re-apply reverted commit Iabda95073faa2191fd117e9637e0858c589e9d9e
("Drop python2 support"), but with additional changes to make the
scripts actually work with python3 and to make it build without python2.
I have verified, that the contrib/jenkins.sh scripts of all Osmocom
repositories (with their python3 patches on top) are working with this
patch and that all Osmocom repositories with the python3 patches build
in OBS (tested in own namespace).
New fixes:
* osmopy/obscvty.py: verify: fix compare
Comparing maps in python3 does not work the same as in python2. Convert
them to lists first, so the compare works as intended again.
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestvty.py", line 57, in test_history
assert(self.vty.w_verify(test_str, [t1]))
AssertionError
* osmopy/obscvty.py: use enc/dec with send/recv
Fix error:
self.socket.send("%s\r" % request)
TypeError: a bytes-like object is required, not 'str'
* scripts/osmotestconfig.py: use encode() before writing to file
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestconfig.py", line 91, in copy_config
tmpfile.write(open(config).read())
File "/usr/lib/python3.5/tempfile.py", line 622, in func_wrapper
return func(*args, **kwargs)
TypeError: a bytes-like object is required, not 'str'
* debian/control: add --buildsystem=pybuild. Otherwise "--with python3"
is ignored and the build fails if python2 is not installed, with:
Can't exec "pyversions": No such file or directory at /usr/[...]/python_distutils.pm line 120.
Related: OS#2819
Change-Id: I3ffc3519bf6c22536a49dad7a966188ddad351a7
2019-12-09 13:41:14 +00:00
|
|
|
data = self.socket.recv(recv_buffer).decode()
|
2015-05-30 14:07:53 +00:00
|
|
|
buffer += data
|
|
|
|
|
|
|
|
while buffer.find(delim) != -1:
|
|
|
|
line, buffer = buffer.split('\n', 1)
|
|
|
|
yield line
|
|
|
|
return
|
|
|
|
|
2013-04-04 15:31:13 +00:00
|
|
|
# There's no close parameter, as close=True makes this useless
|
|
|
|
def enable(self):
|
|
|
|
self.command("enable")
|
|
|
|
|
|
|
|
"""Run a command on the vty"""
|
2013-04-05 15:06:30 +00:00
|
|
|
|
2013-04-04 15:31:13 +00:00
|
|
|
def command(self, request, close=False):
|
|
|
|
return self._common_command(request, close)
|
|
|
|
|
|
|
|
"""Run enable, followed by another command"""
|
|
|
|
def enabled_command(self, request, close=False):
|
|
|
|
self.enable()
|
|
|
|
return self._common_command(request, close)
|
|
|
|
|
|
|
|
"""Verify, ignoring leading/trailing whitespace"""
|
|
|
|
# inspired by diff -w, though not identical
|
|
|
|
def w_verify(self, command, results, close=False, loud=True):
|
|
|
|
return self.verify(command, results, close, loud, lambda x: x.strip())
|
|
|
|
|
|
|
|
"""Verify that a command has the expected results
|
|
|
|
|
|
|
|
command = the command to verify
|
|
|
|
results = the expected results [line1, line2, ...]
|
|
|
|
close = True to close the socket after running the verify
|
|
|
|
loud = True to show what was expected and what actually happend, stdout
|
|
|
|
f = A function to run over the expected and actual results, before compare
|
|
|
|
|
|
|
|
Returns True iff the expected and actual results match"""
|
|
|
|
def verify(self, command, results, close=False, loud=True, f=None):
|
|
|
|
res = self.command(command, close).split('\r\n')
|
|
|
|
if f:
|
Drop python2 support / make it work with python3
Re-apply reverted commit Iabda95073faa2191fd117e9637e0858c589e9d9e
("Drop python2 support"), but with additional changes to make the
scripts actually work with python3 and to make it build without python2.
I have verified, that the contrib/jenkins.sh scripts of all Osmocom
repositories (with their python3 patches on top) are working with this
patch and that all Osmocom repositories with the python3 patches build
in OBS (tested in own namespace).
New fixes:
* osmopy/obscvty.py: verify: fix compare
Comparing maps in python3 does not work the same as in python2. Convert
them to lists first, so the compare works as intended again.
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestvty.py", line 57, in test_history
assert(self.vty.w_verify(test_str, [t1]))
AssertionError
* osmopy/obscvty.py: use enc/dec with send/recv
Fix error:
self.socket.send("%s\r" % request)
TypeError: a bytes-like object is required, not 'str'
* scripts/osmotestconfig.py: use encode() before writing to file
Fix error:
File "/home/user/code/osmo-dev/src/osmo-python-tests/scripts/osmotestconfig.py", line 91, in copy_config
tmpfile.write(open(config).read())
File "/usr/lib/python3.5/tempfile.py", line 622, in func_wrapper
return func(*args, **kwargs)
TypeError: a bytes-like object is required, not 'str'
* debian/control: add --buildsystem=pybuild. Otherwise "--with python3"
is ignored and the build fails if python2 is not installed, with:
Can't exec "pyversions": No such file or directory at /usr/[...]/python_distutils.pm line 120.
Related: OS#2819
Change-Id: I3ffc3519bf6c22536a49dad7a966188ddad351a7
2019-12-09 13:41:14 +00:00
|
|
|
res = list(map(f, res))
|
|
|
|
results = list(map(f, results))
|
2013-04-04 15:31:13 +00:00
|
|
|
|
|
|
|
if loud:
|
|
|
|
if res != results:
|
2017-12-15 11:15:39 +00:00
|
|
|
print("Rec: %s\nExp: %s" % (res, results))
|
2013-04-04 15:31:13 +00:00
|
|
|
|
|
|
|
return res == results
|
2013-06-25 06:17:59 +00:00
|
|
|
|
2013-08-30 16:28:05 +00:00
|
|
|
def node(self):
|
|
|
|
return self.last_node
|
|
|
|
|
2013-06-25 06:17:59 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
import doctest
|
|
|
|
doctest.testmod()
|