trove/integration/tests/integration/tests/util/services.py

281 lines
9.3 KiB
Python

# Copyright (c) 2011 OpenStack, LLC.
# 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.
"""Functions to initiate and shut down services needed by the tests."""
import os
import re
import subprocess
import time
from collections import namedtuple
from httplib2 import Http
from nose.plugins.skip import SkipTest
from proboscis import decorators
def _is_web_service_alive(url):
"""Does a HTTP GET request to see if the web service is up."""
client = Http()
try:
resp = client.request(url, 'GET')
return resp != None
except Exception:
return False
_running_services = []
def get_running_services():
""" Returns the list of services which this program has started."""
return _running_services
def start_proc(cmd, shell=False):
"""Given a command, starts and returns a process."""
env = os.environ.copy()
proc = subprocess.Popen(
cmd,
shell=shell,
stdin=subprocess.PIPE,
bufsize=0,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env
)
return proc
MemoryInfo = namedtuple("MemoryInfo", ['mapped', 'writeable', 'shared'])
class Service(object):
"""Starts and stops a service under test.
The methods to start and stop the service will not actually do anything
if they detect the service is already running on this machine. This is
because it may be useful for developers to start the services themselves
some other way.
"""
# TODO(tim.simpson): Hard to follow, consider renaming certain attributes.
def __init__(self, cmd):
"""Defines a service to run."""
if not isinstance(cmd, list):
raise TypeError()
self.cmd = cmd
self.do_not_manage_proc = False
self.proc = None
def __del__(self):
if self.is_running:
self.stop()
def ensure_started(self):
"""Starts the service if it is not running."""
if not self.is_running:
self.start()
def find_proc_id(self):
"""Finds and returns the process id."""
if not self.cmd:
return False
# The cmd[1] signifies the executable python script. It gets invoked
# as python /path/to/executable args, so the entry is
# /path/to/executable
actual_command = self.cmd[1].split("/")[-1]
proc_command = ["/usr/bin/pgrep", "-f", actual_command]
proc = start_proc(proc_command, shell=False)
# this is to make sure there is only one pid returned from the pgrep
has_two_lines = False
pid = None
for line in iter(proc.stdout.readline, ""):
if has_two_lines:
raise RuntimeError("Found PID twice.")
pid = int(line)
has_two_lines = True
return pid
def get_memory_info(self):
"""Returns how much memory the process is using according to pmap."""
pid = self.find_proc_id()
if not pid:
raise RuntimeError("Can't find PID, so can't get memory.")
proc = start_proc(["/usr/bin/pmap", "-d", str(pid)],
shell=False)
for line in iter(proc.stdout.readline, ""):
m = re.search(r"mapped\:\s([0-9]+)K\s+"
r"writeable/private:\s([0-9]+)K\s+"
r"shared:\s+([0-9]+)K", line)
if m:
return MemoryInfo(int(m.group(1)), int(m.group(2)),
int(m.group(3)))
raise RuntimeError("Memory info not found.")
def get_fd_count_from_proc_file(self):
"""Returns file descriptors according to /proc/<id>/status."""
pid = self.find_proc_id()
with open("/proc/%d/status" % pid) as status:
for line in status.readlines():
index = line.find(":")
name = line[:index]
value = line[index + 1:]
if name == "FDSize":
return int(value)
raise RuntimeError("FDSize not found!")
def get_fd_count(self):
"""Returns file descriptors according to /proc/<id>/status."""
pid = self.find_proc_id()
cmd = "Finding file descriptors..."
print("CMD" + cmd)
proc = start_proc(['ls', '-la', '/proc/%d/fd' % pid], shell=False)
count = -3
has_two_lines = False
for line in iter(proc.stdout.readline, ""):
print("\t" + line)
count += 1
if not count:
raise RuntimeError("Could not get file descriptors!")
return count
with open("/proc/%d/fd" % pid) as status:
for line in status.readlines():
index = line.find(":")
name = line[:index]
value = line[index + 1:]
if name == "FDSize":
return int(value)
raise RuntimeError("FDSize not found!")
def kill_proc(self):
"""Kills the process, wherever it may be."""
pid = self.find_proc_id()
if pid:
start_proc("sudo kill -9 " + str(pid), shell=True)
time.sleep(1)
if self.is_service_alive():
raise RuntimeError('Cannot kill process, PID=' +
str(self.proc.pid))
def is_service_alive(self, proc_name_index=1):
"""Searches for the process to see if its alive.
This function will return true even if this class has not started
the service (searches using ps).
"""
if not self.cmd:
return False
time.sleep(1)
# The cmd[1] signifies the executable python script. It gets invoked
# as python /path/to/executable args, so the entry is
# /path/to/executable
actual_command = self.cmd[proc_name_index].split("/")[-1]
print(actual_command)
proc_command = ["/usr/bin/pgrep", "-f", actual_command]
print(proc_command)
proc = start_proc(proc_command, shell=False)
line = proc.stdout.readline()
print(line)
# pgrep only returns a pid. if there is no pid, it'll return nothing
return len(line) != 0
@property
def is_running(self):
"""Returns true if the service has already been started.
Returns true if this program has started the service or if it
previously detected it had started. The main use of this property
is to know if the service was already begun by this program-
use is_service_alive for a more definitive answer.
"""
return self.proc or self.do_not_manage_proc
def restart(self, extra_args):
if self.do_not_manage_proc:
raise RuntimeError("Can't restart proc as the tests don't own it.")
self.stop()
time.sleep(2)
self.start(extra_args=extra_args)
def start(self, time_out=30, extra_args=None):
"""Starts the service if necessary."""
extra_args = extra_args or []
if self.is_running:
raise RuntimeError("Process is already running.")
if self.is_service_alive():
self.do_not_manage_proc = True
return
self.proc = start_proc(self.cmd + extra_args, shell=False)
if not self._wait_for_start(time_out=time_out):
self.stop()
raise RuntimeError("Issued the command successfully but the "
"service (" + str(self.cmd + extra_args) +
") never seemed to start.")
_running_services.append(self)
def stop(self):
"""Stops the service, but only if this program started it."""
if self.do_not_manage_proc:
return
if not self.proc:
raise RuntimeError("Process was not started.")
self.proc.terminate()
self.proc.kill()
self.proc.wait()
self.proc.stdin.close()
self.kill_proc()
self.proc = None
global _running_services
_running_services = [svc for svc in _running_services if svc != self]
def _wait_for_start(self, time_out):
"""Waits until time_out (in seconds) for service to appear."""
give_up_time = time.time() + time_out
while time.time() < give_up_time:
if self.is_service_alive():
return True
return False
class NativeService(Service):
def is_service_alive(self):
return super(NativeService, self).is_service_alive(proc_name_index=0)
class WebService(Service):
"""Starts and stops a web service under test."""
def __init__(self, cmd, url):
"""Defines a service to run."""
Service.__init__(self, cmd)
if not isinstance(url, (str, unicode)):
raise TypeError()
self.url = url
self.do_not_manage_proc = self.is_service_alive()
def is_service_alive(self):
"""Searches for the process to see if its alive."""
return _is_web_service_alive(self.url)