blob: ec1a4c8a22025a55ed31910dd0afe1dc85a770b3 [file] [log] [blame]
# Copyright 2022 OpenStack Foundation
# All Rights Reserved.
#
# 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.
import signal
import subprocess
import fixtures
from oslo_log import log
LOG = log.getLogger(__name__)
PASSED = 'PASSED'
FAILED = 'FAILED'
METADATA_SCRIPT_PATH = '/tmp/metadata_meter_script.sh'
METADATA_RESULTS_PATH = '/tmp/metadata_meter.log'
METADATA_PID_PATH = '/tmp/metadata_meter.pid'
# /proc/uptime is used because it include two decimals in cirros, while
# `date +%s.%N` does not work in cirros (min granularity is seconds)
METADATA_SCRIPT = """#!/bin/sh
echo $$ > %(metadata_meter_pidfile)s
old_time=$(cut -d" " -f1 /proc/uptime)
while true; do
curl http://169.254.169.254/latest/meta-data/hostname 2>/dev/null | \
grep -q `hostname`
result=$?
new_time=$(cut -d" " -f1 /proc/uptime)
runtime=$(awk -v new=$new_time -v old=$old_time "BEGIN {print new-old}")
old_time=$new_time
if [ $result -eq 0 ]; then
echo "PASSED $runtime"
else
echo "FAILED $runtime"
fi
sleep %(interval)s
done
"""
class NetDowntimeMeter(fixtures.Fixture):
def __init__(self, dest_ip, interval=0.2):
self.dest_ip = dest_ip
# Note: for intervals lower than 0.2 ping requires root privileges
self.interval = float(interval)
self.ping_process = None
def _setUp(self):
self.start_background_pinger()
def start_background_pinger(self):
cmd = ['ping', '-q', '-s1']
cmd.append('-i%g' % self.interval)
cmd.append(self.dest_ip)
LOG.debug("Starting background pinger to '%s' with interval %g",
self.dest_ip, self.interval)
self.ping_process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.addCleanup(self.cleanup)
def cleanup(self):
if self.ping_process and self.ping_process.poll() is None:
LOG.debug('Terminating background pinger with pid %d',
self.ping_process.pid)
self.ping_process.terminate()
self.ping_process = None
def get_downtime(self):
self.ping_process.send_signal(signal.SIGQUIT)
# Example of the expected output:
# 264/274 packets, 3% loss
output = self.ping_process.stderr.readline().strip().decode('utf-8')
if output and len(output.split()[0].split('/')) == 2:
succ, total = output.split()[0].split('/')
return (int(total) - int(succ)) * self.interval
else:
LOG.warning('Unexpected output obtained from the pinger: %s',
output)
class MetadataDowntimeMeter(fixtures.Fixture):
def __init__(self, ssh_client,
interval='0.2', script_path=METADATA_SCRIPT_PATH,
output_path=METADATA_RESULTS_PATH,
pidfile_path=METADATA_PID_PATH):
self.ssh_client = ssh_client
self.interval = interval
self.script_path = script_path
self.output_path = output_path
self.pidfile_path = pidfile_path
self.pid = None
def _setUp(self):
self.addCleanup(self.cleanup)
self.upload_metadata_script()
self.run_metadata_script()
def upload_metadata_script(self):
metadata_script = METADATA_SCRIPT % {
'metadata_meter_pidfile': self.pidfile_path,
'interval': self.interval}
echo_cmd = "echo '{}' > {}".format(
metadata_script, self.script_path)
chmod_cmd = 'chmod +x {}'.format(self.script_path)
self.ssh_client.exec_command(';'.join((echo_cmd, chmod_cmd)))
LOG.debug('script created: %s', self.script_path)
output = self.ssh_client.exec_command(
'cat {}'.format(self.script_path))
LOG.debug('script content: %s', output)
def run_metadata_script(self):
self.ssh_client.exec_command('{} > {} &'.format(self.script_path,
self.output_path))
self.pid = self.ssh_client.exec_command(
'cat {}'.format(self.pidfile_path)).strip()
LOG.debug('running metadata downtime meter script in background with '
'PID = %s', self.pid)
def get_results(self):
output = self.ssh_client.exec_command(
'cat {}'.format(self.output_path))
results = {}
results['successes'] = output.count(PASSED)
results['failures'] = output.count(FAILED)
downtime = {PASSED: 0.0, FAILED: 0.0}
for line in output.splitlines():
key, value = line.strip().split()
downtime[key] += float(value)
results['downtime'] = downtime
LOG.debug('metadata downtime meter results: %r', results)
return results
def cleanup(self):
if self.pid:
self.ssh_client.exec_command('kill {}'.format(self.pid))
LOG.debug('killed metadata downtime script with PID %s', self.pid)
else:
LOG.debug('No metadata downtime script found')