Source code for stepler.third_party.ping
"""
----------------------
Pings checking helpers
----------------------
"""
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.from functools import wraps
import contextlib
import os
import re
import signal
import sys
import tempfile
import six
import waiting
if os.name == 'posix' and sys.version_info[0] < 3:
import subprocess32 as subprocess
else:
import subprocess
[docs]class PingResult(object):
"""Ping result class.
Useful for object-oriented access to results of ping (such as transmitted,
received, loss counts)
"""
_transmitted_count_re = r'(?P<count>\d+)(?: packets transmitted)'
_received_count_re = r'(?P<count>\d+)(?:( packets)? received)'
def __init__(self):
self._stdout = ''
@property
def transmitted(self):
result = re.search(self._transmitted_count_re, self.stdout)
if result is None:
raise ValueError('There is no transmitted count in `{}`'.format(
self.stdout))
return int(result.group('count'))
@property
def received(self):
result = re.search(self._received_count_re, self.stdout)
if result is None:
raise ValueError('There is no received count in `{}`'.format(
self.stdout))
return int(result.group('count'))
@property
def loss(self):
return self.transmitted - self.received
@property
def stdout(self):
return self._stdout
@stdout.setter
def stdout(self, value):
if isinstance(value, six.binary_type):
value = value.decode('utf-8')
self._stdout = value
[docs]class Pinger(object):
"""Pinger class to call ping and return result.
Can be used directly (as Pinger.ping) and as non-blocking context manager.
Example:
>>> with Pinger('10.109.8.2') as result:
... some_action()
>>> print(result.loss)
0
>>> result = Pinger('10.0.0.1', remote=remote).ping(count=3)
>>> print(result.loss)
0
"""
def __init__(self, ip_to_ping, remote=None):
"""Pinger constructor.
Args:
ip_to_ping (str): ip address to ping
remote (object): instance of stepler.third_party.ssh.SshClient
"""
self.remote = remote
self.ip_to_ping = ip_to_ping
if remote is not None:
self.executor = self._remote_ping
else:
self.executor = self._local_ping
def _prepare_cmd(self, count=None):
cmd = ['ping']
if count:
cmd.append('-c{}'.format(count))
cmd.append(self.ip_to_ping)
return cmd
# TODO(schipiga): seems, refactoring is required
@contextlib.contextmanager
def _remote_ping(self, count):
cmd = ' '.join(self._prepare_cmd(count))
output_file = tempfile.mktemp()
pid = self.remote.background_call(cmd, stdout=output_file)
result = PingResult()
yield result
if count:
waiting.wait(
lambda: not self.remote.execute('ps -o pid | grep {}'.format(
pid)).is_ok,
timeout_seconds=count * 10)
self.remote.execute('kill -SIGINT {}'.format(pid))
result.stdout = self.remote.check_call("cat {}".format(
output_file)).stdout
self.remote.execute('rm {}'.format(output_file))
# TODO(schipiga): seems, refactoring is required
@contextlib.contextmanager
def _local_ping(self, count):
cmd = self._prepare_cmd(count)
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# check that process is alive
if p.poll() is not None:
stdout, stderr = p.communicate()
raise Exception(
'Command {!r} unexpectedly exit with message {}'.format(
cmd, stdout, stderr))
result = PingResult()
yield result
if count:
p.wait()
# Check if process still alive
elif p.poll() is None:
p.send_signal(signal.SIGINT)
stdout, stderr = p.communicate()
result.stdout = stdout
[docs] def ping(self, count=1):
"""Start ping command and return result.
Args:
count (int): count of pings to send
Returns:
object: instance of PingResult
"""
with self.executor(count=count) as result:
return result
def __enter__(self):
self.cm = self.executor(count=None)
return self.cm.__enter__()
def __exit__(self, exc_type, exc_value, traceback):
self.cm.__exit__(exc_type, exc_value, traceback)
[docs]class FixedIDPinger(Pinger):
"""Pinger class to work with modified `ping` implementation.
Custom implementation allows to set ID for ICMP requests.
"""
def __init__(self, ip_to_ping, remote, icmp_id, command_path='ping'):
"""FixedIDPinger constructor.
Args:
ip_to_ping (str): ip address to ping
remote (object): instance of stepler.third_party.ssh.SshClient
icmp_id (int): desired ICMP id
command_path (str, optional): path to modified ping command
"""
super(FixedIDPinger, self).__init__(ip_to_ping, remote)
self.icmp_id = str(icmp_id)
self.command_path = command_path
def _prepare_cmd(self, count=None):
return [self.command_path, self.ip_to_ping, self.icmp_id]